@remnic/core 9.4.2 → 9.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.d.ts +6 -6
- package/dist/access-admin-ops-surface.js +28 -28
- package/dist/access-boundary.d.ts +27 -8
- package/dist/access-boundary.js +33 -29
- package/dist/access-cli.js +83 -81
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +52 -6
- package/dist/access-http.js +40 -38
- package/dist/access-identity-continuity-surface.d.ts +5 -5
- package/dist/access-identity-continuity-surface.js +28 -28
- package/dist/access-lcm-surface.d.ts +6 -6
- package/dist/access-lcm-surface.js +28 -28
- package/dist/access-mcp.d.ts +16 -6
- package/dist/access-mcp.js +37 -35
- package/dist/access-observe-write-surface.d.ts +6 -6
- package/dist/access-observe-write-surface.js +28 -28
- package/dist/access-operations-batch.js +33 -32
- package/dist/access-operations.d.ts +9 -9
- package/dist/access-operations.js +35 -34
- package/dist/access-recall-surface.d.ts +6 -6
- package/dist/access-recall-surface.js +28 -28
- package/dist/access-schema.d.ts +4 -4
- package/dist/access-schema.js +4 -4
- package/dist/{access-service-m-2VO_Ig.d.ts → access-service-CXJaql3A.d.ts} +4 -4
- package/dist/access-service.d.ts +6 -6
- package/dist/access-service.js +28 -28
- package/dist/access-surface-catalog.d.ts +6 -6
- package/dist/access-surface-catalog.js +1 -0
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/access-token-capabilities.d.ts +174 -0
- package/dist/access-token-capabilities.js +33 -0
- 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 +2 -2
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/{auto-sync-HMHQHUAG.js → auto-sync-4A5YBWYO.js} +4 -4
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +5 -5
- package/dist/briefing.d.ts +2 -2
- package/dist/briefing.js +9 -9
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +2 -2
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{capsule-crypto-CZJSLEFG.js → capsule-crypto-RAARDY3M.js} +2 -2
- package/dist/capsule-crypto-RAARDY3M.js.map +1 -0
- package/dist/{catalog-BxWokdC2.d.ts → catalog-C9cu67Ig.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-behavior.js +5 -5
- package/dist/causal-chain.js +3 -3
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +13 -13
- package/dist/causal-retrieval.js +3 -3
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-6ZIETXZ2.js → chunk-23JFRB73.js} +2 -2
- package/dist/{chunk-IC6QX5TC.js → chunk-3XIY7MDQ.js} +6 -6
- package/dist/{chunk-FZGGZAAU.js → chunk-4GNEDXDI.js} +146 -2
- package/dist/chunk-4GNEDXDI.js.map +1 -0
- package/dist/{chunk-LOR6ZM5R.js → chunk-4LV23CHK.js} +2 -2
- package/dist/{chunk-E2SPGGUI.js → chunk-4OKLXWDZ.js} +13 -6
- package/dist/chunk-4OKLXWDZ.js.map +1 -0
- package/dist/{chunk-AER6MT24.js → chunk-6SXVCD7W.js} +2 -2
- package/dist/{chunk-OHDJ34CC.js → chunk-72WG5QKN.js} +2 -2
- package/dist/{chunk-CZAZX7JP.js → chunk-7MW3CVLD.js} +4 -4
- package/dist/{chunk-CXLNZXG5.js → chunk-7XGSIUP5.js} +1 -1
- package/dist/{chunk-CZAA5N5S.js → chunk-AYZJID4S.js} +2 -2
- package/dist/{chunk-M7XQSUBB.js → chunk-B3ABVQ36.js} +116 -9
- package/dist/chunk-B3ABVQ36.js.map +1 -0
- package/dist/{chunk-6BISDEN6.js → chunk-BEM4D2QG.js} +14 -14
- package/dist/{chunk-ZPOROUTX.js → chunk-CYWA2CXR.js} +5 -5
- package/dist/{chunk-M5QKGHCR.js → chunk-CYWCKFCL.js} +5 -5
- package/dist/chunk-D7FOKIY5.js +173 -0
- package/dist/chunk-D7FOKIY5.js.map +1 -0
- package/dist/{chunk-4BFRBP45.js → chunk-DEG5ULFJ.js} +2 -2
- package/dist/{chunk-JLR3JSTL.js → chunk-DS7MM2ES.js} +3 -3
- package/dist/{chunk-2BFLDH5F.js → chunk-EEIROWEG.js} +2 -2
- package/dist/{chunk-5QZDMSSN.js → chunk-FKCRGLRD.js} +237 -82
- package/dist/{chunk-5QZDMSSN.js.map → chunk-FKCRGLRD.js.map} +1 -1
- package/dist/{chunk-X4A62ET7.js → chunk-H2DXDQCQ.js} +17 -17
- package/dist/{chunk-27FLVMDC.js → chunk-HLN7RROI.js} +7 -7
- package/dist/chunk-IC2TCZJ2.js +54 -0
- package/dist/chunk-IC2TCZJ2.js.map +1 -0
- package/dist/{chunk-2U2A5HF4.js → chunk-J3XHPFEU.js} +6 -6
- package/dist/{chunk-AKUCU7CI.js → chunk-JI5AVXYO.js} +5 -5
- package/dist/{chunk-KCJ5H6XQ.js → chunk-JSNVWNGX.js} +2 -2
- package/dist/{chunk-7Z2DZNWN.js → chunk-KL4UAXUY.js} +43 -2
- package/dist/{chunk-7Z2DZNWN.js.map → chunk-KL4UAXUY.js.map} +1 -1
- package/dist/{chunk-FLVL4L3L.js → chunk-KTULX4YO.js} +4 -4
- package/dist/{chunk-ILFJ7AVS.js → chunk-LADFWYHY.js} +35 -21
- package/dist/chunk-LADFWYHY.js.map +1 -0
- package/dist/{chunk-437S3G37.js → chunk-LNQUAJNC.js} +1 -1
- package/dist/{chunk-EPGWWCZF.js → chunk-LR5DZ56F.js} +6 -6
- package/dist/{chunk-YDSWRD33.js → chunk-N3JDIRBW.js} +4 -4
- package/dist/{chunk-OUMTBFSQ.js → chunk-NKCPUHCI.js} +5 -5
- package/dist/{chunk-7QWMTOIF.js → chunk-P454KXD6.js} +112 -34
- package/dist/chunk-P454KXD6.js.map +1 -0
- package/dist/{chunk-DR6PEJU3.js → chunk-PCLIWBGD.js} +44 -59
- package/dist/chunk-PCLIWBGD.js.map +1 -0
- package/dist/{chunk-BGTAL46Y.js → chunk-PNLFN2PZ.js} +2 -2
- package/dist/{chunk-P6SYWE4G.js → chunk-PVUJ6WVV.js} +10 -10
- package/dist/{chunk-YPEMNNNY.js → chunk-RGK27ELN.js} +6 -6
- package/dist/{chunk-2QSZNTDO.js → chunk-RKNJBZ55.js} +4 -4
- package/dist/{chunk-66FLU5BT.js → chunk-SIRPLF54.js} +6 -6
- package/dist/{chunk-M7GGMGMZ.js → chunk-SVOHGUMI.js} +1202 -126
- package/dist/chunk-SVOHGUMI.js.map +1 -0
- package/dist/{chunk-HRCACVHL.js → chunk-TLUDFPZL.js} +2 -2
- package/dist/{chunk-2FOD2YY3.js → chunk-UFUKHGZD.js} +236 -103
- package/dist/chunk-UFUKHGZD.js.map +1 -0
- package/dist/{chunk-2MR3MFQB.js → chunk-UPSAT4PY.js} +2 -2
- package/dist/{chunk-4P4626RY.js → chunk-UTRJFDP2.js} +4 -4
- package/dist/{chunk-CGS7UKWM.js → chunk-VCW74PNH.js} +2 -2
- package/dist/{chunk-5GPPACXK.js → chunk-VSMRDBFT.js} +2 -1
- package/dist/{chunk-NG7YBCJO.js → chunk-XCL57M6Q.js} +4 -4
- package/dist/{chunk-CLX5VDNY.js → chunk-Y4RKAQZ7.js} +4 -4
- package/dist/{chunk-DGYCWCQ7.js → chunk-Y7UNNAZD.js} +2 -2
- package/dist/{chunk-EZ25VE3G.js → chunk-YNDLCWXS.js} +4 -4
- package/dist/{chunk-XN3TUUX2.js → chunk-YQB32INI.js} +2 -2
- package/dist/{cli-EiBBOt3r.d.ts → cli-CqdPA6z9.d.ts} +3 -3
- package/dist/cli.d.ts +7 -7
- package/dist/cli.js +61 -59
- package/dist/compounding/engine.d.ts +2 -2
- package/dist/compounding/engine.js +9 -9
- 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 +2 -2
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +9 -9
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +13 -11
- package/dist/consolidation-provenance-check.d.ts +2 -2
- package/dist/consolidation-undo.d.ts +2 -2
- package/dist/contradiction/index.d.ts +2 -2
- package/dist/contradiction/index.js +4 -4
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/backend.js +2 -2
- 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/dashboard-runtime.js +2 -2
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.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 +9 -9
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +5 -5
- 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.d.ts +1 -1
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{first-start-migration-DY6YCRC2.js → first-start-migration-VBJQL6OI.js} +5 -5
- 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-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +271 -17
- package/dist/index.js +163 -99
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +2 -2
- 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 +10 -10
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +9 -9
- package/dist/maintenance/rebuild-memory-projection.js +11 -11
- package/dist/mcp-memory-inspector-app.d.ts +6 -6
- package/dist/mcp-read-only-tools.d.ts +16 -0
- package/dist/mcp-read-only-tools.js +8 -0
- package/dist/mcp-read-only-tools.js.map +1 -0
- 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-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 +19 -19
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +10 -10
- package/dist/namespaces/storage.d.ts +3 -3
- package/dist/namespaces/storage.js +9 -9
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +2 -2
- package/dist/operator-toolkit.js +25 -25
- package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +11 -11
- package/dist/{orchestrator-B2Y28Z9t.d.ts → orchestrator-CWFrT8AF.d.ts} +91 -60
- package/dist/orchestrator.d.ts +5 -5
- package/dist/orchestrator.js +85 -83
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/{qmd-Db9mOkzB.d.ts → qmd--fnPMK60.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-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.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/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +4 -4
- 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/schemas.d.ts +22 -22
- package/dist/search/document-scanner.js +2 -2
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +9 -9
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +13 -13
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/lancedb-backend.js +3 -3
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.js +3 -3
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/orama-backend.js +3 -3
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-BDVIbLRw.d.ts → semantic-consolidation-O5lMZ4LG.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +11 -11
- package/dist/semantic-rule-promotion.js +9 -9
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +10 -10
- 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 +1 -1
- package/dist/signal.d.ts +1 -1
- package/dist/storage-C1maCNbc.d.ts +1591 -0
- package/dist/storage.d.ts +5 -1239
- package/dist/storage.js +8 -8
- 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-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/{tier-stats-TG6GUZ6N.js → tier-stats-5XF4UBRQ.js} +4 -4
- package/dist/tokens.d.ts +17 -2
- package/dist/tokens.js +3 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +3 -3
- package/dist/transfer/capsule-import.js +3 -3
- package/dist/transfer/types.d.ts +12 -12
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-ljkd82rn.d.ts → types-CGgcNWCG.d.ts} +31 -1
- package/dist/types.d.ts +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +10 -10
- package/package.json +2 -2
- package/src/access-boundary.test.ts +62 -0
- package/src/access-boundary.ts +165 -126
- package/src/access-http.test.ts +781 -0
- package/src/access-http.ts +334 -108
- package/src/access-mcp.test.ts +264 -0
- package/src/access-mcp.ts +45 -70
- package/src/access-operations-batch.ts +35 -12
- package/src/access-surface-catalog.test.ts +22 -14
- package/src/access-surface-catalog.ts +6 -3
- package/src/access-token-capabilities.test.ts +844 -0
- package/src/access-token-capabilities.ts +391 -0
- package/src/chat/chat-mcp.test.ts +232 -0
- package/src/chat/chat-session.ts +34 -0
- package/src/cli-wearables-fuse.test.ts +197 -0
- package/src/cli.ts +25 -0
- package/src/index.ts +14 -0
- package/src/mcp-read-only-tools.ts +61 -0
- package/src/storage.ts +16 -0
- package/src/tokens.ts +32 -3
- package/src/wearables/cli.test.ts +102 -0
- package/src/wearables/cli.ts +65 -0
- package/src/wearables/config.test.ts +32 -0
- package/src/wearables/config.ts +52 -0
- package/src/wearables/day-store.test.ts +246 -0
- package/src/wearables/day-store.ts +272 -3
- package/src/wearables/fusion/cluster.ts +230 -0
- package/src/wearables/fusion/fuse.ts +218 -0
- package/src/wearables/fusion/fusion.test.ts +2575 -0
- package/src/wearables/fusion/index.ts +50 -0
- package/src/wearables/fusion/reconcile.ts +831 -0
- package/src/wearables/fusion/reconstruct.ts +251 -0
- package/src/wearables/fusion/store.ts +480 -0
- package/src/wearables/fusion/types.ts +228 -0
- package/src/wearables/index.ts +41 -0
- package/src/wearables/service.test.ts +1030 -2
- package/src/wearables/service.ts +258 -16
- package/src/wearables/speakers.test.ts +128 -0
- package/src/wearables/speakers.ts +58 -4
- package/src/wearables/storage-io.test.ts +146 -0
- package/src/wearables/types.ts +31 -0
- package/dist/chunk-2FOD2YY3.js.map +0 -1
- package/dist/chunk-7QWMTOIF.js.map +0 -1
- package/dist/chunk-DR6PEJU3.js.map +0 -1
- package/dist/chunk-E2SPGGUI.js.map +0 -1
- package/dist/chunk-FZGGZAAU.js.map +0 -1
- package/dist/chunk-ILFJ7AVS.js.map +0 -1
- package/dist/chunk-M7GGMGMZ.js.map +0 -1
- package/dist/chunk-M7XQSUBB.js.map +0 -1
- /package/dist/{capsule-crypto-CZJSLEFG.js.map → access-token-capabilities.js.map} +0 -0
- /package/dist/{auto-sync-HMHQHUAG.js.map → auto-sync-4A5YBWYO.js.map} +0 -0
- /package/dist/{chunk-6ZIETXZ2.js.map → chunk-23JFRB73.js.map} +0 -0
- /package/dist/{chunk-IC6QX5TC.js.map → chunk-3XIY7MDQ.js.map} +0 -0
- /package/dist/{chunk-LOR6ZM5R.js.map → chunk-4LV23CHK.js.map} +0 -0
- /package/dist/{chunk-AER6MT24.js.map → chunk-6SXVCD7W.js.map} +0 -0
- /package/dist/{chunk-OHDJ34CC.js.map → chunk-72WG5QKN.js.map} +0 -0
- /package/dist/{chunk-CZAZX7JP.js.map → chunk-7MW3CVLD.js.map} +0 -0
- /package/dist/{chunk-CXLNZXG5.js.map → chunk-7XGSIUP5.js.map} +0 -0
- /package/dist/{chunk-CZAA5N5S.js.map → chunk-AYZJID4S.js.map} +0 -0
- /package/dist/{chunk-6BISDEN6.js.map → chunk-BEM4D2QG.js.map} +0 -0
- /package/dist/{chunk-ZPOROUTX.js.map → chunk-CYWA2CXR.js.map} +0 -0
- /package/dist/{chunk-M5QKGHCR.js.map → chunk-CYWCKFCL.js.map} +0 -0
- /package/dist/{chunk-4BFRBP45.js.map → chunk-DEG5ULFJ.js.map} +0 -0
- /package/dist/{chunk-JLR3JSTL.js.map → chunk-DS7MM2ES.js.map} +0 -0
- /package/dist/{chunk-2BFLDH5F.js.map → chunk-EEIROWEG.js.map} +0 -0
- /package/dist/{chunk-X4A62ET7.js.map → chunk-H2DXDQCQ.js.map} +0 -0
- /package/dist/{chunk-27FLVMDC.js.map → chunk-HLN7RROI.js.map} +0 -0
- /package/dist/{chunk-2U2A5HF4.js.map → chunk-J3XHPFEU.js.map} +0 -0
- /package/dist/{chunk-AKUCU7CI.js.map → chunk-JI5AVXYO.js.map} +0 -0
- /package/dist/{chunk-KCJ5H6XQ.js.map → chunk-JSNVWNGX.js.map} +0 -0
- /package/dist/{chunk-FLVL4L3L.js.map → chunk-KTULX4YO.js.map} +0 -0
- /package/dist/{chunk-437S3G37.js.map → chunk-LNQUAJNC.js.map} +0 -0
- /package/dist/{chunk-EPGWWCZF.js.map → chunk-LR5DZ56F.js.map} +0 -0
- /package/dist/{chunk-YDSWRD33.js.map → chunk-N3JDIRBW.js.map} +0 -0
- /package/dist/{chunk-OUMTBFSQ.js.map → chunk-NKCPUHCI.js.map} +0 -0
- /package/dist/{chunk-BGTAL46Y.js.map → chunk-PNLFN2PZ.js.map} +0 -0
- /package/dist/{chunk-P6SYWE4G.js.map → chunk-PVUJ6WVV.js.map} +0 -0
- /package/dist/{chunk-YPEMNNNY.js.map → chunk-RGK27ELN.js.map} +0 -0
- /package/dist/{chunk-2QSZNTDO.js.map → chunk-RKNJBZ55.js.map} +0 -0
- /package/dist/{chunk-66FLU5BT.js.map → chunk-SIRPLF54.js.map} +0 -0
- /package/dist/{chunk-HRCACVHL.js.map → chunk-TLUDFPZL.js.map} +0 -0
- /package/dist/{chunk-2MR3MFQB.js.map → chunk-UPSAT4PY.js.map} +0 -0
- /package/dist/{chunk-4P4626RY.js.map → chunk-UTRJFDP2.js.map} +0 -0
- /package/dist/{chunk-CGS7UKWM.js.map → chunk-VCW74PNH.js.map} +0 -0
- /package/dist/{chunk-5GPPACXK.js.map → chunk-VSMRDBFT.js.map} +0 -0
- /package/dist/{chunk-NG7YBCJO.js.map → chunk-XCL57M6Q.js.map} +0 -0
- /package/dist/{chunk-CLX5VDNY.js.map → chunk-Y4RKAQZ7.js.map} +0 -0
- /package/dist/{chunk-DGYCWCQ7.js.map → chunk-Y7UNNAZD.js.map} +0 -0
- /package/dist/{chunk-EZ25VE3G.js.map → chunk-YNDLCWXS.js.map} +0 -0
- /package/dist/{chunk-XN3TUUX2.js.map → chunk-YQB32INI.js.map} +0 -0
- /package/dist/{first-start-migration-DY6YCRC2.js.map → first-start-migration-VBJQL6OI.js.map} +0 -0
- /package/dist/{tier-stats-TG6GUZ6N.js.map → tier-stats-5XF4UBRQ.js.map} +0 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wearable cross-source fusion — deterministic conversation clustering.
|
|
3
|
+
*
|
|
4
|
+
* Conversations from multiple sources recording the same real-world event
|
|
5
|
+
* overlap or sit within a proximity gap. This module groups them into
|
|
6
|
+
* clusters that are fully deterministic: the only inputs are start/end
|
|
7
|
+
* times, source ids, and conversation ids (no randomness, no LLM).
|
|
8
|
+
*
|
|
9
|
+
* Cross-source time proximity BRIDGES the same real-world conversation
|
|
10
|
+
* recorded by DIFFERENT sources. A single source's own distinct
|
|
11
|
+
* conversations are never merged by time proximity alone — that would
|
|
12
|
+
* collapse the source's conversation boundaries. Two same-source
|
|
13
|
+
* conversations may end up in one cluster only when a different-source
|
|
14
|
+
* conversation within the gap corroborates that they are part of the same
|
|
15
|
+
* real-world event (a cross-source chain/bridge).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { FusionConversationInput } from "./types.js";
|
|
19
|
+
|
|
20
|
+
/** Default max gap (ms) between two conversations to merge into one cluster. */
|
|
21
|
+
export const DEFAULT_PROXIMITY_GAP_MS = 5 * 60_000;
|
|
22
|
+
|
|
23
|
+
interface Interval {
|
|
24
|
+
start: number;
|
|
25
|
+
end: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The latest extent of a set of segments as an epoch-ms value + its ISO.
|
|
29
|
+
* Each segment contributes its OWN extent — its `endIso` when known,
|
|
30
|
+
* otherwise its `startIso` — and the result is the maximum. Reconstructed
|
|
31
|
+
* `--:--` transcripts rebuild segments with only a start, so this reaches
|
|
32
|
+
* the last segment start; segment ISOs are already cross-midnight-rolled by
|
|
33
|
+
* the reconstruct layer, so the derived end is consistent with the segment
|
|
34
|
+
* timeline. Returns undefined when no segment carries a parseable extent.
|
|
35
|
+
*
|
|
36
|
+
* This is the SINGLE shared primitive behind every end/interval derivation
|
|
37
|
+
* in the fusion pipeline (cluster interval, fused conversation end): when a
|
|
38
|
+
* conversation-level end is missing, the window falls back to the latest
|
|
39
|
+
* segment extent instead of collapsing to a zero-length point at the start.
|
|
40
|
+
*/
|
|
41
|
+
export interface SegmentExtent {
|
|
42
|
+
/** Epoch ms of the latest extent. */
|
|
43
|
+
ms: number;
|
|
44
|
+
/** ISO string that produced `ms` (a segment endIso, else its startIso). */
|
|
45
|
+
iso: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function maxSegmentExtent(
|
|
49
|
+
segments: readonly { endIso?: string; startIso?: string }[],
|
|
50
|
+
): SegmentExtent | undefined {
|
|
51
|
+
let best: SegmentExtent | undefined;
|
|
52
|
+
for (const seg of segments) {
|
|
53
|
+
const iso = seg.endIso ?? seg.startIso;
|
|
54
|
+
if (iso === undefined) continue;
|
|
55
|
+
const ms = Date.parse(iso);
|
|
56
|
+
if (!Number.isFinite(ms)) continue;
|
|
57
|
+
if (best === undefined || ms > best.ms) {
|
|
58
|
+
best = { ms, iso };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return best;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Resolve a conversation's effective [start, end] window in epoch ms.
|
|
66
|
+
*
|
|
67
|
+
* When the conversation carries no explicit end (`endIso` undefined — the
|
|
68
|
+
* reconstructed form of a stored transcript whose heading end renders as
|
|
69
|
+
* `--:--`), the window is derived from the segments so it spans the actual
|
|
70
|
+
* utterances instead of collapsing to a zero-length point at the start:
|
|
71
|
+
*
|
|
72
|
+
* 1. conversation `endIso`, when known;
|
|
73
|
+
* 2. the latest segment END (`segment.endIso`);
|
|
74
|
+
* 3. the latest segment START (`segment.startIso`) — reconstructed
|
|
75
|
+
* transcripts rebuild segments with only a start, so the window must
|
|
76
|
+
* reach the last segment start;
|
|
77
|
+
* 4. the conversation start itself (no segments at all).
|
|
78
|
+
*
|
|
79
|
+
* Each segment contributes its own extent (end when known, otherwise its
|
|
80
|
+
* start); the window end is the maximum extent. Segment ISOs are already
|
|
81
|
+
* rolled for cross-midnight by the reconstruct layer, so the derived end is
|
|
82
|
+
* consistent with the segment timeline. The end is clamped to `>= start` so
|
|
83
|
+
* the window is always valid regardless of input anomalies.
|
|
84
|
+
*/
|
|
85
|
+
function effectiveInterval(conversation: FusionConversationInput): Interval {
|
|
86
|
+
const start = Date.parse(conversation.startIso);
|
|
87
|
+
const endRaw =
|
|
88
|
+
conversation.endIso !== undefined ? Date.parse(conversation.endIso) : NaN;
|
|
89
|
+
// No conversation-level end ("--:--"): derive the window from the latest
|
|
90
|
+
// SEGMENT extent via the shared primitive, so every end/interval derivation
|
|
91
|
+
// in the pipeline uses ONE coherent implementation. Falling back only to
|
|
92
|
+
// `start` would produce a zero/negative-length window that drops or
|
|
93
|
+
// mis-clusters the conversation (issue #1810).
|
|
94
|
+
const end = Number.isFinite(endRaw)
|
|
95
|
+
? (endRaw as number)
|
|
96
|
+
: (maxSegmentExtent(conversation.segments)?.ms ?? start);
|
|
97
|
+
// Clamp universally so the window is always non-negative. An explicit
|
|
98
|
+
// conversation end that precedes the start (malformed/cross-day input)
|
|
99
|
+
// would otherwise yield a negative-length window that shrinks the merge
|
|
100
|
+
// horizon and can split a within-gap cluster.
|
|
101
|
+
return { start, end: Math.max(end, start) };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function compareConversation(
|
|
105
|
+
a: FusionConversationInput,
|
|
106
|
+
b: FusionConversationInput,
|
|
107
|
+
): number {
|
|
108
|
+
if (a.source !== b.source) return a.source < b.source ? -1 : 1;
|
|
109
|
+
if (a.conversationId !== b.conversationId) {
|
|
110
|
+
return a.conversationId < b.conversationId ? -1 : 1;
|
|
111
|
+
}
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Cluster conversations across sources for one day. Returns clusters in
|
|
117
|
+
* chronological order; each cluster is sorted by (start, source, id).
|
|
118
|
+
* Conversations whose start time is unparseable are emitted as
|
|
119
|
+
* single-element clusters at the tail (sorted by source then id) so no
|
|
120
|
+
* data is lost.
|
|
121
|
+
*
|
|
122
|
+
* ## Cross-source bridging rule (issue #1849)
|
|
123
|
+
*
|
|
124
|
+
* Time proximity is meant to bridge the SAME real-world conversation
|
|
125
|
+
* recorded by DIFFERENT sources — not to merge one source's own distinct
|
|
126
|
+
* conversations. Two conversations from the SAME source are therefore
|
|
127
|
+
* never directly joined by time proximity alone; they may end up in the
|
|
128
|
+
* same cluster only through a cross-source chain where a different-source
|
|
129
|
+
* conversation within the gap corroborates the bridge. This preserves the
|
|
130
|
+
* source's own conversation boundaries when no other source corroborates
|
|
131
|
+
* a merge.
|
|
132
|
+
*
|
|
133
|
+
* Concretely, a union-find pass links every pair of conversations that
|
|
134
|
+
* (a) come from DIFFERENT sources and (b) sit within the proximity gap
|
|
135
|
+
* (the later start ≤ the earlier end + gap). The transitive closure then
|
|
136
|
+
* groups same-source conversations that are bridged by at least one
|
|
137
|
+
* intervening different-source conversation. A single source with two
|
|
138
|
+
* near-back-to-back conversations and no other source recording the same
|
|
139
|
+
* window stays in two separate clusters.
|
|
140
|
+
*/
|
|
141
|
+
export function clusterConversations(
|
|
142
|
+
inputs: readonly FusionConversationInput[],
|
|
143
|
+
proximityGapMs: number = DEFAULT_PROXIMITY_GAP_MS,
|
|
144
|
+
): FusionConversationInput[][] {
|
|
145
|
+
const finite: Array<{ conv: FusionConversationInput; interval: Interval }> = [];
|
|
146
|
+
const noStart: FusionConversationInput[] = [];
|
|
147
|
+
for (const conv of inputs) {
|
|
148
|
+
const interval = effectiveInterval(conv);
|
|
149
|
+
if (Number.isFinite(interval.start)) {
|
|
150
|
+
finite.push({ conv, interval });
|
|
151
|
+
} else {
|
|
152
|
+
noStart.push(conv);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Deterministic processing + output ordering: (start, source, id).
|
|
157
|
+
finite.sort((a, b) => {
|
|
158
|
+
if (a.interval.start !== b.interval.start) {
|
|
159
|
+
return a.interval.start - b.interval.start;
|
|
160
|
+
}
|
|
161
|
+
return compareConversation(a.conv, b.conv);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const n = finite.length;
|
|
165
|
+
|
|
166
|
+
// Union-find so that same-source pairs are never directly joined by time
|
|
167
|
+
// proximity alone; they may share a cluster only through a cross-source
|
|
168
|
+
// bridge (transitive closure over different-source proximity edges).
|
|
169
|
+
const parent = new Array<number>(n);
|
|
170
|
+
for (let i = 0; i < n; i++) parent[i] = i;
|
|
171
|
+
|
|
172
|
+
const find = (x: number): number => {
|
|
173
|
+
let root = x;
|
|
174
|
+
while (parent[root] !== root) root = parent[root];
|
|
175
|
+
// Path compression.
|
|
176
|
+
let cur = x;
|
|
177
|
+
while (parent[cur] !== root) {
|
|
178
|
+
const next = parent[cur]!;
|
|
179
|
+
parent[cur] = root;
|
|
180
|
+
cur = next;
|
|
181
|
+
}
|
|
182
|
+
return root;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const union = (a: number, b: number): void => {
|
|
186
|
+
const ra = find(a);
|
|
187
|
+
const rb = find(b);
|
|
188
|
+
if (ra !== rb) parent[ra] = rb;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// Link every cross-source pair within the proximity gap. `finite` is
|
|
192
|
+
// sorted by start, so once j's start exceeds i's end + gap, every later
|
|
193
|
+
// j is also out of reach — break early.
|
|
194
|
+
for (let i = 0; i < n; i++) {
|
|
195
|
+
const endGap = finite[i]!.interval.end + proximityGapMs;
|
|
196
|
+
for (let j = i + 1; j < n; j++) {
|
|
197
|
+
if (finite[j]!.interval.start > endGap) break;
|
|
198
|
+
// Only DIFFERENT sources may be directly joined by proximity.
|
|
199
|
+
if (finite[i]!.conv.source === finite[j]!.conv.source) continue;
|
|
200
|
+
union(i, j);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Group conversations by union-find root. Because `finite` is sorted and
|
|
205
|
+
// we iterate in index order, each group's conversations stay in (start,
|
|
206
|
+
// source, id) order, and groups are discovered in chronological order
|
|
207
|
+
// (the first root seen belongs to the earliest-starting conversation).
|
|
208
|
+
const clusters: FusionConversationInput[][] = [];
|
|
209
|
+
const rootToCluster = new Map<number, number>();
|
|
210
|
+
for (let i = 0; i < n; i++) {
|
|
211
|
+
const root = find(i);
|
|
212
|
+
let idx = rootToCluster.get(root);
|
|
213
|
+
if (idx === undefined) {
|
|
214
|
+
idx = clusters.length;
|
|
215
|
+
rootToCluster.set(root, idx);
|
|
216
|
+
clusters.push([]);
|
|
217
|
+
}
|
|
218
|
+
clusters[idx]!.push(finite[i]!.conv);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Conversations with no parseable start can't be ordered in time, so
|
|
222
|
+
// each becomes its own cluster, appended after the time-ordered ones.
|
|
223
|
+
// Sort for determinism (source, id).
|
|
224
|
+
noStart.sort(compareConversation);
|
|
225
|
+
for (const conv of noStart) {
|
|
226
|
+
clusters.push([conv]);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return clusters;
|
|
230
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wearable cross-source fusion — day-level orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* Takes every enabled source's normalized conversations for one day,
|
|
5
|
+
* clusters them across sources, reconciles each cluster, and attaches a
|
|
6
|
+
* stable content-hash id so re-running over unchanged inputs produces a
|
|
7
|
+
* byte-identical artifact (idempotent — no duplicate files).
|
|
8
|
+
*
|
|
9
|
+
* Deterministic end to end: no timestamps from "now", no LLM, no
|
|
10
|
+
* randomness. The only time-derived value is the `fusedAt` frontmatter
|
|
11
|
+
* field added at storage time (excluded from the content hash).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createHash } from "node:crypto";
|
|
15
|
+
import { clusterConversations } from "./cluster.js";
|
|
16
|
+
import {
|
|
17
|
+
DEFAULT_SOURCE_TRUST,
|
|
18
|
+
DEFAULT_WINDOW_TOLERANCE_MS,
|
|
19
|
+
fuseCluster,
|
|
20
|
+
type FuseClusterResult,
|
|
21
|
+
} from "./reconcile.js";
|
|
22
|
+
import { DEFAULT_PROXIMITY_GAP_MS } from "./cluster.js";
|
|
23
|
+
import type {
|
|
24
|
+
FusionConversationInput,
|
|
25
|
+
FusionDayResult,
|
|
26
|
+
FusionOptions,
|
|
27
|
+
FusedConversationProvenance,
|
|
28
|
+
FusedWearableConversation,
|
|
29
|
+
} from "./types.js";
|
|
30
|
+
|
|
31
|
+
const FUSION_ID_PREFIX = "fusion";
|
|
32
|
+
/**
|
|
33
|
+
* Fusion algorithm/schema version. Folded into the day content hash (the
|
|
34
|
+
* idempotency key) so a change to the clustering, reconciliation, or
|
|
35
|
+
* reconstruction algorithm invalidates cached artifacts even when the raw
|
|
36
|
+
* inputs and fusion config are byte-identical — pre-existing `_fusion/`
|
|
37
|
+
* files are regenerated rather than served stale. Bump this whenever the
|
|
38
|
+
* fusion algorithm meaningfully changes (this PR changed it repeatedly).
|
|
39
|
+
* The per-conversation id (`canonicalInputsKey`) is intentionally NOT
|
|
40
|
+
* folded with the version, so a conversation keeps a stable identity.
|
|
41
|
+
*/
|
|
42
|
+
export const FUSION_ALGO_VERSION = "2026-07-12-1";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Canonical, stable serialization of a set of fusion inputs — the base
|
|
46
|
+
* for the per-conversation id. Input-only so a conversation keeps a
|
|
47
|
+
* stable identity across re-runs regardless of input order. The day
|
|
48
|
+
* content hash additionally folds the effective fusion config (see
|
|
49
|
+
* `canonicalDayKey`) so a config change invalidates the cached artifact.
|
|
50
|
+
*/
|
|
51
|
+
function canonicalInputsKey(
|
|
52
|
+
date: string,
|
|
53
|
+
inputs: readonly FusionConversationInput[],
|
|
54
|
+
): string {
|
|
55
|
+
const sorted = [...inputs].sort((a, b) => {
|
|
56
|
+
if (a.source !== b.source) return a.source < b.source ? -1 : 1;
|
|
57
|
+
if (a.conversationId !== b.conversationId) {
|
|
58
|
+
return a.conversationId < b.conversationId ? -1 : 1;
|
|
59
|
+
}
|
|
60
|
+
return 0;
|
|
61
|
+
});
|
|
62
|
+
// Serialize the structured tuples UNAMBIGUOUSLY before hashing. The old
|
|
63
|
+
// `|`/newline-delimited form could collide: a speaker label or segment
|
|
64
|
+
// text containing `|` (or a newline plus a forged `seg|…`/`conv|…`
|
|
65
|
+
// prefix) could make two DISTINCT inputs serialize to identical bytes,
|
|
66
|
+
// hashing alike — so fuseDay wrongly took the idempotent-skip path and
|
|
67
|
+
// kept serving a stale artifact. JSON string quoting makes every field
|
|
68
|
+
// boundary unforgeable, and the positional array order is the
|
|
69
|
+
// deterministic key order (no object keys to sort).
|
|
70
|
+
const record = [
|
|
71
|
+
date,
|
|
72
|
+
sorted.map((conv) => [
|
|
73
|
+
conv.source,
|
|
74
|
+
conv.conversationId,
|
|
75
|
+
conv.startIso,
|
|
76
|
+
conv.endIso ?? "",
|
|
77
|
+
conv.title ?? "",
|
|
78
|
+
conv.summary ?? "",
|
|
79
|
+
conv.segments.map((segment) => [
|
|
80
|
+
segment.speaker,
|
|
81
|
+
segment.isSelf ? "self" : "other",
|
|
82
|
+
segment.startIso ?? "",
|
|
83
|
+
segment.endIso ?? "",
|
|
84
|
+
segment.text,
|
|
85
|
+
]),
|
|
86
|
+
]),
|
|
87
|
+
];
|
|
88
|
+
return createHash("sha256").update(JSON.stringify(record), "utf-8").digest("hex");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Effective-config fingerprint folded into the day content hash so a
|
|
93
|
+
* change to any clustering/reconciliation knob (proximity gap, window
|
|
94
|
+
* tolerance, or per-source trust) invalidates the cached artifact.
|
|
95
|
+
* Defaults are resolved so an explicit default hashes identically to an
|
|
96
|
+
* omission, keeping the skip-unchanged path deterministic. Only sources
|
|
97
|
+
* that actually contribute to the day are included, so a trust change
|
|
98
|
+
* for an absent source cannot spuriously trigger a rebuild.
|
|
99
|
+
*/
|
|
100
|
+
function configFingerprint(
|
|
101
|
+
options: FusionOptions,
|
|
102
|
+
sources: readonly string[],
|
|
103
|
+
): string {
|
|
104
|
+
const proximityGapMs = options.proximityGapMs ?? DEFAULT_PROXIMITY_GAP_MS;
|
|
105
|
+
const windowToleranceMs =
|
|
106
|
+
options.windowToleranceMs ?? DEFAULT_WINDOW_TOLERANCE_MS;
|
|
107
|
+
const trustMap = options.sourceTrust ?? {};
|
|
108
|
+
// JSON form so a source id (or future field) containing `|` cannot forge
|
|
109
|
+
// a trust-entry boundary and collide two distinct effective configs into
|
|
110
|
+
// one day hash.
|
|
111
|
+
const trustEntries = [...new Set(sources)]
|
|
112
|
+
.sort()
|
|
113
|
+
.map((source) => [source, trustMap[source] ?? DEFAULT_SOURCE_TRUST]);
|
|
114
|
+
return JSON.stringify([proximityGapMs, windowToleranceMs, trustEntries]);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Day idempotency key: the input-only hash combined with the effective
|
|
119
|
+
* fusion-config fingerprint AND the fusion algorithm version. Same inputs +
|
|
120
|
+
* same config + same version => same hash (no duplicate rewrite); a config
|
|
121
|
+
* change OR an algorithm-version bump => new hash => rebuild. The version
|
|
122
|
+
* is what forces regeneration when the ALGORITHM itself changes but the
|
|
123
|
+
* inputs and config are byte-identical (issue #1849).
|
|
124
|
+
*/
|
|
125
|
+
export function canonicalDayKey(
|
|
126
|
+
date: string,
|
|
127
|
+
inputs: readonly FusionConversationInput[],
|
|
128
|
+
options: FusionOptions,
|
|
129
|
+
algoVersion: string = FUSION_ALGO_VERSION,
|
|
130
|
+
): string {
|
|
131
|
+
const inputsKey = canonicalInputsKey(date, inputs);
|
|
132
|
+
const fingerprint = configFingerprint(
|
|
133
|
+
options,
|
|
134
|
+
inputs.map((conv) => conv.source),
|
|
135
|
+
);
|
|
136
|
+
return createHash("sha256")
|
|
137
|
+
.update(inputsKey)
|
|
138
|
+
.update("\n")
|
|
139
|
+
.update(fingerprint)
|
|
140
|
+
.update("\n")
|
|
141
|
+
.update(algoVersion)
|
|
142
|
+
.digest("hex");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function buildProvenance(
|
|
146
|
+
cluster: readonly FusionConversationInput[],
|
|
147
|
+
proximityGapMs: number,
|
|
148
|
+
windowToleranceMs: number,
|
|
149
|
+
): FusedConversationProvenance {
|
|
150
|
+
return {
|
|
151
|
+
contributions: cluster.map((conv) => ({
|
|
152
|
+
source: conv.source,
|
|
153
|
+
conversationId: conv.conversationId,
|
|
154
|
+
startIso: conv.startIso,
|
|
155
|
+
...(conv.endIso !== undefined ? { endIso: conv.endIso } : {}),
|
|
156
|
+
segmentCount: conv.segments.length,
|
|
157
|
+
})),
|
|
158
|
+
proximityGapMs,
|
|
159
|
+
windowToleranceMs,
|
|
160
|
+
method: "time-proximity",
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Fuse every source's conversations for one day into a stable result.
|
|
166
|
+
* Empty input yields an empty (but well-formed) result so callers can
|
|
167
|
+
* treat "nothing to fuse" uniformly.
|
|
168
|
+
*/
|
|
169
|
+
export function fuseDay(
|
|
170
|
+
date: string,
|
|
171
|
+
inputs: readonly FusionConversationInput[],
|
|
172
|
+
options: FusionOptions = {},
|
|
173
|
+
): FusionDayResult {
|
|
174
|
+
const proximityGapMs = options.proximityGapMs ?? DEFAULT_PROXIMITY_GAP_MS;
|
|
175
|
+
const windowToleranceMs =
|
|
176
|
+
options.windowToleranceMs ?? DEFAULT_WINDOW_TOLERANCE_MS;
|
|
177
|
+
|
|
178
|
+
const clusters = clusterConversations(inputs, proximityGapMs);
|
|
179
|
+
const conversations: FusedWearableConversation[] = clusters.map((cluster) => {
|
|
180
|
+
const fused: FuseClusterResult = fuseCluster(cluster, {
|
|
181
|
+
...options,
|
|
182
|
+
windowToleranceMs,
|
|
183
|
+
});
|
|
184
|
+
const id = `${FUSION_ID_PREFIX}-${canonicalInputsKey(date, cluster).slice(0, 24)}`;
|
|
185
|
+
const result: FusedWearableConversation = {
|
|
186
|
+
id,
|
|
187
|
+
date,
|
|
188
|
+
startIso: fused.startIso,
|
|
189
|
+
sources: fused.sources,
|
|
190
|
+
speakers: fused.speakers,
|
|
191
|
+
segments: fused.segments,
|
|
192
|
+
disagreements: fused.disagreements,
|
|
193
|
+
provenance: buildProvenance(cluster, proximityGapMs, windowToleranceMs),
|
|
194
|
+
};
|
|
195
|
+
if (fused.endIso !== undefined) result.endIso = fused.endIso;
|
|
196
|
+
if (fused.title !== undefined) result.title = fused.title;
|
|
197
|
+
if (fused.summary !== undefined) result.summary = fused.summary;
|
|
198
|
+
return result;
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const sourcesSeen = new Set<string>();
|
|
202
|
+
const sources: string[] = [];
|
|
203
|
+
for (const conv of conversations) {
|
|
204
|
+
for (const source of conv.sources) {
|
|
205
|
+
if (!sourcesSeen.has(source)) {
|
|
206
|
+
sourcesSeen.add(source);
|
|
207
|
+
sources.push(source);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
date,
|
|
214
|
+
conversations,
|
|
215
|
+
sources,
|
|
216
|
+
contentHash: canonicalDayKey(date, inputs, options),
|
|
217
|
+
};
|
|
218
|
+
}
|