@remnic/core 9.69.45 → 9.69.47
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 +15 -14
- package/dist/access-admin-ops-surface.js +40 -39
- package/dist/access-audit.js +2 -2
- package/dist/access-authorization-probe.d.ts +15 -14
- package/dist/access-authorization-probe.js +41 -40
- package/dist/access-boundary.d.ts +15 -14
- package/dist/access-boundary.js +40 -39
- package/dist/access-cli.js +110 -104
- package/dist/access-cli.js.map +1 -1
- package/dist/access-coding-context-resolution.d.ts +1 -1
- package/dist/access-extraction-force-flush.d.ts +15 -14
- package/dist/access-extraction-force-flush.js +40 -39
- package/dist/access-health-types.d.ts +1 -1
- package/dist/access-http-briefing.d.ts +15 -14
- package/dist/access-http-briefing.js +41 -40
- package/dist/access-http-lcm-compaction.d.ts +15 -14
- package/dist/access-http-lifecycle-flush.d.ts +15 -14
- package/dist/access-http-offline-stream.d.ts +15 -14
- package/dist/access-http-query.d.ts +2 -2
- package/dist/access-http-query.js +42 -41
- package/dist/access-http.d.ts +16 -15
- package/dist/access-http.js +64 -60
- package/dist/access-identity-continuity-surface.d.ts +11 -11
- package/dist/access-identity-continuity-surface.js +40 -39
- package/dist/access-lcm-surface.d.ts +15 -14
- package/dist/access-lcm-surface.js +40 -39
- package/dist/access-mcp-output-schemas.js +1 -1
- package/dist/access-mcp-shared-context-tools.d.ts +15 -14
- package/dist/access-mcp.d.ts +15 -14
- package/dist/access-mcp.js +53 -50
- package/dist/access-memory-search-fanout.d.ts +2 -2
- package/dist/{access-namespace-preflight-DDnsN22V.d.ts → access-namespace-preflight-CetF9ziA.d.ts} +1 -1
- package/dist/access-namespace-preflight.d.ts +3 -3
- package/dist/access-namespace-preflight.js +40 -39
- package/dist/access-observe-transcript.d.ts +11 -11
- package/dist/access-observe-write-category.d.ts +3 -3
- package/dist/access-observe-write-surface.d.ts +15 -14
- package/dist/access-observe-write-surface.js +40 -39
- package/dist/access-offline-manifest.d.ts +11 -11
- package/dist/access-offline-manifest.js +2 -2
- package/dist/access-operations-batch.js +42 -41
- package/dist/access-operations.d.ts +16 -15
- package/dist/access-operations.js +49 -47
- package/dist/access-recall-concurrency.d.ts +15 -14
- package/dist/access-recall-concurrency.js +40 -39
- package/dist/access-recall-response.d.ts +15 -14
- package/dist/access-recall-response.js +40 -39
- package/dist/access-recall-surface.d.ts +15 -14
- package/dist/access-recall-surface.js +40 -39
- package/dist/access-schema.d.ts +1 -1
- package/dist/{access-service-CDteK_Gt.d.ts → access-service-BXYrJ3pO.d.ts} +13 -10
- package/dist/access-service-helpers.d.ts +15 -14
- package/dist/access-service.d.ts +15 -14
- package/dist/access-service.js +40 -39
- package/dist/access-surface-catalog.d.ts +15 -14
- package/dist/access-surface-catalog.js +48 -41
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/access-wearables-meetings-surface.d.ts +4 -4
- 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 +3 -2
- package/dist/active-recall.js.map +1 -1
- package/dist/activity/digest.d.ts +1 -1
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/ambient-provenance.d.ts +1 -1
- package/dist/artifact-search.d.ts +1 -1
- package/dist/background-generation.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 +11 -11
- package/dist/briefing-window.d.ts +2 -2
- package/dist/briefing.d.ts +2 -2
- package/dist/briefing.js +13 -13
- 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 +5 -5
- 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-Dl2hYqpS.d.ts → catalog-Bns9zpl3.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 +14 -14
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-OREGA7GY.js → chunk-2JFSQWFH.js} +5 -5
- package/dist/chunk-2JFSQWFH.js.map +1 -0
- package/dist/{chunk-C3MCMFNV.js → chunk-2QX65IEC.js} +2 -2
- package/dist/{chunk-HBGSZ62Z.js → chunk-3RKVSECD.js} +8 -8
- package/dist/{chunk-7LKXP4QG.js → chunk-44BILQ7U.js} +85 -43
- package/dist/chunk-44BILQ7U.js.map +1 -0
- package/dist/{chunk-R4HFCATE.js → chunk-4LPIVP77.js} +2 -2
- package/dist/{chunk-PW4HTWXL.js → chunk-55XWRL6F.js} +2 -2
- package/dist/{chunk-E5VOKZQV.js → chunk-5KBI2MOC.js} +1 -1
- package/dist/{chunk-ZQ533JDE.js → chunk-6A6HSXWV.js} +2 -2
- package/dist/{chunk-4HTQ76O4.js → chunk-7SJCSDQ3.js} +2 -2
- package/dist/{chunk-A2YN3FR4.js → chunk-7YUX52XZ.js} +3 -3
- package/dist/{chunk-NKDQXDFL.js → chunk-AS4YKT75.js} +36 -26
- package/dist/chunk-AS4YKT75.js.map +1 -0
- package/dist/{chunk-LQVXSRAJ.js → chunk-B4EGAHHS.js} +2 -2
- package/dist/{chunk-EUP4AXIW.js → chunk-B6VDWDFN.js} +2 -2
- package/dist/{chunk-Z5YNVWRD.js → chunk-BMENHAJL.js} +2 -2
- package/dist/{chunk-5ZXGH2ZL.js → chunk-BXOPUJ3W.js} +2 -2
- package/dist/{chunk-J6NEOCQC.js → chunk-CD7VTZIG.js} +2 -2
- package/dist/{chunk-EC5SVM6H.js → chunk-DE4ZGH2N.js} +2 -2
- package/dist/{chunk-2LRO73MN.js → chunk-DRLZB3CN.js} +2 -2
- package/dist/{chunk-SHEHQBCG.js → chunk-DUJYRVUD.js} +638 -45
- package/dist/chunk-DUJYRVUD.js.map +1 -0
- package/dist/{chunk-Q4MI2KCG.js → chunk-EGOWY342.js} +2 -2
- package/dist/chunk-EIB4FJVR.js +46 -0
- package/dist/chunk-EIB4FJVR.js.map +1 -0
- package/dist/{chunk-W44T2KHG.js → chunk-EWBDJQ5Y.js} +3 -3
- package/dist/{chunk-JMBTNVMW.js → chunk-FGRJJBFO.js} +2 -2
- package/dist/{chunk-F3TURDFG.js → chunk-FPB5LQWW.js} +4 -1
- package/dist/{chunk-F3TURDFG.js.map → chunk-FPB5LQWW.js.map} +1 -1
- package/dist/{chunk-OOBUWMPY.js → chunk-G2SJRFGP.js} +6 -6
- package/dist/{chunk-WGZHSQDV.js → chunk-G2XZFGYN.js} +4 -4
- package/dist/{chunk-JPNUKKSL.js → chunk-GGROFIYX.js} +2 -2
- package/dist/chunk-GXJSLNYO.js +74 -0
- package/dist/chunk-GXJSLNYO.js.map +1 -0
- package/dist/{chunk-VA5QRTZX.js → chunk-HG74GEVY.js} +2 -2
- package/dist/chunk-HMDYSGY6.js +54 -0
- package/dist/chunk-HMDYSGY6.js.map +1 -0
- package/dist/{chunk-WUUGE7QC.js → chunk-HS75MCQF.js} +4 -4
- package/dist/{chunk-DO6R3LIE.js → chunk-I6IJDTRP.js} +2 -2
- package/dist/{chunk-T3F2HWIG.js → chunk-IO7K7GES.js} +2 -2
- package/dist/{chunk-6II55MXS.js → chunk-JXLEQIJ7.js} +2 -2
- package/dist/{chunk-4XTSKGLX.js → chunk-JYXLW22X.js} +3 -3
- package/dist/chunk-LI4APB6M.js +218 -0
- package/dist/chunk-LI4APB6M.js.map +1 -0
- package/dist/{chunk-IFULZYYQ.js → chunk-LIAM7U2J.js} +2 -2
- package/dist/{chunk-LZAJONO5.js → chunk-LIDJWAQV.js} +2 -2
- package/dist/{chunk-B55FYF6F.js → chunk-LYMJ36UI.js} +2 -2
- package/dist/chunk-M4DB6ZNN.js +87 -0
- package/dist/chunk-M4DB6ZNN.js.map +1 -0
- package/dist/{chunk-5TKHJ5RW.js → chunk-MMQVWKFY.js} +17 -13
- package/dist/chunk-MMQVWKFY.js.map +1 -0
- package/dist/{chunk-IAT5WFEU.js → chunk-MNG536IS.js} +261 -797
- package/dist/chunk-MNG536IS.js.map +1 -0
- package/dist/{chunk-AOMUIF3R.js → chunk-MQT3WP3F.js} +2 -2
- package/dist/{chunk-F6AYWLRN.js → chunk-NE45P3OF.js} +2 -2
- package/dist/{chunk-5XTILJRI.js → chunk-NMA7RQPR.js} +2 -2
- package/dist/{chunk-46A4RGBC.js → chunk-OS4JVTHR.js} +3 -3
- package/dist/{chunk-W6YDAIVB.js → chunk-OSTXIFZW.js} +13 -2
- package/dist/chunk-OSTXIFZW.js.map +1 -0
- package/dist/chunk-P3YNEMYY.js +212 -0
- package/dist/chunk-P3YNEMYY.js.map +1 -0
- package/dist/{chunk-C3YEB7CR.js → chunk-PA67GXYJ.js} +5 -5
- package/dist/{chunk-NGAFYIHQ.js → chunk-PPJLJD2D.js} +2 -2
- package/dist/{chunk-AG6PANBG.js → chunk-PXJ6HE2W.js} +11 -11
- package/dist/{chunk-BOWAP64Q.js → chunk-QB4VXCFN.js} +2 -2
- package/dist/{chunk-L63M2PE4.js → chunk-QGAKUOKU.js} +2 -2
- package/dist/{chunk-T2ZNUWUY.js → chunk-QKFYJVDG.js} +2 -2
- package/dist/{chunk-YHSEUZV5.js → chunk-QLCMOHRE.js} +2 -2
- package/dist/{chunk-Q4AUIFUA.js → chunk-QMWKENNK.js} +4 -4
- package/dist/{chunk-UJXNBZYJ.js → chunk-R4JTUTU6.js} +12 -5
- package/dist/chunk-R4JTUTU6.js.map +1 -0
- package/dist/{chunk-TFS5FAAC.js → chunk-ROBGBT3D.js} +213 -77
- package/dist/chunk-ROBGBT3D.js.map +1 -0
- package/dist/{chunk-TNQR7N52.js → chunk-S3YM4SZQ.js} +2 -1
- package/dist/chunk-S3YM4SZQ.js.map +1 -0
- package/dist/{chunk-RW4QNB4J.js → chunk-S45INSV3.js} +3 -1
- package/dist/{chunk-RW4QNB4J.js.map → chunk-S45INSV3.js.map} +1 -1
- package/dist/{chunk-HVGCQMTQ.js → chunk-SJ7ZNUXB.js} +2 -2
- package/dist/{chunk-WCQ7L54B.js → chunk-SK6HEQTW.js} +2 -2
- package/dist/{chunk-PGAB2V53.js → chunk-SMBWBEE7.js} +2 -2
- package/dist/{chunk-SPN5YFXN.js → chunk-SS33RR4F.js} +8 -8
- package/dist/{chunk-NRFUYVW7.js → chunk-T3T5HTZZ.js} +2 -2
- package/dist/{chunk-KG2OOAFF.js → chunk-TQSCR5DU.js} +6 -6
- package/dist/{chunk-7K5Q6COX.js → chunk-TVVEYCNW.js} +4 -4
- package/dist/{chunk-ZLVURGAJ.js → chunk-V2XNMIAM.js} +2 -2
- package/dist/{chunk-AS2NOPDM.js → chunk-V7IONNTJ.js} +2 -2
- package/dist/{chunk-OFBJD3V5.js → chunk-VV5GCNKG.js} +2 -2
- package/dist/{chunk-MDYZCPNQ.js → chunk-WL3PGPGU.js} +2 -2
- package/dist/{chunk-66YUOI53.js → chunk-WQW6KPVC.js} +1 -1
- package/dist/chunk-WQW6KPVC.js.map +1 -0
- package/dist/{chunk-2AV75BJW.js → chunk-WVI43D54.js} +24 -1
- package/dist/chunk-WVI43D54.js.map +1 -0
- package/dist/{chunk-N6I257OM.js → chunk-WYU66IVZ.js} +3 -3
- package/dist/{chunk-A7GQBJP5.js → chunk-X2NFFBTO.js} +4 -4
- package/dist/{chunk-D3DJYGU2.js → chunk-XEHLYA5Z.js} +3 -3
- package/dist/{chunk-JIWZVOQM.js → chunk-XKHRNAK7.js} +2 -2
- package/dist/{chunk-VW5TGNJN.js → chunk-XXLH6KFO.js} +2 -2
- package/dist/{chunk-WMTYEQO5.js → chunk-XZUHXKM3.js} +2 -2
- package/dist/{chunk-K4KE5MYE.js → chunk-Y5VT7BIT.js} +2 -2
- package/dist/{chunk-M7ECD3KN.js → chunk-YOSLKZLZ.js} +2 -2
- package/dist/{chunk-K75H7FSO.js → chunk-YXWMZAAN.js} +2 -2
- package/dist/{chunk-DSHSQGSN.js → chunk-ZEIJQ4I3.js} +2 -2
- package/dist/{cli-CZDhIcxu.d.ts → cli-B8YyIrPR.d.ts} +5 -5
- package/dist/cli.d.ts +17 -16
- package/dist/cli.js +81 -76
- package/dist/coding/export-okf-codegraph.d.ts +1 -1
- package/dist/coding/export-okf-codegraph.js +12 -12
- 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 +3 -2
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +13 -13
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +13 -13
- package/dist/consolidation-provenance-check.d.ts +2 -2
- package/dist/consolidation-undo.d.ts +2 -2
- package/dist/contradiction/index.d.ts +4 -4
- package/dist/converge-config.d.ts +1 -1
- package/dist/convergence-refresh.d.ts +2 -2
- package/dist/convergence-refresh.js +15 -15
- 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 +15 -15
- package/dist/cross-lingual-recall.d.ts +1 -1
- package/dist/day-summary.d.ts +1 -1
- package/dist/deep-recall-http-glue.d.ts +15 -14
- package/dist/deep-recall-mcp-tools.d.ts +15 -14
- package/dist/deep-recall-policy-llm.d.ts +2 -2
- package/dist/deep-recall-seeds.d.ts +1 -1
- package/dist/deep-recall.js +2 -2
- package/dist/delinearize.d.ts +1 -1
- package/dist/dependency-propagation-config.d.ts +1 -1
- package/dist/{dependency-propagation-delivery-BXaUoVUf.d.ts → dependency-propagation-delivery-B-LvcDaq.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 +13 -13
- package/dist/entity-schema.d.ts +1 -1
- package/dist/episodic-context.d.ts +1 -1
- package/dist/eval-trajectory-contracts.d.ts +1 -1
- package/dist/eval-trajectory-store.d.ts +1 -1
- package/dist/eval-trajectory.d.ts +1 -1
- package/dist/explicit-capture.d.ts +11 -11
- package/dist/explicit-capture.js +2 -2
- package/dist/external-wiki-access.d.ts +15 -14
- package/dist/external-wiki-access.js +41 -40
- 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 +15 -14
- package/dist/extraction-error-classification.d.ts +1 -1
- package/dist/extraction-evidence-class.d.ts +142 -0
- package/dist/extraction-evidence-class.js +113 -0
- package/dist/extraction-evidence-class.js.map +1 -0
- package/dist/extraction-faithfulness.d.ts +2 -2
- package/dist/extraction-judge-telemetry.d.ts +2 -2
- package/dist/extraction-judge-training.d.ts +2 -2
- package/dist/extraction-judge.d.ts +2 -2
- 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-source-grounding.js +2 -2
- package/dist/extraction-span-config.d.ts +1 -1
- package/dist/extraction-span-materialize.d.ts +1 -1
- package/dist/extraction-span-wire.d.ts +1 -1
- package/dist/extraction.d.ts +2 -2
- package/dist/extraction.js +21 -21
- package/dist/fallback-llm-codex-subscription.d.ts +1 -1
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{generic-recall-paths-BkVVEoeO.d.ts → generic-recall-paths-Bfc7zigS.d.ts} +1 -1
- package/dist/graph-append-error.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-jsonl.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/importers/test-utils.d.ts +1 -1
- package/dist/in-flight-reads.d.ts +1 -1
- package/dist/{index-TBPe6NvB.d.ts → index-C7IA1DxB.d.ts} +3 -3
- package/dist/index.d.ts +121 -30
- package/dist/index.js +170 -123
- 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 +4 -4
- 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-DdrhmecM.d.ts → local-llm-DH4tWtck.d.ts} +1 -1
- package/dist/local-llm-helpers.d.ts +2 -2
- package/dist/local-llm.d.ts +2 -2
- package/dist/local-llm.js +2 -2
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/location/index.d.ts +5 -5
- package/dist/location/index.js +15 -15
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +13 -13
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +13 -13
- package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-projection.js +14 -14
- package/dist/{maintenance-B813YFWJ.d.ts → maintenance-n6yeTQhR.d.ts} +4 -4
- package/dist/mcp-memory-inspector-app.d.ts +15 -14
- package/dist/mcp-read-only-tools.js +1 -1
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-age.d.ts +100 -0
- package/dist/memory-age.js +22 -0
- package/dist/memory-age.js.map +1 -0
- package/dist/memory-browse-http-glue.d.ts +170 -0
- package/dist/memory-browse-http-glue.js +9 -0
- package/dist/memory-browse-http-glue.js.map +1 -0
- package/dist/memory-browse-mcp-tools.d.ts +160 -0
- package/dist/memory-browse-mcp-tools.js +8 -0
- package/dist/memory-browse-mcp-tools.js.map +1 -0
- package/dist/memory-browse-operations.d.ts +2 -0
- package/dist/memory-browse-operations.js +246 -0
- package/dist/memory-browse-operations.js.map +1 -0
- package/dist/memory-browse.d.ts +113 -0
- package/dist/memory-browse.js +49 -0
- package/dist/memory-browse.js.map +1 -0
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-controller.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-subject-cli.d.ts +15 -14
- package/dist/memory-subject.d.ts +15 -14
- 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 +14 -14
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +6 -6
- package/dist/namespaces/storage.d.ts +3 -3
- package/dist/namespaces/storage.js +13 -13
- 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 +16 -16
- package/dist/operator-doctor-replica.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +3 -3
- package/dist/operator-toolkit.js +23 -22
- package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
- package/dist/orchestration/maintenance.d.ts +4 -4
- package/dist/orchestration/maintenance.js +23 -23
- package/dist/{orchestrator-BCvQavIS.d.ts → orchestrator-Cirjo3ZB.d.ts} +10 -10
- package/dist/orchestrator.d.ts +11 -11
- package/dist/orchestrator.js +112 -106
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/{pipeline-4lqKJZ_0.d.ts → pipeline-DK79FrgH.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/proactive-contention.d.ts +2 -2
- package/dist/provenance.d.ts +77 -2
- package/dist/provenance.js +7 -3
- package/dist/{public-http-NH72a6aa.d.ts → public-http-VB85y0ZH.d.ts} +1 -1
- package/dist/{qmd-DnoWaXKb.d.ts → qmd-BT0ErlRS.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/qmd.js +4 -4
- 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-navigate-access.d.ts +1 -1
- package/dist/recall-navigate-access.js +2 -2
- package/dist/recall-navigate-disclosure.d.ts +1 -1
- package/dist/recall-navigate-disclosure.js +2 -2
- package/dist/recall-navigate.d.ts +1 -1
- package/dist/recall-navigate.js +2 -2
- package/dist/recall-navigation-http-glue.d.ts +15 -14
- package/dist/recall-navigation-mcp-tools.d.ts +15 -14
- package/dist/recall-navigation-operations.js +41 -40
- package/dist/recall-navigation-renderer.d.ts +1 -1
- package/dist/recall-navigation.d.ts +1 -1
- package/dist/recall-navigation.js +40 -39
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state-view-admission.d.ts +2 -2
- package/dist/recall-state-view-admission.js +5 -5
- package/dist/recall-state-view-anchors.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 +4 -1
- package/dist/reconcile/manifest.js +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 +4 -3
- 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/{scope-profiles-crazXegZ.d.ts → scope-profiles-BgPQx9iE.d.ts} +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +5 -5
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +9 -9
- 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-B09JI9WM.d.ts → semantic-consolidation-BrAWKVcm.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +14 -14
- package/dist/semantic-rule-promotion.js +13 -13
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +13 -13
- package/dist/{service-Echc5Xso.d.ts → service-CqHOTg87.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 +1 -1
- package/dist/signal.d.ts +1 -1
- package/dist/source-agent-qualifier.d.ts +1 -1
- package/dist/source-agent-qualifier.js +17 -17
- package/dist/standing-memory-block.d.ts +77 -0
- package/dist/standing-memory-block.js +40 -0
- package/dist/standing-memory-block.js.map +1 -0
- package/dist/standup/build.js +13 -13
- package/dist/{storage-ClkHCQSE.d.ts → storage-mD2YQ8_2.d.ts} +17 -3
- package/dist/storage.d.ts +2 -2
- package/dist/storage.js +12 -12
- package/dist/subject-config.d.ts +1 -1
- package/dist/summarizer.d.ts +1 -1
- package/dist/summarizer.js +3 -3
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/support-passport/index.d.ts +18 -17
- package/dist/support-passport/index.js +43 -42
- package/dist/{tagging-ksktJ60G.d.ts → tagging-CiAVA-ID.d.ts} +2 -2
- 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/export-okf.d.ts +1 -1
- package/dist/transfer/export-okf.js +13 -13
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-nmhnJpxx.d.ts → types-DgDaoY61.d.ts} +6 -0
- 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 +13 -13
- package/dist/who-knows-cli.d.ts +15 -14
- package/dist/who-knows-cli.js +14 -14
- package/dist/who-knows.d.ts +15 -14
- package/dist/who-knows.js +13 -13
- package/dist/write-envelope.d.ts +1 -1
- package/package.json +2 -2
- package/src/access-boundary.ts +3 -0
- package/src/access-http.ts +2 -2
- package/src/access-mcp-output-schemas.ts +25 -0
- package/src/access-mcp.ts +3 -3
- package/src/access-operations.ts +2 -0
- package/src/access-service.ts +6 -1
- package/src/access-surface-catalog.test.ts +20 -0
- package/src/access-surface-catalog.ts +6 -0
- package/src/activity/timeline/index.ts +1 -0
- package/src/activity/timeline/journal-recap.test.ts +17 -0
- package/src/activity/timeline/journal-recap.ts +19 -6
- package/src/activity/timeline/recap-export-range.test.ts +289 -0
- package/src/activity/timeline/recap-export-range.ts +169 -0
- package/src/activity/timeline/recap-export.test.ts +73 -1
- package/src/activity/timeline/recap-export.ts +57 -4
- package/src/cli/memory-browse-commands.ts +77 -0
- package/src/cli.ts +2 -2
- package/src/config.ts +11 -0
- package/src/dedup/merge.test.ts +185 -8
- package/src/extraction-evidence-class.test.ts +355 -0
- package/src/extraction-evidence-class.ts +241 -0
- package/src/index.ts +1 -0
- package/src/mcp-read-only-tools.ts +3 -0
- package/src/memory-age.test.ts +106 -0
- package/src/memory-age.ts +196 -0
- package/src/memory-browse-http-glue.ts +93 -0
- package/src/memory-browse-mcp-tools.ts +57 -0
- package/src/memory-browse-operations.ts +88 -0
- package/src/memory-browse-surface.test.ts +500 -0
- package/src/memory-browse.ts +316 -0
- package/src/orchestration/recall-search-pipeline.test.ts +207 -1
- package/src/orchestration/recall-search-pipeline.ts +13 -9
- package/src/orchestration/semantic-merge-commit-effects.ts +31 -9
- package/src/orchestration/semantic-merge-persist.ts +16 -5
- package/src/provenance.ts +3 -3
- package/src/reconcile/manifest.test.ts +27 -0
- package/src/reconcile/manifest.ts +7 -0
- package/src/standing-memory-block.test.ts +262 -0
- package/src/standing-memory-block.ts +331 -0
- package/src/storage/cas-revision-host.ts +22 -0
- package/src/storage/cas-revision-store.test.ts +44 -1
- package/src/storage/cas-revision-store.ts +23 -0
- package/src/storage/tombstone-blocked-capture-sync.ts +26 -1
- package/src/storage-frontmatter-cas.test.ts +49 -1
- package/src/storage.ts +1 -1
- package/src/types.ts +6 -0
- package/dist/chunk-2AV75BJW.js.map +0 -1
- package/dist/chunk-5TKHJ5RW.js.map +0 -1
- package/dist/chunk-66YUOI53.js.map +0 -1
- package/dist/chunk-7LKXP4QG.js.map +0 -1
- package/dist/chunk-IAT5WFEU.js.map +0 -1
- package/dist/chunk-NKDQXDFL.js.map +0 -1
- package/dist/chunk-OREGA7GY.js.map +0 -1
- package/dist/chunk-SHEHQBCG.js.map +0 -1
- package/dist/chunk-TFS5FAAC.js.map +0 -1
- package/dist/chunk-TNQR7N52.js.map +0 -1
- package/dist/chunk-UJXNBZYJ.js.map +0 -1
- package/dist/chunk-W6YDAIVB.js.map +0 -1
- /package/dist/{chunk-C3MCMFNV.js.map → chunk-2QX65IEC.js.map} +0 -0
- /package/dist/{chunk-HBGSZ62Z.js.map → chunk-3RKVSECD.js.map} +0 -0
- /package/dist/{chunk-R4HFCATE.js.map → chunk-4LPIVP77.js.map} +0 -0
- /package/dist/{chunk-PW4HTWXL.js.map → chunk-55XWRL6F.js.map} +0 -0
- /package/dist/{chunk-E5VOKZQV.js.map → chunk-5KBI2MOC.js.map} +0 -0
- /package/dist/{chunk-ZQ533JDE.js.map → chunk-6A6HSXWV.js.map} +0 -0
- /package/dist/{chunk-4HTQ76O4.js.map → chunk-7SJCSDQ3.js.map} +0 -0
- /package/dist/{chunk-A2YN3FR4.js.map → chunk-7YUX52XZ.js.map} +0 -0
- /package/dist/{chunk-LQVXSRAJ.js.map → chunk-B4EGAHHS.js.map} +0 -0
- /package/dist/{chunk-EUP4AXIW.js.map → chunk-B6VDWDFN.js.map} +0 -0
- /package/dist/{chunk-Z5YNVWRD.js.map → chunk-BMENHAJL.js.map} +0 -0
- /package/dist/{chunk-5ZXGH2ZL.js.map → chunk-BXOPUJ3W.js.map} +0 -0
- /package/dist/{chunk-J6NEOCQC.js.map → chunk-CD7VTZIG.js.map} +0 -0
- /package/dist/{chunk-EC5SVM6H.js.map → chunk-DE4ZGH2N.js.map} +0 -0
- /package/dist/{chunk-2LRO73MN.js.map → chunk-DRLZB3CN.js.map} +0 -0
- /package/dist/{chunk-Q4MI2KCG.js.map → chunk-EGOWY342.js.map} +0 -0
- /package/dist/{chunk-W44T2KHG.js.map → chunk-EWBDJQ5Y.js.map} +0 -0
- /package/dist/{chunk-JMBTNVMW.js.map → chunk-FGRJJBFO.js.map} +0 -0
- /package/dist/{chunk-OOBUWMPY.js.map → chunk-G2SJRFGP.js.map} +0 -0
- /package/dist/{chunk-WGZHSQDV.js.map → chunk-G2XZFGYN.js.map} +0 -0
- /package/dist/{chunk-JPNUKKSL.js.map → chunk-GGROFIYX.js.map} +0 -0
- /package/dist/{chunk-VA5QRTZX.js.map → chunk-HG74GEVY.js.map} +0 -0
- /package/dist/{chunk-WUUGE7QC.js.map → chunk-HS75MCQF.js.map} +0 -0
- /package/dist/{chunk-DO6R3LIE.js.map → chunk-I6IJDTRP.js.map} +0 -0
- /package/dist/{chunk-T3F2HWIG.js.map → chunk-IO7K7GES.js.map} +0 -0
- /package/dist/{chunk-6II55MXS.js.map → chunk-JXLEQIJ7.js.map} +0 -0
- /package/dist/{chunk-4XTSKGLX.js.map → chunk-JYXLW22X.js.map} +0 -0
- /package/dist/{chunk-IFULZYYQ.js.map → chunk-LIAM7U2J.js.map} +0 -0
- /package/dist/{chunk-LZAJONO5.js.map → chunk-LIDJWAQV.js.map} +0 -0
- /package/dist/{chunk-B55FYF6F.js.map → chunk-LYMJ36UI.js.map} +0 -0
- /package/dist/{chunk-AOMUIF3R.js.map → chunk-MQT3WP3F.js.map} +0 -0
- /package/dist/{chunk-F6AYWLRN.js.map → chunk-NE45P3OF.js.map} +0 -0
- /package/dist/{chunk-5XTILJRI.js.map → chunk-NMA7RQPR.js.map} +0 -0
- /package/dist/{chunk-46A4RGBC.js.map → chunk-OS4JVTHR.js.map} +0 -0
- /package/dist/{chunk-C3YEB7CR.js.map → chunk-PA67GXYJ.js.map} +0 -0
- /package/dist/{chunk-NGAFYIHQ.js.map → chunk-PPJLJD2D.js.map} +0 -0
- /package/dist/{chunk-AG6PANBG.js.map → chunk-PXJ6HE2W.js.map} +0 -0
- /package/dist/{chunk-BOWAP64Q.js.map → chunk-QB4VXCFN.js.map} +0 -0
- /package/dist/{chunk-L63M2PE4.js.map → chunk-QGAKUOKU.js.map} +0 -0
- /package/dist/{chunk-T2ZNUWUY.js.map → chunk-QKFYJVDG.js.map} +0 -0
- /package/dist/{chunk-YHSEUZV5.js.map → chunk-QLCMOHRE.js.map} +0 -0
- /package/dist/{chunk-Q4AUIFUA.js.map → chunk-QMWKENNK.js.map} +0 -0
- /package/dist/{chunk-HVGCQMTQ.js.map → chunk-SJ7ZNUXB.js.map} +0 -0
- /package/dist/{chunk-WCQ7L54B.js.map → chunk-SK6HEQTW.js.map} +0 -0
- /package/dist/{chunk-PGAB2V53.js.map → chunk-SMBWBEE7.js.map} +0 -0
- /package/dist/{chunk-SPN5YFXN.js.map → chunk-SS33RR4F.js.map} +0 -0
- /package/dist/{chunk-NRFUYVW7.js.map → chunk-T3T5HTZZ.js.map} +0 -0
- /package/dist/{chunk-KG2OOAFF.js.map → chunk-TQSCR5DU.js.map} +0 -0
- /package/dist/{chunk-7K5Q6COX.js.map → chunk-TVVEYCNW.js.map} +0 -0
- /package/dist/{chunk-ZLVURGAJ.js.map → chunk-V2XNMIAM.js.map} +0 -0
- /package/dist/{chunk-AS2NOPDM.js.map → chunk-V7IONNTJ.js.map} +0 -0
- /package/dist/{chunk-OFBJD3V5.js.map → chunk-VV5GCNKG.js.map} +0 -0
- /package/dist/{chunk-MDYZCPNQ.js.map → chunk-WL3PGPGU.js.map} +0 -0
- /package/dist/{chunk-N6I257OM.js.map → chunk-WYU66IVZ.js.map} +0 -0
- /package/dist/{chunk-A7GQBJP5.js.map → chunk-X2NFFBTO.js.map} +0 -0
- /package/dist/{chunk-D3DJYGU2.js.map → chunk-XEHLYA5Z.js.map} +0 -0
- /package/dist/{chunk-JIWZVOQM.js.map → chunk-XKHRNAK7.js.map} +0 -0
- /package/dist/{chunk-VW5TGNJN.js.map → chunk-XXLH6KFO.js.map} +0 -0
- /package/dist/{chunk-WMTYEQO5.js.map → chunk-XZUHXKM3.js.map} +0 -0
- /package/dist/{chunk-K4KE5MYE.js.map → chunk-Y5VT7BIT.js.map} +0 -0
- /package/dist/{chunk-M7ECD3KN.js.map → chunk-YOSLKZLZ.js.map} +0 -0
- /package/dist/{chunk-K75H7FSO.js.map → chunk-YXWMZAAN.js.map} +0 -0
- /package/dist/{chunk-DSHSQGSN.js.map → chunk-ZEIJQ4I3.js.map} +0 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory-store browse verbs (issue #2978) — deterministic `ls` / `tree` /
|
|
3
|
+
* `find` over ONE resolved namespace's memory store, so an agent can see
|
|
4
|
+
* what exists instead of guessing ids at an opaque search box.
|
|
5
|
+
*
|
|
6
|
+
* Scope rules (all existing contracts, no new ones):
|
|
7
|
+
* - Every call resolves through the caller's `resolveReadableNamespace`
|
|
8
|
+
* (read/write path symmetry) and ONE storage instance — browse never
|
|
9
|
+
* enumerates across namespaces.
|
|
10
|
+
* - Only the recall corpus is browsable: a path's top segment must be a
|
|
11
|
+
* `RECALL_FALLBACK_DIRS` category AND survive
|
|
12
|
+
* `isGenericRecallExcludedPath`. Derived stores (`artifacts/`,
|
|
13
|
+
* `meetings/` records, wearables day stores, `state/`, ...) are
|
|
14
|
+
* indistinguishable from missing paths — `not_found`, never listed.
|
|
15
|
+
* - Support-passport private memories are filtered exactly like
|
|
16
|
+
* `memoryBrowse` (the paginated list): their names never appear.
|
|
17
|
+
* - Output is deterministically sorted (plain code-point order, no
|
|
18
|
+
* locale) and entry-capped so a huge store cannot blow the response.
|
|
19
|
+
*
|
|
20
|
+
* `ponytail:` the index is rebuilt from `readAllMemories()` per call —
|
|
21
|
+
* O(corpus) per browse. Fine at agent cadence; swap for a cached directory
|
|
22
|
+
* index if browsing ever sits on a hot path.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { toMemoryPathRel } from "./memory-lifecycle-ledger-utils.js";
|
|
26
|
+
import { normalizeProjectionPreview } from "./memory-projection-format.js";
|
|
27
|
+
import { isGenericRecallExcludedPath } from "./orchestration/generic-recall-paths.js";
|
|
28
|
+
import { isSupportPassportPrivateMemory } from "./support-passport/card-projection.js";
|
|
29
|
+
import { RECALL_FALLBACK_DIRS } from "./utils/category-dir.js";
|
|
30
|
+
import type { MemoryFile } from "./types.js";
|
|
31
|
+
|
|
32
|
+
/** Browse reads touch only these storage members. */
|
|
33
|
+
export interface MemoryBrowseStorage {
|
|
34
|
+
readonly dir: string;
|
|
35
|
+
readAllMemories(): Promise<MemoryFile[]>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Injected seams; the service supplies live wiring, tests supply doubles. */
|
|
39
|
+
export interface MemoryBrowseDeps {
|
|
40
|
+
readonly resolveReadableNamespace: (namespace: string | undefined, principal?: string) => string;
|
|
41
|
+
readonly getStorage: (namespace: string) => Promise<MemoryBrowseStorage>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type BrowseVerb = "ls" | "tree" | "find";
|
|
45
|
+
|
|
46
|
+
export interface MemoryStoreBrowseRequest {
|
|
47
|
+
readonly verb: BrowseVerb;
|
|
48
|
+
/** ls/tree: directory relative to the store root ("" or "/" = root). */
|
|
49
|
+
readonly path?: string;
|
|
50
|
+
/** tree: expansion depth, 1..MAX_TREE_DEPTH (default 1). */
|
|
51
|
+
readonly depth?: number;
|
|
52
|
+
/** find: glob (`*` wildcard) or substring matched against path and name. */
|
|
53
|
+
readonly pattern?: string;
|
|
54
|
+
readonly namespace?: string;
|
|
55
|
+
readonly authenticatedPrincipal?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface MemoryBrowseEntry {
|
|
59
|
+
/** Store-relative path with forward slashes; dirs carry no trailing slash. */
|
|
60
|
+
readonly path: string;
|
|
61
|
+
readonly kind: "dir" | "file";
|
|
62
|
+
readonly name: string;
|
|
63
|
+
/** Files: first content line (180-char preview). Dirs: empty. */
|
|
64
|
+
readonly description: string;
|
|
65
|
+
/** Dirs: recursive memory count beneath. Files: 1. */
|
|
66
|
+
readonly count: number;
|
|
67
|
+
/** 0-based depth relative to the browse root (tree nests; ls/find are 0). */
|
|
68
|
+
readonly depth: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type MemoryBrowseError = "invalid_path" | "not_found" | "invalid_depth" | "invalid_pattern";
|
|
72
|
+
|
|
73
|
+
export type MemoryStoreBrowseResult =
|
|
74
|
+
| {
|
|
75
|
+
ok: true;
|
|
76
|
+
verb: BrowseVerb;
|
|
77
|
+
namespace: string;
|
|
78
|
+
path: string;
|
|
79
|
+
total: number;
|
|
80
|
+
entries: MemoryBrowseEntry[];
|
|
81
|
+
truncated: boolean;
|
|
82
|
+
rendered: string;
|
|
83
|
+
}
|
|
84
|
+
| {
|
|
85
|
+
ok: false;
|
|
86
|
+
verb: BrowseVerb;
|
|
87
|
+
path: string;
|
|
88
|
+
error: MemoryBrowseError;
|
|
89
|
+
message: string;
|
|
90
|
+
rendered: string;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const MAX_LS_ENTRIES = 200;
|
|
94
|
+
export const MAX_TREE_ENTRIES = 400;
|
|
95
|
+
export const MAX_TREE_DEPTH = 4;
|
|
96
|
+
export const MAX_FIND_ENTRIES = 200;
|
|
97
|
+
const MAX_PATH_CHARS = 512;
|
|
98
|
+
const MAX_PATTERN_CHARS = 256;
|
|
99
|
+
|
|
100
|
+
const BROWSABLE_ROOTS: Record<string, true> = Object.fromEntries(RECALL_FALLBACK_DIRS.map((dir) => [dir, true]));
|
|
101
|
+
|
|
102
|
+
/** The recall-corpus visibility rule shared by every browse code path. */
|
|
103
|
+
function isBrowsableMemoryPath(relPath: string): boolean {
|
|
104
|
+
if (!relPath.endsWith(".md")) return false;
|
|
105
|
+
const top = relPath.split("/")[0] ?? "";
|
|
106
|
+
return BROWSABLE_ROOTS[top] === true && !isGenericRecallExcludedPath(relPath);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Normalize a caller-supplied store path. Returns "" for the root. Throws
|
|
111
|
+
* `Error` on absolute paths, `..`/`.` segments, backslashes, control
|
|
112
|
+
* characters, or oversize input — never reinterpreted (pattern 39).
|
|
113
|
+
*/
|
|
114
|
+
export function sanitizeBrowsePath(raw: string | undefined): string {
|
|
115
|
+
const trimmed = (raw ?? "").trim();
|
|
116
|
+
if (trimmed === "" || trimmed === "/") return "";
|
|
117
|
+
if (trimmed.length > MAX_PATH_CHARS) throw new Error("path is too long");
|
|
118
|
+
if (trimmed.includes("\\")) throw new Error("path must use '/' separators");
|
|
119
|
+
const segments = trimmed.split("/");
|
|
120
|
+
for (const segment of segments) {
|
|
121
|
+
if (segment === "") throw new Error("path has an empty segment");
|
|
122
|
+
if (segment === "." || segment === "..") throw new Error("path must not contain '.' or '..' segments");
|
|
123
|
+
for (const ch of segment) {
|
|
124
|
+
const code = ch.charCodeAt(0);
|
|
125
|
+
if (code < 0x20 || code === 0x7f) throw new Error("path has control characters");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return segments.join("/");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Escape every regex metacharacter in a glob segment. */
|
|
132
|
+
function segmentRegex(segment: string): RegExp {
|
|
133
|
+
const source = `^${segment.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*")}$`;
|
|
134
|
+
return new RegExp(source, "i");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* `*` glob (one segment per pattern segment, `find -name` semantics — `*`
|
|
139
|
+
* never crosses `/`) or case-insensitive substring over path and name.
|
|
140
|
+
*/
|
|
141
|
+
export function browsePatternMatches(pattern: string, relPath: string): boolean {
|
|
142
|
+
const segments = relPath.split("/");
|
|
143
|
+
const name = segments[segments.length - 1] ?? "";
|
|
144
|
+
const patternSegments = pattern.split("/");
|
|
145
|
+
if (patternSegments.length === 1) {
|
|
146
|
+
const single = patternSegments[0]!;
|
|
147
|
+
if (single.includes("*")) return segmentRegex(single).test(name);
|
|
148
|
+
const needle = single.toLowerCase();
|
|
149
|
+
return relPath.toLowerCase().includes(needle) || name.toLowerCase().includes(needle);
|
|
150
|
+
}
|
|
151
|
+
if (patternSegments.length !== segments.length) return false;
|
|
152
|
+
return patternSegments.every((segment, index) => segmentRegex(segment).test(segments[index]!));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
interface DirNode {
|
|
156
|
+
readonly subdirs: Map<string, DirNode>;
|
|
157
|
+
readonly files: Map<string, MemoryFile>;
|
|
158
|
+
count: number;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function emptyNode(): DirNode {
|
|
162
|
+
return { subdirs: new Map(), files: new Map(), count: 0 };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Directory index over the browsable corpus: root "" holds category dirs. */
|
|
166
|
+
function buildIndex(memories: readonly MemoryFile[], dir: string): DirNode {
|
|
167
|
+
const root = emptyNode();
|
|
168
|
+
for (const memory of memories) {
|
|
169
|
+
const rel = toMemoryPathRel(dir, memory.path);
|
|
170
|
+
if (!isBrowsableMemoryPath(rel)) continue;
|
|
171
|
+
const segments = rel.split("/");
|
|
172
|
+
let node = root;
|
|
173
|
+
node.count += 1;
|
|
174
|
+
for (let index = 0; index < segments.length - 1; index += 1) {
|
|
175
|
+
const name = segments[index]!;
|
|
176
|
+
let child = node.subdirs.get(name);
|
|
177
|
+
if (child === undefined) {
|
|
178
|
+
child = emptyNode();
|
|
179
|
+
node.subdirs.set(name, child);
|
|
180
|
+
}
|
|
181
|
+
node = child;
|
|
182
|
+
node.count += 1;
|
|
183
|
+
}
|
|
184
|
+
node.files.set(segments[segments.length - 1]!, memory);
|
|
185
|
+
}
|
|
186
|
+
return root;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function byPath(left: { path: string }, right: { path: string }): number {
|
|
190
|
+
return left.path < right.path ? -1 : left.path > right.path ? 1 : 0;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function dirEntries(node: DirNode, dirPath: string): MemoryBrowseEntry[] {
|
|
194
|
+
const prefix = dirPath === "" ? "" : `${dirPath}/`;
|
|
195
|
+
const entries: MemoryBrowseEntry[] = [];
|
|
196
|
+
for (const [name, child] of node.subdirs) {
|
|
197
|
+
entries.push({ path: `${prefix}${name}`, kind: "dir", name, description: "", count: child.count, depth: 0 });
|
|
198
|
+
}
|
|
199
|
+
for (const [name, memory] of node.files) {
|
|
200
|
+
entries.push({
|
|
201
|
+
path: `${prefix}${name}`,
|
|
202
|
+
kind: "file",
|
|
203
|
+
name,
|
|
204
|
+
description: normalizeProjectionPreview(memory.content),
|
|
205
|
+
count: 1,
|
|
206
|
+
depth: 0,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
return entries.sort(byPath);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function renderEntries(verb: BrowseVerb, namespace: string, browsePath: string, entries: readonly MemoryBrowseEntry[]): string {
|
|
213
|
+
const lines = [`${verb} ${namespace}:${browsePath === "" ? "/" : browsePath}`];
|
|
214
|
+
for (const entry of entries) {
|
|
215
|
+
const indent = " ".repeat(entry.depth);
|
|
216
|
+
if (entry.kind === "dir") {
|
|
217
|
+
lines.push(`${indent}${entry.path}/ (${entry.count} memories)`);
|
|
218
|
+
} else {
|
|
219
|
+
const description = entry.description.length > 0 ? ` — ${entry.description}` : "";
|
|
220
|
+
lines.push(`${indent}${entry.path}${description}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return lines.join("\n");
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function refusal(verb: BrowseVerb, browsePath: string, error: MemoryBrowseError, message: string): MemoryStoreBrowseResult {
|
|
227
|
+
return { ok: false, verb, path: browsePath, error, message, rendered: `browse ${verb} failed: ${error} — ${message}` };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Run one browse verb. Malformed input throws `Error`; typed refusals return in-band. */
|
|
231
|
+
export async function runMemoryBrowse(
|
|
232
|
+
deps: MemoryBrowseDeps,
|
|
233
|
+
request: MemoryStoreBrowseRequest,
|
|
234
|
+
): Promise<MemoryStoreBrowseResult> {
|
|
235
|
+
const namespace = deps.resolveReadableNamespace(request.namespace, request.authenticatedPrincipal);
|
|
236
|
+
const storage = await deps.getStorage(namespace);
|
|
237
|
+
const memories = (await storage.readAllMemories()).filter((memory) => !isSupportPassportPrivateMemory(memory));
|
|
238
|
+
|
|
239
|
+
if (request.verb === "find") {
|
|
240
|
+
const pattern = (request.pattern ?? "").trim();
|
|
241
|
+
if (pattern.length === 0) return refusal(request.verb, "", "invalid_pattern", "pattern is required");
|
|
242
|
+
if (pattern.length > MAX_PATTERN_CHARS) {
|
|
243
|
+
return refusal(request.verb, "", "invalid_pattern", "pattern is too long");
|
|
244
|
+
}
|
|
245
|
+
const hits: MemoryBrowseEntry[] = [];
|
|
246
|
+
let truncated = false;
|
|
247
|
+
let total = 0;
|
|
248
|
+
for (const memory of memories) {
|
|
249
|
+
const rel = toMemoryPathRel(storage.dir, memory.path);
|
|
250
|
+
if (!isBrowsableMemoryPath(rel)) continue;
|
|
251
|
+
if (!browsePatternMatches(pattern, rel)) continue;
|
|
252
|
+
total += 1;
|
|
253
|
+
if (hits.length >= MAX_FIND_ENTRIES) {
|
|
254
|
+
truncated = true;
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
hits.push({
|
|
258
|
+
path: rel,
|
|
259
|
+
kind: "file",
|
|
260
|
+
name: rel.split("/").pop() ?? rel,
|
|
261
|
+
description: normalizeProjectionPreview(memory.content),
|
|
262
|
+
count: 1,
|
|
263
|
+
depth: 0,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
hits.sort(byPath);
|
|
267
|
+
return { ok: true, verb: "find", namespace, path: "", total, entries: hits, truncated, rendered: renderEntries("find", namespace, "", hits) };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let browsePath: string;
|
|
271
|
+
try {
|
|
272
|
+
browsePath = sanitizeBrowsePath(request.path);
|
|
273
|
+
} catch (err) {
|
|
274
|
+
return refusal(request.verb, request.path ?? "", "invalid_path", err instanceof Error ? err.message : String(err));
|
|
275
|
+
}
|
|
276
|
+
const root = buildIndex(memories, storage.dir);
|
|
277
|
+
let node: DirNode | undefined = root;
|
|
278
|
+
for (const segment of browsePath === "" ? [] : browsePath.split("/")) {
|
|
279
|
+
node = node?.subdirs.get(segment);
|
|
280
|
+
if (node === undefined) break;
|
|
281
|
+
}
|
|
282
|
+
// Invisible-by-design stores resolve as not_found, indistinguishable from
|
|
283
|
+
// a missing path: the index only ever contains browsable corpus paths.
|
|
284
|
+
if (node === undefined) {
|
|
285
|
+
return refusal(request.verb, browsePath, "not_found", `no browsable store path: ${browsePath}`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (request.verb === "ls") {
|
|
289
|
+
const all = dirEntries(node, browsePath);
|
|
290
|
+
const truncated = all.length > MAX_LS_ENTRIES;
|
|
291
|
+
const entries = all.slice(0, MAX_LS_ENTRIES);
|
|
292
|
+
return { ok: true, verb: "ls", namespace, path: browsePath, total: all.length, entries, truncated, rendered: renderEntries("ls", namespace, browsePath, entries) };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (request.depth !== undefined && (!Number.isInteger(request.depth) || request.depth < 1 || request.depth > MAX_TREE_DEPTH)) {
|
|
296
|
+
return refusal(request.verb, browsePath, "invalid_depth", `depth must be an integer between 1 and ${MAX_TREE_DEPTH}`);
|
|
297
|
+
}
|
|
298
|
+
const depth = request.depth ?? 1;
|
|
299
|
+
const entries: MemoryBrowseEntry[] = [];
|
|
300
|
+
let truncated = false;
|
|
301
|
+
const walk = (current: DirNode, currentPath: string, currentDepth: number): void => {
|
|
302
|
+
for (const entry of dirEntries(current, currentPath)) {
|
|
303
|
+
if (entries.length >= MAX_TREE_ENTRIES) {
|
|
304
|
+
truncated = true;
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
entries.push({ ...entry, depth: currentDepth });
|
|
308
|
+
if (entry.kind === "dir" && currentDepth < depth - 1) {
|
|
309
|
+
walk(current.subdirs.get(entry.name)!, entry.path, currentDepth + 1);
|
|
310
|
+
if (truncated) return;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
walk(node, browsePath, 0);
|
|
315
|
+
return { ok: true, verb: "tree", namespace, path: browsePath, total: entries.length, entries, truncated, rendered: renderEntries("tree", namespace, browsePath, entries) };
|
|
316
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { mkdtemp, rm } from "node:fs/promises";
|
|
4
|
+
import { mkdir, mkdtemp, rm, utimes, writeFile } from "node:fs/promises";
|
|
5
5
|
import test from "node:test";
|
|
6
6
|
|
|
7
7
|
import { parseConfig } from "../config.js";
|
|
@@ -565,3 +565,209 @@ test("QMD stops after an underfilled archive-only page", async () => {
|
|
|
565
565
|
assert.deepEqual(out, []);
|
|
566
566
|
assert.equal(searchCalls, 1, "an underfilled raw page must not trigger widening");
|
|
567
567
|
});
|
|
568
|
+
|
|
569
|
+
// ─── #2976: memory age from content dates; bulk-touch mtime distrust ─────────
|
|
570
|
+
|
|
571
|
+
const AGE_DAY_MS = 86_400_000;
|
|
572
|
+
|
|
573
|
+
function scoredResult(namespace: string, p: string, score: number): QmdSearchResult {
|
|
574
|
+
return { docid: `${namespace}:${p}`, namespace, path: p, score, snippet: p };
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function ageMemory(absPath: string, frontmatter: Record<string, unknown>): MemoryFile {
|
|
578
|
+
return {
|
|
579
|
+
path: absPath,
|
|
580
|
+
content: absPath,
|
|
581
|
+
frontmatter: { status: "active", memoryKind: "fact", ...frontmatter } as unknown as MemoryFile["frontmatter"],
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function boostCoordinator(
|
|
586
|
+
memoryDir: string,
|
|
587
|
+
recencyWeight: number,
|
|
588
|
+
): RecallSearchPipelineCoordinator {
|
|
589
|
+
const config = parseConfig({
|
|
590
|
+
openaiApiKey: "sk-test",
|
|
591
|
+
memoryDir,
|
|
592
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
593
|
+
});
|
|
594
|
+
const deps = {
|
|
595
|
+
config,
|
|
596
|
+
effectiveRecencyWeight: () => recencyWeight,
|
|
597
|
+
filterSearchResultsForRecall: async (
|
|
598
|
+
results: QmdSearchResult[],
|
|
599
|
+
preloaded?: Map<string, MemoryFile>,
|
|
600
|
+
) => ({ results, memoryByPath: preloaded ?? new Map() }),
|
|
601
|
+
} as unknown as RecallSearchPipelineDeps;
|
|
602
|
+
return new RecallSearchPipelineCoordinator(deps);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
async function writeDatedFile(filePath: string, mtimeMs: number): Promise<void> {
|
|
606
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
607
|
+
await writeFile(filePath, "fixture\n");
|
|
608
|
+
await utimes(filePath, mtimeMs / 1000, mtimeMs / 1000);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function isoAgo(now: number, days: number): string {
|
|
612
|
+
return new Date(now - days * AGE_DAY_MS).toISOString();
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
test("#2976 content dates age a memory even when its mtime is fresh (updated backs a missing created)", async () => {
|
|
616
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-age-content-"));
|
|
617
|
+
try {
|
|
618
|
+
const now = Date.now();
|
|
619
|
+
const fileA = path.join(memoryDir, "facts", "a.md");
|
|
620
|
+
const fileB = path.join(memoryDir, "facts", "b.md");
|
|
621
|
+
// Both files carry today's mtime — the bulk import / cp -r shape. A has
|
|
622
|
+
// no created, only a 40-day-old updated; B carries a 40-day-old created.
|
|
623
|
+
await writeDatedFile(fileA, now);
|
|
624
|
+
await writeDatedFile(fileB, now);
|
|
625
|
+
const memoryByPath = new Map<string, MemoryFile>([
|
|
626
|
+
["default\0facts/a.md", ageMemory(fileA, { updated: isoAgo(now, 40) })],
|
|
627
|
+
["default\0facts/b.md", ageMemory(fileB, { created: isoAgo(now, 40), updated: isoAgo(now, 40) })],
|
|
628
|
+
]);
|
|
629
|
+
|
|
630
|
+
const boosted = await boostCoordinator(memoryDir, 0.5).boostSearchResults(
|
|
631
|
+
[scoredResult("default", "facts/a.md", 1), scoredResult("default", "facts/b.md", 1)],
|
|
632
|
+
["default"],
|
|
633
|
+
undefined,
|
|
634
|
+
memoryByPath,
|
|
635
|
+
);
|
|
636
|
+
|
|
637
|
+
const [a, b] = boosted;
|
|
638
|
+
const expected = 1 * 0.5 + Math.pow(0.5, 40 / 7) * 0.5;
|
|
639
|
+
assert.ok(a && Number.isFinite(a.score), "a memory without created must not score NaN");
|
|
640
|
+
assert.ok(b && Number.isFinite(b.score));
|
|
641
|
+
assert.ok(Math.abs(a.score - expected) < 1e-9, `ages by the 40-day-old content date (${a?.score}), not the fresh mtime (1.0)`);
|
|
642
|
+
assert.equal(a.score, b.score, "updated and created of the same age produce the same score");
|
|
643
|
+
} finally {
|
|
644
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
test("#2976 a bulk-touch mtime cluster never inflates freshness (ranking unchanged vs pre-touch base order)", async () => {
|
|
649
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-age-bulk-"));
|
|
650
|
+
try {
|
|
651
|
+
const now = Date.now();
|
|
652
|
+
const files = ["facts/x.md", "facts/y.md", "facts/z.md"];
|
|
653
|
+
const baseScores = [0.9, 0.8, 0.7];
|
|
654
|
+
const mtimes = [now - 10 * AGE_DAY_MS, now - 30 * AGE_DAY_MS, now - 90 * AGE_DAY_MS];
|
|
655
|
+
// Undated memories: only mtime can say how old they are.
|
|
656
|
+
const buildMap = () =>
|
|
657
|
+
new Map<string, MemoryFile>(
|
|
658
|
+
files.map((rel) => ["default\0" + rel, ageMemory(path.join(memoryDir, rel), {})]),
|
|
659
|
+
);
|
|
660
|
+
for (let i = 0; i < files.length; i += 1) {
|
|
661
|
+
await writeDatedFile(path.join(memoryDir, files[i]!), mtimes[i]!);
|
|
662
|
+
}
|
|
663
|
+
const results = () => files.map((rel, i) => scoredResult("default", rel, baseScores[i]!));
|
|
664
|
+
|
|
665
|
+
// Pre-touch: mtimes are scattered, so each trusted mtime ages its memory.
|
|
666
|
+
const preTouch = await boostCoordinator(memoryDir, 0.5).boostSearchResults(
|
|
667
|
+
results(),
|
|
668
|
+
["default"],
|
|
669
|
+
undefined,
|
|
670
|
+
buildMap(),
|
|
671
|
+
);
|
|
672
|
+
for (const r of preTouch) {
|
|
673
|
+
assert.ok(Number.isFinite(r.score), "an undated memory with a trusted mtime must not score NaN");
|
|
674
|
+
}
|
|
675
|
+
assert.deepEqual(
|
|
676
|
+
preTouch.map((r) => r.path),
|
|
677
|
+
["facts/x.md", "facts/y.md", "facts/z.md"],
|
|
678
|
+
"pre-touch, the scattered trusted mtimes preserve the base ranking",
|
|
679
|
+
);
|
|
680
|
+
|
|
681
|
+
// Bulk touch: every mtime reset to one calendar day (cp -r / restore).
|
|
682
|
+
const bulkDay = now - 1 * AGE_DAY_MS;
|
|
683
|
+
for (const rel of files) {
|
|
684
|
+
await writeDatedFile(path.join(memoryDir, rel), bulkDay);
|
|
685
|
+
}
|
|
686
|
+
const postTouch = await boostCoordinator(memoryDir, 0.5).boostSearchResults(
|
|
687
|
+
results(),
|
|
688
|
+
["default"],
|
|
689
|
+
undefined,
|
|
690
|
+
buildMap(),
|
|
691
|
+
);
|
|
692
|
+
assert.deepEqual(
|
|
693
|
+
postTouch.map((r) => r.score),
|
|
694
|
+
baseScores,
|
|
695
|
+
"a same-day mtime cluster is distrusted: no recency boost, scores stay at base",
|
|
696
|
+
);
|
|
697
|
+
assert.deepEqual(
|
|
698
|
+
postTouch.map((r) => r.path),
|
|
699
|
+
["facts/x.md", "facts/y.md", "facts/z.md"],
|
|
700
|
+
"ranking is unchanged versus the pre-touch base order",
|
|
701
|
+
);
|
|
702
|
+
} finally {
|
|
703
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
test("#2976 an undated memory falls back to a trusted scattered mtime; a dated sibling keeps its content age", async () => {
|
|
708
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-age-fallback-"));
|
|
709
|
+
try {
|
|
710
|
+
const now = Date.now();
|
|
711
|
+
const fileA = path.join(memoryDir, "facts", "undated.md");
|
|
712
|
+
const fileB = path.join(memoryDir, "facts", "dated.md");
|
|
713
|
+
// A: no dates, mtime 2 days ago. B: created 2 days ago, mtime 5 days ago
|
|
714
|
+
// (different days, one file each — no cluster, mtime stays trusted).
|
|
715
|
+
await writeDatedFile(fileA, now - 2 * AGE_DAY_MS);
|
|
716
|
+
await writeDatedFile(fileB, now - 5 * AGE_DAY_MS);
|
|
717
|
+
const memoryByPath = new Map<string, MemoryFile>([
|
|
718
|
+
["default\0facts/undated.md", ageMemory(fileA, {})],
|
|
719
|
+
["default\0facts/dated.md", ageMemory(fileB, { created: isoAgo(now, 2), updated: isoAgo(now, 2) })],
|
|
720
|
+
]);
|
|
721
|
+
|
|
722
|
+
const boosted = await boostCoordinator(memoryDir, 0.5).boostSearchResults(
|
|
723
|
+
[scoredResult("default", "facts/undated.md", 1), scoredResult("default", "facts/dated.md", 1)],
|
|
724
|
+
["default"],
|
|
725
|
+
undefined,
|
|
726
|
+
memoryByPath,
|
|
727
|
+
);
|
|
728
|
+
|
|
729
|
+
const [undated, dated] = boosted;
|
|
730
|
+
// 1e-6 (not 1e-9): the pipeline stamps its own Date.now() after this
|
|
731
|
+
// test's fixture writes, so a few ms of clock drift shifts the 2-day
|
|
732
|
+
// blend in the 8th decimal.
|
|
733
|
+
const expected = 1 * 0.5 + Math.pow(0.5, 2 / 7) * 0.5;
|
|
734
|
+
assert.ok(undated && Number.isFinite(undated.score), "the undated memory must not score NaN");
|
|
735
|
+
assert.ok(
|
|
736
|
+
undated && Math.abs(undated.score - expected) < 1e-6,
|
|
737
|
+
`an undated memory ages by its trusted mtime (2 days): got ${undated?.score}, want ~${expected}`,
|
|
738
|
+
);
|
|
739
|
+
assert.ok(
|
|
740
|
+
undated && dated && Math.abs(undated.score - dated.score) < 1e-6,
|
|
741
|
+
"mtime fallback and a same-aged content date score identically",
|
|
742
|
+
);
|
|
743
|
+
} finally {
|
|
744
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
test("#2976 guard: a memory whose content date is older than its fresh mtime ages by the content date", async () => {
|
|
749
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-age-guard-"));
|
|
750
|
+
try {
|
|
751
|
+
const now = Date.now();
|
|
752
|
+
const filePath = path.join(memoryDir, "facts", "old.md");
|
|
753
|
+
await writeDatedFile(filePath, now); // fresh mtime, 90-day-old content
|
|
754
|
+
const memoryByPath = new Map<string, MemoryFile>([
|
|
755
|
+
["default\0facts/old.md", ageMemory(filePath, { created: isoAgo(now, 90), updated: isoAgo(now, 90) })],
|
|
756
|
+
]);
|
|
757
|
+
|
|
758
|
+
const boosted = await boostCoordinator(memoryDir, 0.5).boostSearchResults(
|
|
759
|
+
[scoredResult("default", "facts/old.md", 1)],
|
|
760
|
+
["default"],
|
|
761
|
+
undefined,
|
|
762
|
+
memoryByPath,
|
|
763
|
+
);
|
|
764
|
+
|
|
765
|
+
const expected = 1 * 0.5 + Math.pow(0.5, 90 / 7) * 0.5;
|
|
766
|
+
assert.ok(
|
|
767
|
+
boosted[0] && Math.abs(boosted[0].score - expected) < 1e-9,
|
|
768
|
+
"ages by the 90-day-old created date, not the fresh mtime",
|
|
769
|
+
);
|
|
770
|
+
} finally {
|
|
771
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
772
|
+
}
|
|
773
|
+
});
|
|
@@ -34,6 +34,7 @@ import { RerankCache, rerankLocalOrNoop, reorderByRankedKeys } from "../rerank.j
|
|
|
34
34
|
import type { SearchBackend, SearchDegradation, SearchExecutionOptions, SearchQueryOptions } from "../search/port.js";
|
|
35
35
|
import { SecureStoreLockedError } from "../secure-store/index.js";
|
|
36
36
|
import { isPathInsideStorageRoot } from "../storage-paths.js";
|
|
37
|
+
import { applyRecencyBoost, collectMtimeFallbackContext } from "../memory-age.js";
|
|
37
38
|
import type { CorpusReadOptions } from "../corpus-read-cancellation.js";
|
|
38
39
|
import { extractTagsFromPrompt, isTemporalQuery, queryByDateRangeAsync, queryByTagsAsync, readIndexSnapshotAsync, recencyWindowFromPrompt } from "../temporal-index.js";
|
|
39
40
|
import {
|
|
@@ -1558,20 +1559,23 @@ export class RecallSearchPipelineCoordinator {
|
|
|
1558
1559
|
|
|
1559
1560
|
const boosted: QmdSearchResult[] = [];
|
|
1560
1561
|
const recencyWeight = this.deps.effectiveRecencyWeight();
|
|
1562
|
+
// #2976: mtime backs age only for memories without content dates, and a
|
|
1563
|
+
// same-day mtime cluster (bulk import / restore / cp -r) is distrusted.
|
|
1564
|
+
const mtimeFallback =
|
|
1565
|
+
recencyWeight > 0
|
|
1566
|
+
? await collectMtimeFallbackContext(
|
|
1567
|
+
safeResults.flatMap((r) => { const m = memoryForResult(memoryByPath, r); return m ? [m] : []; }),
|
|
1568
|
+
)
|
|
1569
|
+
: null;
|
|
1561
1570
|
for (const r of safeResults) {
|
|
1562
1571
|
const memory = memoryForResult(memoryByPath, r);
|
|
1563
1572
|
let score = r.score;
|
|
1564
1573
|
|
|
1565
1574
|
if (memory) {
|
|
1566
|
-
// Recency boost: exponential decay over
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
const ageDays = ageMs / (1000 * 60 * 60 * 24);
|
|
1571
|
-
const halfLifeDays = 7;
|
|
1572
|
-
const recencyScore = Math.pow(0.5, ageDays / halfLifeDays);
|
|
1573
|
-
score = score * (1 - recencyWeight) + recencyScore * recencyWeight;
|
|
1574
|
-
}
|
|
1575
|
+
// Recency boost: 7-day exponential decay over the shared age resolver
|
|
1576
|
+
// (#2976): content dates first, trusted mtime as fallback, unknown
|
|
1577
|
+
// age never fresh. See memory-age.ts.
|
|
1578
|
+
score = applyRecencyBoost(score, memory, recencyWeight, now, mtimeFallback);
|
|
1575
1579
|
|
|
1576
1580
|
// Access count boost: log scale, capped
|
|
1577
1581
|
if (this.deps.config.boostAccessCount && memory.frontmatter.accessCount) {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { GraphEdgeAppendError } from "../graph-append-error.js";
|
|
16
|
+
import type { CasRevisionReadStatus } from "../storage/cas-revision-store.js";
|
|
16
17
|
import type { GraphEdge, GraphType } from "../graph.js";
|
|
17
18
|
import { readFile } from "node:fs/promises";
|
|
18
19
|
import { removeNodeEdgesForRewrite, rollbackNodeEdgeRewrite } from "../graph-jsonl.js";
|
|
@@ -259,8 +260,8 @@ export async function persistMergedTargetThreadEpisode(
|
|
|
259
260
|
}
|
|
260
261
|
|
|
261
262
|
/**
|
|
262
|
-
* Round N+12 (C) + N+14: replace a merged target's generated graph
|
|
263
|
-
* with the rebuild's rows, revision-guarded end to end. The caller's
|
|
263
|
+
* Round N+12 (C) + N+14 + #2837: replace a merged target's generated graph
|
|
264
|
+
* edges with the rebuild's rows, revision-guarded end to end. The caller's
|
|
264
265
|
* committed-body check can pass, writer B can then commit a NEWER merge and
|
|
265
266
|
* finish rebuilding edges for it, and writer A would resume to remove B's
|
|
266
267
|
* edges and install edges derived from A's older body. The revision observed
|
|
@@ -271,6 +272,15 @@ export async function persistMergedTargetThreadEpisode(
|
|
|
271
272
|
* chain only then). A build failure rolls back the same way (round N+10 C)
|
|
272
273
|
* and rethrows — fail-open stays the caller's decision.
|
|
273
274
|
*
|
|
275
|
+
* #2837: the post-build guard reads the truthful receipt STATUS, never the
|
|
276
|
+
* fail-open token. The old guard collapsed an unreadable sidecar into
|
|
277
|
+
* undefined on both reads, so an unavailable-vs-unavailable rewrite compared
|
|
278
|
+
* undefined against undefined, passed, and installed replacement edges no
|
|
279
|
+
* receipt ever verified. An `unavailable` post-build status now aborts the
|
|
280
|
+
* install (rollback, false) so the next merge retries; only a PROVEN match —
|
|
281
|
+
* present-and-equal, or absent-against-absent for a pre-sidecar target —
|
|
282
|
+
* may install.
|
|
283
|
+
*
|
|
274
284
|
* Round N+14 makes the ROLLBACK surgical: `build` returns the exact rows it
|
|
275
285
|
* appended (by identity), and the rollback removes only those — never a
|
|
276
286
|
* node-wide sweep — so rows a newer writer appended after this writer's
|
|
@@ -283,13 +293,14 @@ export async function rewriteMergedTargetGraphEdges(
|
|
|
283
293
|
storage: {
|
|
284
294
|
dir: string;
|
|
285
295
|
getMemoryByIdIncludingArchived: (id: string) => Promise<MemoryFile | null>;
|
|
286
|
-
|
|
296
|
+
readCasRevisionStatus: (filePath: string) => Promise<CasRevisionReadStatus>;
|
|
287
297
|
},
|
|
288
298
|
input: {
|
|
289
299
|
targetId: string;
|
|
290
300
|
memoryRelPath: string;
|
|
291
301
|
mergedContent: string;
|
|
292
|
-
/** The CAS revision token the committed-body check observed
|
|
302
|
+
/** The CAS revision token the committed-body check observed (undefined
|
|
303
|
+
* only when the check PROVED the receipt absent — #2837). */
|
|
293
304
|
revisionChecked: string | undefined;
|
|
294
305
|
rewriteTypes: readonly GraphType[];
|
|
295
306
|
build: () => Promise<GraphEdge[] | void>;
|
|
@@ -306,15 +317,26 @@ export async function rewriteMergedTargetGraphEdges(
|
|
|
306
317
|
try {
|
|
307
318
|
appended = (await input.build()) ?? undefined;
|
|
308
319
|
const committedNow = await storage.getMemoryByIdIncludingArchived(input.targetId);
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
}
|
|
320
|
+
let standing: CasRevisionReadStatus;
|
|
321
|
+
if (committedNow === null) {
|
|
322
|
+
standing = { status: "absent" };
|
|
323
|
+
} else {
|
|
324
|
+
standing = await storage.readCasRevisionStatus(committedNow.path).catch((err: unknown) => ({
|
|
325
|
+
status: "unavailable" as const,
|
|
326
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
327
|
+
}));
|
|
328
|
+
}
|
|
313
329
|
if (
|
|
314
330
|
!committedNow ||
|
|
315
331
|
committedNow.content !== input.mergedContent ||
|
|
316
|
-
|
|
332
|
+
standing.status === "unavailable" ||
|
|
333
|
+
(standing.status === "present" ? standing.revision : undefined) !== input.revisionChecked
|
|
317
334
|
) {
|
|
335
|
+
if (standing.status === "unavailable") {
|
|
336
|
+
log.warn(
|
|
337
|
+
`rewriteMergedTargetGraphEdges: CAS revision status for ${input.memoryRelPath} is unavailable (${standing.reason}); aborting the install — an unverified replacement must not install (#2837)`,
|
|
338
|
+
);
|
|
339
|
+
}
|
|
318
340
|
await rollbackNodeEdgeRewrite(storage.dir, input.memoryRelPath, input.rewriteTypes, removedEdges, input.targetId, appended);
|
|
319
341
|
return false;
|
|
320
342
|
}
|