@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.
- package/dist/access-admin-ops-surface.d.ts +11 -11
- package/dist/access-admin-ops-surface.js +22 -22
- package/dist/access-authorization-probe.d.ts +11 -11
- package/dist/access-authorization-probe.js +23 -23
- package/dist/access-boundary.d.ts +11 -11
- package/dist/access-boundary.js +22 -22
- package/dist/access-cli.js +55 -55
- package/dist/access-coding-context-resolution.d.ts +1 -1
- package/dist/access-extraction-force-flush.d.ts +11 -11
- package/dist/access-extraction-force-flush.js +22 -22
- package/dist/access-health-types.d.ts +1 -1
- package/dist/access-http-lcm-compaction.d.ts +11 -11
- package/dist/access-http-lifecycle-flush.d.ts +11 -11
- package/dist/access-http-offline-stream.d.ts +11 -11
- package/dist/access-http-query.js +22 -22
- package/dist/access-http.d.ts +11 -11
- package/dist/access-http.js +29 -29
- package/dist/access-identity-continuity-surface.d.ts +9 -9
- package/dist/access-identity-continuity-surface.js +22 -22
- package/dist/access-lcm-surface.d.ts +11 -11
- package/dist/access-lcm-surface.js +22 -22
- package/dist/access-mcp.d.ts +11 -11
- package/dist/access-mcp.js +27 -27
- package/dist/access-memory-search-fanout.d.ts +2 -2
- package/dist/{access-namespace-preflight-Ds6CGlCP.d.ts → access-namespace-preflight-C7n4tgWC.d.ts} +1 -1
- package/dist/access-namespace-preflight.d.ts +3 -3
- package/dist/access-namespace-preflight.js +22 -22
- package/dist/access-observe-write-surface.d.ts +11 -11
- package/dist/access-observe-write-surface.js +22 -22
- package/dist/access-offline-manifest.d.ts +9 -9
- package/dist/access-offline-manifest.js +8 -4
- package/dist/access-operations-batch.js +24 -24
- package/dist/access-operations.d.ts +14 -14
- package/dist/access-operations.js +26 -26
- package/dist/access-recall-concurrency.d.ts +11 -11
- package/dist/access-recall-concurrency.js +22 -22
- package/dist/access-recall-response.d.ts +11 -11
- package/dist/access-recall-response.js +22 -22
- package/dist/access-recall-surface.d.ts +11 -11
- package/dist/access-recall-surface.js +22 -22
- package/dist/access-schema.d.ts +88 -88
- package/dist/{access-service-Cbk3bNbD.d.ts → access-service-CXvbxLK-.d.ts} +7 -7
- package/dist/access-service-helpers.d.ts +11 -11
- package/dist/access-service.d.ts +11 -11
- package/dist/access-service.js +22 -22
- package/dist/access-surface-catalog.d.ts +11 -11
- package/dist/access-wearables-meetings-surface.d.ts +3 -3
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +5 -5
- package/dist/ambient-provenance.d.ts +1 -1
- package/dist/artifact-search.d.ts +1 -1
- package/dist/{auto-sync-QFQGOEO2.js → auto-sync-TDHLZPVP.js} +7 -7
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +9 -9
- package/dist/briefing.d.ts +2 -2
- package/dist/briefing.js +12 -12
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer-turn-helpers.d.ts +1 -1
- package/dist/buffer.d.ts +2 -2
- package/dist/bulk-import/index.d.ts +3 -3
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{catalog-D3B4RDy1.d.ts → catalog-BOOxl-I2.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +13 -13
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-MLXZD4WM.js → chunk-2GFK22OZ.js} +2 -2
- package/dist/{chunk-RANKJI5T.js → chunk-3JLPGJ43.js} +2 -2
- package/dist/{chunk-O5FFBO2M.js → chunk-47P3RYZP.js} +70 -8
- package/dist/{chunk-O5FFBO2M.js.map → chunk-47P3RYZP.js.map} +1 -1
- package/dist/{chunk-X7XEDOSL.js → chunk-4IIFL7GC.js} +8 -8
- package/dist/chunk-5FVVY2EB.js +27 -0
- package/dist/chunk-5FVVY2EB.js.map +1 -0
- package/dist/{chunk-GXSOUIA4.js → chunk-5WQMORU5.js} +4 -4
- package/dist/{chunk-J272WKX2.js → chunk-6HPHEFXT.js} +2 -2
- package/dist/{chunk-B6LZ4IBH.js → chunk-6LAPLZOS.js} +2 -2
- package/dist/{chunk-WD5B5AIE.js → chunk-6R4G7N4S.js} +118 -173
- package/dist/chunk-6R4G7N4S.js.map +1 -0
- package/dist/{chunk-VXCNQYTC.js → chunk-APPFNGX6.js} +2 -2
- package/dist/{chunk-GXFSQ6SD.js → chunk-C4ZNGVK3.js} +2 -2
- package/dist/{chunk-JSEJH2YQ.js → chunk-CGVHPCP2.js} +2 -2
- package/dist/{chunk-BQIFZ2MP.js → chunk-CX5SW2EW.js} +41 -24
- package/dist/chunk-CX5SW2EW.js.map +1 -0
- package/dist/{chunk-RMHMUUBW.js → chunk-CYF6WVE7.js} +105 -49
- package/dist/{chunk-RMHMUUBW.js.map → chunk-CYF6WVE7.js.map} +1 -1
- package/dist/{chunk-TEMSWHQD.js → chunk-DZD576ZU.js} +5 -5
- package/dist/{chunk-AMTEYNYK.js → chunk-EVWWT3KE.js} +4 -4
- package/dist/{chunk-P2KBDJJ6.js → chunk-G2LUMAUF.js} +4 -3
- package/dist/{chunk-P2KBDJJ6.js.map → chunk-G2LUMAUF.js.map} +1 -1
- package/dist/{chunk-AXIDQG5A.js → chunk-G4T7L557.js} +5 -5
- package/dist/{chunk-7M6NYQWB.js → chunk-G7Z6EENO.js} +4 -4
- package/dist/{chunk-IARXXZU4.js → chunk-HOIRB77A.js} +99 -20
- package/dist/chunk-HOIRB77A.js.map +1 -0
- package/dist/{chunk-NDAH7BJ5.js → chunk-HVP33QVW.js} +103 -6
- package/dist/chunk-HVP33QVW.js.map +1 -0
- package/dist/{chunk-YXKVLYXU.js → chunk-HWRD23DW.js} +2 -2
- package/dist/{chunk-SOEN6KCF.js → chunk-IBKR2TFR.js} +2 -2
- package/dist/{chunk-R3DJVOSM.js → chunk-JDSVLVCG.js} +8 -1
- package/dist/chunk-JDSVLVCG.js.map +1 -0
- package/dist/{chunk-F4EU6VUC.js → chunk-JDZPNCMA.js} +2 -2
- package/dist/{chunk-D5YOZ66N.js → chunk-JV5YUBIZ.js} +20 -20
- package/dist/{chunk-DLG5V6ND.js → chunk-K4QS7LXU.js} +4 -7
- package/dist/chunk-K4QS7LXU.js.map +1 -0
- package/dist/{chunk-BAKXNQ3C.js → chunk-KRTY3ENI.js} +2 -2
- package/dist/{chunk-NK5RGJ65.js → chunk-L4DLLKPN.js} +2 -2
- package/dist/{chunk-VFSAWRMZ.js → chunk-LJS7KUPV.js} +2 -2
- package/dist/{chunk-JJRVPWFY.js → chunk-MLPTFODV.js} +166 -37
- package/dist/chunk-MLPTFODV.js.map +1 -0
- package/dist/{chunk-NOEMGE56.js → chunk-NH5IR2WM.js} +2 -2
- package/dist/{chunk-5V6IXHPI.js → chunk-NSVDUN76.js} +2 -2
- package/dist/{chunk-W7Y5JDVB.js → chunk-PDDTI7IC.js} +4 -4
- package/dist/{chunk-QINJITDS.js → chunk-PX2LSHYM.js} +2 -2
- package/dist/{chunk-HDIUYMSB.js → chunk-QWGE2PUH.js} +6 -6
- package/dist/{chunk-X57M2Q4I.js → chunk-RDJRFDJY.js} +3 -3
- package/dist/{chunk-RYLEDKQ2.js → chunk-RUMNY5DT.js} +3 -3
- package/dist/{chunk-BKIFTXHR.js → chunk-RZ4XGKOJ.js} +2 -2
- package/dist/{chunk-66SE3BNH.js → chunk-SUPEYLH2.js} +2 -2
- package/dist/{chunk-BF6SXNQJ.js → chunk-T2JHZWH6.js} +20 -20
- package/dist/{chunk-VHFXP27N.js → chunk-THPPCISA.js} +60 -23
- package/dist/chunk-THPPCISA.js.map +1 -0
- package/dist/{chunk-QPOHMO6M.js → chunk-UES4BHA2.js} +2 -2
- package/dist/{chunk-XAPMEU3K.js → chunk-UU7TRFNE.js} +2 -2
- package/dist/{chunk-QBSFOKY6.js → chunk-VOELBUS4.js} +4 -4
- package/dist/{chunk-2LY6GIIE.js → chunk-WDBX6VER.js} +2 -2
- package/dist/{chunk-BMEYMKWX.js → chunk-WM65LU67.js} +2 -2
- package/dist/{chunk-WLRD2TXF.js → chunk-WOPA3SGI.js} +4 -4
- package/dist/{chunk-5UZ6UCOZ.js → chunk-XUAMCLY4.js} +3 -3
- package/dist/{chunk-7HXZJAID.js → chunk-YGLY6QFV.js} +3 -3
- package/dist/{chunk-2OIQDTDC.js → chunk-ZPJ4QSGP.js} +2 -2
- package/dist/{chunk-3OY7KXHM.js → chunk-ZYLC7NFO.js} +3 -3
- package/dist/{cli-DMAFI0qt.d.ts → cli-DVUFIa5E.d.ts} +5 -5
- package/dist/cli.d.ts +13 -13
- package/dist/cli.js +42 -42
- package/dist/coding/pre-action-gate.d.ts +1 -1
- package/dist/compounding/engine.d.ts +2 -2
- package/dist/compounding/engine.js +13 -13
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +5 -5
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +12 -12
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +12 -12
- package/dist/consolidation-provenance-check.d.ts +2 -2
- package/dist/consolidation-undo.d.ts +2 -2
- package/dist/content-hash.d.ts +12 -2
- package/dist/content-hash.js +9 -3
- package/dist/contradiction/index.d.ts +2 -2
- package/dist/contradiction/index.js +3 -3
- package/dist/converge-config.d.ts +1 -1
- package/dist/convergence-refresh.d.ts +2 -2
- package/dist/convergence-refresh.js +14 -14
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/corpus-watermark.d.ts +1 -1
- package/dist/corpus-watermark.js +14 -14
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/dependency-propagation-config.d.ts +1 -1
- package/dist/{dependency-propagation-delivery-D63r24pH.d.ts → dependency-propagation-delivery-XzF76C9n.d.ts} +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +2 -2
- package/dist/entity-retrieval.js +12 -12
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +9 -9
- package/dist/explicit-capture.js +8 -8
- package/dist/external-wiki-access.d.ts +11 -11
- package/dist/external-wiki-access.js +23 -23
- package/dist/external-wiki-collection-registration.d.ts +1 -1
- package/dist/external-wiki-collection.d.ts +1 -1
- package/dist/external-wiki-mcp-tools.d.ts +11 -11
- package/dist/extraction-error-classification.d.ts +1 -1
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction-liveness.d.ts +1 -1
- package/dist/extraction-normalization.d.ts +1 -1
- package/dist/extraction-normalization.js +2 -2
- package/dist/extraction-prompt.d.ts +1 -1
- package/dist/extraction-source-grounding-rules.d.ts +1 -1
- package/dist/extraction-source-grounding.d.ts +1 -1
- package/dist/extraction-source-grounding.js +3 -3
- package/dist/extraction.d.ts +1 -1
- package/dist/extraction.js +16 -16
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{forget-HDS4N6CG.js → forget-EHGGJZLX.js} +3 -2
- package/dist/{forget-HDS4N6CG.js.map → forget-EHGGJZLX.js.map} +1 -1
- package/dist/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-path-reconstruction.d.ts +1 -1
- package/dist/graph-path-scoring.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/harmonic-construction.js +2 -2
- package/dist/harmonic-retrieval.js +4 -4
- package/dist/importance.d.ts +1 -1
- package/dist/importers/index.d.ts +1 -1
- package/dist/in-flight-reads.d.ts +1 -1
- package/dist/index.d.ts +777 -705
- package/dist/index.js +77 -67
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +12 -12
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +12 -12
- package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-projection.js +13 -13
- package/dist/{maintenance-kBII2qUI.d.ts → maintenance-BJzNIKBE.d.ts} +3 -3
- package/dist/mcp-memory-inspector-app.d.ts +11 -11
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-snapshot.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +2 -2
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +3 -3
- package/dist/namespaces/migrate.js +13 -13
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/storage.d.ts +3 -3
- package/dist/namespaces/storage.js +12 -12
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/offline-sync-impression-drain.d.ts +1 -1
- package/dist/operator-doctor-corpus.d.ts +1 -1
- package/dist/operator-doctor-corpus.js +15 -15
- package/dist/operator-doctor-replica.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +3 -3
- package/dist/operator-toolkit.js +21 -21
- package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
- package/dist/orchestration/maintenance.d.ts +4 -4
- package/dist/orchestration/maintenance.js +17 -17
- package/dist/{orchestrator-DHy5LtTy.d.ts → orchestrator-BiXgBJBQ.d.ts} +8 -8
- package/dist/orchestrator.d.ts +9 -9
- package/dist/orchestrator.js +55 -55
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/{pipeline-D21Rs6gW.d.ts → pipeline-sz_dcuLg.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/proactive-contention.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/provenance.js +2 -2
- package/dist/{qmd-Dhle1P4X.d.ts → qmd-eAWObK4M.d.ts} +1 -1
- package/dist/qmd-preflight.d.ts +2 -2
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +2 -2
- package/dist/recall-concurrency-config.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-memory-map.d.ts +1 -1
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-timings.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray.d.ts +1 -1
- package/dist/reconcile/cursor.d.ts +1 -1
- package/dist/reconcile/manifest.d.ts +4 -2
- package/dist/reconcile/manifest.js +7 -3
- package/dist/reconcile/plan.d.ts +1 -1
- package/dist/replay/normalizers/chatgpt.d.ts +1 -1
- package/dist/replay/normalizers/claude.d.ts +1 -1
- package/dist/replay/normalizers/openclaw.d.ts +1 -1
- package/dist/replay/normalizers/shared.d.ts +1 -1
- package/dist/replay/runner.d.ts +1 -1
- package/dist/replay/types.d.ts +1 -1
- package/dist/replica-divergence.d.ts +1 -1
- package/dist/replica-peers-config.d.ts +1 -1
- package/dist/{resolution-MDPNOEG6.js → resolution-MMOD5WC4.js} +4 -4
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +6 -6
- package/dist/retrieval-agents.d.ts +2 -2
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/salvage-envelope.d.ts +1 -1
- package/dist/salvage-envelope.js +3 -3
- package/dist/schemas.d.ts +138 -138
- package/dist/{scope-profiles-NzGPDXLn.d.ts → scope-profiles-BBQsXuyj.d.ts} +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/index.d.ts +1 -1
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-DLDKvyy5.d.ts → semantic-consolidation-HD8RkzVA.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +13 -13
- package/dist/semantic-rule-promotion.js +12 -12
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +12 -12
- package/dist/{service-PT2_z7xm.d.ts → service-DIrtHHEg.d.ts} +2 -2
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +9 -9
- package/dist/signal.d.ts +1 -1
- package/dist/source-agent-qualifier.d.ts +1 -1
- package/dist/source-agent-qualifier.js +14 -14
- package/dist/{storage-BmVudhk4.d.ts → storage-DqCzXi56.d.ts} +37 -33
- package/dist/storage.d.ts +2 -2
- package/dist/storage.js +14 -14
- package/dist/structured-attributes.d.ts +3 -1
- package/dist/structured-attributes.js +3 -1
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +2 -2
- package/dist/temporal-timeline-recall.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +2 -2
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/types.d.ts +66 -66
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-CkiDUorT.d.ts → types-d_j4vC6N.d.ts} +26 -1
- package/dist/types.d.ts +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +12 -12
- package/dist/write-envelope.d.ts +1 -1
- package/dist/write-envelope.js +2 -2
- package/package.json +2 -2
- package/src/access-offline-manifest.ts +1 -0
- package/src/access-service-offline-file-content.test.ts +1 -0
- package/src/content-hash.ts +34 -1
- package/src/dedup/index.test.ts +56 -38
- package/src/dedup/index.ts +85 -38
- package/src/lifecycle/tombstones.test.ts +763 -16
- package/src/lifecycle/tombstones.ts +217 -50
- package/src/reconcile/manifest.test.ts +158 -1
- package/src/reconcile/manifest.ts +76 -22
- package/src/storage/content-hash-index.ts +56 -40
- package/src/storage/entity-ref-repair.ts +3 -3
- package/src/storage/memory-read-store.ts +9 -0
- package/src/storage/tombstone-blocked-capture-index.ts +4 -9
- package/src/storage-contract/content-hash.test.ts +13 -0
- package/src/storage-fact-hash-index-defer.test.ts +1 -1
- package/src/storage.ts +107 -93
- package/src/structured-attributes.ts +11 -0
- package/src/wearables/cleanup.test.ts +71 -0
- package/src/wearables/cleanup.ts +163 -16
- package/src/wearables/config.test.ts +62 -0
- package/src/wearables/config.ts +99 -5
- package/src/wearables/index.ts +10 -0
- package/src/wearables/pipeline.test.ts +36 -0
- package/src/wearables/pipeline.ts +18 -3
- package/src/wearables/redaction.test.ts +156 -0
- package/src/wearables/redaction.ts +102 -10
- package/src/wearables/text-language.ts +146 -0
- package/src/wearables/types.ts +26 -0
- package/dist/chunk-BQIFZ2MP.js.map +0 -1
- package/dist/chunk-DLG5V6ND.js.map +0 -1
- package/dist/chunk-IARXXZU4.js.map +0 -1
- package/dist/chunk-JJRVPWFY.js.map +0 -1
- package/dist/chunk-NDAH7BJ5.js.map +0 -1
- package/dist/chunk-R3DJVOSM.js.map +0 -1
- package/dist/chunk-VHFXP27N.js.map +0 -1
- package/dist/chunk-W5H6ZRPT.js +0 -14
- package/dist/chunk-W5H6ZRPT.js.map +0 -1
- package/dist/chunk-WD5B5AIE.js.map +0 -1
- /package/dist/{auto-sync-QFQGOEO2.js.map → auto-sync-TDHLZPVP.js.map} +0 -0
- /package/dist/{chunk-MLXZD4WM.js.map → chunk-2GFK22OZ.js.map} +0 -0
- /package/dist/{chunk-RANKJI5T.js.map → chunk-3JLPGJ43.js.map} +0 -0
- /package/dist/{chunk-X7XEDOSL.js.map → chunk-4IIFL7GC.js.map} +0 -0
- /package/dist/{chunk-GXSOUIA4.js.map → chunk-5WQMORU5.js.map} +0 -0
- /package/dist/{chunk-J272WKX2.js.map → chunk-6HPHEFXT.js.map} +0 -0
- /package/dist/{chunk-B6LZ4IBH.js.map → chunk-6LAPLZOS.js.map} +0 -0
- /package/dist/{chunk-VXCNQYTC.js.map → chunk-APPFNGX6.js.map} +0 -0
- /package/dist/{chunk-GXFSQ6SD.js.map → chunk-C4ZNGVK3.js.map} +0 -0
- /package/dist/{chunk-JSEJH2YQ.js.map → chunk-CGVHPCP2.js.map} +0 -0
- /package/dist/{chunk-TEMSWHQD.js.map → chunk-DZD576ZU.js.map} +0 -0
- /package/dist/{chunk-AMTEYNYK.js.map → chunk-EVWWT3KE.js.map} +0 -0
- /package/dist/{chunk-AXIDQG5A.js.map → chunk-G4T7L557.js.map} +0 -0
- /package/dist/{chunk-7M6NYQWB.js.map → chunk-G7Z6EENO.js.map} +0 -0
- /package/dist/{chunk-YXKVLYXU.js.map → chunk-HWRD23DW.js.map} +0 -0
- /package/dist/{chunk-SOEN6KCF.js.map → chunk-IBKR2TFR.js.map} +0 -0
- /package/dist/{chunk-F4EU6VUC.js.map → chunk-JDZPNCMA.js.map} +0 -0
- /package/dist/{chunk-D5YOZ66N.js.map → chunk-JV5YUBIZ.js.map} +0 -0
- /package/dist/{chunk-BAKXNQ3C.js.map → chunk-KRTY3ENI.js.map} +0 -0
- /package/dist/{chunk-NK5RGJ65.js.map → chunk-L4DLLKPN.js.map} +0 -0
- /package/dist/{chunk-VFSAWRMZ.js.map → chunk-LJS7KUPV.js.map} +0 -0
- /package/dist/{chunk-NOEMGE56.js.map → chunk-NH5IR2WM.js.map} +0 -0
- /package/dist/{chunk-5V6IXHPI.js.map → chunk-NSVDUN76.js.map} +0 -0
- /package/dist/{chunk-W7Y5JDVB.js.map → chunk-PDDTI7IC.js.map} +0 -0
- /package/dist/{chunk-QINJITDS.js.map → chunk-PX2LSHYM.js.map} +0 -0
- /package/dist/{chunk-HDIUYMSB.js.map → chunk-QWGE2PUH.js.map} +0 -0
- /package/dist/{chunk-X57M2Q4I.js.map → chunk-RDJRFDJY.js.map} +0 -0
- /package/dist/{chunk-RYLEDKQ2.js.map → chunk-RUMNY5DT.js.map} +0 -0
- /package/dist/{chunk-BKIFTXHR.js.map → chunk-RZ4XGKOJ.js.map} +0 -0
- /package/dist/{chunk-66SE3BNH.js.map → chunk-SUPEYLH2.js.map} +0 -0
- /package/dist/{chunk-BF6SXNQJ.js.map → chunk-T2JHZWH6.js.map} +0 -0
- /package/dist/{chunk-QPOHMO6M.js.map → chunk-UES4BHA2.js.map} +0 -0
- /package/dist/{chunk-XAPMEU3K.js.map → chunk-UU7TRFNE.js.map} +0 -0
- /package/dist/{chunk-QBSFOKY6.js.map → chunk-VOELBUS4.js.map} +0 -0
- /package/dist/{chunk-2LY6GIIE.js.map → chunk-WDBX6VER.js.map} +0 -0
- /package/dist/{chunk-BMEYMKWX.js.map → chunk-WM65LU67.js.map} +0 -0
- /package/dist/{chunk-WLRD2TXF.js.map → chunk-WOPA3SGI.js.map} +0 -0
- /package/dist/{chunk-5UZ6UCOZ.js.map → chunk-XUAMCLY4.js.map} +0 -0
- /package/dist/{chunk-7HXZJAID.js.map → chunk-YGLY6QFV.js.map} +0 -0
- /package/dist/{chunk-2OIQDTDC.js.map → chunk-ZPJ4QSGP.js.map} +0 -0
- /package/dist/{chunk-3OY7KXHM.js.map → chunk-ZYLC7NFO.js.map} +0 -0
- /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
|
+
});
|
package/src/wearables/config.ts
CHANGED
|
@@ -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
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
defaults.offTheRecordEnabled,
|
|
495
|
-
),
|
|
587
|
+
offTheRecordEnabled,
|
|
588
|
+
offTheRecordMarkers,
|
|
589
|
+
fillerTokens,
|
|
496
590
|
digestEnabled: parseBool(
|
|
497
591
|
raw.digestEnabled,
|
|
498
592
|
"wearables.digestEnabled",
|
package/src/wearables/index.ts
CHANGED
|
@@ -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 {
|
|
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(
|
|
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
|
|
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
|
-
|
|
109
|
-
|
|
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
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
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
|
-
|
|
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 (
|
|
231
|
+
if (markers.end?.test(probe)) {
|
|
140
232
|
offRecord = false;
|
|
141
233
|
segments.push({
|
|
142
234
|
...segment,
|