@remnic/core 9.64.0 → 9.64.1
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 +13 -13
- package/dist/access-authorization-probe.d.ts +11 -11
- package/dist/access-authorization-probe.js +14 -14
- package/dist/access-boundary.d.ts +11 -11
- package/dist/access-boundary.js +13 -13
- package/dist/access-cli.js +47 -47
- 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 +13 -13
- 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.d.ts +2 -2
- package/dist/access-http-query.js +15 -15
- package/dist/access-http.d.ts +12 -12
- package/dist/access-http.js +24 -24
- package/dist/access-identity-continuity-surface.d.ts +9 -9
- package/dist/access-identity-continuity-surface.js +13 -13
- package/dist/access-lcm-surface.d.ts +11 -11
- package/dist/access-lcm-surface.js +13 -13
- package/dist/access-mcp.d.ts +11 -11
- package/dist/access-mcp.js +18 -18
- package/dist/access-memory-search-fanout.d.ts +2 -2
- package/dist/{access-namespace-preflight-CrF0FKaz.d.ts → access-namespace-preflight-Dj9uR4Cq.d.ts} +1 -1
- package/dist/access-namespace-preflight.d.ts +3 -3
- package/dist/access-namespace-preflight.js +13 -13
- package/dist/access-observe-write-surface.d.ts +11 -11
- package/dist/access-observe-write-surface.js +13 -13
- package/dist/access-offline-manifest.d.ts +9 -9
- package/dist/access-operations-batch.js +14 -14
- package/dist/access-operations.d.ts +14 -14
- package/dist/access-operations.js +17 -17
- package/dist/access-recall-concurrency.d.ts +11 -11
- package/dist/access-recall-concurrency.js +13 -13
- package/dist/access-recall-response.d.ts +11 -11
- package/dist/access-recall-response.js +13 -13
- package/dist/access-recall-surface.d.ts +11 -11
- package/dist/access-recall-surface.js +13 -13
- package/dist/access-schema.d.ts +88 -88
- package/dist/{access-service-BqF4UK8Z.d.ts → access-service-Bd0lzaHp.d.ts} +71 -71
- package/dist/access-service-helpers.d.ts +11 -11
- package/dist/access-service.d.ts +11 -11
- package/dist/access-service.js +13 -13
- package/dist/access-surface-catalog.d.ts +11 -11
- package/dist/access-surface-catalog.js +15 -15
- 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 +1 -1
- package/dist/ambient-provenance.d.ts +1 -1
- package/dist/artifact-search.d.ts +1 -1
- 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-window.d.ts +2 -2
- package/dist/briefing.d.ts +2 -2
- package/dist/briefing.js +3 -3
- 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/buffer.js +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-C0KIwZqR.d.ts → catalog-rKYbN7zF.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 +4 -4
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-5SZ6CLGJ.js → chunk-2RZCOG63.js} +3 -2
- package/dist/{chunk-5SZ6CLGJ.js.map → chunk-2RZCOG63.js.map} +1 -1
- package/dist/{chunk-7DRSWPUV.js → chunk-3CYVTO6A.js} +3 -3
- package/dist/{chunk-LJQGQUNS.js → chunk-4FSCGYTQ.js} +2 -2
- package/dist/{chunk-ML4MI2RR.js → chunk-4KKVDOCD.js} +24 -24
- package/dist/{chunk-Z3Q5I5F6.js → chunk-5Q5BEWJ3.js} +2 -2
- package/dist/{chunk-6BV6ZZZA.js → chunk-6I6IOMKS.js} +2 -2
- package/dist/{chunk-GIMQJ5RX.js → chunk-7D3TTGHY.js} +20 -11
- package/dist/chunk-7D3TTGHY.js.map +1 -0
- package/dist/{chunk-4IIDTPSK.js → chunk-7X35RPN3.js} +2 -2
- package/dist/{chunk-FEP45MYY.js → chunk-AGC3Q53R.js} +2 -2
- package/dist/{chunk-ZWYXSF2T.js → chunk-AL43367I.js} +96 -4
- package/dist/chunk-AL43367I.js.map +1 -0
- package/dist/{chunk-WCWIV2JH.js → chunk-B2GICNOJ.js} +2 -2
- package/dist/{chunk-BTBAHTQ5.js → chunk-CI6YIT5S.js} +2 -2
- package/dist/{chunk-CUDSA46O.js → chunk-CUIAHZPZ.js} +2 -2
- package/dist/{chunk-BTUIHRRR.js → chunk-D267K4V5.js} +2 -2
- package/dist/{chunk-QICOU4RY.js → chunk-DLZBOEJY.js} +2 -2
- package/dist/{chunk-AR253JBM.js → chunk-DPPTG6VJ.js} +1 -1
- package/dist/chunk-DPPTG6VJ.js.map +1 -0
- package/dist/{chunk-BPLPGOUJ.js → chunk-DRS3OX77.js} +2 -2
- package/dist/{chunk-3F3CVVDS.js → chunk-DTQ3OB7O.js} +2 -2
- package/dist/{chunk-Z4UPRZR6.js → chunk-DZ2AVS2H.js} +2 -2
- package/dist/{chunk-YINSSARY.js → chunk-EAWJSY2Z.js} +1 -1
- package/dist/{chunk-QFPX7QZG.js → chunk-FMGTFMT3.js} +9 -9
- package/dist/{chunk-CTD5CPD2.js → chunk-GSQDPL45.js} +3 -3
- package/dist/{chunk-UUYQWKHO.js → chunk-IHPYTG6F.js} +2 -2
- package/dist/{chunk-QATILW4B.js → chunk-IPUUMLSM.js} +21 -21
- package/dist/{chunk-2BRCV2IV.js → chunk-JE3AKPOO.js} +2 -2
- package/dist/{chunk-DZFPHARJ.js → chunk-KOE3RLNN.js} +2 -2
- package/dist/{chunk-WQ6TRWAE.js → chunk-NMQXUGTU.js} +3 -3
- package/dist/{chunk-65NQFK7D.js → chunk-OTVHRZZD.js} +2 -2
- package/dist/{chunk-77E7LLMV.js → chunk-OWAYQK5V.js} +8 -8
- package/dist/{chunk-YGSDWO3U.js → chunk-P4SD7BSA.js} +2 -2
- package/dist/{chunk-G5F4MXXC.js → chunk-Q3IRE6E4.js} +2 -2
- package/dist/{chunk-HZXO2CJ6.js → chunk-QHNO6AND.js} +2 -2
- package/dist/{chunk-5OOTHWGJ.js → chunk-RMB22Q3R.js} +2 -2
- package/dist/{chunk-BB2LH5JM.js → chunk-RNORDDD6.js} +11 -1
- package/dist/chunk-RNORDDD6.js.map +1 -0
- package/dist/{chunk-R7XE5ZUR.js → chunk-UIYYS2YI.js} +2 -2
- package/dist/{chunk-SYU5JOBQ.js → chunk-UWSETW6O.js} +2 -2
- package/dist/{chunk-CK3DZ6OE.js → chunk-VCNGVU62.js} +2 -2
- package/dist/{chunk-KG2CT762.js → chunk-VSKA2ATE.js} +2 -2
- package/dist/{chunk-PQODT53H.js → chunk-WDFXRHOZ.js} +3 -3
- package/dist/{chunk-6NMZXEJ7.js → chunk-WNO5W2TP.js} +2 -2
- package/dist/{chunk-63JMI45N.js → chunk-XBRGJJK7.js} +2 -2
- package/dist/{chunk-S6KMN6MJ.js → chunk-XFAWGUNX.js} +2 -2
- package/dist/{chunk-SRLQROQJ.js → chunk-XSPRZZEX.js} +3 -3
- package/dist/{chunk-EHC3FIJ4.js → chunk-Y5ZIQFWH.js} +2 -2
- package/dist/{chunk-6IKXAAJI.js → chunk-YY63AMGP.js} +2 -2
- package/dist/{chunk-7ML7STOD.js → chunk-ZGGTIBWN.js} +2 -2
- package/dist/{chunk-X6HNXVXS.js → chunk-ZUIVJ3BB.js} +4 -4
- package/dist/{cli-BX0O688c.d.ts → cli-0dDmOsJ8.d.ts} +5 -5
- package/dist/cli.d.ts +13 -13
- package/dist/cli.js +37 -37
- package/dist/coding/pre-action-gate.d.ts +1 -1
- package/dist/compounding/engine.d.ts +2 -2
- package/dist/compounding/engine.js +3 -3
- 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 +1 -1
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +3 -3
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +3 -3
- 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/converge-config.d.ts +1 -1
- package/dist/convergence-refresh.d.ts +2 -2
- package/dist/convergence-refresh.js +5 -5
- 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 +5 -5
- 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-Cph4PnBe.d.ts → dependency-propagation-delivery-Cr8u-XwC.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-origin-fields.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +2 -2
- package/dist/entity-retrieval.js +3 -3
- package/dist/entity-schema.d.ts +1 -1
- package/dist/episodic-context.d.ts +1 -1
- package/dist/explicit-capture.d.ts +9 -9
- package/dist/external-wiki-access.d.ts +11 -11
- package/dist/external-wiki-access.js +14 -14
- 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-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.d.ts +1 -1
- package/dist/extraction.js +6 -6
- package/dist/fallback-llm.d.ts +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-retrieval.js +1 -1
- 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 +697 -697
- package/dist/index.js +47 -47
- 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 +3 -3
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
- package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-projection.js +4 -4
- package/dist/{maintenance-BNq31xpo.d.ts → maintenance-BpNjuZk4.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 +4 -4
- 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 +3 -3
- 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 +6 -6
- package/dist/operator-doctor-replica.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +3 -3
- package/dist/operator-toolkit.js +12 -12
- package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
- package/dist/orchestration/maintenance.d.ts +4 -4
- package/dist/orchestration/maintenance.js +9 -9
- package/dist/{orchestrator-CBuUOP2J.d.ts → orchestrator-B3ZQ8CTJ.d.ts} +8 -8
- package/dist/orchestrator.d.ts +9 -9
- package/dist/orchestrator.js +47 -47
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/{pipeline-CW_Q4WvX.d.ts → pipeline-q5emoXD6.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/{public-http-BeJ1_JI1.d.ts → public-http-CQN-i7Ai.d.ts} +1 -1
- package/dist/{qmd-q-cAzYuC.d.ts → qmd-YC6YGphs.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-explain-renderer.js +3 -3
- 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-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +2 -2
- package/dist/reconcile/cursor.d.ts +1 -1
- package/dist/reconcile/manifest.d.ts +1 -1
- 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/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +2 -2
- 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/schemas.d.ts +108 -108
- package/dist/{scope-profiles-CIr4mXgp.d.ts → scope-profiles-De8F7YQ6.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-BZ6J46j-.d.ts → semantic-consolidation-Bf-hm2LQ.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +4 -4
- package/dist/semantic-rule-promotion.js +3 -3
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +3 -3
- package/dist/{service-CCl4iyyY.d.ts → service-D83osSRn.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 +5 -5
- package/dist/{storage-BPXmJKIt.d.ts → storage-BHWbpbhj.d.ts} +1 -1
- package/dist/storage.d.ts +2 -2
- package/dist/storage.js +2 -2
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/support-passport/index.d.ts +14 -14
- package/dist/support-passport/index.js +16 -16
- 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-CltSWcND.d.ts → types-CpgsN-Gb.d.ts} +24 -3
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +3 -3
- package/dist/write-envelope.d.ts +1 -1
- package/package.json +2 -2
- package/src/config.ts +5 -6
- package/src/location/cli.ts +163 -0
- package/src/location/config.test.ts +75 -0
- package/src/location/config.ts +105 -0
- package/src/location/intervals.test.ts +202 -0
- package/src/location/intervals.ts +221 -0
- package/src/location/parse-companions.ts +12 -0
- package/src/location/pipeline.test.ts +324 -0
- package/src/location/pipeline.ts +245 -0
- package/src/location/recall-exclusion.test.ts +36 -0
- package/src/location/registry.test.ts +74 -0
- package/src/location/registry.ts +54 -0
- package/src/location/store.test.ts +170 -0
- package/src/location/store.ts +478 -0
- package/src/location/types.ts +95 -0
- package/src/orchestration/generic-recall-paths.ts +17 -0
- package/src/orchestration/orchestrator-helpers.ts +22 -0
- package/src/orchestration/qmd-result-resolver.ts +1 -0
- package/src/types.ts +2 -3
- package/dist/chunk-AR253JBM.js.map +0 -1
- package/dist/chunk-BB2LH5JM.js.map +0 -1
- package/dist/chunk-GIMQJ5RX.js.map +0 -1
- package/dist/chunk-ZWYXSF2T.js.map +0 -1
- /package/dist/{chunk-7DRSWPUV.js.map → chunk-3CYVTO6A.js.map} +0 -0
- /package/dist/{chunk-LJQGQUNS.js.map → chunk-4FSCGYTQ.js.map} +0 -0
- /package/dist/{chunk-ML4MI2RR.js.map → chunk-4KKVDOCD.js.map} +0 -0
- /package/dist/{chunk-Z3Q5I5F6.js.map → chunk-5Q5BEWJ3.js.map} +0 -0
- /package/dist/{chunk-6BV6ZZZA.js.map → chunk-6I6IOMKS.js.map} +0 -0
- /package/dist/{chunk-4IIDTPSK.js.map → chunk-7X35RPN3.js.map} +0 -0
- /package/dist/{chunk-FEP45MYY.js.map → chunk-AGC3Q53R.js.map} +0 -0
- /package/dist/{chunk-WCWIV2JH.js.map → chunk-B2GICNOJ.js.map} +0 -0
- /package/dist/{chunk-BTBAHTQ5.js.map → chunk-CI6YIT5S.js.map} +0 -0
- /package/dist/{chunk-CUDSA46O.js.map → chunk-CUIAHZPZ.js.map} +0 -0
- /package/dist/{chunk-BTUIHRRR.js.map → chunk-D267K4V5.js.map} +0 -0
- /package/dist/{chunk-QICOU4RY.js.map → chunk-DLZBOEJY.js.map} +0 -0
- /package/dist/{chunk-BPLPGOUJ.js.map → chunk-DRS3OX77.js.map} +0 -0
- /package/dist/{chunk-3F3CVVDS.js.map → chunk-DTQ3OB7O.js.map} +0 -0
- /package/dist/{chunk-Z4UPRZR6.js.map → chunk-DZ2AVS2H.js.map} +0 -0
- /package/dist/{chunk-YINSSARY.js.map → chunk-EAWJSY2Z.js.map} +0 -0
- /package/dist/{chunk-QFPX7QZG.js.map → chunk-FMGTFMT3.js.map} +0 -0
- /package/dist/{chunk-CTD5CPD2.js.map → chunk-GSQDPL45.js.map} +0 -0
- /package/dist/{chunk-UUYQWKHO.js.map → chunk-IHPYTG6F.js.map} +0 -0
- /package/dist/{chunk-QATILW4B.js.map → chunk-IPUUMLSM.js.map} +0 -0
- /package/dist/{chunk-2BRCV2IV.js.map → chunk-JE3AKPOO.js.map} +0 -0
- /package/dist/{chunk-DZFPHARJ.js.map → chunk-KOE3RLNN.js.map} +0 -0
- /package/dist/{chunk-WQ6TRWAE.js.map → chunk-NMQXUGTU.js.map} +0 -0
- /package/dist/{chunk-65NQFK7D.js.map → chunk-OTVHRZZD.js.map} +0 -0
- /package/dist/{chunk-77E7LLMV.js.map → chunk-OWAYQK5V.js.map} +0 -0
- /package/dist/{chunk-YGSDWO3U.js.map → chunk-P4SD7BSA.js.map} +0 -0
- /package/dist/{chunk-G5F4MXXC.js.map → chunk-Q3IRE6E4.js.map} +0 -0
- /package/dist/{chunk-HZXO2CJ6.js.map → chunk-QHNO6AND.js.map} +0 -0
- /package/dist/{chunk-5OOTHWGJ.js.map → chunk-RMB22Q3R.js.map} +0 -0
- /package/dist/{chunk-R7XE5ZUR.js.map → chunk-UIYYS2YI.js.map} +0 -0
- /package/dist/{chunk-SYU5JOBQ.js.map → chunk-UWSETW6O.js.map} +0 -0
- /package/dist/{chunk-CK3DZ6OE.js.map → chunk-VCNGVU62.js.map} +0 -0
- /package/dist/{chunk-KG2CT762.js.map → chunk-VSKA2ATE.js.map} +0 -0
- /package/dist/{chunk-PQODT53H.js.map → chunk-WDFXRHOZ.js.map} +0 -0
- /package/dist/{chunk-6NMZXEJ7.js.map → chunk-WNO5W2TP.js.map} +0 -0
- /package/dist/{chunk-63JMI45N.js.map → chunk-XBRGJJK7.js.map} +0 -0
- /package/dist/{chunk-S6KMN6MJ.js.map → chunk-XFAWGUNX.js.map} +0 -0
- /package/dist/{chunk-SRLQROQJ.js.map → chunk-XSPRZZEX.js.map} +0 -0
- /package/dist/{chunk-EHC3FIJ4.js.map → chunk-Y5ZIQFWH.js.map} +0 -0
- /package/dist/{chunk-6IKXAAJI.js.map → chunk-YY63AMGP.js.map} +0 -0
- /package/dist/{chunk-7ML7STOD.js.map → chunk-ZGGTIBWN.js.map} +0 -0
- /package/dist/{chunk-X6HNXVXS.js.map → chunk-ZUIVJ3BB.js.map} +0 -0
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Location day-file + sync-state store (issue #2044).
|
|
3
|
+
*
|
|
4
|
+
* Day documents live at `<memoryDir>/locations/<YYYY-MM-DD>.md` — OUTSIDE the
|
|
5
|
+
* memory scan roots (facts/, entities/, …) so raw location history never
|
|
6
|
+
* surfaces as a memory in recall or governance passes, and INSIDE the QMD
|
|
7
|
+
* collection root so the markdown stays full-text searchable once an index
|
|
8
|
+
* update runs. Per-source sync state lives at
|
|
9
|
+
* `<memoryDir>/state/locations/sync.json` and is written only after the day
|
|
10
|
+
* document is durable (see pipeline.ts).
|
|
11
|
+
*
|
|
12
|
+
* Both writes are atomic (temp file + rename) and containment-checked: a
|
|
13
|
+
* symlinked `locations/` root or day file is rejected even when it resolves
|
|
14
|
+
* inside the memory dir (AGENTS.md pattern #3), and a path that escapes the
|
|
15
|
+
* memory dir is refused outright.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { createHash } from "node:crypto";
|
|
19
|
+
import { lstat, mkdir, readFile, readdir, realpath } from "node:fs/promises";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
|
|
22
|
+
import { writeFileAtomically } from "../maintenance/atomic-file.js";
|
|
23
|
+
import { pathIsInside } from "../utils/path-containment.js";
|
|
24
|
+
import { isValidLocationDate, placeDurations } from "./intervals.js";
|
|
25
|
+
import type { LocationSegment } from "./types.js";
|
|
26
|
+
|
|
27
|
+
export const LOCATIONS_DIR_NAME = "locations";
|
|
28
|
+
export const LOCATION_DAY_KIND = "location-day";
|
|
29
|
+
export const LOCATION_DAY_FORMAT_VERSION = 1;
|
|
30
|
+
|
|
31
|
+
/** Cap on remembered day payloads per source (~3 months of days). */
|
|
32
|
+
const MAX_TRACKED_LOCATION_DAYS = 90;
|
|
33
|
+
|
|
34
|
+
const DAY_FILE_NAME_PATTERN = /^\d{4}-\d{2}-\d{2}\.md$/;
|
|
35
|
+
|
|
36
|
+
// Serializes state load→mutate→save sections per state file so two sources
|
|
37
|
+
// syncing concurrently cannot lose-update each other's entries.
|
|
38
|
+
const stateLocks = new Map<string, Promise<unknown>>();
|
|
39
|
+
|
|
40
|
+
function withStateLock<T>(key: string, fn: () => Promise<T>): Promise<T> {
|
|
41
|
+
const prior = stateLocks.get(key) ?? Promise.resolve();
|
|
42
|
+
const next = prior.then(fn, fn);
|
|
43
|
+
const tail = next.then(
|
|
44
|
+
() => undefined,
|
|
45
|
+
() => undefined,
|
|
46
|
+
);
|
|
47
|
+
stateLocks.set(key, tail);
|
|
48
|
+
void tail.then(() => {
|
|
49
|
+
if (stateLocks.get(key) === tail) stateLocks.delete(key);
|
|
50
|
+
});
|
|
51
|
+
return next;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function locationDayFilePath(memoryDir: string, date: string): string {
|
|
55
|
+
if (!isValidLocationDate(date)) {
|
|
56
|
+
// Never interpolate an unvalidated date into a filesystem path (a `../`
|
|
57
|
+
// would escape <memoryDir>/locations/). Reject loudly.
|
|
58
|
+
throw new RangeError(`Invalid location date "${date}"; expected a real YYYY-MM-DD day.`);
|
|
59
|
+
}
|
|
60
|
+
return path.join(memoryDir, LOCATIONS_DIR_NAME, `${date}.md`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function locationSyncStateFilePath(memoryDir: string): string {
|
|
64
|
+
return path.join(memoryDir, "state", "locations", "sync.json");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** A source's derived day payload, kept in sync state for multi-source merges. */
|
|
68
|
+
export interface LocationSourceDayPayload {
|
|
69
|
+
observationCount: number;
|
|
70
|
+
segments: LocationSegment[];
|
|
71
|
+
providerDisplayName?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface LocationSourceSyncState {
|
|
75
|
+
lastSyncedAtUtc?: string;
|
|
76
|
+
days?: Record<string, LocationSourceDayPayload>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface LocationSyncStateFile {
|
|
80
|
+
version: 1;
|
|
81
|
+
sources: Record<string, LocationSourceSyncState>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function emptyLocationSyncState(): LocationSyncStateFile {
|
|
85
|
+
return { version: 1, sources: {} };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function loadLocationSyncState(memoryDir: string): Promise<LocationSyncStateFile> {
|
|
89
|
+
let raw: string;
|
|
90
|
+
try {
|
|
91
|
+
raw = await readFile(locationSyncStateFilePath(memoryDir), "utf-8");
|
|
92
|
+
} catch (err) {
|
|
93
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") return emptyLocationSyncState();
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
let parsed: unknown;
|
|
97
|
+
try {
|
|
98
|
+
parsed = JSON.parse(raw);
|
|
99
|
+
} catch {
|
|
100
|
+
// A corrupt state file must not brick syncing forever; treat it as a cold
|
|
101
|
+
// start. The worst case is re-syncing days already on disk, and the day
|
|
102
|
+
// file's contentHash makes that rewrite idempotent.
|
|
103
|
+
return emptyLocationSyncState();
|
|
104
|
+
}
|
|
105
|
+
if (
|
|
106
|
+
typeof parsed !== "object" ||
|
|
107
|
+
parsed === null ||
|
|
108
|
+
Array.isArray(parsed) ||
|
|
109
|
+
typeof (parsed as LocationSyncStateFile).sources !== "object" ||
|
|
110
|
+
(parsed as LocationSyncStateFile).sources === null
|
|
111
|
+
) {
|
|
112
|
+
return emptyLocationSyncState();
|
|
113
|
+
}
|
|
114
|
+
return { version: 1, sources: (parsed as LocationSyncStateFile).sources };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function saveLocationSyncState(
|
|
118
|
+
memoryDir: string,
|
|
119
|
+
state: LocationSyncStateFile,
|
|
120
|
+
): Promise<void> {
|
|
121
|
+
await mkdir(path.dirname(locationSyncStateFilePath(memoryDir)), { recursive: true, mode: 0o700 });
|
|
122
|
+
await writeFileAtomically(
|
|
123
|
+
locationSyncStateFilePath(memoryDir),
|
|
124
|
+
`${JSON.stringify(state, null, 2)}\n`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function pruneSourceDays(source: LocationSourceSyncState): void {
|
|
129
|
+
const days = source.days;
|
|
130
|
+
if (days === undefined) return;
|
|
131
|
+
const keys = Object.keys(days).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
132
|
+
for (const key of keys.slice(0, Math.max(0, keys.length - MAX_TRACKED_LOCATION_DAYS))) {
|
|
133
|
+
delete days[key];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Record one source's completed day sync. Runs the whole read-modify-write
|
|
139
|
+
* under the per-directory state lock so concurrent sources cannot clobber
|
|
140
|
+
* each other, and prunes day payloads beyond the retention cap.
|
|
141
|
+
*/
|
|
142
|
+
export async function updateLocationSourceDay(
|
|
143
|
+
memoryDir: string,
|
|
144
|
+
sourceId: string,
|
|
145
|
+
date: string,
|
|
146
|
+
payload: LocationSourceDayPayload,
|
|
147
|
+
completedAtUtc: string,
|
|
148
|
+
): Promise<LocationSyncStateFile> {
|
|
149
|
+
if (!isValidLocationDate(date)) {
|
|
150
|
+
throw new RangeError(`Invalid location date "${date}"; expected a real YYYY-MM-DD day.`);
|
|
151
|
+
}
|
|
152
|
+
return withStateLock(locationSyncStateFilePath(memoryDir), async () => {
|
|
153
|
+
const state = await loadLocationSyncState(memoryDir);
|
|
154
|
+
const source = state.sources[sourceId] ?? {};
|
|
155
|
+
const days = source.days ?? {};
|
|
156
|
+
days[date] = payload;
|
|
157
|
+
source.days = days;
|
|
158
|
+
source.lastSyncedAtUtc = completedAtUtc;
|
|
159
|
+
state.sources[sourceId] = source;
|
|
160
|
+
for (const candidate of Object.values(state.sources)) pruneSourceDays(candidate);
|
|
161
|
+
await saveLocationSyncState(memoryDir, state);
|
|
162
|
+
return state;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
/** Frontmatter persisted on a rendered day document. */
|
|
166
|
+
export interface LocationDayMeta {
|
|
167
|
+
kind: typeof LOCATION_DAY_KIND;
|
|
168
|
+
date: string;
|
|
169
|
+
timezone: string;
|
|
170
|
+
formatVersion: number;
|
|
171
|
+
sources: string[];
|
|
172
|
+
observationCount: number;
|
|
173
|
+
/** SHA-256 of the rendered body (rebuild idempotency). */
|
|
174
|
+
contentHash: string;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** A parsed day document (frontmatter + rendered body). */
|
|
178
|
+
export interface LocationDayDocument {
|
|
179
|
+
meta: LocationDayMeta;
|
|
180
|
+
body: string;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** The per-source view a day document is composed from. */
|
|
184
|
+
export interface LocationDaySourceEntry extends LocationSourceDayPayload {
|
|
185
|
+
/** Provider display name for the timeline heading; falls back to the id. */
|
|
186
|
+
providerDisplayName?: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function hashLocationBody(body: string): string {
|
|
190
|
+
return createHash("sha256").update(body, "utf8").digest("hex");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function clockHhMm(iso: string, timezone: string): string {
|
|
194
|
+
return new Intl.DateTimeFormat("en-GB", {
|
|
195
|
+
timeZone: timezone,
|
|
196
|
+
hour: "2-digit",
|
|
197
|
+
minute: "2-digit",
|
|
198
|
+
hour12: false,
|
|
199
|
+
}).format(new Date(iso));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function formatDuration(ms: number): string {
|
|
203
|
+
const totalMinutes = Math.round(ms / 60_000);
|
|
204
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
205
|
+
const minutes = totalMinutes % 60;
|
|
206
|
+
if (hours <= 0) return `${minutes}m`;
|
|
207
|
+
return `${hours}h ${String(minutes).padStart(2, "0")}m`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Collapse whitespace and strip markdown emphasis/backticks from a label. */
|
|
211
|
+
function safeLabel(label: string): string {
|
|
212
|
+
return label.replace(/\s+/g, " ").replace(/[`*_]/g, "").trim();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function formatPlaceLabel(segment: LocationSegment): string {
|
|
216
|
+
const label = safeLabel(segment.place.label);
|
|
217
|
+
return segment.place.kind === undefined ? label : `${label} (${segment.place.kind})`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function formatCoordinates(segment: LocationSegment): string {
|
|
221
|
+
if (segment.place.latitude === undefined || segment.place.longitude === undefined) return "";
|
|
222
|
+
return ` @ ${segment.place.latitude.toFixed(4)},${segment.place.longitude.toFixed(4)}`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function composeLocationDayBody(
|
|
226
|
+
date: string,
|
|
227
|
+
timezone: string,
|
|
228
|
+
sources: Record<string, LocationDaySourceEntry>,
|
|
229
|
+
): string {
|
|
230
|
+
const lines: string[] = [`# Location day — ${date} (${timezone})`, ""];
|
|
231
|
+
const allSegments: LocationSegment[] = [];
|
|
232
|
+
let observationCount = 0;
|
|
233
|
+
for (const entry of Object.values(sources)) {
|
|
234
|
+
allSegments.push(...entry.segments);
|
|
235
|
+
observationCount += entry.observationCount;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
lines.push("## Summary", "");
|
|
239
|
+
if (observationCount === 0) {
|
|
240
|
+
lines.push("No location observations recorded.", "");
|
|
241
|
+
} else {
|
|
242
|
+
const durations = placeDurations(allSegments).sort((a, b) => b.totalMs - a.totalMs);
|
|
243
|
+
lines.push(
|
|
244
|
+
`- Places visited: ${durations.length}`,
|
|
245
|
+
...durations.map(
|
|
246
|
+
(duration) => `- ${safeLabel(duration.place.label)}: ${formatDuration(duration.totalMs)}`,
|
|
247
|
+
),
|
|
248
|
+
"",
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const sourceIds = Object.keys(sources).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
253
|
+
for (const sourceId of sourceIds) {
|
|
254
|
+
const entry = sources[sourceId]!;
|
|
255
|
+
lines.push(`## Timeline — ${safeLabel(entry.providerDisplayName ?? sourceId)}`, "");
|
|
256
|
+
if (entry.segments.length === 0) {
|
|
257
|
+
lines.push("No observations.", "");
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
for (const segment of entry.segments) {
|
|
261
|
+
lines.push(
|
|
262
|
+
`- ${clockHhMm(segment.startUtc, timezone)}–${clockHhMm(segment.endUtc, timezone)} — ${formatPlaceLabel(segment)}${formatCoordinates(segment)}`,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
lines.push("");
|
|
266
|
+
}
|
|
267
|
+
return `${lines.join("\n").trimEnd()}\n`;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function composeLocationDayMeta(
|
|
271
|
+
date: string,
|
|
272
|
+
timezone: string,
|
|
273
|
+
sources: Record<string, LocationDaySourceEntry>,
|
|
274
|
+
body: string,
|
|
275
|
+
): LocationDayMeta {
|
|
276
|
+
let observationCount = 0;
|
|
277
|
+
for (const entry of Object.values(sources)) observationCount += entry.observationCount;
|
|
278
|
+
return {
|
|
279
|
+
kind: LOCATION_DAY_KIND,
|
|
280
|
+
date,
|
|
281
|
+
timezone,
|
|
282
|
+
formatVersion: LOCATION_DAY_FORMAT_VERSION,
|
|
283
|
+
sources: Object.keys(sources).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)),
|
|
284
|
+
observationCount,
|
|
285
|
+
contentHash: hashLocationBody(body),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function frontmatterScalar(key: string, value: string | number): string {
|
|
290
|
+
return `${key}: ${typeof value === "number" ? value : JSON.stringify(value)}`;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function frontmatterList(key: string, values: readonly string[]): string[] {
|
|
294
|
+
if (values.length === 0) return [`${key}: []`];
|
|
295
|
+
const lines = [`${key}:`];
|
|
296
|
+
for (const value of values) lines.push(` - ${JSON.stringify(value)}`);
|
|
297
|
+
return lines;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function serializeLocationDay(meta: LocationDayMeta, body: string): string {
|
|
301
|
+
const lines: string[] = ["---"];
|
|
302
|
+
lines.push(frontmatterScalar("kind", meta.kind));
|
|
303
|
+
lines.push(frontmatterScalar("date", meta.date));
|
|
304
|
+
lines.push(frontmatterScalar("timezone", meta.timezone));
|
|
305
|
+
lines.push(frontmatterScalar("formatVersion", meta.formatVersion));
|
|
306
|
+
lines.push(...frontmatterList("sources", meta.sources));
|
|
307
|
+
lines.push(frontmatterScalar("observationCount", meta.observationCount));
|
|
308
|
+
lines.push(frontmatterScalar("contentHash", meta.contentHash));
|
|
309
|
+
lines.push("---", "", body);
|
|
310
|
+
return lines.join("\n");
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function parseScalar(raw: string): string {
|
|
314
|
+
try {
|
|
315
|
+
const parsed = JSON.parse(raw);
|
|
316
|
+
return typeof parsed === "string" ? parsed : raw;
|
|
317
|
+
} catch {
|
|
318
|
+
return raw;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** A day document's frontmatter, parsed for idempotency checks and status. */
|
|
323
|
+
export interface LocationDaySummary {
|
|
324
|
+
date: string;
|
|
325
|
+
timezone: string;
|
|
326
|
+
sources: string[];
|
|
327
|
+
observationCount: number;
|
|
328
|
+
contentHash: string;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Parse a day document's frontmatter. Returns null when the content is not a
|
|
333
|
+
* location day document (wrong kind, missing frontmatter) so callers can
|
|
334
|
+
* distinguish a foreign file from a corrupt one.
|
|
335
|
+
*/
|
|
336
|
+
export function parseLocationDaySummary(raw: string): LocationDaySummary | null {
|
|
337
|
+
if (!raw.startsWith("---\n")) return null;
|
|
338
|
+
const closeIndex = raw.indexOf("\n---\n", 4);
|
|
339
|
+
if (closeIndex === -1) return null;
|
|
340
|
+
const scalars: Record<string, string> = {};
|
|
341
|
+
const lists: Record<string, string[]> = {};
|
|
342
|
+
let currentList: string | null = null;
|
|
343
|
+
for (const line of raw.slice(4, closeIndex).split("\n")) {
|
|
344
|
+
const listItem = /^ {2}- (.*)$/.exec(line);
|
|
345
|
+
if (listItem !== null && currentList !== null) {
|
|
346
|
+
lists[currentList]!.push(parseScalar(listItem[1]!));
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
currentList = null;
|
|
350
|
+
const separator = line.indexOf(":");
|
|
351
|
+
if (separator === -1) continue;
|
|
352
|
+
const key = line.slice(0, separator);
|
|
353
|
+
const value = line.slice(separator + 1).trim();
|
|
354
|
+
if (value === "" || value === "[]") {
|
|
355
|
+
lists[key] = [];
|
|
356
|
+
currentList = key;
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
if (value.startsWith("[")) {
|
|
360
|
+
lists[key] = [parseScalar(value)];
|
|
361
|
+
currentList = key;
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
scalars[key] = value;
|
|
365
|
+
}
|
|
366
|
+
if (scalars.kind === undefined || parseScalar(scalars.kind) !== LOCATION_DAY_KIND) return null;
|
|
367
|
+
if (scalars.date === undefined || scalars.contentHash === undefined) return null;
|
|
368
|
+
return {
|
|
369
|
+
date: parseScalar(scalars.date),
|
|
370
|
+
timezone: scalars.timezone === undefined ? "UTC" : parseScalar(scalars.timezone),
|
|
371
|
+
sources: lists.sources ?? [],
|
|
372
|
+
observationCount: scalars.observationCount === undefined ? 0 : Number(scalars.observationCount) || 0,
|
|
373
|
+
contentHash: parseScalar(scalars.contentHash),
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// ── Containment-checked day-file IO ─────────────────────────────────────────
|
|
378
|
+
|
|
379
|
+
function isErrnoCode(err: unknown, code: string): boolean {
|
|
380
|
+
return err instanceof Error && (err as NodeJS.ErrnoException).code === code;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async function assertNotSymlink(target: string, kind: "directory" | "day file"): Promise<void> {
|
|
384
|
+
let stat: { isSymbolicLink(): boolean };
|
|
385
|
+
try {
|
|
386
|
+
stat = await lstat(target);
|
|
387
|
+
} catch (err) {
|
|
388
|
+
if (isErrnoCode(err, "ENOENT")) return;
|
|
389
|
+
throw err;
|
|
390
|
+
}
|
|
391
|
+
if (stat.isSymbolicLink()) {
|
|
392
|
+
throw new Error(
|
|
393
|
+
`locations ${kind} '${path.basename(target)}' is a symbolic link — refusing to follow it even when it resolves inside the memory dir (AGENTS.md pattern #3)`,
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async function assertNoEscape(rootReal: string, candidate: string): Promise<void> {
|
|
399
|
+
let candidateReal: string;
|
|
400
|
+
try {
|
|
401
|
+
candidateReal = await realpath(candidate);
|
|
402
|
+
} catch {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
if (!pathIsInside(rootReal, candidateReal)) {
|
|
406
|
+
throw new Error(
|
|
407
|
+
"location day path resolves outside the memory dir — refusing to follow a symlink/traversal (AGENTS.md pattern #3)",
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async function assertDayPathContained(memoryDir: string, targetPath: string): Promise<void> {
|
|
413
|
+
let rootReal: string;
|
|
414
|
+
try {
|
|
415
|
+
rootReal = await realpath(memoryDir);
|
|
416
|
+
} catch {
|
|
417
|
+
rootReal = path.resolve(memoryDir);
|
|
418
|
+
}
|
|
419
|
+
const locationsDir = path.join(memoryDir, LOCATIONS_DIR_NAME);
|
|
420
|
+
await assertNoEscape(rootReal, locationsDir);
|
|
421
|
+
await assertNoEscape(rootReal, targetPath);
|
|
422
|
+
// Reject a symlinked locations root or day file outright — even when the
|
|
423
|
+
// link resolves inside the memory dir (aliasing / tampering) — before IO.
|
|
424
|
+
await assertNotSymlink(locationsDir, "directory");
|
|
425
|
+
await assertNotSymlink(targetPath, "day file");
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Idempotently persist a day document: skip the write when the on-disk
|
|
430
|
+
* contentHash already matches. The write is atomic and containment-checked;
|
|
431
|
+
* the caller (pipeline) advances sync state only after this resolves.
|
|
432
|
+
*/
|
|
433
|
+
export async function writeLocationDay(
|
|
434
|
+
memoryDir: string,
|
|
435
|
+
date: string,
|
|
436
|
+
serialized: string,
|
|
437
|
+
): Promise<boolean> {
|
|
438
|
+
const target = locationDayFilePath(memoryDir, date);
|
|
439
|
+
await assertDayPathContained(memoryDir, target);
|
|
440
|
+
try {
|
|
441
|
+
if ((await readFile(target, "utf-8")) === serialized) return false;
|
|
442
|
+
} catch (err) {
|
|
443
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
444
|
+
}
|
|
445
|
+
await mkdir(path.dirname(target), { recursive: true, mode: 0o700 });
|
|
446
|
+
await writeFileAtomically(target, serialized);
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** Read a stored day document's raw markdown; null when absent. */
|
|
451
|
+
export async function readLocationDay(memoryDir: string, date: string): Promise<string | null> {
|
|
452
|
+
const target = locationDayFilePath(memoryDir, date);
|
|
453
|
+
await assertDayPathContained(memoryDir, target);
|
|
454
|
+
try {
|
|
455
|
+
return await readFile(target, "utf-8");
|
|
456
|
+
} catch (err) {
|
|
457
|
+
if (isErrnoCode(err, "ENOENT")) return null;
|
|
458
|
+
throw err;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/** List dates that have a stored day document, newest first. */
|
|
463
|
+
export async function listLocationDayDates(memoryDir: string): Promise<string[]> {
|
|
464
|
+
const locationsDir = path.join(memoryDir, LOCATIONS_DIR_NAME);
|
|
465
|
+
await assertDayPathContained(memoryDir, locationsDir);
|
|
466
|
+
let entries: string[];
|
|
467
|
+
try {
|
|
468
|
+
entries = await readdir(locationsDir);
|
|
469
|
+
} catch (err) {
|
|
470
|
+
if (isErrnoCode(err, "ENOENT")) return [];
|
|
471
|
+
throw err;
|
|
472
|
+
}
|
|
473
|
+
const dates = entries
|
|
474
|
+
.filter((entry) => DAY_FILE_NAME_PATTERN.test(entry))
|
|
475
|
+
.map((entry) => entry.slice(0, -3));
|
|
476
|
+
dates.sort((a, b) => (a > b ? -1 : a < b ? 1 : 0));
|
|
477
|
+
return dates;
|
|
478
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Location context subsystem — shared types (issue #2044).
|
|
3
|
+
*
|
|
4
|
+
* A host-agnostic foundation: the provider contract, the observation/day
|
|
5
|
+
* shapes, and health-check results. No HTTP client lives here — providers are
|
|
6
|
+
* registered from host adapters (a future provider package is one such
|
|
7
|
+
* adapter), keeping `@remnic/core` free of host SDKs and network specifics.
|
|
8
|
+
*
|
|
9
|
+
* All timestamps are UTC ISO-8601; day bucketing is half-open [start, end).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** A named place the user was observed at. */
|
|
13
|
+
export interface LocationPlace {
|
|
14
|
+
/** Provider-stable place identifier. */
|
|
15
|
+
id: string;
|
|
16
|
+
/** Human-readable label, already redacted by the provider. */
|
|
17
|
+
label: string;
|
|
18
|
+
kind?: "home" | "work" | "poi" | "transit" | "other";
|
|
19
|
+
/** Present only when coordinate retention is enabled. */
|
|
20
|
+
latitude?: number;
|
|
21
|
+
longitude?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** One place observation at a single instant, from a single provider. */
|
|
25
|
+
export interface LocationObservation {
|
|
26
|
+
/** UTC ISO-8601 observation instant. */
|
|
27
|
+
observedAtUtc: string;
|
|
28
|
+
place: LocationPlace;
|
|
29
|
+
/** Provider confidence in [0,1], when reported. */
|
|
30
|
+
confidence?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** One page of observations from a provider fetch. */
|
|
34
|
+
export interface LocationObservationPage {
|
|
35
|
+
observations: LocationObservation[];
|
|
36
|
+
nextCursor: string | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Result of a provider auth/health probe. */
|
|
40
|
+
export interface LocationProviderCheck {
|
|
41
|
+
ok: boolean;
|
|
42
|
+
detail?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Provider contract for a location source. Implemented by host adapters and
|
|
47
|
+
* registered in `registry.ts`; core never statically imports a provider
|
|
48
|
+
* package, so à-la-carte installs stay partial (a source whose provider is
|
|
49
|
+
* not registered is skipped, not an error).
|
|
50
|
+
*/
|
|
51
|
+
export interface LocationProvider {
|
|
52
|
+
/** Stable provider id (registry key; matches a config source id). */
|
|
53
|
+
id: string;
|
|
54
|
+
displayName: string;
|
|
55
|
+
/** Probe connectivity/auth without mutating anything. */
|
|
56
|
+
verify(signal?: AbortSignal): Promise<LocationProviderCheck>;
|
|
57
|
+
/** Fetch every observation in the half-open [startUtc, endUtc) window. */
|
|
58
|
+
fetchObservations(opts: {
|
|
59
|
+
startUtc: string;
|
|
60
|
+
endUtc: string;
|
|
61
|
+
cursor?: string | null;
|
|
62
|
+
signal?: AbortSignal;
|
|
63
|
+
}): Promise<LocationObservationPage>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** One configured location source: a registered provider plus its toggle. */
|
|
67
|
+
export interface LocationSourceConfig {
|
|
68
|
+
/** Must match a registered provider id. */
|
|
69
|
+
id: string;
|
|
70
|
+
/** `false` short-circuits this source only. */
|
|
71
|
+
enabled: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Opt-in location synchronization settings. */
|
|
75
|
+
export interface LocationConfig {
|
|
76
|
+
/** Master gate: `false` short-circuits every location path. */
|
|
77
|
+
enabled: boolean;
|
|
78
|
+
/** IANA timezone used to bucket observations into local days. */
|
|
79
|
+
timezone: string;
|
|
80
|
+
/** Number of local days to synchronize per run; integer 1..90. */
|
|
81
|
+
syncDays: number;
|
|
82
|
+
/**
|
|
83
|
+
* When `false` (the default), place coordinates are dropped before any
|
|
84
|
+
* persistence, rendering, or error reporting — only place labels remain.
|
|
85
|
+
*/
|
|
86
|
+
retainCoordinates: boolean;
|
|
87
|
+
sources: LocationSourceConfig[];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** A time-resolved place visit derived from consecutive observations. */
|
|
91
|
+
export interface LocationSegment {
|
|
92
|
+
startUtc: string;
|
|
93
|
+
endUtc: string;
|
|
94
|
+
place: LocationPlace;
|
|
95
|
+
}
|
|
@@ -6,6 +6,7 @@ import { ALL_CATEGORY_DIRS } from "../utils/category-dir.js";
|
|
|
6
6
|
import {
|
|
7
7
|
isActivityDigestPath,
|
|
8
8
|
isArtifactMemoryPath,
|
|
9
|
+
isLocationDayPath,
|
|
9
10
|
isMeetingRecordPath,
|
|
10
11
|
} from "./orchestrator-helpers.js";
|
|
11
12
|
|
|
@@ -133,6 +134,7 @@ export function isSearchExcludedPath(
|
|
|
133
134
|
if (
|
|
134
135
|
isArtifactMemoryPath(candidate) ||
|
|
135
136
|
isNamespacedActivityDigestPath(candidate, policy.memoryDir) ||
|
|
137
|
+
isNamespacedLocationDayPath(candidate, policy.memoryDir) ||
|
|
136
138
|
isMeetingRecordPath(candidate)
|
|
137
139
|
) {
|
|
138
140
|
return true;
|
|
@@ -160,6 +162,21 @@ function isNamespacedActivityDigestPath(candidate: string, memoryDir?: string):
|
|
|
160
162
|
return isActivityDigestPath(namespaced[2] ?? "", memoryDir);
|
|
161
163
|
}
|
|
162
164
|
|
|
165
|
+
/**
|
|
166
|
+
* The location day-file check, applied against the NAMESPACE root a hit came
|
|
167
|
+
* from — same rationale as `isNamespacedActivityDigestPath`: a namespaced
|
|
168
|
+
* search rewrites hits to absolute paths beneath `…/namespaces/<ns>/`, so the
|
|
169
|
+
* root-aware predicate is re-measured against the namespace's own root.
|
|
170
|
+
*/
|
|
171
|
+
function isNamespacedLocationDayPath(candidate: string, memoryDir?: string): boolean {
|
|
172
|
+
if (isLocationDayPath(candidate, memoryDir)) return true;
|
|
173
|
+
if (memoryDir === undefined || memoryDir.length === 0) return false;
|
|
174
|
+
const relative = path.relative(memoryDir, path.resolve(memoryDir, candidate));
|
|
175
|
+
const namespaced = /^namespaces[\\/]([^\\/]+)[\\/](.*)$/.exec(relative);
|
|
176
|
+
if (namespaced === null) return false;
|
|
177
|
+
return isLocationDayPath(namespaced[2] ?? "", memoryDir);
|
|
178
|
+
}
|
|
179
|
+
|
|
163
180
|
/** A path as given, plus its form with a leading COLLECTION segment removed. */
|
|
164
181
|
function collectionSpellings(
|
|
165
182
|
filePath: string,
|
|
@@ -643,6 +643,28 @@ export function isActivityDigestPath(filePath: string, memoryRoot?: string): boo
|
|
|
643
643
|
return ACTIVITY_DIGEST_ANYWHERE.test(filePath);
|
|
644
644
|
}
|
|
645
645
|
|
|
646
|
+
/** Exactly `locations/<YYYY-MM-DD>.md`, at the top level of a memory root. */
|
|
647
|
+
const LOCATION_DAY_TOPLEVEL = /^locations[\\/]\d{4}-\d{2}-\d{2}\.md$/i;
|
|
648
|
+
/** The day-file shape anywhere in a path (root-unaware best-effort). */
|
|
649
|
+
const LOCATION_DAY_ANYWHERE = /(?:^|[\\/])locations[\\/]\d{4}-\d{2}-\d{2}\.md$/i;
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Location day documents (issue #2044) live at
|
|
653
|
+
* `<memoryRoot>/locations/<YYYY-MM-DD>.md` — a dedicated searchable surface,
|
|
654
|
+
* never generic recall (place history must not auto-inject into ordinary
|
|
655
|
+
* prompts). Root-aware exactly like `isActivityDigestPath`: with a root, only
|
|
656
|
+
* the top-level `locations/<date>.md` matches, so an ordinary memory nested
|
|
657
|
+
* under a category (e.g. `facts/locations-note.md`) stays recallable; without
|
|
658
|
+
* a root, falls back to the day-file shape anywhere (best-effort).
|
|
659
|
+
*/
|
|
660
|
+
export function isLocationDayPath(filePath: string, memoryRoot?: string): boolean {
|
|
661
|
+
if (memoryRoot !== undefined && memoryRoot.length > 0) {
|
|
662
|
+
const relative = path.relative(memoryRoot, path.resolve(memoryRoot, filePath));
|
|
663
|
+
return LOCATION_DAY_TOPLEVEL.test(relative);
|
|
664
|
+
}
|
|
665
|
+
return LOCATION_DAY_ANYWHERE.test(filePath);
|
|
666
|
+
}
|
|
667
|
+
|
|
646
668
|
export function buildCompressionGuidelinesMarkdown(
|
|
647
669
|
events: MemoryActionEvent[],
|
|
648
670
|
generatedAtIso: string = new Date().toISOString(),
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BoundedJsonlStateConfig } from "./bounded-jsonl-state.js";
|
|
2
2
|
import type { MeetingsConfig } from "./meetings/types.js";
|
|
3
|
+
import type { LocationConfig } from "./location/types.js";
|
|
3
4
|
import type { ActivityConfig } from "./activity/types.js";
|
|
4
5
|
import type { WearablesConfig } from "./wearables/types.js";
|
|
5
6
|
import type { ExtractionLivenessConfig } from "./extraction-liveness.js";
|
|
@@ -1232,10 +1233,8 @@ export interface PluginConfig extends BoundedJsonlStateConfig, SecurityConfig {
|
|
|
1232
1233
|
*/
|
|
1233
1234
|
wearables: WearablesConfig;
|
|
1234
1235
|
activity: ActivityConfig;
|
|
1235
|
-
/** Retrospective meeting intelligence (issue #1900): detect + fuse meetings
|
|
1236
|
-
* from already-ingested audio + screen activity. Disabled by default.
|
|
1237
|
-
*/
|
|
1238
1236
|
meetings: MeetingsConfig;
|
|
1237
|
+
location: LocationConfig;
|
|
1239
1238
|
/**
|
|
1240
1239
|
* At-rest encryption configuration (issue #690 PR 3/4).
|
|
1241
1240
|
*
|