@remnic/core 9.22.0 → 9.22.2

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 (402) hide show
  1. package/dist/access-admin-ops-surface.d.ts +15 -12
  2. package/dist/access-admin-ops-surface.js +25 -24
  3. package/dist/access-audit.js +2 -2
  4. package/dist/access-boundary.d.ts +13 -10
  5. package/dist/access-boundary.js +25 -24
  6. package/dist/access-cli.js +84 -82
  7. package/dist/access-cli.js.map +1 -1
  8. package/dist/access-http.d.ts +16 -9
  9. package/dist/access-http.js +33 -32
  10. package/dist/access-identity-continuity-surface.d.ts +9 -7
  11. package/dist/access-identity-continuity-surface.js +25 -24
  12. package/dist/access-lcm-surface.d.ts +12 -9
  13. package/dist/access-lcm-surface.js +25 -24
  14. package/dist/access-mcp-output-schemas.js +1 -1
  15. package/dist/access-mcp.d.ts +22 -10
  16. package/dist/access-mcp.js +31 -30
  17. package/dist/access-memory-search-fanout.d.ts +2 -2
  18. package/dist/{access-namespace-preflight-tmwQgCT6.d.ts → access-namespace-preflight-CmErR03h.d.ts} +1 -1
  19. package/dist/access-namespace-preflight.d.ts +3 -3
  20. package/dist/access-namespace-preflight.js +25 -24
  21. package/dist/access-observe-write-surface.d.ts +12 -9
  22. package/dist/access-observe-write-surface.js +25 -24
  23. package/dist/access-operations-batch.js +27 -26
  24. package/dist/access-operations.d.ts +16 -13
  25. package/dist/access-operations.js +28 -27
  26. package/dist/access-recall-concurrency.d.ts +12 -9
  27. package/dist/access-recall-concurrency.js +25 -24
  28. package/dist/access-recall-response.d.ts +12 -9
  29. package/dist/access-recall-response.js +25 -24
  30. package/dist/access-recall-surface.d.ts +14 -11
  31. package/dist/access-recall-surface.js +25 -24
  32. package/dist/access-schema.d.ts +76 -76
  33. package/dist/{access-service-BtxW52vv.d.ts → access-service-CKEROeFP.d.ts} +22 -11
  34. package/dist/access-service.d.ts +13 -10
  35. package/dist/access-service.js +25 -24
  36. package/dist/access-surface-catalog.d.ts +12 -9
  37. package/dist/access-surface-catalog.js +6 -0
  38. package/dist/access-surface-catalog.js.map +1 -1
  39. package/dist/access-wearables-meetings-surface.d.ts +78 -0
  40. package/dist/access-wearables-meetings-surface.js +22 -0
  41. package/dist/access-wearables-meetings-surface.js.map +1 -0
  42. package/dist/action-confidence.d.ts +1 -1
  43. package/dist/active-memory-bridge.d.ts +1 -1
  44. package/dist/active-recall.d.ts +1 -1
  45. package/dist/active-recall.js +2 -2
  46. package/dist/{auto-sync-UPTUVE4X.js → auto-sync-5OE5QOZB.js} +3 -3
  47. package/dist/behavior-learner.d.ts +1 -1
  48. package/dist/behavior-signals.d.ts +1 -1
  49. package/dist/bootstrap.d.ts +9 -7
  50. package/dist/briefing.d.ts +2 -2
  51. package/dist/briefing.js +5 -5
  52. package/dist/buffer-surprise-report.d.ts +1 -1
  53. package/dist/buffer.d.ts +2 -2
  54. package/dist/bulk-import/index.d.ts +3 -3
  55. package/dist/calibration.d.ts +1 -1
  56. package/dist/capabilities.d.ts +1 -1
  57. package/dist/{catalog-iWvWz61I.d.ts → catalog-BFTVo2Ez.d.ts} +1 -1
  58. package/dist/causal-behavior.d.ts +1 -1
  59. package/dist/causal-behavior.js +2 -2
  60. package/dist/causal-chain.js +2 -2
  61. package/dist/causal-consolidation.d.ts +1 -1
  62. package/dist/causal-consolidation.js +9 -9
  63. package/dist/causal-retrieval.js +2 -2
  64. package/dist/causal-trajectory-graph.d.ts +1 -1
  65. package/dist/causal-trajectory.js +1 -1
  66. package/dist/{chunk-GF22HWS2.js → chunk-2PHX3I5C.js} +7 -7
  67. package/dist/{chunk-JFDEYXF7.js → chunk-3NAHZQU3.js} +2 -2
  68. package/dist/{chunk-XBRPEGXF.js → chunk-3UOSVTFR.js} +5 -5
  69. package/dist/{chunk-TTKWTT72.js → chunk-3ZW6ZQO6.js} +3 -3
  70. package/dist/{chunk-UV72DEZD.js → chunk-4B77XSG5.js} +5 -5
  71. package/dist/{chunk-IC2TCZJ2.js → chunk-57QKLRBV.js} +3 -1
  72. package/dist/chunk-57QKLRBV.js.map +1 -0
  73. package/dist/{chunk-LMHDFOVV.js → chunk-5CPI2QWG.js} +3 -3
  74. package/dist/{chunk-P72WZMWQ.js → chunk-5FZQ74K5.js} +2 -2
  75. package/dist/{chunk-BRT7KEXU.js → chunk-6TWXQ4E2.js} +5 -5
  76. package/dist/{chunk-CV6ZYXK3.js → chunk-7I7AYIUQ.js} +3 -3
  77. package/dist/{chunk-TVVEYCNW.js → chunk-7K5Q6COX.js} +4 -4
  78. package/dist/{chunk-MONWCSVL.js → chunk-7RAJ3GXO.js} +2 -1
  79. package/dist/chunk-7RAJ3GXO.js.map +1 -0
  80. package/dist/{chunk-SQEDDWAM.js → chunk-A2RFKL3V.js} +5 -34
  81. package/dist/chunk-A2RFKL3V.js.map +1 -0
  82. package/dist/{chunk-JN57HZCD.js → chunk-ATEAL5EI.js} +6 -6
  83. package/dist/{chunk-4PYLBXX6.js → chunk-AYAROCWD.js} +2 -2
  84. package/dist/chunk-BLDBT6HZ.js +39 -0
  85. package/dist/chunk-BLDBT6HZ.js.map +1 -0
  86. package/dist/{chunk-NB2P47N7.js → chunk-CUUY5CC4.js} +6 -6
  87. package/dist/{chunk-KL5Y6PEV.js → chunk-E3ZMFFQZ.js} +2 -2
  88. package/dist/{chunk-55FDQYPS.js → chunk-ES5IHQ6M.js} +2 -2
  89. package/dist/{chunk-N47ZWQBO.js → chunk-G2NSSQES.js} +2 -2
  90. package/dist/{chunk-GSWPU5CR.js → chunk-GD3QOBMB.js} +2 -2
  91. package/dist/{chunk-6ANPNUVL.js → chunk-IIXLB5E7.js} +58 -36
  92. package/dist/chunk-IIXLB5E7.js.map +1 -0
  93. package/dist/{chunk-SACPRJ7F.js → chunk-JNIJBXVK.js} +2 -2
  94. package/dist/{chunk-W3XT32W7.js → chunk-JVGUY2AU.js} +23 -13
  95. package/dist/chunk-JVGUY2AU.js.map +1 -0
  96. package/dist/{chunk-DGLON3AL.js → chunk-KNRG6ZGJ.js} +6 -6
  97. package/dist/{chunk-GEUDRENF.js → chunk-MMTG3SCJ.js} +2 -2
  98. package/dist/{chunk-WZFAPHIK.js → chunk-MU7ZHSZE.js} +2 -2
  99. package/dist/{chunk-W4FBGYGK.js → chunk-NRV4Y3PM.js} +1 -1
  100. package/dist/{chunk-QTO2FGBT.js → chunk-OUW4PAY4.js} +191 -117
  101. package/dist/chunk-OUW4PAY4.js.map +1 -0
  102. package/dist/{chunk-KQ2XCQYN.js → chunk-P2BLHUL2.js} +2 -2
  103. package/dist/{chunk-ACGGTPU4.js → chunk-QCKQEBIY.js} +17 -9
  104. package/dist/chunk-QCKQEBIY.js.map +1 -0
  105. package/dist/{chunk-6L775BUB.js → chunk-QE63OHLB.js} +2 -2
  106. package/dist/{chunk-ONHRRJZM.js → chunk-QJAQNXWI.js} +6 -6
  107. package/dist/{chunk-EQYBHXRR.js → chunk-ROUL7RZU.js} +385 -59
  108. package/dist/chunk-ROUL7RZU.js.map +1 -0
  109. package/dist/{chunk-N6M7MRVN.js → chunk-RT4TSR2C.js} +9034 -8726
  110. package/dist/chunk-RT4TSR2C.js.map +1 -0
  111. package/dist/{chunk-6NZ7U4R4.js → chunk-SCOT5DTA.js} +1 -1
  112. package/dist/chunk-SCOT5DTA.js.map +1 -0
  113. package/dist/{chunk-6RYWNBSA.js → chunk-SVZGA2MK.js} +2 -2
  114. package/dist/chunk-SYOIVK7K.js +63 -0
  115. package/dist/chunk-SYOIVK7K.js.map +1 -0
  116. package/dist/{chunk-DM5A4ZFL.js → chunk-TWCOYET2.js} +11 -1
  117. package/dist/chunk-TWCOYET2.js.map +1 -0
  118. package/dist/{chunk-7S5OMI76.js → chunk-WS32QRLO.js} +109 -44
  119. package/dist/chunk-WS32QRLO.js.map +1 -0
  120. package/dist/{chunk-OCMLGND7.js → chunk-WWZ35GWW.js} +5 -5
  121. package/dist/{chunk-QMGIQUBG.js → chunk-XWPFZX6S.js} +503 -139
  122. package/dist/chunk-XWPFZX6S.js.map +1 -0
  123. package/dist/{chunk-A5N6ISRK.js → chunk-ZN4UZWMT.js} +2 -2
  124. package/dist/{chunk-7M6APKJL.js → chunk-ZYU7ZFLT.js} +2 -2
  125. package/dist/{cli-D9vXgWwM.d.ts → cli-BrwT9YMD.d.ts} +5 -5
  126. package/dist/cli.d.ts +14 -11
  127. package/dist/cli.js +57 -56
  128. package/dist/compounding/engine.d.ts +2 -2
  129. package/dist/compounding/engine.js +5 -5
  130. package/dist/compounding/preference-consolidator.d.ts +1 -1
  131. package/dist/compression-optimizer.d.ts +1 -1
  132. package/dist/config.d.ts +1 -1
  133. package/dist/config.js +2 -2
  134. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  135. package/dist/connectors/codex-materialize-runner.js +5 -5
  136. package/dist/connectors/codex-materialize.d.ts +1 -1
  137. package/dist/connectors/index.d.ts +1 -1
  138. package/dist/connectors/index.js +5 -5
  139. package/dist/consolidation-provenance-check.d.ts +2 -2
  140. package/dist/consolidation-undo.d.ts +2 -2
  141. package/dist/contradiction/index.d.ts +2 -2
  142. package/dist/conversation-index/backend.d.ts +1 -1
  143. package/dist/conversation-index/chunker.d.ts +1 -1
  144. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  145. package/dist/conversation-index/indexer.d.ts +1 -1
  146. package/dist/conversation-index/search.d.ts +1 -1
  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 +5 -5
  155. package/dist/entity-schema.d.ts +1 -1
  156. package/dist/explicit-capture.d.ts +9 -7
  157. package/dist/extraction-error-classification.d.ts +22 -0
  158. package/dist/extraction-error-classification.js +11 -0
  159. package/dist/extraction-error-classification.js.map +1 -0
  160. package/dist/extraction-faithfulness.d.ts +1 -1
  161. package/dist/extraction-judge-telemetry.d.ts +1 -1
  162. package/dist/extraction-judge-training.d.ts +1 -1
  163. package/dist/extraction-judge.d.ts +1 -1
  164. package/dist/extraction.d.ts +3 -17
  165. package/dist/extraction.js +5 -3
  166. package/dist/fallback-llm.d.ts +1 -1
  167. package/dist/{first-start-migration-LEXL3XYL.js → first-start-migration-BKNGOFED.js} +4 -4
  168. package/dist/graph-dashboard-diff.d.ts +1 -1
  169. package/dist/graph-dashboard-key.d.ts +1 -1
  170. package/dist/graph-dashboard-parser.d.ts +1 -1
  171. package/dist/graph-edge-reinforcement.d.ts +1 -1
  172. package/dist/graph-snapshot.d.ts +1 -1
  173. package/dist/graph.d.ts +1 -1
  174. package/dist/importance.d.ts +1 -1
  175. package/dist/importers/index.d.ts +1 -1
  176. package/dist/in-flight-reads.d.ts +1 -1
  177. package/dist/index.d.ts +713 -923
  178. package/dist/index.js +176 -140
  179. package/dist/intent.d.ts +1 -1
  180. package/dist/lcm/engine.d.ts +1 -1
  181. package/dist/lcm/engine.js +3 -3
  182. package/dist/lcm/index.d.ts +1 -1
  183. package/dist/lcm/index.js +11 -11
  184. package/dist/lcm/tools.d.ts +1 -1
  185. package/dist/lifecycle.d.ts +1 -1
  186. package/dist/live-connectors-runner.d.ts +1 -1
  187. package/dist/local-llm.d.ts +1 -1
  188. package/dist/local-model-endpoint.d.ts +1 -1
  189. package/dist/maintenance/memory-governance.d.ts +1 -1
  190. package/dist/maintenance/memory-governance.js +6 -6
  191. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
  192. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +5 -5
  193. package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
  194. package/dist/maintenance/rebuild-memory-projection.js +7 -7
  195. package/dist/maintenance-C98njYb8.d.ts +366 -0
  196. package/dist/mcp-memory-inspector-app.d.ts +12 -9
  197. package/dist/mcp-read-only-tools.js +1 -1
  198. package/dist/memory-action-policy.d.ts +1 -1
  199. package/dist/memory-cache.d.ts +1 -1
  200. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  201. package/dist/memory-projection-store.d.ts +1 -1
  202. package/dist/memory-provenance.d.ts +1 -1
  203. package/dist/memory-worth-outcomes.d.ts +2 -2
  204. package/dist/models-json.d.ts +1 -1
  205. package/dist/namespaces/migrate.d.ts +3 -3
  206. package/dist/namespaces/migrate.js +12 -12
  207. package/dist/namespaces/principal.d.ts +1 -1
  208. package/dist/namespaces/search.d.ts +1 -1
  209. package/dist/namespaces/search.js +6 -6
  210. package/dist/namespaces/storage.d.ts +3 -3
  211. package/dist/namespaces/storage.js +5 -5
  212. package/dist/native-knowledge.d.ts +1 -1
  213. package/dist/offline-sync-impression-drain.d.ts +1 -1
  214. package/dist/operator-toolkit.d.ts +2 -2
  215. package/dist/operator-toolkit.js +18 -18
  216. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  217. package/dist/orchestration/maintenance.d.ts +6 -223
  218. package/dist/orchestration/maintenance.js +12 -12
  219. package/dist/{orchestrator-DB93S48I.d.ts → orchestrator-C9ZVppNh.d.ts} +12 -481
  220. package/dist/orchestrator.d.ts +10 -8
  221. package/dist/orchestrator.js +84 -82
  222. package/dist/patterns-cli.d.ts +1 -1
  223. package/dist/{pipeline-DmGc-H66.d.ts → pipeline-id_-sqYm.d.ts} +1 -1
  224. package/dist/policy-runtime.d.ts +1 -1
  225. package/dist/proactive-contention.d.ts +1 -1
  226. package/dist/provenance.d.ts +1 -1
  227. package/dist/{qmd-cJT0crkF.d.ts → qmd-R6XaQOZK.d.ts} +1 -1
  228. package/dist/qmd-preflight.d.ts +2 -2
  229. package/dist/qmd-recall-cache.d.ts +1 -1
  230. package/dist/qmd.d.ts +2 -2
  231. package/dist/qmd.js +4 -4
  232. package/dist/recall-concurrency-config.d.ts +1 -1
  233. package/dist/recall-disclosure-escalation.d.ts +1 -1
  234. package/dist/recall-explain-renderer.d.ts +1 -1
  235. package/dist/recall-explain-renderer.js +3 -3
  236. package/dist/recall-memory-map.d.ts +1 -1
  237. package/dist/recall-planner-llm.d.ts +1 -1
  238. package/dist/recall-state.d.ts +1 -1
  239. package/dist/recall-tag-filter.d.ts +1 -1
  240. package/dist/recall-timings.d.ts +1 -1
  241. package/dist/recall-xray-cli.d.ts +1 -1
  242. package/dist/recall-xray-cli.js +4 -4
  243. package/dist/recall-xray-renderer.d.ts +1 -1
  244. package/dist/recall-xray-renderer.js +3 -3
  245. package/dist/recall-xray.d.ts +1 -1
  246. package/dist/recall-xray.js +2 -2
  247. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  248. package/dist/replay/normalizers/claude.d.ts +1 -1
  249. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  250. package/dist/replay/normalizers/shared.d.ts +1 -1
  251. package/dist/replay/runner.d.ts +1 -1
  252. package/dist/replay/types.d.ts +1 -1
  253. package/dist/resolve-auth-token.d.ts +1 -1
  254. package/dist/resume-bundles.js +4 -4
  255. package/dist/retrieval-agents.d.ts +2 -2
  256. package/dist/retrieval-tiers.d.ts +1 -1
  257. package/dist/routing/engine.d.ts +1 -1
  258. package/dist/routing/store.d.ts +1 -1
  259. package/dist/salvage-envelope.d.ts +1 -1
  260. package/dist/schemas.d.ts +76 -76
  261. package/dist/{scope-profiles-KG2nODX7.d.ts → scope-profiles-EhyuAKkd.d.ts} +1 -1
  262. package/dist/search/embed-helper.d.ts +1 -1
  263. package/dist/search/factory.d.ts +1 -1
  264. package/dist/search/factory.js +5 -5
  265. package/dist/search/index.d.ts +1 -1
  266. package/dist/search/index.js +9 -9
  267. package/dist/search/lancedb-backend.d.ts +1 -1
  268. package/dist/search/meilisearch-backend.d.ts +1 -1
  269. package/dist/search/noop-backend.d.ts +1 -1
  270. package/dist/search/orama-backend.d.ts +1 -1
  271. package/dist/search/port.d.ts +1 -1
  272. package/dist/search/remote-backend.d.ts +1 -1
  273. package/dist/{semantic-consolidation-BaZlg0D8.d.ts → semantic-consolidation-B2xo26UQ.d.ts} +1 -1
  274. package/dist/semantic-consolidation.d.ts +2 -2
  275. package/dist/semantic-consolidation.js +7 -7
  276. package/dist/semantic-rule-promotion.js +5 -5
  277. package/dist/semantic-rule-verifier.d.ts +1 -1
  278. package/dist/semantic-rule-verifier.js +6 -6
  279. package/dist/service-DgFWlZ-r.d.ts +835 -0
  280. package/dist/session-observer-bands.d.ts +1 -1
  281. package/dist/session-observer-state.d.ts +1 -1
  282. package/dist/shared-context/manager.d.ts +9 -9
  283. package/dist/signal.d.ts +1 -1
  284. package/dist/{storage-BKhHHnRo.d.ts → storage-XGawwqXE.d.ts} +131 -2
  285. package/dist/storage.d.ts +2 -2
  286. package/dist/storage.js +4 -4
  287. package/dist/summarizer.d.ts +1 -1
  288. package/dist/summary-snapshot.d.ts +1 -1
  289. package/dist/temporal-supersession.d.ts +2 -2
  290. package/dist/temporal-timeline-recall.d.ts +1 -1
  291. package/dist/temporal-validity.d.ts +1 -1
  292. package/dist/threading.d.ts +1 -1
  293. package/dist/tier-migration.d.ts +2 -2
  294. package/dist/tier-routing.d.ts +1 -1
  295. package/dist/topics.d.ts +1 -1
  296. package/dist/transcript.d.ts +1 -1
  297. package/dist/transfer/types.d.ts +66 -66
  298. package/dist/trust-score-stage.d.ts +1 -1
  299. package/dist/trust-score.d.ts +1 -1
  300. package/dist/{types-GBQZuT7l.d.ts → types-Bbs0qz6b.d.ts} +2 -3
  301. package/dist/types.d.ts +1 -1
  302. package/dist/types.js +1 -1
  303. package/dist/utility-runtime.d.ts +1 -1
  304. package/dist/verified-recall.js +6 -6
  305. package/dist/write-envelope.d.ts +1 -1
  306. package/package.json +2 -2
  307. package/src/access-boundary.ts +3 -0
  308. package/src/access-http.test.ts +63 -0
  309. package/src/access-http.ts +26 -37
  310. package/src/access-mcp-output-schemas.ts +10 -0
  311. package/src/access-mcp.ts +4 -121
  312. package/src/access-operations-batch.ts +10 -5
  313. package/src/access-service.ts +43 -46
  314. package/src/access-surface-catalog.ts +6 -0
  315. package/src/access-wearables-meetings-surface.ts +139 -0
  316. package/src/activity/digest.ts +1 -1
  317. package/src/activity/runner.test.ts +37 -0
  318. package/src/activity/runner.ts +12 -0
  319. package/src/activity/scheduler.test.ts +1 -0
  320. package/src/activity/sync-registration.ts +13 -0
  321. package/src/cli/meetings-commands.ts +70 -0
  322. package/src/cli.ts +7 -6
  323. package/src/extraction-error-classification.ts +52 -0
  324. package/src/extraction.ts +2 -50
  325. package/src/mcp-read-only-tools.ts +2 -0
  326. package/src/meetings/build-memory.test.ts +364 -0
  327. package/src/meetings/build-scheduler.test.ts +55 -0
  328. package/src/meetings/build-scheduler.ts +96 -0
  329. package/src/meetings/day-source.test.ts +275 -0
  330. package/src/meetings/day-source.ts +352 -0
  331. package/src/meetings/http-glue.ts +126 -0
  332. package/src/meetings/index.ts +5 -4
  333. package/src/meetings/manual-sync-flush.test.ts +72 -0
  334. package/src/meetings/mcp-tools.ts +65 -0
  335. package/src/meetings/memory-gen.test.ts +498 -0
  336. package/src/meetings/memory-gen.ts +549 -0
  337. package/src/meetings/memory-generator.ts +5 -0
  338. package/src/meetings/namespace-isolation.test.ts +159 -0
  339. package/src/meetings/service.test.ts +195 -0
  340. package/src/meetings/service.ts +157 -0
  341. package/src/meetings/summary-extractor.test.ts +287 -0
  342. package/src/meetings/summary-extractor.ts +264 -0
  343. package/src/orchestration/maintenance.ts +8 -0
  344. package/src/orchestration/orchestrator-init.ts +6 -1
  345. package/src/orchestration/workspace-ops.ts +172 -8
  346. package/src/orchestrator.ts +12 -13
  347. package/src/storage.ts +21 -0
  348. package/src/testing/subjects/meetings-namespace-harness.ts +159 -0
  349. package/src/testing/subjects/meetings-namespace.test.ts +93 -0
  350. package/src/types.ts +1 -2
  351. package/src/wearables/cli.ts +2 -1
  352. package/src/wearables/index.ts +1 -0
  353. package/src/wearables/mcp-tools.ts +143 -0
  354. package/src/wearables/registry.test.ts +11 -0
  355. package/src/wearables/registry.ts +13 -0
  356. package/src/wearables/service.test.ts +62 -1
  357. package/src/wearables/service.ts +30 -2
  358. package/src/wearables/transcript-search-namespace-isolation.test.ts +98 -0
  359. package/dist/chunk-6ANPNUVL.js.map +0 -1
  360. package/dist/chunk-6NZ7U4R4.js.map +0 -1
  361. package/dist/chunk-7S5OMI76.js.map +0 -1
  362. package/dist/chunk-ACGGTPU4.js.map +0 -1
  363. package/dist/chunk-DM5A4ZFL.js.map +0 -1
  364. package/dist/chunk-EQYBHXRR.js.map +0 -1
  365. package/dist/chunk-IC2TCZJ2.js.map +0 -1
  366. package/dist/chunk-MONWCSVL.js.map +0 -1
  367. package/dist/chunk-N6M7MRVN.js.map +0 -1
  368. package/dist/chunk-QMGIQUBG.js.map +0 -1
  369. package/dist/chunk-QTO2FGBT.js.map +0 -1
  370. package/dist/chunk-SQEDDWAM.js.map +0 -1
  371. package/dist/chunk-W3XT32W7.js.map +0 -1
  372. /package/dist/{auto-sync-UPTUVE4X.js.map → auto-sync-5OE5QOZB.js.map} +0 -0
  373. /package/dist/{chunk-GF22HWS2.js.map → chunk-2PHX3I5C.js.map} +0 -0
  374. /package/dist/{chunk-JFDEYXF7.js.map → chunk-3NAHZQU3.js.map} +0 -0
  375. /package/dist/{chunk-XBRPEGXF.js.map → chunk-3UOSVTFR.js.map} +0 -0
  376. /package/dist/{chunk-TTKWTT72.js.map → chunk-3ZW6ZQO6.js.map} +0 -0
  377. /package/dist/{chunk-UV72DEZD.js.map → chunk-4B77XSG5.js.map} +0 -0
  378. /package/dist/{chunk-LMHDFOVV.js.map → chunk-5CPI2QWG.js.map} +0 -0
  379. /package/dist/{chunk-P72WZMWQ.js.map → chunk-5FZQ74K5.js.map} +0 -0
  380. /package/dist/{chunk-BRT7KEXU.js.map → chunk-6TWXQ4E2.js.map} +0 -0
  381. /package/dist/{chunk-CV6ZYXK3.js.map → chunk-7I7AYIUQ.js.map} +0 -0
  382. /package/dist/{chunk-TVVEYCNW.js.map → chunk-7K5Q6COX.js.map} +0 -0
  383. /package/dist/{chunk-JN57HZCD.js.map → chunk-ATEAL5EI.js.map} +0 -0
  384. /package/dist/{chunk-4PYLBXX6.js.map → chunk-AYAROCWD.js.map} +0 -0
  385. /package/dist/{chunk-NB2P47N7.js.map → chunk-CUUY5CC4.js.map} +0 -0
  386. /package/dist/{chunk-KL5Y6PEV.js.map → chunk-E3ZMFFQZ.js.map} +0 -0
  387. /package/dist/{chunk-55FDQYPS.js.map → chunk-ES5IHQ6M.js.map} +0 -0
  388. /package/dist/{chunk-N47ZWQBO.js.map → chunk-G2NSSQES.js.map} +0 -0
  389. /package/dist/{chunk-GSWPU5CR.js.map → chunk-GD3QOBMB.js.map} +0 -0
  390. /package/dist/{chunk-SACPRJ7F.js.map → chunk-JNIJBXVK.js.map} +0 -0
  391. /package/dist/{chunk-DGLON3AL.js.map → chunk-KNRG6ZGJ.js.map} +0 -0
  392. /package/dist/{chunk-GEUDRENF.js.map → chunk-MMTG3SCJ.js.map} +0 -0
  393. /package/dist/{chunk-WZFAPHIK.js.map → chunk-MU7ZHSZE.js.map} +0 -0
  394. /package/dist/{chunk-W4FBGYGK.js.map → chunk-NRV4Y3PM.js.map} +0 -0
  395. /package/dist/{chunk-KQ2XCQYN.js.map → chunk-P2BLHUL2.js.map} +0 -0
  396. /package/dist/{chunk-6L775BUB.js.map → chunk-QE63OHLB.js.map} +0 -0
  397. /package/dist/{chunk-ONHRRJZM.js.map → chunk-QJAQNXWI.js.map} +0 -0
  398. /package/dist/{chunk-6RYWNBSA.js.map → chunk-SVZGA2MK.js.map} +0 -0
  399. /package/dist/{chunk-OCMLGND7.js.map → chunk-WWZ35GWW.js.map} +0 -0
  400. /package/dist/{chunk-A5N6ISRK.js.map → chunk-ZN4UZWMT.js.map} +0 -0
  401. /package/dist/{chunk-7M6APKJL.js.map → chunk-ZYU7ZFLT.js.map} +0 -0
  402. /package/dist/{first-start-migration-LEXL3XYL.js.map → first-start-migration-BKNGOFED.js.map} +0 -0
