@remnic/core 9.52.2 → 9.54.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 (429) hide show
  1. package/dist/access-admin-ops-surface.d.ts +11 -11
  2. package/dist/access-admin-ops-surface.js +22 -22
  3. package/dist/access-authorization-probe.d.ts +11 -11
  4. package/dist/access-authorization-probe.js +23 -23
  5. package/dist/access-boundary.d.ts +11 -11
  6. package/dist/access-boundary.js +22 -22
  7. package/dist/access-cli.js +55 -55
  8. package/dist/access-coding-context-resolution.d.ts +1 -1
  9. package/dist/access-extraction-force-flush.d.ts +11 -11
  10. package/dist/access-extraction-force-flush.js +22 -22
  11. package/dist/access-health-types.d.ts +1 -1
  12. package/dist/access-http-lcm-compaction.d.ts +11 -11
  13. package/dist/access-http-lifecycle-flush.d.ts +11 -11
  14. package/dist/access-http-offline-stream.d.ts +11 -11
  15. package/dist/access-http-query.js +22 -22
  16. package/dist/access-http.d.ts +11 -11
  17. package/dist/access-http.js +29 -29
  18. package/dist/access-identity-continuity-surface.d.ts +9 -9
  19. package/dist/access-identity-continuity-surface.js +22 -22
  20. package/dist/access-lcm-surface.d.ts +11 -11
  21. package/dist/access-lcm-surface.js +22 -22
  22. package/dist/access-mcp.d.ts +11 -11
  23. package/dist/access-mcp.js +27 -27
  24. package/dist/access-memory-search-fanout.d.ts +2 -2
  25. package/dist/{access-namespace-preflight-Ds6CGlCP.d.ts → access-namespace-preflight-C7n4tgWC.d.ts} +1 -1
  26. package/dist/access-namespace-preflight.d.ts +3 -3
  27. package/dist/access-namespace-preflight.js +22 -22
  28. package/dist/access-observe-write-surface.d.ts +11 -11
  29. package/dist/access-observe-write-surface.js +22 -22
  30. package/dist/access-offline-manifest.d.ts +9 -9
  31. package/dist/access-offline-manifest.js +8 -4
  32. package/dist/access-operations-batch.js +24 -24
  33. package/dist/access-operations.d.ts +14 -14
  34. package/dist/access-operations.js +26 -26
  35. package/dist/access-recall-concurrency.d.ts +11 -11
  36. package/dist/access-recall-concurrency.js +22 -22
  37. package/dist/access-recall-response.d.ts +11 -11
  38. package/dist/access-recall-response.js +22 -22
  39. package/dist/access-recall-surface.d.ts +11 -11
  40. package/dist/access-recall-surface.js +22 -22
  41. package/dist/access-schema.d.ts +88 -88
  42. package/dist/{access-service-Cbk3bNbD.d.ts → access-service-CXvbxLK-.d.ts} +7 -7
  43. package/dist/access-service-helpers.d.ts +11 -11
  44. package/dist/access-service.d.ts +11 -11
  45. package/dist/access-service.js +22 -22
  46. package/dist/access-surface-catalog.d.ts +11 -11
  47. package/dist/access-wearables-meetings-surface.d.ts +3 -3
  48. package/dist/action-confidence.d.ts +1 -1
  49. package/dist/active-memory-bridge.d.ts +1 -1
  50. package/dist/active-recall.d.ts +1 -1
  51. package/dist/active-recall.js +5 -5
  52. package/dist/ambient-provenance.d.ts +1 -1
  53. package/dist/artifact-search.d.ts +1 -1
  54. package/dist/{auto-sync-QFQGOEO2.js → auto-sync-TDHLZPVP.js} +7 -7
  55. package/dist/behavior-learner.d.ts +1 -1
  56. package/dist/behavior-signals.d.ts +1 -1
  57. package/dist/bootstrap.d.ts +9 -9
  58. package/dist/briefing.d.ts +2 -2
  59. package/dist/briefing.js +12 -12
  60. package/dist/buffer-surprise-report.d.ts +1 -1
  61. package/dist/buffer-turn-helpers.d.ts +1 -1
  62. package/dist/buffer.d.ts +2 -2
  63. package/dist/bulk-import/index.d.ts +3 -3
  64. package/dist/calibration.d.ts +1 -1
  65. package/dist/capabilities.d.ts +1 -1
  66. package/dist/{catalog-D3B4RDy1.d.ts → catalog-BOOxl-I2.d.ts} +1 -1
  67. package/dist/causal-behavior.d.ts +1 -1
  68. package/dist/causal-consolidation.d.ts +1 -1
  69. package/dist/causal-consolidation.js +13 -13
  70. package/dist/causal-trajectory-graph.d.ts +1 -1
  71. package/dist/{chunk-MLXZD4WM.js → chunk-2GFK22OZ.js} +2 -2
  72. package/dist/{chunk-RANKJI5T.js → chunk-3JLPGJ43.js} +2 -2
  73. package/dist/{chunk-O5FFBO2M.js → chunk-47P3RYZP.js} +70 -8
  74. package/dist/{chunk-O5FFBO2M.js.map → chunk-47P3RYZP.js.map} +1 -1
  75. package/dist/{chunk-X7XEDOSL.js → chunk-4IIFL7GC.js} +8 -8
  76. package/dist/chunk-5FVVY2EB.js +27 -0
  77. package/dist/chunk-5FVVY2EB.js.map +1 -0
  78. package/dist/{chunk-GXSOUIA4.js → chunk-5WQMORU5.js} +4 -4
  79. package/dist/{chunk-J272WKX2.js → chunk-6HPHEFXT.js} +2 -2
  80. package/dist/{chunk-B6LZ4IBH.js → chunk-6LAPLZOS.js} +2 -2
  81. package/dist/{chunk-WD5B5AIE.js → chunk-6R4G7N4S.js} +118 -173
  82. package/dist/chunk-6R4G7N4S.js.map +1 -0
  83. package/dist/{chunk-VXCNQYTC.js → chunk-APPFNGX6.js} +2 -2
  84. package/dist/{chunk-GXFSQ6SD.js → chunk-C4ZNGVK3.js} +2 -2
  85. package/dist/{chunk-JSEJH2YQ.js → chunk-CGVHPCP2.js} +2 -2
  86. package/dist/{chunk-BQIFZ2MP.js → chunk-CX5SW2EW.js} +41 -24
  87. package/dist/chunk-CX5SW2EW.js.map +1 -0
  88. package/dist/{chunk-RMHMUUBW.js → chunk-CYF6WVE7.js} +105 -49
  89. package/dist/{chunk-RMHMUUBW.js.map → chunk-CYF6WVE7.js.map} +1 -1
  90. package/dist/{chunk-TEMSWHQD.js → chunk-DZD576ZU.js} +5 -5
  91. package/dist/{chunk-AMTEYNYK.js → chunk-EVWWT3KE.js} +4 -4
  92. package/dist/{chunk-P2KBDJJ6.js → chunk-G2LUMAUF.js} +4 -3
  93. package/dist/{chunk-P2KBDJJ6.js.map → chunk-G2LUMAUF.js.map} +1 -1
  94. package/dist/{chunk-AXIDQG5A.js → chunk-G4T7L557.js} +5 -5
  95. package/dist/{chunk-7M6NYQWB.js → chunk-G7Z6EENO.js} +4 -4
  96. package/dist/{chunk-IARXXZU4.js → chunk-HOIRB77A.js} +99 -20
  97. package/dist/chunk-HOIRB77A.js.map +1 -0
  98. package/dist/{chunk-NDAH7BJ5.js → chunk-HVP33QVW.js} +103 -6
  99. package/dist/chunk-HVP33QVW.js.map +1 -0
  100. package/dist/{chunk-YXKVLYXU.js → chunk-HWRD23DW.js} +2 -2
  101. package/dist/{chunk-SOEN6KCF.js → chunk-IBKR2TFR.js} +2 -2
  102. package/dist/{chunk-R3DJVOSM.js → chunk-JDSVLVCG.js} +8 -1
  103. package/dist/chunk-JDSVLVCG.js.map +1 -0
  104. package/dist/{chunk-F4EU6VUC.js → chunk-JDZPNCMA.js} +2 -2
  105. package/dist/{chunk-D5YOZ66N.js → chunk-JV5YUBIZ.js} +20 -20
  106. package/dist/{chunk-DLG5V6ND.js → chunk-K4QS7LXU.js} +4 -7
  107. package/dist/chunk-K4QS7LXU.js.map +1 -0
  108. package/dist/{chunk-BAKXNQ3C.js → chunk-KRTY3ENI.js} +2 -2
  109. package/dist/{chunk-NK5RGJ65.js → chunk-L4DLLKPN.js} +2 -2
  110. package/dist/{chunk-VFSAWRMZ.js → chunk-LJS7KUPV.js} +2 -2
  111. package/dist/{chunk-JJRVPWFY.js → chunk-MLPTFODV.js} +166 -37
  112. package/dist/chunk-MLPTFODV.js.map +1 -0
  113. package/dist/{chunk-NOEMGE56.js → chunk-NH5IR2WM.js} +2 -2
  114. package/dist/{chunk-5V6IXHPI.js → chunk-NSVDUN76.js} +2 -2
  115. package/dist/{chunk-W7Y5JDVB.js → chunk-PDDTI7IC.js} +4 -4
  116. package/dist/{chunk-QINJITDS.js → chunk-PX2LSHYM.js} +2 -2
  117. package/dist/{chunk-HDIUYMSB.js → chunk-QWGE2PUH.js} +6 -6
  118. package/dist/{chunk-X57M2Q4I.js → chunk-RDJRFDJY.js} +3 -3
  119. package/dist/{chunk-RYLEDKQ2.js → chunk-RUMNY5DT.js} +3 -3
  120. package/dist/{chunk-BKIFTXHR.js → chunk-RZ4XGKOJ.js} +2 -2
  121. package/dist/{chunk-66SE3BNH.js → chunk-SUPEYLH2.js} +2 -2
  122. package/dist/{chunk-BF6SXNQJ.js → chunk-T2JHZWH6.js} +20 -20
  123. package/dist/{chunk-VHFXP27N.js → chunk-THPPCISA.js} +60 -23
  124. package/dist/chunk-THPPCISA.js.map +1 -0
  125. package/dist/{chunk-QPOHMO6M.js → chunk-UES4BHA2.js} +2 -2
  126. package/dist/{chunk-XAPMEU3K.js → chunk-UU7TRFNE.js} +2 -2
  127. package/dist/{chunk-QBSFOKY6.js → chunk-VOELBUS4.js} +4 -4
  128. package/dist/{chunk-2LY6GIIE.js → chunk-WDBX6VER.js} +2 -2
  129. package/dist/{chunk-BMEYMKWX.js → chunk-WM65LU67.js} +2 -2
  130. package/dist/{chunk-WLRD2TXF.js → chunk-WOPA3SGI.js} +4 -4
  131. package/dist/{chunk-5UZ6UCOZ.js → chunk-XUAMCLY4.js} +3 -3
  132. package/dist/{chunk-7HXZJAID.js → chunk-YGLY6QFV.js} +3 -3
  133. package/dist/{chunk-2OIQDTDC.js → chunk-ZPJ4QSGP.js} +2 -2
  134. package/dist/{chunk-3OY7KXHM.js → chunk-ZYLC7NFO.js} +3 -3
  135. package/dist/{cli-DMAFI0qt.d.ts → cli-DVUFIa5E.d.ts} +5 -5
  136. package/dist/cli.d.ts +13 -13
  137. package/dist/cli.js +42 -42
  138. package/dist/coding/pre-action-gate.d.ts +1 -1
  139. package/dist/compounding/engine.d.ts +2 -2
  140. package/dist/compounding/engine.js +13 -13
  141. package/dist/compounding/preference-consolidator.d.ts +1 -1
  142. package/dist/compression-optimizer.d.ts +1 -1
  143. package/dist/config.d.ts +1 -1
  144. package/dist/config.js +5 -5
  145. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  146. package/dist/connectors/codex-materialize-runner.js +12 -12
  147. package/dist/connectors/codex-materialize.d.ts +1 -1
  148. package/dist/connectors/index.d.ts +1 -1
  149. package/dist/connectors/index.js +12 -12
  150. package/dist/consolidation-provenance-check.d.ts +2 -2
  151. package/dist/consolidation-undo.d.ts +2 -2
  152. package/dist/content-hash.d.ts +12 -2
  153. package/dist/content-hash.js +9 -3
  154. package/dist/contradiction/index.d.ts +2 -2
  155. package/dist/contradiction/index.js +3 -3
  156. package/dist/converge-config.d.ts +1 -1
  157. package/dist/convergence-refresh.d.ts +2 -2
  158. package/dist/convergence-refresh.js +14 -14
  159. package/dist/conversation-index/backend.d.ts +1 -1
  160. package/dist/conversation-index/chunker.d.ts +1 -1
  161. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  162. package/dist/conversation-index/indexer.d.ts +1 -1
  163. package/dist/conversation-index/search.d.ts +1 -1
  164. package/dist/corpus-watermark.d.ts +1 -1
  165. package/dist/corpus-watermark.js +14 -14
  166. package/dist/day-summary.d.ts +1 -1
  167. package/dist/delinearize.d.ts +1 -1
  168. package/dist/dependency-propagation-config.d.ts +1 -1
  169. package/dist/{dependency-propagation-delivery-D63r24pH.d.ts → dependency-propagation-delivery-XzF76C9n.d.ts} +1 -1
  170. package/dist/direct-answer-wiring.d.ts +1 -1
  171. package/dist/direct-answer.d.ts +1 -1
  172. package/dist/embedding-fallback.d.ts +1 -1
  173. package/dist/enrichment/index.d.ts +1 -1
  174. package/dist/entity-retrieval.d.ts +2 -2
  175. package/dist/entity-retrieval.js +12 -12
  176. package/dist/entity-schema.d.ts +1 -1
  177. package/dist/explicit-capture.d.ts +9 -9
  178. package/dist/explicit-capture.js +8 -8
  179. package/dist/external-wiki-access.d.ts +11 -11
  180. package/dist/external-wiki-access.js +23 -23
  181. package/dist/external-wiki-collection-registration.d.ts +1 -1
  182. package/dist/external-wiki-collection.d.ts +1 -1
  183. package/dist/external-wiki-mcp-tools.d.ts +11 -11
  184. package/dist/extraction-error-classification.d.ts +1 -1
  185. package/dist/extraction-faithfulness.d.ts +1 -1
  186. package/dist/extraction-judge-telemetry.d.ts +1 -1
  187. package/dist/extraction-judge-training.d.ts +1 -1
  188. package/dist/extraction-judge.d.ts +1 -1
  189. package/dist/extraction-liveness.d.ts +1 -1
  190. package/dist/extraction-normalization.d.ts +1 -1
  191. package/dist/extraction-normalization.js +2 -2
  192. package/dist/extraction-prompt.d.ts +1 -1
  193. package/dist/extraction-source-grounding-rules.d.ts +1 -1
  194. package/dist/extraction-source-grounding.d.ts +1 -1
  195. package/dist/extraction-source-grounding.js +3 -3
  196. package/dist/extraction.d.ts +1 -1
  197. package/dist/extraction.js +16 -16
  198. package/dist/fallback-llm.d.ts +1 -1
  199. package/dist/{forget-HDS4N6CG.js → forget-EHGGJZLX.js} +3 -2
  200. package/dist/{forget-HDS4N6CG.js.map → forget-EHGGJZLX.js.map} +1 -1
  201. package/dist/graph-dashboard-diff.d.ts +1 -1
  202. package/dist/graph-dashboard-key.d.ts +1 -1
  203. package/dist/graph-dashboard-parser.d.ts +1 -1
  204. package/dist/graph-edge-reinforcement.d.ts +1 -1
  205. package/dist/graph-path-reconstruction.d.ts +1 -1
  206. package/dist/graph-path-scoring.d.ts +1 -1
  207. package/dist/graph-snapshot.d.ts +1 -1
  208. package/dist/graph.d.ts +1 -1
  209. package/dist/harmonic-construction.js +2 -2
  210. package/dist/harmonic-retrieval.js +4 -4
  211. package/dist/importance.d.ts +1 -1
  212. package/dist/importers/index.d.ts +1 -1
  213. package/dist/in-flight-reads.d.ts +1 -1
  214. package/dist/index.d.ts +777 -705
  215. package/dist/index.js +77 -67
  216. package/dist/intent.d.ts +1 -1
  217. package/dist/lcm/engine.d.ts +1 -1
  218. package/dist/lcm/index.d.ts +1 -1
  219. package/dist/lcm/tools.d.ts +1 -1
  220. package/dist/lifecycle.d.ts +1 -1
  221. package/dist/live-connectors-runner.d.ts +1 -1
  222. package/dist/local-llm.d.ts +1 -1
  223. package/dist/local-model-endpoint.d.ts +1 -1
  224. package/dist/maintenance/memory-governance.d.ts +1 -1
  225. package/dist/maintenance/memory-governance.js +12 -12
  226. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
  227. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +12 -12
  228. package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
  229. package/dist/maintenance/rebuild-memory-projection.js +13 -13
  230. package/dist/{maintenance-kBII2qUI.d.ts → maintenance-BJzNIKBE.d.ts} +3 -3
  231. package/dist/mcp-memory-inspector-app.d.ts +11 -11
  232. package/dist/memory-action-policy.d.ts +1 -1
  233. package/dist/memory-cache.d.ts +1 -1
  234. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  235. package/dist/memory-projection-store.d.ts +1 -1
  236. package/dist/memory-provenance.d.ts +1 -1
  237. package/dist/memory-snapshot.d.ts +1 -1
  238. package/dist/memory-worth-outcomes.d.ts +2 -2
  239. package/dist/models-json.d.ts +1 -1
  240. package/dist/namespaces/migrate.d.ts +3 -3
  241. package/dist/namespaces/migrate.js +13 -13
  242. package/dist/namespaces/principal.d.ts +1 -1
  243. package/dist/namespaces/search.d.ts +1 -1
  244. package/dist/namespaces/storage.d.ts +3 -3
  245. package/dist/namespaces/storage.js +12 -12
  246. package/dist/native-knowledge.d.ts +1 -1
  247. package/dist/offline-sync-impression-drain.d.ts +1 -1
  248. package/dist/operator-doctor-corpus.d.ts +1 -1
  249. package/dist/operator-doctor-corpus.js +15 -15
  250. package/dist/operator-doctor-replica.d.ts +1 -1
  251. package/dist/operator-toolkit.d.ts +3 -3
  252. package/dist/operator-toolkit.js +21 -21
  253. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  254. package/dist/orchestration/maintenance.d.ts +4 -4
  255. package/dist/orchestration/maintenance.js +17 -17
  256. package/dist/{orchestrator-DHy5LtTy.d.ts → orchestrator-BiXgBJBQ.d.ts} +8 -8
  257. package/dist/orchestrator.d.ts +9 -9
  258. package/dist/orchestrator.js +55 -55
  259. package/dist/patterns-cli.d.ts +1 -1
  260. package/dist/{pipeline-D21Rs6gW.d.ts → pipeline-sz_dcuLg.d.ts} +1 -1
  261. package/dist/policy-runtime.d.ts +1 -1
  262. package/dist/proactive-contention.d.ts +1 -1
  263. package/dist/provenance.d.ts +1 -1
  264. package/dist/provenance.js +2 -2
  265. package/dist/{qmd-Dhle1P4X.d.ts → qmd-eAWObK4M.d.ts} +1 -1
  266. package/dist/qmd-preflight.d.ts +2 -2
  267. package/dist/qmd-recall-cache.d.ts +1 -1
  268. package/dist/qmd.d.ts +2 -2
  269. package/dist/recall-concurrency-config.d.ts +1 -1
  270. package/dist/recall-disclosure-escalation.d.ts +1 -1
  271. package/dist/recall-explain-renderer.d.ts +1 -1
  272. package/dist/recall-memory-map.d.ts +1 -1
  273. package/dist/recall-planner-llm.d.ts +1 -1
  274. package/dist/recall-state.d.ts +1 -1
  275. package/dist/recall-tag-filter.d.ts +1 -1
  276. package/dist/recall-timings.d.ts +1 -1
  277. package/dist/recall-xray-cli.d.ts +1 -1
  278. package/dist/recall-xray-renderer.d.ts +1 -1
  279. package/dist/recall-xray.d.ts +1 -1
  280. package/dist/reconcile/cursor.d.ts +1 -1
  281. package/dist/reconcile/manifest.d.ts +4 -2
  282. package/dist/reconcile/manifest.js +7 -3
  283. package/dist/reconcile/plan.d.ts +1 -1
  284. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  285. package/dist/replay/normalizers/claude.d.ts +1 -1
  286. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  287. package/dist/replay/normalizers/shared.d.ts +1 -1
  288. package/dist/replay/runner.d.ts +1 -1
  289. package/dist/replay/types.d.ts +1 -1
  290. package/dist/replica-divergence.d.ts +1 -1
  291. package/dist/replica-peers-config.d.ts +1 -1
  292. package/dist/{resolution-MDPNOEG6.js → resolution-MMOD5WC4.js} +4 -4
  293. package/dist/resolve-auth-token.d.ts +1 -1
  294. package/dist/resume-bundles.js +6 -6
  295. package/dist/retrieval-agents.d.ts +2 -2
  296. package/dist/retrieval-tiers.d.ts +1 -1
  297. package/dist/routing/engine.d.ts +1 -1
  298. package/dist/routing/store.d.ts +1 -1
  299. package/dist/salvage-envelope.d.ts +1 -1
  300. package/dist/salvage-envelope.js +3 -3
  301. package/dist/schemas.d.ts +138 -138
  302. package/dist/{scope-profiles-NzGPDXLn.d.ts → scope-profiles-BBQsXuyj.d.ts} +1 -1
  303. package/dist/search/embed-helper.d.ts +1 -1
  304. package/dist/search/factory.d.ts +1 -1
  305. package/dist/search/index.d.ts +1 -1
  306. package/dist/search/lancedb-backend.d.ts +1 -1
  307. package/dist/search/meilisearch-backend.d.ts +1 -1
  308. package/dist/search/noop-backend.d.ts +1 -1
  309. package/dist/search/orama-backend.d.ts +1 -1
  310. package/dist/search/port.d.ts +1 -1
  311. package/dist/search/remote-backend.d.ts +1 -1
  312. package/dist/{semantic-consolidation-DLDKvyy5.d.ts → semantic-consolidation-HD8RkzVA.d.ts} +1 -1
  313. package/dist/semantic-consolidation.d.ts +2 -2
  314. package/dist/semantic-consolidation.js +13 -13
  315. package/dist/semantic-rule-promotion.js +12 -12
  316. package/dist/semantic-rule-verifier.d.ts +1 -1
  317. package/dist/semantic-rule-verifier.js +12 -12
  318. package/dist/{service-PT2_z7xm.d.ts → service-DIrtHHEg.d.ts} +2 -2
  319. package/dist/session-observer-bands.d.ts +1 -1
  320. package/dist/session-observer-state.d.ts +1 -1
  321. package/dist/shared-context/manager.d.ts +9 -9
  322. package/dist/signal.d.ts +1 -1
  323. package/dist/source-agent-qualifier.d.ts +1 -1
  324. package/dist/source-agent-qualifier.js +14 -14
  325. package/dist/{storage-BmVudhk4.d.ts → storage-DqCzXi56.d.ts} +37 -33
  326. package/dist/storage.d.ts +2 -2
  327. package/dist/storage.js +14 -14
  328. package/dist/structured-attributes.d.ts +3 -1
  329. package/dist/structured-attributes.js +3 -1
  330. package/dist/summarizer.d.ts +1 -1
  331. package/dist/summary-snapshot.d.ts +1 -1
  332. package/dist/temporal-supersession.d.ts +2 -2
  333. package/dist/temporal-timeline-recall.d.ts +1 -1
  334. package/dist/temporal-validity.d.ts +1 -1
  335. package/dist/threading.d.ts +1 -1
  336. package/dist/tier-migration.d.ts +2 -2
  337. package/dist/tier-routing.d.ts +1 -1
  338. package/dist/topics.d.ts +1 -1
  339. package/dist/transcript.d.ts +1 -1
  340. package/dist/transfer/types.d.ts +66 -66
  341. package/dist/trust-score-stage.d.ts +1 -1
  342. package/dist/trust-score.d.ts +1 -1
  343. package/dist/{types-CkiDUorT.d.ts → types-d_j4vC6N.d.ts} +26 -1
  344. package/dist/types.d.ts +1 -1
  345. package/dist/utility-runtime.d.ts +1 -1
  346. package/dist/verified-recall.js +12 -12
  347. package/dist/write-envelope.d.ts +1 -1
  348. package/dist/write-envelope.js +2 -2
  349. package/package.json +2 -2
  350. package/src/access-offline-manifest.ts +1 -0
  351. package/src/access-service-offline-file-content.test.ts +1 -0
  352. package/src/content-hash.ts +34 -1
  353. package/src/dedup/index.test.ts +56 -38
  354. package/src/dedup/index.ts +85 -38
  355. package/src/lifecycle/tombstones.test.ts +763 -16
  356. package/src/lifecycle/tombstones.ts +217 -50
  357. package/src/reconcile/manifest.test.ts +158 -1
  358. package/src/reconcile/manifest.ts +76 -22
  359. package/src/storage/content-hash-index.ts +56 -40
  360. package/src/storage/entity-ref-repair.ts +3 -3
  361. package/src/storage/memory-read-store.ts +9 -0
  362. package/src/storage/tombstone-blocked-capture-index.ts +4 -9
  363. package/src/storage-contract/content-hash.test.ts +13 -0
  364. package/src/storage-fact-hash-index-defer.test.ts +1 -1
  365. package/src/storage.ts +107 -93
  366. package/src/structured-attributes.ts +11 -0
  367. package/src/wearables/cleanup.test.ts +71 -0
  368. package/src/wearables/cleanup.ts +163 -16
  369. package/src/wearables/config.test.ts +62 -0
  370. package/src/wearables/config.ts +99 -5
  371. package/src/wearables/index.ts +10 -0
  372. package/src/wearables/pipeline.test.ts +36 -0
  373. package/src/wearables/pipeline.ts +18 -3
  374. package/src/wearables/redaction.test.ts +156 -0
  375. package/src/wearables/redaction.ts +102 -10
  376. package/src/wearables/text-language.ts +146 -0
  377. package/src/wearables/types.ts +26 -0
  378. package/dist/chunk-BQIFZ2MP.js.map +0 -1
  379. package/dist/chunk-DLG5V6ND.js.map +0 -1
  380. package/dist/chunk-IARXXZU4.js.map +0 -1
  381. package/dist/chunk-JJRVPWFY.js.map +0 -1
  382. package/dist/chunk-NDAH7BJ5.js.map +0 -1
  383. package/dist/chunk-R3DJVOSM.js.map +0 -1
  384. package/dist/chunk-VHFXP27N.js.map +0 -1
  385. package/dist/chunk-W5H6ZRPT.js +0 -14
  386. package/dist/chunk-W5H6ZRPT.js.map +0 -1
  387. package/dist/chunk-WD5B5AIE.js.map +0 -1
  388. /package/dist/{auto-sync-QFQGOEO2.js.map → auto-sync-TDHLZPVP.js.map} +0 -0
  389. /package/dist/{chunk-MLXZD4WM.js.map → chunk-2GFK22OZ.js.map} +0 -0
  390. /package/dist/{chunk-RANKJI5T.js.map → chunk-3JLPGJ43.js.map} +0 -0
  391. /package/dist/{chunk-X7XEDOSL.js.map → chunk-4IIFL7GC.js.map} +0 -0
  392. /package/dist/{chunk-GXSOUIA4.js.map → chunk-5WQMORU5.js.map} +0 -0
  393. /package/dist/{chunk-J272WKX2.js.map → chunk-6HPHEFXT.js.map} +0 -0
  394. /package/dist/{chunk-B6LZ4IBH.js.map → chunk-6LAPLZOS.js.map} +0 -0
  395. /package/dist/{chunk-VXCNQYTC.js.map → chunk-APPFNGX6.js.map} +0 -0
  396. /package/dist/{chunk-GXFSQ6SD.js.map → chunk-C4ZNGVK3.js.map} +0 -0
  397. /package/dist/{chunk-JSEJH2YQ.js.map → chunk-CGVHPCP2.js.map} +0 -0
  398. /package/dist/{chunk-TEMSWHQD.js.map → chunk-DZD576ZU.js.map} +0 -0
  399. /package/dist/{chunk-AMTEYNYK.js.map → chunk-EVWWT3KE.js.map} +0 -0
  400. /package/dist/{chunk-AXIDQG5A.js.map → chunk-G4T7L557.js.map} +0 -0
  401. /package/dist/{chunk-7M6NYQWB.js.map → chunk-G7Z6EENO.js.map} +0 -0
  402. /package/dist/{chunk-YXKVLYXU.js.map → chunk-HWRD23DW.js.map} +0 -0
  403. /package/dist/{chunk-SOEN6KCF.js.map → chunk-IBKR2TFR.js.map} +0 -0
  404. /package/dist/{chunk-F4EU6VUC.js.map → chunk-JDZPNCMA.js.map} +0 -0
  405. /package/dist/{chunk-D5YOZ66N.js.map → chunk-JV5YUBIZ.js.map} +0 -0
  406. /package/dist/{chunk-BAKXNQ3C.js.map → chunk-KRTY3ENI.js.map} +0 -0
  407. /package/dist/{chunk-NK5RGJ65.js.map → chunk-L4DLLKPN.js.map} +0 -0
  408. /package/dist/{chunk-VFSAWRMZ.js.map → chunk-LJS7KUPV.js.map} +0 -0
  409. /package/dist/{chunk-NOEMGE56.js.map → chunk-NH5IR2WM.js.map} +0 -0
  410. /package/dist/{chunk-5V6IXHPI.js.map → chunk-NSVDUN76.js.map} +0 -0
  411. /package/dist/{chunk-W7Y5JDVB.js.map → chunk-PDDTI7IC.js.map} +0 -0
  412. /package/dist/{chunk-QINJITDS.js.map → chunk-PX2LSHYM.js.map} +0 -0
  413. /package/dist/{chunk-HDIUYMSB.js.map → chunk-QWGE2PUH.js.map} +0 -0
  414. /package/dist/{chunk-X57M2Q4I.js.map → chunk-RDJRFDJY.js.map} +0 -0
  415. /package/dist/{chunk-RYLEDKQ2.js.map → chunk-RUMNY5DT.js.map} +0 -0
  416. /package/dist/{chunk-BKIFTXHR.js.map → chunk-RZ4XGKOJ.js.map} +0 -0
  417. /package/dist/{chunk-66SE3BNH.js.map → chunk-SUPEYLH2.js.map} +0 -0
  418. /package/dist/{chunk-BF6SXNQJ.js.map → chunk-T2JHZWH6.js.map} +0 -0
  419. /package/dist/{chunk-QPOHMO6M.js.map → chunk-UES4BHA2.js.map} +0 -0
  420. /package/dist/{chunk-XAPMEU3K.js.map → chunk-UU7TRFNE.js.map} +0 -0
  421. /package/dist/{chunk-QBSFOKY6.js.map → chunk-VOELBUS4.js.map} +0 -0
  422. /package/dist/{chunk-2LY6GIIE.js.map → chunk-WDBX6VER.js.map} +0 -0
  423. /package/dist/{chunk-BMEYMKWX.js.map → chunk-WM65LU67.js.map} +0 -0
  424. /package/dist/{chunk-WLRD2TXF.js.map → chunk-WOPA3SGI.js.map} +0 -0
  425. /package/dist/{chunk-5UZ6UCOZ.js.map → chunk-XUAMCLY4.js.map} +0 -0
  426. /package/dist/{chunk-7HXZJAID.js.map → chunk-YGLY6QFV.js.map} +0 -0
  427. /package/dist/{chunk-2OIQDTDC.js.map → chunk-ZPJ4QSGP.js.map} +0 -0
  428. /package/dist/{chunk-3OY7KXHM.js.map → chunk-ZYLC7NFO.js.map} +0 -0
  429. /package/dist/{resolution-MDPNOEG6.js.map → resolution-MMOD5WC4.js.map} +0 -0
