@remnic/core 9.3.612 → 9.3.614
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 +58 -57
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +4 -2
- package/dist/access-http.js +22 -22
- package/dist/access-mcp.d.ts +9 -2
- package/dist/access-mcp.js +19 -19
- package/dist/access-schema.d.ts +12 -12
- package/dist/access-schema.js +3 -3
- package/dist/{access-service-D2J9dh_9.d.ts → access-service-DGG_2xPK.d.ts} +1 -1
- package/dist/access-service.d.ts +2 -2
- package/dist/access-service.js +16 -16
- 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-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-IOTENEVL.js → chunk-7YQFWOF7.js} +57 -50
- package/dist/chunk-7YQFWOF7.js.map +1 -0
- package/dist/{chunk-2QANQKSQ.js → chunk-ADNZVFXG.js} +15 -15
- 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-TH67Q46T.js → chunk-B6FDZPCF.js} +17 -9
- package/dist/chunk-B6FDZPCF.js.map +1 -0
- 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-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-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-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-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-PPPZY2EU.js → chunk-QEMCQFDW.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-HJNQQICM.js → chunk-T5XWMMU2.js} +107 -50
- package/dist/chunk-T5XWMMU2.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-5RPTH6AU.js → chunk-VPGUMLBA.js} +8 -7
- package/dist/chunk-VPGUMLBA.js.map +1 -0
- package/dist/{chunk-KM2A35EO.js → chunk-WB3LYXC5.js} +11 -7
- package/dist/chunk-WB3LYXC5.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/{cli-OrfKXNU4.d.ts → cli-DWeu7eTY.d.ts} +6 -2
- package/dist/cli.d.ts +3 -3
- package/dist/cli.js +60 -59
- 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 +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 +94 -93
- 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 +47 -44
- 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/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/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 +3 -0
- package/src/access-mcp.test.ts +51 -0
- package/src/access-mcp.ts +26 -5
- 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/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-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-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-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-2QANQKSQ.js.map → chunk-ADNZVFXG.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-PPPZY2EU.js.map → chunk-QEMCQFDW.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/qmd.ts
CHANGED
|
@@ -9,7 +9,12 @@ import {
|
|
|
9
9
|
throwIfAborted,
|
|
10
10
|
} from "./abort-error.js";
|
|
11
11
|
import type { QmdSearchExplain, QmdSearchResult } from "./types.js";
|
|
12
|
-
import
|
|
12
|
+
import {
|
|
13
|
+
resolveEnsureCollectionArgs,
|
|
14
|
+
type SearchBackend,
|
|
15
|
+
type SearchExecutionOptions,
|
|
16
|
+
type SearchQueryOptions,
|
|
17
|
+
} from "./search/port.js";
|
|
13
18
|
import { launchProcess, type CommandChildProcess } from "./runtime/child-process.js";
|
|
14
19
|
import { mergeEnv } from "./runtime/env.js";
|
|
15
20
|
|
|
@@ -33,11 +38,32 @@ export interface QmdClientOptions {
|
|
|
33
38
|
qmdEmbedModel?: string;
|
|
34
39
|
qmdRerankModel?: string;
|
|
35
40
|
qmdGenerateModel?: string;
|
|
41
|
+
/** Daemon search plan; default "hybrid" preserves lex+vec+hyde. Issue #1335. */
|
|
42
|
+
qmdSearchStrategy?: QmdSearchStrategy;
|
|
43
|
+
/** Subprocess fallback command; default "query" keeps LLM expansion. Issue #1335. */
|
|
44
|
+
qmdSubprocessStrategy?: QmdSubprocessStrategy;
|
|
45
|
+
/** Per-call daemon search timeout in ms; default 8000. Issue #1335. */
|
|
46
|
+
qmdDaemonTimeoutMs?: number;
|
|
36
47
|
}
|
|
37
48
|
|
|
38
49
|
export type QmdVersionTuple = [number, number, number];
|
|
39
50
|
export type QmdChunkStrategy = "auto" | "regex";
|
|
40
51
|
export type QmdStructuredSearchType = "lex" | "vec" | "hyde";
|
|
52
|
+
/**
|
|
53
|
+
* Daemon search plan. Issue #1335.
|
|
54
|
+
* - "hybrid" → lex + vec + hyde (DEFAULT — full recall; runs an embedding pass
|
|
55
|
+
* plus a HyDE generate pass, which dominates latency on CPU-only models).
|
|
56
|
+
* - "lex-vec" → lex + vec (drops the expensive HyDE generate leg).
|
|
57
|
+
* - "lex" → lex only (BM25; fastest, no model inference).
|
|
58
|
+
*/
|
|
59
|
+
export type QmdSearchStrategy = "hybrid" | "lex-vec" | "lex";
|
|
60
|
+
/**
|
|
61
|
+
* Subprocess fallback command (used only when the daemon is unavailable). Issue #1335.
|
|
62
|
+
* - "query" → `qmd query` (DEFAULT — LLM query expansion + rerank; see CLAUDE.md
|
|
63
|
+
* gotcha #7, this is intentional and must remain the default).
|
|
64
|
+
* - "search" → `qmd search` (BM25-only; fast, but no expansion/rerank).
|
|
65
|
+
*/
|
|
66
|
+
export type QmdSubprocessStrategy = "query" | "search";
|
|
41
67
|
export interface QmdStructuredSearch {
|
|
42
68
|
type: QmdStructuredSearchType;
|
|
43
69
|
query: string;
|
|
@@ -100,6 +126,9 @@ const QMD_TIMEOUT_MS = 30_000;
|
|
|
100
126
|
// During the loading window, searches will timeout/return [] quickly — this is preferable to
|
|
101
127
|
// blocking the full 75s on every recall request.
|
|
102
128
|
// Note: keep this ≥ 5s to allow normal searches (post-load) to complete reliably.
|
|
129
|
+
// This is the DEFAULT only — operators can override per-client via the
|
|
130
|
+
// `qmdDaemonTimeoutMs` config knob (issue #1335), e.g. to give CPU-only HyDE
|
|
131
|
+
// queries more headroom. The effective value lives in `this.daemonTimeoutMs`.
|
|
103
132
|
const QMD_DAEMON_TIMEOUT_MS = 8_000;
|
|
104
133
|
const QMD_PROBE_TIMEOUT_MS = 8_000;
|
|
105
134
|
const QMD_UPDATE_BACKOFF_MS = 15 * 60 * 1000; // 15m
|
|
@@ -404,17 +433,32 @@ function buildSyntheticHydeQuery(query: string, intent?: string): string {
|
|
|
404
433
|
: base;
|
|
405
434
|
}
|
|
406
435
|
|
|
407
|
-
|
|
436
|
+
/**
|
|
437
|
+
* Build the structured sub-queries the daemon `query` tool runs in one request.
|
|
438
|
+
*
|
|
439
|
+
* The default `"hybrid"` plan (lex + vec + hyde) intentionally exercises QMD's
|
|
440
|
+
* full RRF + rerank path for best recall. On CPU-only models the `hyde` leg
|
|
441
|
+
* (1.7B generate + embed) dominates wall-clock — operators on constrained
|
|
442
|
+
* hardware can trade recall for latency via `qmdSearchStrategy` (issue #1335)
|
|
443
|
+
* without losing the default behavior. A per-call `structuredSearches` override
|
|
444
|
+
* always wins so callers with stronger query-document structure stay in control.
|
|
445
|
+
*/
|
|
446
|
+
export function buildDefaultStructuredSearches(
|
|
408
447
|
query: string,
|
|
409
448
|
options?: SearchQueryOptions,
|
|
449
|
+
strategy: QmdSearchStrategy = "hybrid",
|
|
410
450
|
): QmdStructuredSearch[] {
|
|
411
451
|
const explicit = normalizeStructuredSearches(options?.structuredSearches);
|
|
412
452
|
if (explicit.length > 0) return explicit;
|
|
413
453
|
const trimmed = query.trim();
|
|
414
454
|
if (!trimmed) return [];
|
|
455
|
+
const lex: QmdStructuredSearch = { type: "lex", query: trimmed };
|
|
456
|
+
if (strategy === "lex") return [lex];
|
|
457
|
+
const vec: QmdStructuredSearch = { type: "vec", query: trimmed };
|
|
458
|
+
if (strategy === "lex-vec") return [lex, vec];
|
|
415
459
|
return [
|
|
416
|
-
|
|
417
|
-
|
|
460
|
+
lex,
|
|
461
|
+
vec,
|
|
418
462
|
{ type: "hyde", query: buildSyntheticHydeQuery(trimmed, options?.intent) },
|
|
419
463
|
];
|
|
420
464
|
}
|
|
@@ -1202,6 +1246,9 @@ export class QmdClient implements SearchBackend {
|
|
|
1202
1246
|
private readonly qmdCandidateLimit?: number;
|
|
1203
1247
|
private readonly qmdQueryRerankEnabled: boolean;
|
|
1204
1248
|
private readonly qmdIndexName?: string;
|
|
1249
|
+
private readonly qmdSearchStrategy: QmdSearchStrategy;
|
|
1250
|
+
private readonly qmdSubprocessStrategy: QmdSubprocessStrategy;
|
|
1251
|
+
private readonly daemonTimeoutMs: number;
|
|
1205
1252
|
private readonly qmdRuntimeEnv: QmdRuntimeEnv;
|
|
1206
1253
|
private qmdPathSource: "auto-path" | "auto-fallback" | "configured" = "auto-path";
|
|
1207
1254
|
private cliVersion: string | null = null;
|
|
@@ -1249,6 +1296,20 @@ export class QmdClient implements SearchBackend {
|
|
|
1249
1296
|
: undefined;
|
|
1250
1297
|
this.qmdQueryRerankEnabled = opts?.qmdQueryRerankEnabled !== false;
|
|
1251
1298
|
this.qmdIndexName = opts?.qmdIndexName?.trim() || undefined;
|
|
1299
|
+
// Default "hybrid" preserves the historical lex+vec+hyde daemon plan. Issue #1335.
|
|
1300
|
+
this.qmdSearchStrategy =
|
|
1301
|
+
opts?.qmdSearchStrategy === "lex" || opts?.qmdSearchStrategy === "lex-vec"
|
|
1302
|
+
? opts.qmdSearchStrategy
|
|
1303
|
+
: "hybrid";
|
|
1304
|
+
// Default "query" keeps `qmd query` (LLM expansion + rerank) per gotcha #7. Issue #1335.
|
|
1305
|
+
this.qmdSubprocessStrategy = opts?.qmdSubprocessStrategy === "search" ? "search" : "query";
|
|
1306
|
+
// Default 8000ms preserves the historical hardcoded daemon timeout. Issue #1335.
|
|
1307
|
+
// Floor of 1000ms avoids absurdly small values; callers wanting CPU-only HyDE
|
|
1308
|
+
// headroom can raise this (e.g. 20000) without code changes.
|
|
1309
|
+
this.daemonTimeoutMs =
|
|
1310
|
+
typeof opts?.qmdDaemonTimeoutMs === "number" && Number.isFinite(opts.qmdDaemonTimeoutMs)
|
|
1311
|
+
? Math.max(1_000, Math.floor(opts.qmdDaemonTimeoutMs))
|
|
1312
|
+
: QMD_DAEMON_TIMEOUT_MS;
|
|
1252
1313
|
this.qmdRuntimeEnv = this.buildRuntimeEnv(opts);
|
|
1253
1314
|
if (this.configuredQmdPath) {
|
|
1254
1315
|
this.qmdPath = this.configuredQmdPath;
|
|
@@ -1823,7 +1884,15 @@ export class QmdClient implements SearchBackend {
|
|
|
1823
1884
|
// repeated queries within the same recall cycle (e.g., primary + hybrid
|
|
1824
1885
|
// top-up, or conversation recall using the same collection).
|
|
1825
1886
|
const optionsFingerprint = searchOptions ? JSON.stringify(searchOptions) : "";
|
|
1826
|
-
|
|
1887
|
+
// The QMD search cache is a process-global keyed map (memory-cache.ts), so the
|
|
1888
|
+
// key must capture every input that changes the result — including the daemon
|
|
1889
|
+
// and subprocess strategies. Otherwise two QmdClient instances (or a reloaded
|
|
1890
|
+
// config) with different strategies collide within the TTL and one plan serves
|
|
1891
|
+
// another plan's cached results. Issue #1335 (codex review on #1422).
|
|
1892
|
+
const strategyFingerprint = `${this.qmdSearchStrategy}:${this.qmdSubprocessStrategy}`;
|
|
1893
|
+
const cacheKey = createHash("sha256")
|
|
1894
|
+
.update(`${strategyFingerprint}:${col}:${n}:${optionsFingerprint}:${trimmed}`)
|
|
1895
|
+
.digest("hex");
|
|
1827
1896
|
const cached = getCachedQmdSearch(cacheKey);
|
|
1828
1897
|
if (cached) {
|
|
1829
1898
|
log.debug(`QMD search cache hit (${cached.length} results)`);
|
|
@@ -2066,8 +2135,9 @@ export class QmdClient implements SearchBackend {
|
|
|
2066
2135
|
// QMD v2: query tool expects { searches: [...], collections?: [...] }
|
|
2067
2136
|
// The MCP tool is structured-only in 2.x; use lex+vec+hyde by default
|
|
2068
2137
|
// to exercise QMD's RRF + rerank path and let callers override when
|
|
2069
|
-
// they have stronger query-document structure.
|
|
2070
|
-
|
|
2138
|
+
// they have stronger query-document structure. Operators on CPU-only
|
|
2139
|
+
// models can downgrade the plan via qmdSearchStrategy (issue #1335).
|
|
2140
|
+
const searches = buildDefaultStructuredSearches(query, options, this.qmdSearchStrategy);
|
|
2071
2141
|
args = { searches, limit: maxResults };
|
|
2072
2142
|
if (collection) {
|
|
2073
2143
|
args.collections = [collection];
|
|
@@ -2082,7 +2152,7 @@ export class QmdClient implements SearchBackend {
|
|
|
2082
2152
|
this.addResolvedSearchOptionsToMcpArgs(args, options);
|
|
2083
2153
|
}
|
|
2084
2154
|
|
|
2085
|
-
const result = await this.daemonSession.callTool("query", args,
|
|
2155
|
+
const result = await this.daemonSession.callTool("query", args, this.daemonTimeoutMs, signal);
|
|
2086
2156
|
const durationMs = Date.now() - startedAtMs;
|
|
2087
2157
|
|
|
2088
2158
|
if (this.slowLog?.enabled && durationMs >= this.slowLog.thresholdMs) {
|
|
@@ -2134,7 +2204,7 @@ export class QmdClient implements SearchBackend {
|
|
|
2134
2204
|
collections: [collection],
|
|
2135
2205
|
limit: maxResults,
|
|
2136
2206
|
},
|
|
2137
|
-
|
|
2207
|
+
this.daemonTimeoutMs,
|
|
2138
2208
|
signal,
|
|
2139
2209
|
);
|
|
2140
2210
|
} else {
|
|
@@ -2142,7 +2212,7 @@ export class QmdClient implements SearchBackend {
|
|
|
2142
2212
|
result = await this.daemonSession.callTool(
|
|
2143
2213
|
"search",
|
|
2144
2214
|
{ query, limit: maxResults, collection },
|
|
2145
|
-
|
|
2215
|
+
this.daemonTimeoutMs,
|
|
2146
2216
|
signal,
|
|
2147
2217
|
);
|
|
2148
2218
|
}
|
|
@@ -2187,7 +2257,7 @@ export class QmdClient implements SearchBackend {
|
|
|
2187
2257
|
collections: [collection],
|
|
2188
2258
|
limit: maxResults,
|
|
2189
2259
|
},
|
|
2190
|
-
|
|
2260
|
+
this.daemonTimeoutMs,
|
|
2191
2261
|
signal,
|
|
2192
2262
|
);
|
|
2193
2263
|
} else {
|
|
@@ -2195,7 +2265,7 @@ export class QmdClient implements SearchBackend {
|
|
|
2195
2265
|
result = await this.daemonSession.callTool(
|
|
2196
2266
|
"vsearch",
|
|
2197
2267
|
{ query, limit: maxResults, collection },
|
|
2198
|
-
|
|
2268
|
+
this.daemonTimeoutMs,
|
|
2199
2269
|
signal,
|
|
2200
2270
|
);
|
|
2201
2271
|
}
|
|
@@ -2228,6 +2298,17 @@ export class QmdClient implements SearchBackend {
|
|
|
2228
2298
|
): Promise<QmdSearchResult[]> {
|
|
2229
2299
|
if (this.available === false) return [];
|
|
2230
2300
|
|
|
2301
|
+
// INTENTIONAL — DO NOT change the default command from `query` to `search`.
|
|
2302
|
+
// `qmd query` performs LLM query expansion + reranking that Remnic depends on
|
|
2303
|
+
// (Remnic's own reranking is disabled because `qmd query` handles it). This is
|
|
2304
|
+
// CLAUDE.md gotcha #7. On very large collections `qmd query` can be slow/hang
|
|
2305
|
+
// (issue #1335), so operators may opt into BM25-only `qmd search` by setting
|
|
2306
|
+
// `qmdSubprocessStrategy: "search"` — but that trades away expansion + rerank,
|
|
2307
|
+
// so it stays opt-in and the default remains `query`.
|
|
2308
|
+
if (this.qmdSubprocessStrategy === "search") {
|
|
2309
|
+
return this.bm25SearchViaSubprocess(query, collection, maxResults, signal);
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2231
2312
|
const startedAtMs = Date.now();
|
|
2232
2313
|
try {
|
|
2233
2314
|
const args = ["query", query, "-c", collection];
|
|
@@ -2311,15 +2392,24 @@ export class QmdClient implements SearchBackend {
|
|
|
2311
2392
|
|
|
2312
2393
|
const startedAtMs = Date.now();
|
|
2313
2394
|
try {
|
|
2314
|
-
|
|
2395
|
+
// Mirror searchViaSubprocess: default `qmd query` keeps LLM expansion +
|
|
2396
|
+
// rerank (gotcha #7); `qmdSubprocessStrategy: "search"` opts into BM25-only
|
|
2397
|
+
// `qmd search` for both scoped AND global recall so the gate stays uniform
|
|
2398
|
+
// across every subprocess path (gotcha #39). Issue #1335.
|
|
2399
|
+
const bm25 = this.qmdSubprocessStrategy === "search";
|
|
2400
|
+
const args = bm25 ? ["search", query] : ["query", query];
|
|
2315
2401
|
this.addQmdJsonOutputArgs(args);
|
|
2316
2402
|
args.push("-n", String(maxResults));
|
|
2317
|
-
|
|
2403
|
+
// BM25 `qmd search` takes no expansion/rerank options — only forward them
|
|
2404
|
+
// for the `query` command.
|
|
2405
|
+
if (!bm25) {
|
|
2406
|
+
this.addResolvedSearchOptionsToArgs(args, options);
|
|
2407
|
+
}
|
|
2318
2408
|
const { stdout } = await this.runQmdCommand(args, QMD_TIMEOUT_MS, signal);
|
|
2319
2409
|
const durationMs = Date.now() - startedAtMs;
|
|
2320
2410
|
if (this.slowLog?.enabled && durationMs >= this.slowLog.thresholdMs) {
|
|
2321
2411
|
log.warn(
|
|
2322
|
-
`SLOW QMD global query: durationMs=${durationMs} maxResults=${maxResults} queryChars=${query.length}`,
|
|
2412
|
+
`SLOW QMD global ${bm25 ? "search" : "query"}: durationMs=${durationMs} maxResults=${maxResults} queryChars=${query.length}`,
|
|
2323
2413
|
);
|
|
2324
2414
|
}
|
|
2325
2415
|
|
|
@@ -2597,16 +2687,22 @@ export class QmdClient implements SearchBackend {
|
|
|
2597
2687
|
|
|
2598
2688
|
async ensureCollection(
|
|
2599
2689
|
memoryDir: string,
|
|
2690
|
+
collectionOrExecution?: string | SearchExecutionOptions,
|
|
2600
2691
|
execution?: SearchExecutionOptions,
|
|
2601
2692
|
): Promise<"present" | "missing" | "unknown" | "skipped"> {
|
|
2693
|
+
const { collection, execution: effectiveExecution } = resolveEnsureCollectionArgs(
|
|
2694
|
+
collectionOrExecution,
|
|
2695
|
+
execution,
|
|
2696
|
+
);
|
|
2602
2697
|
if (this.available === false && !this.daemonAvailable) return "unknown";
|
|
2603
2698
|
// If only daemon is available (no CLI), skip collection check
|
|
2604
2699
|
if (this.available === false) return "skipped";
|
|
2700
|
+
const targetCollection = collection ?? this.collection;
|
|
2605
2701
|
try {
|
|
2606
|
-
const { stdout } = await this.runQmdCommand(["collection", "list"], QMD_TIMEOUT_MS,
|
|
2702
|
+
const { stdout } = await this.runQmdCommand(["collection", "list"], QMD_TIMEOUT_MS, effectiveExecution?.signal);
|
|
2607
2703
|
// Parse text output: "openclaw-engram (qmd://openclaw-engram/)"
|
|
2608
2704
|
const collectionRegex = new RegExp(
|
|
2609
|
-
`^${
|
|
2705
|
+
`^${escapeRegExp(targetCollection)}\\s+\\(qmd://`,
|
|
2610
2706
|
"m",
|
|
2611
2707
|
);
|
|
2612
2708
|
if (collectionRegex.test(stdout)) {
|
|
@@ -2616,15 +2712,19 @@ export class QmdClient implements SearchBackend {
|
|
|
2616
2712
|
// Treat command/probe failures as unknown so callers do not disable features
|
|
2617
2713
|
// permanently after a transient CLI or daemon hiccup.
|
|
2618
2714
|
log.debug(
|
|
2619
|
-
`QMD collection check unavailable for "${
|
|
2715
|
+
`QMD collection check unavailable for "${targetCollection}" (will not disable features): ${err instanceof Error ? err.message : String(err)}`,
|
|
2620
2716
|
);
|
|
2621
2717
|
return "unknown";
|
|
2622
2718
|
}
|
|
2623
2719
|
|
|
2624
2720
|
log.info(
|
|
2625
|
-
`QMD collection "${
|
|
2721
|
+
`QMD collection "${targetCollection}" not found. ` +
|
|
2626
2722
|
`Add it to ~/.config/qmd/index.yml pointing at ${memoryDir}`,
|
|
2627
2723
|
);
|
|
2628
2724
|
return "missing";
|
|
2629
2725
|
}
|
|
2630
2726
|
}
|
|
2727
|
+
|
|
2728
|
+
function escapeRegExp(value: string): string {
|
|
2729
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2730
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { parseConfig } from "./config.js";
|
|
5
|
+
import {
|
|
6
|
+
planRecallModeLLM,
|
|
7
|
+
resolveRecallPlannerLlmOptions,
|
|
8
|
+
} from "./recall-planner-llm.js";
|
|
9
|
+
import type { FallbackLlmClient } from "./fallback-llm.js";
|
|
10
|
+
import type { RecallPlanMode } from "./types.js";
|
|
11
|
+
|
|
12
|
+
// A stub FallbackLlmClient that records the options it was called with and
|
|
13
|
+
// returns a scripted classification (or simulates a failure).
|
|
14
|
+
function stubLlm(opts: {
|
|
15
|
+
available?: boolean;
|
|
16
|
+
capturedOptions?: Array<Record<string, unknown>>;
|
|
17
|
+
result?: { mode: RecallPlanMode; reason?: string | null } | null;
|
|
18
|
+
modelUsed?: string;
|
|
19
|
+
throwError?: string;
|
|
20
|
+
}): FallbackLlmClient {
|
|
21
|
+
return {
|
|
22
|
+
isAvailable: () => opts.available !== false,
|
|
23
|
+
parseWithSchemaDetailed: async (
|
|
24
|
+
_messages: unknown,
|
|
25
|
+
schema: { parse: (v: unknown) => unknown },
|
|
26
|
+
options: Record<string, unknown>,
|
|
27
|
+
) => {
|
|
28
|
+
opts.capturedOptions?.push(options);
|
|
29
|
+
if (opts.throwError) throw new Error(opts.throwError);
|
|
30
|
+
if (opts.result === null || opts.result === undefined) return null;
|
|
31
|
+
// Exercise the real schema so malformed scripted output is caught too.
|
|
32
|
+
const parsed = schema.parse(opts.result);
|
|
33
|
+
return { result: parsed, modelUsed: opts.modelUsed ?? "test/model" };
|
|
34
|
+
},
|
|
35
|
+
} as unknown as FallbackLlmClient;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
test("returns heuristic without calling the LLM when recallPlannerLlmEnabled is false", async () => {
|
|
39
|
+
const config = parseConfig({ recallPlannerLlmEnabled: false });
|
|
40
|
+
const captured: Array<Record<string, unknown>> = [];
|
|
41
|
+
const llm = stubLlm({ capturedOptions: captured, result: { mode: "no_recall" } });
|
|
42
|
+
|
|
43
|
+
const result = await planRecallModeLLM("what did we decide about auth?", undefined, config, llm);
|
|
44
|
+
|
|
45
|
+
assert.equal(captured.length, 0, "LLM must not be contacted when disabled");
|
|
46
|
+
assert.equal(result.source, "heuristic");
|
|
47
|
+
assert.equal(result.fallbackUsed, false);
|
|
48
|
+
// Memory-seeking question → heuristic "full".
|
|
49
|
+
assert.equal(result.mode, "full");
|
|
50
|
+
assert.equal(result.heuristicMode, "full");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("uses the LLM classification when enabled", async () => {
|
|
54
|
+
const config = parseConfig({ recallPlannerLlmEnabled: true });
|
|
55
|
+
const llm = stubLlm({ result: { mode: "graph_mode", reason: "asks for root cause" }, modelUsed: "anthropic/claude" });
|
|
56
|
+
|
|
57
|
+
const result = await planRecallModeLLM("restart the gateway", undefined, config, llm);
|
|
58
|
+
|
|
59
|
+
assert.equal(result.source, "llm");
|
|
60
|
+
assert.equal(result.mode, "graph_mode");
|
|
61
|
+
assert.equal(result.reason, "asks for root cause");
|
|
62
|
+
assert.equal(result.modelUsed, "anthropic/claude");
|
|
63
|
+
assert.equal(result.fallbackUsed, false);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("forwards taskModelChain AND recallPlannerModel in gateway mode (provider-agnostic routing)", async () => {
|
|
67
|
+
const config = parseConfig({
|
|
68
|
+
recallPlannerLlmEnabled: true,
|
|
69
|
+
modelSource: "gateway",
|
|
70
|
+
gatewayAgentId: "persona-agent",
|
|
71
|
+
taskModelChain: { primary: "zai/glm-4.7-flash", fallbacks: ["fireworks/x/glm-5p1"] },
|
|
72
|
+
recallPlannerModel: "anthropic/claude-haiku-4-5",
|
|
73
|
+
});
|
|
74
|
+
const captured: Array<Record<string, unknown>> = [];
|
|
75
|
+
const llm = stubLlm({ capturedOptions: captured, result: { mode: "minimal" } });
|
|
76
|
+
|
|
77
|
+
await planRecallModeLLM("check status", undefined, config, llm);
|
|
78
|
+
|
|
79
|
+
assert.equal(captured.length, 1);
|
|
80
|
+
// recallPlannerModel is tried first (prepended), taskModelChain is the fallback chain.
|
|
81
|
+
assert.equal(captured[0]?.model, "anthropic/claude-haiku-4-5");
|
|
82
|
+
assert.deepEqual(captured[0]?.modelChain, {
|
|
83
|
+
primary: "zai/glm-4.7-flash",
|
|
84
|
+
fallbacks: ["fireworks/x/glm-5p1"],
|
|
85
|
+
});
|
|
86
|
+
// taskModelChain wins over the agent persona (gotcha #22).
|
|
87
|
+
assert.equal(captured[0]?.agentId, undefined);
|
|
88
|
+
assert.equal(captured[0]?.timeoutMs, config.recallPlannerTimeoutMs);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("plugin mode passes only the explicit model, no gateway chain", async () => {
|
|
92
|
+
const config = parseConfig({
|
|
93
|
+
recallPlannerLlmEnabled: true,
|
|
94
|
+
modelSource: "plugin",
|
|
95
|
+
recallPlannerModel: "openai/gpt-5.5",
|
|
96
|
+
});
|
|
97
|
+
const captured: Array<Record<string, unknown>> = [];
|
|
98
|
+
const llm = stubLlm({ capturedOptions: captured, result: { mode: "full" } });
|
|
99
|
+
|
|
100
|
+
await planRecallModeLLM("summarize the project", undefined, config, llm);
|
|
101
|
+
|
|
102
|
+
assert.equal(captured.length, 1);
|
|
103
|
+
assert.equal(captured[0]?.model, "openai/gpt-5.5");
|
|
104
|
+
assert.equal(captured[0]?.modelChain, undefined);
|
|
105
|
+
assert.equal(captured[0]?.agentId, undefined);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("falls back to heuristic when the LLM throws", async () => {
|
|
109
|
+
const config = parseConfig({ recallPlannerLlmEnabled: true });
|
|
110
|
+
const llm = stubLlm({ throwError: "boom" });
|
|
111
|
+
|
|
112
|
+
const result = await planRecallModeLLM("what happened during the outage?", undefined, config, llm);
|
|
113
|
+
|
|
114
|
+
assert.equal(result.source, "heuristic-fallback");
|
|
115
|
+
assert.equal(result.fallbackUsed, true);
|
|
116
|
+
assert.match(result.reason, /llm-error:boom/);
|
|
117
|
+
// "what happened" → heuristic graph_mode.
|
|
118
|
+
assert.equal(result.mode, "graph_mode");
|
|
119
|
+
assert.equal(result.mode, result.heuristicMode);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("falls back to heuristic when the LLM returns no parseable result", async () => {
|
|
123
|
+
const config = parseConfig({ recallPlannerLlmEnabled: true });
|
|
124
|
+
const llm = stubLlm({ result: null });
|
|
125
|
+
|
|
126
|
+
const result = await planRecallModeLLM("how did we get here?", undefined, config, llm);
|
|
127
|
+
|
|
128
|
+
assert.equal(result.source, "heuristic-fallback");
|
|
129
|
+
assert.equal(result.fallbackUsed, true);
|
|
130
|
+
assert.equal(result.reason, "llm-empty");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("falls back without a network attempt when the chain is empty and the model is bare (default gpt-5.5)", async () => {
|
|
134
|
+
// The legacy default recallPlannerModel "gpt-5.5" is bare (no provider/),
|
|
135
|
+
// which FallbackLlmClient cannot resolve — so with no gateway chain there is
|
|
136
|
+
// nothing routable and the planner must short-circuit to the heuristic
|
|
137
|
+
// (issue #1367 review on PR #1428), not log an invalid-model warning per call.
|
|
138
|
+
const config = parseConfig({ recallPlannerLlmEnabled: true });
|
|
139
|
+
const captured: Array<Record<string, unknown>> = [];
|
|
140
|
+
const llm = stubLlm({ available: false, capturedOptions: captured, result: { mode: "full" } });
|
|
141
|
+
|
|
142
|
+
const result = await planRecallModeLLM("anything", undefined, config, llm);
|
|
143
|
+
|
|
144
|
+
assert.equal(captured.length, 0, "no network attempt when nothing is routable");
|
|
145
|
+
assert.equal(result.source, "heuristic-fallback");
|
|
146
|
+
assert.equal(result.fallbackUsed, true);
|
|
147
|
+
assert.equal(result.reason, "llm-no-model");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("attempts the call (and falls back) when a provider-qualified model override is set even if the chain is empty", async () => {
|
|
151
|
+
// A qualified `provider/model` override is genuinely routable, so we attempt
|
|
152
|
+
// it even when the chain probe reports unavailable, then fall back on a null
|
|
153
|
+
// response.
|
|
154
|
+
const config = parseConfig({ recallPlannerLlmEnabled: true, recallPlannerModel: "openai/gpt-5.5" });
|
|
155
|
+
const captured: Array<Record<string, unknown>> = [];
|
|
156
|
+
const llm = stubLlm({ available: false, capturedOptions: captured, result: null });
|
|
157
|
+
|
|
158
|
+
const result = await planRecallModeLLM("anything", undefined, config, llm);
|
|
159
|
+
|
|
160
|
+
assert.equal(captured.length, 1, "qualified model override → still attempt the call");
|
|
161
|
+
assert.equal(captured[0]?.model, "openai/gpt-5.5");
|
|
162
|
+
assert.equal(result.source, "heuristic-fallback");
|
|
163
|
+
assert.equal(result.reason, "llm-empty");
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("an already-aborted recall short-circuits to the heuristic without an LLM call", async () => {
|
|
167
|
+
const config = parseConfig({ recallPlannerLlmEnabled: true, recallPlannerModel: "openai/gpt-5.5" });
|
|
168
|
+
const captured: Array<Record<string, unknown>> = [];
|
|
169
|
+
const llm = stubLlm({ capturedOptions: captured, result: { mode: "full" } });
|
|
170
|
+
const ac = new AbortController();
|
|
171
|
+
ac.abort();
|
|
172
|
+
|
|
173
|
+
const result = await planRecallModeLLM("what did we decide?", undefined, config, llm, ac.signal);
|
|
174
|
+
|
|
175
|
+
assert.equal(captured.length, 0, "no LLM call when the recall is already aborted");
|
|
176
|
+
assert.equal(result.source, "heuristic-fallback");
|
|
177
|
+
assert.equal(result.reason, "aborted");
|
|
178
|
+
assert.equal(result.fallbackUsed, true);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("forwards the abort signal into the LLM call (cancellation contract)", async () => {
|
|
182
|
+
const config = parseConfig({ recallPlannerLlmEnabled: true, recallPlannerModel: "openai/gpt-5.5" });
|
|
183
|
+
const captured: Array<Record<string, unknown>> = [];
|
|
184
|
+
const llm = stubLlm({ capturedOptions: captured, result: { mode: "minimal" } });
|
|
185
|
+
const ac = new AbortController();
|
|
186
|
+
|
|
187
|
+
await planRecallModeLLM("check status", undefined, config, llm, ac.signal);
|
|
188
|
+
|
|
189
|
+
assert.equal(captured.length, 1);
|
|
190
|
+
assert.equal(captured[0]?.signal, ac.signal, "recall abort signal must reach FallbackLlmClient");
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("empty prompts skip the LLM entirely", async () => {
|
|
194
|
+
const config = parseConfig({ recallPlannerLlmEnabled: true });
|
|
195
|
+
const captured: Array<Record<string, unknown>> = [];
|
|
196
|
+
const llm = stubLlm({ capturedOptions: captured, result: { mode: "full" } });
|
|
197
|
+
|
|
198
|
+
const result = await planRecallModeLLM(" ", undefined, config, llm);
|
|
199
|
+
|
|
200
|
+
assert.equal(captured.length, 0);
|
|
201
|
+
assert.equal(result.mode, "no_recall"); // heuristic returns no_recall for empty
|
|
202
|
+
assert.equal(result.source, "heuristic");
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("resolveRecallPlannerLlmOptions clamps timeout and sets deterministic decoding", () => {
|
|
206
|
+
const config = parseConfig({ recallPlannerLlmEnabled: true, recallPlannerTimeoutMs: 0 });
|
|
207
|
+
const options = resolveRecallPlannerLlmOptions(config);
|
|
208
|
+
assert.equal(options.temperature, 0);
|
|
209
|
+
assert.equal(options.maxTokens, 64);
|
|
210
|
+
assert.equal(options.timeoutMs, 1500, "non-positive timeout falls back to 1500");
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("resolveRecallPlannerLlmOptions drops bare model names but keeps provider-qualified ones", () => {
|
|
214
|
+
// Bare "gpt-5.5" is unresolvable by FallbackLlmClient → dropped (routing falls
|
|
215
|
+
// through to the chain); a qualified value is forwarded as the override.
|
|
216
|
+
const bare = resolveRecallPlannerLlmOptions(
|
|
217
|
+
parseConfig({ recallPlannerLlmEnabled: true, recallPlannerModel: "gpt-5.5" }),
|
|
218
|
+
);
|
|
219
|
+
assert.equal(bare.model, undefined);
|
|
220
|
+
const qualified = resolveRecallPlannerLlmOptions(
|
|
221
|
+
parseConfig({ recallPlannerLlmEnabled: true, recallPlannerModel: "anthropic/claude-haiku-4-5" }),
|
|
222
|
+
);
|
|
223
|
+
assert.equal(qualified.model, "anthropic/claude-haiku-4-5");
|
|
224
|
+
});
|