@remnic/core 9.3.751 → 9.3.752
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-boundary.d.ts +5 -5
- package/dist/access-boundary.js +29 -29
- package/dist/access-cli.js +88 -88
- package/dist/access-http.d.ts +5 -5
- package/dist/access-http.js +35 -35
- package/dist/access-mcp.d.ts +5 -5
- package/dist/access-mcp.js +34 -34
- package/dist/access-operations-batch.js +31 -31
- package/dist/access-operations.d.ts +5 -5
- package/dist/access-operations.js +33 -33
- package/dist/access-recall-surface.d.ts +363 -0
- package/dist/access-recall-surface.js +128 -0
- package/dist/access-schema.js +7 -7
- package/dist/{access-service-DmAEOJdJ.d.ts → access-service-C2cOHQvg.d.ts} +7 -26
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +28 -28
- package/dist/access-surface-catalog.d.ts +5 -5
- 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/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +4 -4
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +4 -4
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +1 -1
- package/dist/buffer.js +2 -2
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-CZJSLEFG.js} +3 -3
- package/dist/{catalog-BpOcwHdE.d.ts → catalog-D_FvzTM0.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +9 -9
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-2NLLXCJG.js → chunk-2BFLDH5F.js} +2 -2
- package/dist/{chunk-UU32EAIY.js → chunk-2DPCAW2T.js} +5 -5
- package/dist/{chunk-5PZCDUJ6.js → chunk-2U4E34Y3.js} +4 -4
- package/dist/{chunk-JRZV3767.js → chunk-33NX5BLR.js} +4 -4
- package/dist/{chunk-X7Y7WX73.js → chunk-437S3G37.js} +2 -2
- package/dist/{chunk-HAZVVOYT.js → chunk-5DXUE7GB.js} +2 -2
- package/dist/{chunk-4DLFJJOQ.js → chunk-5JAT4ZJD.js} +4 -4
- package/dist/{chunk-RS25QOKZ.js → chunk-64SE4X5G.js} +2 -2
- package/dist/{chunk-QRRBXD24.js → chunk-67JRXV6Z.js} +7 -7
- package/dist/{chunk-WA7FECSG.js → chunk-6ROCH5JV.js} +7 -7
- package/dist/{chunk-5QANTTY2.js → chunk-BIAMJUUJ.js} +4 -4
- package/dist/{chunk-IKHKHADN.js → chunk-C4LNKQ3M.js} +5 -5
- package/dist/{chunk-B35L45HZ.js → chunk-DC5OBZS3.js} +2 -2
- package/dist/{chunk-ORAK3LNZ.js → chunk-ER3CCWK2.js} +2 -2
- package/dist/{chunk-SD34EK4Z.js → chunk-FEELYTRT.js} +5 -5
- package/dist/{chunk-KQAFEZQX.js → chunk-FLVL4L3L.js} +2 -2
- package/dist/{chunk-WIWPSQYU.js → chunk-FXRFFYWD.js} +4 -4
- package/dist/{chunk-W3M34XT4.js → chunk-GECEETQ3.js} +4 -4
- package/dist/{chunk-WWSXVOGY.js → chunk-GFGYHLON.js} +2 -2
- package/dist/{chunk-BJMBJZ2Y.js → chunk-GWTFLF2P.js} +59 -59
- package/dist/chunk-GWTFLF2P.js.map +1 -0
- package/dist/{chunk-WOX2UW4K.js → chunk-IBPEVGP5.js} +2 -2
- package/dist/{chunk-JZ7P26GF.js → chunk-IQWFWSCP.js} +4 -4
- package/dist/{chunk-6SXNQFHB.js → chunk-J2JB6BHT.js} +8 -8
- package/dist/{chunk-K2JM4DZZ.js → chunk-JHZ3LEM7.js} +2 -2
- package/dist/{chunk-YGGXUNS4.js → chunk-JNKSFIAY.js} +45 -45
- package/dist/{chunk-CGZWGVQF.js → chunk-K5CWCJKJ.js} +6 -6
- package/dist/{chunk-TPLE6SNY.js → chunk-KFNG72W7.js} +7 -7
- package/dist/{chunk-4NWIGAIC.js → chunk-KT2VQLP7.js} +8 -8
- package/dist/{chunk-6FNZ3NMS.js → chunk-L3VSTJ2V.js} +2 -2
- package/dist/{chunk-ARV3AUOM.js → chunk-M5QKGHCR.js} +2 -2
- package/dist/{chunk-E4PFDD3N.js → chunk-MEPWWSTD.js} +4 -4
- package/dist/{chunk-V5RVMULT.js → chunk-MLDAD27I.js} +7 -7
- package/dist/{chunk-K2R3DEEH.js → chunk-MTN2G4DT.js} +2 -2
- package/dist/{chunk-DBSMLRTO.js → chunk-MW7UUXTA.js} +5 -5
- package/dist/{chunk-OSMBC3JU.js → chunk-OKOF7ITI.js} +6 -6
- package/dist/{chunk-DBRVU5PR.js → chunk-OVMLS7TN.js} +3 -3
- package/dist/{chunk-NNL4LJAD.js → chunk-QMMCMV2L.js} +5 -5
- package/dist/{chunk-RNWWAS5V.js → chunk-QRM3K3XT.js} +5 -5
- package/dist/{chunk-HM6UJPPO.js → chunk-R7TGDLV3.js} +4 -4
- package/dist/{chunk-EEKDRAWX.js → chunk-RBYV3WSB.js} +4 -4
- package/dist/{chunk-LOPZ2DWR.js → chunk-RRRZ3CLN.js} +4 -4
- package/dist/{chunk-7TWA7DKP.js → chunk-S5UYWK3O.js} +6 -6
- package/dist/{chunk-QOGXUNWL.js → chunk-SC5AEZLU.js} +9 -9
- package/dist/{chunk-SNEXVX3U.js → chunk-UFNQKZV7.js} +15 -15
- package/dist/{chunk-W3BFT5OJ.js → chunk-V4ZPLQLC.js} +2 -2
- package/dist/{chunk-SQNED75R.js → chunk-V5UV7BOA.js} +1 -1
- package/dist/{chunk-RWZB6MAY.js → chunk-VQL22SHO.js} +891 -1002
- package/dist/chunk-VQL22SHO.js.map +1 -0
- package/dist/{chunk-UETBEHBA.js → chunk-VX2RYM2K.js} +2232 -1942
- package/dist/chunk-VX2RYM2K.js.map +1 -0
- package/dist/{chunk-K5LLP4CY.js → chunk-XKAOT4ZB.js} +4 -4
- package/dist/{chunk-ZBVQ4ZM4.js → chunk-YGNQG4Q6.js} +8 -8
- package/dist/{chunk-EZ25VE3G.js → chunk-YNDLCWXS.js} +4 -4
- package/dist/{chunk-HYI2GP3F.js → chunk-ZGGI3COG.js} +9 -9
- package/dist/{cli-lmf5RGyQ.d.ts → cli-DFUU9WYu.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +60 -60
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +6 -6
- 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 +5 -5
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +7 -7
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/contradiction/index.js +2 -2
- package/dist/{contradiction-scan-3FJYWS3G.js → contradiction-scan-KO4WWCS4.js} +3 -3
- package/dist/contradiction-scan-KO4WWCS4.js.map +1 -0
- 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/dashboard-runtime.js +2 -2
- package/dist/day-summary.d.ts +1 -1
- package/dist/day-summary.js +2 -2
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/embedding-fallback.js +2 -2
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +5 -5
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +4 -4
- package/dist/explicit-capture.js +2 -2
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction.d.ts +1 -1
- package/dist/extraction.js +4 -4
- 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-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.js +142 -142
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/engine.js +3 -3
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +8 -8
- 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-llm.js +2 -2
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +4 -4
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +4 -4
- package/dist/maintenance/rebuild-memory-projection.js +5 -5
- package/dist/mcp-memory-inspector-app.d.ts +5 -5
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +2 -2
- package/dist/namespaces/migrate.js +10 -10
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +4 -4
- package/dist/namespaces/storage.d.ts +2 -2
- package/dist/namespaces/storage.js +6 -6
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +17 -17
- package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
- package/dist/orchestration/compression-guideline-coordinator.js +2 -2
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +10 -10
- package/dist/{orchestrator-D7bb8ZGp.d.ts → orchestrator-BhxMxtQJ.d.ts} +27 -106
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +86 -86
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.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-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-xray-cli.d.ts +1 -1
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray.d.ts +1 -1
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +1 -1
- package/dist/retrieval-agents.d.ts +1 -1
- 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/routing/store.js +2 -2
- package/dist/schemas.d.ts +22 -22
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/embed-helper.js +2 -2
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +3 -3
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +3 -3
- 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/secure-store/index.js +2 -2
- package/dist/{semantic-consolidation-CTfbVwCA.d.ts → semantic-consolidation-BrsoFeMP.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +7 -7
- package/dist/semantic-rule-promotion.js +4 -4
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +4 -4
- 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/shared-context/manager.js +2 -2
- package/dist/signal.d.ts +1 -1
- package/dist/storage.d.ts +1 -1
- package/dist/storage.js +3 -3
- package/dist/summarizer.d.ts +1 -1
- package/dist/summarizer.js +3 -3
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.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 +1 -1
- 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/backup.js +3 -3
- package/dist/transfer/capsule-export.js +3 -3
- package/dist/transfer/capsule-import.js +3 -3
- package/dist/transfer/types.d.ts +12 -12
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-D4NYDtXI.d.ts → types-BfI4uJeb.d.ts} +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +4 -4
- package/package.json +2 -2
- package/src/access-recall-surface.ts +1552 -0
- package/src/access-service.ts +29 -1219
- package/src/orchestration/recall-entry.ts +290 -0
- package/src/orchestration/session-context.ts +407 -0
- package/src/orchestrator.ts +125 -462
- package/dist/chunk-BJMBJZ2Y.js.map +0 -1
- package/dist/chunk-RWZB6MAY.js.map +0 -1
- package/dist/chunk-UETBEHBA.js.map +0 -1
- /package/dist/{capsule-crypto-7FJQINUR.js.map → access-recall-surface.js.map} +0 -0
- /package/dist/{contradiction-scan-3FJYWS3G.js.map → capsule-crypto-CZJSLEFG.js.map} +0 -0
- /package/dist/{chunk-2NLLXCJG.js.map → chunk-2BFLDH5F.js.map} +0 -0
- /package/dist/{chunk-UU32EAIY.js.map → chunk-2DPCAW2T.js.map} +0 -0
- /package/dist/{chunk-5PZCDUJ6.js.map → chunk-2U4E34Y3.js.map} +0 -0
- /package/dist/{chunk-JRZV3767.js.map → chunk-33NX5BLR.js.map} +0 -0
- /package/dist/{chunk-X7Y7WX73.js.map → chunk-437S3G37.js.map} +0 -0
- /package/dist/{chunk-HAZVVOYT.js.map → chunk-5DXUE7GB.js.map} +0 -0
- /package/dist/{chunk-4DLFJJOQ.js.map → chunk-5JAT4ZJD.js.map} +0 -0
- /package/dist/{chunk-RS25QOKZ.js.map → chunk-64SE4X5G.js.map} +0 -0
- /package/dist/{chunk-QRRBXD24.js.map → chunk-67JRXV6Z.js.map} +0 -0
- /package/dist/{chunk-WA7FECSG.js.map → chunk-6ROCH5JV.js.map} +0 -0
- /package/dist/{chunk-5QANTTY2.js.map → chunk-BIAMJUUJ.js.map} +0 -0
- /package/dist/{chunk-IKHKHADN.js.map → chunk-C4LNKQ3M.js.map} +0 -0
- /package/dist/{chunk-B35L45HZ.js.map → chunk-DC5OBZS3.js.map} +0 -0
- /package/dist/{chunk-ORAK3LNZ.js.map → chunk-ER3CCWK2.js.map} +0 -0
- /package/dist/{chunk-SD34EK4Z.js.map → chunk-FEELYTRT.js.map} +0 -0
- /package/dist/{chunk-KQAFEZQX.js.map → chunk-FLVL4L3L.js.map} +0 -0
- /package/dist/{chunk-WIWPSQYU.js.map → chunk-FXRFFYWD.js.map} +0 -0
- /package/dist/{chunk-W3M34XT4.js.map → chunk-GECEETQ3.js.map} +0 -0
- /package/dist/{chunk-WWSXVOGY.js.map → chunk-GFGYHLON.js.map} +0 -0
- /package/dist/{chunk-WOX2UW4K.js.map → chunk-IBPEVGP5.js.map} +0 -0
- /package/dist/{chunk-JZ7P26GF.js.map → chunk-IQWFWSCP.js.map} +0 -0
- /package/dist/{chunk-6SXNQFHB.js.map → chunk-J2JB6BHT.js.map} +0 -0
- /package/dist/{chunk-K2JM4DZZ.js.map → chunk-JHZ3LEM7.js.map} +0 -0
- /package/dist/{chunk-YGGXUNS4.js.map → chunk-JNKSFIAY.js.map} +0 -0
- /package/dist/{chunk-CGZWGVQF.js.map → chunk-K5CWCJKJ.js.map} +0 -0
- /package/dist/{chunk-TPLE6SNY.js.map → chunk-KFNG72W7.js.map} +0 -0
- /package/dist/{chunk-4NWIGAIC.js.map → chunk-KT2VQLP7.js.map} +0 -0
- /package/dist/{chunk-6FNZ3NMS.js.map → chunk-L3VSTJ2V.js.map} +0 -0
- /package/dist/{chunk-ARV3AUOM.js.map → chunk-M5QKGHCR.js.map} +0 -0
- /package/dist/{chunk-E4PFDD3N.js.map → chunk-MEPWWSTD.js.map} +0 -0
- /package/dist/{chunk-V5RVMULT.js.map → chunk-MLDAD27I.js.map} +0 -0
- /package/dist/{chunk-K2R3DEEH.js.map → chunk-MTN2G4DT.js.map} +0 -0
- /package/dist/{chunk-DBSMLRTO.js.map → chunk-MW7UUXTA.js.map} +0 -0
- /package/dist/{chunk-OSMBC3JU.js.map → chunk-OKOF7ITI.js.map} +0 -0
- /package/dist/{chunk-DBRVU5PR.js.map → chunk-OVMLS7TN.js.map} +0 -0
- /package/dist/{chunk-NNL4LJAD.js.map → chunk-QMMCMV2L.js.map} +0 -0
- /package/dist/{chunk-RNWWAS5V.js.map → chunk-QRM3K3XT.js.map} +0 -0
- /package/dist/{chunk-HM6UJPPO.js.map → chunk-R7TGDLV3.js.map} +0 -0
- /package/dist/{chunk-EEKDRAWX.js.map → chunk-RBYV3WSB.js.map} +0 -0
- /package/dist/{chunk-LOPZ2DWR.js.map → chunk-RRRZ3CLN.js.map} +0 -0
- /package/dist/{chunk-7TWA7DKP.js.map → chunk-S5UYWK3O.js.map} +0 -0
- /package/dist/{chunk-QOGXUNWL.js.map → chunk-SC5AEZLU.js.map} +0 -0
- /package/dist/{chunk-SNEXVX3U.js.map → chunk-UFNQKZV7.js.map} +0 -0
- /package/dist/{chunk-W3BFT5OJ.js.map → chunk-V4ZPLQLC.js.map} +0 -0
- /package/dist/{chunk-SQNED75R.js.map → chunk-V5UV7BOA.js.map} +0 -0
- /package/dist/{chunk-K5LLP4CY.js.map → chunk-XKAOT4ZB.js.map} +0 -0
- /package/dist/{chunk-ZBVQ4ZM4.js.map → chunk-YGNQG4Q6.js.map} +0 -0
- /package/dist/{chunk-EZ25VE3G.js.map → chunk-YNDLCWXS.js.map} +0 -0
- /package/dist/{chunk-HYI2GP3F.js.map → chunk-ZGGI3COG.js.map} +0 -0
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "./chunk-GDB4J2H3.js";
|
|
9
9
|
import {
|
|
10
10
|
importCapsule
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-M5QKGHCR.js";
|
|
12
12
|
import {
|
|
13
13
|
getTrustZoneStoreStatus,
|
|
14
14
|
isTrustZoneName,
|
|
@@ -56,10 +56,10 @@ import {
|
|
|
56
56
|
resolveScopeProfilePlan,
|
|
57
57
|
resolveWritableNamespaceValue,
|
|
58
58
|
sanitizeFragment
|
|
59
|
-
} from "./chunk-
|
|
59
|
+
} from "./chunk-GECEETQ3.js";
|
|
60
60
|
import {
|
|
61
61
|
namespaceCollectionName
|
|
62
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-5DXUE7GB.js";
|
|
63
63
|
import {
|
|
64
64
|
namespaceIdentityFromToken
|
|
65
65
|
} from "./chunk-ZFXCQPNO.js";
|
|
@@ -70,7 +70,7 @@ import {
|
|
|
70
70
|
listMemoryGovernanceRuns,
|
|
71
71
|
readMemoryGovernanceRunArtifact,
|
|
72
72
|
runMemoryGovernance
|
|
73
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-JHZ3LEM7.js";
|
|
74
74
|
import {
|
|
75
75
|
recordMemoryOutcome
|
|
76
76
|
} from "./chunk-EIR5VLIH.js";
|
|
@@ -91,7 +91,7 @@ import {
|
|
|
91
91
|
persistExplicitCapture,
|
|
92
92
|
queueExplicitCaptureForReview,
|
|
93
93
|
validateExplicitCaptureInput
|
|
94
|
-
} from "./chunk-
|
|
94
|
+
} from "./chunk-XKAOT4ZB.js";
|
|
95
95
|
import {
|
|
96
96
|
CrossNamespaceBudget
|
|
97
97
|
} from "./chunk-ODPLEWB6.js";
|
|
@@ -101,17 +101,17 @@ import {
|
|
|
101
101
|
import {
|
|
102
102
|
filterTrajectoriesByLookbackDays,
|
|
103
103
|
readCausalTrajectoryRecords
|
|
104
|
-
} from "./chunk-
|
|
104
|
+
} from "./chunk-V5UV7BOA.js";
|
|
105
105
|
import {
|
|
106
106
|
FileCalendarSource,
|
|
107
107
|
buildBriefing,
|
|
108
108
|
enqueuePassiveCorrectionNotification,
|
|
109
109
|
parseBriefingFocus,
|
|
110
110
|
parseBriefingWindow
|
|
111
|
-
} from "./chunk-
|
|
111
|
+
} from "./chunk-MTN2G4DT.js";
|
|
112
112
|
import {
|
|
113
113
|
parseEntityFile
|
|
114
|
-
} from "./chunk-
|
|
114
|
+
} from "./chunk-YGNQG4Q6.js";
|
|
115
115
|
import {
|
|
116
116
|
stripAttributesSuffix
|
|
117
117
|
} from "./chunk-SVOZFLIQ.js";
|
|
@@ -145,9 +145,32 @@ import {
|
|
|
145
145
|
import {
|
|
146
146
|
computeContentHash
|
|
147
147
|
} from "./chunk-W5H6ZRPT.js";
|
|
148
|
+
import {
|
|
149
|
+
sanitizeMemoryContent
|
|
150
|
+
} from "./chunk-5GKPFL3S.js";
|
|
151
|
+
import {
|
|
152
|
+
expandTildePath
|
|
153
|
+
} from "./chunk-EYIEWJNI.js";
|
|
154
|
+
import {
|
|
155
|
+
log
|
|
156
|
+
} from "./chunk-NU3CSQ4H.js";
|
|
148
157
|
import {
|
|
149
158
|
exportCapsule
|
|
150
|
-
} from "./chunk-
|
|
159
|
+
} from "./chunk-FLVL4L3L.js";
|
|
160
|
+
import {
|
|
161
|
+
OFFLINE_SYNC_SNAPSHOT_FORMAT,
|
|
162
|
+
applyOfflineSyncChangeset,
|
|
163
|
+
applyOfflineSyncFileContentChunk,
|
|
164
|
+
buildOfflineSyncSnapshot,
|
|
165
|
+
buildOfflineSyncSnapshotForPaths,
|
|
166
|
+
buildOfflineSyncSnapshotFromBase,
|
|
167
|
+
compileOfflineSyncExcludeGlobs,
|
|
168
|
+
iterateOfflineSyncSnapshotFileRecords,
|
|
169
|
+
readOfflineSyncFileContentChunk
|
|
170
|
+
} from "./chunk-X64YLC24.js";
|
|
171
|
+
import {
|
|
172
|
+
SecureStoreLockedError
|
|
173
|
+
} from "./chunk-J2HSAU72.js";
|
|
151
174
|
import {
|
|
152
175
|
evaluateActionConfidence
|
|
153
176
|
} from "./chunk-Z2YFLXHU.js";
|
|
@@ -173,29 +196,6 @@ import {
|
|
|
173
196
|
resolveRecallEnhancementCapabilities,
|
|
174
197
|
resolveSecurityCapabilities
|
|
175
198
|
} from "./chunk-VSF7AAGE.js";
|
|
176
|
-
import {
|
|
177
|
-
sanitizeMemoryContent
|
|
178
|
-
} from "./chunk-5GKPFL3S.js";
|
|
179
|
-
import {
|
|
180
|
-
expandTildePath
|
|
181
|
-
} from "./chunk-EYIEWJNI.js";
|
|
182
|
-
import {
|
|
183
|
-
log
|
|
184
|
-
} from "./chunk-NU3CSQ4H.js";
|
|
185
|
-
import {
|
|
186
|
-
OFFLINE_SYNC_SNAPSHOT_FORMAT,
|
|
187
|
-
applyOfflineSyncChangeset,
|
|
188
|
-
applyOfflineSyncFileContentChunk,
|
|
189
|
-
buildOfflineSyncSnapshot,
|
|
190
|
-
buildOfflineSyncSnapshotForPaths,
|
|
191
|
-
buildOfflineSyncSnapshotFromBase,
|
|
192
|
-
compileOfflineSyncExcludeGlobs,
|
|
193
|
-
iterateOfflineSyncSnapshotFileRecords,
|
|
194
|
-
readOfflineSyncFileContentChunk
|
|
195
|
-
} from "./chunk-X64YLC24.js";
|
|
196
|
-
import {
|
|
197
|
-
SecureStoreLockedError
|
|
198
|
-
} from "./chunk-J2HSAU72.js";
|
|
199
199
|
import {
|
|
200
200
|
isHandleToken
|
|
201
201
|
} from "./chunk-JAKIDQB2.js";
|
|
@@ -207,6 +207,10 @@ import {
|
|
|
207
207
|
hashAccessIdempotencyPayload
|
|
208
208
|
} from "./chunk-VFUEZZBS.js";
|
|
209
209
|
|
|
210
|
+
// src/access-recall-surface.ts
|
|
211
|
+
import { createHash as createHash5 } from "crypto";
|
|
212
|
+
import * as nodePath2 from "path";
|
|
213
|
+
|
|
210
214
|
// src/access-service.ts
|
|
211
215
|
import { stat as stat2 } from "fs/promises";
|
|
212
216
|
import * as nodeFs from "fs/promises";
|
|
@@ -4051,7 +4055,7 @@ function isErrnoCode(error, code) {
|
|
|
4051
4055
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
4052
4056
|
}
|
|
4053
4057
|
function sleep(ms) {
|
|
4054
|
-
return new Promise((
|
|
4058
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
4055
4059
|
}
|
|
4056
4060
|
async function acquireClusterWriteLock(memoryDir, clusterHash) {
|
|
4057
4061
|
const lockRoot = path6.join(path6.resolve(memoryDir), "state", "procedure-miner-locks");
|
|
@@ -4090,8 +4094,8 @@ async function withClusterWriteLock(memoryDir, clusterHash, fn) {
|
|
|
4090
4094
|
const previous = inProcessClusterWriteLocks.get(inProcessKey) ?? Promise.resolve();
|
|
4091
4095
|
let releaseInProcess;
|
|
4092
4096
|
const current = previous.catch(() => void 0).then(
|
|
4093
|
-
() => new Promise((
|
|
4094
|
-
releaseInProcess =
|
|
4097
|
+
() => new Promise((resolve3) => {
|
|
4098
|
+
releaseInProcess = resolve3;
|
|
4095
4099
|
})
|
|
4096
4100
|
);
|
|
4097
4101
|
inProcessClusterWriteLocks.set(inProcessKey, current);
|
|
@@ -4747,44 +4751,29 @@ async function promoteMemory(options) {
|
|
|
4747
4751
|
|
|
4748
4752
|
// src/access-service.ts
|
|
4749
4753
|
import * as nodePath from "path";
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
function
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
}
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
const storageRoot = nodePath.resolve(storageDir);
|
|
4767
|
-
const addCandidate = (candidate) => {
|
|
4768
|
-
const resolved = nodePath.resolve(candidate);
|
|
4769
|
-
if (isPathInsideStorageRoot(storageRoot, resolved)) {
|
|
4770
|
-
candidates.add(resolved);
|
|
4771
|
-
}
|
|
4772
|
-
};
|
|
4773
|
-
const addRelativeCandidates = (relativePath) => {
|
|
4774
|
-
const normalized = relativePath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
4775
|
-
if (!normalized) return;
|
|
4776
|
-
addCandidate(nodePath.join(storageRoot, normalized));
|
|
4777
|
-
if (/^\d{4}-\d{2}-\d{2}\//.test(normalized)) {
|
|
4778
|
-
addCandidate(nodePath.join(storageRoot, "facts", normalized));
|
|
4754
|
+
|
|
4755
|
+
// src/orchestration/self-deps.ts
|
|
4756
|
+
function selfDeps(self) {
|
|
4757
|
+
const source = self;
|
|
4758
|
+
return new Proxy(/* @__PURE__ */ Object.create(null), {
|
|
4759
|
+
get(_target, prop) {
|
|
4760
|
+
const value = source[prop];
|
|
4761
|
+
return typeof value === "function" ? value.bind(self) : value;
|
|
4762
|
+
},
|
|
4763
|
+
set(_target, prop, value) {
|
|
4764
|
+
if (!(prop in source)) return false;
|
|
4765
|
+
source[prop] = value;
|
|
4766
|
+
return true;
|
|
4767
|
+
},
|
|
4768
|
+
has(_target, prop) {
|
|
4769
|
+
return prop in source;
|
|
4779
4770
|
}
|
|
4780
|
-
};
|
|
4781
|
-
if (nodePath.isAbsolute(resultPath)) {
|
|
4782
|
-
addCandidate(resultPath);
|
|
4783
|
-
} else {
|
|
4784
|
-
addRelativeCandidates(resultPath);
|
|
4785
|
-
}
|
|
4786
|
-
return [...candidates];
|
|
4771
|
+
});
|
|
4787
4772
|
}
|
|
4773
|
+
|
|
4774
|
+
// src/access-service.ts
|
|
4775
|
+
var EngramAccessInputError = class extends Error {
|
|
4776
|
+
};
|
|
4788
4777
|
var cachedPackageVersion = null;
|
|
4789
4778
|
async function getPackageVersion() {
|
|
4790
4779
|
if (cachedPackageVersion !== null) return cachedPackageVersion;
|
|
@@ -4965,6 +4954,16 @@ var EngramAccessService = class {
|
|
|
4965
4954
|
budgetLocks = /* @__PURE__ */ new Map();
|
|
4966
4955
|
budget;
|
|
4967
4956
|
auditAdapter;
|
|
4957
|
+
/** AccessRecallSurface (access-service decomposition). Lazy; selfDeps live wiring. */
|
|
4958
|
+
_accessRecallSurface;
|
|
4959
|
+
get accessRecallSurface() {
|
|
4960
|
+
if (!this._accessRecallSurface) {
|
|
4961
|
+
this._accessRecallSurface = new AccessRecallSurface(
|
|
4962
|
+
selfDeps(this)
|
|
4963
|
+
);
|
|
4964
|
+
}
|
|
4965
|
+
return this._accessRecallSurface;
|
|
4966
|
+
}
|
|
4968
4967
|
get briefingEnabled() {
|
|
4969
4968
|
return this.orchestrator.config.briefing?.enabled === true;
|
|
4970
4969
|
}
|
|
@@ -5453,313 +5452,64 @@ var EngramAccessService = class {
|
|
|
5453
5452
|
} : void 0;
|
|
5454
5453
|
}
|
|
5455
5454
|
async buildRecallResponseFromXraySnapshot(options) {
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
const namespace = options.snapshot.namespace ? this.resolveNamespace(options.snapshot.namespace) : this.orchestrator.config.defaultNamespace;
|
|
5459
|
-
const sourcesUsed = Array.from(
|
|
5460
|
-
new Set(options.snapshot.results.map((result) => result.servedBy))
|
|
5461
|
-
);
|
|
5462
|
-
const snapshotForSerialization = {
|
|
5463
|
-
sessionKey: options.sessionKey ?? "",
|
|
5464
|
-
recordedAt: new Date(options.snapshot.capturedAt).toISOString(),
|
|
5465
|
-
queryHash: createHash4("sha256").update(options.query).digest("hex"),
|
|
5466
|
-
queryLen: options.query.length,
|
|
5467
|
-
memoryIds,
|
|
5468
|
-
namespace,
|
|
5469
|
-
recallNamespaces: [namespace],
|
|
5470
|
-
traceId: options.snapshot.traceId,
|
|
5471
|
-
plannerMode: options.normalizedMode,
|
|
5472
|
-
requestedMode: options.requestedMode && options.requestedMode !== "auto" ? options.requestedMode : void 0,
|
|
5473
|
-
sourcesUsed,
|
|
5474
|
-
budgetsApplied: {
|
|
5475
|
-
appliedTopK: memoryIds.length,
|
|
5476
|
-
recallBudgetChars: options.snapshot.budget.chars,
|
|
5477
|
-
maxMemoryTokens: this.orchestrator.config.maxMemoryTokens,
|
|
5478
|
-
finalContextChars: options.snapshot.budget.used
|
|
5479
|
-
},
|
|
5480
|
-
latencyMs: Date.now() - options.startedAt,
|
|
5481
|
-
resultPaths
|
|
5482
|
-
};
|
|
5483
|
-
const results = await this.serializeRecallResults(
|
|
5484
|
-
snapshotForSerialization,
|
|
5485
|
-
options.disclosure,
|
|
5486
|
-
{
|
|
5487
|
-
query: options.query,
|
|
5488
|
-
...options.sessionKey ? { sessionKey: options.sessionKey } : {},
|
|
5489
|
-
...options.rawExcerptNamespace ? { rawExcerptNamespace: options.rawExcerptNamespace } : {},
|
|
5490
|
-
...options.rawExcerptSessionIds !== void 0 ? { rawExcerptSessionIds: options.rawExcerptSessionIds } : {},
|
|
5491
|
-
...options.rawExcerptsSuppressed ? { rawExcerptsSuppressed: options.rawExcerptsSuppressed } : {}
|
|
5492
|
-
}
|
|
5455
|
+
return this.accessRecallSurface.buildRecallResponseFromXraySnapshot(
|
|
5456
|
+
options
|
|
5493
5457
|
);
|
|
5494
|
-
const context = results.map((result) => {
|
|
5495
|
-
const content = typeof result.content === "string" && result.content.length > 0 ? result.content : "";
|
|
5496
|
-
return content || result.preview;
|
|
5497
|
-
}).filter((text) => text.length > 0).join("\n\n");
|
|
5498
|
-
return {
|
|
5499
|
-
query: options.query,
|
|
5500
|
-
...options.sessionKey ? { sessionKey: options.sessionKey } : {},
|
|
5501
|
-
namespace,
|
|
5502
|
-
context,
|
|
5503
|
-
count: memoryIds.length,
|
|
5504
|
-
memoryIds,
|
|
5505
|
-
results,
|
|
5506
|
-
recordedAt: snapshotForSerialization.recordedAt,
|
|
5507
|
-
traceId: options.snapshot.traceId,
|
|
5508
|
-
plannerMode: options.normalizedMode,
|
|
5509
|
-
fallbackUsed: sourcesUsed.some((source) => source !== "hybrid"),
|
|
5510
|
-
sourcesUsed,
|
|
5511
|
-
disclosure: options.disclosure,
|
|
5512
|
-
budgetsApplied: snapshotForSerialization.budgetsApplied,
|
|
5513
|
-
latencyMs: snapshotForSerialization.latencyMs
|
|
5514
|
-
};
|
|
5515
5458
|
}
|
|
5516
5459
|
async serializeRecallResults(snapshot, disclosure, rawContext = null) {
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
const recallNamespaces = Array.from(
|
|
5522
|
-
new Set(
|
|
5523
|
-
[
|
|
5524
|
-
namespace,
|
|
5525
|
-
...Array.isArray(snapshot.recallNamespaces) ? snapshot.recallNamespaces.map((ns) => this.resolveNamespace(ns)) : [],
|
|
5526
|
-
this.orchestrator.config.defaultNamespace,
|
|
5527
|
-
this.orchestrator.config.sharedNamespace,
|
|
5528
|
-
...(this.orchestrator.config.namespacePolicies ?? []).map((p) => p.name)
|
|
5529
|
-
].filter((ns) => typeof ns === "string" && ns.length > 0)
|
|
5530
|
-
)
|
|
5460
|
+
return this.accessRecallSurface.serializeRecallResults(
|
|
5461
|
+
snapshot,
|
|
5462
|
+
disclosure,
|
|
5463
|
+
rawContext
|
|
5531
5464
|
);
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
const
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5465
|
+
}
|
|
5466
|
+
async storageForAbsoluteRecallPath(memoryPath, primaryNamespace, recallNamespaces = []) {
|
|
5467
|
+
const resolvedPath = nodePath.resolve(memoryPath);
|
|
5468
|
+
const memoryRoot = nodePath.resolve(this.orchestrator.config.memoryDir);
|
|
5469
|
+
const namespacesRoot = nodePath.join(memoryRoot, "namespaces");
|
|
5470
|
+
const configuredNamespaces = /* @__PURE__ */ new Set();
|
|
5471
|
+
configuredNamespaces.add(primaryNamespace);
|
|
5472
|
+
for (const namespace of recallNamespaces) {
|
|
5473
|
+
configuredNamespaces.add(namespace);
|
|
5474
|
+
}
|
|
5475
|
+
configuredNamespaces.add(this.orchestrator.config.defaultNamespace);
|
|
5476
|
+
configuredNamespaces.add(this.orchestrator.config.sharedNamespace);
|
|
5477
|
+
if (isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
|
|
5478
|
+
const relativeToNamespaces = nodePath.relative(namespacesRoot, resolvedPath);
|
|
5479
|
+
const [namespaceSegment] = relativeToNamespaces.split(/[\\/]/);
|
|
5480
|
+
if (namespaceSegment) {
|
|
5481
|
+
configuredNamespaces.add(
|
|
5482
|
+
namespaceIdentityFromToken(namespaceSegment) ?? namespaceSegment
|
|
5483
|
+
);
|
|
5544
5484
|
}
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
storages.push({ storage: candidateStorage, dir: candidateDir });
|
|
5558
|
-
};
|
|
5559
|
-
addStorage(storage);
|
|
5560
|
-
for (const recallNamespace of recallNamespaces) {
|
|
5561
|
-
try {
|
|
5562
|
-
addStorage(await this.orchestrator.getStorage(recallNamespace));
|
|
5563
|
-
} catch {
|
|
5564
|
-
continue;
|
|
5565
|
-
}
|
|
5566
|
-
}
|
|
5567
|
-
for (const candidateStorage of storages) {
|
|
5568
|
-
try {
|
|
5569
|
-
const coldRoot = nodePath.join(candidateStorage.dir, "cold");
|
|
5570
|
-
for (const candidatePath of qmdResultPathCandidates(
|
|
5571
|
-
coldRoot,
|
|
5572
|
-
parts.relativePath
|
|
5573
|
-
)) {
|
|
5574
|
-
const memory = await candidateStorage.storage.readMemoryByPath(candidatePath);
|
|
5575
|
-
if (memory) return { memory, baseDir: candidateStorage.dir };
|
|
5576
|
-
}
|
|
5577
|
-
} catch (err) {
|
|
5578
|
-
if (err instanceof SecureStoreLockedError) throw err;
|
|
5579
|
-
continue;
|
|
5580
|
-
}
|
|
5581
|
-
}
|
|
5582
|
-
return null;
|
|
5485
|
+
}
|
|
5486
|
+
for (const policy of this.orchestrator.config.namespacePolicies ?? []) {
|
|
5487
|
+
configuredNamespaces.add(policy.name);
|
|
5488
|
+
}
|
|
5489
|
+
const matches = [];
|
|
5490
|
+
for (const ns of configuredNamespaces) {
|
|
5491
|
+
if (!ns) continue;
|
|
5492
|
+
let candidateStorage;
|
|
5493
|
+
try {
|
|
5494
|
+
candidateStorage = await this.orchestrator.getStorage(ns);
|
|
5495
|
+
} catch {
|
|
5496
|
+
continue;
|
|
5583
5497
|
}
|
|
5584
|
-
const
|
|
5585
|
-
if (
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
for (const candidate of qmdResultPathCandidates(
|
|
5589
|
-
collectionStorage.dir,
|
|
5590
|
-
parts.relativePath
|
|
5591
|
-
)) {
|
|
5592
|
-
const memory = await collectionStorage.readMemoryByPath(candidate);
|
|
5593
|
-
if (memory) return { memory, baseDir: collectionStorage.dir };
|
|
5594
|
-
}
|
|
5595
|
-
return null;
|
|
5596
|
-
} catch (err) {
|
|
5597
|
-
if (err instanceof SecureStoreLockedError) throw err;
|
|
5598
|
-
return null;
|
|
5599
|
-
}
|
|
5600
|
-
}
|
|
5601
|
-
if (nodePath.isAbsolute(memoryPath)) {
|
|
5602
|
-
const ownerStorage = await this.storageForAbsoluteRecallPath(
|
|
5603
|
-
memoryPath,
|
|
5604
|
-
namespace,
|
|
5605
|
-
recallNamespaces
|
|
5606
|
-
);
|
|
5607
|
-
if (!ownerStorage) return null;
|
|
5608
|
-
for (const candidate of qmdResultPathCandidates(
|
|
5609
|
-
ownerStorage.dir,
|
|
5610
|
-
memoryPath
|
|
5611
|
-
)) {
|
|
5612
|
-
const memory = await ownerStorage.storage.readMemoryByPath(candidate);
|
|
5613
|
-
if (memory) return { memory, baseDir: ownerStorage.dir };
|
|
5614
|
-
}
|
|
5615
|
-
return null;
|
|
5616
|
-
}
|
|
5617
|
-
for (const candidate of qmdResultPathCandidates(storageDir, memoryPath)) {
|
|
5618
|
-
const memory = await storage.readMemoryByPath(candidate);
|
|
5619
|
-
if (memory) return { memory, baseDir: storageDir };
|
|
5620
|
-
}
|
|
5621
|
-
return null;
|
|
5622
|
-
};
|
|
5623
|
-
const rawExcerptsResult = rawContext?.rawExcerptsSuppressed === true ? (
|
|
5624
|
-
// Implicit raw recall with NO readable LCM namespace (#1505 thread
|
|
5625
|
-
// NBHWz): emit empty excerpts rather than falling back to the
|
|
5626
|
-
// write/overlay `namespace` the read gate excludes.
|
|
5627
|
-
[]
|
|
5628
|
-
) : await this.fetchRawExcerpts(
|
|
5629
|
-
disclosure,
|
|
5630
|
-
rawContext ? {
|
|
5631
|
-
query: rawContext.query,
|
|
5632
|
-
...rawContext.sessionKey ? { sessionKey: rawContext.sessionKey } : {},
|
|
5633
|
-
namespace: rawContext.rawExcerptNamespace ?? namespace,
|
|
5634
|
-
...rawContext.rawExcerptSessionIds !== void 0 ? { lcmSessionIds: rawContext.rawExcerptSessionIds } : {}
|
|
5635
|
-
} : null
|
|
5636
|
-
);
|
|
5637
|
-
const rawExcerpts = rawExcerptsResult ?? void 0;
|
|
5638
|
-
for (const memoryPath of snapshot.resultPaths ?? []) {
|
|
5639
|
-
if (!memoryPath || seen.has(memoryPath)) continue;
|
|
5640
|
-
const resolved = await readResultPath(memoryPath);
|
|
5641
|
-
if (!resolved) continue;
|
|
5642
|
-
const { memory, baseDir } = resolved;
|
|
5643
|
-
seen.add(memoryPath);
|
|
5644
|
-
results.push(
|
|
5645
|
-
this.serializeMemorySummary(
|
|
5646
|
-
memory,
|
|
5647
|
-
baseDir,
|
|
5648
|
-
disclosure,
|
|
5649
|
-
// Attach the (possibly empty) raw excerpts to the first raw
|
|
5650
|
-
// result; subsequent results do not duplicate the array.
|
|
5651
|
-
results.length === 0 ? rawExcerpts : void 0
|
|
5652
|
-
)
|
|
5653
|
-
);
|
|
5654
|
-
}
|
|
5655
|
-
if (results.length > 0) return results;
|
|
5656
|
-
for (const memoryId of snapshot.memoryIds) {
|
|
5657
|
-
const memory = await storage.getMemoryById(memoryId);
|
|
5658
|
-
if (!memory || seen.has(memory.path)) continue;
|
|
5659
|
-
seen.add(memory.path);
|
|
5660
|
-
results.push(
|
|
5661
|
-
this.serializeMemorySummary(
|
|
5662
|
-
memory,
|
|
5663
|
-
storageDir,
|
|
5664
|
-
disclosure,
|
|
5665
|
-
results.length === 0 ? rawExcerpts : void 0
|
|
5666
|
-
)
|
|
5667
|
-
);
|
|
5668
|
-
}
|
|
5669
|
-
return results;
|
|
5670
|
-
}
|
|
5671
|
-
async storageForAbsoluteRecallPath(memoryPath, primaryNamespace, recallNamespaces = []) {
|
|
5672
|
-
const resolvedPath = nodePath.resolve(memoryPath);
|
|
5673
|
-
const memoryRoot = nodePath.resolve(this.orchestrator.config.memoryDir);
|
|
5674
|
-
const namespacesRoot = nodePath.join(memoryRoot, "namespaces");
|
|
5675
|
-
const configuredNamespaces = /* @__PURE__ */ new Set();
|
|
5676
|
-
configuredNamespaces.add(primaryNamespace);
|
|
5677
|
-
for (const namespace of recallNamespaces) {
|
|
5678
|
-
configuredNamespaces.add(namespace);
|
|
5679
|
-
}
|
|
5680
|
-
configuredNamespaces.add(this.orchestrator.config.defaultNamespace);
|
|
5681
|
-
configuredNamespaces.add(this.orchestrator.config.sharedNamespace);
|
|
5682
|
-
if (isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
|
|
5683
|
-
const relativeToNamespaces = nodePath.relative(namespacesRoot, resolvedPath);
|
|
5684
|
-
const [namespaceSegment] = relativeToNamespaces.split(/[\\/]/);
|
|
5685
|
-
if (namespaceSegment) {
|
|
5686
|
-
configuredNamespaces.add(
|
|
5687
|
-
namespaceIdentityFromToken(namespaceSegment) ?? namespaceSegment
|
|
5688
|
-
);
|
|
5689
|
-
}
|
|
5690
|
-
}
|
|
5691
|
-
for (const policy of this.orchestrator.config.namespacePolicies ?? []) {
|
|
5692
|
-
configuredNamespaces.add(policy.name);
|
|
5693
|
-
}
|
|
5694
|
-
const matches = [];
|
|
5695
|
-
for (const ns of configuredNamespaces) {
|
|
5696
|
-
if (!ns) continue;
|
|
5697
|
-
let candidateStorage;
|
|
5698
|
-
try {
|
|
5699
|
-
candidateStorage = await this.orchestrator.getStorage(ns);
|
|
5700
|
-
} catch {
|
|
5701
|
-
continue;
|
|
5702
|
-
}
|
|
5703
|
-
const candidateRoot = nodePath.resolve(candidateStorage.dir);
|
|
5704
|
-
if (!isPathInsideStorageRoot(candidateRoot, resolvedPath)) continue;
|
|
5705
|
-
if (candidateRoot === memoryRoot && isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
|
|
5706
|
-
continue;
|
|
5498
|
+
const candidateRoot = nodePath.resolve(candidateStorage.dir);
|
|
5499
|
+
if (!isPathInsideStorageRoot(candidateRoot, resolvedPath)) continue;
|
|
5500
|
+
if (candidateRoot === memoryRoot && isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
|
|
5501
|
+
continue;
|
|
5707
5502
|
}
|
|
5708
5503
|
matches.push({ storage: candidateStorage, dir: candidateRoot });
|
|
5709
5504
|
}
|
|
5710
5505
|
matches.sort((a, b) => b.dir.length - a.dir.length);
|
|
5711
5506
|
return matches[0] ?? null;
|
|
5712
5507
|
}
|
|
5713
|
-
/**
|
|
5714
|
-
* Fetch raw transcript excerpts from the LCM archive for `disclosure ===
|
|
5715
|
-
* "raw"` recalls (issue #677 PR 2/4). Returns `null` for non-raw recall
|
|
5716
|
-
* depths, an empty array when LCM is disabled / not initialized / has no
|
|
5717
|
-
* matches, and an array of LCM-side excerpts otherwise. Errors are
|
|
5718
|
-
* swallowed and treated as "no excerpts" so a failing LCM never breaks
|
|
5719
|
-
* the recall response.
|
|
5720
|
-
*
|
|
5721
|
-
* Namespace handling: LCM archival prefixes non-default-namespace
|
|
5722
|
-
* sessions with `${namespace}:${sessionKey}` (see `observe()` around
|
|
5723
|
-
* line 2498), so the lookup must mirror that prefix or raw recalls in
|
|
5724
|
-
* non-default namespaces miss their own excerpts.
|
|
5725
|
-
*/
|
|
5726
5508
|
async fetchRawExcerpts(disclosure, context) {
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
if (!lcm || !lcm.enabled) return [];
|
|
5732
|
-
try {
|
|
5733
|
-
const legacyKey = context.namespace && context.namespace !== this.orchestrator.config.defaultNamespace ? `${context.namespace}:${context.sessionKey}` : context.sessionKey;
|
|
5734
|
-
const lcmSessionIds = context.lcmSessionIds !== void 0 ? context.lcmSessionIds : [legacyKey];
|
|
5735
|
-
const limit = 5;
|
|
5736
|
-
const seenRows = /* @__PURE__ */ new Set();
|
|
5737
|
-
const excerpts = [];
|
|
5738
|
-
const settledRows = await Promise.allSettled(
|
|
5739
|
-
lcmSessionIds.map(
|
|
5740
|
-
async (lcmSessionKey) => lcm.searchContextFull(context.query, limit, lcmSessionKey)
|
|
5741
|
-
)
|
|
5742
|
-
);
|
|
5743
|
-
for (const result of settledRows) {
|
|
5744
|
-
if (excerpts.length >= limit) break;
|
|
5745
|
-
if (result.status !== "fulfilled") continue;
|
|
5746
|
-
for (const r of result.value) {
|
|
5747
|
-
const dedupeKey = `${r.session_id} ${r.turn_index}`;
|
|
5748
|
-
if (seenRows.has(dedupeKey)) continue;
|
|
5749
|
-
seenRows.add(dedupeKey);
|
|
5750
|
-
excerpts.push({
|
|
5751
|
-
turnIndex: r.turn_index,
|
|
5752
|
-
role: r.role,
|
|
5753
|
-
content: r.content,
|
|
5754
|
-
sessionId: r.session_id
|
|
5755
|
-
});
|
|
5756
|
-
if (excerpts.length >= limit) break;
|
|
5757
|
-
}
|
|
5758
|
-
}
|
|
5759
|
-
return excerpts;
|
|
5760
|
-
} catch {
|
|
5761
|
-
return [];
|
|
5762
|
-
}
|
|
5509
|
+
return this.accessRecallSurface.fetchRawExcerpts(
|
|
5510
|
+
disclosure,
|
|
5511
|
+
context
|
|
5512
|
+
);
|
|
5763
5513
|
}
|
|
5764
5514
|
async handleIdempotentWrite(options) {
|
|
5765
5515
|
if (options.skip === true) {
|
|
@@ -5845,8 +5595,8 @@ var EngramAccessService = class {
|
|
|
5845
5595
|
async withIdempotencyLock(key, fn) {
|
|
5846
5596
|
const previous = this.idempotencyLocks.get(key) ?? Promise.resolve();
|
|
5847
5597
|
let release;
|
|
5848
|
-
const current = new Promise((
|
|
5849
|
-
release =
|
|
5598
|
+
const current = new Promise((resolve3) => {
|
|
5599
|
+
release = resolve3;
|
|
5850
5600
|
});
|
|
5851
5601
|
const queued = previous.then(() => current, () => current);
|
|
5852
5602
|
this.idempotencyLocks.set(key, queued);
|
|
@@ -5865,8 +5615,8 @@ var EngramAccessService = class {
|
|
|
5865
5615
|
const key = principal || "__anonymous__";
|
|
5866
5616
|
const previous = this.budgetLocks.get(key) ?? Promise.resolve();
|
|
5867
5617
|
let release;
|
|
5868
|
-
const current = new Promise((
|
|
5869
|
-
release =
|
|
5618
|
+
const current = new Promise((resolve3) => {
|
|
5619
|
+
release = resolve3;
|
|
5870
5620
|
});
|
|
5871
5621
|
const queued = previous.then(() => current, () => current);
|
|
5872
5622
|
this.budgetLocks.set(key, queued);
|
|
@@ -6056,14 +5806,14 @@ var EngramAccessService = class {
|
|
|
6056
5806
|
const timer = setTimeout(() => controller.abort(), 2e3);
|
|
6057
5807
|
timer.unref?.();
|
|
6058
5808
|
try {
|
|
6059
|
-
return await new Promise((
|
|
5809
|
+
return await new Promise((resolve3) => {
|
|
6060
5810
|
const onAbort = () => {
|
|
6061
5811
|
controller.signal.removeEventListener("abort", onAbort);
|
|
6062
|
-
|
|
5812
|
+
resolve3(false);
|
|
6063
5813
|
};
|
|
6064
5814
|
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
6065
5815
|
const probe = typeof qmd.checkAvailability === "function" ? qmd.checkAvailability({ signal: controller.signal }) : qmd.probe();
|
|
6066
|
-
probe.then(
|
|
5816
|
+
probe.then(resolve3, () => resolve3(false)).finally(() => {
|
|
6067
5817
|
controller.signal.removeEventListener("abort", onAbort);
|
|
6068
5818
|
});
|
|
6069
5819
|
});
|
|
@@ -6292,588 +6042,90 @@ var EngramAccessService = class {
|
|
|
6292
6042
|
});
|
|
6293
6043
|
}
|
|
6294
6044
|
async executeRecall(request) {
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6045
|
+
return this.accessRecallSurface.executeRecall(
|
|
6046
|
+
request
|
|
6047
|
+
);
|
|
6048
|
+
}
|
|
6049
|
+
async recallExplain(request = {}) {
|
|
6050
|
+
const requestedNamespace = request.namespace?.trim() ? this.resolveNamespace(request.namespace) : void 0;
|
|
6051
|
+
const authenticatedPrincipal = request.authenticatedPrincipal?.trim();
|
|
6052
|
+
const principal = authenticatedPrincipal || resolvePrincipal(request.sessionKey, this.orchestrator.config);
|
|
6053
|
+
if (requestedNamespace) {
|
|
6054
|
+
if (!canReadNamespace(principal, requestedNamespace, this.orchestrator.config)) {
|
|
6055
|
+
return { found: false };
|
|
6305
6056
|
}
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
if (request.codingContext !== void 0 && request.sessionKey) {
|
|
6309
|
-
this.setCodingContext({
|
|
6310
|
-
sessionKey: request.sessionKey,
|
|
6311
|
-
codingContext: request.codingContext
|
|
6312
|
-
});
|
|
6057
|
+
} else if (resolveNamespaceCapabilities(this.orchestrator.config).namespaces && !principal) {
|
|
6058
|
+
return { found: false };
|
|
6313
6059
|
}
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6060
|
+
const snapshot = request.sessionKey ? (() => {
|
|
6061
|
+
const candidate = this.orchestrator.lastRecall.get(request.sessionKey);
|
|
6062
|
+
if (!candidate) return null;
|
|
6063
|
+
if (!requestedNamespace) return candidate;
|
|
6064
|
+
return candidate.namespace === requestedNamespace ? candidate : null;
|
|
6065
|
+
})() : (() => {
|
|
6066
|
+
const candidate = this.orchestrator.lastRecall.getMostRecent();
|
|
6067
|
+
if (!candidate) return null;
|
|
6068
|
+
if (!requestedNamespace) return candidate;
|
|
6069
|
+
return candidate.namespace === requestedNamespace ? candidate : null;
|
|
6070
|
+
})();
|
|
6071
|
+
const readableSnapshot = (() => {
|
|
6072
|
+
if (!snapshot || !resolveNamespaceCapabilities(this.orchestrator.config).namespaces) return snapshot;
|
|
6073
|
+
const snapshotNamespace = snapshot.namespace ?? this.orchestrator.config.defaultNamespace;
|
|
6074
|
+
return canReadNamespace(principal, snapshotNamespace, this.orchestrator.config) ? snapshot : null;
|
|
6075
|
+
})();
|
|
6076
|
+
const namespace = (() => {
|
|
6077
|
+
if (requestedNamespace) return requestedNamespace;
|
|
6078
|
+
if (readableSnapshot?.namespace) return readableSnapshot.namespace;
|
|
6079
|
+
const fallbackNamespace = this.orchestrator.config.defaultNamespace;
|
|
6080
|
+
if (!resolveNamespaceCapabilities(this.orchestrator.config).namespaces) return fallbackNamespace;
|
|
6081
|
+
return canReadNamespace(principal, fallbackNamespace, this.orchestrator.config) ? fallbackNamespace : null;
|
|
6082
|
+
})();
|
|
6083
|
+
if (!namespace) return { found: false };
|
|
6084
|
+
const [intent, graph] = await Promise.all([
|
|
6085
|
+
this.orchestrator.getLastIntentSnapshot(namespace),
|
|
6086
|
+
this.orchestrator.getLastGraphRecallSnapshot(namespace)
|
|
6087
|
+
]);
|
|
6088
|
+
if (!readableSnapshot && !intent && !graph) return { found: false };
|
|
6089
|
+
return { found: true, snapshot: readableSnapshot ?? void 0, intent, graph };
|
|
6090
|
+
}
|
|
6091
|
+
async recallTierExplain(sessionKey, namespace, authenticatedPrincipal) {
|
|
6092
|
+
const namespacesEnabled = resolveNamespaceCapabilities(this.orchestrator.config).namespaces;
|
|
6093
|
+
const requestedNamespace = namespace?.trim() ? this.resolveNamespace(namespace) : void 0;
|
|
6094
|
+
const principal = authenticatedPrincipal?.trim() || resolvePrincipal(sessionKey, this.orchestrator.config);
|
|
6095
|
+
if (requestedNamespace) {
|
|
6096
|
+
if (!canReadNamespace(principal, requestedNamespace, this.orchestrator.config)) {
|
|
6097
|
+
return toRecallExplainJson(null);
|
|
6098
|
+
}
|
|
6099
|
+
} else if (namespacesEnabled && !authenticatedPrincipal?.trim() && !sessionKey?.trim()) {
|
|
6100
|
+
return toRecallExplainJson(null);
|
|
6319
6101
|
}
|
|
6320
|
-
const
|
|
6321
|
-
const
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6102
|
+
const candidate = sessionKey ? this.orchestrator.lastRecall.get(sessionKey) : this.orchestrator.lastRecall.getMostRecent();
|
|
6103
|
+
const snapshot = (() => {
|
|
6104
|
+
if (!candidate) return null;
|
|
6105
|
+
if (requestedNamespace) {
|
|
6106
|
+
return candidate.namespace === requestedNamespace ? candidate : null;
|
|
6107
|
+
}
|
|
6108
|
+
if (!namespacesEnabled) return candidate;
|
|
6109
|
+
const snapshotNs = candidate.namespace ?? this.orchestrator.config.defaultNamespace;
|
|
6110
|
+
return canReadNamespace(principal, snapshotNs, this.orchestrator.config) ? candidate : null;
|
|
6111
|
+
})();
|
|
6112
|
+
return toRecallExplainJson(snapshot);
|
|
6113
|
+
}
|
|
6114
|
+
async recallXray(request) {
|
|
6115
|
+
return this.accessRecallSurface.recallXray(
|
|
6116
|
+
request
|
|
6325
6117
|
);
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
const
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
const profileCodingOverlay = !namespaceOverride && profileCodingContext && resolveNamespaceCapabilities(this.orchestrator.config).namespaces && this.orchestrator.config.codingMode?.projectScope ? resolveCodingNamespaceOverlay(
|
|
6338
|
-
profileCodingContext,
|
|
6339
|
-
this.orchestrator.config.codingMode,
|
|
6340
|
-
this.orchestrator.config.defaultNamespace
|
|
6341
|
-
) : null;
|
|
6342
|
-
const profilePlan = namespaceOverride ? null : resolveScopeProfilePlan({
|
|
6343
|
-
config: this.orchestrator.config,
|
|
6344
|
-
principal,
|
|
6345
|
-
codingContext: profileCodingContext,
|
|
6346
|
-
codingOverlay: profileCodingOverlay
|
|
6347
|
-
});
|
|
6348
|
-
const modeSkipsBudget = mode === "no_recall";
|
|
6349
|
-
const legacyRecallNamespaces = Array.isArray(this.orchestrator.config.defaultRecallNamespaces) ? recallNamespacesForPrincipal(principal, this.orchestrator.config) : [];
|
|
6350
|
-
const effectiveNamespaces = namespaceOverride ? [namespaceOverride] : profilePlan ? expandScopeProfileReadNamespaces({
|
|
6351
|
-
profilePlan,
|
|
6352
|
-
principalSelfNamespace: profilePlan.baseNamespace,
|
|
6353
|
-
config: this.orchestrator.config,
|
|
6354
|
-
principal,
|
|
6355
|
-
codingOverlay: profileCodingOverlay,
|
|
6356
|
-
legacyRecallNamespaces
|
|
6357
|
-
}) : legacyRecallNamespaces;
|
|
6358
|
-
const budgetPrincipalNamespace = profilePlan?.baseNamespace ?? principalNamespace;
|
|
6359
|
-
let budgetDecision;
|
|
6360
|
-
let recordBudgetAfterSuccess = false;
|
|
6361
|
-
if (modeSkipsBudget) {
|
|
6362
|
-
budgetDecision = {
|
|
6363
|
-
allowed: true,
|
|
6364
|
-
reason: "allowed-same-namespace",
|
|
6365
|
-
count: 0,
|
|
6366
|
-
limit: {
|
|
6367
|
-
softLimit: this.orchestrator.config.recallCrossNamespaceBudgetSoftLimit ?? 10,
|
|
6368
|
-
hardLimit: this.orchestrator.config.recallCrossNamespaceBudgetHardLimit ?? 30,
|
|
6369
|
-
windowMs: this.orchestrator.config.recallCrossNamespaceBudgetWindowMs ?? 6e4
|
|
6370
|
-
}
|
|
6371
|
-
};
|
|
6372
|
-
} else {
|
|
6373
|
-
let anyCrossNamespace = false;
|
|
6374
|
-
let denied = null;
|
|
6375
|
-
let crossNamespaceDecision = null;
|
|
6376
|
-
for (const ns of effectiveNamespaces) {
|
|
6377
|
-
const peek = this.budget.peek({
|
|
6378
|
-
principal,
|
|
6379
|
-
principalNamespace: budgetPrincipalNamespace,
|
|
6380
|
-
queryNamespace: ns
|
|
6381
|
-
});
|
|
6382
|
-
if (peek.reason !== "allowed-same-namespace") {
|
|
6383
|
-
anyCrossNamespace = true;
|
|
6384
|
-
crossNamespaceDecision ??= peek;
|
|
6385
|
-
}
|
|
6386
|
-
if (!peek.allowed) {
|
|
6387
|
-
denied = peek;
|
|
6388
|
-
break;
|
|
6389
|
-
}
|
|
6390
|
-
}
|
|
6391
|
-
if (denied) {
|
|
6392
|
-
budgetDecision = denied;
|
|
6393
|
-
} else if (anyCrossNamespace) {
|
|
6394
|
-
budgetDecision = crossNamespaceDecision ?? {
|
|
6395
|
-
allowed: true,
|
|
6396
|
-
reason: "allowed-under-soft",
|
|
6397
|
-
count: 0,
|
|
6398
|
-
limit: {
|
|
6399
|
-
softLimit: this.orchestrator.config.recallCrossNamespaceBudgetSoftLimit ?? 10,
|
|
6400
|
-
hardLimit: this.orchestrator.config.recallCrossNamespaceBudgetHardLimit ?? 30,
|
|
6401
|
-
windowMs: this.orchestrator.config.recallCrossNamespaceBudgetWindowMs ?? 6e4
|
|
6402
|
-
}
|
|
6403
|
-
};
|
|
6404
|
-
recordBudgetAfterSuccess = true;
|
|
6405
|
-
} else {
|
|
6406
|
-
budgetDecision = {
|
|
6407
|
-
allowed: true,
|
|
6408
|
-
reason: "allowed-same-namespace",
|
|
6409
|
-
count: 0,
|
|
6410
|
-
limit: {
|
|
6411
|
-
softLimit: this.orchestrator.config.recallCrossNamespaceBudgetSoftLimit ?? 10,
|
|
6412
|
-
hardLimit: this.orchestrator.config.recallCrossNamespaceBudgetHardLimit ?? 30,
|
|
6413
|
-
windowMs: this.orchestrator.config.recallCrossNamespaceBudgetWindowMs ?? 6e4
|
|
6414
|
-
}
|
|
6415
|
-
};
|
|
6416
|
-
}
|
|
6417
|
-
if (!budgetDecision.allowed) {
|
|
6418
|
-
throw new EngramAccessInputError(
|
|
6419
|
-
`recall denied: cross-namespace budget exceeded (${budgetDecision.count}/${budgetDecision.limit.hardLimit} in ${budgetDecision.limit.windowMs}ms window)`
|
|
6420
|
-
);
|
|
6421
|
-
}
|
|
6422
|
-
this.budget.gc();
|
|
6423
|
-
}
|
|
6424
|
-
const topK = Number.isFinite(request.topK) ? Math.max(0, Math.floor(request.topK ?? 0)) : void 0;
|
|
6425
|
-
let asOf;
|
|
6426
|
-
if (request.asOf !== void 0 && request.asOf !== null) {
|
|
6427
|
-
if (typeof request.asOf !== "string" || request.asOf.trim().length === 0) {
|
|
6428
|
-
throw new EngramAccessInputError(
|
|
6429
|
-
"asOf must be a non-empty ISO 8601 timestamp string"
|
|
6430
|
-
);
|
|
6431
|
-
}
|
|
6432
|
-
const parsed = Date.parse(request.asOf);
|
|
6433
|
-
if (!Number.isFinite(parsed)) {
|
|
6434
|
-
throw new EngramAccessInputError(
|
|
6435
|
-
`asOf must be a parseable ISO 8601 timestamp (got: "${request.asOf}")`
|
|
6436
|
-
);
|
|
6437
|
-
}
|
|
6438
|
-
asOf = request.asOf;
|
|
6439
|
-
}
|
|
6440
|
-
const recallOptions = {
|
|
6441
|
-
namespace: namespaceOverride,
|
|
6442
|
-
topK,
|
|
6443
|
-
mode,
|
|
6444
|
-
...authenticatedPrincipal ? { principalOverride: authenticatedPrincipal } : {},
|
|
6445
|
-
...asOf !== void 0 ? { asOf } : {},
|
|
6446
|
-
...request.includeLowConfidence === true ? { includeLowConfidence: true } : {}
|
|
6447
|
-
};
|
|
6448
|
-
const startedAt = Date.now();
|
|
6449
|
-
const context = await this.orchestrator.recall(query, request.sessionKey, recallOptions);
|
|
6450
|
-
const snapshot = request.sessionKey ? this.orchestrator.lastRecall.get(request.sessionKey) : null;
|
|
6451
|
-
const effectiveNamespace = snapshot?.namespace ? this.resolveNamespace(snapshot.namespace) : namespace;
|
|
6452
|
-
const resultsReturned = snapshot?.memoryIds?.length ?? 0;
|
|
6453
|
-
const appliedTopK = snapshot?.budgetsApplied?.appliedTopK;
|
|
6454
|
-
const configMaxResults = typeof this.orchestrator.config.qmdMaxResults === "number" && Number.isFinite(this.orchestrator.config.qmdMaxResults) && this.orchestrator.config.qmdMaxResults > 0 ? this.orchestrator.config.qmdMaxResults : 0;
|
|
6455
|
-
const topKDenominator = typeof appliedTopK === "number" && Number.isFinite(appliedTopK) && appliedTopK > 0 ? Math.max(appliedTopK, resultsReturned) : typeof topK === "number" && topK > 0 ? Math.max(topK, resultsReturned) : Math.max(configMaxResults, resultsReturned, 1);
|
|
6456
|
-
const topKConfidence = snapshot && topKDenominator > 0 ? Math.min(1, resultsReturned / topKDenominator) : void 0;
|
|
6457
|
-
const escalationDecision = decideDisclosureEscalation({
|
|
6458
|
-
mode: this.orchestrator.config.recallDisclosureEscalation,
|
|
6459
|
-
threshold: this.orchestrator.config.recallDisclosureEscalationThreshold,
|
|
6460
|
-
originalDisclosure: requestedDisclosure,
|
|
6461
|
-
callerProvidedDisclosure,
|
|
6462
|
-
topKConfidence
|
|
6463
|
-
});
|
|
6464
|
-
const disclosure = escalationDecision.effective;
|
|
6465
|
-
const trimmedSessionKey = request.sessionKey?.trim() || void 0;
|
|
6466
|
-
const rawExcerptNamespace = disclosure === "raw" ? this.resolveRawExcerptReadNamespace(
|
|
6467
|
-
request.namespace,
|
|
6468
|
-
trimmedSessionKey,
|
|
6469
|
-
authenticatedPrincipal
|
|
6470
|
-
) : void 0;
|
|
6471
|
-
const hasExplicitNamespace = typeof request.namespace === "string" && request.namespace.trim().length > 0;
|
|
6472
|
-
const rawExcerptsSuppressed = disclosure === "raw" && !hasExplicitNamespace && rawExcerptNamespace === void 0;
|
|
6473
|
-
const rawExcerptSessionIds = disclosure === "raw" && rawExcerptNamespace && trimmedSessionKey ? this.resolveLcmReadSessionIds(
|
|
6474
|
-
request.namespace,
|
|
6475
|
-
rawExcerptNamespace,
|
|
6476
|
-
trimmedSessionKey,
|
|
6477
|
-
authenticatedPrincipal
|
|
6478
|
-
) : void 0;
|
|
6479
|
-
let results = await this.serializeRecallResults(snapshot, disclosure, {
|
|
6480
|
-
query,
|
|
6481
|
-
sessionKey: trimmedSessionKey,
|
|
6482
|
-
...rawExcerptNamespace ? { rawExcerptNamespace } : {},
|
|
6483
|
-
...rawExcerptSessionIds !== void 0 ? { rawExcerptSessionIds } : {},
|
|
6484
|
-
...rawExcerptsSuppressed ? { rawExcerptsSuppressed } : {}
|
|
6485
|
-
});
|
|
6486
|
-
const filterTags = normalizeTags(request.tags);
|
|
6487
|
-
let tagMatchMode;
|
|
6488
|
-
try {
|
|
6489
|
-
tagMatchMode = parseTagMatch(request.tagMatch);
|
|
6490
|
-
} catch (err) {
|
|
6491
|
-
throw new EngramAccessInputError(
|
|
6492
|
-
err instanceof Error ? err.message : String(err)
|
|
6493
|
-
);
|
|
6494
|
-
}
|
|
6495
|
-
let effectiveContext = context;
|
|
6496
|
-
if (filterTags && filterTags.length > 0) {
|
|
6497
|
-
const beforeIds = results.map((r) => r.id);
|
|
6498
|
-
const { results: admitted } = applyTagFilter(results, {
|
|
6499
|
-
tags: filterTags,
|
|
6500
|
-
tagMatch: tagMatchMode
|
|
6501
|
-
});
|
|
6502
|
-
results = admitted;
|
|
6503
|
-
const admittedIds = new Set(results.map((r) => r.id));
|
|
6504
|
-
const droppedAny = beforeIds.some((id) => !admittedIds.has(id));
|
|
6505
|
-
if (droppedAny) {
|
|
6506
|
-
effectiveContext = results.map((r) => {
|
|
6507
|
-
const content = typeof r.content === "string" ? r.content ?? "" : "";
|
|
6508
|
-
const preview = typeof r.preview === "string" ? r.preview ?? "" : "";
|
|
6509
|
-
return content || preview;
|
|
6510
|
-
}).filter((s) => s.length > 0).join("\n\n");
|
|
6511
|
-
}
|
|
6512
|
-
}
|
|
6513
|
-
const filteredMemoryIds = filterTags && filterTags.length > 0 ? results.map((r) => r.id) : snapshot?.memoryIds ?? [];
|
|
6514
|
-
const debug = await this.buildRecallDebug(
|
|
6515
|
-
snapshot,
|
|
6516
|
-
effectiveNamespace,
|
|
6517
|
-
request.includeDebug === true,
|
|
6518
|
-
request.sessionKey
|
|
6519
|
-
);
|
|
6520
|
-
let auditAnomalies;
|
|
6521
|
-
if (this.auditAdapter) {
|
|
6522
|
-
try {
|
|
6523
|
-
const resolvedAgentId = principal ?? "__anonymous__";
|
|
6524
|
-
const auditEntry = {
|
|
6525
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6526
|
-
sessionKey: request.sessionKey ?? "",
|
|
6527
|
-
agentId: resolvedAgentId,
|
|
6528
|
-
trigger: "access-surface",
|
|
6529
|
-
queryText: query,
|
|
6530
|
-
candidateMemoryIds: snapshot?.memoryIds ?? [],
|
|
6531
|
-
// Audit must reflect what was actually injected, not what
|
|
6532
|
-
// recall produced before the tag filter. Using `context`
|
|
6533
|
-
// (pre-filter) overstates injectedChars and can leak content
|
|
6534
|
-
// from excluded memories into the audit summary (cursor
|
|
6535
|
-
// Medium on PR #712).
|
|
6536
|
-
summary: effectiveContext.slice(0, 200) || null,
|
|
6537
|
-
injectedChars: effectiveContext.length,
|
|
6538
|
-
toggleState: "enabled",
|
|
6539
|
-
latencyMs: Date.now() - startedAt,
|
|
6540
|
-
plannerMode: snapshot?.plannerMode ?? mode,
|
|
6541
|
-
requestedMode: mode,
|
|
6542
|
-
fallbackUsed: snapshot?.fallbackUsed ?? false
|
|
6543
|
-
};
|
|
6544
|
-
const auditResult = await this.auditAdapter.record(
|
|
6545
|
-
resolvedAgentId || "__anonymous__",
|
|
6546
|
-
auditEntry
|
|
6547
|
-
);
|
|
6548
|
-
auditAnomalies = auditResult.anomalies;
|
|
6549
|
-
} catch {
|
|
6550
|
-
}
|
|
6551
|
-
}
|
|
6552
|
-
return {
|
|
6553
|
-
response: {
|
|
6554
|
-
query,
|
|
6555
|
-
sessionKey: request.sessionKey,
|
|
6556
|
-
namespace: effectiveNamespace,
|
|
6557
|
-
context: effectiveContext,
|
|
6558
|
-
count: filterTags && filterTags.length > 0 ? results.length : snapshot?.memoryIds.length ?? results.length,
|
|
6559
|
-
memoryIds: filteredMemoryIds,
|
|
6560
|
-
results,
|
|
6561
|
-
recordedAt: snapshot?.recordedAt,
|
|
6562
|
-
traceId: snapshot?.traceId,
|
|
6563
|
-
plannerMode: snapshot?.plannerMode ?? mode,
|
|
6564
|
-
fallbackUsed: snapshot?.fallbackUsed ?? false,
|
|
6565
|
-
sourcesUsed: snapshot?.sourcesUsed ?? [],
|
|
6566
|
-
disclosure,
|
|
6567
|
-
budgetsApplied: snapshot?.budgetsApplied,
|
|
6568
|
-
auditAnomalies,
|
|
6569
|
-
budgetWarning: budgetDecision.reason === "warn-over-soft" ? budgetDecision : void 0,
|
|
6570
|
-
latencyMs: snapshot?.latencyMs ?? Date.now() - startedAt,
|
|
6571
|
-
debug
|
|
6572
|
-
},
|
|
6573
|
-
budgetRecordPrincipal: recordBudgetAfterSuccess ? principal : null
|
|
6574
|
-
};
|
|
6575
|
-
}
|
|
6576
|
-
async recallExplain(request = {}) {
|
|
6577
|
-
const requestedNamespace = request.namespace?.trim() ? this.resolveNamespace(request.namespace) : void 0;
|
|
6578
|
-
const authenticatedPrincipal = request.authenticatedPrincipal?.trim();
|
|
6579
|
-
const principal = authenticatedPrincipal || resolvePrincipal(request.sessionKey, this.orchestrator.config);
|
|
6580
|
-
if (requestedNamespace) {
|
|
6581
|
-
if (!canReadNamespace(principal, requestedNamespace, this.orchestrator.config)) {
|
|
6582
|
-
return { found: false };
|
|
6583
|
-
}
|
|
6584
|
-
} else if (resolveNamespaceCapabilities(this.orchestrator.config).namespaces && !principal) {
|
|
6585
|
-
return { found: false };
|
|
6586
|
-
}
|
|
6587
|
-
const snapshot = request.sessionKey ? (() => {
|
|
6588
|
-
const candidate = this.orchestrator.lastRecall.get(request.sessionKey);
|
|
6589
|
-
if (!candidate) return null;
|
|
6590
|
-
if (!requestedNamespace) return candidate;
|
|
6591
|
-
return candidate.namespace === requestedNamespace ? candidate : null;
|
|
6592
|
-
})() : (() => {
|
|
6593
|
-
const candidate = this.orchestrator.lastRecall.getMostRecent();
|
|
6594
|
-
if (!candidate) return null;
|
|
6595
|
-
if (!requestedNamespace) return candidate;
|
|
6596
|
-
return candidate.namespace === requestedNamespace ? candidate : null;
|
|
6597
|
-
})();
|
|
6598
|
-
const readableSnapshot = (() => {
|
|
6599
|
-
if (!snapshot || !resolveNamespaceCapabilities(this.orchestrator.config).namespaces) return snapshot;
|
|
6600
|
-
const snapshotNamespace = snapshot.namespace ?? this.orchestrator.config.defaultNamespace;
|
|
6601
|
-
return canReadNamespace(principal, snapshotNamespace, this.orchestrator.config) ? snapshot : null;
|
|
6602
|
-
})();
|
|
6603
|
-
const namespace = (() => {
|
|
6604
|
-
if (requestedNamespace) return requestedNamespace;
|
|
6605
|
-
if (readableSnapshot?.namespace) return readableSnapshot.namespace;
|
|
6606
|
-
const fallbackNamespace = this.orchestrator.config.defaultNamespace;
|
|
6607
|
-
if (!resolveNamespaceCapabilities(this.orchestrator.config).namespaces) return fallbackNamespace;
|
|
6608
|
-
return canReadNamespace(principal, fallbackNamespace, this.orchestrator.config) ? fallbackNamespace : null;
|
|
6609
|
-
})();
|
|
6610
|
-
if (!namespace) return { found: false };
|
|
6611
|
-
const [intent, graph] = await Promise.all([
|
|
6612
|
-
this.orchestrator.getLastIntentSnapshot(namespace),
|
|
6613
|
-
this.orchestrator.getLastGraphRecallSnapshot(namespace)
|
|
6614
|
-
]);
|
|
6615
|
-
if (!readableSnapshot && !intent && !graph) return { found: false };
|
|
6616
|
-
return { found: true, snapshot: readableSnapshot ?? void 0, intent, graph };
|
|
6617
|
-
}
|
|
6618
|
-
async recallTierExplain(sessionKey, namespace, authenticatedPrincipal) {
|
|
6619
|
-
const namespacesEnabled = resolveNamespaceCapabilities(this.orchestrator.config).namespaces;
|
|
6620
|
-
const requestedNamespace = namespace?.trim() ? this.resolveNamespace(namespace) : void 0;
|
|
6621
|
-
const principal = authenticatedPrincipal?.trim() || resolvePrincipal(sessionKey, this.orchestrator.config);
|
|
6622
|
-
if (requestedNamespace) {
|
|
6623
|
-
if (!canReadNamespace(principal, requestedNamespace, this.orchestrator.config)) {
|
|
6624
|
-
return toRecallExplainJson(null);
|
|
6625
|
-
}
|
|
6626
|
-
} else if (namespacesEnabled && !authenticatedPrincipal?.trim() && !sessionKey?.trim()) {
|
|
6627
|
-
return toRecallExplainJson(null);
|
|
6628
|
-
}
|
|
6629
|
-
const candidate = sessionKey ? this.orchestrator.lastRecall.get(sessionKey) : this.orchestrator.lastRecall.getMostRecent();
|
|
6630
|
-
const snapshot = (() => {
|
|
6631
|
-
if (!candidate) return null;
|
|
6632
|
-
if (requestedNamespace) {
|
|
6633
|
-
return candidate.namespace === requestedNamespace ? candidate : null;
|
|
6634
|
-
}
|
|
6635
|
-
if (!namespacesEnabled) return candidate;
|
|
6636
|
-
const snapshotNs = candidate.namespace ?? this.orchestrator.config.defaultNamespace;
|
|
6637
|
-
return canReadNamespace(principal, snapshotNs, this.orchestrator.config) ? candidate : null;
|
|
6638
|
-
})();
|
|
6639
|
-
return toRecallExplainJson(snapshot);
|
|
6640
|
-
}
|
|
6641
|
-
/**
|
|
6642
|
-
* Recall X-ray (issue #570). Runs a recall with `xrayCapture: true`
|
|
6643
|
-
* and returns the resulting snapshot as structured JSON so every
|
|
6644
|
-
* surface (CLI / HTTP / MCP) gets the same payload. Namespace scope
|
|
6645
|
-
* is enforced before the recall fires (CLAUDE.md rule 42 — read and
|
|
6646
|
-
* write paths must resolve through the same namespace layer) so an
|
|
6647
|
-
* unauthorized principal cannot capture an x-ray for a namespace it
|
|
6648
|
-
* cannot read.
|
|
6649
|
-
*/
|
|
6650
|
-
async recallXray(request) {
|
|
6651
|
-
const query = typeof request.query === "string" ? request.query : "";
|
|
6652
|
-
if (query.trim().length === 0) {
|
|
6653
|
-
throw new Error("recallXray: query is required and must be non-empty");
|
|
6654
|
-
}
|
|
6655
|
-
if (request.disclosure !== void 0 && !isRecallDisclosure(request.disclosure)) {
|
|
6656
|
-
throw new EngramAccessInputError(
|
|
6657
|
-
`recallXray: disclosure must be one of: chunk, section, raw (got: ${String(request.disclosure)})`
|
|
6658
|
-
);
|
|
6659
|
-
}
|
|
6660
|
-
const namespacesEnabled = resolveNamespaceCapabilities(this.orchestrator.config).namespaces;
|
|
6661
|
-
const requestedNamespace = request.namespace?.trim() ? this.resolveNamespace(request.namespace) : void 0;
|
|
6662
|
-
const authenticatedPrincipal = request.authenticatedPrincipal?.trim();
|
|
6663
|
-
const principal = authenticatedPrincipal || resolvePrincipal(request.sessionKey, this.orchestrator.config);
|
|
6664
|
-
if (requestedNamespace) {
|
|
6665
|
-
if (!canReadNamespace(
|
|
6666
|
-
principal,
|
|
6667
|
-
requestedNamespace,
|
|
6668
|
-
this.orchestrator.config
|
|
6669
|
-
)) {
|
|
6670
|
-
return { snapshotFound: false };
|
|
6671
|
-
}
|
|
6672
|
-
} else if (namespacesEnabled && !authenticatedPrincipal && !request.sessionKey?.trim()) {
|
|
6673
|
-
return { snapshotFound: false };
|
|
6674
|
-
}
|
|
6675
|
-
let budgetOverride;
|
|
6676
|
-
if (request.budget !== void 0 && request.budget !== null) {
|
|
6677
|
-
const parsed = typeof request.budget === "number" ? request.budget : Number(request.budget);
|
|
6678
|
-
if (!Number.isFinite(parsed) || parsed <= 0 || !Number.isInteger(parsed)) {
|
|
6679
|
-
throw new Error(
|
|
6680
|
-
`recallXray: budget expects a positive integer; got ${JSON.stringify(request.budget)}`
|
|
6681
|
-
);
|
|
6682
|
-
}
|
|
6683
|
-
budgetOverride = parsed;
|
|
6684
|
-
}
|
|
6685
|
-
const mode = this.normalizeRecallMode(request.mode);
|
|
6686
|
-
const disclosure = request.disclosure ?? DEFAULT_RECALL_DISCLOSURE;
|
|
6687
|
-
const previousQueue = this.xrayQueue;
|
|
6688
|
-
let release = () => {
|
|
6689
|
-
};
|
|
6690
|
-
this.xrayQueue = new Promise((resolve2) => {
|
|
6691
|
-
release = resolve2;
|
|
6692
|
-
});
|
|
6693
|
-
await previousQueue;
|
|
6694
|
-
const recallStartedAt = Date.now();
|
|
6695
|
-
const recallSessionKey = request.sessionKey?.trim() || void 0;
|
|
6696
|
-
let xrayResponse = { snapshotFound: false };
|
|
6697
|
-
try {
|
|
6698
|
-
this.orchestrator.clearLastXraySnapshot();
|
|
6699
|
-
await this.orchestrator.recall(query, recallSessionKey, {
|
|
6700
|
-
xrayCapture: true,
|
|
6701
|
-
...requestedNamespace ? { namespace: requestedNamespace } : {},
|
|
6702
|
-
...budgetOverride !== void 0 ? { budgetCharsOverride: budgetOverride } : {},
|
|
6703
|
-
...mode !== void 0 ? { mode } : {},
|
|
6704
|
-
// When the caller supplies an authenticated principal, forward
|
|
6705
|
-
// it via the dedicated override channel so orchestrator-side
|
|
6706
|
-
// ACL decisions use the SAME principal the access-surface
|
|
6707
|
-
// pre-check above authorized. Threading an
|
|
6708
|
-
// `authenticatedPrincipal` through `sessionKey` would be wrong:
|
|
6709
|
-
// `resolvePrincipal(sessionKey)` only maps configured raw
|
|
6710
|
-
// session keys and otherwise collapses to `"default"`, which
|
|
6711
|
-
// in namespace-enabled deployments produces false denials /
|
|
6712
|
-
// wrong-scope serving despite the pre-check passing
|
|
6713
|
-
// (CLAUDE.md rule 42).
|
|
6714
|
-
...authenticatedPrincipal ? { principalOverride: authenticatedPrincipal } : {},
|
|
6715
|
-
...request.currentContextScopes !== void 0 ? { currentContextScopes: request.currentContextScopes } : {}
|
|
6716
|
-
});
|
|
6717
|
-
const rawSnapshot = this.orchestrator.getLastXraySnapshot();
|
|
6718
|
-
const namespaceMismatch = requestedNamespace !== void 0 && rawSnapshot?.namespace !== requestedNamespace;
|
|
6719
|
-
if (!rawSnapshot) {
|
|
6720
|
-
xrayResponse = { snapshotFound: false };
|
|
6721
|
-
} else if (namespaceMismatch) {
|
|
6722
|
-
xrayResponse = { snapshotFound: false };
|
|
6723
|
-
} else {
|
|
6724
|
-
let snapshot = rawSnapshot;
|
|
6725
|
-
const xrayFilterTags = normalizeTags(request.tags);
|
|
6726
|
-
let xrayTagMatch;
|
|
6727
|
-
try {
|
|
6728
|
-
xrayTagMatch = parseTagMatch(request.tagMatch);
|
|
6729
|
-
} catch (err) {
|
|
6730
|
-
throw new EngramAccessInputError(
|
|
6731
|
-
err instanceof Error ? err.message : String(err)
|
|
6732
|
-
);
|
|
6733
|
-
}
|
|
6734
|
-
if (xrayFilterTags && xrayFilterTags.length > 0) {
|
|
6735
|
-
const namespace = snapshot.namespace ? this.resolveNamespace(snapshot.namespace) : this.orchestrator.config.defaultNamespace;
|
|
6736
|
-
const tagsByIndex = await Promise.all(
|
|
6737
|
-
snapshot.results.map(async (result) => {
|
|
6738
|
-
try {
|
|
6739
|
-
const storage = await this.orchestrator.getStorage(namespace);
|
|
6740
|
-
const memory = await storage.readMemoryByPath(result.path);
|
|
6741
|
-
const t = memory?.frontmatter?.tags;
|
|
6742
|
-
return Array.isArray(t) ? normalizeProjectionTags(t) : [];
|
|
6743
|
-
} catch {
|
|
6744
|
-
return [];
|
|
6745
|
-
}
|
|
6746
|
-
})
|
|
6747
|
-
);
|
|
6748
|
-
const tagged = snapshot.results.map((result, index) => ({
|
|
6749
|
-
result,
|
|
6750
|
-
tags: tagsByIndex[index] ?? []
|
|
6751
|
-
}));
|
|
6752
|
-
const { results: admittedTagged, trace } = applyTagFilter(tagged, {
|
|
6753
|
-
tags: xrayFilterTags,
|
|
6754
|
-
tagMatch: xrayTagMatch
|
|
6755
|
-
});
|
|
6756
|
-
const admittedResults = admittedTagged.map((entry) => entry.result);
|
|
6757
|
-
const filters = trace ? [...snapshot.filters, trace] : snapshot.filters;
|
|
6758
|
-
snapshot = { ...snapshot, results: admittedResults, filters };
|
|
6759
|
-
}
|
|
6760
|
-
if (request.disclosure !== void 0) {
|
|
6761
|
-
const disclosure2 = request.disclosure;
|
|
6762
|
-
const namespace = snapshot.namespace ? this.resolveNamespace(snapshot.namespace) : this.orchestrator.config.defaultNamespace;
|
|
6763
|
-
const trimmedSessionKey = request.sessionKey?.trim() || void 0;
|
|
6764
|
-
const rawExcerptNamespace = disclosure2 === "raw" ? this.resolveRawExcerptReadNamespace(
|
|
6765
|
-
request.namespace,
|
|
6766
|
-
trimmedSessionKey,
|
|
6767
|
-
authenticatedPrincipal
|
|
6768
|
-
) : namespace;
|
|
6769
|
-
const xrayHasExplicitNamespace = typeof request.namespace === "string" && request.namespace.trim().length > 0;
|
|
6770
|
-
const rawExcerptsSuppressed = disclosure2 === "raw" && !xrayHasExplicitNamespace && rawExcerptNamespace === void 0;
|
|
6771
|
-
const rawExcerptSessionIds = disclosure2 === "raw" && trimmedSessionKey && rawExcerptNamespace ? this.resolveLcmReadSessionIds(
|
|
6772
|
-
request.namespace,
|
|
6773
|
-
rawExcerptNamespace,
|
|
6774
|
-
trimmedSessionKey,
|
|
6775
|
-
authenticatedPrincipal
|
|
6776
|
-
) : void 0;
|
|
6777
|
-
const rawExcerpts = disclosure2 === "raw" && !rawExcerptsSuppressed ? await this.fetchRawExcerpts(disclosure2, {
|
|
6778
|
-
query,
|
|
6779
|
-
...trimmedSessionKey ? { sessionKey: trimmedSessionKey } : {},
|
|
6780
|
-
...rawExcerptNamespace ? { namespace: rawExcerptNamespace } : {},
|
|
6781
|
-
...rawExcerptSessionIds !== void 0 ? { lcmSessionIds: rawExcerptSessionIds } : {}
|
|
6782
|
-
}) : disclosure2 === "raw" ? [] : null;
|
|
6783
|
-
const rawExcerptText = rawExcerpts && rawExcerpts.length > 0 ? rawExcerpts.map((e) => e.content).join("\n") : "";
|
|
6784
|
-
const memoryByIndex = await Promise.all(
|
|
6785
|
-
snapshot.results.map(async (result) => {
|
|
6786
|
-
try {
|
|
6787
|
-
const storage = await this.orchestrator.getStorage(namespace);
|
|
6788
|
-
return await storage.readMemoryByPath(result.path);
|
|
6789
|
-
} catch {
|
|
6790
|
-
return null;
|
|
6791
|
-
}
|
|
6792
|
-
})
|
|
6793
|
-
);
|
|
6794
|
-
const firstReadableIndex = memoryByIndex.findIndex((m) => m !== null);
|
|
6795
|
-
const baseDir = (await this.orchestrator.getStorage(namespace)).dir;
|
|
6796
|
-
const decorated = snapshot.results.map((result, index) => {
|
|
6797
|
-
const memory = memoryByIndex[index];
|
|
6798
|
-
if (!memory) {
|
|
6799
|
-
return { ...result, disclosure: disclosure2 };
|
|
6800
|
-
}
|
|
6801
|
-
const shaped = shapeMemorySummary(
|
|
6802
|
-
memory,
|
|
6803
|
-
baseDir,
|
|
6804
|
-
disclosure2,
|
|
6805
|
-
disclosure2 === "raw" && index === firstReadableIndex && rawExcerpts && rawExcerpts.length > 0 ? rawExcerpts : void 0
|
|
6806
|
-
);
|
|
6807
|
-
return {
|
|
6808
|
-
...result,
|
|
6809
|
-
disclosure: disclosure2,
|
|
6810
|
-
estimatedTokens: estimateRecallTokens(JSON.stringify(shaped))
|
|
6811
|
-
};
|
|
6812
|
-
});
|
|
6813
|
-
if (disclosure2 === "raw" && firstReadableIndex === -1 && rawExcerptText.length > 0 && decorated.length > 0) {
|
|
6814
|
-
decorated[0] = {
|
|
6815
|
-
...decorated[0],
|
|
6816
|
-
disclosure: disclosure2,
|
|
6817
|
-
estimatedTokens: estimateRecallTokens(rawExcerptText)
|
|
6818
|
-
};
|
|
6819
|
-
}
|
|
6820
|
-
const decoratedSnapshot = { ...snapshot, results: decorated };
|
|
6821
|
-
xrayResponse = {
|
|
6822
|
-
snapshotFound: true,
|
|
6823
|
-
snapshot: decoratedSnapshot
|
|
6824
|
-
};
|
|
6825
|
-
} else {
|
|
6826
|
-
xrayResponse = {
|
|
6827
|
-
snapshotFound: true,
|
|
6828
|
-
snapshot
|
|
6829
|
-
};
|
|
6830
|
-
}
|
|
6831
|
-
}
|
|
6832
|
-
} finally {
|
|
6833
|
-
release();
|
|
6834
|
-
}
|
|
6835
|
-
if (request.includeRecall === true && xrayResponse.snapshotFound === true && xrayResponse.snapshot) {
|
|
6836
|
-
const xrayRawExcerptNamespace = disclosure === "raw" ? this.resolveRawExcerptReadNamespace(
|
|
6837
|
-
request.namespace,
|
|
6838
|
-
recallSessionKey,
|
|
6839
|
-
authenticatedPrincipal
|
|
6840
|
-
) : void 0;
|
|
6841
|
-
const xrayHasExplicitNamespace = typeof request.namespace === "string" && request.namespace.trim().length > 0;
|
|
6842
|
-
const xrayRawExcerptsSuppressed = disclosure === "raw" && !xrayHasExplicitNamespace && xrayRawExcerptNamespace === void 0;
|
|
6843
|
-
const xrayRawExcerptSessionIds = disclosure === "raw" && xrayRawExcerptNamespace && recallSessionKey ? this.resolveLcmReadSessionIds(
|
|
6844
|
-
request.namespace,
|
|
6845
|
-
xrayRawExcerptNamespace,
|
|
6846
|
-
recallSessionKey,
|
|
6847
|
-
authenticatedPrincipal
|
|
6848
|
-
) : void 0;
|
|
6849
|
-
return {
|
|
6850
|
-
...xrayResponse,
|
|
6851
|
-
recall: await this.buildRecallResponseFromXraySnapshot({
|
|
6852
|
-
query,
|
|
6853
|
-
sessionKey: recallSessionKey,
|
|
6854
|
-
snapshot: xrayResponse.snapshot,
|
|
6855
|
-
disclosure,
|
|
6856
|
-
startedAt: recallStartedAt,
|
|
6857
|
-
requestedMode: request.mode,
|
|
6858
|
-
normalizedMode: mode,
|
|
6859
|
-
...xrayRawExcerptNamespace ? { rawExcerptNamespace: xrayRawExcerptNamespace } : {},
|
|
6860
|
-
...xrayRawExcerptSessionIds !== void 0 ? { rawExcerptSessionIds: xrayRawExcerptSessionIds } : {},
|
|
6861
|
-
...xrayRawExcerptsSuppressed ? { rawExcerptsSuppressed: xrayRawExcerptsSuppressed } : {}
|
|
6862
|
-
})
|
|
6863
|
-
};
|
|
6864
|
-
}
|
|
6865
|
-
return xrayResponse;
|
|
6866
|
-
}
|
|
6867
|
-
// Sequence lock for `recallXray` — see comment inside the method.
|
|
6868
|
-
// Lives on the instance so every x-ray call on the same service
|
|
6869
|
-
// shares it, and so separate services in the same process (e.g.
|
|
6870
|
-
// per-tenant) do not block each other.
|
|
6871
|
-
xrayQueue = Promise.resolve();
|
|
6872
|
-
async memoryStore(request, hooks) {
|
|
6873
|
-
const namespace = await this.resolveCodingScopedWriteNamespace(request);
|
|
6874
|
-
const schemaVersion = request.schemaVersion ?? ENGRAM_ACCESS_WRITE_SCHEMA_VERSION;
|
|
6875
|
-
if (schemaVersion !== ENGRAM_ACCESS_WRITE_SCHEMA_VERSION) {
|
|
6876
|
-
throw new EngramAccessInputError(`unsupported schemaVersion: ${schemaVersion}`);
|
|
6118
|
+
}
|
|
6119
|
+
// Sequence lock for `recallXray` — see comment inside the method.
|
|
6120
|
+
// Lives on the instance so every x-ray call on the same service
|
|
6121
|
+
// shares it, and so separate services in the same process (e.g.
|
|
6122
|
+
// per-tenant) do not block each other.
|
|
6123
|
+
xrayQueue = Promise.resolve();
|
|
6124
|
+
async memoryStore(request, hooks) {
|
|
6125
|
+
const namespace = await this.resolveCodingScopedWriteNamespace(request);
|
|
6126
|
+
const schemaVersion = request.schemaVersion ?? ENGRAM_ACCESS_WRITE_SCHEMA_VERSION;
|
|
6127
|
+
if (schemaVersion !== ENGRAM_ACCESS_WRITE_SCHEMA_VERSION) {
|
|
6128
|
+
throw new EngramAccessInputError(`unsupported schemaVersion: ${schemaVersion}`);
|
|
6877
6129
|
}
|
|
6878
6130
|
const execute = async () => {
|
|
6879
6131
|
const candidate = this.validateWriteCandidate(request, namespace);
|
|
@@ -9242,1123 +8494,2157 @@ ${next}`);
|
|
|
9242
8494
|
return { explanation };
|
|
9243
8495
|
}
|
|
9244
8496
|
/**
|
|
9245
|
-
* Read-only graph snapshot for the admin pane (issue #691 PR 2/5).
|
|
9246
|
-
*
|
|
9247
|
-
* Reads adjacency from the JSONL edge store written by `GraphIndex` and
|
|
9248
|
-
* resolves node metadata via the namespaced storage manager. Namespace
|
|
9249
|
-
* resolution mirrors the read-side path used by `recall` /
|
|
9250
|
-
* `procedureStats`, so multi-principal deployments can't leak edges from
|
|
9251
|
-
* a peer namespace (CLAUDE.md rule 42).
|
|
8497
|
+
* Read-only graph snapshot for the admin pane (issue #691 PR 2/5).
|
|
8498
|
+
*
|
|
8499
|
+
* Reads adjacency from the JSONL edge store written by `GraphIndex` and
|
|
8500
|
+
* resolves node metadata via the namespaced storage manager. Namespace
|
|
8501
|
+
* resolution mirrors the read-side path used by `recall` /
|
|
8502
|
+
* `procedureStats`, so multi-principal deployments can't leak edges from
|
|
8503
|
+
* a peer namespace (CLAUDE.md rule 42).
|
|
8504
|
+
*/
|
|
8505
|
+
async graphSnapshot(request, authenticatedPrincipal) {
|
|
8506
|
+
const namespace = this.resolveReadableNamespace(
|
|
8507
|
+
request.namespace,
|
|
8508
|
+
authenticatedPrincipal
|
|
8509
|
+
);
|
|
8510
|
+
const storage = await this.orchestrator.getStorage(namespace);
|
|
8511
|
+
const cfg = this.orchestrator.config;
|
|
8512
|
+
const graphCaps = resolveGraphConstructionCapabilities(cfg);
|
|
8513
|
+
let namespaceRootReal;
|
|
8514
|
+
try {
|
|
8515
|
+
namespaceRootReal = await nodeFs.realpath(storage.dir);
|
|
8516
|
+
} catch {
|
|
8517
|
+
namespaceRootReal = nodePath.resolve(storage.dir);
|
|
8518
|
+
}
|
|
8519
|
+
const namespaceRootWithSep = namespaceRootReal.endsWith(nodePath.sep) ? namespaceRootReal : namespaceRootReal + nodePath.sep;
|
|
8520
|
+
const loadNode = async (relPath) => {
|
|
8521
|
+
if (nodePath.isAbsolute(relPath)) {
|
|
8522
|
+
log.warn(
|
|
8523
|
+
`graphSnapshot: rejected absolute edge endpoint (len=${relPath.length}) outside namespace root`
|
|
8524
|
+
);
|
|
8525
|
+
return null;
|
|
8526
|
+
}
|
|
8527
|
+
const candidate = nodePath.resolve(namespaceRootReal, relPath);
|
|
8528
|
+
if (candidate !== namespaceRootReal && !candidate.startsWith(namespaceRootWithSep)) {
|
|
8529
|
+
log.warn(
|
|
8530
|
+
`graphSnapshot: rejected traversing edge endpoint (len=${relPath.length}) outside namespace root`
|
|
8531
|
+
);
|
|
8532
|
+
return null;
|
|
8533
|
+
}
|
|
8534
|
+
let canonical;
|
|
8535
|
+
try {
|
|
8536
|
+
canonical = await nodeFs.realpath(candidate);
|
|
8537
|
+
} catch {
|
|
8538
|
+
canonical = candidate;
|
|
8539
|
+
}
|
|
8540
|
+
if (canonical !== namespaceRootReal && !canonical.startsWith(namespaceRootWithSep)) {
|
|
8541
|
+
log.warn(
|
|
8542
|
+
`graphSnapshot: rejected symlinked edge endpoint (len=${relPath.length}) that resolved outside namespace root`
|
|
8543
|
+
);
|
|
8544
|
+
return null;
|
|
8545
|
+
}
|
|
8546
|
+
const memory = await storage.readMemoryByPath(canonical);
|
|
8547
|
+
if (!memory) return null;
|
|
8548
|
+
const fm = memory.frontmatter;
|
|
8549
|
+
return {
|
|
8550
|
+
category: fm.category ?? "unknown",
|
|
8551
|
+
label: fm.id ?? nodePath.basename(canonical, nodePath.extname(canonical)),
|
|
8552
|
+
updated: fm.updated
|
|
8553
|
+
};
|
|
8554
|
+
};
|
|
8555
|
+
return buildGraphSnapshot({
|
|
8556
|
+
memoryDir: namespaceRootReal,
|
|
8557
|
+
graphConfig: {
|
|
8558
|
+
entityGraph: graphCaps.entityGraph,
|
|
8559
|
+
timeGraph: graphCaps.timeGraph,
|
|
8560
|
+
causalGraph: graphCaps.causalGraph
|
|
8561
|
+
},
|
|
8562
|
+
request: {
|
|
8563
|
+
limit: request.limit,
|
|
8564
|
+
since: request.since,
|
|
8565
|
+
focusNodeId: request.focusNodeId,
|
|
8566
|
+
categories: request.categories
|
|
8567
|
+
},
|
|
8568
|
+
loadNode
|
|
8569
|
+
});
|
|
8570
|
+
}
|
|
8571
|
+
async memoryFeedback(request) {
|
|
8572
|
+
if (!resolveRecallEnhancementCapabilities(this.orchestrator.config).feedback) {
|
|
8573
|
+
return {
|
|
8574
|
+
recorded: false,
|
|
8575
|
+
enabled: false,
|
|
8576
|
+
reason: "Feedback is disabled. Enable `feedbackEnabled: true` in the Engram config to store feedback."
|
|
8577
|
+
};
|
|
8578
|
+
}
|
|
8579
|
+
await this.orchestrator.recordMemoryFeedback(
|
|
8580
|
+
request.memoryId,
|
|
8581
|
+
request.vote,
|
|
8582
|
+
request.note
|
|
8583
|
+
);
|
|
8584
|
+
return { recorded: true };
|
|
8585
|
+
}
|
|
8586
|
+
/**
|
|
8587
|
+
* Record a Memory Worth outcome observation (issue #560 PR 3).
|
|
8588
|
+
*
|
|
8589
|
+
* This is distinct from `memoryFeedback` — feedback is a human thumbs
|
|
8590
|
+
* up/down on whether a recalled memory was relevant; outcome is an
|
|
8591
|
+
* automated signal about whether the session that consumed the memory
|
|
8592
|
+
* ultimately succeeded or failed. Outcomes feed the Laplace-smoothed
|
|
8593
|
+
* worth score (`computeMemoryWorth`, PR 2) that PR 4 will use to
|
|
8594
|
+
* downweight memories correlated with bad sessions.
|
|
8595
|
+
*
|
|
8596
|
+
* The underlying writer only touches fact-category memories. Corrections,
|
|
8597
|
+
* procedures, and other kinds return `{ ok: false, reason:
|
|
8598
|
+
* "ineligible_category" }` so a ledger drainer doesn't need to pre-filter.
|
|
8599
|
+
*/
|
|
8600
|
+
async memoryOutcome(request) {
|
|
8601
|
+
if (request.memoryId.includes("/") || request.memoryId.includes("\\")) {
|
|
8602
|
+
throw new EngramAccessInputError(
|
|
8603
|
+
"memoryId must not contain path separators"
|
|
8604
|
+
);
|
|
8605
|
+
}
|
|
8606
|
+
const resolvedNs = this.writableNamespaceFor(
|
|
8607
|
+
request.namespace,
|
|
8608
|
+
request.sessionKey,
|
|
8609
|
+
request.principal
|
|
8610
|
+
);
|
|
8611
|
+
const storage = await this.orchestrator.getStorage(resolvedNs);
|
|
8612
|
+
return recordMemoryOutcome(storage, {
|
|
8613
|
+
memoryPath: `${request.memoryId}.md`,
|
|
8614
|
+
outcome: request.outcome,
|
|
8615
|
+
timestamp: request.timestamp
|
|
8616
|
+
});
|
|
8617
|
+
}
|
|
8618
|
+
async memoryPromote(request) {
|
|
8619
|
+
const resolvedNs = this.writableNamespaceFor(request.namespace, request.sessionKey, request.principal);
|
|
8620
|
+
const storage = await this.orchestrator.getStorage(resolvedNs);
|
|
8621
|
+
await storage.updateMemoryFrontmatter(request.memoryId, {
|
|
8622
|
+
lifecycleState: "active",
|
|
8623
|
+
updated: (/* @__PURE__ */ new Date()).toISOString()
|
|
8624
|
+
});
|
|
8625
|
+
return { promoted: true, memoryId: request.memoryId };
|
|
8626
|
+
}
|
|
8627
|
+
async memoryActionApply(request) {
|
|
8628
|
+
const actionTypes = /* @__PURE__ */ new Set([
|
|
8629
|
+
"store_episode",
|
|
8630
|
+
"store_note",
|
|
8631
|
+
"update_note",
|
|
8632
|
+
"create_artifact",
|
|
8633
|
+
"summarize_node",
|
|
8634
|
+
"discard",
|
|
8635
|
+
"link_graph"
|
|
8636
|
+
]);
|
|
8637
|
+
if (!actionTypes.has(request.action)) {
|
|
8638
|
+
throw new EngramAccessInputError(
|
|
8639
|
+
`memory_action_apply: invalid action ${JSON.stringify(request.action)}`
|
|
8640
|
+
);
|
|
8641
|
+
}
|
|
8642
|
+
if (resolveCompressionCapabilities(this.orchestrator.config).contextCompressionActions !== true) {
|
|
8643
|
+
throw new EngramAccessInputError(
|
|
8644
|
+
"memory_action_apply is disabled; enable contextCompressionActionsEnabled to use this tool"
|
|
8645
|
+
);
|
|
8646
|
+
}
|
|
8647
|
+
const outcome = request.outcome ?? "skipped";
|
|
8648
|
+
if (outcome !== "applied" && outcome !== "skipped" && outcome !== "failed") {
|
|
8649
|
+
throw new EngramAccessInputError(
|
|
8650
|
+
`memory_action_apply: outcome must be "applied", "skipped", or "failed"; got ${JSON.stringify(outcome)}`
|
|
8651
|
+
);
|
|
8652
|
+
}
|
|
8653
|
+
const resolvedNs = this.writableNamespaceFor(
|
|
8654
|
+
request.namespace,
|
|
8655
|
+
request.sessionKey,
|
|
8656
|
+
request.principal
|
|
8657
|
+
);
|
|
8658
|
+
const inputSummaryParts = [
|
|
8659
|
+
request.content,
|
|
8660
|
+
request.category ? `category=${request.category}` : void 0,
|
|
8661
|
+
request.linkTargetId ? `linkTargetId=${request.linkTargetId}` : void 0,
|
|
8662
|
+
request.linkType ? `linkType=${request.linkType}` : void 0,
|
|
8663
|
+
typeof request.linkStrength === "number" ? `linkStrength=${request.linkStrength}` : void 0,
|
|
8664
|
+
request.artifactType ? `artifactType=${request.artifactType}` : void 0,
|
|
8665
|
+
typeof request.execute === "boolean" ? `execute=${request.execute}` : void 0
|
|
8666
|
+
].filter((part) => typeof part === "string" && part.length > 0);
|
|
8667
|
+
const event = {
|
|
8668
|
+
action: request.action,
|
|
8669
|
+
outcome,
|
|
8670
|
+
namespace: resolvedNs,
|
|
8671
|
+
actor: "access.memory_action_apply",
|
|
8672
|
+
subsystem: "access.memory_action_apply",
|
|
8673
|
+
reason: request.reason,
|
|
8674
|
+
memoryId: request.memoryId,
|
|
8675
|
+
sourceSessionKey: request.sessionKey,
|
|
8676
|
+
inputSummary: inputSummaryParts.length > 0 ? inputSummaryParts.join(" | ").slice(0, 500) : void 0,
|
|
8677
|
+
dryRun: request.dryRun === true,
|
|
8678
|
+
promptHash: typeof request.sourcePrompt === "string" && request.sourcePrompt.length > 0 ? createHash4("sha256").update(request.sourcePrompt).digest("hex") : void 0
|
|
8679
|
+
};
|
|
8680
|
+
const preview = this.orchestrator.previewMemoryActionEvent(event);
|
|
8681
|
+
if (request.dryRun === true) {
|
|
8682
|
+
return { recorded: false, dryRun: true, event: preview };
|
|
8683
|
+
}
|
|
8684
|
+
const recorded = await this.orchestrator.appendMemoryActionEvent(event);
|
|
8685
|
+
return { recorded, event: preview };
|
|
8686
|
+
}
|
|
8687
|
+
async contextCheckpoint(request) {
|
|
8688
|
+
const resolvedNs = this.writableNamespaceFor(request.namespace, request.sessionKey, request.principal);
|
|
8689
|
+
const storage = await this.orchestrator.getStorage(resolvedNs);
|
|
8690
|
+
const storageDir = storage.dir;
|
|
8691
|
+
const { writeFile: writeFile4, mkdir: mkdir5 } = await import("fs/promises");
|
|
8692
|
+
const { join: join3, resolve: resolve3 } = await import("path");
|
|
8693
|
+
const safeKey = request.sessionKey.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
8694
|
+
if (!safeKey) throw new EngramAccessInputError("sessionKey is required");
|
|
8695
|
+
const checkpointDir = join3(storageDir, "checkpoints", safeKey);
|
|
8696
|
+
const resolved = resolve3(checkpointDir);
|
|
8697
|
+
if (!resolved.startsWith(resolve3(storageDir))) {
|
|
8698
|
+
throw new EngramAccessInputError("Invalid sessionKey");
|
|
8699
|
+
}
|
|
8700
|
+
await mkdir5(checkpointDir, { recursive: true });
|
|
8701
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
8702
|
+
const filePath = join3(checkpointDir, `checkpoint-${ts}.md`);
|
|
8703
|
+
await writeFile4(filePath, request.context, "utf-8");
|
|
8704
|
+
return { saved: true };
|
|
8705
|
+
}
|
|
8706
|
+
async lcmStatus() {
|
|
8707
|
+
if (!this.orchestrator.lcmEngine || !this.orchestrator.lcmEngine.enabled) {
|
|
8708
|
+
return {
|
|
8709
|
+
enabled: false,
|
|
8710
|
+
archiveAvailable: false
|
|
8711
|
+
};
|
|
8712
|
+
}
|
|
8713
|
+
const stats = await this.orchestrator.lcmEngine.getStats();
|
|
8714
|
+
return {
|
|
8715
|
+
enabled: true,
|
|
8716
|
+
archiveAvailable: true,
|
|
8717
|
+
stats: {
|
|
8718
|
+
totalTurns: stats.totalMessages
|
|
8719
|
+
}
|
|
8720
|
+
};
|
|
8721
|
+
}
|
|
8722
|
+
/**
|
|
8723
|
+
* Record citation usage from an observed oai-mem-citation block.
|
|
8724
|
+
* For each citation entry, extract the memory ID from the path and
|
|
8725
|
+
* increment its access tracking via the orchestrator. Returns the
|
|
8726
|
+
* count of submitted IDs and the count of IDs that matched real memories.
|
|
9252
8727
|
*/
|
|
9253
|
-
async
|
|
9254
|
-
|
|
8728
|
+
async recordCitationUsage(request) {
|
|
8729
|
+
if (request.entries.length === 0) return { submitted: 0, matched: 0 };
|
|
8730
|
+
const resolvedNamespace = this.writableNamespaceFor(
|
|
9255
8731
|
request.namespace,
|
|
9256
|
-
|
|
8732
|
+
request.sessionId,
|
|
8733
|
+
request.authenticatedPrincipal
|
|
9257
8734
|
);
|
|
9258
|
-
const
|
|
9259
|
-
const
|
|
9260
|
-
|
|
9261
|
-
|
|
9262
|
-
|
|
9263
|
-
|
|
9264
|
-
} catch {
|
|
9265
|
-
namespaceRootReal = nodePath.resolve(storage.dir);
|
|
9266
|
-
}
|
|
9267
|
-
const namespaceRootWithSep = namespaceRootReal.endsWith(nodePath.sep) ? namespaceRootReal : namespaceRootReal + nodePath.sep;
|
|
9268
|
-
const loadNode = async (relPath) => {
|
|
9269
|
-
if (nodePath.isAbsolute(relPath)) {
|
|
9270
|
-
log.warn(
|
|
9271
|
-
`graphSnapshot: rejected absolute edge endpoint (len=${relPath.length}) outside namespace root`
|
|
9272
|
-
);
|
|
9273
|
-
return null;
|
|
9274
|
-
}
|
|
9275
|
-
const candidate = nodePath.resolve(namespaceRootReal, relPath);
|
|
9276
|
-
if (candidate !== namespaceRootReal && !candidate.startsWith(namespaceRootWithSep)) {
|
|
9277
|
-
log.warn(
|
|
9278
|
-
`graphSnapshot: rejected traversing edge endpoint (len=${relPath.length}) outside namespace root`
|
|
9279
|
-
);
|
|
9280
|
-
return null;
|
|
8735
|
+
const memoryIds = [];
|
|
8736
|
+
for (const entry of request.entries) {
|
|
8737
|
+
const basename2 = entry.path.split("/").pop() ?? entry.path;
|
|
8738
|
+
const id = basename2.endsWith(".md") ? basename2.slice(0, -3) : basename2;
|
|
8739
|
+
if (id.length > 0) {
|
|
8740
|
+
memoryIds.push(id);
|
|
9281
8741
|
}
|
|
9282
|
-
|
|
8742
|
+
}
|
|
8743
|
+
if (memoryIds.length === 0) return { submitted: 0, matched: 0 };
|
|
8744
|
+
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
8745
|
+
const existingIds = await storage.filterExistingMemoryIds(memoryIds);
|
|
8746
|
+
const matchedIds = memoryIds.filter((id) => existingIds.has(id));
|
|
8747
|
+
if (matchedIds.length > 0) {
|
|
9283
8748
|
try {
|
|
9284
|
-
|
|
8749
|
+
this.orchestrator.trackMemoryAccess(matchedIds);
|
|
9285
8750
|
} catch {
|
|
9286
|
-
|
|
8751
|
+
log.debug("citation usage tracking: failed to record access for cited memories");
|
|
9287
8752
|
}
|
|
9288
|
-
|
|
9289
|
-
|
|
9290
|
-
|
|
8753
|
+
}
|
|
8754
|
+
return { submitted: memoryIds.length, matched: matchedIds.length };
|
|
8755
|
+
}
|
|
8756
|
+
// ── Operator Console state (issue #688 PR 2/3) ────────────────────────────
|
|
8757
|
+
/**
|
|
8758
|
+
* Gather a point-in-time `ConsoleStateSnapshot` from the orchestrator.
|
|
8759
|
+
*
|
|
8760
|
+
* Principal-aware: `resolveReadableNamespace` enforces ACL before the
|
|
8761
|
+
* snapshot is gathered, so callers cannot read a namespace they don't
|
|
8762
|
+
* have read access to (CLAUDE.md rule 42). The resolved namespace's
|
|
8763
|
+
* storage directory is forwarded as `config.memoryDir` so the ledger-
|
|
8764
|
+
* tail reader in `gatherConsoleState` scans the correct namespace root
|
|
8765
|
+
* rather than the global root. Read-only — never mutates orchestrator state.
|
|
8766
|
+
*/
|
|
8767
|
+
async consoleState(namespace, principal) {
|
|
8768
|
+
const resolvedNamespace = this.resolveReadableNamespace(namespace, principal);
|
|
8769
|
+
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
8770
|
+
const { gatherConsoleState } = await import("./state-ITXJKXRE.js");
|
|
8771
|
+
const orchestratorProxy = Object.create(this.orchestrator, {
|
|
8772
|
+
config: {
|
|
8773
|
+
value: { ...this.orchestrator.config, memoryDir: storage.dir },
|
|
8774
|
+
enumerable: true,
|
|
8775
|
+
configurable: true
|
|
8776
|
+
}
|
|
8777
|
+
});
|
|
8778
|
+
return gatherConsoleState(orchestratorProxy);
|
|
8779
|
+
}
|
|
8780
|
+
// ── Peer Registry surfaces (issue #679 PR 4/5) ────────────────────────────
|
|
8781
|
+
/**
|
|
8782
|
+
* List all registered peers. Returns the array of `Peer` objects in
|
|
8783
|
+
* deterministic alphabetical order (mirroring `listPeers` storage semantics).
|
|
8784
|
+
*/
|
|
8785
|
+
async peerList() {
|
|
8786
|
+
const { listPeers } = await import("./peers/index.js");
|
|
8787
|
+
const peers = await listPeers(this.orchestrator.config.memoryDir);
|
|
8788
|
+
return { peers };
|
|
8789
|
+
}
|
|
8790
|
+
/**
|
|
8791
|
+
* Get a single peer by id. Returns `{ found: false }` when the peer does
|
|
8792
|
+
* not exist rather than throwing, matching the `memoryGet` / `entityGet`
|
|
8793
|
+
* pattern used throughout the service.
|
|
8794
|
+
*/
|
|
8795
|
+
async peerGet(peerId) {
|
|
8796
|
+
const peers = await import("./peers/index.js");
|
|
8797
|
+
const validateId = peers.assertValidPeerId;
|
|
8798
|
+
try {
|
|
8799
|
+
validateId(peerId);
|
|
8800
|
+
} catch (err) {
|
|
8801
|
+
throw new EngramAccessInputError(err.message);
|
|
8802
|
+
}
|
|
8803
|
+
const peer = await peers.readPeer(this.orchestrator.config.memoryDir, peerId);
|
|
8804
|
+
if (!peer) return { found: false };
|
|
8805
|
+
return { found: true, peer };
|
|
8806
|
+
}
|
|
8807
|
+
/**
|
|
8808
|
+
* Upsert a peer. Writes `peers/{id}/identity.md`. On first write the
|
|
8809
|
+
* `createdAt` timestamp is set to now; on subsequent writes only
|
|
8810
|
+
* `displayName` and `notes` are mutated (kind and createdAt are immutable
|
|
8811
|
+
* once set, per the storage contract).
|
|
8812
|
+
*
|
|
8813
|
+
* Returns `{ created: true }` on first write, `{ created: false }` on update.
|
|
8814
|
+
*/
|
|
8815
|
+
async peerSet(input) {
|
|
8816
|
+
const peers = await import("./peers/index.js");
|
|
8817
|
+
const validateId = peers.assertValidPeerId;
|
|
8818
|
+
const { id } = input;
|
|
8819
|
+
try {
|
|
8820
|
+
validateId(id);
|
|
8821
|
+
} catch (err) {
|
|
8822
|
+
throw new EngramAccessInputError(err.message);
|
|
8823
|
+
}
|
|
8824
|
+
const memoryDir = this.orchestrator.config.memoryDir;
|
|
8825
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8826
|
+
const existing = await peers.readPeer(memoryDir, id);
|
|
8827
|
+
const ALLOWED_KINDS = /* @__PURE__ */ new Set(["self", "human", "agent", "integration"]);
|
|
8828
|
+
if (!existing) {
|
|
8829
|
+
const kind = input.kind ?? "human";
|
|
8830
|
+
if (!ALLOWED_KINDS.has(kind)) {
|
|
8831
|
+
throw new EngramAccessInputError(
|
|
8832
|
+
`peer kind must be one of ${[...ALLOWED_KINDS].join(", ")}`
|
|
9291
8833
|
);
|
|
9292
|
-
return null;
|
|
9293
8834
|
}
|
|
9294
|
-
const
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9299
|
-
|
|
9300
|
-
|
|
8835
|
+
const newPeer = {
|
|
8836
|
+
id,
|
|
8837
|
+
kind,
|
|
8838
|
+
displayName: input.displayName ?? id,
|
|
8839
|
+
createdAt: now,
|
|
8840
|
+
updatedAt: now,
|
|
8841
|
+
...typeof input.notes === "string" ? { notes: input.notes } : {}
|
|
9301
8842
|
};
|
|
8843
|
+
await peers.writePeer(memoryDir, newPeer);
|
|
8844
|
+
return { ok: true, created: true, peer: newPeer };
|
|
8845
|
+
}
|
|
8846
|
+
const updated = {
|
|
8847
|
+
id: existing.id,
|
|
8848
|
+
kind: existing.kind,
|
|
8849
|
+
createdAt: existing.createdAt,
|
|
8850
|
+
updatedAt: now,
|
|
8851
|
+
displayName: input.displayName !== void 0 ? input.displayName : existing.displayName,
|
|
8852
|
+
...input.notes !== void 0 ? { notes: input.notes } : existing.notes !== void 0 ? { notes: existing.notes } : {}
|
|
9302
8853
|
};
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
|
|
9311
|
-
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
|
|
9315
|
-
|
|
9316
|
-
|
|
8854
|
+
await peers.writePeer(memoryDir, updated);
|
|
8855
|
+
return { ok: true, created: false, peer: updated };
|
|
8856
|
+
}
|
|
8857
|
+
/**
|
|
8858
|
+
* Delete a peer by removing `peers/{id}/identity.md`. If the file does not
|
|
8859
|
+
* exist the call is a no-op (idempotent). The peer directory itself
|
|
8860
|
+
* (`peers/{id}/`) is intentionally left in place — profile and interaction
|
|
8861
|
+
* log data are not destroyed.
|
|
8862
|
+
*/
|
|
8863
|
+
async peerDelete(peerId) {
|
|
8864
|
+
const peers = await import("./peers/index.js");
|
|
8865
|
+
const validateId = peers.assertValidPeerId;
|
|
8866
|
+
try {
|
|
8867
|
+
validateId(peerId);
|
|
8868
|
+
} catch (err) {
|
|
8869
|
+
throw new EngramAccessInputError(err.message);
|
|
8870
|
+
}
|
|
8871
|
+
const deleted = await peers.deletePeer(this.orchestrator.config.memoryDir, peerId);
|
|
8872
|
+
return { ok: true, deleted };
|
|
8873
|
+
}
|
|
8874
|
+
/**
|
|
8875
|
+
* Destructively purge the entire peer directory for a given peerId —
|
|
8876
|
+
* `identity.md`, `profile.md`, `interactions.log.md`, and any other
|
|
8877
|
+
* files in `peers/{id}/`. Requires `confirm: "yes"` to prevent
|
|
8878
|
+
* accidental invocation.
|
|
8879
|
+
*
|
|
8880
|
+
* This is the DESTRUCTIVE counterpart to `peerDelete`, which only
|
|
8881
|
+
* removes `identity.md`. All companion files are permanently removed.
|
|
8882
|
+
*
|
|
8883
|
+
* Returns `{ ok: true, purged: true }` when the directory existed and
|
|
8884
|
+
* was removed; `{ ok: true, purged: false }` when the directory did
|
|
8885
|
+
* not exist (idempotent no-op).
|
|
8886
|
+
*/
|
|
8887
|
+
async peerForget(peerId, opts) {
|
|
8888
|
+
const peers = await import("./peers/index.js");
|
|
8889
|
+
const validateId = peers.assertValidPeerId;
|
|
8890
|
+
try {
|
|
8891
|
+
validateId(peerId);
|
|
8892
|
+
} catch (err) {
|
|
8893
|
+
throw new EngramAccessInputError(err.message);
|
|
8894
|
+
}
|
|
8895
|
+
if (opts.confirm !== "yes") {
|
|
8896
|
+
throw new EngramAccessInputError(
|
|
8897
|
+
"peerForget requires confirm: 'yes' to prevent accidental data loss"
|
|
8898
|
+
);
|
|
8899
|
+
}
|
|
8900
|
+
const result = await peers.forgetPeer(this.orchestrator.config.memoryDir, peerId, {
|
|
8901
|
+
confirm: "yes"
|
|
9317
8902
|
});
|
|
8903
|
+
return { ok: true, purged: result.purged };
|
|
9318
8904
|
}
|
|
9319
|
-
|
|
9320
|
-
|
|
9321
|
-
|
|
9322
|
-
|
|
9323
|
-
|
|
9324
|
-
|
|
9325
|
-
|
|
8905
|
+
/**
|
|
8906
|
+
* Get the evolving cognitive profile for a peer. Returns `{ found: false }`
|
|
8907
|
+
* when no profile file exists yet (profile is written by the async reasoner,
|
|
8908
|
+
* PR 2/5). The peer identity itself need not exist for a profile to exist,
|
|
8909
|
+
* but in practice the reasoner only writes profiles for registered peers.
|
|
8910
|
+
*/
|
|
8911
|
+
async peerProfileGet(peerId) {
|
|
8912
|
+
const peers = await import("./peers/index.js");
|
|
8913
|
+
const validateId = peers.assertValidPeerId;
|
|
8914
|
+
try {
|
|
8915
|
+
validateId(peerId);
|
|
8916
|
+
} catch (err) {
|
|
8917
|
+
throw new EngramAccessInputError(err.message);
|
|
9326
8918
|
}
|
|
9327
|
-
await this.orchestrator.
|
|
9328
|
-
|
|
9329
|
-
|
|
9330
|
-
|
|
9331
|
-
|
|
9332
|
-
|
|
8919
|
+
const profile = await peers.readPeerProfile(this.orchestrator.config.memoryDir, peerId);
|
|
8920
|
+
if (!profile) return { found: false };
|
|
8921
|
+
return { found: true, profile };
|
|
8922
|
+
}
|
|
8923
|
+
// ── Contradiction Review (issue #520) ──────────────────────────────────────
|
|
8924
|
+
get memoryDir() {
|
|
8925
|
+
return this.orchestrator.config.memoryDir;
|
|
9333
8926
|
}
|
|
9334
8927
|
/**
|
|
9335
|
-
*
|
|
8928
|
+
* Resolve the storage directory for a given namespace. Used by the SSE
|
|
8929
|
+
* graph-event handler to subscribe to the correct per-namespace bus rather
|
|
8930
|
+
* than the global root (CLAUDE.md rule 42 — read/write paths must resolve
|
|
8931
|
+
* through the same namespace layer).
|
|
9336
8932
|
*
|
|
9337
|
-
*
|
|
9338
|
-
*
|
|
9339
|
-
*
|
|
9340
|
-
*
|
|
9341
|
-
* worth score (`computeMemoryWorth`, PR 2) that PR 4 will use to
|
|
9342
|
-
* downweight memories correlated with bad sessions.
|
|
8933
|
+
* `principal` must be the transport-bound request principal (from
|
|
8934
|
+
* `resolveRequestPrincipal`). When namespaces are enabled, an absent
|
|
8935
|
+
* principal causes `resolveReadableNamespace` to throw an auth error,
|
|
8936
|
+
* matching the behaviour of every other authenticated read path.
|
|
9343
8937
|
*
|
|
9344
|
-
*
|
|
9345
|
-
*
|
|
9346
|
-
* "ineligible_category" }` so a ledger drainer doesn't need to pre-filter.
|
|
8938
|
+
* Falls back to `this.memoryDir` when namespaces are disabled or the
|
|
8939
|
+
* namespace is absent, matching the behaviour of every other read path.
|
|
9347
8940
|
*/
|
|
9348
|
-
async
|
|
9349
|
-
|
|
8941
|
+
async getMemoryDirForNamespace(namespace, principal) {
|
|
8942
|
+
const resolved = this.resolveReadableNamespace(namespace, principal);
|
|
8943
|
+
const storage = await this.orchestrator.getStorage(resolved);
|
|
8944
|
+
return storage.dir;
|
|
8945
|
+
}
|
|
8946
|
+
async getReadableStorageForNamespace(namespace, principal) {
|
|
8947
|
+
const resolved = this.resolveReadableNamespace(namespace, principal);
|
|
8948
|
+
const storage = await this.orchestrator.getStorage(resolved);
|
|
8949
|
+
return { namespace: resolved, storage };
|
|
8950
|
+
}
|
|
8951
|
+
async getWritableStorageForNamespace(namespace, principal) {
|
|
8952
|
+
if (resolveNamespaceCapabilities(this.orchestrator.config).namespaces && !principal?.trim()) {
|
|
9350
8953
|
throw new EngramAccessInputError(
|
|
9351
|
-
"
|
|
8954
|
+
"authentication required: namespaces are enabled and no principal was supplied"
|
|
9352
8955
|
);
|
|
9353
8956
|
}
|
|
9354
|
-
const
|
|
9355
|
-
|
|
9356
|
-
|
|
9357
|
-
request.principal
|
|
9358
|
-
);
|
|
9359
|
-
const storage = await this.orchestrator.getStorage(resolvedNs);
|
|
9360
|
-
return recordMemoryOutcome(storage, {
|
|
9361
|
-
memoryPath: `${request.memoryId}.md`,
|
|
9362
|
-
outcome: request.outcome,
|
|
9363
|
-
timestamp: request.timestamp
|
|
9364
|
-
});
|
|
8957
|
+
const resolved = this.writableNamespaceFor(namespace, void 0, principal);
|
|
8958
|
+
const storage = await this.orchestrator.getStorage(resolved);
|
|
8959
|
+
return { namespace: resolved, storage };
|
|
9365
8960
|
}
|
|
9366
|
-
|
|
9367
|
-
|
|
9368
|
-
|
|
9369
|
-
|
|
9370
|
-
|
|
9371
|
-
|
|
8961
|
+
get storageRef() {
|
|
8962
|
+
return this.orchestrator.storage;
|
|
8963
|
+
}
|
|
8964
|
+
// #1522: recordCatalogWrite removed — catalog touch handled at the storage chokepoint.
|
|
8965
|
+
get configRef() {
|
|
8966
|
+
return this.orchestrator.config;
|
|
8967
|
+
}
|
|
8968
|
+
get localLlmRef() {
|
|
8969
|
+
return this.orchestrator.localLlm ?? null;
|
|
8970
|
+
}
|
|
8971
|
+
get fallbackLlmRef() {
|
|
8972
|
+
return this.orchestrator.fastGatewayLlm ?? null;
|
|
8973
|
+
}
|
|
8974
|
+
get embeddingLookupFactoryRef() {
|
|
8975
|
+
return (storage) => {
|
|
8976
|
+
if (!resolveMemoryLifecycleCapabilities(this.orchestrator.config).embeddingFallback) return void 0;
|
|
8977
|
+
return async (content, limit) => {
|
|
8978
|
+
try {
|
|
8979
|
+
return await this.orchestrator.semanticDedupLookup(content, limit, storage);
|
|
8980
|
+
} catch {
|
|
8981
|
+
return [];
|
|
8982
|
+
}
|
|
8983
|
+
};
|
|
8984
|
+
};
|
|
8985
|
+
}
|
|
8986
|
+
/**
|
|
8987
|
+
* Import a capsule archive into the orchestrator's memory directory.
|
|
8988
|
+
*
|
|
8989
|
+
* Delegates directly to the standalone {@link importCapsuleFn} function.
|
|
8990
|
+
* The `root` parameter defaults to the orchestrator's `memoryDir` when
|
|
8991
|
+
* omitted, so callers that only have access to the service do not need to
|
|
8992
|
+
* thread the config value through.
|
|
8993
|
+
*
|
|
8994
|
+
* `versioning` defaults to the orchestrator's page-versioning config so
|
|
8995
|
+
* `mode: "overwrite"` automatically snapshots prior content without the
|
|
8996
|
+
* caller having to construct the config object.
|
|
8997
|
+
*/
|
|
8998
|
+
async capsuleImport(opts) {
|
|
8999
|
+
const { namespace, principal, root: explicitRoot, memoryDir: explicitMemoryDir, ...importOptions } = opts;
|
|
9000
|
+
const resolvedNamespace = this.writableNamespaceFor(namespace, void 0, principal);
|
|
9001
|
+
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
9002
|
+
const root = explicitRoot ?? storage.dir;
|
|
9003
|
+
const memoryDir = explicitMemoryDir ?? this.orchestrator.config.memoryDir;
|
|
9004
|
+
const versioning = importOptions.versioning ?? {
|
|
9005
|
+
enabled: resolveRecallAuxiliaryCapabilities(this.orchestrator.config).versioning,
|
|
9006
|
+
maxVersionsPerPage: this.orchestrator.config.versioningMaxPerPage,
|
|
9007
|
+
sidecarDir: this.orchestrator.config.versioningSidecarDir
|
|
9008
|
+
};
|
|
9009
|
+
await this.validateCapsuleImportArchivePath(importOptions.archivePath);
|
|
9010
|
+
try {
|
|
9011
|
+
return await importCapsule({ ...importOptions, root, memoryDir, versioning });
|
|
9012
|
+
} catch (err) {
|
|
9013
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
9014
|
+
if (this.isCapsuleImportArchiveInputError(err, message)) {
|
|
9015
|
+
throw new EngramAccessInputError(`capsule import failed: ${message}`);
|
|
9016
|
+
}
|
|
9017
|
+
throw err;
|
|
9018
|
+
}
|
|
9019
|
+
}
|
|
9020
|
+
async validateCapsuleImportArchivePath(archivePath) {
|
|
9021
|
+
let archiveStat;
|
|
9022
|
+
try {
|
|
9023
|
+
archiveStat = await stat2(archivePath);
|
|
9024
|
+
} catch (err) {
|
|
9025
|
+
if (!this.isCapsuleImportPathInputFsError(err)) throw err;
|
|
9026
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
9027
|
+
throw new EngramAccessInputError(`capsule import failed: archive is not readable: ${message}`);
|
|
9028
|
+
}
|
|
9029
|
+
if (!archiveStat.isFile()) {
|
|
9030
|
+
throw new EngramAccessInputError("capsule import failed: archivePath must point to a file");
|
|
9031
|
+
}
|
|
9032
|
+
try {
|
|
9033
|
+
await nodeFs.access(archivePath, fsConstants.R_OK);
|
|
9034
|
+
} catch (err) {
|
|
9035
|
+
if (!this.isCapsuleImportPathInputFsError(err)) throw err;
|
|
9036
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
9037
|
+
throw new EngramAccessInputError(`capsule import failed: archive is not readable: ${message}`);
|
|
9038
|
+
}
|
|
9039
|
+
}
|
|
9040
|
+
isCapsuleImportPathInputFsError(err) {
|
|
9041
|
+
const code = typeof err === "object" && err !== null && "code" in err ? err.code : void 0;
|
|
9042
|
+
return code === "ENOENT" || code === "ENOTDIR" || code === "EACCES" || code === "EPERM" || code === "ELOOP";
|
|
9043
|
+
}
|
|
9044
|
+
isCapsuleImportArchiveInputError(err, message) {
|
|
9045
|
+
if (err instanceof ZodError) return true;
|
|
9046
|
+
const code = typeof err === "object" && err !== null && "code" in err ? err.code : void 0;
|
|
9047
|
+
if (typeof code === "string" && code.startsWith("Z_")) return true;
|
|
9048
|
+
return message.startsWith("importCapsule: archive") || message.startsWith("importCapsule: bundle") || message.startsWith("importCapsule: manifest") || message.startsWith("importCapsule: record") || /incorrect header check|invalid stored block lengths|not in gzip format|unexpected end of file/i.test(message);
|
|
9049
|
+
}
|
|
9050
|
+
/**
|
|
9051
|
+
* Export a capsule archive from the orchestrator's memory directory.
|
|
9052
|
+
*
|
|
9053
|
+
* HTTP and future MCP surfaces use this rather than calling the transfer
|
|
9054
|
+
* helper directly so namespace ACL checks stay consistent with the archive
|
|
9055
|
+
* write side effect. The exporter still owns archive construction and
|
|
9056
|
+
* validation.
|
|
9057
|
+
*/
|
|
9058
|
+
async capsuleExport(opts) {
|
|
9059
|
+
const { namespace, principal, root: explicitRoot, memoryDir: explicitMemoryDir, ...exportOptions } = opts;
|
|
9060
|
+
const resolvedNamespace = this.writableNamespaceFor(namespace, void 0, principal);
|
|
9061
|
+
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
9062
|
+
const root = explicitRoot ?? storage.dir;
|
|
9063
|
+
const memoryDir = explicitMemoryDir ?? this.orchestrator.config.memoryDir;
|
|
9064
|
+
const pluginVersion = exportOptions.pluginVersion ?? await getPackageVersion();
|
|
9065
|
+
return exportCapsule({
|
|
9066
|
+
...exportOptions,
|
|
9067
|
+
pluginVersion,
|
|
9068
|
+
root,
|
|
9069
|
+
memoryDir: exportOptions.encrypt === true ? memoryDir : void 0
|
|
9372
9070
|
});
|
|
9373
|
-
return { promoted: true, memoryId: request.memoryId };
|
|
9374
9071
|
}
|
|
9375
|
-
|
|
9376
|
-
|
|
9377
|
-
|
|
9378
|
-
|
|
9379
|
-
|
|
9380
|
-
|
|
9381
|
-
|
|
9382
|
-
|
|
9383
|
-
|
|
9384
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
9387
|
-
|
|
9388
|
-
);
|
|
9072
|
+
/**
|
|
9073
|
+
* List capsule archives in the namespace-scoped capsule store.
|
|
9074
|
+
*
|
|
9075
|
+
* MCP uses this access-layer method instead of reading arbitrary paths so
|
|
9076
|
+
* capsule discovery remains bound to the same namespace ACLs as export and
|
|
9077
|
+
* import.
|
|
9078
|
+
*/
|
|
9079
|
+
async capsuleList(options) {
|
|
9080
|
+
const resolvedNamespace = this.resolveReadableNamespace(options?.namespace, options?.principal);
|
|
9081
|
+
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
9082
|
+
const capsulesDir = defaultCapsulesDir(storage.dir);
|
|
9083
|
+
let dirEntries;
|
|
9084
|
+
try {
|
|
9085
|
+
const capsulesDirStat = await nodeFs.lstat(capsulesDir);
|
|
9086
|
+
if (capsulesDirStat.isSymbolicLink()) {
|
|
9087
|
+
throw new EngramAccessInputError("capsule list failed: capsule store directory must not be a symlink");
|
|
9088
|
+
}
|
|
9089
|
+
if (!capsulesDirStat.isDirectory()) {
|
|
9090
|
+
throw new EngramAccessInputError("capsule list failed: capsule store path must be a directory");
|
|
9091
|
+
}
|
|
9092
|
+
dirEntries = await nodeFs.readdir(capsulesDir, { withFileTypes: true });
|
|
9093
|
+
} catch (err) {
|
|
9094
|
+
const code = typeof err === "object" && err !== null && "code" in err ? err.code : void 0;
|
|
9095
|
+
if (code === "ENOENT") {
|
|
9096
|
+
return { namespace: resolvedNamespace, capsulesDir, capsules: [] };
|
|
9097
|
+
}
|
|
9098
|
+
throw err;
|
|
9389
9099
|
}
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
|
|
9393
|
-
|
|
9100
|
+
const archiveNames = dirEntries.filter(
|
|
9101
|
+
(entry) => entry.isFile() && (entry.name.endsWith(".capsule.json.gz") || entry.name.endsWith(".capsule.json.gz.enc"))
|
|
9102
|
+
).map((entry) => entry.name).sort();
|
|
9103
|
+
const capsules = [];
|
|
9104
|
+
for (const archiveName of archiveNames) {
|
|
9105
|
+
const archivePath = nodePath.join(capsulesDir, archiveName);
|
|
9106
|
+
const id = archiveName.replace(/\.capsule\.json\.gz\.enc$/, "").replace(/\.capsule\.json\.gz$/, "");
|
|
9107
|
+
const manifestPath = nodePath.join(capsulesDir, `${id}.manifest.json`);
|
|
9108
|
+
let createdAt = null;
|
|
9109
|
+
let pluginVersion = null;
|
|
9110
|
+
let fileCount = null;
|
|
9111
|
+
let description = null;
|
|
9112
|
+
let manifestPathOrNull = manifestPath;
|
|
9113
|
+
try {
|
|
9114
|
+
const manifestStat = await nodeFs.lstat(manifestPath);
|
|
9115
|
+
if (manifestStat.isSymbolicLink() || !manifestStat.isFile()) {
|
|
9116
|
+
capsules.push({
|
|
9117
|
+
id,
|
|
9118
|
+
archivePath,
|
|
9119
|
+
manifestPath: manifestPathOrNull,
|
|
9120
|
+
createdAt,
|
|
9121
|
+
pluginVersion,
|
|
9122
|
+
fileCount,
|
|
9123
|
+
description
|
|
9124
|
+
});
|
|
9125
|
+
continue;
|
|
9126
|
+
}
|
|
9127
|
+
const raw = await nodeFs.readFile(manifestPath, "utf-8");
|
|
9128
|
+
const sidecar = JSON.parse(raw);
|
|
9129
|
+
createdAt = typeof sidecar.createdAt === "string" ? sidecar.createdAt : null;
|
|
9130
|
+
pluginVersion = typeof sidecar.pluginVersion === "string" ? sidecar.pluginVersion : null;
|
|
9131
|
+
fileCount = Array.isArray(sidecar.files) ? sidecar.files.length : null;
|
|
9132
|
+
const capsule = sidecar.capsule;
|
|
9133
|
+
description = capsule && typeof capsule.description === "string" ? capsule.description : null;
|
|
9134
|
+
} catch (err) {
|
|
9135
|
+
const code = typeof err === "object" && err !== null && "code" in err ? err.code : void 0;
|
|
9136
|
+
if (code === "ENOENT") {
|
|
9137
|
+
manifestPathOrNull = null;
|
|
9138
|
+
}
|
|
9139
|
+
}
|
|
9140
|
+
capsules.push({
|
|
9141
|
+
id,
|
|
9142
|
+
archivePath,
|
|
9143
|
+
manifestPath: manifestPathOrNull,
|
|
9144
|
+
createdAt,
|
|
9145
|
+
pluginVersion,
|
|
9146
|
+
fileCount,
|
|
9147
|
+
description
|
|
9148
|
+
});
|
|
9394
9149
|
}
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9150
|
+
return { namespace: resolvedNamespace, capsulesDir, capsules };
|
|
9151
|
+
}
|
|
9152
|
+
/**
|
|
9153
|
+
* Operator-configured offline-sync excludes (#1786), compiled once per
|
|
9154
|
+
* service instance. Applied to push-side (snapshot/read) enumeration
|
|
9155
|
+
* only — apply-side stays permissive so remote-authoritative runtime
|
|
9156
|
+
* files keep flowing in.
|
|
9157
|
+
*/
|
|
9158
|
+
_offlineSyncUserExcludes = null;
|
|
9159
|
+
get offlineSyncUserExcludes() {
|
|
9160
|
+
if (!this._offlineSyncUserExcludes) {
|
|
9161
|
+
this._offlineSyncUserExcludes = compileOfflineSyncExcludeGlobs(
|
|
9162
|
+
this.orchestrator.config.offlineSyncExcludes ?? []
|
|
9399
9163
|
);
|
|
9400
9164
|
}
|
|
9401
|
-
|
|
9402
|
-
|
|
9403
|
-
|
|
9404
|
-
|
|
9405
|
-
);
|
|
9406
|
-
const
|
|
9407
|
-
|
|
9408
|
-
|
|
9409
|
-
|
|
9410
|
-
|
|
9411
|
-
|
|
9412
|
-
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
|
|
9416
|
-
|
|
9417
|
-
|
|
9418
|
-
|
|
9419
|
-
|
|
9420
|
-
|
|
9421
|
-
|
|
9422
|
-
memoryId: request.memoryId,
|
|
9423
|
-
sourceSessionKey: request.sessionKey,
|
|
9424
|
-
inputSummary: inputSummaryParts.length > 0 ? inputSummaryParts.join(" | ").slice(0, 500) : void 0,
|
|
9425
|
-
dryRun: request.dryRun === true,
|
|
9426
|
-
promptHash: typeof request.sourcePrompt === "string" && request.sourcePrompt.length > 0 ? createHash4("sha256").update(request.sourcePrompt).digest("hex") : void 0
|
|
9165
|
+
return this._offlineSyncUserExcludes;
|
|
9166
|
+
}
|
|
9167
|
+
async offlineSyncSnapshot(options = {}) {
|
|
9168
|
+
const resolvedNamespace = this.resolveReadableNamespace(options.namespace, options.principal);
|
|
9169
|
+
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
9170
|
+
const storageHash = createHash4("sha256").update(storage.dir).digest("hex").slice(0, 16);
|
|
9171
|
+
const snapshotBuilder = options.includeContent === false && options.baseFiles && options.baseFiles.length > 0 ? buildOfflineSyncSnapshotFromBase : buildOfflineSyncSnapshot;
|
|
9172
|
+
const snapshot = await snapshotBuilder({
|
|
9173
|
+
root: storage.dir,
|
|
9174
|
+
sourceId: `remnic:${resolvedNamespace}:${storageHash}`,
|
|
9175
|
+
...options.baseFiles && options.baseFiles.length > 0 ? { baseFiles: options.baseFiles } : {},
|
|
9176
|
+
includeContent: options.includeContent !== false,
|
|
9177
|
+
includeTranscripts: options.includeTranscripts !== false,
|
|
9178
|
+
readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
|
|
9179
|
+
readFileDigest: async ({ filePath }) => storage.digestOfflineSyncFile(filePath),
|
|
9180
|
+
signal: options.signal,
|
|
9181
|
+
userExcludeRegexps: this.offlineSyncUserExcludes
|
|
9182
|
+
});
|
|
9183
|
+
return {
|
|
9184
|
+
namespace: resolvedNamespace,
|
|
9185
|
+
...snapshot
|
|
9427
9186
|
};
|
|
9428
|
-
const preview = this.orchestrator.previewMemoryActionEvent(event);
|
|
9429
|
-
if (request.dryRun === true) {
|
|
9430
|
-
return { recorded: false, dryRun: true, event: preview };
|
|
9431
|
-
}
|
|
9432
|
-
const recorded = await this.orchestrator.appendMemoryActionEvent(event);
|
|
9433
|
-
return { recorded, event: preview };
|
|
9434
9187
|
}
|
|
9435
|
-
async
|
|
9436
|
-
const
|
|
9437
|
-
const storage = await this.orchestrator.getStorage(
|
|
9438
|
-
const
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
|
|
9188
|
+
async offlineSyncSnapshotStream(options = {}) {
|
|
9189
|
+
const resolvedNamespace = this.resolveReadableNamespace(options.namespace, options.principal);
|
|
9190
|
+
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
9191
|
+
const storageHash = createHash4("sha256").update(storage.dir).digest("hex").slice(0, 16);
|
|
9192
|
+
return {
|
|
9193
|
+
namespace: resolvedNamespace,
|
|
9194
|
+
format: OFFLINE_SYNC_SNAPSHOT_FORMAT,
|
|
9195
|
+
schemaVersion: 1,
|
|
9196
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9197
|
+
sourceId: `remnic:${resolvedNamespace}:${storageHash}`,
|
|
9198
|
+
includeTranscripts: options.includeTranscripts !== false,
|
|
9199
|
+
files: iterateOfflineSyncSnapshotFileRecords({
|
|
9200
|
+
root: storage.dir,
|
|
9201
|
+
includeContent: options.includeContent === true,
|
|
9202
|
+
includeTranscripts: options.includeTranscripts !== false,
|
|
9203
|
+
readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
|
|
9204
|
+
readFileDigest: async ({ filePath }) => storage.digestOfflineSyncFile(filePath),
|
|
9205
|
+
signal: options.signal,
|
|
9206
|
+
userExcludeRegexps: this.offlineSyncUserExcludes
|
|
9207
|
+
})
|
|
9208
|
+
};
|
|
9453
9209
|
}
|
|
9454
|
-
async
|
|
9455
|
-
|
|
9210
|
+
async offlineSyncFiles(options) {
|
|
9211
|
+
const resolvedNamespace = this.resolveReadableNamespace(options.namespace, options.principal);
|
|
9212
|
+
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
9213
|
+
const storageHash = createHash4("sha256").update(storage.dir).digest("hex").slice(0, 16);
|
|
9214
|
+
try {
|
|
9215
|
+
const snapshot = await buildOfflineSyncSnapshotForPaths({
|
|
9216
|
+
root: storage.dir,
|
|
9217
|
+
sourceId: `remnic:${resolvedNamespace}:${storageHash}`,
|
|
9218
|
+
paths: options.paths,
|
|
9219
|
+
includeContent: true,
|
|
9220
|
+
includeTranscripts: options.includeTranscripts !== false,
|
|
9221
|
+
readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
|
|
9222
|
+
userExcludeRegexps: this.offlineSyncUserExcludes
|
|
9223
|
+
});
|
|
9456
9224
|
return {
|
|
9457
|
-
|
|
9458
|
-
|
|
9225
|
+
namespace: resolvedNamespace,
|
|
9226
|
+
...snapshot
|
|
9459
9227
|
};
|
|
9228
|
+
} catch (error) {
|
|
9229
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
9230
|
+
if (message.startsWith("paths[]:") || message.startsWith("buildOfflineSyncSnapshotForPaths: record path ") || message.startsWith("offline sync snapshot path is excluded:")) {
|
|
9231
|
+
throw new EngramAccessInputError(message);
|
|
9232
|
+
}
|
|
9233
|
+
throw error;
|
|
9460
9234
|
}
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
|
|
9464
|
-
|
|
9465
|
-
|
|
9466
|
-
|
|
9235
|
+
}
|
|
9236
|
+
async offlineSyncFileContent(options) {
|
|
9237
|
+
const resolvedNamespace = this.resolveReadableNamespace(options.namespace, options.principal);
|
|
9238
|
+
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
9239
|
+
try {
|
|
9240
|
+
const chunk = await readOfflineSyncFileContentChunk({
|
|
9241
|
+
root: storage.dir,
|
|
9242
|
+
path: options.path,
|
|
9243
|
+
offset: options.offset,
|
|
9244
|
+
length: options.length,
|
|
9245
|
+
includeTranscripts: options.includeTranscripts !== false,
|
|
9246
|
+
readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
|
|
9247
|
+
userExcludeRegexps: this.offlineSyncUserExcludes
|
|
9248
|
+
});
|
|
9249
|
+
return {
|
|
9250
|
+
namespace: resolvedNamespace,
|
|
9251
|
+
...chunk
|
|
9252
|
+
};
|
|
9253
|
+
} catch (error) {
|
|
9254
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
9255
|
+
if (message.startsWith("path:") || message.startsWith("offset ") || message.startsWith("offset must ") || message.startsWith("length ") || message.startsWith("offline sync file content ")) {
|
|
9256
|
+
throw new EngramAccessInputError(message);
|
|
9467
9257
|
}
|
|
9468
|
-
|
|
9258
|
+
throw error;
|
|
9259
|
+
}
|
|
9469
9260
|
}
|
|
9470
|
-
|
|
9471
|
-
* Record citation usage from an observed oai-mem-citation block.
|
|
9472
|
-
* For each citation entry, extract the memory ID from the path and
|
|
9473
|
-
* increment its access tracking via the orchestrator. Returns the
|
|
9474
|
-
* count of submitted IDs and the count of IDs that matched real memories.
|
|
9475
|
-
*/
|
|
9476
|
-
async recordCitationUsage(request) {
|
|
9477
|
-
if (request.entries.length === 0) return { submitted: 0, matched: 0 };
|
|
9261
|
+
async offlineSyncApplyFileContent(options) {
|
|
9478
9262
|
const resolvedNamespace = this.writableNamespaceFor(
|
|
9479
|
-
|
|
9480
|
-
|
|
9481
|
-
|
|
9263
|
+
options.namespace,
|
|
9264
|
+
void 0,
|
|
9265
|
+
options.principal
|
|
9482
9266
|
);
|
|
9483
|
-
const
|
|
9484
|
-
|
|
9485
|
-
const
|
|
9486
|
-
|
|
9487
|
-
|
|
9488
|
-
|
|
9267
|
+
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
9268
|
+
try {
|
|
9269
|
+
const result = await applyOfflineSyncFileContentChunk({
|
|
9270
|
+
root: storage.dir,
|
|
9271
|
+
sourceId: options.sourceId,
|
|
9272
|
+
path: options.path,
|
|
9273
|
+
sha256: options.sha256,
|
|
9274
|
+
bytes: options.bytes,
|
|
9275
|
+
mtimeMs: options.mtimeMs,
|
|
9276
|
+
offset: options.offset,
|
|
9277
|
+
baseSha256: options.baseSha256,
|
|
9278
|
+
content: options.content,
|
|
9279
|
+
includeTranscripts: options.includeTranscripts !== false,
|
|
9280
|
+
readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
|
|
9281
|
+
readFileDigest: async ({ filePath }) => storage.digestOfflineSyncFile(filePath),
|
|
9282
|
+
writeFile: async ({ filePath, content }) => storage.writeOfflineSyncFile(filePath, content),
|
|
9283
|
+
writeStagingFile: async ({ filePath, content }) => storage.writeOfflineSyncStagingFile(filePath, content),
|
|
9284
|
+
writeFileChunks: async ({ filePath, chunks }) => storage.writeOfflineSyncFileChunks(filePath, chunks)
|
|
9285
|
+
});
|
|
9286
|
+
return {
|
|
9287
|
+
namespace: resolvedNamespace,
|
|
9288
|
+
...result
|
|
9289
|
+
};
|
|
9290
|
+
} catch (error) {
|
|
9291
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
9292
|
+
if (message.startsWith("offline sync") || message.startsWith("path:") || message.startsWith("sourceId must ") || message.startsWith("sha256 must ") || message.startsWith("baseSha256 must ") || message.startsWith("bytes must ") || message.startsWith("mtimeMs must ") || message.startsWith("offset must ") || message.startsWith("content chunk ") || message === "content must be a Buffer") {
|
|
9293
|
+
throw new EngramAccessInputError(message);
|
|
9489
9294
|
}
|
|
9295
|
+
throw error;
|
|
9490
9296
|
}
|
|
9491
|
-
|
|
9297
|
+
}
|
|
9298
|
+
async offlineSyncApply(options) {
|
|
9299
|
+
const resolvedNamespace = this.writableNamespaceFor(
|
|
9300
|
+
options.namespace,
|
|
9301
|
+
void 0,
|
|
9302
|
+
options.principal
|
|
9303
|
+
);
|
|
9492
9304
|
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
9493
|
-
|
|
9494
|
-
|
|
9495
|
-
|
|
9496
|
-
|
|
9497
|
-
|
|
9498
|
-
|
|
9499
|
-
|
|
9305
|
+
try {
|
|
9306
|
+
const result = await applyOfflineSyncChangeset({
|
|
9307
|
+
root: storage.dir,
|
|
9308
|
+
changeset: options.changeset,
|
|
9309
|
+
returnCurrentFiles: options.returnCurrentFiles,
|
|
9310
|
+
readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
|
|
9311
|
+
readFileDigest: async ({ filePath }) => storage.digestOfflineSyncFile(filePath),
|
|
9312
|
+
writeFile: async ({ filePath, content }) => storage.writeOfflineSyncFile(filePath, content),
|
|
9313
|
+
deleteFile: async ({ filePath }) => storage.deleteOfflineSyncFile(filePath)
|
|
9314
|
+
});
|
|
9315
|
+
return {
|
|
9316
|
+
namespace: resolvedNamespace,
|
|
9317
|
+
...result
|
|
9318
|
+
};
|
|
9319
|
+
} catch (error) {
|
|
9320
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
9321
|
+
if (message.startsWith("offline sync")) {
|
|
9322
|
+
throw new EngramAccessInputError(message);
|
|
9500
9323
|
}
|
|
9324
|
+
throw error;
|
|
9501
9325
|
}
|
|
9502
|
-
return { submitted: memoryIds.length, matched: matchedIds.length };
|
|
9503
9326
|
}
|
|
9504
|
-
// ──
|
|
9327
|
+
// ── Dreams pipeline telemetry surfaces (issue #678 PR 3+4) ──────────────
|
|
9505
9328
|
/**
|
|
9506
|
-
*
|
|
9329
|
+
* Return per-phase Dreams telemetry for the last N hours (default 24).
|
|
9330
|
+
*/
|
|
9331
|
+
async dreamsStatus(options) {
|
|
9332
|
+
const { getDreamsStatus, normalizeDreamsStatusWindowHours } = await import("./dreams-ledger-6UODQOX6.js");
|
|
9333
|
+
let windowHours;
|
|
9334
|
+
try {
|
|
9335
|
+
windowHours = normalizeDreamsStatusWindowHours(options?.windowHours);
|
|
9336
|
+
} catch (error) {
|
|
9337
|
+
throw new EngramAccessInputError(error instanceof Error ? error.message : String(error));
|
|
9338
|
+
}
|
|
9339
|
+
const resolvedNamespace = this.resolveReadableNamespace(options?.namespace, options?.principal);
|
|
9340
|
+
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
9341
|
+
return getDreamsStatus(storage.dir, windowHours);
|
|
9342
|
+
}
|
|
9343
|
+
/**
|
|
9344
|
+
* Manually invoke a single Dreams phase pass (PR 4/4).
|
|
9507
9345
|
*
|
|
9508
|
-
*
|
|
9509
|
-
*
|
|
9510
|
-
*
|
|
9511
|
-
* storage directory is forwarded as `config.memoryDir` so the ledger-
|
|
9512
|
-
* tail reader in `gatherConsoleState` scans the correct namespace root
|
|
9513
|
-
* rather than the global root. Read-only — never mutates orchestrator state.
|
|
9346
|
+
* Deep-sleep delegates to memory governance (shadow → dry-run, apply → live).
|
|
9347
|
+
* Light-sleep and REM scan the observation ledger and memory corpus
|
|
9348
|
+
* respectively, returning the same telemetry shape as a scheduled run.
|
|
9514
9349
|
*/
|
|
9515
|
-
async
|
|
9516
|
-
const
|
|
9350
|
+
async dreamsRun(options) {
|
|
9351
|
+
const { runDreamsPhase } = await import("./dreams-ledger-6UODQOX6.js");
|
|
9352
|
+
const validPhases = ["lightSleep", "rem", "deepSleep"];
|
|
9353
|
+
if (!validPhases.includes(options.phase)) {
|
|
9354
|
+
throw new EngramAccessInputError(
|
|
9355
|
+
`Invalid phase: ${String(options.phase)}. Must be one of: ${validPhases.join(", ")}`
|
|
9356
|
+
);
|
|
9357
|
+
}
|
|
9358
|
+
const deepSleep = this.orchestrator.config.dreamsPhases.deepSleep;
|
|
9359
|
+
if (options.phase === "deepSleep" && deepSleep.enabled === false && deepSleep.enabledExplicitlySet === true) {
|
|
9360
|
+
throw new EngramAccessInputError(
|
|
9361
|
+
"memory governance is disabled by dreams.phases.deepSleep.enabled=false"
|
|
9362
|
+
);
|
|
9363
|
+
}
|
|
9364
|
+
const dryRun = options.dryRun === true;
|
|
9365
|
+
const resolvedNamespace = this.writableNamespaceFor(
|
|
9366
|
+
options.namespace,
|
|
9367
|
+
void 0,
|
|
9368
|
+
options.authenticatedPrincipal
|
|
9369
|
+
);
|
|
9517
9370
|
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
9518
|
-
const
|
|
9519
|
-
const
|
|
9520
|
-
|
|
9521
|
-
|
|
9522
|
-
|
|
9523
|
-
|
|
9371
|
+
const memoryDir = storage.dir;
|
|
9372
|
+
const phaseRunner = dryRun || options.phase === "deepSleep" ? void 0 : async (_opts) => {
|
|
9373
|
+
if (_opts.phase === "lightSleep") {
|
|
9374
|
+
const result3 = await this.orchestrator.runLifecyclePolicyNow(storage);
|
|
9375
|
+
return {
|
|
9376
|
+
itemsProcessed: result3.memoriesAssessed,
|
|
9377
|
+
notes: `scored ${result3.memoriesAssessed} memories`
|
|
9378
|
+
};
|
|
9524
9379
|
}
|
|
9380
|
+
const result2 = await this.orchestrator.runSemanticConsolidationNow({
|
|
9381
|
+
dryRun: false,
|
|
9382
|
+
storage
|
|
9383
|
+
});
|
|
9384
|
+
const itemsProcessed = result2.clusters.reduce(
|
|
9385
|
+
(sum, cluster) => sum + cluster.memories.length,
|
|
9386
|
+
0
|
|
9387
|
+
);
|
|
9388
|
+
return {
|
|
9389
|
+
itemsProcessed,
|
|
9390
|
+
notes: `REM consolidation found ${result2.clustersFound} clusters`
|
|
9391
|
+
};
|
|
9392
|
+
};
|
|
9393
|
+
const governanceRunner = options.phase === "deepSleep" ? async (_opts) => {
|
|
9394
|
+
return this.orchestrator.runDeepSleepGovernanceNow({
|
|
9395
|
+
storage,
|
|
9396
|
+
dryRun: _opts.dryRun
|
|
9397
|
+
});
|
|
9398
|
+
} : void 0;
|
|
9399
|
+
const result = await runDreamsPhase(
|
|
9400
|
+
{ memoryDir, phase: options.phase, dryRun },
|
|
9401
|
+
governanceRunner,
|
|
9402
|
+
phaseRunner
|
|
9403
|
+
);
|
|
9404
|
+
return {
|
|
9405
|
+
phase: result.phase,
|
|
9406
|
+
dryRun: result.dryRun,
|
|
9407
|
+
durationMs: result.durationMs,
|
|
9408
|
+
itemsProcessed: result.itemsProcessed,
|
|
9409
|
+
notes: result.notes
|
|
9410
|
+
};
|
|
9411
|
+
}
|
|
9412
|
+
// ---------------------------------------------------------------------------
|
|
9413
|
+
// Wearables (Limitless / Bee / Omi transcript ingestion)
|
|
9414
|
+
//
|
|
9415
|
+
// Thin delegations to the orchestrator-owned WearablesService — the
|
|
9416
|
+
// same instance behind the CLI, so HTTP/MCP callers observe identical
|
|
9417
|
+
// behavior and validation (renderer-sharing rule).
|
|
9418
|
+
// ---------------------------------------------------------------------------
|
|
9419
|
+
async wearablesStatus() {
|
|
9420
|
+
return this.orchestrator.getWearablesService().status();
|
|
9421
|
+
}
|
|
9422
|
+
async wearablesSync(request) {
|
|
9423
|
+
return this.orchestrator.getWearablesService().sync({
|
|
9424
|
+
source: request.source,
|
|
9425
|
+
date: request.date,
|
|
9426
|
+
days: request.days,
|
|
9427
|
+
forceMemories: request.forceMemories
|
|
9525
9428
|
});
|
|
9526
|
-
return gatherConsoleState(orchestratorProxy);
|
|
9527
9429
|
}
|
|
9528
|
-
|
|
9430
|
+
async wearablesTranscriptDay(request) {
|
|
9431
|
+
return this.orchestrator.getWearablesService().dayTranscript(request.date, request.source);
|
|
9432
|
+
}
|
|
9433
|
+
async wearablesTranscriptSearch(request) {
|
|
9434
|
+
return this.orchestrator.getWearablesService().searchTranscripts(request.query, {
|
|
9435
|
+
source: request.source,
|
|
9436
|
+
from: request.from,
|
|
9437
|
+
to: request.to,
|
|
9438
|
+
limit: request.limit
|
|
9439
|
+
});
|
|
9440
|
+
}
|
|
9441
|
+
async wearablesTranscriptMemories(request) {
|
|
9442
|
+
return this.orchestrator.getWearablesService().transcriptMemories({
|
|
9443
|
+
source: request.source,
|
|
9444
|
+
date: request.date,
|
|
9445
|
+
limit: request.limit
|
|
9446
|
+
});
|
|
9447
|
+
}
|
|
9448
|
+
// ── Admin console surfaces (issue #1502) ────────────────────────────────
|
|
9449
|
+
//
|
|
9450
|
+
// Thin delegators. Every method threads `this.orchestrator` references
|
|
9451
|
+
// into the pure `admin/admin-surfaces.ts` functions so the dashboard
|
|
9452
|
+
// never re-resolves scope, re-derives promotion targets, or re-lists
|
|
9453
|
+
// namespaces.
|
|
9529
9454
|
/**
|
|
9530
|
-
*
|
|
9531
|
-
*
|
|
9455
|
+
* Resolve an effective scope inspection for the console. Inputs are
|
|
9456
|
+
* redacted before return so an operator-supplied principal preview /
|
|
9457
|
+
* namespace override never echoes a pasted credential back.
|
|
9532
9458
|
*/
|
|
9533
|
-
async
|
|
9534
|
-
const
|
|
9535
|
-
const
|
|
9536
|
-
|
|
9459
|
+
async adminInspectScope(options) {
|
|
9460
|
+
const config = this.orchestrator.config;
|
|
9461
|
+
const codingContext = options.sessionKey ? this.orchestrator.getCodingContextForSession(options.sessionKey) ?? null : null;
|
|
9462
|
+
const inspection = inspectScope({
|
|
9463
|
+
config,
|
|
9464
|
+
sessionKey: options.sessionKey,
|
|
9465
|
+
namespace: options.namespace,
|
|
9466
|
+
principalOverride: options.principalOverride,
|
|
9467
|
+
codingContext,
|
|
9468
|
+
operation: options.operation
|
|
9469
|
+
});
|
|
9470
|
+
return redactSensitive(inspection);
|
|
9537
9471
|
}
|
|
9538
9472
|
/**
|
|
9539
|
-
*
|
|
9540
|
-
*
|
|
9541
|
-
* pattern used throughout the service.
|
|
9473
|
+
* List configured + discovered namespaces from the catalog with the
|
|
9474
|
+
* admin filters the console exposes.
|
|
9542
9475
|
*/
|
|
9543
|
-
async
|
|
9544
|
-
const
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
throw new EngramAccessInputError(err.message);
|
|
9550
|
-
}
|
|
9551
|
-
const peer = await peers.readPeer(this.orchestrator.config.memoryDir, peerId);
|
|
9552
|
-
if (!peer) return { found: false };
|
|
9553
|
-
return { found: true, peer };
|
|
9476
|
+
async adminListNamespaces(filter) {
|
|
9477
|
+
const result = await listAdminNamespaces({
|
|
9478
|
+
catalog: this.orchestrator.namespaceCatalog,
|
|
9479
|
+
filter
|
|
9480
|
+
});
|
|
9481
|
+
return redactSensitive(result);
|
|
9554
9482
|
}
|
|
9555
9483
|
/**
|
|
9556
|
-
*
|
|
9557
|
-
*
|
|
9558
|
-
* `displayName` and `notes` are mutated (kind and createdAt are immutable
|
|
9559
|
-
* once set, per the storage contract).
|
|
9560
|
-
*
|
|
9561
|
-
* Returns `{ created: true }` on first write, `{ created: false }` on update.
|
|
9484
|
+
* Per-namespace maintenance + QMD health. QMD diagnostics are probed via
|
|
9485
|
+
* the orchestrator's search health accessor (one probe per namespace).
|
|
9562
9486
|
*/
|
|
9563
|
-
async
|
|
9564
|
-
const
|
|
9565
|
-
|
|
9566
|
-
|
|
9567
|
-
|
|
9568
|
-
|
|
9569
|
-
|
|
9570
|
-
|
|
9571
|
-
|
|
9572
|
-
|
|
9573
|
-
|
|
9574
|
-
|
|
9575
|
-
|
|
9576
|
-
|
|
9577
|
-
|
|
9578
|
-
|
|
9579
|
-
|
|
9580
|
-
|
|
9581
|
-
)
|
|
9487
|
+
async adminMaintenanceHealth() {
|
|
9488
|
+
const report = await gatherMaintenanceHealth({
|
|
9489
|
+
catalog: this.orchestrator.namespaceCatalog,
|
|
9490
|
+
qmdHealthProvider: async (namespace) => {
|
|
9491
|
+
try {
|
|
9492
|
+
const health = await this.orchestrator.searchHealthForNamespace(namespace);
|
|
9493
|
+
return {
|
|
9494
|
+
namespace,
|
|
9495
|
+
collection: health.collection,
|
|
9496
|
+
available: health.available,
|
|
9497
|
+
collectionState: health.collectionState,
|
|
9498
|
+
debugStatus: health.debugStatus,
|
|
9499
|
+
installedVersion: health.installedVersion,
|
|
9500
|
+
supportedVersion: health.supportedVersion,
|
|
9501
|
+
supported: health.supported,
|
|
9502
|
+
upgradeAvailable: health.upgradeAvailable,
|
|
9503
|
+
daemonMode: health.daemonMode
|
|
9504
|
+
};
|
|
9505
|
+
} catch (err) {
|
|
9506
|
+
throw err;
|
|
9507
|
+
}
|
|
9582
9508
|
}
|
|
9583
|
-
|
|
9584
|
-
|
|
9585
|
-
kind,
|
|
9586
|
-
displayName: input.displayName ?? id,
|
|
9587
|
-
createdAt: now,
|
|
9588
|
-
updatedAt: now,
|
|
9589
|
-
...typeof input.notes === "string" ? { notes: input.notes } : {}
|
|
9590
|
-
};
|
|
9591
|
-
await peers.writePeer(memoryDir, newPeer);
|
|
9592
|
-
return { ok: true, created: true, peer: newPeer };
|
|
9593
|
-
}
|
|
9594
|
-
const updated = {
|
|
9595
|
-
id: existing.id,
|
|
9596
|
-
kind: existing.kind,
|
|
9597
|
-
createdAt: existing.createdAt,
|
|
9598
|
-
updatedAt: now,
|
|
9599
|
-
displayName: input.displayName !== void 0 ? input.displayName : existing.displayName,
|
|
9600
|
-
...input.notes !== void 0 ? { notes: input.notes } : existing.notes !== void 0 ? { notes: existing.notes } : {}
|
|
9601
|
-
};
|
|
9602
|
-
await peers.writePeer(memoryDir, updated);
|
|
9603
|
-
return { ok: true, created: false, peer: updated };
|
|
9509
|
+
});
|
|
9510
|
+
return redactSensitive(report);
|
|
9604
9511
|
}
|
|
9605
9512
|
/**
|
|
9606
|
-
*
|
|
9607
|
-
*
|
|
9608
|
-
* (`peers/{id}/`) is intentionally left in place — profile and interaction
|
|
9609
|
-
* log data are not destroyed.
|
|
9513
|
+
* Dry-run transcript/session audit. Detects mixed `other/default` data
|
|
9514
|
+
* and legacy stranded directories. Never applies a migration.
|
|
9610
9515
|
*/
|
|
9611
|
-
async
|
|
9612
|
-
const
|
|
9613
|
-
|
|
9614
|
-
try {
|
|
9615
|
-
validateId(peerId);
|
|
9616
|
-
} catch (err) {
|
|
9617
|
-
throw new EngramAccessInputError(err.message);
|
|
9618
|
-
}
|
|
9619
|
-
const deleted = await peers.deletePeer(this.orchestrator.config.memoryDir, peerId);
|
|
9620
|
-
return { ok: true, deleted };
|
|
9516
|
+
async adminTranscriptAudit() {
|
|
9517
|
+
const result = await auditTranscripts(this.orchestrator.config.memoryDir);
|
|
9518
|
+
return redactSensitive(result);
|
|
9621
9519
|
}
|
|
9622
9520
|
/**
|
|
9623
|
-
*
|
|
9624
|
-
*
|
|
9625
|
-
*
|
|
9626
|
-
*
|
|
9627
|
-
*
|
|
9628
|
-
* This is the DESTRUCTIVE counterpart to `peerDelete`, which only
|
|
9629
|
-
* removes `identity.md`. All companion files are permanently removed.
|
|
9630
|
-
*
|
|
9631
|
-
* Returns `{ ok: true, purged: true }` when the directory existed and
|
|
9632
|
-
* was removed; `{ ok: true, purged: false }` when the directory did
|
|
9633
|
-
* not exist (idempotent no-op).
|
|
9521
|
+
* Manually promote a memory into one or more authorized targets. Requires
|
|
9522
|
+
* a non-empty reason (audit-logged). Reuses the scope-profile promotion
|
|
9523
|
+
* resolution and `canWriteNamespace` gate — there is no dashboard-only
|
|
9524
|
+
* write path.
|
|
9634
9525
|
*/
|
|
9635
|
-
async
|
|
9636
|
-
const
|
|
9637
|
-
const
|
|
9638
|
-
|
|
9639
|
-
|
|
9640
|
-
|
|
9641
|
-
|
|
9642
|
-
|
|
9643
|
-
|
|
9644
|
-
|
|
9645
|
-
|
|
9526
|
+
async adminPromoteMemory(request) {
|
|
9527
|
+
const config = this.orchestrator.config;
|
|
9528
|
+
const namespacesEnabled = resolveNamespaceCapabilities(config).namespaces === true;
|
|
9529
|
+
let sourceNamespace;
|
|
9530
|
+
if (request.namespace) {
|
|
9531
|
+
sourceNamespace = this.resolveReadableNamespace(
|
|
9532
|
+
request.namespace,
|
|
9533
|
+
request.principal
|
|
9534
|
+
);
|
|
9535
|
+
} else {
|
|
9536
|
+
const codingContext2 = request.sessionKey ? this.orchestrator.getCodingContextForSession(request.sessionKey) ?? null : null;
|
|
9537
|
+
const plan = resolveScopePlan({
|
|
9538
|
+
config,
|
|
9539
|
+
sessionKey: request.sessionKey,
|
|
9540
|
+
principalOverride: request.principal,
|
|
9541
|
+
codingContext: codingContext2,
|
|
9542
|
+
namespacesEnabled
|
|
9543
|
+
});
|
|
9544
|
+
sourceNamespace = this.resolveReadableNamespace(
|
|
9545
|
+
plan.baseNamespace,
|
|
9546
|
+
request.principal
|
|
9646
9547
|
);
|
|
9647
9548
|
}
|
|
9648
|
-
const
|
|
9649
|
-
|
|
9549
|
+
const codingContext = request.sessionKey ? this.orchestrator.getCodingContextForSession(request.sessionKey) ?? null : null;
|
|
9550
|
+
const codingOverlay = codingContext ? resolveCodingNamespaceOverlay(
|
|
9551
|
+
codingContext,
|
|
9552
|
+
config.codingMode,
|
|
9553
|
+
config.defaultNamespace
|
|
9554
|
+
) : null;
|
|
9555
|
+
const scopeProfilePlan = resolveScopeProfilePlan({
|
|
9556
|
+
config,
|
|
9557
|
+
principal: request.principal,
|
|
9558
|
+
codingContext,
|
|
9559
|
+
codingOverlay
|
|
9650
9560
|
});
|
|
9651
|
-
|
|
9652
|
-
|
|
9653
|
-
|
|
9654
|
-
|
|
9655
|
-
|
|
9656
|
-
|
|
9657
|
-
|
|
9658
|
-
|
|
9659
|
-
|
|
9660
|
-
|
|
9661
|
-
|
|
9561
|
+
const storage = {
|
|
9562
|
+
readMemory: async (namespace, memoryId) => {
|
|
9563
|
+
const resolved = await this.orchestrator.getStorage(namespace);
|
|
9564
|
+
const memory = await resolved.getMemoryById(memoryId);
|
|
9565
|
+
if (!memory) return null;
|
|
9566
|
+
return {
|
|
9567
|
+
category: memory.frontmatter.category,
|
|
9568
|
+
content: memory.content,
|
|
9569
|
+
frontmatter: memory.frontmatter
|
|
9570
|
+
};
|
|
9571
|
+
},
|
|
9572
|
+
writePromotedMemory: async (namespace, memory) => {
|
|
9573
|
+
const resolved = await this.orchestrator.getStorage(namespace);
|
|
9574
|
+
const { id, tombstoneBlocked } = await resolved.writeMemory(
|
|
9575
|
+
memory.category,
|
|
9576
|
+
memory.content,
|
|
9577
|
+
{
|
|
9578
|
+
confidence: memory.confidence,
|
|
9579
|
+
tags: memory.tags,
|
|
9580
|
+
entityRef: memory.entityRef,
|
|
9581
|
+
source: `admin-promotion:${memory.sourceNamespace}:${memory.reason.slice(0, 120)}`,
|
|
9582
|
+
lineage: memory.lineage,
|
|
9583
|
+
sourceMemoryId: memory.sourceMemoryId,
|
|
9584
|
+
actor: memory.actor,
|
|
9585
|
+
validAt: memory.validAt
|
|
9586
|
+
}
|
|
9587
|
+
);
|
|
9588
|
+
if (tombstoneBlocked) {
|
|
9589
|
+
throw new Error(
|
|
9590
|
+
"target namespace tombstone-blocked the promoted content (pending_review)"
|
|
9591
|
+
);
|
|
9592
|
+
}
|
|
9593
|
+
return id;
|
|
9594
|
+
}
|
|
9595
|
+
};
|
|
9662
9596
|
try {
|
|
9663
|
-
|
|
9597
|
+
return await promoteMemory({
|
|
9598
|
+
config,
|
|
9599
|
+
sourceMemoryId: request.sourceMemoryId,
|
|
9600
|
+
sourceNamespace,
|
|
9601
|
+
principal: request.principal,
|
|
9602
|
+
targets: request.targets,
|
|
9603
|
+
reason: request.reason,
|
|
9604
|
+
actor: request.principal ?? "admin-console",
|
|
9605
|
+
storage,
|
|
9606
|
+
scopeProfilePlan
|
|
9607
|
+
});
|
|
9664
9608
|
} catch (err) {
|
|
9665
|
-
|
|
9609
|
+
if (err instanceof AdminPromotionError) {
|
|
9610
|
+
throw new EngramAccessInputError(err.message);
|
|
9611
|
+
}
|
|
9612
|
+
throw err;
|
|
9666
9613
|
}
|
|
9667
|
-
const profile = await peers.readPeerProfile(this.orchestrator.config.memoryDir, peerId);
|
|
9668
|
-
if (!profile) return { found: false };
|
|
9669
|
-
return { found: true, profile };
|
|
9670
|
-
}
|
|
9671
|
-
// ── Contradiction Review (issue #520) ──────────────────────────────────────
|
|
9672
|
-
get memoryDir() {
|
|
9673
|
-
return this.orchestrator.config.memoryDir;
|
|
9674
|
-
}
|
|
9675
|
-
/**
|
|
9676
|
-
* Resolve the storage directory for a given namespace. Used by the SSE
|
|
9677
|
-
* graph-event handler to subscribe to the correct per-namespace bus rather
|
|
9678
|
-
* than the global root (CLAUDE.md rule 42 — read/write paths must resolve
|
|
9679
|
-
* through the same namespace layer).
|
|
9680
|
-
*
|
|
9681
|
-
* `principal` must be the transport-bound request principal (from
|
|
9682
|
-
* `resolveRequestPrincipal`). When namespaces are enabled, an absent
|
|
9683
|
-
* principal causes `resolveReadableNamespace` to throw an auth error,
|
|
9684
|
-
* matching the behaviour of every other authenticated read path.
|
|
9685
|
-
*
|
|
9686
|
-
* Falls back to `this.memoryDir` when namespaces are disabled or the
|
|
9687
|
-
* namespace is absent, matching the behaviour of every other read path.
|
|
9688
|
-
*/
|
|
9689
|
-
async getMemoryDirForNamespace(namespace, principal) {
|
|
9690
|
-
const resolved = this.resolveReadableNamespace(namespace, principal);
|
|
9691
|
-
const storage = await this.orchestrator.getStorage(resolved);
|
|
9692
|
-
return storage.dir;
|
|
9693
|
-
}
|
|
9694
|
-
async getReadableStorageForNamespace(namespace, principal) {
|
|
9695
|
-
const resolved = this.resolveReadableNamespace(namespace, principal);
|
|
9696
|
-
const storage = await this.orchestrator.getStorage(resolved);
|
|
9697
|
-
return { namespace: resolved, storage };
|
|
9698
9614
|
}
|
|
9699
|
-
|
|
9700
|
-
|
|
9701
|
-
|
|
9702
|
-
|
|
9703
|
-
|
|
9615
|
+
};
|
|
9616
|
+
|
|
9617
|
+
// src/orchestration/qmd-result-resolver.ts
|
|
9618
|
+
import path8 from "path";
|
|
9619
|
+
function qmdCollectionPathParts(resultPath) {
|
|
9620
|
+
if (!resultPath || path8.isAbsolute(resultPath)) return null;
|
|
9621
|
+
const normalized = resultPath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
9622
|
+
const slashIndex = normalized.indexOf("/");
|
|
9623
|
+
if (slashIndex <= 0 || slashIndex >= normalized.length - 1) return null;
|
|
9624
|
+
const collection = normalized.slice(0, slashIndex);
|
|
9625
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(collection)) return null;
|
|
9626
|
+
return {
|
|
9627
|
+
collection,
|
|
9628
|
+
relativePath: normalized.slice(slashIndex + 1)
|
|
9629
|
+
};
|
|
9630
|
+
}
|
|
9631
|
+
function qmdResultPathCandidates(storageDir, resultPath) {
|
|
9632
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
9633
|
+
const storageRoot = path8.resolve(storageDir);
|
|
9634
|
+
const addCandidate = (candidate) => {
|
|
9635
|
+
const resolved = path8.resolve(candidate);
|
|
9636
|
+
if (isPathInsideStorageRoot(storageRoot, resolved)) {
|
|
9637
|
+
candidates.add(resolved);
|
|
9704
9638
|
}
|
|
9705
|
-
|
|
9706
|
-
|
|
9707
|
-
|
|
9708
|
-
|
|
9709
|
-
|
|
9710
|
-
|
|
9711
|
-
|
|
9712
|
-
|
|
9713
|
-
|
|
9714
|
-
|
|
9715
|
-
|
|
9716
|
-
|
|
9717
|
-
|
|
9718
|
-
}
|
|
9719
|
-
get fallbackLlmRef() {
|
|
9720
|
-
return this.orchestrator.fastGatewayLlm ?? null;
|
|
9639
|
+
};
|
|
9640
|
+
const addRelativeCandidates = (relativePath) => {
|
|
9641
|
+
const normalized = relativePath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
9642
|
+
if (!normalized) return;
|
|
9643
|
+
addCandidate(path8.join(storageRoot, normalized));
|
|
9644
|
+
if (/^\d{4}-\d{2}-\d{2}\//.test(normalized)) {
|
|
9645
|
+
addCandidate(path8.join(storageRoot, "facts", normalized));
|
|
9646
|
+
}
|
|
9647
|
+
};
|
|
9648
|
+
if (path8.isAbsolute(resultPath)) {
|
|
9649
|
+
addCandidate(resultPath);
|
|
9650
|
+
} else {
|
|
9651
|
+
addRelativeCandidates(resultPath);
|
|
9721
9652
|
}
|
|
9722
|
-
|
|
9723
|
-
|
|
9724
|
-
|
|
9725
|
-
|
|
9653
|
+
return [...candidates];
|
|
9654
|
+
}
|
|
9655
|
+
function storageDirOrNull(storage) {
|
|
9656
|
+
const dir = storage.dir;
|
|
9657
|
+
return typeof dir === "string" && dir.length > 0 ? dir : null;
|
|
9658
|
+
}
|
|
9659
|
+
var QmdResultResolver = class {
|
|
9660
|
+
getConfig;
|
|
9661
|
+
storageFor;
|
|
9662
|
+
storageDirNamespace;
|
|
9663
|
+
qmdCollectionNamespaceFromPrefix;
|
|
9664
|
+
namespaceFromPath;
|
|
9665
|
+
constructor(options) {
|
|
9666
|
+
this.getConfig = options.getConfig;
|
|
9667
|
+
this.storageFor = options.storageFor;
|
|
9668
|
+
this.storageDirNamespace = options.storageDirNamespace;
|
|
9669
|
+
this.qmdCollectionNamespaceFromPrefix = options.qmdCollectionNamespaceFromPrefix;
|
|
9670
|
+
this.namespaceFromPath = options.namespaceFromPath;
|
|
9671
|
+
}
|
|
9672
|
+
async readQmdResultMemory(resultPath, fallbackStorage, recallNamespaces = []) {
|
|
9673
|
+
const parts = qmdCollectionPathParts(resultPath);
|
|
9674
|
+
const fallbackStorageDir = storageDirOrNull(fallbackStorage);
|
|
9675
|
+
const config = this.getConfig();
|
|
9676
|
+
const coldCollection = config.qmdColdCollection ?? "openclaw-engram-cold";
|
|
9677
|
+
if (parts && parts.collection === coldCollection) {
|
|
9678
|
+
const storages = [];
|
|
9679
|
+
const seenStorageDirs = /* @__PURE__ */ new Set();
|
|
9680
|
+
const addStorage = (storage) => {
|
|
9681
|
+
const storageDir = storageDirOrNull(storage);
|
|
9682
|
+
const storageKey = storageDir ? path8.resolve(storageDir) : `storage-without-dir-${storages.length}`;
|
|
9683
|
+
if (seenStorageDirs.has(storageKey)) return;
|
|
9684
|
+
seenStorageDirs.add(storageKey);
|
|
9685
|
+
storages.push(storage);
|
|
9686
|
+
};
|
|
9687
|
+
const fallbackNamespace = fallbackStorageDir !== null ? this.storageDirNamespace(fallbackStorageDir) : config.defaultNamespace;
|
|
9688
|
+
if (recallNamespaces.length === 0 || !resolveNamespaceCapabilities(config).namespaces || recallNamespaces.includes(fallbackNamespace)) {
|
|
9689
|
+
addStorage(fallbackStorage);
|
|
9690
|
+
}
|
|
9691
|
+
if (recallNamespaces.length > 0) {
|
|
9692
|
+
for (const namespace of recallNamespaces) {
|
|
9693
|
+
try {
|
|
9694
|
+
addStorage(await this.storageFor(namespace));
|
|
9695
|
+
} catch (err) {
|
|
9696
|
+
log.debug("qmd cold result namespace storage lookup skipped", {
|
|
9697
|
+
path: resultPath,
|
|
9698
|
+
namespace,
|
|
9699
|
+
error: err.message
|
|
9700
|
+
});
|
|
9701
|
+
}
|
|
9702
|
+
}
|
|
9703
|
+
}
|
|
9704
|
+
for (const storage of storages) {
|
|
9705
|
+
const storageDir = storageDirOrNull(storage);
|
|
9706
|
+
if (!storageDir) {
|
|
9707
|
+
const memory = await storage.readMemoryByPath(resultPath);
|
|
9708
|
+
if (memory) return memory;
|
|
9709
|
+
continue;
|
|
9710
|
+
}
|
|
9726
9711
|
try {
|
|
9727
|
-
|
|
9728
|
-
|
|
9729
|
-
|
|
9712
|
+
const coldRoot = path8.join(storageDir, "cold");
|
|
9713
|
+
for (const candidate of qmdResultPathCandidates(
|
|
9714
|
+
coldRoot,
|
|
9715
|
+
parts.relativePath
|
|
9716
|
+
)) {
|
|
9717
|
+
const memory = await storage.readMemoryByPath(candidate);
|
|
9718
|
+
if (memory) return memory;
|
|
9719
|
+
}
|
|
9720
|
+
} catch (err) {
|
|
9721
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
9722
|
+
log.debug("qmd cold result path lookup failed open", {
|
|
9723
|
+
path: resultPath,
|
|
9724
|
+
collection: coldCollection,
|
|
9725
|
+
error: err.message
|
|
9726
|
+
});
|
|
9727
|
+
}
|
|
9728
|
+
}
|
|
9729
|
+
return null;
|
|
9730
|
+
}
|
|
9731
|
+
const collectionNamespace = parts ? this.qmdCollectionNamespaceFromPrefix(parts.collection) : null;
|
|
9732
|
+
if (parts && collectionNamespace) {
|
|
9733
|
+
try {
|
|
9734
|
+
const collectionStorage = await this.storageFor(collectionNamespace);
|
|
9735
|
+
for (const candidate of qmdResultPathCandidates(
|
|
9736
|
+
collectionStorage.dir,
|
|
9737
|
+
parts.relativePath
|
|
9738
|
+
)) {
|
|
9739
|
+
const memory = await collectionStorage.readMemoryByPath(candidate);
|
|
9740
|
+
if (memory) return memory;
|
|
9730
9741
|
}
|
|
9731
|
-
|
|
9732
|
-
|
|
9733
|
-
|
|
9734
|
-
|
|
9735
|
-
|
|
9736
|
-
|
|
9737
|
-
|
|
9738
|
-
|
|
9739
|
-
|
|
9740
|
-
* thread the config value through.
|
|
9741
|
-
*
|
|
9742
|
-
* `versioning` defaults to the orchestrator's page-versioning config so
|
|
9743
|
-
* `mode: "overwrite"` automatically snapshots prior content without the
|
|
9744
|
-
* caller having to construct the config object.
|
|
9745
|
-
*/
|
|
9746
|
-
async capsuleImport(opts) {
|
|
9747
|
-
const { namespace, principal, root: explicitRoot, memoryDir: explicitMemoryDir, ...importOptions } = opts;
|
|
9748
|
-
const resolvedNamespace = this.writableNamespaceFor(namespace, void 0, principal);
|
|
9749
|
-
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
9750
|
-
const root = explicitRoot ?? storage.dir;
|
|
9751
|
-
const memoryDir = explicitMemoryDir ?? this.orchestrator.config.memoryDir;
|
|
9752
|
-
const versioning = importOptions.versioning ?? {
|
|
9753
|
-
enabled: resolveRecallAuxiliaryCapabilities(this.orchestrator.config).versioning,
|
|
9754
|
-
maxVersionsPerPage: this.orchestrator.config.versioningMaxPerPage,
|
|
9755
|
-
sidecarDir: this.orchestrator.config.versioningSidecarDir
|
|
9756
|
-
};
|
|
9757
|
-
await this.validateCapsuleImportArchivePath(importOptions.archivePath);
|
|
9758
|
-
try {
|
|
9759
|
-
return await importCapsule({ ...importOptions, root, memoryDir, versioning });
|
|
9760
|
-
} catch (err) {
|
|
9761
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
9762
|
-
if (this.isCapsuleImportArchiveInputError(err, message)) {
|
|
9763
|
-
throw new EngramAccessInputError(`capsule import failed: ${message}`);
|
|
9742
|
+
return null;
|
|
9743
|
+
} catch (err) {
|
|
9744
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
9745
|
+
log.debug("qmd result namespace path lookup failed open", {
|
|
9746
|
+
path: resultPath,
|
|
9747
|
+
namespace: collectionNamespace,
|
|
9748
|
+
error: err.message
|
|
9749
|
+
});
|
|
9750
|
+
return null;
|
|
9764
9751
|
}
|
|
9765
|
-
throw err;
|
|
9766
9752
|
}
|
|
9767
|
-
|
|
9768
|
-
|
|
9769
|
-
|
|
9770
|
-
|
|
9771
|
-
|
|
9772
|
-
|
|
9773
|
-
|
|
9774
|
-
|
|
9775
|
-
|
|
9753
|
+
if (path8.isAbsolute(resultPath)) {
|
|
9754
|
+
if (!fallbackStorageDir) {
|
|
9755
|
+
return await fallbackStorage.readMemoryByPath(resultPath);
|
|
9756
|
+
}
|
|
9757
|
+
const ownerStorage = await this.storageForAbsoluteQmdResultPath(
|
|
9758
|
+
resultPath,
|
|
9759
|
+
fallbackStorage,
|
|
9760
|
+
recallNamespaces
|
|
9761
|
+
);
|
|
9762
|
+
if (!ownerStorage) return null;
|
|
9763
|
+
for (const candidate of qmdResultPathCandidates(
|
|
9764
|
+
ownerStorage.dir,
|
|
9765
|
+
resultPath
|
|
9766
|
+
)) {
|
|
9767
|
+
const memory = await ownerStorage.storage.readMemoryByPath(candidate);
|
|
9768
|
+
if (memory) return memory;
|
|
9769
|
+
}
|
|
9770
|
+
return null;
|
|
9776
9771
|
}
|
|
9777
|
-
if (!
|
|
9778
|
-
|
|
9772
|
+
if (!fallbackStorageDir) {
|
|
9773
|
+
return await fallbackStorage.readMemoryByPath(resultPath);
|
|
9779
9774
|
}
|
|
9780
|
-
|
|
9781
|
-
|
|
9782
|
-
|
|
9783
|
-
|
|
9784
|
-
const
|
|
9785
|
-
|
|
9775
|
+
for (const candidate of qmdResultPathCandidates(
|
|
9776
|
+
fallbackStorageDir,
|
|
9777
|
+
resultPath
|
|
9778
|
+
)) {
|
|
9779
|
+
const memory = await fallbackStorage.readMemoryByPath(candidate);
|
|
9780
|
+
if (memory) return memory;
|
|
9786
9781
|
}
|
|
9782
|
+
return null;
|
|
9787
9783
|
}
|
|
9788
|
-
|
|
9789
|
-
const
|
|
9790
|
-
|
|
9784
|
+
async resolveColdQmdResultForRecall(result, fallbackStorage, recallNamespaces = []) {
|
|
9785
|
+
const memory = await this.readQmdResultMemory(
|
|
9786
|
+
result.path,
|
|
9787
|
+
fallbackStorage,
|
|
9788
|
+
recallNamespaces
|
|
9789
|
+
);
|
|
9790
|
+
if (!memory) return null;
|
|
9791
|
+
let ownerNamespace = null;
|
|
9792
|
+
if (path8.isAbsolute(memory.path)) {
|
|
9793
|
+
const ownerStorage = await this.storageForAbsoluteQmdResultPath(
|
|
9794
|
+
memory.path,
|
|
9795
|
+
fallbackStorage,
|
|
9796
|
+
recallNamespaces
|
|
9797
|
+
);
|
|
9798
|
+
ownerNamespace = ownerStorage?.namespace ?? null;
|
|
9799
|
+
if (!ownerNamespace && resolveNamespaceCapabilities(this.getConfig()).namespaces) return null;
|
|
9800
|
+
}
|
|
9801
|
+
ownerNamespace ??= this.namespaceFromPath(memory.path);
|
|
9802
|
+
if (recallNamespaces.length > 0 && !recallNamespaces.includes(ownerNamespace)) {
|
|
9803
|
+
return null;
|
|
9804
|
+
}
|
|
9805
|
+
return {
|
|
9806
|
+
namespace: ownerNamespace,
|
|
9807
|
+
result: {
|
|
9808
|
+
...result,
|
|
9809
|
+
docid: result.docid || memory.frontmatter.id,
|
|
9810
|
+
path: memory.path,
|
|
9811
|
+
snippet: result.snippet || memory.content.slice(0, 400)
|
|
9812
|
+
}
|
|
9813
|
+
};
|
|
9791
9814
|
}
|
|
9792
|
-
|
|
9793
|
-
|
|
9794
|
-
const
|
|
9795
|
-
|
|
9796
|
-
|
|
9815
|
+
async storageForAbsoluteQmdResultPath(resultPath, fallbackStorage, recallNamespaces = []) {
|
|
9816
|
+
const resolvedPath = path8.resolve(resultPath);
|
|
9817
|
+
const config = this.getConfig();
|
|
9818
|
+
const memoryRoot = path8.resolve(config.memoryDir);
|
|
9819
|
+
const namespacesRoot = path8.join(memoryRoot, "namespaces");
|
|
9820
|
+
const fallbackStorageDir = storageDirOrNull(fallbackStorage);
|
|
9821
|
+
const matches = [];
|
|
9822
|
+
const seenDirs = /* @__PURE__ */ new Set();
|
|
9823
|
+
const maybeAddStorage = (storage, namespace) => {
|
|
9824
|
+
const storageDir = storageDirOrNull(storage);
|
|
9825
|
+
if (!storageDir) return;
|
|
9826
|
+
const candidateRoot = path8.resolve(storageDir);
|
|
9827
|
+
if (seenDirs.has(candidateRoot)) return;
|
|
9828
|
+
if (!isPathInsideStorageRoot(candidateRoot, resolvedPath)) return;
|
|
9829
|
+
if (candidateRoot === memoryRoot && isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
|
|
9830
|
+
return;
|
|
9831
|
+
}
|
|
9832
|
+
seenDirs.add(candidateRoot);
|
|
9833
|
+
matches.push({ storage, dir: candidateRoot, namespace });
|
|
9834
|
+
};
|
|
9835
|
+
const fallbackNamespace = fallbackStorageDir !== null ? this.storageDirNamespace(fallbackStorageDir) : config.defaultNamespace;
|
|
9836
|
+
maybeAddStorage(fallbackStorage, fallbackNamespace);
|
|
9837
|
+
const candidateNamespaces = /* @__PURE__ */ new Set();
|
|
9838
|
+
candidateNamespaces.add(config.defaultNamespace);
|
|
9839
|
+
candidateNamespaces.add(config.sharedNamespace);
|
|
9840
|
+
for (const ns of recallNamespaces) {
|
|
9841
|
+
candidateNamespaces.add(ns);
|
|
9842
|
+
}
|
|
9843
|
+
if (isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
|
|
9844
|
+
const relativeToNamespaces = path8.relative(namespacesRoot, resolvedPath);
|
|
9845
|
+
const [namespaceSegment] = relativeToNamespaces.split(/[\\/]/);
|
|
9846
|
+
if (namespaceSegment) {
|
|
9847
|
+
candidateNamespaces.add(
|
|
9848
|
+
namespaceIdentityFromToken(namespaceSegment) ?? namespaceSegment
|
|
9849
|
+
);
|
|
9850
|
+
}
|
|
9851
|
+
}
|
|
9852
|
+
for (const policy of config.namespacePolicies ?? []) {
|
|
9853
|
+
candidateNamespaces.add(policy.name);
|
|
9854
|
+
}
|
|
9855
|
+
for (const ns of candidateNamespaces) {
|
|
9856
|
+
if (!ns) continue;
|
|
9857
|
+
try {
|
|
9858
|
+
maybeAddStorage(await this.storageFor(ns), ns);
|
|
9859
|
+
} catch {
|
|
9860
|
+
continue;
|
|
9861
|
+
}
|
|
9862
|
+
}
|
|
9863
|
+
matches.sort((a, b) => b.dir.length - a.dir.length);
|
|
9864
|
+
return matches[0] ?? null;
|
|
9797
9865
|
}
|
|
9798
|
-
|
|
9799
|
-
|
|
9800
|
-
|
|
9801
|
-
|
|
9802
|
-
|
|
9803
|
-
|
|
9804
|
-
* validation.
|
|
9805
|
-
*/
|
|
9806
|
-
async capsuleExport(opts) {
|
|
9807
|
-
const { namespace, principal, root: explicitRoot, memoryDir: explicitMemoryDir, ...exportOptions } = opts;
|
|
9808
|
-
const resolvedNamespace = this.writableNamespaceFor(namespace, void 0, principal);
|
|
9809
|
-
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
9810
|
-
const root = explicitRoot ?? storage.dir;
|
|
9811
|
-
const memoryDir = explicitMemoryDir ?? this.orchestrator.config.memoryDir;
|
|
9812
|
-
const pluginVersion = exportOptions.pluginVersion ?? await getPackageVersion();
|
|
9813
|
-
return exportCapsule({
|
|
9814
|
-
...exportOptions,
|
|
9815
|
-
pluginVersion,
|
|
9816
|
-
root,
|
|
9817
|
-
memoryDir: exportOptions.encrypt === true ? memoryDir : void 0
|
|
9818
|
-
});
|
|
9866
|
+
};
|
|
9867
|
+
|
|
9868
|
+
// src/access-recall-surface.ts
|
|
9869
|
+
var AccessRecallSurface = class {
|
|
9870
|
+
constructor(deps) {
|
|
9871
|
+
this.deps = deps;
|
|
9819
9872
|
}
|
|
9820
|
-
|
|
9821
|
-
|
|
9822
|
-
|
|
9823
|
-
|
|
9824
|
-
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
const
|
|
9829
|
-
|
|
9830
|
-
|
|
9831
|
-
|
|
9832
|
-
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
|
|
9873
|
+
deps;
|
|
9874
|
+
async buildRecallResponseFromXraySnapshot(options) {
|
|
9875
|
+
const memoryIds = options.snapshot.results.map((result) => result.memoryId);
|
|
9876
|
+
const resultPaths = options.snapshot.results.map((result) => result.path);
|
|
9877
|
+
const namespace = options.snapshot.namespace ? this.deps.resolveNamespace(options.snapshot.namespace) : this.deps.orchestrator.config.defaultNamespace;
|
|
9878
|
+
const sourcesUsed = Array.from(
|
|
9879
|
+
new Set(options.snapshot.results.map((result) => result.servedBy))
|
|
9880
|
+
);
|
|
9881
|
+
const snapshotForSerialization = {
|
|
9882
|
+
sessionKey: options.sessionKey ?? "",
|
|
9883
|
+
recordedAt: new Date(options.snapshot.capturedAt).toISOString(),
|
|
9884
|
+
queryHash: createHash5("sha256").update(options.query).digest("hex"),
|
|
9885
|
+
queryLen: options.query.length,
|
|
9886
|
+
memoryIds,
|
|
9887
|
+
namespace,
|
|
9888
|
+
recallNamespaces: [namespace],
|
|
9889
|
+
traceId: options.snapshot.traceId,
|
|
9890
|
+
plannerMode: options.normalizedMode,
|
|
9891
|
+
requestedMode: options.requestedMode && options.requestedMode !== "auto" ? options.requestedMode : void 0,
|
|
9892
|
+
sourcesUsed,
|
|
9893
|
+
budgetsApplied: {
|
|
9894
|
+
appliedTopK: memoryIds.length,
|
|
9895
|
+
recallBudgetChars: options.snapshot.budget.chars,
|
|
9896
|
+
maxMemoryTokens: this.deps.orchestrator.config.maxMemoryTokens,
|
|
9897
|
+
finalContextChars: options.snapshot.budget.used
|
|
9898
|
+
},
|
|
9899
|
+
latencyMs: Date.now() - options.startedAt,
|
|
9900
|
+
resultPaths
|
|
9901
|
+
};
|
|
9902
|
+
const results = await this.deps.serializeRecallResults(
|
|
9903
|
+
snapshotForSerialization,
|
|
9904
|
+
options.disclosure,
|
|
9905
|
+
{
|
|
9906
|
+
query: options.query,
|
|
9907
|
+
...options.sessionKey ? { sessionKey: options.sessionKey } : {},
|
|
9908
|
+
...options.rawExcerptNamespace ? { rawExcerptNamespace: options.rawExcerptNamespace } : {},
|
|
9909
|
+
...options.rawExcerptSessionIds !== void 0 ? { rawExcerptSessionIds: options.rawExcerptSessionIds } : {},
|
|
9910
|
+
...options.rawExcerptsSuppressed ? { rawExcerptsSuppressed: options.rawExcerptsSuppressed } : {}
|
|
9836
9911
|
}
|
|
9837
|
-
|
|
9838
|
-
|
|
9912
|
+
);
|
|
9913
|
+
const context = results.map((result) => {
|
|
9914
|
+
const content = typeof result.content === "string" && result.content.length > 0 ? result.content : "";
|
|
9915
|
+
return content || result.preview;
|
|
9916
|
+
}).filter((text) => text.length > 0).join("\n\n");
|
|
9917
|
+
return {
|
|
9918
|
+
query: options.query,
|
|
9919
|
+
...options.sessionKey ? { sessionKey: options.sessionKey } : {},
|
|
9920
|
+
namespace,
|
|
9921
|
+
context,
|
|
9922
|
+
count: memoryIds.length,
|
|
9923
|
+
memoryIds,
|
|
9924
|
+
results,
|
|
9925
|
+
recordedAt: snapshotForSerialization.recordedAt,
|
|
9926
|
+
traceId: options.snapshot.traceId,
|
|
9927
|
+
plannerMode: options.normalizedMode,
|
|
9928
|
+
fallbackUsed: sourcesUsed.some((source) => source !== "hybrid"),
|
|
9929
|
+
sourcesUsed,
|
|
9930
|
+
disclosure: options.disclosure,
|
|
9931
|
+
budgetsApplied: snapshotForSerialization.budgetsApplied,
|
|
9932
|
+
latencyMs: snapshotForSerialization.latencyMs
|
|
9933
|
+
};
|
|
9934
|
+
}
|
|
9935
|
+
async serializeRecallResults(snapshot, disclosure, rawContext = null) {
|
|
9936
|
+
if (!snapshot) return [];
|
|
9937
|
+
const namespace = snapshot.namespace ? this.deps.resolveNamespace(snapshot.namespace) : this.deps.orchestrator.config.defaultNamespace;
|
|
9938
|
+
const storage = await this.deps.orchestrator.getStorage(namespace);
|
|
9939
|
+
const storageDir = storage.dir;
|
|
9940
|
+
const recallNamespaces = Array.from(
|
|
9941
|
+
new Set(
|
|
9942
|
+
[
|
|
9943
|
+
namespace,
|
|
9944
|
+
...Array.isArray(snapshot.recallNamespaces) ? snapshot.recallNamespaces.map((ns) => this.deps.resolveNamespace(ns)) : [],
|
|
9945
|
+
this.deps.orchestrator.config.defaultNamespace,
|
|
9946
|
+
this.deps.orchestrator.config.sharedNamespace,
|
|
9947
|
+
...(this.deps.orchestrator.config.namespacePolicies ?? []).map((p) => p.name)
|
|
9948
|
+
].filter((ns) => typeof ns === "string" && ns.length > 0)
|
|
9949
|
+
)
|
|
9950
|
+
);
|
|
9951
|
+
const results = [];
|
|
9952
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9953
|
+
const collectionNamespaceFromPrefix = (collectionPrefix) => {
|
|
9954
|
+
const baseCollection = this.deps.orchestrator.config.qmdCollection;
|
|
9955
|
+
if (collectionPrefix === baseCollection) return this.deps.orchestrator.config.defaultNamespace;
|
|
9956
|
+
const namespaceSuffix = collectionPrefix.startsWith(`${baseCollection}--`) ? collectionPrefix.slice(baseCollection.length + 2) : "";
|
|
9957
|
+
if (!namespaceSuffix) return null;
|
|
9958
|
+
const decoded = namespaceIdentityFromToken(namespaceSuffix);
|
|
9959
|
+
if (decoded !== null) return decoded || this.deps.orchestrator.config.defaultNamespace;
|
|
9960
|
+
if (namespaceSuffix.startsWith("ns--")) {
|
|
9961
|
+
const legacyNamespace = namespaceSuffix.slice("ns--".length).trim();
|
|
9962
|
+
return legacyNamespace || null;
|
|
9839
9963
|
}
|
|
9840
|
-
|
|
9841
|
-
}
|
|
9842
|
-
|
|
9843
|
-
|
|
9844
|
-
|
|
9964
|
+
return null;
|
|
9965
|
+
};
|
|
9966
|
+
const readResultPath = async (memoryPath) => {
|
|
9967
|
+
const parts = qmdCollectionPathParts(memoryPath);
|
|
9968
|
+
const coldCollection = this.deps.orchestrator.config.qmdColdCollection ?? "openclaw-engram-cold";
|
|
9969
|
+
if (parts && parts.collection === coldCollection) {
|
|
9970
|
+
const storages = [];
|
|
9971
|
+
const seenStorageDirs = /* @__PURE__ */ new Set();
|
|
9972
|
+
const addStorage = (candidateStorage) => {
|
|
9973
|
+
const candidateDir = nodePath2.resolve(candidateStorage.dir);
|
|
9974
|
+
if (seenStorageDirs.has(candidateDir)) return;
|
|
9975
|
+
seenStorageDirs.add(candidateDir);
|
|
9976
|
+
storages.push({ storage: candidateStorage, dir: candidateDir });
|
|
9977
|
+
};
|
|
9978
|
+
addStorage(storage);
|
|
9979
|
+
for (const recallNamespace of recallNamespaces) {
|
|
9980
|
+
try {
|
|
9981
|
+
addStorage(await this.deps.orchestrator.getStorage(recallNamespace));
|
|
9982
|
+
} catch {
|
|
9983
|
+
continue;
|
|
9984
|
+
}
|
|
9985
|
+
}
|
|
9986
|
+
for (const candidateStorage of storages) {
|
|
9987
|
+
try {
|
|
9988
|
+
const coldRoot = nodePath2.join(candidateStorage.dir, "cold");
|
|
9989
|
+
for (const candidatePath of qmdResultPathCandidates(
|
|
9990
|
+
coldRoot,
|
|
9991
|
+
parts.relativePath
|
|
9992
|
+
)) {
|
|
9993
|
+
const memory = await candidateStorage.storage.readMemoryByPath(candidatePath);
|
|
9994
|
+
if (memory) return { memory, baseDir: candidateStorage.dir };
|
|
9995
|
+
}
|
|
9996
|
+
} catch (err) {
|
|
9997
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
9998
|
+
continue;
|
|
9999
|
+
}
|
|
10000
|
+
}
|
|
10001
|
+
return null;
|
|
9845
10002
|
}
|
|
9846
|
-
|
|
9847
|
-
|
|
9848
|
-
|
|
9849
|
-
|
|
9850
|
-
|
|
9851
|
-
|
|
9852
|
-
|
|
9853
|
-
|
|
9854
|
-
|
|
9855
|
-
|
|
9856
|
-
|
|
9857
|
-
|
|
9858
|
-
|
|
9859
|
-
|
|
9860
|
-
|
|
9861
|
-
try {
|
|
9862
|
-
const manifestStat = await nodeFs.lstat(manifestPath);
|
|
9863
|
-
if (manifestStat.isSymbolicLink() || !manifestStat.isFile()) {
|
|
9864
|
-
capsules.push({
|
|
9865
|
-
id,
|
|
9866
|
-
archivePath,
|
|
9867
|
-
manifestPath: manifestPathOrNull,
|
|
9868
|
-
createdAt,
|
|
9869
|
-
pluginVersion,
|
|
9870
|
-
fileCount,
|
|
9871
|
-
description
|
|
9872
|
-
});
|
|
9873
|
-
continue;
|
|
10003
|
+
const collectionNamespace = parts ? collectionNamespaceFromPrefix(parts.collection) : null;
|
|
10004
|
+
if (parts && collectionNamespace) {
|
|
10005
|
+
try {
|
|
10006
|
+
const collectionStorage = await this.deps.orchestrator.getStorage(collectionNamespace);
|
|
10007
|
+
for (const candidate of qmdResultPathCandidates(
|
|
10008
|
+
collectionStorage.dir,
|
|
10009
|
+
parts.relativePath
|
|
10010
|
+
)) {
|
|
10011
|
+
const memory = await collectionStorage.readMemoryByPath(candidate);
|
|
10012
|
+
if (memory) return { memory, baseDir: collectionStorage.dir };
|
|
10013
|
+
}
|
|
10014
|
+
return null;
|
|
10015
|
+
} catch (err) {
|
|
10016
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
10017
|
+
return null;
|
|
9874
10018
|
}
|
|
9875
|
-
|
|
9876
|
-
|
|
9877
|
-
|
|
9878
|
-
|
|
9879
|
-
|
|
9880
|
-
|
|
9881
|
-
|
|
9882
|
-
|
|
9883
|
-
const
|
|
9884
|
-
|
|
9885
|
-
|
|
10019
|
+
}
|
|
10020
|
+
if (nodePath2.isAbsolute(memoryPath)) {
|
|
10021
|
+
const ownerStorage = await this.deps.storageForAbsoluteRecallPath(
|
|
10022
|
+
memoryPath,
|
|
10023
|
+
namespace,
|
|
10024
|
+
recallNamespaces
|
|
10025
|
+
);
|
|
10026
|
+
if (!ownerStorage) return null;
|
|
10027
|
+
for (const candidate of qmdResultPathCandidates(
|
|
10028
|
+
ownerStorage.dir,
|
|
10029
|
+
memoryPath
|
|
10030
|
+
)) {
|
|
10031
|
+
const memory = await ownerStorage.storage.readMemoryByPath(candidate);
|
|
10032
|
+
if (memory) return { memory, baseDir: ownerStorage.dir };
|
|
9886
10033
|
}
|
|
10034
|
+
return null;
|
|
9887
10035
|
}
|
|
9888
|
-
|
|
9889
|
-
|
|
9890
|
-
|
|
9891
|
-
|
|
9892
|
-
|
|
9893
|
-
|
|
9894
|
-
|
|
9895
|
-
|
|
9896
|
-
|
|
9897
|
-
|
|
9898
|
-
|
|
9899
|
-
|
|
9900
|
-
|
|
9901
|
-
|
|
9902
|
-
|
|
9903
|
-
|
|
9904
|
-
|
|
9905
|
-
|
|
9906
|
-
|
|
9907
|
-
|
|
9908
|
-
|
|
9909
|
-
|
|
9910
|
-
|
|
10036
|
+
for (const candidate of qmdResultPathCandidates(storageDir, memoryPath)) {
|
|
10037
|
+
const memory = await storage.readMemoryByPath(candidate);
|
|
10038
|
+
if (memory) return { memory, baseDir: storageDir };
|
|
10039
|
+
}
|
|
10040
|
+
return null;
|
|
10041
|
+
};
|
|
10042
|
+
const rawExcerptsResult = rawContext?.rawExcerptsSuppressed === true ? (
|
|
10043
|
+
// Implicit raw recall with NO readable LCM namespace (#1505 thread
|
|
10044
|
+
// NBHWz): emit empty excerpts rather than falling back to the
|
|
10045
|
+
// write/overlay `namespace` the read gate excludes.
|
|
10046
|
+
[]
|
|
10047
|
+
) : await this.deps.fetchRawExcerpts(
|
|
10048
|
+
disclosure,
|
|
10049
|
+
rawContext ? {
|
|
10050
|
+
query: rawContext.query,
|
|
10051
|
+
...rawContext.sessionKey ? { sessionKey: rawContext.sessionKey } : {},
|
|
10052
|
+
namespace: rawContext.rawExcerptNamespace ?? namespace,
|
|
10053
|
+
...rawContext.rawExcerptSessionIds !== void 0 ? { lcmSessionIds: rawContext.rawExcerptSessionIds } : {}
|
|
10054
|
+
} : null
|
|
10055
|
+
);
|
|
10056
|
+
const rawExcerpts = rawExcerptsResult ?? void 0;
|
|
10057
|
+
for (const memoryPath of snapshot.resultPaths ?? []) {
|
|
10058
|
+
if (!memoryPath || seen.has(memoryPath)) continue;
|
|
10059
|
+
const resolved = await readResultPath(memoryPath);
|
|
10060
|
+
if (!resolved) continue;
|
|
10061
|
+
const { memory, baseDir } = resolved;
|
|
10062
|
+
seen.add(memoryPath);
|
|
10063
|
+
results.push(
|
|
10064
|
+
this.deps.serializeMemorySummary(
|
|
10065
|
+
memory,
|
|
10066
|
+
baseDir,
|
|
10067
|
+
disclosure,
|
|
10068
|
+
// Attach the (possibly empty) raw excerpts to the first raw
|
|
10069
|
+
// result; subsequent results do not duplicate the array.
|
|
10070
|
+
results.length === 0 ? rawExcerpts : void 0
|
|
10071
|
+
)
|
|
9911
10072
|
);
|
|
9912
10073
|
}
|
|
9913
|
-
return
|
|
9914
|
-
|
|
9915
|
-
|
|
9916
|
-
|
|
9917
|
-
|
|
9918
|
-
|
|
9919
|
-
|
|
9920
|
-
|
|
9921
|
-
|
|
9922
|
-
|
|
9923
|
-
|
|
9924
|
-
|
|
9925
|
-
|
|
9926
|
-
readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
|
|
9927
|
-
readFileDigest: async ({ filePath }) => storage.digestOfflineSyncFile(filePath),
|
|
9928
|
-
signal: options.signal,
|
|
9929
|
-
userExcludeRegexps: this.offlineSyncUserExcludes
|
|
9930
|
-
});
|
|
9931
|
-
return {
|
|
9932
|
-
namespace: resolvedNamespace,
|
|
9933
|
-
...snapshot
|
|
9934
|
-
};
|
|
9935
|
-
}
|
|
9936
|
-
async offlineSyncSnapshotStream(options = {}) {
|
|
9937
|
-
const resolvedNamespace = this.resolveReadableNamespace(options.namespace, options.principal);
|
|
9938
|
-
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
9939
|
-
const storageHash = createHash4("sha256").update(storage.dir).digest("hex").slice(0, 16);
|
|
9940
|
-
return {
|
|
9941
|
-
namespace: resolvedNamespace,
|
|
9942
|
-
format: OFFLINE_SYNC_SNAPSHOT_FORMAT,
|
|
9943
|
-
schemaVersion: 1,
|
|
9944
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9945
|
-
sourceId: `remnic:${resolvedNamespace}:${storageHash}`,
|
|
9946
|
-
includeTranscripts: options.includeTranscripts !== false,
|
|
9947
|
-
files: iterateOfflineSyncSnapshotFileRecords({
|
|
9948
|
-
root: storage.dir,
|
|
9949
|
-
includeContent: options.includeContent === true,
|
|
9950
|
-
includeTranscripts: options.includeTranscripts !== false,
|
|
9951
|
-
readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
|
|
9952
|
-
readFileDigest: async ({ filePath }) => storage.digestOfflineSyncFile(filePath),
|
|
9953
|
-
signal: options.signal,
|
|
9954
|
-
userExcludeRegexps: this.offlineSyncUserExcludes
|
|
9955
|
-
})
|
|
9956
|
-
};
|
|
9957
|
-
}
|
|
9958
|
-
async offlineSyncFiles(options) {
|
|
9959
|
-
const resolvedNamespace = this.resolveReadableNamespace(options.namespace, options.principal);
|
|
9960
|
-
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
9961
|
-
const storageHash = createHash4("sha256").update(storage.dir).digest("hex").slice(0, 16);
|
|
9962
|
-
try {
|
|
9963
|
-
const snapshot = await buildOfflineSyncSnapshotForPaths({
|
|
9964
|
-
root: storage.dir,
|
|
9965
|
-
sourceId: `remnic:${resolvedNamespace}:${storageHash}`,
|
|
9966
|
-
paths: options.paths,
|
|
9967
|
-
includeContent: true,
|
|
9968
|
-
includeTranscripts: options.includeTranscripts !== false,
|
|
9969
|
-
readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
|
|
9970
|
-
userExcludeRegexps: this.offlineSyncUserExcludes
|
|
9971
|
-
});
|
|
9972
|
-
return {
|
|
9973
|
-
namespace: resolvedNamespace,
|
|
9974
|
-
...snapshot
|
|
9975
|
-
};
|
|
9976
|
-
} catch (error) {
|
|
9977
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
9978
|
-
if (message.startsWith("paths[]:") || message.startsWith("buildOfflineSyncSnapshotForPaths: record path ") || message.startsWith("offline sync snapshot path is excluded:")) {
|
|
9979
|
-
throw new EngramAccessInputError(message);
|
|
9980
|
-
}
|
|
9981
|
-
throw error;
|
|
10074
|
+
if (results.length > 0) return results;
|
|
10075
|
+
for (const memoryId of snapshot.memoryIds) {
|
|
10076
|
+
const memory = await storage.getMemoryById(memoryId);
|
|
10077
|
+
if (!memory || seen.has(memory.path)) continue;
|
|
10078
|
+
seen.add(memory.path);
|
|
10079
|
+
results.push(
|
|
10080
|
+
this.deps.serializeMemorySummary(
|
|
10081
|
+
memory,
|
|
10082
|
+
storageDir,
|
|
10083
|
+
disclosure,
|
|
10084
|
+
results.length === 0 ? rawExcerpts : void 0
|
|
10085
|
+
)
|
|
10086
|
+
);
|
|
9982
10087
|
}
|
|
10088
|
+
return results;
|
|
9983
10089
|
}
|
|
9984
|
-
|
|
9985
|
-
|
|
9986
|
-
|
|
10090
|
+
/**
|
|
10091
|
+
* Fetch raw transcript excerpts from the LCM archive for `disclosure ===
|
|
10092
|
+
* "raw"` recalls (issue #677 PR 2/4). Returns `null` for non-raw recall
|
|
10093
|
+
* depths, an empty array when LCM is disabled / not initialized / has no
|
|
10094
|
+
* matches, and an array of LCM-side excerpts otherwise. Errors are
|
|
10095
|
+
* swallowed and treated as "no excerpts" so a failing LCM never breaks
|
|
10096
|
+
* the recall response.
|
|
10097
|
+
*
|
|
10098
|
+
* Namespace handling: LCM archival prefixes non-default-namespace
|
|
10099
|
+
* sessions with `${namespace}:${sessionKey}` (see `observe()` around
|
|
10100
|
+
* line 2498), so the lookup must mirror that prefix or raw recalls in
|
|
10101
|
+
* non-default namespaces miss their own excerpts.
|
|
10102
|
+
*/
|
|
10103
|
+
async fetchRawExcerpts(disclosure, context) {
|
|
10104
|
+
if (disclosure !== "raw") return null;
|
|
10105
|
+
if (!context || !context.query) return [];
|
|
10106
|
+
if (!context.sessionKey) return [];
|
|
10107
|
+
const lcm = this.deps.orchestrator.lcmEngine;
|
|
10108
|
+
if (!lcm || !lcm.enabled) return [];
|
|
9987
10109
|
try {
|
|
9988
|
-
const
|
|
9989
|
-
|
|
9990
|
-
|
|
9991
|
-
|
|
9992
|
-
|
|
9993
|
-
|
|
9994
|
-
|
|
9995
|
-
|
|
9996
|
-
|
|
9997
|
-
|
|
9998
|
-
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
|
|
10002
|
-
|
|
10003
|
-
|
|
10004
|
-
|
|
10110
|
+
const legacyKey = context.namespace && context.namespace !== this.deps.orchestrator.config.defaultNamespace ? `${context.namespace}:${context.sessionKey}` : context.sessionKey;
|
|
10111
|
+
const lcmSessionIds = context.lcmSessionIds !== void 0 ? context.lcmSessionIds : [legacyKey];
|
|
10112
|
+
const limit = 5;
|
|
10113
|
+
const seenRows = /* @__PURE__ */ new Set();
|
|
10114
|
+
const excerpts = [];
|
|
10115
|
+
const settledRows = await Promise.allSettled(
|
|
10116
|
+
lcmSessionIds.map(
|
|
10117
|
+
async (lcmSessionKey) => lcm.searchContextFull(context.query, limit, lcmSessionKey)
|
|
10118
|
+
)
|
|
10119
|
+
);
|
|
10120
|
+
for (const result of settledRows) {
|
|
10121
|
+
if (excerpts.length >= limit) break;
|
|
10122
|
+
if (result.status !== "fulfilled") continue;
|
|
10123
|
+
for (const r of result.value) {
|
|
10124
|
+
const dedupeKey = `${r.session_id} ${r.turn_index}`;
|
|
10125
|
+
if (seenRows.has(dedupeKey)) continue;
|
|
10126
|
+
seenRows.add(dedupeKey);
|
|
10127
|
+
excerpts.push({
|
|
10128
|
+
turnIndex: r.turn_index,
|
|
10129
|
+
role: r.role,
|
|
10130
|
+
content: r.content,
|
|
10131
|
+
sessionId: r.session_id
|
|
10132
|
+
});
|
|
10133
|
+
if (excerpts.length >= limit) break;
|
|
10134
|
+
}
|
|
10005
10135
|
}
|
|
10006
|
-
|
|
10136
|
+
return excerpts;
|
|
10137
|
+
} catch {
|
|
10138
|
+
return [];
|
|
10007
10139
|
}
|
|
10008
10140
|
}
|
|
10009
|
-
async
|
|
10010
|
-
const
|
|
10011
|
-
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
|
|
10015
|
-
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
10016
|
-
try {
|
|
10017
|
-
const result = await applyOfflineSyncFileContentChunk({
|
|
10018
|
-
root: storage.dir,
|
|
10019
|
-
sourceId: options.sourceId,
|
|
10020
|
-
path: options.path,
|
|
10021
|
-
sha256: options.sha256,
|
|
10022
|
-
bytes: options.bytes,
|
|
10023
|
-
mtimeMs: options.mtimeMs,
|
|
10024
|
-
offset: options.offset,
|
|
10025
|
-
baseSha256: options.baseSha256,
|
|
10026
|
-
content: options.content,
|
|
10027
|
-
includeTranscripts: options.includeTranscripts !== false,
|
|
10028
|
-
readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
|
|
10029
|
-
readFileDigest: async ({ filePath }) => storage.digestOfflineSyncFile(filePath),
|
|
10030
|
-
writeFile: async ({ filePath, content }) => storage.writeOfflineSyncFile(filePath, content),
|
|
10031
|
-
writeStagingFile: async ({ filePath, content }) => storage.writeOfflineSyncStagingFile(filePath, content),
|
|
10032
|
-
writeFileChunks: async ({ filePath, chunks }) => storage.writeOfflineSyncFileChunks(filePath, chunks)
|
|
10033
|
-
});
|
|
10034
|
-
return {
|
|
10035
|
-
namespace: resolvedNamespace,
|
|
10036
|
-
...result
|
|
10037
|
-
};
|
|
10038
|
-
} catch (error) {
|
|
10039
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
10040
|
-
if (message.startsWith("offline sync") || message.startsWith("path:") || message.startsWith("sourceId must ") || message.startsWith("sha256 must ") || message.startsWith("baseSha256 must ") || message.startsWith("bytes must ") || message.startsWith("mtimeMs must ") || message.startsWith("offset must ") || message.startsWith("content chunk ") || message === "content must be a Buffer") {
|
|
10041
|
-
throw new EngramAccessInputError(message);
|
|
10141
|
+
async executeRecall(request) {
|
|
10142
|
+
const query = request.query;
|
|
10143
|
+
const callerProvidedDisclosure = request.disclosure !== void 0 && request.disclosure !== null;
|
|
10144
|
+
const requestedDisclosure = (() => {
|
|
10145
|
+
if (!callerProvidedDisclosure) {
|
|
10146
|
+
return DEFAULT_RECALL_DISCLOSURE;
|
|
10042
10147
|
}
|
|
10043
|
-
|
|
10148
|
+
if (!isRecallDisclosure(request.disclosure)) {
|
|
10149
|
+
throw new EngramAccessInputError(
|
|
10150
|
+
`disclosure must be one of: chunk, section, raw (got: ${String(request.disclosure)})`
|
|
10151
|
+
);
|
|
10152
|
+
}
|
|
10153
|
+
return request.disclosure;
|
|
10154
|
+
})();
|
|
10155
|
+
if (request.codingContext !== void 0 && request.sessionKey) {
|
|
10156
|
+
this.deps.setCodingContext({
|
|
10157
|
+
sessionKey: request.sessionKey,
|
|
10158
|
+
codingContext: request.codingContext
|
|
10159
|
+
});
|
|
10044
10160
|
}
|
|
10045
|
-
|
|
10046
|
-
|
|
10047
|
-
|
|
10048
|
-
|
|
10049
|
-
void 0,
|
|
10050
|
-
options.principal
|
|
10051
|
-
);
|
|
10052
|
-
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
10053
|
-
try {
|
|
10054
|
-
const result = await applyOfflineSyncChangeset({
|
|
10055
|
-
root: storage.dir,
|
|
10056
|
-
changeset: options.changeset,
|
|
10057
|
-
returnCurrentFiles: options.returnCurrentFiles,
|
|
10058
|
-
readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
|
|
10059
|
-
readFileDigest: async ({ filePath }) => storage.digestOfflineSyncFile(filePath),
|
|
10060
|
-
writeFile: async ({ filePath, content }) => storage.writeOfflineSyncFile(filePath, content),
|
|
10061
|
-
deleteFile: async ({ filePath }) => storage.deleteOfflineSyncFile(filePath)
|
|
10161
|
+
if (request.codingContext === void 0 && request.sessionKey) {
|
|
10162
|
+
await this.deps.maybeAttachCodingContext(request.sessionKey, {
|
|
10163
|
+
cwd: request.cwd,
|
|
10164
|
+
projectTag: request.projectTag
|
|
10062
10165
|
});
|
|
10063
|
-
|
|
10064
|
-
|
|
10065
|
-
|
|
10166
|
+
}
|
|
10167
|
+
const authenticatedPrincipal = request.authenticatedPrincipal?.trim();
|
|
10168
|
+
const namespaceOverride = this.deps.resolveRecallNamespace(
|
|
10169
|
+
request.namespace,
|
|
10170
|
+
request.sessionKey,
|
|
10171
|
+
authenticatedPrincipal
|
|
10172
|
+
);
|
|
10173
|
+
const namespace = namespaceOverride ?? this.deps.orchestrator.config.defaultNamespace;
|
|
10174
|
+
const mode = this.deps.normalizeRecallMode(request.mode);
|
|
10175
|
+
const maybePrincipal = this.deps.resolveRequestPrincipal(request.sessionKey, authenticatedPrincipal);
|
|
10176
|
+
if (resolveNamespaceCapabilities(this.deps.orchestrator.config).namespaces && !maybePrincipal) {
|
|
10177
|
+
throw new EngramAccessInputError(
|
|
10178
|
+
"authentication required: namespaces are enabled and no principal was supplied"
|
|
10179
|
+
);
|
|
10180
|
+
}
|
|
10181
|
+
const principal = maybePrincipal ?? "default";
|
|
10182
|
+
const principalNamespace = defaultNamespaceForPrincipal(principal, this.deps.orchestrator.config);
|
|
10183
|
+
const profileCodingContext = request.sessionKey && typeof this.deps.orchestrator.getCodingContextForSession === "function" ? this.deps.orchestrator.getCodingContextForSession(request.sessionKey) : null;
|
|
10184
|
+
const profileCodingOverlay = !namespaceOverride && profileCodingContext && resolveNamespaceCapabilities(this.deps.orchestrator.config).namespaces && this.deps.orchestrator.config.codingMode?.projectScope ? resolveCodingNamespaceOverlay(
|
|
10185
|
+
profileCodingContext,
|
|
10186
|
+
this.deps.orchestrator.config.codingMode,
|
|
10187
|
+
this.deps.orchestrator.config.defaultNamespace
|
|
10188
|
+
) : null;
|
|
10189
|
+
const profilePlan = namespaceOverride ? null : resolveScopeProfilePlan({
|
|
10190
|
+
config: this.deps.orchestrator.config,
|
|
10191
|
+
principal,
|
|
10192
|
+
codingContext: profileCodingContext,
|
|
10193
|
+
codingOverlay: profileCodingOverlay
|
|
10194
|
+
});
|
|
10195
|
+
const modeSkipsBudget = mode === "no_recall";
|
|
10196
|
+
const legacyRecallNamespaces = Array.isArray(this.deps.orchestrator.config.defaultRecallNamespaces) ? recallNamespacesForPrincipal(principal, this.deps.orchestrator.config) : [];
|
|
10197
|
+
const effectiveNamespaces = namespaceOverride ? [namespaceOverride] : profilePlan ? expandScopeProfileReadNamespaces({
|
|
10198
|
+
profilePlan,
|
|
10199
|
+
principalSelfNamespace: profilePlan.baseNamespace,
|
|
10200
|
+
config: this.deps.orchestrator.config,
|
|
10201
|
+
principal,
|
|
10202
|
+
codingOverlay: profileCodingOverlay,
|
|
10203
|
+
legacyRecallNamespaces
|
|
10204
|
+
}) : legacyRecallNamespaces;
|
|
10205
|
+
const budgetPrincipalNamespace = profilePlan?.baseNamespace ?? principalNamespace;
|
|
10206
|
+
let budgetDecision;
|
|
10207
|
+
let recordBudgetAfterSuccess = false;
|
|
10208
|
+
if (modeSkipsBudget) {
|
|
10209
|
+
budgetDecision = {
|
|
10210
|
+
allowed: true,
|
|
10211
|
+
reason: "allowed-same-namespace",
|
|
10212
|
+
count: 0,
|
|
10213
|
+
limit: {
|
|
10214
|
+
softLimit: this.deps.orchestrator.config.recallCrossNamespaceBudgetSoftLimit ?? 10,
|
|
10215
|
+
hardLimit: this.deps.orchestrator.config.recallCrossNamespaceBudgetHardLimit ?? 30,
|
|
10216
|
+
windowMs: this.deps.orchestrator.config.recallCrossNamespaceBudgetWindowMs ?? 6e4
|
|
10217
|
+
}
|
|
10066
10218
|
};
|
|
10067
|
-
}
|
|
10068
|
-
|
|
10069
|
-
|
|
10070
|
-
|
|
10219
|
+
} else {
|
|
10220
|
+
let anyCrossNamespace = false;
|
|
10221
|
+
let denied = null;
|
|
10222
|
+
let crossNamespaceDecision = null;
|
|
10223
|
+
for (const ns of effectiveNamespaces) {
|
|
10224
|
+
const peek = this.deps.budget.peek({
|
|
10225
|
+
principal,
|
|
10226
|
+
principalNamespace: budgetPrincipalNamespace,
|
|
10227
|
+
queryNamespace: ns
|
|
10228
|
+
});
|
|
10229
|
+
if (peek.reason !== "allowed-same-namespace") {
|
|
10230
|
+
anyCrossNamespace = true;
|
|
10231
|
+
crossNamespaceDecision ??= peek;
|
|
10232
|
+
}
|
|
10233
|
+
if (!peek.allowed) {
|
|
10234
|
+
denied = peek;
|
|
10235
|
+
break;
|
|
10236
|
+
}
|
|
10071
10237
|
}
|
|
10072
|
-
|
|
10238
|
+
if (denied) {
|
|
10239
|
+
budgetDecision = denied;
|
|
10240
|
+
} else if (anyCrossNamespace) {
|
|
10241
|
+
budgetDecision = crossNamespaceDecision ?? {
|
|
10242
|
+
allowed: true,
|
|
10243
|
+
reason: "allowed-under-soft",
|
|
10244
|
+
count: 0,
|
|
10245
|
+
limit: {
|
|
10246
|
+
softLimit: this.deps.orchestrator.config.recallCrossNamespaceBudgetSoftLimit ?? 10,
|
|
10247
|
+
hardLimit: this.deps.orchestrator.config.recallCrossNamespaceBudgetHardLimit ?? 30,
|
|
10248
|
+
windowMs: this.deps.orchestrator.config.recallCrossNamespaceBudgetWindowMs ?? 6e4
|
|
10249
|
+
}
|
|
10250
|
+
};
|
|
10251
|
+
recordBudgetAfterSuccess = true;
|
|
10252
|
+
} else {
|
|
10253
|
+
budgetDecision = {
|
|
10254
|
+
allowed: true,
|
|
10255
|
+
reason: "allowed-same-namespace",
|
|
10256
|
+
count: 0,
|
|
10257
|
+
limit: {
|
|
10258
|
+
softLimit: this.deps.orchestrator.config.recallCrossNamespaceBudgetSoftLimit ?? 10,
|
|
10259
|
+
hardLimit: this.deps.orchestrator.config.recallCrossNamespaceBudgetHardLimit ?? 30,
|
|
10260
|
+
windowMs: this.deps.orchestrator.config.recallCrossNamespaceBudgetWindowMs ?? 6e4
|
|
10261
|
+
}
|
|
10262
|
+
};
|
|
10263
|
+
}
|
|
10264
|
+
if (!budgetDecision.allowed) {
|
|
10265
|
+
throw new EngramAccessInputError(
|
|
10266
|
+
`recall denied: cross-namespace budget exceeded (${budgetDecision.count}/${budgetDecision.limit.hardLimit} in ${budgetDecision.limit.windowMs}ms window)`
|
|
10267
|
+
);
|
|
10268
|
+
}
|
|
10269
|
+
this.deps.budget.gc();
|
|
10073
10270
|
}
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
|
|
10271
|
+
const topK = Number.isFinite(request.topK) ? Math.max(0, Math.floor(request.topK ?? 0)) : void 0;
|
|
10272
|
+
let asOf;
|
|
10273
|
+
if (request.asOf !== void 0 && request.asOf !== null) {
|
|
10274
|
+
if (typeof request.asOf !== "string" || request.asOf.trim().length === 0) {
|
|
10275
|
+
throw new EngramAccessInputError(
|
|
10276
|
+
"asOf must be a non-empty ISO 8601 timestamp string"
|
|
10277
|
+
);
|
|
10278
|
+
}
|
|
10279
|
+
const parsed = Date.parse(request.asOf);
|
|
10280
|
+
if (!Number.isFinite(parsed)) {
|
|
10281
|
+
throw new EngramAccessInputError(
|
|
10282
|
+
`asOf must be a parseable ISO 8601 timestamp (got: "${request.asOf}")`
|
|
10283
|
+
);
|
|
10284
|
+
}
|
|
10285
|
+
asOf = request.asOf;
|
|
10086
10286
|
}
|
|
10087
|
-
const
|
|
10088
|
-
|
|
10089
|
-
|
|
10090
|
-
|
|
10091
|
-
|
|
10092
|
-
|
|
10093
|
-
|
|
10094
|
-
|
|
10095
|
-
|
|
10096
|
-
|
|
10097
|
-
|
|
10098
|
-
|
|
10099
|
-
const
|
|
10100
|
-
const
|
|
10101
|
-
|
|
10287
|
+
const recallOptions = {
|
|
10288
|
+
namespace: namespaceOverride,
|
|
10289
|
+
topK,
|
|
10290
|
+
mode,
|
|
10291
|
+
...authenticatedPrincipal ? { principalOverride: authenticatedPrincipal } : {},
|
|
10292
|
+
...asOf !== void 0 ? { asOf } : {},
|
|
10293
|
+
...request.includeLowConfidence === true ? { includeLowConfidence: true } : {}
|
|
10294
|
+
};
|
|
10295
|
+
const startedAt = Date.now();
|
|
10296
|
+
const context = await this.deps.orchestrator.recall(query, request.sessionKey, recallOptions);
|
|
10297
|
+
const snapshot = request.sessionKey ? this.deps.orchestrator.lastRecall.get(request.sessionKey) : null;
|
|
10298
|
+
const effectiveNamespace = snapshot?.namespace ? this.deps.resolveNamespace(snapshot.namespace) : namespace;
|
|
10299
|
+
const resultsReturned = snapshot?.memoryIds?.length ?? 0;
|
|
10300
|
+
const appliedTopK = snapshot?.budgetsApplied?.appliedTopK;
|
|
10301
|
+
const configMaxResults = typeof this.deps.orchestrator.config.qmdMaxResults === "number" && Number.isFinite(this.deps.orchestrator.config.qmdMaxResults) && this.deps.orchestrator.config.qmdMaxResults > 0 ? this.deps.orchestrator.config.qmdMaxResults : 0;
|
|
10302
|
+
const topKDenominator = typeof appliedTopK === "number" && Number.isFinite(appliedTopK) && appliedTopK > 0 ? Math.max(appliedTopK, resultsReturned) : typeof topK === "number" && topK > 0 ? Math.max(topK, resultsReturned) : Math.max(configMaxResults, resultsReturned, 1);
|
|
10303
|
+
const topKConfidence = snapshot && topKDenominator > 0 ? Math.min(1, resultsReturned / topKDenominator) : void 0;
|
|
10304
|
+
const escalationDecision = decideDisclosureEscalation({
|
|
10305
|
+
mode: this.deps.orchestrator.config.recallDisclosureEscalation,
|
|
10306
|
+
threshold: this.deps.orchestrator.config.recallDisclosureEscalationThreshold,
|
|
10307
|
+
originalDisclosure: requestedDisclosure,
|
|
10308
|
+
callerProvidedDisclosure,
|
|
10309
|
+
topKConfidence
|
|
10310
|
+
});
|
|
10311
|
+
const disclosure = escalationDecision.effective;
|
|
10312
|
+
const trimmedSessionKey = request.sessionKey?.trim() || void 0;
|
|
10313
|
+
const rawExcerptNamespace = disclosure === "raw" ? this.deps.resolveRawExcerptReadNamespace(
|
|
10314
|
+
request.namespace,
|
|
10315
|
+
trimmedSessionKey,
|
|
10316
|
+
authenticatedPrincipal
|
|
10317
|
+
) : void 0;
|
|
10318
|
+
const hasExplicitNamespace = typeof request.namespace === "string" && request.namespace.trim().length > 0;
|
|
10319
|
+
const rawExcerptsSuppressed = disclosure === "raw" && !hasExplicitNamespace && rawExcerptNamespace === void 0;
|
|
10320
|
+
const rawExcerptSessionIds = disclosure === "raw" && rawExcerptNamespace && trimmedSessionKey ? this.deps.resolveLcmReadSessionIds(
|
|
10321
|
+
request.namespace,
|
|
10322
|
+
rawExcerptNamespace,
|
|
10323
|
+
trimmedSessionKey,
|
|
10324
|
+
authenticatedPrincipal
|
|
10325
|
+
) : void 0;
|
|
10326
|
+
let results = await this.deps.serializeRecallResults(snapshot, disclosure, {
|
|
10327
|
+
query,
|
|
10328
|
+
sessionKey: trimmedSessionKey,
|
|
10329
|
+
...rawExcerptNamespace ? { rawExcerptNamespace } : {},
|
|
10330
|
+
...rawExcerptSessionIds !== void 0 ? { rawExcerptSessionIds } : {},
|
|
10331
|
+
...rawExcerptsSuppressed ? { rawExcerptsSuppressed } : {}
|
|
10332
|
+
});
|
|
10333
|
+
const filterTags = normalizeTags(request.tags);
|
|
10334
|
+
let tagMatchMode;
|
|
10335
|
+
try {
|
|
10336
|
+
tagMatchMode = parseTagMatch(request.tagMatch);
|
|
10337
|
+
} catch (err) {
|
|
10102
10338
|
throw new EngramAccessInputError(
|
|
10103
|
-
|
|
10339
|
+
err instanceof Error ? err.message : String(err)
|
|
10104
10340
|
);
|
|
10105
10341
|
}
|
|
10106
|
-
|
|
10107
|
-
if (
|
|
10108
|
-
|
|
10109
|
-
|
|
10110
|
-
|
|
10342
|
+
let effectiveContext = context;
|
|
10343
|
+
if (filterTags && filterTags.length > 0) {
|
|
10344
|
+
const beforeIds = results.map((r) => r.id);
|
|
10345
|
+
const { results: admitted } = applyTagFilter(results, {
|
|
10346
|
+
tags: filterTags,
|
|
10347
|
+
tagMatch: tagMatchMode
|
|
10348
|
+
});
|
|
10349
|
+
results = admitted;
|
|
10350
|
+
const admittedIds = new Set(results.map((r) => r.id));
|
|
10351
|
+
const droppedAny = beforeIds.some((id) => !admittedIds.has(id));
|
|
10352
|
+
if (droppedAny) {
|
|
10353
|
+
effectiveContext = results.map((r) => {
|
|
10354
|
+
const content = typeof r.content === "string" ? r.content ?? "" : "";
|
|
10355
|
+
const preview = typeof r.preview === "string" ? r.preview ?? "" : "";
|
|
10356
|
+
return content || preview;
|
|
10357
|
+
}).filter((s) => s.length > 0).join("\n\n");
|
|
10358
|
+
}
|
|
10111
10359
|
}
|
|
10112
|
-
const
|
|
10113
|
-
const
|
|
10114
|
-
|
|
10115
|
-
|
|
10116
|
-
|
|
10360
|
+
const filteredMemoryIds = filterTags && filterTags.length > 0 ? results.map((r) => r.id) : snapshot?.memoryIds ?? [];
|
|
10361
|
+
const debug = await this.deps.buildRecallDebug(
|
|
10362
|
+
snapshot,
|
|
10363
|
+
effectiveNamespace,
|
|
10364
|
+
request.includeDebug === true,
|
|
10365
|
+
request.sessionKey
|
|
10117
10366
|
);
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
const
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10367
|
+
let auditAnomalies;
|
|
10368
|
+
if (this.deps.auditAdapter) {
|
|
10369
|
+
try {
|
|
10370
|
+
const resolvedAgentId = principal ?? "__anonymous__";
|
|
10371
|
+
const auditEntry = {
|
|
10372
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10373
|
+
sessionKey: request.sessionKey ?? "",
|
|
10374
|
+
agentId: resolvedAgentId,
|
|
10375
|
+
trigger: "access-surface",
|
|
10376
|
+
queryText: query,
|
|
10377
|
+
candidateMemoryIds: snapshot?.memoryIds ?? [],
|
|
10378
|
+
// Audit must reflect what was actually injected, not what
|
|
10379
|
+
// recall produced before the tag filter. Using `context`
|
|
10380
|
+
// (pre-filter) overstates injectedChars and can leak content
|
|
10381
|
+
// from excluded memories into the audit summary (cursor
|
|
10382
|
+
// Medium on PR #712).
|
|
10383
|
+
summary: effectiveContext.slice(0, 200) || null,
|
|
10384
|
+
injectedChars: effectiveContext.length,
|
|
10385
|
+
toggleState: "enabled",
|
|
10386
|
+
latencyMs: Date.now() - startedAt,
|
|
10387
|
+
plannerMode: snapshot?.plannerMode ?? mode,
|
|
10388
|
+
requestedMode: mode,
|
|
10389
|
+
fallbackUsed: snapshot?.fallbackUsed ?? false
|
|
10126
10390
|
};
|
|
10391
|
+
const auditResult = await this.deps.auditAdapter.record(
|
|
10392
|
+
resolvedAgentId || "__anonymous__",
|
|
10393
|
+
auditEntry
|
|
10394
|
+
);
|
|
10395
|
+
auditAnomalies = auditResult.anomalies;
|
|
10396
|
+
} catch {
|
|
10127
10397
|
}
|
|
10128
|
-
|
|
10129
|
-
dryRun: false,
|
|
10130
|
-
storage
|
|
10131
|
-
});
|
|
10132
|
-
const itemsProcessed = result2.clusters.reduce(
|
|
10133
|
-
(sum, cluster) => sum + cluster.memories.length,
|
|
10134
|
-
0
|
|
10135
|
-
);
|
|
10136
|
-
return {
|
|
10137
|
-
itemsProcessed,
|
|
10138
|
-
notes: `REM consolidation found ${result2.clustersFound} clusters`
|
|
10139
|
-
};
|
|
10140
|
-
};
|
|
10141
|
-
const governanceRunner = options.phase === "deepSleep" ? async (_opts) => {
|
|
10142
|
-
return this.orchestrator.runDeepSleepGovernanceNow({
|
|
10143
|
-
storage,
|
|
10144
|
-
dryRun: _opts.dryRun
|
|
10145
|
-
});
|
|
10146
|
-
} : void 0;
|
|
10147
|
-
const result = await runDreamsPhase(
|
|
10148
|
-
{ memoryDir, phase: options.phase, dryRun },
|
|
10149
|
-
governanceRunner,
|
|
10150
|
-
phaseRunner
|
|
10151
|
-
);
|
|
10398
|
+
}
|
|
10152
10399
|
return {
|
|
10153
|
-
|
|
10154
|
-
|
|
10155
|
-
durationMs: result.durationMs,
|
|
10156
|
-
itemsProcessed: result.itemsProcessed,
|
|
10157
|
-
notes: result.notes
|
|
10158
|
-
};
|
|
10159
|
-
}
|
|
10160
|
-
// ---------------------------------------------------------------------------
|
|
10161
|
-
// Wearables (Limitless / Bee / Omi transcript ingestion)
|
|
10162
|
-
//
|
|
10163
|
-
// Thin delegations to the orchestrator-owned WearablesService — the
|
|
10164
|
-
// same instance behind the CLI, so HTTP/MCP callers observe identical
|
|
10165
|
-
// behavior and validation (renderer-sharing rule).
|
|
10166
|
-
// ---------------------------------------------------------------------------
|
|
10167
|
-
async wearablesStatus() {
|
|
10168
|
-
return this.orchestrator.getWearablesService().status();
|
|
10169
|
-
}
|
|
10170
|
-
async wearablesSync(request) {
|
|
10171
|
-
return this.orchestrator.getWearablesService().sync({
|
|
10172
|
-
source: request.source,
|
|
10173
|
-
date: request.date,
|
|
10174
|
-
days: request.days,
|
|
10175
|
-
forceMemories: request.forceMemories
|
|
10176
|
-
});
|
|
10177
|
-
}
|
|
10178
|
-
async wearablesTranscriptDay(request) {
|
|
10179
|
-
return this.orchestrator.getWearablesService().dayTranscript(request.date, request.source);
|
|
10180
|
-
}
|
|
10181
|
-
async wearablesTranscriptSearch(request) {
|
|
10182
|
-
return this.orchestrator.getWearablesService().searchTranscripts(request.query, {
|
|
10183
|
-
source: request.source,
|
|
10184
|
-
from: request.from,
|
|
10185
|
-
to: request.to,
|
|
10186
|
-
limit: request.limit
|
|
10187
|
-
});
|
|
10188
|
-
}
|
|
10189
|
-
async wearablesTranscriptMemories(request) {
|
|
10190
|
-
return this.orchestrator.getWearablesService().transcriptMemories({
|
|
10191
|
-
source: request.source,
|
|
10192
|
-
date: request.date,
|
|
10193
|
-
limit: request.limit
|
|
10194
|
-
});
|
|
10195
|
-
}
|
|
10196
|
-
// ── Admin console surfaces (issue #1502) ────────────────────────────────
|
|
10197
|
-
//
|
|
10198
|
-
// Thin delegators. Every method threads `this.orchestrator` references
|
|
10199
|
-
// into the pure `admin/admin-surfaces.ts` functions so the dashboard
|
|
10200
|
-
// never re-resolves scope, re-derives promotion targets, or re-lists
|
|
10201
|
-
// namespaces.
|
|
10202
|
-
/**
|
|
10203
|
-
* Resolve an effective scope inspection for the console. Inputs are
|
|
10204
|
-
* redacted before return so an operator-supplied principal preview /
|
|
10205
|
-
* namespace override never echoes a pasted credential back.
|
|
10206
|
-
*/
|
|
10207
|
-
async adminInspectScope(options) {
|
|
10208
|
-
const config = this.orchestrator.config;
|
|
10209
|
-
const codingContext = options.sessionKey ? this.orchestrator.getCodingContextForSession(options.sessionKey) ?? null : null;
|
|
10210
|
-
const inspection = inspectScope({
|
|
10211
|
-
config,
|
|
10212
|
-
sessionKey: options.sessionKey,
|
|
10213
|
-
namespace: options.namespace,
|
|
10214
|
-
principalOverride: options.principalOverride,
|
|
10215
|
-
codingContext,
|
|
10216
|
-
operation: options.operation
|
|
10217
|
-
});
|
|
10218
|
-
return redactSensitive(inspection);
|
|
10219
|
-
}
|
|
10220
|
-
/**
|
|
10221
|
-
* List configured + discovered namespaces from the catalog with the
|
|
10222
|
-
* admin filters the console exposes.
|
|
10223
|
-
*/
|
|
10224
|
-
async adminListNamespaces(filter) {
|
|
10225
|
-
const result = await listAdminNamespaces({
|
|
10226
|
-
catalog: this.orchestrator.namespaceCatalog,
|
|
10227
|
-
filter
|
|
10228
|
-
});
|
|
10229
|
-
return redactSensitive(result);
|
|
10230
|
-
}
|
|
10231
|
-
/**
|
|
10232
|
-
* Per-namespace maintenance + QMD health. QMD diagnostics are probed via
|
|
10233
|
-
* the orchestrator's search health accessor (one probe per namespace).
|
|
10234
|
-
*/
|
|
10235
|
-
async adminMaintenanceHealth() {
|
|
10236
|
-
const report = await gatherMaintenanceHealth({
|
|
10237
|
-
catalog: this.orchestrator.namespaceCatalog,
|
|
10238
|
-
qmdHealthProvider: async (namespace) => {
|
|
10239
|
-
try {
|
|
10240
|
-
const health = await this.orchestrator.searchHealthForNamespace(namespace);
|
|
10241
|
-
return {
|
|
10242
|
-
namespace,
|
|
10243
|
-
collection: health.collection,
|
|
10244
|
-
available: health.available,
|
|
10245
|
-
collectionState: health.collectionState,
|
|
10246
|
-
debugStatus: health.debugStatus,
|
|
10247
|
-
installedVersion: health.installedVersion,
|
|
10248
|
-
supportedVersion: health.supportedVersion,
|
|
10249
|
-
supported: health.supported,
|
|
10250
|
-
upgradeAvailable: health.upgradeAvailable,
|
|
10251
|
-
daemonMode: health.daemonMode
|
|
10252
|
-
};
|
|
10253
|
-
} catch (err) {
|
|
10254
|
-
throw err;
|
|
10255
|
-
}
|
|
10256
|
-
}
|
|
10257
|
-
});
|
|
10258
|
-
return redactSensitive(report);
|
|
10259
|
-
}
|
|
10260
|
-
/**
|
|
10261
|
-
* Dry-run transcript/session audit. Detects mixed `other/default` data
|
|
10262
|
-
* and legacy stranded directories. Never applies a migration.
|
|
10263
|
-
*/
|
|
10264
|
-
async adminTranscriptAudit() {
|
|
10265
|
-
const result = await auditTranscripts(this.orchestrator.config.memoryDir);
|
|
10266
|
-
return redactSensitive(result);
|
|
10267
|
-
}
|
|
10268
|
-
/**
|
|
10269
|
-
* Manually promote a memory into one or more authorized targets. Requires
|
|
10270
|
-
* a non-empty reason (audit-logged). Reuses the scope-profile promotion
|
|
10271
|
-
* resolution and `canWriteNamespace` gate — there is no dashboard-only
|
|
10272
|
-
* write path.
|
|
10273
|
-
*/
|
|
10274
|
-
async adminPromoteMemory(request) {
|
|
10275
|
-
const config = this.orchestrator.config;
|
|
10276
|
-
const namespacesEnabled = resolveNamespaceCapabilities(config).namespaces === true;
|
|
10277
|
-
let sourceNamespace;
|
|
10278
|
-
if (request.namespace) {
|
|
10279
|
-
sourceNamespace = this.resolveReadableNamespace(
|
|
10280
|
-
request.namespace,
|
|
10281
|
-
request.principal
|
|
10282
|
-
);
|
|
10283
|
-
} else {
|
|
10284
|
-
const codingContext2 = request.sessionKey ? this.orchestrator.getCodingContextForSession(request.sessionKey) ?? null : null;
|
|
10285
|
-
const plan = resolveScopePlan({
|
|
10286
|
-
config,
|
|
10400
|
+
response: {
|
|
10401
|
+
query,
|
|
10287
10402
|
sessionKey: request.sessionKey,
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
|
|
10294
|
-
|
|
10403
|
+
namespace: effectiveNamespace,
|
|
10404
|
+
context: effectiveContext,
|
|
10405
|
+
count: filterTags && filterTags.length > 0 ? results.length : snapshot?.memoryIds.length ?? results.length,
|
|
10406
|
+
memoryIds: filteredMemoryIds,
|
|
10407
|
+
results,
|
|
10408
|
+
recordedAt: snapshot?.recordedAt,
|
|
10409
|
+
traceId: snapshot?.traceId,
|
|
10410
|
+
plannerMode: snapshot?.plannerMode ?? mode,
|
|
10411
|
+
fallbackUsed: snapshot?.fallbackUsed ?? false,
|
|
10412
|
+
sourcesUsed: snapshot?.sourcesUsed ?? [],
|
|
10413
|
+
disclosure,
|
|
10414
|
+
budgetsApplied: snapshot?.budgetsApplied,
|
|
10415
|
+
auditAnomalies,
|
|
10416
|
+
budgetWarning: budgetDecision.reason === "warn-over-soft" ? budgetDecision : void 0,
|
|
10417
|
+
latencyMs: snapshot?.latencyMs ?? Date.now() - startedAt,
|
|
10418
|
+
debug
|
|
10419
|
+
},
|
|
10420
|
+
budgetRecordPrincipal: recordBudgetAfterSuccess ? principal : null
|
|
10421
|
+
};
|
|
10422
|
+
}
|
|
10423
|
+
/**
|
|
10424
|
+
* Recall X-ray (issue #570). Runs a recall with `xrayCapture: true`
|
|
10425
|
+
* and returns the resulting snapshot as structured JSON so every
|
|
10426
|
+
* surface (CLI / HTTP / MCP) gets the same payload. Namespace scope
|
|
10427
|
+
* is enforced before the recall fires (CLAUDE.md rule 42 — read and
|
|
10428
|
+
* write paths must resolve through the same namespace layer) so an
|
|
10429
|
+
* unauthorized principal cannot capture an x-ray for a namespace it
|
|
10430
|
+
* cannot read.
|
|
10431
|
+
*/
|
|
10432
|
+
async recallXray(request) {
|
|
10433
|
+
const query = typeof request.query === "string" ? request.query : "";
|
|
10434
|
+
if (query.trim().length === 0) {
|
|
10435
|
+
throw new Error("recallXray: query is required and must be non-empty");
|
|
10436
|
+
}
|
|
10437
|
+
if (request.disclosure !== void 0 && !isRecallDisclosure(request.disclosure)) {
|
|
10438
|
+
throw new EngramAccessInputError(
|
|
10439
|
+
`recallXray: disclosure must be one of: chunk, section, raw (got: ${String(request.disclosure)})`
|
|
10295
10440
|
);
|
|
10296
10441
|
}
|
|
10297
|
-
const
|
|
10298
|
-
const
|
|
10299
|
-
|
|
10300
|
-
|
|
10301
|
-
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
|
|
10306
|
-
|
|
10307
|
-
|
|
10308
|
-
|
|
10309
|
-
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
|
|
10313
|
-
|
|
10314
|
-
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
};
|
|
10319
|
-
},
|
|
10320
|
-
writePromotedMemory: async (namespace, memory) => {
|
|
10321
|
-
const resolved = await this.orchestrator.getStorage(namespace);
|
|
10322
|
-
const { id, tombstoneBlocked } = await resolved.writeMemory(
|
|
10323
|
-
memory.category,
|
|
10324
|
-
memory.content,
|
|
10325
|
-
{
|
|
10326
|
-
confidence: memory.confidence,
|
|
10327
|
-
tags: memory.tags,
|
|
10328
|
-
entityRef: memory.entityRef,
|
|
10329
|
-
source: `admin-promotion:${memory.sourceNamespace}:${memory.reason.slice(0, 120)}`,
|
|
10330
|
-
lineage: memory.lineage,
|
|
10331
|
-
sourceMemoryId: memory.sourceMemoryId,
|
|
10332
|
-
actor: memory.actor,
|
|
10333
|
-
validAt: memory.validAt
|
|
10334
|
-
}
|
|
10442
|
+
const namespacesEnabled = resolveNamespaceCapabilities(this.deps.orchestrator.config).namespaces;
|
|
10443
|
+
const requestedNamespace = request.namespace?.trim() ? this.deps.resolveNamespace(request.namespace) : void 0;
|
|
10444
|
+
const authenticatedPrincipal = request.authenticatedPrincipal?.trim();
|
|
10445
|
+
const principal = authenticatedPrincipal || resolvePrincipal(request.sessionKey, this.deps.orchestrator.config);
|
|
10446
|
+
if (requestedNamespace) {
|
|
10447
|
+
if (!canReadNamespace(
|
|
10448
|
+
principal,
|
|
10449
|
+
requestedNamespace,
|
|
10450
|
+
this.deps.orchestrator.config
|
|
10451
|
+
)) {
|
|
10452
|
+
return { snapshotFound: false };
|
|
10453
|
+
}
|
|
10454
|
+
} else if (namespacesEnabled && !authenticatedPrincipal && !request.sessionKey?.trim()) {
|
|
10455
|
+
return { snapshotFound: false };
|
|
10456
|
+
}
|
|
10457
|
+
let budgetOverride;
|
|
10458
|
+
if (request.budget !== void 0 && request.budget !== null) {
|
|
10459
|
+
const parsed = typeof request.budget === "number" ? request.budget : Number(request.budget);
|
|
10460
|
+
if (!Number.isFinite(parsed) || parsed <= 0 || !Number.isInteger(parsed)) {
|
|
10461
|
+
throw new Error(
|
|
10462
|
+
`recallXray: budget expects a positive integer; got ${JSON.stringify(request.budget)}`
|
|
10335
10463
|
);
|
|
10336
|
-
if (tombstoneBlocked) {
|
|
10337
|
-
throw new Error(
|
|
10338
|
-
"target namespace tombstone-blocked the promoted content (pending_review)"
|
|
10339
|
-
);
|
|
10340
|
-
}
|
|
10341
|
-
return id;
|
|
10342
10464
|
}
|
|
10465
|
+
budgetOverride = parsed;
|
|
10466
|
+
}
|
|
10467
|
+
const mode = this.deps.normalizeRecallMode(request.mode);
|
|
10468
|
+
const disclosure = request.disclosure ?? DEFAULT_RECALL_DISCLOSURE;
|
|
10469
|
+
const previousQueue = this.deps.xrayQueue;
|
|
10470
|
+
let release = () => {
|
|
10343
10471
|
};
|
|
10472
|
+
this.deps.xrayQueue = new Promise((resolve3) => {
|
|
10473
|
+
release = resolve3;
|
|
10474
|
+
});
|
|
10475
|
+
await previousQueue;
|
|
10476
|
+
const recallStartedAt = Date.now();
|
|
10477
|
+
const recallSessionKey = request.sessionKey?.trim() || void 0;
|
|
10478
|
+
let xrayResponse = { snapshotFound: false };
|
|
10344
10479
|
try {
|
|
10345
|
-
|
|
10346
|
-
|
|
10347
|
-
|
|
10348
|
-
|
|
10349
|
-
|
|
10350
|
-
|
|
10351
|
-
|
|
10352
|
-
|
|
10353
|
-
|
|
10354
|
-
|
|
10480
|
+
this.deps.orchestrator.clearLastXraySnapshot();
|
|
10481
|
+
await this.deps.orchestrator.recall(query, recallSessionKey, {
|
|
10482
|
+
xrayCapture: true,
|
|
10483
|
+
...requestedNamespace ? { namespace: requestedNamespace } : {},
|
|
10484
|
+
...budgetOverride !== void 0 ? { budgetCharsOverride: budgetOverride } : {},
|
|
10485
|
+
...mode !== void 0 ? { mode } : {},
|
|
10486
|
+
// When the caller supplies an authenticated principal, forward
|
|
10487
|
+
// it via the dedicated override channel so orchestrator-side
|
|
10488
|
+
// ACL decisions use the SAME principal the access-surface
|
|
10489
|
+
// pre-check above authorized. Threading an
|
|
10490
|
+
// `authenticatedPrincipal` through `sessionKey` would be wrong:
|
|
10491
|
+
// `resolvePrincipal(sessionKey)` only maps configured raw
|
|
10492
|
+
// session keys and otherwise collapses to `"default"`, which
|
|
10493
|
+
// in namespace-enabled deployments produces false denials /
|
|
10494
|
+
// wrong-scope serving despite the pre-check passing
|
|
10495
|
+
// (CLAUDE.md rule 42).
|
|
10496
|
+
...authenticatedPrincipal ? { principalOverride: authenticatedPrincipal } : {},
|
|
10497
|
+
...request.currentContextScopes !== void 0 ? { currentContextScopes: request.currentContextScopes } : {}
|
|
10355
10498
|
});
|
|
10356
|
-
|
|
10357
|
-
|
|
10358
|
-
|
|
10499
|
+
const rawSnapshot = this.deps.orchestrator.getLastXraySnapshot();
|
|
10500
|
+
const namespaceMismatch = requestedNamespace !== void 0 && rawSnapshot?.namespace !== requestedNamespace;
|
|
10501
|
+
if (!rawSnapshot) {
|
|
10502
|
+
xrayResponse = { snapshotFound: false };
|
|
10503
|
+
} else if (namespaceMismatch) {
|
|
10504
|
+
xrayResponse = { snapshotFound: false };
|
|
10505
|
+
} else {
|
|
10506
|
+
let snapshot = rawSnapshot;
|
|
10507
|
+
const xrayFilterTags = normalizeTags(request.tags);
|
|
10508
|
+
let xrayTagMatch;
|
|
10509
|
+
try {
|
|
10510
|
+
xrayTagMatch = parseTagMatch(request.tagMatch);
|
|
10511
|
+
} catch (err) {
|
|
10512
|
+
throw new EngramAccessInputError(
|
|
10513
|
+
err instanceof Error ? err.message : String(err)
|
|
10514
|
+
);
|
|
10515
|
+
}
|
|
10516
|
+
if (xrayFilterTags && xrayFilterTags.length > 0) {
|
|
10517
|
+
const namespace = snapshot.namespace ? this.deps.resolveNamespace(snapshot.namespace) : this.deps.orchestrator.config.defaultNamespace;
|
|
10518
|
+
const tagsByIndex = await Promise.all(
|
|
10519
|
+
snapshot.results.map(async (result) => {
|
|
10520
|
+
try {
|
|
10521
|
+
const storage = await this.deps.orchestrator.getStorage(namespace);
|
|
10522
|
+
const memory = await storage.readMemoryByPath(result.path);
|
|
10523
|
+
const t = memory?.frontmatter?.tags;
|
|
10524
|
+
return Array.isArray(t) ? normalizeProjectionTags(t) : [];
|
|
10525
|
+
} catch {
|
|
10526
|
+
return [];
|
|
10527
|
+
}
|
|
10528
|
+
})
|
|
10529
|
+
);
|
|
10530
|
+
const tagged = snapshot.results.map((result, index) => ({
|
|
10531
|
+
result,
|
|
10532
|
+
tags: tagsByIndex[index] ?? []
|
|
10533
|
+
}));
|
|
10534
|
+
const { results: admittedTagged, trace } = applyTagFilter(tagged, {
|
|
10535
|
+
tags: xrayFilterTags,
|
|
10536
|
+
tagMatch: xrayTagMatch
|
|
10537
|
+
});
|
|
10538
|
+
const admittedResults = admittedTagged.map((entry) => entry.result);
|
|
10539
|
+
const filters = trace ? [...snapshot.filters, trace] : snapshot.filters;
|
|
10540
|
+
snapshot = { ...snapshot, results: admittedResults, filters };
|
|
10541
|
+
}
|
|
10542
|
+
if (request.disclosure !== void 0) {
|
|
10543
|
+
const disclosure2 = request.disclosure;
|
|
10544
|
+
const namespace = snapshot.namespace ? this.deps.resolveNamespace(snapshot.namespace) : this.deps.orchestrator.config.defaultNamespace;
|
|
10545
|
+
const trimmedSessionKey = request.sessionKey?.trim() || void 0;
|
|
10546
|
+
const rawExcerptNamespace = disclosure2 === "raw" ? this.deps.resolveRawExcerptReadNamespace(
|
|
10547
|
+
request.namespace,
|
|
10548
|
+
trimmedSessionKey,
|
|
10549
|
+
authenticatedPrincipal
|
|
10550
|
+
) : namespace;
|
|
10551
|
+
const xrayHasExplicitNamespace = typeof request.namespace === "string" && request.namespace.trim().length > 0;
|
|
10552
|
+
const rawExcerptsSuppressed = disclosure2 === "raw" && !xrayHasExplicitNamespace && rawExcerptNamespace === void 0;
|
|
10553
|
+
const rawExcerptSessionIds = disclosure2 === "raw" && trimmedSessionKey && rawExcerptNamespace ? this.deps.resolveLcmReadSessionIds(
|
|
10554
|
+
request.namespace,
|
|
10555
|
+
rawExcerptNamespace,
|
|
10556
|
+
trimmedSessionKey,
|
|
10557
|
+
authenticatedPrincipal
|
|
10558
|
+
) : void 0;
|
|
10559
|
+
const rawExcerpts = disclosure2 === "raw" && !rawExcerptsSuppressed ? await this.deps.fetchRawExcerpts(disclosure2, {
|
|
10560
|
+
query,
|
|
10561
|
+
...trimmedSessionKey ? { sessionKey: trimmedSessionKey } : {},
|
|
10562
|
+
...rawExcerptNamespace ? { namespace: rawExcerptNamespace } : {},
|
|
10563
|
+
...rawExcerptSessionIds !== void 0 ? { lcmSessionIds: rawExcerptSessionIds } : {}
|
|
10564
|
+
}) : disclosure2 === "raw" ? [] : null;
|
|
10565
|
+
const rawExcerptText = rawExcerpts && rawExcerpts.length > 0 ? rawExcerpts.map((e) => e.content).join("\n") : "";
|
|
10566
|
+
const memoryByIndex = await Promise.all(
|
|
10567
|
+
snapshot.results.map(async (result) => {
|
|
10568
|
+
try {
|
|
10569
|
+
const storage = await this.deps.orchestrator.getStorage(namespace);
|
|
10570
|
+
return await storage.readMemoryByPath(result.path);
|
|
10571
|
+
} catch {
|
|
10572
|
+
return null;
|
|
10573
|
+
}
|
|
10574
|
+
})
|
|
10575
|
+
);
|
|
10576
|
+
const firstReadableIndex = memoryByIndex.findIndex((m) => m !== null);
|
|
10577
|
+
const baseDir = (await this.deps.orchestrator.getStorage(namespace)).dir;
|
|
10578
|
+
const decorated = snapshot.results.map((result, index) => {
|
|
10579
|
+
const memory = memoryByIndex[index];
|
|
10580
|
+
if (!memory) {
|
|
10581
|
+
return { ...result, disclosure: disclosure2 };
|
|
10582
|
+
}
|
|
10583
|
+
const shaped = shapeMemorySummary(
|
|
10584
|
+
memory,
|
|
10585
|
+
baseDir,
|
|
10586
|
+
disclosure2,
|
|
10587
|
+
disclosure2 === "raw" && index === firstReadableIndex && rawExcerpts && rawExcerpts.length > 0 ? rawExcerpts : void 0
|
|
10588
|
+
);
|
|
10589
|
+
return {
|
|
10590
|
+
...result,
|
|
10591
|
+
disclosure: disclosure2,
|
|
10592
|
+
estimatedTokens: estimateRecallTokens(JSON.stringify(shaped))
|
|
10593
|
+
};
|
|
10594
|
+
});
|
|
10595
|
+
if (disclosure2 === "raw" && firstReadableIndex === -1 && rawExcerptText.length > 0 && decorated.length > 0) {
|
|
10596
|
+
decorated[0] = {
|
|
10597
|
+
...decorated[0],
|
|
10598
|
+
disclosure: disclosure2,
|
|
10599
|
+
estimatedTokens: estimateRecallTokens(rawExcerptText)
|
|
10600
|
+
};
|
|
10601
|
+
}
|
|
10602
|
+
const decoratedSnapshot = { ...snapshot, results: decorated };
|
|
10603
|
+
xrayResponse = {
|
|
10604
|
+
snapshotFound: true,
|
|
10605
|
+
snapshot: decoratedSnapshot
|
|
10606
|
+
};
|
|
10607
|
+
} else {
|
|
10608
|
+
xrayResponse = {
|
|
10609
|
+
snapshotFound: true,
|
|
10610
|
+
snapshot
|
|
10611
|
+
};
|
|
10612
|
+
}
|
|
10359
10613
|
}
|
|
10360
|
-
|
|
10614
|
+
} finally {
|
|
10615
|
+
release();
|
|
10616
|
+
}
|
|
10617
|
+
if (request.includeRecall === true && xrayResponse.snapshotFound === true && xrayResponse.snapshot) {
|
|
10618
|
+
const xrayRawExcerptNamespace = disclosure === "raw" ? this.deps.resolveRawExcerptReadNamespace(
|
|
10619
|
+
request.namespace,
|
|
10620
|
+
recallSessionKey,
|
|
10621
|
+
authenticatedPrincipal
|
|
10622
|
+
) : void 0;
|
|
10623
|
+
const xrayHasExplicitNamespace = typeof request.namespace === "string" && request.namespace.trim().length > 0;
|
|
10624
|
+
const xrayRawExcerptsSuppressed = disclosure === "raw" && !xrayHasExplicitNamespace && xrayRawExcerptNamespace === void 0;
|
|
10625
|
+
const xrayRawExcerptSessionIds = disclosure === "raw" && xrayRawExcerptNamespace && recallSessionKey ? this.deps.resolveLcmReadSessionIds(
|
|
10626
|
+
request.namespace,
|
|
10627
|
+
xrayRawExcerptNamespace,
|
|
10628
|
+
recallSessionKey,
|
|
10629
|
+
authenticatedPrincipal
|
|
10630
|
+
) : void 0;
|
|
10631
|
+
return {
|
|
10632
|
+
...xrayResponse,
|
|
10633
|
+
recall: await this.deps.buildRecallResponseFromXraySnapshot({
|
|
10634
|
+
query,
|
|
10635
|
+
sessionKey: recallSessionKey,
|
|
10636
|
+
snapshot: xrayResponse.snapshot,
|
|
10637
|
+
disclosure,
|
|
10638
|
+
startedAt: recallStartedAt,
|
|
10639
|
+
requestedMode: request.mode,
|
|
10640
|
+
normalizedMode: mode,
|
|
10641
|
+
...xrayRawExcerptNamespace ? { rawExcerptNamespace: xrayRawExcerptNamespace } : {},
|
|
10642
|
+
...xrayRawExcerptSessionIds !== void 0 ? { rawExcerptSessionIds: xrayRawExcerptSessionIds } : {},
|
|
10643
|
+
...xrayRawExcerptsSuppressed ? { rawExcerptsSuppressed: xrayRawExcerptsSuppressed } : {}
|
|
10644
|
+
})
|
|
10645
|
+
};
|
|
10361
10646
|
}
|
|
10647
|
+
return xrayResponse;
|
|
10362
10648
|
}
|
|
10363
10649
|
};
|
|
10364
10650
|
|
|
@@ -10373,9 +10659,13 @@ export {
|
|
|
10373
10659
|
clusterByKey,
|
|
10374
10660
|
computeProcedureStats,
|
|
10375
10661
|
formatProcedureStatsText,
|
|
10662
|
+
qmdCollectionPathParts,
|
|
10663
|
+
QmdResultResolver,
|
|
10664
|
+
AccessRecallSurface,
|
|
10665
|
+
selfDeps,
|
|
10376
10666
|
EngramAccessInputError,
|
|
10377
10667
|
ENGRAM_ACCESS_WRITE_SCHEMA_VERSION,
|
|
10378
10668
|
shapeMemorySummary,
|
|
10379
10669
|
EngramAccessService
|
|
10380
10670
|
};
|
|
10381
|
-
//# sourceMappingURL=chunk-
|
|
10671
|
+
//# sourceMappingURL=chunk-VX2RYM2K.js.map
|