@@ -277,3 +277,65 @@ test("fusion knobs reject non-positive integers", () => {
277
277
  test("fusion block must be an object when present", () => {
278
278
  assert.throws(() => parseWearablesConfig({ fusion: "on" }), /must be an object/);
279
279
  });
280
+
281
+ test("off-the-record marker phrases and filler tokens parse", () => {
282
+ const parsed = parseWearablesConfig({
283
+ offTheRecordMarkers: {
284
+ start: [" poza protokołem "],
285
+ end: ["z powrotem do protokołu"],
286
+ },
287
+ fillerTokens: ["bueno", "那个"],
288
+ });
289
+ assert.deepEqual(parsed.offTheRecordMarkers.start, ["poza protokołem"]);
290
+ assert.deepEqual(parsed.offTheRecordMarkers.end, ["z powrotem do protokołu"]);
291
+ assert.equal(parsed.offTheRecordMarkers.useBuiltIns, true);
292
+ assert.deepEqual(parsed.fillerTokens, ["bueno", "那个"]);
293
+ });
294
+
295
+ test("marker and filler lists reject unusable entries loudly", () => {
296
+ assert.throws(
297
+ () => parseWearablesConfig({ offTheRecordMarkers: { start: "poza" } }),
298
+ /offTheRecordMarkers\.start must be an array of strings/,
299
+ );
300
+ assert.throws(
301
+ () => parseWearablesConfig({ offTheRecordMarkers: { end: [" "] } }),
302
+ /offTheRecordMarkers\.end\[0\] must be a non-empty string/,
303
+ );
304
+ assert.throws(
305
+ () => parseWearablesConfig({ fillerTokens: ["a".repeat(65)] }),
306
+ /fillerTokens\[0\] exceeds 64 characters/,
307
+ );
308
+ assert.throws(
309
+ () => parseWearablesConfig({ offTheRecordMarkers: { start: ["a".repeat(129)] } }),
310
+ /offTheRecordMarkers\.start\[0\] exceeds 128 characters/,
311
+ );
312
+ assert.throws(
313
+ () => parseWearablesConfig({ offTheRecordMarkers: [] }),
314
+ /offTheRecordMarkers must be an object/,
315
+ );
316
+ });
317
+
318
+ test("disabling built-in markers without a start phrase is rejected, not silent", () => {
319
+ assert.throws(
320
+ () =>
321
+ parseWearablesConfig({
322
+ offTheRecordEnabled: true,
323
+ offTheRecordMarkers: { useBuiltIns: false },
324
+ }),
325
+ /useBuiltIns is false but no start phrase is configured/,
326
+ );
327
+ // Turning the whole feature off is the documented escape hatch.
328
+ const parsed = parseWearablesConfig({
329
+ offTheRecordEnabled: false,
330
+ offTheRecordMarkers: { useBuiltIns: false },
331
+ });
332
+ assert.equal(parsed.offTheRecordEnabled, false);
333
+ assert.equal(parsed.offTheRecordMarkers.useBuiltIns, false);
334
+ });
335
+
336
+ test("an unknown off-the-record marker key is rejected, not dropped", () => {
337
+ assert.throws(
338
+ () => parseWearablesConfig({ offTheRecordMarkers: { starts: ["private"] } }),
339
+ /offTheRecordMarkers has unknown key\(s\) starts/,
340
+ );
341
+ });
@@ -13,6 +13,7 @@ import type { ImportanceLevel } from "../types.js";
13
13
  import { compileCorrectionRules } from "./corrections.js";
14
14
  import { compileRedactionPatterns } from "./redaction.js";
15
15
  import type {
16
+ OffTheRecordMarkerSettings,
16
17
  WearableCleanupSettings,
17
18
  WearableCorrectionRule,
18
19
  WearableMemoryMode,
@@ -111,6 +112,8 @@ export function defaultWearablesConfig(): WearablesConfig {
111
112
  redactionEnabled: true,
112
113
  redactionPatterns: [],
113
114
  offTheRecordEnabled: true,
115
+ offTheRecordMarkers: { start: [], end: [], useBuiltIns: true },
116
+ fillerTokens: [],
114
117
  digestEnabled: true,
115
118
  autoSyncEnabled: DEFAULT_AUTO_SYNC_ENABLED,
116
119
  autoSyncIntervalMinutes: DEFAULT_AUTO_SYNC_INTERVAL_MINUTES,
@@ -134,6 +137,37 @@ function requireObject(
134
137
  return value as Record<string, unknown>;
135
138
  }
136
139
 
140
+ /** Longest accepted off-the-record marker phrase. */
141
+ const MAX_MARKER_PHRASE_LENGTH = 128;
142
+ /** Longest accepted filler token. */
143
+ const MAX_FILLER_TOKEN_LENGTH = 64;
144
+
145
+ /**
146
+ * Parse an optional array of non-empty phrases. Rejects a non-array, a
147
+ * non-string entry, a blank entry, and an over-long entry — an operator
148
+ * privacy phrase that is silently discarded is the failure this parser
149
+ * exists to prevent (issue #2196).
150
+ */
151
+ function parsePhraseList(
152
+ value: unknown,
153
+ keyPath: string,
154
+ maxLength: number,
155
+ ): string[] {
156
+ if (value === undefined) return [];
157
+ if (!Array.isArray(value)) {
158
+ throw new Error(`${keyPath} must be an array of strings`);
159
+ }
160
+ return value.map((entry, index) => {
161
+ if (typeof entry !== "string" || entry.trim().length === 0) {
162
+ throw new Error(`${keyPath}[${index}] must be a non-empty string`);
163
+ }
164
+ if (entry.length > maxLength) {
165
+ throw new Error(`${keyPath}[${index}] exceeds ${maxLength} characters`);
166
+ }
167
+ return entry.trim();
168
+ });
169
+ }
170
+
137
171
  function parseBool(
138
172
  value: unknown,
139
173
  keyPath: string,
@@ -411,6 +445,68 @@ export function parseWearablesConfig(value: unknown): WearablesConfig {
411
445
  compileRedactionPatterns(redactionPatterns);
412
446
  }
413
447
 
448
+ const offTheRecordEnabled = parseBool(
449
+ raw.offTheRecordEnabled,
450
+ "wearables.offTheRecordEnabled",
451
+ defaults.offTheRecordEnabled,
452
+ );
453
+ const rawMarkers =
454
+ raw.offTheRecordMarkers === undefined
455
+ ? {}
456
+ : requireObject(raw.offTheRecordMarkers, "wearables.offTheRecordMarkers");
457
+ // A typo such as `starts:` would otherwise be dropped without a word,
458
+ // and the operator would believe a private span is protected. Core and
459
+ // standalone callers never see OpenClaw's JSON schema, so the check
460
+ // belongs here. Keys are compared literally so the config-contract
461
+ // extractor can still enumerate this parser's surface.
462
+ const {
463
+ start: rawMarkerStart,
464
+ end: rawMarkerEnd,
465
+ useBuiltIns: rawMarkerUseBuiltIns,
466
+ ...unknownMarkerKeys
467
+ } = rawMarkers;
468
+ const unknownMarkerNames = Object.keys(unknownMarkerKeys);
469
+ if (unknownMarkerNames.length > 0) {
470
+ throw new Error(
471
+ `wearables.offTheRecordMarkers has unknown key(s) ${unknownMarkerNames.sort().join(", ")} — valid keys are start, end, useBuiltIns`,
472
+ );
473
+ }
474
+ const offTheRecordMarkers: OffTheRecordMarkerSettings = {
475
+ start: parsePhraseList(
476
+ rawMarkerStart,
477
+ "wearables.offTheRecordMarkers.start",
478
+ MAX_MARKER_PHRASE_LENGTH,
479
+ ),
480
+ end: parsePhraseList(
481
+ rawMarkerEnd,
482
+ "wearables.offTheRecordMarkers.end",
483
+ MAX_MARKER_PHRASE_LENGTH,
484
+ ),
485
+ useBuiltIns: parseBool(
486
+ rawMarkerUseBuiltIns,
487
+ "wearables.offTheRecordMarkers.useBuiltIns",
488
+ defaults.offTheRecordMarkers.useBuiltIns,
489
+ ),
490
+ };
491
+ // A silent no-op is the exact failure this feature exists to remove
492
+ // (issue #2196): turning the built-ins off without supplying a start
493
+ // phrase would leave the gate on and matching nothing.
494
+ if (
495
+ offTheRecordEnabled &&
496
+ !offTheRecordMarkers.useBuiltIns &&
497
+ offTheRecordMarkers.start.length === 0
498
+ ) {
499
+ throw new Error(
500
+ "wearables.offTheRecordMarkers.useBuiltIns is false but no start phrase is configured — add wearables.offTheRecordMarkers.start, or set wearables.offTheRecordEnabled to false",
501
+ );
502
+ }
503
+
504
+ const fillerTokens = parsePhraseList(
505
+ raw.fillerTokens,
506
+ "wearables.fillerTokens",
507
+ MAX_FILLER_TOKEN_LENGTH,
508
+ );
509
+
414
510
  const parseBoundedInt = (
415
511
  value: unknown,
416
512
  name: string,
@@ -488,11 +584,9 @@ export function parseWearablesConfig(value: unknown): WearablesConfig {
488
584
  defaults.redactionEnabled,
489
585
  ),
490
586
  redactionPatterns,
491
- offTheRecordEnabled: parseBool(
492
- raw.offTheRecordEnabled,
493
- "wearables.offTheRecordEnabled",
494
- defaults.offTheRecordEnabled,
495
- ),
587
+ offTheRecordEnabled,
588
+ offTheRecordMarkers,
589
+ fillerTokens,
496
590
  digestEnabled: parseBool(
497
591
  raw.digestEnabled,
498
592
  "wearables.digestEnabled",
@@ -35,10 +35,20 @@ export {
35
35
  } from "./cleanup.js";
36
36
  export {
37
37
  applyOffTheRecord,
38
+ BUILT_IN_OFF_THE_RECORD_END,
39
+ BUILT_IN_OFF_THE_RECORD_START,
40
+ compileOffTheRecordMarkers,
38
41
  compileRedactionPatterns,
39
42
  redactText,
40
43
  REDACTION_PLACEHOLDER,
44
+ type CompiledOffTheRecordMarkers,
45
+ type OffTheRecordMarkerInput,
41
46
  } from "./redaction.js";
47
+ export {
48
+ buildPhraseMatcher,
49
+ detectScriptHints,
50
+ type ScriptHint,
51
+ } from "./text-language.js";
42
52
  export {
43
53
  applyCorrections,
44
54
  compileCorrectionRule,
@@ -1039,3 +1039,39 @@ test("a transcript write failure prevents the sync watermark from advancing", as
1039
1039
  rmSync(memoryDir, { recursive: true, force: true });
1040
1040
  }
1041
1041
  });
1042
+
1043
+ test("configured non-English off-the-record markers elide a span end to end", async () => {
1044
+ const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-otr-"));
1045
+ try {
1046
+ const byDate = {
1047
+ "2026-06-11": [
1048
+ makeConversation("c1", "2026-06-11", [
1049
+ { speaker: "user", isWearer: true, text: "えーと、ここからはオフレコでお願いします。" },
1050
+ { speaker: "Speaker 2", text: "買収の金額は非公開です。" },
1051
+ { speaker: "user", isWearer: true, text: "オンレコに戻ります。" },
1052
+ { speaker: "Speaker 2", text: "bueno, 天気の話をしましょう。" },
1053
+ ]),
1054
+ ],
1055
+ };
1056
+ const { deps, written } = makeDeps(memoryDir);
1057
+ const summary = await syncWearableSource(
1058
+ fakeConnector(byDate),
1059
+ settings({ memoryMode: "off" }),
1060
+ config({ offTheRecordEnabled: true, fillerTokens: ["bueno"] }),
1061
+ { days: 1 },
1062
+ deps,
1063
+ );
1064
+
1065
+ assert.equal(summary.transcriptsWritten.length, 1);
1066
+ const body = written[0]?.serialized ?? "";
1067
+ assert.ok(!body.includes("買収の金額は非公開です"), "off-record segment must not be stored");
1068
+ assert.ok(body.includes("[off the record"), "elision placeholder is visible");
1069
+ assert.ok(body.includes("[back on the record]"), "span closes on the Japanese end marker");
1070
+ assert.ok(body.includes("天気の話をしましょう"), "on-record speech survives");
1071
+ assert.ok(!body.includes("えーと"), "Japanese filler is stripped");
1072
+ assert.ok(!body.includes("bueno"), "configured filler token is stripped");
1073
+ assert.equal(summary.segmentsDropped, 1);
1074
+ } finally {
1075
+ rmSync(memoryDir, { recursive: true, force: true });
1076
+ }
1077
+ });
@@ -37,7 +37,13 @@ import {
37
37
  type WearableMemoryGenDeps,
38
38
  } from "./memory-gen.js";
39
39
  import { tokenizeDayBody, type CorroborationContext } from "./trust.js";
40
- import { applyOffTheRecord, compileRedactionPatterns, redactText } from "./redaction.js";
40
+ import {
41
+ applyOffTheRecord,
42
+ compileOffTheRecordMarkers,
43
+ compileRedactionPatterns,
44
+ redactText,
45
+ type CompiledOffTheRecordMarkers,
46
+ } from "./redaction.js";
41
47
  import { loadSpeakerRegistry } from "./speakers.js";
42
48
  import {
43
49
  loadSyncState,
@@ -259,6 +265,7 @@ function cleanDay(
259
265
  config: WearablesConfig,
260
266
  userRedaction: RegExp[],
261
267
  correctionRules: CompiledCorrectionRule[],
268
+ offTheRecordMarkers: CompiledOffTheRecordMarkers,
262
269
  ): CleanedDay {
263
270
  const { offTheRecordEnabled, redactionEnabled } = config;
264
271
  const out: CleanedDay = {
@@ -271,11 +278,15 @@ function cleanDay(
271
278
  for (const conversation of raw) {
272
279
  let current = conversation;
273
280
  if (offTheRecordEnabled) {
274
- const otr = applyOffTheRecord(current);
281
+ const otr = applyOffTheRecord(current, offTheRecordMarkers);
275
282
  current = otr.conversation;
276
283
  out.segmentsDropped += otr.droppedSegments;
277
284
  }
278
- const cleaned = cleanConversation(current, settings.cleanup);
285
+ const cleaned = cleanConversation(
286
+ current,
287
+ settings.cleanup,
288
+ config.fillerTokens,
289
+ );
279
290
  current = cleaned.conversation;
280
291
  out.segmentsDropped += cleaned.droppedSegments;
281
292
 
@@ -337,6 +348,9 @@ export async function syncWearableSource(
337
348
  ...compileCorrectionRules(stateRules, "state corrections"),
338
349
  ];
339
350
  const userRedaction = compileRedactionPatterns(config.redactionPatterns);
351
+ const offTheRecordMarkers = compileOffTheRecordMarkers(
352
+ config.offTheRecordMarkers,
353
+ );
340
354
 
341
355
  let syncState = await loadSyncState(deps.memoryDir);
342
356
  const previousState = syncState.sources[connector.id];
@@ -360,6 +374,7 @@ export async function syncWearableSource(
360
374
  config,
361
375
  userRedaction,
362
376
  correctionRules,
377
+ offTheRecordMarkers,
363
378
  );
364
379
  summary.conversations += cleaned.conversations.length;
365
380
  summary.segmentsKept += cleaned.segmentsKept;
@@ -3,6 +3,7 @@ import { test } from "node:test";
3
3
 
4
4
  import {
5
5
  applyOffTheRecord,
6
+ compileOffTheRecordMarkers,
6
7
  compileRedactionPatterns,
7
8
  redactText,
8
9
  REDACTION_PLACEHOLDER,
@@ -92,3 +93,158 @@ test("conversations without the marker pass through untouched", () => {
92
93
  );
93
94
  assert.equal(result.droppedSegments, 0);
94
95
  });
96
+
97
+ test("built-in markers elide a Japanese off-the-record span", () => {
98
+ const result = applyOffTheRecord(
99
+ conversation([
100
+ "ここからはオフレコでお願いします。",
101
+ "来週の買収は金曜に完了します。",
102
+ "オンレコに戻ります。",
103
+ "昼食はおいしかったです。",
104
+ ]),
105
+ );
106
+ assert.deepEqual(
107
+ result.conversation.segments.map((segment) => segment.text),
108
+ [
109
+ "[off the record — segment elided]",
110
+ "[back on the record]",
111
+ "昼食はおいしかったです。",
112
+ ],
113
+ );
114
+ assert.equal(result.droppedSegments, 1);
115
+ });
116
+
117
+ test("built-in markers elide a Korean span through conversation end", () => {
118
+ const result = applyOffTheRecord(
119
+ conversation(["지금부터 오프더레코드입니다", "비밀 계약 조건입니다"]),
120
+ );
121
+ assert.equal(result.conversation.segments.length, 1);
122
+ assert.equal(result.droppedSegments, 1);
123
+ });
124
+
125
+ test("configured markers extend the built-in phrases", () => {
126
+ const markers = compileOffTheRecordMarkers({
127
+ start: ["poza protokołem"],
128
+ end: ["z powrotem do protokołu"],
129
+ });
130
+ const result = applyOffTheRecord(
131
+ conversation([
132
+ "To jest poza protokołem.",
133
+ "Tajna informacja.",
134
+ "Wracamy z powrotem do protokołu.",
135
+ "Normalna rozmowa.",
136
+ ]),
137
+ markers,
138
+ );
139
+ assert.deepEqual(
140
+ result.conversation.segments.map((segment) => segment.text),
141
+ [
142
+ "[off the record — segment elided]",
143
+ "[back on the record]",
144
+ "Normalna rozmowa.",
145
+ ],
146
+ );
147
+ assert.equal(result.droppedSegments, 1);
148
+ // The built-in English phrase still applies alongside the custom set.
149
+ assert.equal(
150
+ applyOffTheRecord(conversation(["off the record", "secret"]), markers)
151
+ .droppedSegments,
152
+ 1,
153
+ );
154
+ });
155
+
156
+ test("useBuiltIns false honors only the configured phrases", () => {
157
+ const markers = compileOffTheRecordMarkers({
158
+ start: ["poza protokołem"],
159
+ useBuiltIns: false,
160
+ });
161
+ const result = applyOffTheRecord(
162
+ conversation(["Let me say this off the record.", "The merger closes Friday."]),
163
+ markers,
164
+ );
165
+ assert.equal(result.droppedSegments, 0);
166
+ assert.equal(result.conversation.segments.length, 2);
167
+ // Non-vacuous: the configured phrase must still start an elision, so
168
+ // this cannot pass by discarding `start` along with the built-ins.
169
+ assert.equal(
170
+ applyOffTheRecord(conversation(["poza protokołem", "secret"]), markers)
171
+ .droppedSegments,
172
+ 1,
173
+ );
174
+ });
175
+
176
+ test("a Latin marker phrase never matches inside a longer word", () => {
177
+ const result = applyOffTheRecord(
178
+ conversation(["We discussed hors microphone placement.", "Normal talk."]),
179
+ );
180
+ assert.equal(result.droppedSegments, 0);
181
+ assert.equal(result.conversation.segments.length, 2);
182
+ });
183
+
184
+ test("an Arabic marker does not match inside a longer Arabic word", () => {
185
+ assert.equal(
186
+ applyOffTheRecord(conversation(["لدينا بدون تسجيلات كثيرة", "كلام عادي"]))
187
+ .droppedSegments,
188
+ 0,
189
+ );
190
+ assert.equal(
191
+ applyOffTheRecord(conversation(["هذا بدون تسجيل من فضلك", "سر"])).droppedSegments,
192
+ 1,
193
+ );
194
+ });
195
+
196
+ test("an Arabic proclitic still reaches the built-in marker", () => {
197
+ // Arabic writes و/ف/ب attached to the next word, so a leading guard
198
+ // would silently disable the marker for ordinary phrasing.
199
+ assert.equal(
200
+ applyOffTheRecord(conversation(["وبدون تسجيل من فضلك", "سر"])).droppedSegments,
201
+ 1,
202
+ );
203
+ });
204
+
205
+ test("a decomposed accent is a word character, not a boundary", () => {
206
+ const markers = compileOffTheRecordMarkers({
207
+ start: ["cafe"],
208
+ useBuiltIns: false,
209
+ });
210
+ const decomposed = `cafe\u0301teria is open`;
211
+ assert.equal(applyOffTheRecord(conversation([decomposed, "x"]), markers).droppedSegments, 0);
212
+ assert.equal(applyOffTheRecord(conversation(["cafe is open", "x"]), markers).droppedSegments, 1);
213
+ });
214
+
215
+ test("a decomposed transcript still reaches a composed marker", () => {
216
+ // Some ASR output is canonically decomposed. Without normalizing the
217
+ // probe, `nicht fürs protokoll` would never match and the span would be
218
+ // persisted.
219
+ const decomposed = "nicht fu\u0308rs protokoll, bitte".normalize("NFD");
220
+ const result = applyOffTheRecord(conversation([decomposed, "geheim", "wieder fürs protokoll"]));
221
+ assert.equal(result.droppedSegments, 1);
222
+ assert.equal(result.conversation.segments[0]?.text, "[off the record — segment elided]");
223
+ assert.equal(result.conversation.segments[1]?.text, "[back on the record]");
224
+ });
225
+
226
+ test("a marker never fires at the tail of a longer word", () => {
227
+ // Korean particles attach at the END, so the leading guard is safe there
228
+ // while the trailing edge stays open.
229
+ const korean = compileOffTheRecordMarkers({ start: ["기록"], useBuiltIns: false });
230
+ assert.equal(applyOffTheRecord(conversation(["신기록 입니다", "x"]), korean).droppedSegments, 0);
231
+ assert.equal(applyOffTheRecord(conversation(["기록을 멈춰주세요", "비밀"]), korean).droppedSegments, 1);
232
+
233
+ // Arabic admits ONE word-initial proclitic and nothing longer.
234
+ const arabic = compileOffTheRecordMarkers({ start: ["خاص"], useBuiltIns: false });
235
+ assert.equal(applyOffTheRecord(conversation(["أشخاص كثيرون", "x"]), arabic).droppedSegments, 0);
236
+ assert.equal(applyOffTheRecord(conversation(["وخاص جدا", "سر"]), arabic).droppedSegments, 1);
237
+ });
238
+
239
+ test("a Turkish marker matches an uppercase transcript", () => {
240
+ // JS `/iu` folding is locale-independent: it never equates `ı` with `I`.
241
+ const markers = compileOffTheRecordMarkers({ start: ["kayıt dışı"], useBuiltIns: false });
242
+ assert.equal(
243
+ applyOffTheRecord(conversation(["BU KAYIT DIŞI", "gizli bilgi"]), markers).droppedSegments,
244
+ 1,
245
+ );
246
+ assert.equal(
247
+ applyOffTheRecord(conversation(["bu kayıt dışı", "gizli bilgi"]), markers).droppedSegments,
248
+ 1,
249
+ );
250
+ });
@@ -12,7 +12,11 @@
12
12
  * to stay safely outside polynomial-ReDoS territory.
13
13
  */
14
14
 
15
- import type { WearableConversation } from "./types.js";
15
+ import { buildPhraseMatcher, foldForMatching } from "./text-language.js";
16
+ import type {
17
+ OffTheRecordMarkerSettings,
18
+ WearableConversation,
19
+ } from "./types.js";
16
20
 
17
21
  export const REDACTION_PLACEHOLDER = "[redacted]";
18
22
 
@@ -105,8 +109,88 @@ export function compileRedactionPatterns(patterns: string[]): RegExp[] {
105
109
  });
106
110
  }
107
111
 
108
- const OFF_THE_RECORD = /\boff\s+the\s+record\b/i;
109
- const BACK_ON_THE_RECORD = /\b(?:back\s+)?on\s+the\s+record\b/i;
112
+ /**
113
+ * Built-in phrases that BEGIN an off-the-record span.
114
+ *
115
+ * Conservative by design: a false positive elides real transcript
116
+ * content. Every phrase is a fixed multi-word expression, or a loanword
117
+ * that only means "off the record" (issue #2196). Operators extend this
118
+ * list through `wearables.offTheRecordMarkers.start`.
119
+ */
120
+ export const BUILT_IN_OFF_THE_RECORD_START: readonly string[] = [
121
+ "off the record",
122
+ "fuera de registro",
123
+ "extraoficialmente",
124
+ "fora de registro",
125
+ "fora do registro",
126
+ "hors micro",
127
+ "nicht fürs protokoll",
128
+ "nicht für das protokoll",
129
+ "fuori registro",
130
+ "オフレコ",
131
+ "오프더레코드",
132
+ "不要记录",
133
+ "не для протокола",
134
+ "بدون تسجيل",
135
+ ];
136
+
137
+ /**
138
+ * Built-in phrases that END an off-the-record span.
139
+ *
140
+ * Shorter than the start list on purpose. A start phrase with no
141
+ * matching end phrase elides through the end of the conversation, which
142
+ * is the fail-closed direction; an over-eager end phrase would leak. So
143
+ * a language only appears here when the phrase is unambiguous.
144
+ */
145
+ export const BUILT_IN_OFF_THE_RECORD_END: readonly string[] = [
146
+ "back on the record",
147
+ "on the record",
148
+ "de nuevo en registro",
149
+ "de volta ao registro",
150
+ "wieder fürs protokoll",
151
+ "wieder für das protokoll",
152
+ "オンレコ",
153
+ "온더레코드",
154
+ ];
155
+
156
+ /**
157
+ * Loose form of the parsed `OffTheRecordMarkerSettings`: callers that
158
+ * only override one field pass a partial, so every property is
159
+ * optional and read-only here.
160
+ */
161
+ export type OffTheRecordMarkerInput = {
162
+ readonly [K in keyof OffTheRecordMarkerSettings]?: K extends "useBuiltIns"
163
+ ? boolean
164
+ : readonly string[];
165
+ };
166
+
167
+ export interface CompiledOffTheRecordMarkers {
168
+ start: RegExp | null;
169
+ end: RegExp | null;
170
+ }
171
+
172
+ /**
173
+ * Compile marker settings into matchers. Omitting `settings` yields the
174
+ * built-in lists, so callers that never configured markers keep the
175
+ * previous behavior plus the new languages.
176
+ */
177
+ export function compileOffTheRecordMarkers(
178
+ settings?: OffTheRecordMarkerInput,
179
+ ): CompiledOffTheRecordMarkers {
180
+ const useBuiltIns = settings?.useBuiltIns !== false;
181
+ const start = [
182
+ ...(useBuiltIns ? BUILT_IN_OFF_THE_RECORD_START : []),
183
+ ...(settings?.start ?? []),
184
+ ];
185
+ const end = [
186
+ ...(useBuiltIns ? BUILT_IN_OFF_THE_RECORD_END : []),
187
+ ...(settings?.end ?? []),
188
+ ];
189
+ return {
190
+ start: buildPhraseMatcher(start),
191
+ end: buildPhraseMatcher(end),
192
+ };
193
+ }
110
194
 
111
195
  export interface OffTheRecordResult {
112
196
  conversation: WearableConversation;
@@ -114,20 +198,28 @@ export interface OffTheRecordResult {
114
198
  }
115
199
 
116
200
  /**
117
- * Drop segments between a spoken "off the record" marker and the next
118
- * "(back) on the record" marker (or conversation end). The marker
119
- * segments themselves are kept, with the off-record span replaced by a
120
- * visible placeholder so the transcript shows that content was elided
121
- * by request rather than lost.
201
+ * Drop segments between a spoken off-the-record marker and the next
202
+ * back-on-the-record marker (or conversation end). The marker segments
203
+ * themselves are kept, with the off-record span replaced by a visible
204
+ * placeholder so the transcript shows that content was elided by
205
+ * request rather than lost.
122
206
  */
123
207
  export function applyOffTheRecord(
124
208
  conversation: WearableConversation,
209
+ markers: CompiledOffTheRecordMarkers = compileOffTheRecordMarkers(),
125
210
  ): OffTheRecordResult {
211
+ if (!markers.start) {
212
+ return { conversation, droppedSegments: 0 };
213
+ }
126
214
  let offRecord = false;
127
215
  let droppedSegments = 0;
128
216
  const segments = [];
129
217
  for (const segment of conversation.segments) {
130
- if (!offRecord && OFF_THE_RECORD.test(segment.text)) {
218
+ // Matched folded, stored as written: the transcript keeps the ASR's own
219
+ // bytes while a decomposed or Turkic-uppercase spelling still reaches
220
+ // the marker.
221
+ const probe = foldForMatching(segment.text);
222
+ if (!offRecord && markers.start.test(probe)) {
131
223
  offRecord = true;
132
224
  segments.push({
133
225
  ...segment,
@@ -136,7 +228,7 @@ export function applyOffTheRecord(
136
228
  continue;
137
229
  }
138
230
  if (offRecord) {
139
- if (BACK_ON_THE_RECORD.test(segment.text)) {
231
+ if (markers.end?.test(probe)) {
140
232
  offRecord = false;
141
233
  segments.push({
142
234
  ...segment,