@@ -0,0 +1,287 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+
4
+ import { composeMeetingRecord, type MeetingRecordBase } from "./store.js";
5
+ import { DEFAULT_MEETINGS_CONFIG } from "./config.js";
6
+ import {
7
+ generateMeetingSummaryFacts,
8
+ type MeetingFactCandidate,
9
+ } from "./memory-gen.js";
10
+ import type { MeetingMemoryWriter } from "./memory-generator.js";
11
+ import {
12
+ LlmMeetingSummaryExtractor,
13
+ createMeetingSummaryDeps,
14
+ createMeetingSummaryJudge,
15
+ parseMeetingSummaryResponse,
16
+ type MeetingSummaryChatClient,
17
+ } from "./summary-extractor.js";
18
+ import type { FusedMeeting, MeetingRecord, MeetingsConfig } from "./types.js";
19
+ import type { FusedSegment } from "../wearables/fusion/types.js";
20
+ import type { SealedMemoryEnvelope } from "../write-envelope.js";
21
+ import type { MemoryWriteResult } from "../storage.js";
22
+ import type { JudgeBatchResult, JudgeCandidate, JudgeVerdict } from "../extraction-judge.js";
23
+
24
+ const DATE = "2026-03-10";
25
+
26
+ /** Chat client double: replays canned responses (string, null, or throw) and
27
+ * records how it was called. `null`/`Error` items exercise chain fallthrough. */
28
+ function fakeLlm(script: Array<string | null | Error>): MeetingSummaryChatClient & {
29
+ calls: Array<{ system: string; user: string }>;
30
+ } {
31
+ let i = 0;
32
+ return {
33
+ calls: [],
34
+ async chatCompletion(messages) {
35
+ const system = messages.find((m) => m.role === "system")?.content ?? "";
36
+ const user = messages.find((m) => m.role === "user")?.content ?? "";
37
+ this.calls.push({ system, user });
38
+ const next = script[Math.min(i, script.length - 1)];
39
+ i++;
40
+ if (next instanceof Error) throw next;
41
+ return next === null ? null : { content: next };
42
+ },
43
+ };
44
+ }
45
+
46
+ function segment(text: string): FusedSegment {
47
+ return {
48
+ speaker: "Jane",
49
+ isSelf: false,
50
+ text,
51
+ startIso: "2026-03-10T14:05:00.000Z",
52
+ confidence: 0.9,
53
+ provenance: { source: "desktop", conversationId: "d1", sourceTrust: 0.9, reason: "only-source", alternatives: [] },
54
+ };
55
+ }
56
+
57
+ function fused(overrides: Partial<FusedMeeting> = {}): FusedMeeting {
58
+ return {
59
+ attendees: ["Jane", "Sam"],
60
+ sources: ["desktop", "pendant"],
61
+ corroboratedBy: [],
62
+ screenContext: [],
63
+ contextExcerpts: [],
64
+ transcript: [segment("we shipped v2")],
65
+ speakers: [],
66
+ snapshotCount: 0,
67
+ ...overrides,
68
+ };
69
+ }
70
+
71
+ function record(f: Partial<FusedMeeting> = {}): MeetingRecord {
72
+ const base: MeetingRecordBase = {
73
+ id: "mtg-2026-03-10-abcdef01",
74
+ date: DATE,
75
+ startUtc: "2026-03-10T14:00:00.000Z",
76
+ endUtc: "2026-03-10T14:30:00.000Z",
77
+ app: "Zoom",
78
+ detectionSource: "app+audio",
79
+ };
80
+ return composeMeetingRecord(base, fused(f));
81
+ }
82
+
83
+ function meetingConfig(overrides: Partial<MeetingsConfig> = {}): MeetingsConfig {
84
+ return { ...DEFAULT_MEETINGS_CONFIG, appPatterns: [...DEFAULT_MEETINGS_CONFIG.appPatterns], enabled: true, ...overrides };
85
+ }
86
+
87
+ interface WriteCall {
88
+ envelope: SealedMemoryEnvelope;
89
+ extras: { status?: string; memoryKind?: string };
90
+ }
91
+
92
+ /** In-memory writer modelling the sealed write path + source-scoped dedup. */
93
+ class FakeWriter implements MeetingMemoryWriter {
94
+ writes: WriteCall[] = [];
95
+ private bySource = new Map<string, Set<string>>();
96
+
97
+ async writeSealedMemory(
98
+ envelope: SealedMemoryEnvelope,
99
+ extras: { status?: string; memoryKind?: string; contentHashSource?: string },
100
+ ): Promise<MemoryWriteResult> {
101
+ this.writes.push({ envelope, extras });
102
+ if (extras.contentHashSource !== undefined) {
103
+ const set = this.bySource.get(envelope.source) ?? new Set<string>();
104
+ set.add(extras.contentHashSource);
105
+ this.bySource.set(envelope.source, set);
106
+ }
107
+ return { id: `m${this.writes.length}`, path: `moments/m${this.writes.length}.md` } as unknown as MemoryWriteResult;
108
+ }
109
+ async hasMemoryFromSource(source: string, content: string): Promise<boolean> {
110
+ return this.bySource.get(source)?.has(content) ?? false;
111
+ }
112
+ async retireMemoriesFromSource(source: string): Promise<number> {
113
+ const count = this.bySource.get(source)?.size ?? 0;
114
+ this.bySource.delete(source);
115
+ return count;
116
+ }
117
+ }
118
+
119
+ const DECISION_COMMITMENT_QUESTION_JSON = JSON.stringify({
120
+ summary: "The team reviewed the v2 launch and settled the ship date.",
121
+ decisions: ["Ship v2 on Friday"],
122
+ commitments: [{ owner: "Sam", action: "send the launch recap by EOD" }],
123
+ openQuestions: ["Who owns the rollback plan?"],
124
+ });
125
+
126
+ /** A judge closure that returns the given verdict kind for every candidate,
127
+ * in the shared `JudgeBatchResult` shape `judgeFactDurability` produces. */
128
+ function judgeAll(kind: JudgeVerdict["kind"]): (candidates: JudgeCandidate[]) => Promise<JudgeBatchResult> {
129
+ return async (candidates) => {
130
+ const verdicts = new Map<number, JudgeVerdict>();
131
+ candidates.forEach((_, index) => verdicts.set(index, { durable: kind === "accept", reason: "test", kind }));
132
+ return { verdicts, cached: 0, judged: candidates.length, elapsed: 0, deferred: 0, deferredCappedToReject: 0 };
133
+ };
134
+ }
135
+
136
+ // ---------------------------------------------------------------------------
137
+ // Extractor
138
+ // ---------------------------------------------------------------------------
139
+
140
+ test("extractor categorizes decisions, commitments, and open questions from a JSON response", async () => {
141
+ const llm = fakeLlm([DECISION_COMMITMENT_QUESTION_JSON]);
142
+ const extractor = new LlmMeetingSummaryExtractor([llm]);
143
+ const { summary, candidates } = await extractor.extract({
144
+ record: record(),
145
+ transcriptText: "**Jane** [14:05]: we shipped v2",
146
+ screenContextText: "",
147
+ });
148
+ assert.equal(llm.calls.length, 1);
149
+ assert.match(summary, /settled the ship date/);
150
+ const decision = candidates.find((c) => c.category === "decision");
151
+ assert.equal(decision?.content, "Ship v2 on Friday");
152
+ const commitment = candidates.find((c) => c.category === "commitment");
153
+ assert.equal(commitment?.content, "Sam: send the launch recap by EOD", "owner-bearing commitment carries its owner");
154
+ const question = candidates.find((c) => c.category === "fact");
155
+ assert.equal(question?.content, "Open question: Who owns the rollback plan?");
156
+ });
157
+
158
+ test("extractor passes transcript + screen context into the prompt and tolerates code fences", async () => {
159
+ const fenced = "```json\n" + DECISION_COMMITMENT_QUESTION_JSON + "\n```";
160
+ const llm = fakeLlm([fenced]);
161
+ const extractor = new LlmMeetingSummaryExtractor([llm]);
162
+ const res = await extractor.extract({
163
+ record: record(),
164
+ transcriptText: "**Jane** [14:05]: ship it",
165
+ screenContextText: "[14:05] Zoom — Standup",
166
+ });
167
+ assert.equal(res.candidates.length, 3, "a fenced JSON body still parses to 3 candidates");
168
+ assert.match(llm.calls[0]!.user, /## Transcript/);
169
+ assert.match(llm.calls[0]!.user, /ship it/);
170
+ assert.match(llm.calls[0]!.user, /## Screen context/);
171
+ assert.match(llm.calls[0]!.user, /Standup/);
172
+ });
173
+
174
+ test("extractor tolerates malformed output — empty candidates, best-effort summary, never throws", async () => {
175
+ const extractor = new LlmMeetingSummaryExtractor([fakeLlm(["this is not json, just prose about the meeting"])]);
176
+ const res = await extractor.extract({ record: record(), transcriptText: "x", screenContextText: "" });
177
+ assert.deepEqual(res.candidates, []);
178
+ assert.equal(res.summary, "this is not json, just prose about the meeting");
179
+ });
180
+
181
+ test("extractor returns empty when no client yields content (null / empty chain)", async () => {
182
+ const nullRes = await new LlmMeetingSummaryExtractor([fakeLlm([null])]).extract({
183
+ record: record(),
184
+ transcriptText: "x",
185
+ screenContextText: "",
186
+ });
187
+ assert.deepEqual(nullRes, { summary: "", candidates: [] });
188
+ const emptyChain = await new LlmMeetingSummaryExtractor([]).extract({
189
+ record: record(),
190
+ transcriptText: "x",
191
+ screenContextText: "",
192
+ });
193
+ assert.deepEqual(emptyChain, { summary: "", candidates: [] });
194
+ });
195
+
196
+ test("extractor falls through the client chain: a throwing/empty client yields to the next", async () => {
197
+ const first = fakeLlm([new Error("local backend down")]);
198
+ const second = fakeLlm([DECISION_COMMITMENT_QUESTION_JSON]);
199
+ const extractor = new LlmMeetingSummaryExtractor([first, second]);
200
+ const res = await extractor.extract({ record: record(), transcriptText: "x", screenContextText: "" });
201
+ assert.equal(first.calls.length, 1, "the first client was tried");
202
+ assert.equal(second.calls.length, 1, "the chain fell through to the second client");
203
+ assert.equal(res.candidates.length, 3);
204
+ });
205
+
206
+ test("parseMeetingSummaryResponse never throws on garbage and salvages prose", () => {
207
+ assert.deepEqual(parseMeetingSummaryResponse("{ broken json"), { summary: "{ broken json", candidates: [] });
208
+ assert.deepEqual(parseMeetingSummaryResponse(""), { summary: "", candidates: [] });
209
+ });
210
+
211
+ // ---------------------------------------------------------------------------
212
+ // Judge adapter
213
+ // ---------------------------------------------------------------------------
214
+
215
+ test("judge adapter maps shared judge verdicts aligned to candidate order", async () => {
216
+ const judge = createMeetingSummaryJudge(async (candidates) => {
217
+ const verdicts = new Map<number, JudgeVerdict>([
218
+ [0, { durable: true, reason: "keep", kind: "accept" }],
219
+ [1, { durable: false, reason: "drop", kind: "reject" }],
220
+ [2, { durable: false, reason: "later", kind: "defer" }],
221
+ ]);
222
+ return { verdicts, cached: 0, judged: candidates.length, elapsed: 0, deferred: 1, deferredCappedToReject: 0 };
223
+ });
224
+ const candidates: MeetingFactCandidate[] = [
225
+ { content: "a decision", category: "decision", confidence: 0.9 },
226
+ { content: "a weak claim", category: "fact", confidence: 0.5 },
227
+ { content: "an ambiguous claim", category: "fact", confidence: 0.6 },
228
+ ];
229
+ assert.deepEqual(await judge.judge(candidates), ["accept", "reject", "defer"]);
230
+ });
231
+
232
+ test("judge adapter degrades to no verdicts (empty array) when the shared judge throws", async () => {
233
+ const judge = createMeetingSummaryJudge(async () => {
234
+ throw new Error("judge backend down");
235
+ });
236
+ assert.deepEqual(await judge.judge([{ content: "x", category: "decision", confidence: 0.9 }]), []);
237
+ });
238
+
239
+ // ---------------------------------------------------------------------------
240
+ // Production wiring: the SAME factory workspace-ops injects, driven through the
241
+ // real trust-gated generateMeetingSummaryFacts path.
242
+ // ---------------------------------------------------------------------------
243
+
244
+ test("createMeetingSummaryDeps: smart mode writes a trust-gated decision + commitment with meeting provenance", async () => {
245
+ const llm = fakeLlm([DECISION_COMMITMENT_QUESTION_JSON]);
246
+ const { extractor, judge } = createMeetingSummaryDeps({
247
+ localLlm: llm,
248
+ fallbackLlm: null,
249
+ judgeFacts: judgeAll("accept"),
250
+ });
251
+ const writer = new FakeWriter();
252
+ const res = await generateMeetingSummaryFacts(record(), meetingConfig({ summaryMode: "smart" }), {
253
+ extractor,
254
+ judge,
255
+ writer,
256
+ });
257
+ assert.equal(res.llmInvoked, true);
258
+ assert.equal(llm.calls.length, 1, "the production extractor invoked the LLM once");
259
+
260
+ const decision = writer.writes.find((w) => w.envelope.category === "decision");
261
+ assert.ok(decision, "a decision fact was written");
262
+ assert.equal(decision!.extras.status, "active", "judge-accepted decision (0.8*0.85+0.15=0.83) is auto-active");
263
+ assert.equal(decision!.envelope.rawStructuredAttributes?.meetingId, "mtg-2026-03-10-abcdef01");
264
+
265
+ const commitment = writer.writes.find((w) => w.envelope.category === "commitment");
266
+ assert.ok(commitment, "a commitment fact (category commitment) was written");
267
+ assert.equal(commitment!.extras.status, "active");
268
+ assert.equal(commitment!.envelope.rawStructuredAttributes?.meetingId, "mtg-2026-03-10-abcdef01");
269
+ });
270
+
271
+ test("createMeetingSummaryDeps: off mode makes zero LLM calls", async () => {
272
+ const llm = fakeLlm([DECISION_COMMITMENT_QUESTION_JSON]);
273
+ const { extractor, judge } = createMeetingSummaryDeps({
274
+ localLlm: llm,
275
+ fallbackLlm: null,
276
+ judgeFacts: judgeAll("accept"),
277
+ });
278
+ const writer = new FakeWriter();
279
+ const res = await generateMeetingSummaryFacts(record(), meetingConfig({ summaryMode: "off" }), {
280
+ extractor,
281
+ judge,
282
+ writer,
283
+ });
284
+ assert.equal(res.llmInvoked, false);
285
+ assert.equal(llm.calls.length, 0, "off mode must NEVER invoke the LLM");
286
+ assert.equal(writer.writes.length, 0);
287
+ });
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Production meeting summary extractor + durability-judge adapter (issue #1900).
3
+ *
4
+ * The engine's `MeetingsBuilder` accepts an optional `summary` dep pair
5
+ * ({ extractor, judge }); when present alongside `memoryWriter` and
6
+ * `summaryMode !== "off"` it runs `generateMeetingSummaryFacts` per record. This
7
+ * module supplies the PRODUCTION implementations of those interfaces so the
8
+ * default `summaryMode: "smart"` actually generates trust-gated meeting facts in
9
+ * production. It lives beside `memory-gen` (whose interfaces it implements) so
10
+ * `workspace-ops` stays thin — that file only constructs + injects.
11
+ *
12
+ * Reuse, not fork: the judge adapter wraps the SAME `judgeFactDurability` the
13
+ * live extraction + wearables pipelines use (verdict cache + defer counters
14
+ * included), and the extractor calls the shared local -> gateway LLM clients. No
15
+ * parallel trust/judge/LLM machinery lives here.
16
+ */
17
+
18
+ import { getVerdictKind, type JudgeBatchResult, type JudgeCandidate } from "../extraction-judge.js";
19
+ import { log } from "../logger.js";
20
+ import type {
21
+ MeetingFactCandidate,
22
+ MeetingSummaryExtractor,
23
+ MeetingSummaryJudge,
24
+ } from "./memory-gen.js";
25
+ import type { MeetingRecord } from "./types.js";
26
+
27
+ /**
28
+ * Narrow chat client the extractor needs. Structurally satisfied by BOTH
29
+ * `LocalLlmClient` and `FallbackLlmClient`, so production injects the real
30
+ * local -> gateway chain and unit tests inject a fake. Kept minimal (never fork
31
+ * the clients) so a client outage or a null return degrades to "no candidates"
32
+ * rather than throwing through the build.
33
+ */
34
+ export interface MeetingSummaryChatClient {
35
+ chatCompletion(
36
+ messages: Array<{ role: "system" | "user" | "assistant"; content: string }>,
37
+ options?: { temperature?: number; maxTokens?: number; operation?: string },
38
+ ): Promise<{ content: string } | null>;
39
+ }
40
+
41
+ const SUMMARY_SYSTEM_PROMPT = `You are a meeting scribe extracting DURABLE memory from a meeting transcript and its on-screen context.
42
+
43
+ Return ONLY a single JSON object, no prose, no code fences, in exactly this shape:
44
+ {
45
+ "summary": "one concise paragraph (2-4 sentences) describing what the meeting was about and its outcome",
46
+ "decisions": ["a concrete decision the group reached", ...],
47
+ "commitments": [{"owner": "who owns it", "action": "what they committed to do, including any due date"}, ...],
48
+ "openQuestions": ["an unresolved question that was raised", ...]
49
+ }
50
+
51
+ Rules:
52
+ - decisions: concrete choices the group made. Omit hypotheticals and discussion that led nowhere.
53
+ - commitments: owner-bearing action items only. If there is no clear owner, omit it.
54
+ - openQuestions: genuinely unresolved questions, not rhetorical asides.
55
+ - Prefer fewer, higher-quality items. Use [] for any empty list. Never invent content the transcript does not support.`;
56
+
57
+ const DECISION_CONFIDENCE = 0.8;
58
+ const COMMITMENT_CONFIDENCE = 0.8;
59
+ const QUESTION_CONFIDENCE = 0.6;
60
+ const DEFAULT_MAX_TOKENS = 1024;
61
+ /** Cap a salvaged (non-JSON) best-effort summary so a runaway response cannot
62
+ * push a huge blob into the trust-gated summary fact. */
63
+ const MAX_BEST_EFFORT_SUMMARY = 2000;
64
+
65
+ function meetingPromptHeader(record: MeetingRecord): string {
66
+ const parts = [`Meeting on ${record.date}`];
67
+ if (record.app !== undefined) parts.push(`app: ${record.app}`);
68
+ if (record.attendees.length > 0) parts.push(`attendees: ${record.attendees.join(", ")}`);
69
+ return parts.join(" | ");
70
+ }
71
+
72
+ function buildUserPrompt(
73
+ record: MeetingRecord,
74
+ transcriptText: string,
75
+ screenContextText: string,
76
+ ): string {
77
+ const sections = [meetingPromptHeader(record), "", "## Transcript", transcriptText || "(none)"];
78
+ if (screenContextText.trim().length > 0) {
79
+ sections.push("", "## Screen context", screenContextText);
80
+ }
81
+ return sections.join("\n");
82
+ }
83
+
84
+ /** Strip a wrapping markdown code fence, if present. */
85
+ function stripCodeFence(text: string): string {
86
+ const trimmed = text.trim();
87
+ if (!trimmed.startsWith("```")) return trimmed;
88
+ const withoutOpen = trimmed.replace(/^```[a-zA-Z0-9]*\n?/, "");
89
+ const closeIdx = withoutOpen.lastIndexOf("```");
90
+ return (closeIdx === -1 ? withoutOpen : withoutOpen.slice(0, closeIdx)).trim();
91
+ }
92
+
93
+ function asString(value: unknown): string {
94
+ return typeof value === "string" ? value.trim() : "";
95
+ }
96
+
97
+ function pushIfContent(
98
+ out: MeetingFactCandidate[],
99
+ content: string,
100
+ category: MeetingFactCandidate["category"],
101
+ confidence: number,
102
+ ): void {
103
+ const trimmed = content.trim();
104
+ if (trimmed.length > 0) out.push({ content: trimmed, category, confidence });
105
+ }
106
+
107
+ function candidatesFromParsed(parsed: Record<string, unknown>): MeetingFactCandidate[] {
108
+ const out: MeetingFactCandidate[] = [];
109
+ const decisions = Array.isArray(parsed.decisions) ? parsed.decisions : [];
110
+ for (const d of decisions) pushIfContent(out, asString(d), "decision", DECISION_CONFIDENCE);
111
+
112
+ const commitments = Array.isArray(parsed.commitments) ? parsed.commitments : [];
113
+ for (const c of commitments) {
114
+ if (typeof c === "string") {
115
+ pushIfContent(out, c, "commitment", COMMITMENT_CONFIDENCE);
116
+ } else if (c !== null && typeof c === "object") {
117
+ const owner = asString((c as Record<string, unknown>).owner);
118
+ const action = asString((c as Record<string, unknown>).action);
119
+ const content = owner.length > 0 && action.length > 0 ? `${owner}: ${action}` : owner || action;
120
+ pushIfContent(out, content, "commitment", COMMITMENT_CONFIDENCE);
121
+ }
122
+ }
123
+
124
+ const questions = Array.isArray(parsed.openQuestions) ? parsed.openQuestions : [];
125
+ for (const q of questions) {
126
+ const text = asString(q);
127
+ if (text.length > 0) pushIfContent(out, `Open question: ${text}`, "fact", QUESTION_CONFIDENCE);
128
+ }
129
+ return out;
130
+ }
131
+
132
+ /**
133
+ * Parse the model response into a summary + categorized candidates. Tolerant by
134
+ * contract: malformed output yields empty candidates plus a best-effort summary
135
+ * (the salvaged prose), and NEVER throws — a bad LLM response must degrade the
136
+ * build, not fail it.
137
+ */
138
+ export function parseMeetingSummaryResponse(raw: string): {
139
+ summary: string;
140
+ candidates: MeetingFactCandidate[];
141
+ } {
142
+ const body = stripCodeFence(raw);
143
+ const start = body.indexOf("{");
144
+ const end = body.lastIndexOf("}");
145
+ if (start !== -1 && end > start) {
146
+ try {
147
+ const parsed: unknown = JSON.parse(body.slice(start, end + 1));
148
+ if (parsed !== null && typeof parsed === "object") {
149
+ const obj = parsed as Record<string, unknown>;
150
+ return { summary: asString(obj.summary), candidates: candidatesFromParsed(obj) };
151
+ }
152
+ } catch {
153
+ // fall through to best-effort salvage
154
+ }
155
+ }
156
+ // Best-effort: a model that ignored the JSON contract still gave us prose.
157
+ return { summary: body.slice(0, MAX_BEST_EFFORT_SUMMARY).trim(), candidates: [] };
158
+ }
159
+
160
+ /**
161
+ * Production extractor. Tries each injected chat client in order (local first,
162
+ * then the gateway fallback) until one returns content; a null/empty result or
163
+ * a thrown client error degrades to an empty extraction (no summary, no
164
+ * candidates) instead of failing the meeting build.
165
+ */
166
+ export class LlmMeetingSummaryExtractor implements MeetingSummaryExtractor {
167
+ constructor(
168
+ private readonly clients: readonly MeetingSummaryChatClient[],
169
+ private readonly options: { maxTokens?: number } = {},
170
+ ) {}
171
+
172
+ async extract(input: {
173
+ record: MeetingRecord;
174
+ transcriptText: string;
175
+ screenContextText: string;
176
+ }): Promise<{ summary: string; candidates: MeetingFactCandidate[] }> {
177
+ const raw = await this.complete(
178
+ buildUserPrompt(input.record, input.transcriptText, input.screenContextText),
179
+ );
180
+ if (raw === null) return { summary: "", candidates: [] };
181
+ return parseMeetingSummaryResponse(raw);
182
+ }
183
+
184
+ private async complete(userPrompt: string): Promise<string | null> {
185
+ for (const client of this.clients) {
186
+ try {
187
+ const res = await client.chatCompletion(
188
+ [
189
+ { role: "system", content: SUMMARY_SYSTEM_PROMPT },
190
+ { role: "user", content: userPrompt },
191
+ ],
192
+ { temperature: 0, maxTokens: this.options.maxTokens ?? DEFAULT_MAX_TOKENS, operation: "meeting-summary" },
193
+ );
194
+ const content = res?.content?.trim();
195
+ if (content !== undefined && content.length > 0) return content;
196
+ } catch (err) {
197
+ log.warn(
198
+ `meetings: summary extractor LLM call failed, trying next client: ${
199
+ err instanceof Error ? err.message : String(err)
200
+ }`,
201
+ );
202
+ }
203
+ }
204
+ return null;
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Judge adapter over the SHARED durability judge. Maps meeting candidates to
210
+ * `JudgeCandidate`s, runs `judgeFacts` (the same `judgeFactDurability` closure
211
+ * the wearables service passes — cache + defer counters included), and returns
212
+ * verdict kinds aligned to the input candidate order. A judge outage returns an
213
+ * empty array so `generateMeetingSummaryFacts` continues on trust score alone
214
+ * (matching the wearables graceful-degradation contract); it never throws.
215
+ */
216
+ export function createMeetingSummaryJudge(
217
+ judgeFacts: (candidates: JudgeCandidate[]) => Promise<JudgeBatchResult>,
218
+ ): MeetingSummaryJudge {
219
+ return {
220
+ async judge(candidates) {
221
+ if (candidates.length === 0) return [];
222
+ const judgeCandidates: JudgeCandidate[] = candidates.map((c) => ({
223
+ text: c.content,
224
+ category: c.category,
225
+ confidence: c.confidence ?? 0.7,
226
+ }));
227
+ try {
228
+ const { verdicts } = await judgeFacts(judgeCandidates);
229
+ return candidates.map((_, i) => {
230
+ const verdict = verdicts.get(i);
231
+ return verdict ? getVerdictKind(verdict) : "defer";
232
+ });
233
+ } catch (err) {
234
+ log.warn(
235
+ `meetings: durability judge unavailable, scoring without verdicts: ${
236
+ err instanceof Error ? err.message : String(err)
237
+ }`,
238
+ );
239
+ return [];
240
+ }
241
+ },
242
+ };
243
+ }
244
+
245
+ /**
246
+ * Build the production summary deps (extractor + judge) the `MeetingsBuilder`
247
+ * consumes. Pure construction — no disk, no network — so a caller can build it
248
+ * unconditionally and let the builder gate on `summaryMode`. Reuses the caller's
249
+ * local + fallback clients and the shared judge closure, so nothing here forks
250
+ * the trust/judge/LLM contracts.
251
+ */
252
+ export function createMeetingSummaryDeps(deps: {
253
+ localLlm: MeetingSummaryChatClient | null;
254
+ fallbackLlm: MeetingSummaryChatClient | null;
255
+ judgeFacts: (candidates: JudgeCandidate[]) => Promise<JudgeBatchResult>;
256
+ }): { extractor: MeetingSummaryExtractor; judge: MeetingSummaryJudge } {
257
+ const clients: MeetingSummaryChatClient[] = [];
258
+ if (deps.localLlm !== null) clients.push(deps.localLlm);
259
+ if (deps.fallbackLlm !== null) clients.push(deps.fallbackLlm);
260
+ return {
261
+ extractor: new LlmMeetingSummaryExtractor(clients),
262
+ judge: createMeetingSummaryJudge(deps.judgeFacts),
263
+ };
264
+ }
@@ -62,6 +62,7 @@ import {
62
62
  } from "../storage/memory-lifecycle-ledger-access.js";
63
63
  import { STATE_FILE_MAX_DECRYPT_BYTES } from "../storage/secure-line-reader.js";
64
64
  import { ActivitySyncRegistrar } from "../activity/sync-registration.js";
65
+ import type { ActivitySyncRunSummary } from "../activity/runner.js";
65
66
  import { resolveHomeDir } from "../runtime/env.js";
66
67
  import { log } from "../logger.js";
67
68
  import { isErrnoCode } from "../utils/errno.js";
@@ -105,6 +106,12 @@ export interface MaintenanceSchedulerDeps {
105
106
  * (#1910). Consulted only when namespaces are enabled.
106
107
  */
107
108
  storageForNamespace?: (namespace: string) => Promise<StorageManager>;
109
+ /**
110
+ * Tail step (issue #1900): observe a durable activity sync tick so the
111
+ * meetings builder can rebuild the affected day(s). Optional; omitted by
112
+ * hosts that do not build meetings.
113
+ */
114
+ onActivitySynced?: (summary: ActivitySyncRunSummary) => void;
108
115
  }
109
116
 
110
117
  /**
@@ -153,6 +160,7 @@ export class MaintenanceScheduler {
153
160
  secureStoreEnabled: resolveRecallAuxiliaryCapabilities(deps.config).secureStore,
154
161
  getQmd: () => deps.getQmd(),
155
162
  requestReindexRetry: () => this.requestQmdMaintenanceForTool("activity-reindex-retry"),
163
+ ...(deps.onActivitySynced ? { onSynced: deps.onActivitySynced } : {}),
156
164
  });
157
165
  }
158
166
 
@@ -45,6 +45,7 @@ import { TranscriptManager } from "../transcript.js";
45
45
  import type { PluginConfig } from "../types.js";
46
46
  import { type UtilityRuntimeValues, loadUtilityRuntimeValues } from "../utility-runtime.js";
47
47
  import { WearablesService } from "../wearables/service.js";
48
+ import type { MeetingsService } from "../meetings/service.js";
48
49
  import {
49
50
  COMPACTION_SIGNAL_MAX_AGE_MS,
50
51
  defaultWorkspaceDir,
@@ -64,7 +65,8 @@ export interface OrchestratorInitDeps {
64
65
  deferredSyncSucceeded: boolean;
65
66
  disposeSearchBackendIfNeeded(): Promise<void>;
66
67
  readonly embeddingFallback: EmbeddingFallback;
67
- getWearablesService(): WearablesService;
68
+ getWearablesService(namespace?: string): WearablesService;
69
+ getMeetingsService(namespace?: string): Promise<MeetingsService>;
68
70
  readonly handleHistory: RecallHandleHistoryStore;
69
71
  readonly lastRecall: LastRecallStore;
70
72
  maintenanceNamespaces(
@@ -562,6 +564,9 @@ export class OrchestratorInitCoordinator {
562
564
  : {}),
563
565
  },
564
566
  {
567
+ // The shared wearables service fires its own meeting tail-step hook
568
+ // (wired in workspace-ops), so both this auto-sync path and the
569
+ // manual sync path rebuild affected meetings — no per-adapter fan-out.
565
570
  sync: (options) => this.deps.getWearablesService().sync(options),
566
571
  log: {
567
572
  info: (message) => log.info(message),