@remnic/core 9.3.613 → 9.3.615
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-cli.js +59 -58
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +4 -2
- package/dist/access-http.js +23 -23
- package/dist/access-mcp.d.ts +9 -2
- package/dist/access-mcp.js +20 -20
- package/dist/access-schema.d.ts +26 -14
- package/dist/access-schema.js +3 -3
- package/dist/{access-service-D2J9dh_9.d.ts → access-service-CBNEKjzN.d.ts} +71 -6
- package/dist/access-service.d.ts +2 -2
- package/dist/access-service.js +17 -17
- package/dist/active-recall.js +20 -3
- package/dist/active-recall.js.map +1 -1
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/behavior-learner.js +2 -3
- package/dist/behavior-learner.js.map +1 -1
- package/dist/bootstrap.d.ts +1 -1
- package/dist/briefing.js +3 -3
- package/dist/buffer.d.ts +1 -1
- package/dist/buffer.js +1 -1
- package/dist/calibration.d.ts +5 -2
- package/dist/calibration.js +7 -5
- package/dist/calibration.js.map +1 -1
- package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-YO5QJ6L3.js} +2 -2
- package/dist/causal-consolidation.d.ts +8 -2
- package/dist/causal-consolidation.js +13 -11
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/{chunk-3BP57I6J.js → chunk-2F6NP3NT.js} +2 -1
- package/dist/{chunk-3BP57I6J.js.map → chunk-2F6NP3NT.js.map} +1 -1
- package/dist/{chunk-AU7Q3LSC.js → chunk-2QSZNTDO.js} +4 -4
- package/dist/{chunk-HSVJGWYS.js → chunk-2ROPI5OE.js} +2 -2
- package/dist/{chunk-C4SQJZAF.js → chunk-2SGJY2UY.js} +6 -3
- package/dist/chunk-2SGJY2UY.js.map +1 -0
- package/dist/{chunk-ZDTVJXIP.js → chunk-3MAONBX3.js} +13 -5
- package/dist/chunk-3MAONBX3.js.map +1 -0
- package/dist/{chunk-G3Z3QEF5.js → chunk-3PY7VHV7.js} +2 -2
- package/dist/chunk-3PY7VHV7.js.map +1 -0
- package/dist/{chunk-CF3ZF2YU.js → chunk-3QSU4NFF.js} +3 -3
- package/dist/{chunk-AJA46VX5.js → chunk-3T74IZB3.js} +11 -2
- package/dist/chunk-3T74IZB3.js.map +1 -0
- package/dist/{chunk-KVEVLBKC.js → chunk-4HFJQCJZ.js} +13 -8
- package/dist/chunk-4HFJQCJZ.js.map +1 -0
- package/dist/{chunk-KGK2QKWL.js → chunk-4R4KTDIE.js} +1 -1
- package/dist/chunk-4R4KTDIE.js.map +1 -0
- package/dist/{chunk-OI27U2HT.js → chunk-5BTCT236.js} +2 -2
- package/dist/{chunk-TH67Q46T.js → chunk-5OHHEORR.js} +64 -21
- package/dist/chunk-5OHHEORR.js.map +1 -0
- package/dist/{chunk-CO7ZO4TU.js → chunk-5VDJMYTF.js} +2 -2
- package/dist/{chunk-BFBF3XEF.js → chunk-6BDVBBBY.js} +33 -25
- package/dist/{chunk-BFBF3XEF.js.map → chunk-6BDVBBBY.js.map} +1 -1
- package/dist/{chunk-EAZGEEG2.js → chunk-6L46YAEZ.js} +45 -9
- package/dist/chunk-6L46YAEZ.js.map +1 -0
- package/dist/{chunk-YFS5OEKO.js → chunk-7MLB4NCL.js} +2 -2
- package/dist/{chunk-LZ3VEOU5.js → chunk-AL4RAJL5.js} +22 -5
- package/dist/chunk-AL4RAJL5.js.map +1 -0
- package/dist/{chunk-557IAFPD.js → chunk-APRRL26Q.js} +2 -2
- package/dist/{chunk-QDDHYAKV.js → chunk-AZDOWD2L.js} +2 -2
- package/dist/{chunk-MLT75J5S.js → chunk-B6SU7YSE.js} +3 -3
- package/dist/{chunk-FXKPZ3H6.js → chunk-BPSGLMQ4.js} +2 -2
- package/dist/{chunk-2NLLXCJG.js → chunk-BXLOS5AJ.js} +2 -2
- package/dist/{chunk-NOMEVTUD.js → chunk-C6C7XVKG.js} +5 -4
- package/dist/chunk-C6C7XVKG.js.map +1 -0
- package/dist/{chunk-XKIQZXUB.js → chunk-CI7RKSRE.js} +7 -1
- package/dist/chunk-CI7RKSRE.js.map +1 -0
- package/dist/{chunk-IK34DVAC.js → chunk-CIOMS6DI.js} +2 -2
- package/dist/{chunk-2I5JGH3M.js → chunk-CYEPCZN5.js} +2 -2
- package/dist/{chunk-2I5JGH3M.js.map → chunk-CYEPCZN5.js.map} +1 -1
- package/dist/{chunk-JHMFYY7L.js → chunk-DCGT4FPP.js} +13 -5
- package/dist/chunk-DCGT4FPP.js.map +1 -0
- package/dist/{chunk-7DZRO2DC.js → chunk-DEPRLVLK.js} +2 -2
- package/dist/{chunk-CSKLPDN6.js → chunk-DEVUWMME.js} +52 -19
- package/dist/chunk-DEVUWMME.js.map +1 -0
- package/dist/{chunk-DHGSZ3UD.js → chunk-DGNQRNLL.js} +2 -2
- package/dist/{chunk-X7Y7WX73.js → chunk-DQEMWVMT.js} +1 -1
- package/dist/{chunk-HJNQQICM.js → chunk-EXUAP5LH.js} +108 -51
- package/dist/chunk-EXUAP5LH.js.map +1 -0
- package/dist/chunk-FAV25DUZ.js +12 -0
- package/dist/chunk-FAV25DUZ.js.map +1 -0
- package/dist/{chunk-ETUPBUHB.js → chunk-GDASG7NC.js} +2 -2
- package/dist/{chunk-L227SKTB.js → chunk-GDB4J2H3.js} +17 -1
- package/dist/chunk-GDB4J2H3.js.map +1 -0
- package/dist/{chunk-IP73YCZP.js → chunk-GLPBYIXN.js} +4 -2
- package/dist/chunk-GLPBYIXN.js.map +1 -0
- package/dist/{chunk-4HP7HIE3.js → chunk-HP5FMB6L.js} +2 -2
- package/dist/{chunk-EVZFIAPG.js → chunk-IBTZEBUD.js} +23 -10
- package/dist/chunk-IBTZEBUD.js.map +1 -0
- package/dist/{chunk-DOX2CG6Y.js → chunk-IEUU7O4F.js} +2 -2
- package/dist/{chunk-EUML3N6B.js → chunk-IMA6GU4Y.js} +3 -3
- package/dist/chunk-IMA6GU4Y.js.map +1 -0
- package/dist/{chunk-JNANKJLN.js → chunk-JOASJWQR.js} +2 -2
- package/dist/chunk-JOASJWQR.js.map +1 -0
- package/dist/{chunk-WSGF57U2.js → chunk-JQDZQ4TB.js} +2 -2
- package/dist/{chunk-HINSGUA7.js → chunk-KBL3JJR6.js} +9 -13
- package/dist/chunk-KBL3JJR6.js.map +1 -0
- package/dist/{chunk-IOTENEVL.js → chunk-KGLPJROV.js} +57 -50
- package/dist/chunk-KGLPJROV.js.map +1 -0
- package/dist/{chunk-W7L6HXUC.js → chunk-LXOM6IQU.js} +2 -2
- package/dist/{chunk-G6R5UD3Q.js → chunk-MGN7VHWQ.js} +42 -1
- package/dist/{chunk-G6R5UD3Q.js.map → chunk-MGN7VHWQ.js.map} +1 -1
- package/dist/{chunk-DLJ4IR6M.js → chunk-MHQC2WU2.js} +2 -2
- package/dist/chunk-MHQC2WU2.js.map +1 -0
- package/dist/{chunk-5RPTH6AU.js → chunk-NM5NQYJE.js} +20 -19
- package/dist/chunk-NM5NQYJE.js.map +1 -0
- package/dist/{chunk-6JGNHWCI.js → chunk-OBIRVF36.js} +3 -3
- package/dist/{chunk-CHCA44C3.js → chunk-ODPLEWB6.js} +3 -3
- package/dist/chunk-ODPLEWB6.js.map +1 -0
- package/dist/{chunk-HENLZHIT.js → chunk-OIF36KGD.js} +7 -4
- package/dist/chunk-OIF36KGD.js.map +1 -0
- package/dist/{chunk-GUPISBV2.js → chunk-PP2JH3GP.js} +2 -2
- package/dist/{chunk-OXJBNGBK.js → chunk-PSUB67YB.js} +2 -2
- package/dist/{chunk-UWY7GIVS.js → chunk-PYIFUBRK.js} +45 -13
- package/dist/chunk-PYIFUBRK.js.map +1 -0
- package/dist/{chunk-KIB7SDIJ.js → chunk-Q6YIJGXJ.js} +2 -2
- package/dist/{chunk-ZT3EGNLR.js → chunk-QPD426WT.js} +2 -2
- package/dist/{chunk-RLV3PQGH.js → chunk-QVO4YOB7.js} +6 -6
- package/dist/{chunk-GMAG2HS4.js → chunk-RG3LBSGH.js} +46 -9
- package/dist/chunk-RG3LBSGH.js.map +1 -0
- package/dist/{chunk-XSWKORGM.js → chunk-S53OYO3F.js} +3 -1
- package/dist/chunk-S53OYO3F.js.map +1 -0
- package/dist/{chunk-YCN4BVDK.js → chunk-SCPFRKIT.js} +4 -2
- package/dist/chunk-SCPFRKIT.js.map +1 -0
- package/dist/{chunk-NZPF2SYV.js → chunk-T7N6KQGS.js} +138 -5
- package/dist/chunk-T7N6KQGS.js.map +1 -0
- package/dist/{chunk-VJXSUAO7.js → chunk-TNOWU6RP.js} +13 -10
- package/dist/chunk-TNOWU6RP.js.map +1 -0
- package/dist/{chunk-PCI747N2.js → chunk-TZVQQTG4.js} +48 -19
- package/dist/chunk-TZVQQTG4.js.map +1 -0
- package/dist/{chunk-KQAFEZQX.js → chunk-VDX2J7OX.js} +2 -2
- package/dist/{chunk-IK7DCC5H.js → chunk-VMGLYN42.js} +2 -2
- package/dist/{chunk-KM2A35EO.js → chunk-WB3LYXC5.js} +11 -7
- package/dist/chunk-WB3LYXC5.js.map +1 -0
- package/dist/{chunk-PPPZY2EU.js → chunk-WD2W4234.js} +9 -3
- package/dist/chunk-WD2W4234.js.map +1 -0
- package/dist/{chunk-NSKYFGDL.js → chunk-X4QQB7O6.js} +2 -2
- package/dist/{chunk-HPWVAEET.js → chunk-X6IRLNOO.js} +3 -7
- package/dist/chunk-X6IRLNOO.js.map +1 -0
- package/dist/{chunk-46GJIW5M.js → chunk-XAZOWLW4.js} +5 -5
- package/dist/{chunk-46GJIW5M.js.map → chunk-XAZOWLW4.js.map} +1 -1
- package/dist/{chunk-XPSVGJYA.js → chunk-YRMKDTKF.js} +12 -9
- package/dist/chunk-YRMKDTKF.js.map +1 -0
- package/dist/{chunk-6ZZP4EJF.js → chunk-ZJR7VG5L.js} +3 -3
- package/dist/{chunk-6ZZP4EJF.js.map → chunk-ZJR7VG5L.js.map} +1 -1
- package/dist/{chunk-2QANQKSQ.js → chunk-ZK32E74R.js} +156 -45
- package/dist/chunk-ZK32E74R.js.map +1 -0
- package/dist/{cli-OrfKXNU4.d.ts → cli-Cw729yLf.d.ts} +6 -2
- package/dist/cli.d.ts +3 -3
- package/dist/cli.js +61 -60
- package/dist/compounding/engine.js +3 -3
- package/dist/compounding/preference-consolidator.js +39 -11
- package/dist/compounding/preference-consolidator.js.map +1 -1
- package/dist/config.js +1 -1
- package/dist/connectors/codex-materialize-runner.js +3 -3
- package/dist/connectors/index.js +3 -3
- package/dist/consolidation-provenance-check.js +1 -1
- package/dist/contradiction/index.js +4 -4
- package/dist/conversation-index/backend.js +2 -2
- package/dist/conversation-index/indexer.js +1 -1
- package/dist/cross-namespace-budget.js +1 -1
- package/dist/enrichment/index.js +1 -1
- package/dist/entity-retrieval.js +3 -3
- package/dist/evals.js +1 -1
- package/dist/explicit-capture.d.ts +11 -1
- package/dist/explicit-capture.js +1 -1
- package/dist/extraction-judge.js +8 -1
- package/dist/extraction.js +2 -2
- package/dist/fallback-llm.d.ts +23 -6
- package/dist/fallback-llm.js +5 -3
- package/dist/{first-start-migration-GYJWIH36.js → first-start-migration-FF7YFGRP.js} +6 -6
- package/dist/index.d.ts +3 -3
- package/dist/index.js +95 -94
- package/dist/index.js.map +1 -1
- package/dist/lcm/archive.js +2 -2
- package/dist/lcm/engine.js +5 -5
- package/dist/lcm/index.js +7 -7
- package/dist/lcm/summarizer.js +3 -3
- package/dist/maintenance/memory-governance-cron.d.ts +6 -4
- package/dist/maintenance/memory-governance-cron.js +1 -1
- package/dist/maintenance/memory-governance.js +3 -3
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
- package/dist/maintenance/rebuild-memory-projection.js +4 -4
- package/dist/mcp-memory-inspector-app.d.ts +2 -2
- package/dist/mcp-memory-inspector-app.js +1 -1
- package/dist/migrate/from-engram.js +1 -1
- package/dist/namespaces/migrate.js +16 -15
- package/dist/namespaces/search.js +12 -11
- package/dist/namespaces/storage.js +3 -3
- package/dist/network/webdav.d.ts +2 -0
- package/dist/network/webdav.js +1 -1
- package/dist/objective-state-writers.js +2 -2
- package/dist/operator-toolkit.d.ts +3 -1
- package/dist/operator-toolkit.js +21 -20
- package/dist/{orchestrator-DTRQG75J.d.ts → orchestrator-CqWOjfgl.d.ts} +46 -3
- package/dist/orchestrator.d.ts +1 -1
- package/dist/orchestrator.js +48 -45
- package/dist/patterns-cli.js +1 -1
- package/dist/qmd-recall-cache.d.ts +2 -0
- package/dist/qmd-recall-cache.js +1 -1
- package/dist/qmd.d.ts +37 -2
- package/dist/qmd.js +4 -1
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-planner-llm.d.ts +57 -0
- package/dist/recall-planner-llm.js +167 -0
- package/dist/recall-planner-llm.js.map +1 -0
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.js +2 -2
- package/dist/resume-bundles.js +2 -2
- package/dist/retrieval-agents.js +2 -2
- package/dist/routing/store.js +1 -1
- package/dist/schemas.d.ts +22 -22
- package/dist/search/factory.js +11 -10
- package/dist/search/index.js +11 -10
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/lancedb-backend.js +3 -2
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.js +3 -2
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/noop-backend.js +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/orama-backend.js +3 -2
- package/dist/search/port.d.ts +6 -1
- package/dist/search/port.js +7 -0
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/search/remote-backend.js +1 -1
- package/dist/semantic-consolidation.js +4 -4
- package/dist/semantic-rule-promotion.js +3 -3
- package/dist/semantic-rule-verifier.js +3 -3
- package/dist/session-observer-state.js +1 -1
- package/dist/storage.js +2 -2
- package/dist/summarizer.js +2 -2
- package/dist/temporal-index.js +1 -1
- package/dist/{tier-stats-SKML2OSF.js → tier-stats-3LYQ3VV5.js} +3 -3
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/capsule-import.js +2 -2
- package/dist/transfer/export-sqlite.js +1 -1
- package/dist/transfer/types.d.ts +12 -12
- package/dist/types.d.ts +32 -0
- package/dist/types.js +1 -1
- package/dist/utility-learner.js +1 -1
- package/dist/utility-runtime.js +2 -2
- package/dist/verified-recall.js +3 -3
- package/dist/work/board.js +2 -2
- package/dist/work/storage.d.ts +2 -0
- package/dist/work/storage.js +1 -1
- package/package.json +1 -1
- package/src/access-http.ts +24 -10
- package/src/access-mcp.test.ts +160 -0
- package/src/access-mcp.ts +72 -7
- package/src/access-schema.ts +11 -0
- package/src/access-service-coding-write.test.ts +478 -0
- package/src/access-service.ts +237 -32
- package/src/active-recall.test.ts +40 -0
- package/src/active-recall.ts +19 -2
- package/src/behavior-learner.ts +5 -3
- package/src/buffer-session.test.ts +58 -0
- package/src/buffer-surprise-trigger.test.ts +4 -18
- package/src/buffer.ts +39 -11
- package/src/calibration.ts +10 -4
- package/src/causal-consolidation.test.ts +47 -2
- package/src/causal-consolidation.ts +13 -9
- package/src/cli.ts +19 -4
- package/src/compounding/engine.ts +2 -0
- package/src/compounding/preference-consolidator.test.ts +292 -0
- package/src/compounding/preference-consolidator.ts +55 -19
- package/src/config.test.ts +213 -0
- package/src/config.ts +175 -4
- package/src/connectors/codex-materialize-runner.ts +7 -4
- package/src/consolidation-provenance-check.ts +24 -5
- package/src/conversation-index/indexer.test.ts +22 -0
- package/src/conversation-index/indexer.ts +7 -3
- package/src/cross-namespace-budget.test.ts +44 -21
- package/src/cross-namespace-budget.ts +2 -2
- package/src/enrichment/pipeline.ts +11 -16
- package/src/evals.ts +1 -1
- package/src/explicit-capture.ts +19 -2
- package/src/extraction-judge-chain.test.ts +55 -0
- package/src/extraction-judge.ts +7 -9
- package/src/extraction.ts +16 -5
- package/src/fallback-llm.test.ts +600 -1
- package/src/fallback-llm.ts +91 -22
- package/src/maintenance/memory-governance-cron.ts +39 -29
- package/src/mcp-memory-inspector-app.ts +54 -12
- package/src/message-parts/index.ts +6 -0
- package/src/message-parts/message-parts.test.ts +30 -0
- package/src/migrate/from-engram.ts +19 -5
- package/src/namespaces/search.test.ts +15 -2
- package/src/namespaces/search.ts +1 -1
- package/src/network/webdav.ts +61 -21
- package/src/operator-toolkit.ts +6 -2
- package/src/orchestrator.ts +173 -20
- package/src/qmd-client.test.ts +85 -0
- package/src/qmd-recall-cache.test.ts +16 -0
- package/src/qmd-recall-cache.ts +7 -0
- package/src/qmd.test.ts +54 -0
- package/src/qmd.ts +119 -19
- package/src/recall-planner-llm.test.ts +224 -0
- package/src/recall-planner-llm.ts +289 -0
- package/src/routing/store.ts +4 -8
- package/src/search/factory.ts +3 -0
- package/src/search/lancedb-backend.ts +15 -3
- package/src/search/meilisearch-backend.ts +70 -7
- package/src/search/noop-backend.ts +5 -1
- package/src/search/orama-backend.ts +15 -3
- package/src/search/port.ts +15 -0
- package/src/search/remote-backend.ts +5 -1
- package/src/session-observer-state.ts +1 -1
- package/src/summarizer.ts +3 -3
- package/src/temporal-index.test.ts +18 -0
- package/src/temporal-index.ts +45 -0
- package/src/training-export/cli-date-validation.test.ts +36 -0
- package/src/training-export/date-parse.ts +21 -2
- package/src/transfer/export-sqlite.ts +3 -0
- package/src/types.ts +35 -0
- package/src/utility-learner.ts +1 -0
- package/src/work/storage.ts +23 -0
- package/dist/chunk-2QANQKSQ.js.map +0 -1
- package/dist/chunk-5RPTH6AU.js.map +0 -1
- package/dist/chunk-AJA46VX5.js.map +0 -1
- package/dist/chunk-C4SQJZAF.js.map +0 -1
- package/dist/chunk-CHCA44C3.js.map +0 -1
- package/dist/chunk-CSKLPDN6.js.map +0 -1
- package/dist/chunk-DLJ4IR6M.js.map +0 -1
- package/dist/chunk-EAZGEEG2.js.map +0 -1
- package/dist/chunk-EUML3N6B.js.map +0 -1
- package/dist/chunk-EVZFIAPG.js.map +0 -1
- package/dist/chunk-G3Z3QEF5.js.map +0 -1
- package/dist/chunk-GMAG2HS4.js.map +0 -1
- package/dist/chunk-HENLZHIT.js.map +0 -1
- package/dist/chunk-HINSGUA7.js.map +0 -1
- package/dist/chunk-HJNQQICM.js.map +0 -1
- package/dist/chunk-HPWVAEET.js.map +0 -1
- package/dist/chunk-IOTENEVL.js.map +0 -1
- package/dist/chunk-IP73YCZP.js.map +0 -1
- package/dist/chunk-JHMFYY7L.js.map +0 -1
- package/dist/chunk-JNANKJLN.js.map +0 -1
- package/dist/chunk-KGK2QKWL.js.map +0 -1
- package/dist/chunk-KM2A35EO.js.map +0 -1
- package/dist/chunk-KVEVLBKC.js.map +0 -1
- package/dist/chunk-L227SKTB.js.map +0 -1
- package/dist/chunk-LZ3VEOU5.js.map +0 -1
- package/dist/chunk-NOMEVTUD.js.map +0 -1
- package/dist/chunk-NZPF2SYV.js.map +0 -1
- package/dist/chunk-PCI747N2.js.map +0 -1
- package/dist/chunk-PPPZY2EU.js.map +0 -1
- package/dist/chunk-TH67Q46T.js.map +0 -1
- package/dist/chunk-UWY7GIVS.js.map +0 -1
- package/dist/chunk-VJXSUAO7.js.map +0 -1
- package/dist/chunk-XKIQZXUB.js.map +0 -1
- package/dist/chunk-XPSVGJYA.js.map +0 -1
- package/dist/chunk-XSWKORGM.js.map +0 -1
- package/dist/chunk-YCN4BVDK.js.map +0 -1
- package/dist/chunk-ZDTVJXIP.js.map +0 -1
- /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-YO5QJ6L3.js.map} +0 -0
- /package/dist/{chunk-AU7Q3LSC.js.map → chunk-2QSZNTDO.js.map} +0 -0
- /package/dist/{chunk-HSVJGWYS.js.map → chunk-2ROPI5OE.js.map} +0 -0
- /package/dist/{chunk-CF3ZF2YU.js.map → chunk-3QSU4NFF.js.map} +0 -0
- /package/dist/{chunk-OI27U2HT.js.map → chunk-5BTCT236.js.map} +0 -0
- /package/dist/{chunk-CO7ZO4TU.js.map → chunk-5VDJMYTF.js.map} +0 -0
- /package/dist/{chunk-YFS5OEKO.js.map → chunk-7MLB4NCL.js.map} +0 -0
- /package/dist/{chunk-557IAFPD.js.map → chunk-APRRL26Q.js.map} +0 -0
- /package/dist/{chunk-QDDHYAKV.js.map → chunk-AZDOWD2L.js.map} +0 -0
- /package/dist/{chunk-MLT75J5S.js.map → chunk-B6SU7YSE.js.map} +0 -0
- /package/dist/{chunk-FXKPZ3H6.js.map → chunk-BPSGLMQ4.js.map} +0 -0
- /package/dist/{chunk-2NLLXCJG.js.map → chunk-BXLOS5AJ.js.map} +0 -0
- /package/dist/{chunk-IK34DVAC.js.map → chunk-CIOMS6DI.js.map} +0 -0
- /package/dist/{chunk-7DZRO2DC.js.map → chunk-DEPRLVLK.js.map} +0 -0
- /package/dist/{chunk-DHGSZ3UD.js.map → chunk-DGNQRNLL.js.map} +0 -0
- /package/dist/{chunk-X7Y7WX73.js.map → chunk-DQEMWVMT.js.map} +0 -0
- /package/dist/{chunk-ETUPBUHB.js.map → chunk-GDASG7NC.js.map} +0 -0
- /package/dist/{chunk-4HP7HIE3.js.map → chunk-HP5FMB6L.js.map} +0 -0
- /package/dist/{chunk-DOX2CG6Y.js.map → chunk-IEUU7O4F.js.map} +0 -0
- /package/dist/{chunk-WSGF57U2.js.map → chunk-JQDZQ4TB.js.map} +0 -0
- /package/dist/{chunk-W7L6HXUC.js.map → chunk-LXOM6IQU.js.map} +0 -0
- /package/dist/{chunk-6JGNHWCI.js.map → chunk-OBIRVF36.js.map} +0 -0
- /package/dist/{chunk-GUPISBV2.js.map → chunk-PP2JH3GP.js.map} +0 -0
- /package/dist/{chunk-OXJBNGBK.js.map → chunk-PSUB67YB.js.map} +0 -0
- /package/dist/{chunk-KIB7SDIJ.js.map → chunk-Q6YIJGXJ.js.map} +0 -0
- /package/dist/{chunk-ZT3EGNLR.js.map → chunk-QPD426WT.js.map} +0 -0
- /package/dist/{chunk-RLV3PQGH.js.map → chunk-QVO4YOB7.js.map} +0 -0
- /package/dist/{chunk-KQAFEZQX.js.map → chunk-VDX2J7OX.js.map} +0 -0
- /package/dist/{chunk-IK7DCC5H.js.map → chunk-VMGLYN42.js.map} +0 -0
- /package/dist/{chunk-NSKYFGDL.js.map → chunk-X4QQB7O6.js.map} +0 -0
- /package/dist/{first-start-migration-GYJWIH36.js.map → first-start-migration-FF7YFGRP.js.map} +0 -0
- /package/dist/{tier-stats-SKML2OSF.js.map → tier-stats-3LYQ3VV5.js.map} +0 -0
package/src/config.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
HeartbeatConfig,
|
|
14
14
|
IdentityInjectionMode,
|
|
15
15
|
MemoryOsPresetName,
|
|
16
|
+
AgentPersonaModelConfig,
|
|
16
17
|
PluginConfig,
|
|
17
18
|
PrincipalRule,
|
|
18
19
|
RecallPipelineConfig,
|
|
@@ -69,6 +70,81 @@ function parseBoundedIntegerMs(
|
|
|
69
70
|
return Math.min(max, Math.max(min, Math.floor(coerced)));
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
// A gateway model string must be "provider/model" — at least one "/" with a
|
|
74
|
+
// non-empty provider segment and a non-empty model segment. This mirrors
|
|
75
|
+
// FallbackLlmClient.parseModelString (which requires >= 2 slash-parts), so a
|
|
76
|
+
// model the runtime would silently drop is rejected at config time instead.
|
|
77
|
+
function isQualifiedModelString(value: string): boolean {
|
|
78
|
+
const slash = value.indexOf("/");
|
|
79
|
+
return slash > 0 && slash < value.length - 1;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function parseModelChainConfig(
|
|
83
|
+
value: unknown,
|
|
84
|
+
keyName: string,
|
|
85
|
+
): AgentPersonaModelConfig | undefined {
|
|
86
|
+
// Absent → not configured (no error).
|
|
87
|
+
if (value === undefined || value === null) return undefined;
|
|
88
|
+
|
|
89
|
+
// Present but malformed → reject loudly rather than silently dropping it,
|
|
90
|
+
// so a typo'd chain surfaces instead of quietly reverting to defaults
|
|
91
|
+
// (gotcha #51). Issue #1365 / PR #1370.
|
|
92
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`${keyName} must be an object like { "primary": "provider/model", "fallbacks": ["provider/model", ...] }; got ${JSON.stringify(value)}`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
const raw = value as Record<string, unknown>;
|
|
98
|
+
// Reject unknown keys (matches the manifest's additionalProperties:false) so a
|
|
99
|
+
// misspelled "fallback"/"fallbackModels" doesn't silently drop the fallback
|
|
100
|
+
// chain (gotcha #51, codex review #1425).
|
|
101
|
+
const unknownKeys = Object.keys(raw).filter((k) => k !== "primary" && k !== "fallbacks");
|
|
102
|
+
if (unknownKeys.length > 0) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`${keyName} has unknown propert${unknownKeys.length === 1 ? "y" : "ies"}: ${unknownKeys.join(", ")}. Allowed: "primary", "fallbacks".`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
if (typeof raw.primary !== "string" || raw.primary.trim().length === 0) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`${keyName}.primary is required and must be a non-empty "provider/model" string; got ${JSON.stringify(raw.primary)}`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
const primary = raw.primary.trim();
|
|
113
|
+
if (!isQualifiedModelString(primary)) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`${keyName}.primary must be in "provider/model" form (e.g. "zai/glm-4.7-flash"); got ${JSON.stringify(primary)}`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let dedupedFallbacks: string[] | undefined;
|
|
120
|
+
if (raw.fallbacks !== undefined) {
|
|
121
|
+
if (!Array.isArray(raw.fallbacks)) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`${keyName}.fallbacks must be an array of "provider/model" strings; got ${JSON.stringify(raw.fallbacks)}`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
if (raw.fallbacks.some((item) => typeof item !== "string")) {
|
|
127
|
+
throw new Error(`${keyName}.fallbacks must contain only strings`);
|
|
128
|
+
}
|
|
129
|
+
const trimmed = raw.fallbacks
|
|
130
|
+
.map((item) => (item as string).trim())
|
|
131
|
+
.filter((item) => item.length > 0 && item !== primary);
|
|
132
|
+
for (const fb of trimmed) {
|
|
133
|
+
if (!isQualifiedModelString(fb)) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
`${keyName}.fallbacks entries must be in "provider/model" form; got ${JSON.stringify(fb)}`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
dedupedFallbacks = [...new Set(trimmed)];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
primary,
|
|
144
|
+
...(dedupedFallbacks && dedupedFallbacks.length > 0 ? { fallbacks: dedupedFallbacks } : {}),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
72
148
|
function parsePositiveInteger(value: unknown, keyName: string): number | undefined {
|
|
73
149
|
if (value === undefined || value === null) return undefined;
|
|
74
150
|
const coerced = coerceNumber(value);
|
|
@@ -139,6 +215,51 @@ function parseQmdChunkStrategy(value: unknown): "auto" | "regex" {
|
|
|
139
215
|
throw new Error(`qmdChunkStrategy must be "auto" or "regex"; got ${JSON.stringify(value)}`);
|
|
140
216
|
}
|
|
141
217
|
|
|
218
|
+
// Issue #1335. Default "hybrid" preserves the historical lex+vec+hyde daemon plan.
|
|
219
|
+
function parseQmdSearchStrategy(value: unknown): "hybrid" | "lex-vec" | "lex" {
|
|
220
|
+
if (value === undefined || value === null) return "hybrid";
|
|
221
|
+
if (typeof value !== "string") {
|
|
222
|
+
throw new Error(
|
|
223
|
+
`qmdSearchStrategy must be one of "hybrid", "lex-vec", or "lex"; got ${JSON.stringify(value)}`,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
const normalized = value.trim().toLowerCase();
|
|
227
|
+
if (normalized === "hybrid" || normalized === "lex-vec" || normalized === "lex") {
|
|
228
|
+
return normalized;
|
|
229
|
+
}
|
|
230
|
+
throw new Error(
|
|
231
|
+
`qmdSearchStrategy must be one of "hybrid", "lex-vec", or "lex"; got ${JSON.stringify(value)}`,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Issue #1335. Reject non-numeric / non-integer timeouts rather than silently
|
|
236
|
+
// coercing them (gotcha #51), then clamp valid integers to the documented bounds.
|
|
237
|
+
function parseQmdDaemonTimeoutMs(value: unknown): number {
|
|
238
|
+
if (value === undefined || value === null) return 8_000;
|
|
239
|
+
const coerced = coerceNumber(value);
|
|
240
|
+
if (coerced === undefined || !Number.isInteger(coerced)) {
|
|
241
|
+
throw new Error(
|
|
242
|
+
`qmdDaemonTimeoutMs must be an integer number of milliseconds between 1000 and 120000; got ${JSON.stringify(value)}`,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
return Math.min(120_000, Math.max(1_000, coerced));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Issue #1335. Default "query" keeps `qmd query` (LLM expansion + rerank) per gotcha #7.
|
|
249
|
+
function parseQmdSubprocessStrategy(value: unknown): "query" | "search" {
|
|
250
|
+
if (value === undefined || value === null) return "query";
|
|
251
|
+
if (typeof value !== "string") {
|
|
252
|
+
throw new Error(
|
|
253
|
+
`qmdSubprocessStrategy must be one of "query" or "search"; got ${JSON.stringify(value)}`,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
const normalized = value.trim().toLowerCase();
|
|
257
|
+
if (normalized === "query" || normalized === "search") return normalized;
|
|
258
|
+
throw new Error(
|
|
259
|
+
`qmdSubprocessStrategy must be one of "query" or "search"; got ${JSON.stringify(value)}`,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
142
263
|
function parseOptionalNonEmptyString(value: unknown): string | undefined {
|
|
143
264
|
if (typeof value !== "string") return undefined;
|
|
144
265
|
const normalized = value.trim();
|
|
@@ -230,6 +351,38 @@ function coerceBooleanLike(value: unknown): boolean | undefined {
|
|
|
230
351
|
return undefined;
|
|
231
352
|
}
|
|
232
353
|
|
|
354
|
+
/**
|
|
355
|
+
* Resolve the `emitLegacyTools` opt-out (issue #1427): config field wins, then
|
|
356
|
+
* the REMNIC_/ENGRAM_ env var, then default true. A *present-but-malformed*
|
|
357
|
+
* value fails fast rather than silently re-enabling legacy aliases — this knob
|
|
358
|
+
* controls the advertised MCP `tools/list` surface, so a typo like
|
|
359
|
+
* `emitLegacyTools=fales` must not be misread as `true` (gotcha #51).
|
|
360
|
+
*/
|
|
361
|
+
function resolveEmitLegacyTools(configValue: unknown): boolean {
|
|
362
|
+
const ACCEPTED = "true/false/1/0/yes/no/on/off";
|
|
363
|
+
if (configValue !== undefined && configValue !== null) {
|
|
364
|
+
const coerced = coerceBooleanLike(configValue);
|
|
365
|
+
if (coerced === undefined) {
|
|
366
|
+
throw new Error(
|
|
367
|
+
`emitLegacyTools must be a boolean-like value (${ACCEPTED}); got ${JSON.stringify(configValue)}`,
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
return coerced;
|
|
371
|
+
}
|
|
372
|
+
const envRaw =
|
|
373
|
+
readEnvVar("REMNIC_EMIT_LEGACY_TOOLS") ?? readEnvVar("ENGRAM_EMIT_LEGACY_TOOLS");
|
|
374
|
+
if (envRaw !== undefined) {
|
|
375
|
+
const coerced = coerceBooleanLike(envRaw);
|
|
376
|
+
if (coerced === undefined) {
|
|
377
|
+
throw new Error(
|
|
378
|
+
`REMNIC_EMIT_LEGACY_TOOLS must be a boolean-like value (${ACCEPTED}); got "${envRaw}"`,
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
return coerced;
|
|
382
|
+
}
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
|
|
233
386
|
export function isOpenaiApiKeyDisabled(value: unknown): boolean {
|
|
234
387
|
return value === false || (typeof value === "string" && value.trim().toLowerCase() === "false");
|
|
235
388
|
}
|
|
@@ -1379,6 +1532,9 @@ export function parseConfig(raw: unknown): PluginConfig {
|
|
|
1379
1532
|
qmdChunkStrategy: parseQmdChunkStrategy(cfg.qmdChunkStrategy),
|
|
1380
1533
|
qmdCandidateLimit: parsePositiveInteger(cfg.qmdCandidateLimit, "qmdCandidateLimit"),
|
|
1381
1534
|
qmdQueryRerankEnabled: coerceBooleanLike(cfg.qmdQueryRerankEnabled) ?? true,
|
|
1535
|
+
qmdSearchStrategy: parseQmdSearchStrategy(cfg.qmdSearchStrategy),
|
|
1536
|
+
qmdSubprocessStrategy: parseQmdSubprocessStrategy(cfg.qmdSubprocessStrategy),
|
|
1537
|
+
qmdDaemonTimeoutMs: parseQmdDaemonTimeoutMs(cfg.qmdDaemonTimeoutMs),
|
|
1382
1538
|
qmdIndexName: parseOptionalNonEmptyString(cfg.qmdIndexName),
|
|
1383
1539
|
qmdForceCpu: coerceBooleanLike(cfg.qmdForceCpu) ?? false,
|
|
1384
1540
|
qmdGpuBackend: parseQmdGpuBackend(cfg.qmdGpuBackend),
|
|
@@ -2377,6 +2533,7 @@ export function parseConfig(raw: unknown): PluginConfig {
|
|
|
2377
2533
|
typeof cfg.fastGatewayAgentId === "string" && cfg.fastGatewayAgentId.length > 0
|
|
2378
2534
|
? cfg.fastGatewayAgentId
|
|
2379
2535
|
: "",
|
|
2536
|
+
taskModelChain: parseModelChainConfig(cfg.taskModelChain, "taskModelChain"),
|
|
2380
2537
|
|
|
2381
2538
|
// v3.0 namespaces (default off)
|
|
2382
2539
|
namespacesEnabled: cfg.namespacesEnabled === true,
|
|
@@ -2716,20 +2873,30 @@ export function parseConfig(raw: unknown): PluginConfig {
|
|
|
2716
2873
|
.filter((param): param is string => typeof param === "string" && param.trim().length > 0)
|
|
2717
2874
|
: [...DEFAULT_BEHAVIOR_LOOP_PROTECTED_PARAMS],
|
|
2718
2875
|
// v8.0 phase 1
|
|
2719
|
-
|
|
2876
|
+
// All recallPlanner boolean gates coerce boolean-like strings so CLI/env
|
|
2877
|
+
// surfaces (`--config recallPlanner*=true|false`) behave correctly — the
|
|
2878
|
+
// shadow-mode/telemetry/enable flags are documented rollout switches and
|
|
2879
|
+
// must not silently ignore string values (gotcha #36, #1428 review).
|
|
2880
|
+
recallPlannerEnabled: coerceBooleanLike(cfg.recallPlannerEnabled) ?? true,
|
|
2881
|
+
// Issue #1367 / Option C: LLM-based recall planning is opt-in so the
|
|
2882
|
+
// default recall path stays heuristic (no added latency / LLM call unless
|
|
2883
|
+
// the operator asks for it — gotcha #30). Coerce boolean-like strings so
|
|
2884
|
+
// CLI/env surfaces (`--config recallPlannerLlmEnabled=true`) actually
|
|
2885
|
+
// enable it (gotcha #36); defaults off.
|
|
2886
|
+
recallPlannerLlmEnabled: coerceBooleanLike(cfg.recallPlannerLlmEnabled) ?? false,
|
|
2720
2887
|
recallPlannerModel:
|
|
2721
2888
|
typeof cfg.recallPlannerModel === "string" && cfg.recallPlannerModel.trim().length > 0
|
|
2722
2889
|
? cfg.recallPlannerModel.trim()
|
|
2723
2890
|
: DEFAULT_REASONING_MODEL,
|
|
2724
2891
|
recallPlannerTimeoutMs:
|
|
2725
2892
|
typeof cfg.recallPlannerTimeoutMs === "number" ? cfg.recallPlannerTimeoutMs : 1500,
|
|
2726
|
-
recallPlannerUseResponsesApi: cfg.recallPlannerUseResponsesApi
|
|
2893
|
+
recallPlannerUseResponsesApi: coerceBooleanLike(cfg.recallPlannerUseResponsesApi) ?? true,
|
|
2727
2894
|
recallPlannerMaxPromptChars:
|
|
2728
2895
|
typeof cfg.recallPlannerMaxPromptChars === "number" ? cfg.recallPlannerMaxPromptChars : 4000,
|
|
2729
2896
|
recallPlannerMaxMemoryHints:
|
|
2730
2897
|
typeof cfg.recallPlannerMaxMemoryHints === "number" ? cfg.recallPlannerMaxMemoryHints : 24,
|
|
2731
|
-
recallPlannerShadowMode: cfg.recallPlannerShadowMode
|
|
2732
|
-
recallPlannerTelemetryEnabled: cfg.recallPlannerTelemetryEnabled
|
|
2898
|
+
recallPlannerShadowMode: coerceBooleanLike(cfg.recallPlannerShadowMode) ?? false,
|
|
2899
|
+
recallPlannerTelemetryEnabled: coerceBooleanLike(cfg.recallPlannerTelemetryEnabled) ?? true,
|
|
2733
2900
|
recallPlannerMaxQmdResultsMinimal:
|
|
2734
2901
|
typeof cfg.recallPlannerMaxQmdResultsMinimal === "number"
|
|
2735
2902
|
? cfg.recallPlannerMaxQmdResultsMinimal
|
|
@@ -3422,6 +3589,10 @@ export function parseConfig(raw: unknown): PluginConfig {
|
|
|
3422
3589
|
? cfg.binaryLifecycleBackendPath.trim()
|
|
3423
3590
|
: "",
|
|
3424
3591
|
|
|
3592
|
+
// Legacy MCP tool aliases opt-out (issue #1427). Config field wins; then
|
|
3593
|
+
// the REMNIC_/ENGRAM_ env var (gotcha #9); default true for back-compat.
|
|
3594
|
+
// Malformed values fail fast rather than silently defaulting (gotcha #51).
|
|
3595
|
+
emitLegacyTools: resolveEmitLegacyTools(cfg.emitLegacyTools),
|
|
3425
3596
|
// Codex citation parity (issue #379)
|
|
3426
3597
|
citationsEnabled: cfg.citationsEnabled === true,
|
|
3427
3598
|
citationsAutoDetect: cfg.citationsAutoDetect !== false,
|
|
@@ -70,8 +70,9 @@ export async function runCodexMaterialize(
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
// Per-trigger gate: session-end runs must honor codexMaterializeOnSessionEnd.
|
|
73
|
-
//
|
|
74
|
-
//
|
|
73
|
+
// The Codex Stop hook (remnic-codex-hook.cjs, session-end event) passes
|
|
74
|
+
// reason="session_end"; when the user has turned off the session-end trigger
|
|
75
|
+
// we short-circuit here without touching disk.
|
|
75
76
|
if (options.reason === "session_end" && cfg.codexMaterializeOnSessionEnd === false) {
|
|
76
77
|
log.debug(
|
|
77
78
|
`[codex-materialize] skipped — session-end disabled via codexMaterializeOnSessionEnd=false`,
|
|
@@ -199,8 +200,10 @@ function resolveNamespaceDir(
|
|
|
199
200
|
): string {
|
|
200
201
|
if (!cfg.namespacesEnabled) return memoryDir;
|
|
201
202
|
|
|
202
|
-
const
|
|
203
|
-
const
|
|
203
|
+
const configuredDefaultNamespace = (cfg.defaultNamespace ?? "").trim();
|
|
204
|
+
const defaultNamespace =
|
|
205
|
+
configuredDefaultNamespace.length > 0 ? configuredDefaultNamespace : "default";
|
|
206
|
+
const ns = (namespace || defaultNamespace).trim();
|
|
204
207
|
if (!isSafeRouteNamespace(ns)) {
|
|
205
208
|
throw new Error(`invalid materialize namespace: ${ns}`);
|
|
206
209
|
}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import path from "node:path";
|
|
21
|
-
import {
|
|
21
|
+
import { lstat, readdir, readFile, realpath, stat } from "node:fs/promises";
|
|
22
22
|
import { constants as fsConstants } from "node:fs";
|
|
23
23
|
import type { StorageManager } from "./storage.js";
|
|
24
24
|
import {
|
|
@@ -499,7 +499,7 @@ export async function runConsolidationProvenanceCheck(options: {
|
|
|
499
499
|
const scanRoots = ["facts", "corrections", "procedures", "reasoning-traces"];
|
|
500
500
|
for (const rootName of scanRoots) {
|
|
501
501
|
const rootPath = path.join(memoryDir, rootName);
|
|
502
|
-
for await (const file of walkMarkdownFiles(rootPath)) {
|
|
502
|
+
for await (const file of walkMarkdownFiles(rootPath, memoryDir)) {
|
|
503
503
|
if (seenPaths.has(file)) continue;
|
|
504
504
|
try {
|
|
505
505
|
const raw = await readFile(file, "utf-8");
|
|
@@ -531,21 +531,40 @@ export async function runConsolidationProvenanceCheck(options: {
|
|
|
531
531
|
/**
|
|
532
532
|
* Recursively yield all `.md` file paths under `root`. Silent on
|
|
533
533
|
* missing directories — the facts/corrections dirs may not exist in
|
|
534
|
-
* fresh installs.
|
|
534
|
+
* fresh installs. Symlinked roots/directories are skipped so the
|
|
535
|
+
* best-effort parse-failure pass cannot escape `memoryDir`.
|
|
535
536
|
*/
|
|
536
|
-
async function* walkMarkdownFiles(root: string): AsyncGenerator<string> {
|
|
537
|
+
async function* walkMarkdownFiles(root: string, memoryDir: string): AsyncGenerator<string> {
|
|
537
538
|
let entries;
|
|
539
|
+
let memoryDirReal: string;
|
|
538
540
|
try {
|
|
541
|
+
const rootStat = await lstat(root);
|
|
542
|
+
if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) return;
|
|
543
|
+
memoryDirReal = await realpath(memoryDir);
|
|
544
|
+
const rootReal = await realpath(root);
|
|
545
|
+
if (!isPathWithin(rootReal, memoryDirReal)) return;
|
|
539
546
|
entries = await readdir(root, { withFileTypes: true });
|
|
540
547
|
} catch {
|
|
541
548
|
return;
|
|
542
549
|
}
|
|
543
550
|
for (const entry of entries) {
|
|
544
551
|
const full = path.join(root, entry.name);
|
|
552
|
+
if (entry.isSymbolicLink()) continue;
|
|
545
553
|
if (entry.isDirectory()) {
|
|
546
|
-
yield* walkMarkdownFiles(full);
|
|
554
|
+
yield* walkMarkdownFiles(full, memoryDirReal);
|
|
547
555
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
556
|
+
try {
|
|
557
|
+
const fileReal = await realpath(full);
|
|
558
|
+
if (!isPathWithin(fileReal, memoryDirReal)) continue;
|
|
559
|
+
} catch {
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
548
562
|
yield full;
|
|
549
563
|
}
|
|
550
564
|
}
|
|
551
565
|
}
|
|
566
|
+
|
|
567
|
+
function isPathWithin(candidate: string, root: string): boolean {
|
|
568
|
+
const relative = path.relative(root, candidate);
|
|
569
|
+
return relative === "" || (!!relative && !relative.startsWith("..") && !path.isAbsolute(relative));
|
|
570
|
+
}
|
|
@@ -98,6 +98,28 @@ test("writeConversationChunks keeps distinct raw session keys from overwriting a
|
|
|
98
98
|
}
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
+
test("writeConversationChunks quotes metadata scalars in front matter", async () => {
|
|
102
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-conversation-index-"));
|
|
103
|
+
try {
|
|
104
|
+
const sessionKey = "agent\nkind: other\n---\ncolon: value";
|
|
105
|
+
const written = await writeConversationChunks(root, [
|
|
106
|
+
sampleChunk({
|
|
107
|
+
sessionKey,
|
|
108
|
+
startTs: "2026-05-17T00:00:00.000Z",
|
|
109
|
+
endTs: "2026-05-17T00:01:00.000Z",
|
|
110
|
+
}),
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
const content = await readFile(written[0]!, "utf-8");
|
|
114
|
+
assert.match(content, /^sessionKey: "agent\\nkind: other\\n---\\ncolon: value"$/m);
|
|
115
|
+
assert.match(content, /^startTs: "2026-05-17T00:00:00.000Z"$/m);
|
|
116
|
+
assert.match(content, /^endTs: "2026-05-17T00:01:00.000Z"$/m);
|
|
117
|
+
assert.doesNotMatch(content, /^kind: other$/m);
|
|
118
|
+
} finally {
|
|
119
|
+
await rm(root, { recursive: true, force: true });
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
101
123
|
test("writeConversationChunks rejects invalid chunk timestamps before deriving paths", async () => {
|
|
102
124
|
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-conversation-index-"));
|
|
103
125
|
try {
|
|
@@ -40,6 +40,10 @@ function sanitizeChunkId(id: string): string {
|
|
|
40
40
|
return sanitizePathComponent(id, "chunk");
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
function yamlQuotedScalar(value: string): string {
|
|
44
|
+
return JSON.stringify(value);
|
|
45
|
+
}
|
|
46
|
+
|
|
43
47
|
function datePathComponent(startTs: string): string {
|
|
44
48
|
const match = typeof startTs === "string"
|
|
45
49
|
? /^(\d{4})-(\d{2})-(\d{2})T/.exec(startTs)
|
|
@@ -153,9 +157,9 @@ export async function writeConversationChunks(
|
|
|
153
157
|
const content =
|
|
154
158
|
`---\n` +
|
|
155
159
|
`kind: conversation_chunk\n` +
|
|
156
|
-
`sessionKey: ${c.sessionKey}\n` +
|
|
157
|
-
`startTs: ${c.startTs}\n` +
|
|
158
|
-
`endTs: ${c.endTs}\n` +
|
|
160
|
+
`sessionKey: ${yamlQuotedScalar(c.sessionKey)}\n` +
|
|
161
|
+
`startTs: ${yamlQuotedScalar(c.startTs)}\n` +
|
|
162
|
+
`endTs: ${yamlQuotedScalar(c.endTs)}\n` +
|
|
159
163
|
`---\n\n` +
|
|
160
164
|
c.text +
|
|
161
165
|
"\n";
|
|
@@ -40,18 +40,47 @@ test("enabled budget warns past soft and denies past hard", () => {
|
|
|
40
40
|
const limiter = new CrossNamespaceBudget({
|
|
41
41
|
enabled: true,
|
|
42
42
|
softLimit: 2,
|
|
43
|
-
hardLimit:
|
|
43
|
+
hardLimit: 4,
|
|
44
44
|
windowMs: 10_000,
|
|
45
45
|
});
|
|
46
46
|
assert.equal(limiter.record("p1", 1).reason, "allowed-under-soft");
|
|
47
47
|
assert.equal(limiter.record("p1", 2).reason, "allowed-under-soft");
|
|
48
48
|
assert.equal(limiter.record("p1", 3).reason, "warn-over-soft");
|
|
49
|
-
// 4th call
|
|
49
|
+
// 4th call reaches hardLimit and is denied.
|
|
50
50
|
const deny = limiter.record("p1", 4);
|
|
51
51
|
assert.equal(deny.allowed, false);
|
|
52
52
|
assert.equal(deny.reason, "deny-over-hard");
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
+
test("enabled budget denies the threshold-crossing hard-limit request", () => {
|
|
56
|
+
const limiter = new CrossNamespaceBudget({
|
|
57
|
+
enabled: true,
|
|
58
|
+
softLimit: 0,
|
|
59
|
+
hardLimit: 2,
|
|
60
|
+
windowMs: 10_000,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const first = limiter.record("p1", 1);
|
|
64
|
+
assert.equal(first.allowed, true);
|
|
65
|
+
assert.equal(first.reason, "warn-over-soft");
|
|
66
|
+
assert.equal(first.count, 1);
|
|
67
|
+
|
|
68
|
+
const beforeSecond = limiter.peek({
|
|
69
|
+
principal: "p1",
|
|
70
|
+
principalNamespace: "alice",
|
|
71
|
+
queryNamespace: "bob",
|
|
72
|
+
now: 2,
|
|
73
|
+
});
|
|
74
|
+
assert.equal(beforeSecond.allowed, false);
|
|
75
|
+
assert.equal(beforeSecond.reason, "deny-over-hard");
|
|
76
|
+
assert.equal(beforeSecond.count, 1);
|
|
77
|
+
|
|
78
|
+
const second = limiter.record("p1", 2);
|
|
79
|
+
assert.equal(second.allowed, false);
|
|
80
|
+
assert.equal(second.reason, "deny-over-hard");
|
|
81
|
+
assert.equal(second.count, 1);
|
|
82
|
+
});
|
|
83
|
+
|
|
55
84
|
test("sliding window drops old timestamps", () => {
|
|
56
85
|
const limiter = new CrossNamespaceBudget({
|
|
57
86
|
enabled: true,
|
|
@@ -61,10 +90,9 @@ test("sliding window drops old timestamps", () => {
|
|
|
61
90
|
});
|
|
62
91
|
// Fill to hard.
|
|
63
92
|
limiter.record("p1", 0);
|
|
64
|
-
limiter.record("p1", 50);
|
|
65
|
-
assert.equal(limiter.record("p1", 80).reason, "deny-over-hard");
|
|
93
|
+
assert.equal(limiter.record("p1", 50).reason, "deny-over-hard");
|
|
66
94
|
|
|
67
|
-
// Walk past the window so the first
|
|
95
|
+
// Walk past the window so the first allowed timestamp slides out.
|
|
68
96
|
const d = limiter.record("p1", 201);
|
|
69
97
|
assert.equal(d.allowed, true);
|
|
70
98
|
assert.equal(d.reason, "allowed-under-soft");
|
|
@@ -75,7 +103,7 @@ test("per-principal isolation: one principal's denial does not affect another",
|
|
|
75
103
|
const limiter = new CrossNamespaceBudget({
|
|
76
104
|
enabled: true,
|
|
77
105
|
softLimit: 1,
|
|
78
|
-
hardLimit:
|
|
106
|
+
hardLimit: 2,
|
|
79
107
|
windowMs: 10_000,
|
|
80
108
|
});
|
|
81
109
|
limiter.record("alice", 10);
|
|
@@ -107,7 +135,7 @@ test("check() engages on cross-namespace", () => {
|
|
|
107
135
|
const limiter = new CrossNamespaceBudget({
|
|
108
136
|
enabled: true,
|
|
109
137
|
softLimit: 1,
|
|
110
|
-
hardLimit:
|
|
138
|
+
hardLimit: 2,
|
|
111
139
|
windowMs: 10_000,
|
|
112
140
|
});
|
|
113
141
|
const d1 = limiter.check({
|
|
@@ -131,7 +159,7 @@ test("denied calls do not push bucket forward", () => {
|
|
|
131
159
|
const limiter = new CrossNamespaceBudget({
|
|
132
160
|
enabled: true,
|
|
133
161
|
softLimit: 1,
|
|
134
|
-
hardLimit:
|
|
162
|
+
hardLimit: 2,
|
|
135
163
|
windowMs: 100,
|
|
136
164
|
});
|
|
137
165
|
limiter.record("p1", 0);
|
|
@@ -150,7 +178,7 @@ test("missing principal is bucketed under __anonymous__ rather than failing open
|
|
|
150
178
|
const limiter = new CrossNamespaceBudget({
|
|
151
179
|
enabled: true,
|
|
152
180
|
softLimit: 0,
|
|
153
|
-
hardLimit:
|
|
181
|
+
hardLimit: 2,
|
|
154
182
|
windowMs: 10_000,
|
|
155
183
|
});
|
|
156
184
|
// An empty-string principal shares the anonymous bucket.
|
|
@@ -168,8 +196,7 @@ test("reset clears all state", () => {
|
|
|
168
196
|
windowMs: 10_000,
|
|
169
197
|
});
|
|
170
198
|
limiter.record("p1", 1);
|
|
171
|
-
limiter.record("p1", 2);
|
|
172
|
-
assert.equal(limiter.record("p1", 3).reason, "deny-over-hard");
|
|
199
|
+
assert.equal(limiter.record("p1", 2).reason, "deny-over-hard");
|
|
173
200
|
limiter.reset();
|
|
174
201
|
const after = limiter.record("p1", 4);
|
|
175
202
|
assert.equal(after.allowed, true);
|
|
@@ -231,7 +258,7 @@ test("record() normalizes non-finite clocks before mutating limiter state", () =
|
|
|
231
258
|
const limiter = new CrossNamespaceBudget({
|
|
232
259
|
enabled: true,
|
|
233
260
|
softLimit: 1,
|
|
234
|
-
hardLimit:
|
|
261
|
+
hardLimit: 2,
|
|
235
262
|
windowMs: 100,
|
|
236
263
|
});
|
|
237
264
|
|
|
@@ -254,7 +281,7 @@ test("peek() with a non-finite clock is read-only and does not poison state", ()
|
|
|
254
281
|
const limiter = new CrossNamespaceBudget({
|
|
255
282
|
enabled: true,
|
|
256
283
|
softLimit: 0,
|
|
257
|
-
hardLimit:
|
|
284
|
+
hardLimit: 2,
|
|
258
285
|
windowMs: 100,
|
|
259
286
|
});
|
|
260
287
|
|
|
@@ -293,14 +320,10 @@ test("bucket is evicted after a denial rolls the only timestamp back", () => {
|
|
|
293
320
|
hardLimit: 1,
|
|
294
321
|
windowMs: 100,
|
|
295
322
|
});
|
|
296
|
-
limiter.record("p1", 0);
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
limiter.record("p1", 150);
|
|
301
|
-
// (wait — the above is allowed because the earlier timestamp slid out.)
|
|
302
|
-
// Force a deny path differently:
|
|
303
|
-
assert.equal(limiter.bucketCount(), 1);
|
|
323
|
+
const denied = limiter.record("p1", 0);
|
|
324
|
+
assert.equal(denied.allowed, false);
|
|
325
|
+
assert.equal(denied.reason, "deny-over-hard");
|
|
326
|
+
assert.equal(limiter.bucketCount(), 0);
|
|
304
327
|
});
|
|
305
328
|
|
|
306
329
|
test("check() does NOT fail-open when both namespaces are empty or undefined", () => {
|
|
@@ -207,7 +207,7 @@ export class CrossNamespaceBudget {
|
|
|
207
207
|
this.buckets.set(principal, bucket);
|
|
208
208
|
const count = bucket.timestamps.length;
|
|
209
209
|
|
|
210
|
-
if (count
|
|
210
|
+
if (count >= hardLimit) {
|
|
211
211
|
// Denied: roll back the timestamp we just added so a repeated denied
|
|
212
212
|
// call does not push the bucket further into the future. This keeps
|
|
213
213
|
// the limiter stateless with respect to denied attempts.
|
|
@@ -290,7 +290,7 @@ export class CrossNamespaceBudget {
|
|
|
290
290
|
if (ts >= cutoff) liveCount++;
|
|
291
291
|
}
|
|
292
292
|
const projected = liveCount + 1; // +1 for the current call
|
|
293
|
-
if (projected
|
|
293
|
+
if (projected >= hardLimit) {
|
|
294
294
|
return { allowed: false, reason: "deny-over-hard", count: liveCount, limit };
|
|
295
295
|
}
|
|
296
296
|
if (projected > softLimit) {
|
|
@@ -32,12 +32,12 @@ interface RateLimitBucket {
|
|
|
32
32
|
|
|
33
33
|
const rateBuckets = new Map<string, RateLimitBucket>();
|
|
34
34
|
|
|
35
|
-
function
|
|
35
|
+
function reserveRateLimitSlot(
|
|
36
36
|
provider: EnrichmentProvider,
|
|
37
37
|
config: EnrichmentPipelineConfig,
|
|
38
38
|
): boolean {
|
|
39
39
|
const providerCfg = config.providers.find((p) => p.id === provider.id);
|
|
40
|
-
if (!providerCfg?.rateLimit) return
|
|
40
|
+
if (!providerCfg?.rateLimit) return true;
|
|
41
41
|
|
|
42
42
|
const now = Date.now();
|
|
43
43
|
let bucket = rateBuckets.get(provider.id);
|
|
@@ -62,17 +62,13 @@ function isRateLimited(
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
const { maxPerMinute, maxPerDay } = providerCfg.rateLimit;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
function recordCall(
|
|
69
|
-
providerId: string,
|
|
70
|
-
): void {
|
|
71
|
-
const bucket = rateBuckets.get(providerId);
|
|
72
|
-
if (bucket) {
|
|
73
|
-
bucket.minuteCount += 1;
|
|
74
|
-
bucket.dayCount += 1;
|
|
65
|
+
if (bucket.minuteCount >= maxPerMinute || bucket.dayCount >= maxPerDay) {
|
|
66
|
+
return false;
|
|
75
67
|
}
|
|
68
|
+
|
|
69
|
+
bucket.minuteCount += 1;
|
|
70
|
+
bucket.dayCount += 1;
|
|
71
|
+
return true;
|
|
76
72
|
}
|
|
77
73
|
|
|
78
74
|
// ---------------------------------------------------------------------------
|
|
@@ -129,8 +125,9 @@ export async function runEnrichmentPipeline(
|
|
|
129
125
|
continue;
|
|
130
126
|
}
|
|
131
127
|
|
|
132
|
-
//
|
|
133
|
-
|
|
128
|
+
// Reserve quota before the awaited provider call so concurrent pipelines
|
|
129
|
+
// cannot all pass the same pre-await rate-limit check.
|
|
130
|
+
if (!reserveRateLimitSlot(provider, config)) {
|
|
134
131
|
log.debug?.(
|
|
135
132
|
`enrichment: skipping provider ${provider.id} for ${entity.name} — rate limited`,
|
|
136
133
|
);
|
|
@@ -154,7 +151,6 @@ export async function runEnrichmentPipeline(
|
|
|
154
151
|
try {
|
|
155
152
|
candidates = await provider.enrich(entity);
|
|
156
153
|
} catch (err) {
|
|
157
|
-
recordCall(provider.id);
|
|
158
154
|
log.error?.(
|
|
159
155
|
`enrichment: provider ${provider.id} failed for ${entity.name}: ${err instanceof Error ? err.message : String(err)}`,
|
|
160
156
|
);
|
|
@@ -169,7 +165,6 @@ export async function runEnrichmentPipeline(
|
|
|
169
165
|
});
|
|
170
166
|
continue;
|
|
171
167
|
}
|
|
172
|
-
recordCall(provider.id);
|
|
173
168
|
|
|
174
169
|
// Tag each candidate with provider id
|
|
175
170
|
for (const candidate of candidates) {
|
package/src/evals.ts
CHANGED
|
@@ -359,7 +359,7 @@ export function validateEvalBenchmarkManifest(
|
|
|
359
359
|
|
|
360
360
|
return {
|
|
361
361
|
schemaVersion: 1,
|
|
362
|
-
benchmarkId: assertString(raw.benchmarkId, "benchmarkId"),
|
|
362
|
+
benchmarkId: assertSafeBenchmarkId(assertString(raw.benchmarkId, "benchmarkId")),
|
|
363
363
|
benchmarkType,
|
|
364
364
|
title: assertString(raw.title, "title"),
|
|
365
365
|
description:
|
package/src/explicit-capture.ts
CHANGED
|
@@ -25,6 +25,16 @@ export type ValidExplicitCapture = {
|
|
|
25
25
|
entityRef?: string;
|
|
26
26
|
expiresAt?: string;
|
|
27
27
|
sourceReason?: string;
|
|
28
|
+
/**
|
|
29
|
+
* When true, `namespace` was already resolved AND authorized by the caller
|
|
30
|
+
* (the access service's `resolveCodingScopedWriteNamespace`, which auth-checks
|
|
31
|
+
* the base and derives a session-owned `project-*` overlay). The persist /
|
|
32
|
+
* queue layer then routes to it directly instead of re-validating against the
|
|
33
|
+
* static policy allow-list — which would otherwise reject legitimately-derived
|
|
34
|
+
* dynamic project namespaces (#1434). Callers that do NOT pre-authorize the
|
|
35
|
+
* namespace must leave this unset so the allow-list guard still applies.
|
|
36
|
+
*/
|
|
37
|
+
namespacePreResolved?: boolean;
|
|
28
38
|
};
|
|
29
39
|
|
|
30
40
|
export type ExplicitCaptureSource = "memory_store" | "memory_capture" | "suggestion_submit" | "inline";
|
|
@@ -404,7 +414,9 @@ export async function persistExplicitCapture(
|
|
|
404
414
|
candidate: ValidExplicitCapture,
|
|
405
415
|
source: ExplicitCaptureSource,
|
|
406
416
|
): Promise<{ id: string; duplicateOf?: string }> {
|
|
407
|
-
const resolvedNamespace =
|
|
417
|
+
const resolvedNamespace = candidate.namespacePreResolved
|
|
418
|
+
? asTrimmed(candidate.namespace)
|
|
419
|
+
: resolveExplicitCaptureNamespace(orchestrator, candidate.namespace);
|
|
408
420
|
const duplicateOf = await findDuplicateExplicitCapture(orchestrator, resolvedNamespace, candidate);
|
|
409
421
|
if (duplicateOf) {
|
|
410
422
|
return { id: duplicateOf, duplicateOf };
|
|
@@ -490,7 +502,12 @@ export async function queueExplicitCaptureForReview(
|
|
|
490
502
|
): Promise<{ id: string; duplicateOf?: string }> {
|
|
491
503
|
const reason = sanitizeReviewText(normalizeExplicitCaptureError(error), "explicit capture failed");
|
|
492
504
|
const requestedNamespace = asTrimmed(input.namespace);
|
|
493
|
-
|
|
505
|
+
// A caller-pre-authorized namespace (e.g. a session-owned project overlay
|
|
506
|
+
// from the access service) routes directly; otherwise apply the static
|
|
507
|
+
// policy allow-list guard (#1434).
|
|
508
|
+
const queueNamespace = (input as { namespacePreResolved?: boolean }).namespacePreResolved
|
|
509
|
+
? requestedNamespace
|
|
510
|
+
: resolveExplicitCaptureReviewNamespace(orchestrator, requestedNamespace);
|
|
494
511
|
const content = buildExplicitCaptureReviewContent(input, reason);
|
|
495
512
|
const duplicateOf = await findQueuedExplicitCaptureDuplicate(orchestrator, queueNamespace, content);
|
|
496
513
|
if (duplicateOf) {
|