@remnic/core 1.0.0
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/abstraction-nodes.d.ts +52 -0
- package/dist/abstraction-nodes.js +15 -0
- package/dist/abstraction-nodes.js.map +1 -0
- package/dist/access-cli.d.ts +5 -0
- package/dist/access-cli.js +308 -0
- package/dist/access-cli.js.map +1 -0
- package/dist/access-http.d.ts +158 -0
- package/dist/access-http.js +32 -0
- package/dist/access-http.js.map +1 -0
- package/dist/access-idempotency.d.ts +31 -0
- package/dist/access-idempotency.js +11 -0
- package/dist/access-idempotency.js.map +1 -0
- package/dist/access-mcp.d.ts +76 -0
- package/dist/access-mcp.js +8 -0
- package/dist/access-mcp.js.map +1 -0
- package/dist/access-schema.d.ts +266 -0
- package/dist/access-schema.js +29 -0
- package/dist/access-schema.js.map +1 -0
- package/dist/access-service.d.ts +614 -0
- package/dist/access-service.js +32 -0
- package/dist/access-service.js.map +1 -0
- package/dist/behavior-learner.d.ts +16 -0
- package/dist/behavior-learner.js +124 -0
- package/dist/behavior-learner.js.map +1 -0
- package/dist/behavior-signals.d.ts +15 -0
- package/dist/behavior-signals.js +11 -0
- package/dist/behavior-signals.js.map +1 -0
- package/dist/bootstrap.d.ts +46 -0
- package/dist/bootstrap.js +9 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/boxes.d.ts +93 -0
- package/dist/boxes.js +14 -0
- package/dist/boxes.js.map +1 -0
- package/dist/buffer.d.ts +22 -0
- package/dist/buffer.js +9 -0
- package/dist/buffer.js.map +1 -0
- package/dist/calibration.d.ts +81 -0
- package/dist/calibration.js +239 -0
- package/dist/calibration.js.map +1 -0
- package/dist/causal-behavior.d.ts +79 -0
- package/dist/causal-behavior.js +190 -0
- package/dist/causal-behavior.js.map +1 -0
- package/dist/causal-chain.d.ts +61 -0
- package/dist/causal-chain.js +24 -0
- package/dist/causal-chain.js.map +1 -0
- package/dist/causal-consolidation.d.ts +71 -0
- package/dist/causal-consolidation.js +211 -0
- package/dist/causal-consolidation.js.map +1 -0
- package/dist/causal-retrieval.d.ts +44 -0
- package/dist/causal-retrieval.js +184 -0
- package/dist/causal-retrieval.js.map +1 -0
- package/dist/causal-trajectory-graph.d.ts +13 -0
- package/dist/causal-trajectory-graph.js +59 -0
- package/dist/causal-trajectory-graph.js.map +1 -0
- package/dist/causal-trajectory.d.ts +68 -0
- package/dist/causal-trajectory.js +18 -0
- package/dist/causal-trajectory.js.map +1 -0
- package/dist/chunk-2CJCWDMR.js +87 -0
- package/dist/chunk-2CJCWDMR.js.map +1 -0
- package/dist/chunk-2NMMFZ5T.js +216 -0
- package/dist/chunk-2NMMFZ5T.js.map +1 -0
- package/dist/chunk-2PO5ZRKV.js +103 -0
- package/dist/chunk-2PO5ZRKV.js.map +1 -0
- package/dist/chunk-3QKK7QOS.js +154 -0
- package/dist/chunk-3QKK7QOS.js.map +1 -0
- package/dist/chunk-3SLRNYNG.js +26 -0
- package/dist/chunk-3SLRNYNG.js.map +1 -0
- package/dist/chunk-4A24LIM2.js +68 -0
- package/dist/chunk-4A24LIM2.js.map +1 -0
- package/dist/chunk-6HZ6AO2P.js +164 -0
- package/dist/chunk-6HZ6AO2P.js.map +1 -0
- package/dist/chunk-763GUIOU.js +302 -0
- package/dist/chunk-763GUIOU.js.map +1 -0
- package/dist/chunk-AAI7JARD.js +173 -0
- package/dist/chunk-AAI7JARD.js.map +1 -0
- package/dist/chunk-B7LOFDVE.js +112 -0
- package/dist/chunk-B7LOFDVE.js.map +1 -0
- package/dist/chunk-BDFZXRSO.js +318 -0
- package/dist/chunk-BDFZXRSO.js.map +1 -0
- package/dist/chunk-BOUYNNYD.js +707 -0
- package/dist/chunk-BOUYNNYD.js.map +1 -0
- package/dist/chunk-BRK4ODMI.js +60 -0
- package/dist/chunk-BRK4ODMI.js.map +1 -0
- package/dist/chunk-C6QPK5GG.js +111 -0
- package/dist/chunk-C6QPK5GG.js.map +1 -0
- package/dist/chunk-C7VW7C3F.js +117 -0
- package/dist/chunk-C7VW7C3F.js.map +1 -0
- package/dist/chunk-CDW777AI.js +621 -0
- package/dist/chunk-CDW777AI.js.map +1 -0
- package/dist/chunk-CULXMQJH.js +185 -0
- package/dist/chunk-CULXMQJH.js.map +1 -0
- package/dist/chunk-CXWFUJR2.js +1203 -0
- package/dist/chunk-CXWFUJR2.js.map +1 -0
- package/dist/chunk-DGXUHMOV.js +61 -0
- package/dist/chunk-DGXUHMOV.js.map +1 -0
- package/dist/chunk-DM2T26WE.js +61 -0
- package/dist/chunk-DM2T26WE.js.map +1 -0
- package/dist/chunk-DORBM6OB.js +81 -0
- package/dist/chunk-DORBM6OB.js.map +1 -0
- package/dist/chunk-DT5TVLJE.js +32 -0
- package/dist/chunk-DT5TVLJE.js.map +1 -0
- package/dist/chunk-EEQLFRUM.js +89 -0
- package/dist/chunk-EEQLFRUM.js.map +1 -0
- package/dist/chunk-EQINRHYR.js +672 -0
- package/dist/chunk-EQINRHYR.js.map +1 -0
- package/dist/chunk-ESSMF2FR.js +146 -0
- package/dist/chunk-ESSMF2FR.js.map +1 -0
- package/dist/chunk-ETOW6ACV.js +158 -0
- package/dist/chunk-ETOW6ACV.js.map +1 -0
- package/dist/chunk-FYIYMQ5N.js +221 -0
- package/dist/chunk-FYIYMQ5N.js.map +1 -0
- package/dist/chunk-G3AG3KZN.js +78 -0
- package/dist/chunk-G3AG3KZN.js.map +1 -0
- package/dist/chunk-GJR6D6KC.js +61 -0
- package/dist/chunk-GJR6D6KC.js.map +1 -0
- package/dist/chunk-GPGBSNKM.js +380 -0
- package/dist/chunk-GPGBSNKM.js.map +1 -0
- package/dist/chunk-H63EDPFJ.js +57 -0
- package/dist/chunk-H63EDPFJ.js.map +1 -0
- package/dist/chunk-HG2NKWR2.js +185 -0
- package/dist/chunk-HG2NKWR2.js.map +1 -0
- package/dist/chunk-HL4DB7TO.js +13 -0
- package/dist/chunk-HL4DB7TO.js.map +1 -0
- package/dist/chunk-HLBYLYRD.js +346 -0
- package/dist/chunk-HLBYLYRD.js.map +1 -0
- package/dist/chunk-HLXVTBF3.js +109 -0
- package/dist/chunk-HLXVTBF3.js.map +1 -0
- package/dist/chunk-IFFFR3MR.js +68 -0
- package/dist/chunk-IFFFR3MR.js.map +1 -0
- package/dist/chunk-ISY75RLM.js +1027 -0
- package/dist/chunk-ISY75RLM.js.map +1 -0
- package/dist/chunk-IZME7KW2.js +1886 -0
- package/dist/chunk-IZME7KW2.js.map +1 -0
- package/dist/chunk-J3BT33K7.js +720 -0
- package/dist/chunk-J3BT33K7.js.map +1 -0
- package/dist/chunk-J47FNDR7.js +113 -0
- package/dist/chunk-J47FNDR7.js.map +1 -0
- package/dist/chunk-JWPLJLDU.js +63 -0
- package/dist/chunk-JWPLJLDU.js.map +1 -0
- package/dist/chunk-K6WK37A6.js +865 -0
- package/dist/chunk-K6WK37A6.js.map +1 -0
- package/dist/chunk-KL4CP4SB.js +130 -0
- package/dist/chunk-KL4CP4SB.js.map +1 -0
- package/dist/chunk-KT4NEUNF.js +315 -0
- package/dist/chunk-KT4NEUNF.js.map +1 -0
- package/dist/chunk-KWBU5S5U.js +42 -0
- package/dist/chunk-KWBU5S5U.js.map +1 -0
- package/dist/chunk-L5RPWGFK.js +59 -0
- package/dist/chunk-L5RPWGFK.js.map +1 -0
- package/dist/chunk-L7WO3MZ4.js +128 -0
- package/dist/chunk-L7WO3MZ4.js.map +1 -0
- package/dist/chunk-LIRZNNUP.js +74 -0
- package/dist/chunk-LIRZNNUP.js.map +1 -0
- package/dist/chunk-LK6SGL53.js +22 -0
- package/dist/chunk-LK6SGL53.js.map +1 -0
- package/dist/chunk-LOBRX7VD.js +200 -0
- package/dist/chunk-LOBRX7VD.js.map +1 -0
- package/dist/chunk-LPSF4OQH.js +47 -0
- package/dist/chunk-LPSF4OQH.js.map +1 -0
- package/dist/chunk-LU3GQNDQ.js +152 -0
- package/dist/chunk-LU3GQNDQ.js.map +1 -0
- package/dist/chunk-M5KEYE5E.js +350 -0
- package/dist/chunk-M5KEYE5E.js.map +1 -0
- package/dist/chunk-M62O4P4T.js +41 -0
- package/dist/chunk-M62O4P4T.js.map +1 -0
- package/dist/chunk-MARWOCVP.js +48 -0
- package/dist/chunk-MARWOCVP.js.map +1 -0
- package/dist/chunk-MDDAA2AO.js +925 -0
- package/dist/chunk-MDDAA2AO.js.map +1 -0
- package/dist/chunk-MWGVGUIS.js +198 -0
- package/dist/chunk-MWGVGUIS.js.map +1 -0
- package/dist/chunk-N5AKDXAI.js +74 -0
- package/dist/chunk-N5AKDXAI.js.map +1 -0
- package/dist/chunk-NGAVDO7E.js +115 -0
- package/dist/chunk-NGAVDO7E.js.map +1 -0
- package/dist/chunk-NTTLPF7F.js +283 -0
- package/dist/chunk-NTTLPF7F.js.map +1 -0
- package/dist/chunk-ONRU4L2N.js +240 -0
- package/dist/chunk-ONRU4L2N.js.map +1 -0
- package/dist/chunk-ORZMT74A.js +209 -0
- package/dist/chunk-ORZMT74A.js.map +1 -0
- package/dist/chunk-OTAVQCSF.js +268 -0
- package/dist/chunk-OTAVQCSF.js.map +1 -0
- package/dist/chunk-PGK3VUHN.js +160 -0
- package/dist/chunk-PGK3VUHN.js.map +1 -0
- package/dist/chunk-Q6FETXJA.js +1362 -0
- package/dist/chunk-Q6FETXJA.js.map +1 -0
- package/dist/chunk-QANCTXQF.js +271 -0
- package/dist/chunk-QANCTXQF.js.map +1 -0
- package/dist/chunk-QCCCQT3O.js +189 -0
- package/dist/chunk-QCCCQT3O.js.map +1 -0
- package/dist/chunk-QDOSNLB4.js +1048 -0
- package/dist/chunk-QDOSNLB4.js.map +1 -0
- package/dist/chunk-QFQVZOGA.js +2168 -0
- package/dist/chunk-QFQVZOGA.js.map +1 -0
- package/dist/chunk-QPKFPHOO.js +178 -0
- package/dist/chunk-QPKFPHOO.js.map +1 -0
- package/dist/chunk-QSVPYQPG.js +268 -0
- package/dist/chunk-QSVPYQPG.js.map +1 -0
- package/dist/chunk-QWUUMMIK.js +3045 -0
- package/dist/chunk-QWUUMMIK.js.map +1 -0
- package/dist/chunk-QY2BHY5O.js +2378 -0
- package/dist/chunk-QY2BHY5O.js.map +1 -0
- package/dist/chunk-SCHEKPYH.js +349 -0
- package/dist/chunk-SCHEKPYH.js.map +1 -0
- package/dist/chunk-SCU65EZI.js +15 -0
- package/dist/chunk-SCU65EZI.js.map +1 -0
- package/dist/chunk-T4WRIV2C.js +170 -0
- package/dist/chunk-T4WRIV2C.js.map +1 -0
- package/dist/chunk-TKO4HZCK.js +1852 -0
- package/dist/chunk-TKO4HZCK.js.map +1 -0
- package/dist/chunk-TP4FZJIZ.js +93 -0
- package/dist/chunk-TP4FZJIZ.js.map +1 -0
- package/dist/chunk-TPB3I2AC.js +403 -0
- package/dist/chunk-TPB3I2AC.js.map +1 -0
- package/dist/chunk-TVVVQQAK.js +1431 -0
- package/dist/chunk-TVVVQQAK.js.map +1 -0
- package/dist/chunk-U4PV25RD.js +14 -0
- package/dist/chunk-U4PV25RD.js.map +1 -0
- package/dist/chunk-UCYSTFZR.js +284 -0
- package/dist/chunk-UCYSTFZR.js.map +1 -0
- package/dist/chunk-UHGBNIOS.js +205 -0
- package/dist/chunk-UHGBNIOS.js.map +1 -0
- package/dist/chunk-UIYZ5T3I.js +108 -0
- package/dist/chunk-UIYZ5T3I.js.map +1 -0
- package/dist/chunk-UV2FO7J4.js +747 -0
- package/dist/chunk-UV2FO7J4.js.map +1 -0
- package/dist/chunk-UZB5KHKX.js +63 -0
- package/dist/chunk-UZB5KHKX.js.map +1 -0
- package/dist/chunk-V3RXWQIE.js +626 -0
- package/dist/chunk-V3RXWQIE.js.map +1 -0
- package/dist/chunk-V4YC4LUK.js +444 -0
- package/dist/chunk-V4YC4LUK.js.map +1 -0
- package/dist/chunk-VEWZZM3H.js +133 -0
- package/dist/chunk-VEWZZM3H.js.map +1 -0
- package/dist/chunk-WWIQTB2Y.js +98 -0
- package/dist/chunk-WWIQTB2Y.js.map +1 -0
- package/dist/chunk-X7XN6YU4.js +24 -0
- package/dist/chunk-X7XN6YU4.js.map +1 -0
- package/dist/chunk-XKECPATV.js +202 -0
- package/dist/chunk-XKECPATV.js.map +1 -0
- package/dist/chunk-XYIK4LF6.js +75 -0
- package/dist/chunk-XYIK4LF6.js.map +1 -0
- package/dist/chunk-Y27UJK6V.js +39 -0
- package/dist/chunk-Y27UJK6V.js.map +1 -0
- package/dist/chunk-Y4Z4I6WK.js +9 -0
- package/dist/chunk-Y4Z4I6WK.js.map +1 -0
- package/dist/chunk-YAPUAHAY.js +10761 -0
- package/dist/chunk-YAPUAHAY.js.map +1 -0
- package/dist/chunk-YAZNBMNF.js +92 -0
- package/dist/chunk-YAZNBMNF.js.map +1 -0
- package/dist/chunk-YCN4BVDK.js +66 -0
- package/dist/chunk-YCN4BVDK.js.map +1 -0
- package/dist/chunk-YNCQ7E4M.js +388 -0
- package/dist/chunk-YNCQ7E4M.js.map +1 -0
- package/dist/chunk-YNI4S5WT.js +143 -0
- package/dist/chunk-YNI4S5WT.js.map +1 -0
- package/dist/chunk-YRMVARQP.js +406 -0
- package/dist/chunk-YRMVARQP.js.map +1 -0
- package/dist/chunk-Z5AAYHUC.js +79 -0
- package/dist/chunk-Z5AAYHUC.js.map +1 -0
- package/dist/chunk-Z5LAYHGJ.js +15 -0
- package/dist/chunk-Z5LAYHGJ.js.map +1 -0
- package/dist/chunk-ZJLY4QSU.js +823 -0
- package/dist/chunk-ZJLY4QSU.js.map +1 -0
- package/dist/chunk-ZKYI7UVO.js +276 -0
- package/dist/chunk-ZKYI7UVO.js.map +1 -0
- package/dist/chunk-ZPKBYX2F.js +297 -0
- package/dist/chunk-ZPKBYX2F.js.map +1 -0
- package/dist/chunking.d.ts +48 -0
- package/dist/chunking.js +11 -0
- package/dist/chunking.js.map +1 -0
- package/dist/cli.d.ts +1162 -0
- package/dist/cli.js +7187 -0
- package/dist/cli.js.map +1 -0
- package/dist/commitment-ledger.d.ts +83 -0
- package/dist/commitment-ledger.js +19 -0
- package/dist/commitment-ledger.js.map +1 -0
- package/dist/compression-optimizer.d.ts +37 -0
- package/dist/compression-optimizer.js +13 -0
- package/dist/compression-optimizer.js.map +1 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.js +12 -0
- package/dist/config.js.map +1 -0
- package/dist/cue-anchors.d.ts +50 -0
- package/dist/cue-anchors.js +15 -0
- package/dist/cue-anchors.js.map +1 -0
- package/dist/dashboard-runtime.d.ts +46 -0
- package/dist/dashboard-runtime.js +10 -0
- package/dist/dashboard-runtime.js.map +1 -0
- package/dist/day-summary.d.ts +6 -0
- package/dist/day-summary.js +10 -0
- package/dist/day-summary.js.map +1 -0
- package/dist/delinearize.d.ts +34 -0
- package/dist/delinearize.js +11 -0
- package/dist/delinearize.js.map +1 -0
- package/dist/embedding-fallback.d.ts +22 -0
- package/dist/embedding-fallback.js +8 -0
- package/dist/embedding-fallback.js.map +1 -0
- package/dist/engine-P26JFSVY.js +19 -0
- package/dist/engine-P26JFSVY.js.map +1 -0
- package/dist/entity-retrieval.d.ts +23 -0
- package/dist/entity-retrieval.js +24 -0
- package/dist/entity-retrieval.js.map +1 -0
- package/dist/evals.d.ts +282 -0
- package/dist/evals.js +32 -0
- package/dist/evals.js.map +1 -0
- package/dist/explicit-capture.d.ts +60 -0
- package/dist/explicit-capture.js +23 -0
- package/dist/explicit-capture.js.map +1 -0
- package/dist/extraction.d.ts +141 -0
- package/dist/extraction.js +22 -0
- package/dist/extraction.js.map +1 -0
- package/dist/fallback-llm.d.ts +95 -0
- package/dist/fallback-llm.js +12 -0
- package/dist/fallback-llm.js.map +1 -0
- package/dist/graph-dashboard-diff.d.ts +12 -0
- package/dist/graph-dashboard-diff.js +8 -0
- package/dist/graph-dashboard-diff.js.map +1 -0
- package/dist/graph-dashboard-key.d.ts +5 -0
- package/dist/graph-dashboard-key.js +7 -0
- package/dist/graph-dashboard-key.js.map +1 -0
- package/dist/graph-dashboard-parser.d.ts +20 -0
- package/dist/graph-dashboard-parser.js +8 -0
- package/dist/graph-dashboard-parser.js.map +1 -0
- package/dist/graph.d.ts +157 -0
- package/dist/graph.js +27 -0
- package/dist/graph.js.map +1 -0
- package/dist/harmonic-retrieval.d.ts +27 -0
- package/dist/harmonic-retrieval.js +12 -0
- package/dist/harmonic-retrieval.js.map +1 -0
- package/dist/himem.d.ts +23 -0
- package/dist/himem.js +7 -0
- package/dist/himem.js.map +1 -0
- package/dist/hygiene.d.ts +24 -0
- package/dist/hygiene.js +9 -0
- package/dist/hygiene.js.map +1 -0
- package/dist/identity-continuity.d.ts +17 -0
- package/dist/identity-continuity.js +19 -0
- package/dist/identity-continuity.js.map +1 -0
- package/dist/importance.d.ts +25 -0
- package/dist/importance.js +11 -0
- package/dist/importance.js.map +1 -0
- package/dist/index.d.ts +923 -0
- package/dist/index.js +2512 -0
- package/dist/index.js.map +1 -0
- package/dist/intent.d.ts +8 -0
- package/dist/intent.js +13 -0
- package/dist/intent.js.map +1 -0
- package/dist/json-extract.d.ts +14 -0
- package/dist/json-extract.js +9 -0
- package/dist/json-extract.js.map +1 -0
- package/dist/json-store.d.ts +5 -0
- package/dist/json-store.js +11 -0
- package/dist/json-store.js.map +1 -0
- package/dist/legacy-hook-compat.d.ts +3 -0
- package/dist/legacy-hook-compat.js +35 -0
- package/dist/legacy-hook-compat.js.map +1 -0
- package/dist/lifecycle.d.ts +52 -0
- package/dist/lifecycle.js +21 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/local-llm.d.ts +154 -0
- package/dist/local-llm.js +10 -0
- package/dist/local-llm.js.map +1 -0
- package/dist/logger.d.ts +15 -0
- package/dist/logger.js +9 -0
- package/dist/logger.js.map +1 -0
- package/dist/memory-action-policy.d.ts +13 -0
- package/dist/memory-action-policy.js +7 -0
- package/dist/memory-action-policy.js.map +1 -0
- package/dist/memory-cache.d.ts +35 -0
- package/dist/memory-cache.js +37 -0
- package/dist/memory-cache.js.map +1 -0
- package/dist/memory-lifecycle-ledger-utils.d.ts +13 -0
- package/dist/memory-lifecycle-ledger-utils.js +23 -0
- package/dist/memory-lifecycle-ledger-utils.js.map +1 -0
- package/dist/memory-projection-format.d.ts +4 -0
- package/dist/memory-projection-format.js +9 -0
- package/dist/memory-projection-format.js.map +1 -0
- package/dist/memory-projection-store-NxMkbocT.d.ts +221 -0
- package/dist/memory-projection-store.d.ts +3 -0
- package/dist/memory-projection-store.js +31 -0
- package/dist/memory-projection-store.js.map +1 -0
- package/dist/model-registry.d.ts +60 -0
- package/dist/model-registry.js +8 -0
- package/dist/model-registry.js.map +1 -0
- package/dist/native-knowledge.d.ts +94 -0
- package/dist/native-knowledge.js +26 -0
- package/dist/native-knowledge.js.map +1 -0
- package/dist/negative.d.ts +26 -0
- package/dist/negative.js +8 -0
- package/dist/negative.js.map +1 -0
- package/dist/objective-state-writers.d.ts +22 -0
- package/dist/objective-state-writers.js +313 -0
- package/dist/objective-state-writers.js.map +1 -0
- package/dist/objective-state.d.ts +75 -0
- package/dist/objective-state.js +17 -0
- package/dist/objective-state.js.map +1 -0
- package/dist/openai-chat-compat.d.ts +13 -0
- package/dist/openai-chat-compat.js +11 -0
- package/dist/openai-chat-compat.js.map +1 -0
- package/dist/operator-toolkit.d.ts +304 -0
- package/dist/operator-toolkit.js +41 -0
- package/dist/operator-toolkit.js.map +1 -0
- package/dist/opik-exporter.d.ts +72 -0
- package/dist/opik-exporter.js +361 -0
- package/dist/opik-exporter.js.map +1 -0
- package/dist/orchestrator-zTa-Qo-1.d.ts +1104 -0
- package/dist/orchestrator.d.ts +21 -0
- package/dist/orchestrator.js +145 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/policy-runtime.d.ts +37 -0
- package/dist/policy-runtime.js +13 -0
- package/dist/policy-runtime.js.map +1 -0
- package/dist/port-C1GZFv8h.d.ts +41 -0
- package/dist/profiling.d.ts +80 -0
- package/dist/profiling.js +10 -0
- package/dist/profiling.js.map +1 -0
- package/dist/qmd-recall-cache.d.ts +29 -0
- package/dist/qmd-recall-cache.js +13 -0
- package/dist/qmd-recall-cache.js.map +1 -0
- package/dist/qmd.d.ts +105 -0
- package/dist/qmd.js +13 -0
- package/dist/qmd.js.map +1 -0
- package/dist/recall-qos.d.ts +33 -0
- package/dist/recall-qos.js +10 -0
- package/dist/recall-qos.js.map +1 -0
- package/dist/recall-query-policy.d.ts +20 -0
- package/dist/recall-query-policy.js +11 -0
- package/dist/recall-query-policy.js.map +1 -0
- package/dist/recall-state.d.ts +113 -0
- package/dist/recall-state.js +12 -0
- package/dist/recall-state.js.map +1 -0
- package/dist/recall-tokenization.d.ts +4 -0
- package/dist/recall-tokenization.js +9 -0
- package/dist/recall-tokenization.js.map +1 -0
- package/dist/reconstruct.d.ts +16 -0
- package/dist/reconstruct.js +7 -0
- package/dist/reconstruct.js.map +1 -0
- package/dist/release-changelog.d.ts +7 -0
- package/dist/release-changelog.js +30 -0
- package/dist/release-changelog.js.map +1 -0
- package/dist/relevance.d.ts +18 -0
- package/dist/relevance.js +8 -0
- package/dist/relevance.js.map +1 -0
- package/dist/rerank.d.ts +57 -0
- package/dist/rerank.js +11 -0
- package/dist/rerank.js.map +1 -0
- package/dist/resolve-provider-secret.d.ts +16 -0
- package/dist/resolve-provider-secret.js +11 -0
- package/dist/resolve-provider-secret.js.map +1 -0
- package/dist/resume-bundles.d.ts +66 -0
- package/dist/resume-bundles.js +27 -0
- package/dist/resume-bundles.js.map +1 -0
- package/dist/retrieval-agents.d.ts +129 -0
- package/dist/retrieval-agents.js +23 -0
- package/dist/retrieval-agents.js.map +1 -0
- package/dist/retrieval.d.ts +19 -0
- package/dist/retrieval.js +10 -0
- package/dist/retrieval.js.map +1 -0
- package/dist/sanitize.d.ts +9 -0
- package/dist/sanitize.js +9 -0
- package/dist/sanitize.js.map +1 -0
- package/dist/schemas.d.ts +688 -0
- package/dist/schemas.js +51 -0
- package/dist/schemas.js.map +1 -0
- package/dist/sdk-compat.d.ts +21 -0
- package/dist/sdk-compat.js +28 -0
- package/dist/sdk-compat.js.map +1 -0
- package/dist/semantic-consolidation.d.ts +42 -0
- package/dist/semantic-consolidation.js +12 -0
- package/dist/semantic-consolidation.js.map +1 -0
- package/dist/semantic-rule-promotion.d.ts +28 -0
- package/dist/semantic-rule-promotion.js +17 -0
- package/dist/semantic-rule-promotion.js.map +1 -0
- package/dist/semantic-rule-verifier.d.ts +19 -0
- package/dist/semantic-rule-verifier.js +18 -0
- package/dist/semantic-rule-verifier.js.map +1 -0
- package/dist/session-integrity.d.ts +67 -0
- package/dist/session-integrity.js +11 -0
- package/dist/session-integrity.js.map +1 -0
- package/dist/session-observer-bands.d.ts +6 -0
- package/dist/session-observer-bands.js +9 -0
- package/dist/session-observer-bands.js.map +1 -0
- package/dist/session-observer-state.d.ts +40 -0
- package/dist/session-observer-state.js +11 -0
- package/dist/session-observer-state.js.map +1 -0
- package/dist/signal.d.ts +6 -0
- package/dist/signal.js +9 -0
- package/dist/signal.js.map +1 -0
- package/dist/storage.d.ts +453 -0
- package/dist/storage.js +24 -0
- package/dist/storage.js.map +1 -0
- package/dist/store-contract.d.ts +10 -0
- package/dist/store-contract.js +21 -0
- package/dist/store-contract.js.map +1 -0
- package/dist/summarizer.d.ts +35 -0
- package/dist/summarizer.js +17 -0
- package/dist/summarizer.js.map +1 -0
- package/dist/summary-snapshot.d.ts +8 -0
- package/dist/summary-snapshot.js +13 -0
- package/dist/summary-snapshot.js.map +1 -0
- package/dist/temporal-index.d.ts +139 -0
- package/dist/temporal-index.js +29 -0
- package/dist/temporal-index.js.map +1 -0
- package/dist/threading.d.ts +62 -0
- package/dist/threading.js +8 -0
- package/dist/threading.js.map +1 -0
- package/dist/tier-migration.d.ts +44 -0
- package/dist/tier-migration.js +7 -0
- package/dist/tier-migration.js.map +1 -0
- package/dist/tier-routing.d.ts +21 -0
- package/dist/tier-routing.js +10 -0
- package/dist/tier-routing.js.map +1 -0
- package/dist/tmt.d.ts +79 -0
- package/dist/tmt.js +29 -0
- package/dist/tmt.js.map +1 -0
- package/dist/tokens.d.ts +24 -0
- package/dist/tokens.js +21 -0
- package/dist/tokens.js.map +1 -0
- package/dist/topics.d.ts +29 -0
- package/dist/topics.js +9 -0
- package/dist/topics.js.map +1 -0
- package/dist/transcript.d.ts +171 -0
- package/dist/transcript.js +9 -0
- package/dist/transcript.js.map +1 -0
- package/dist/trust-zones.d.ts +170 -0
- package/dist/trust-zones.js +32 -0
- package/dist/trust-zones.js.map +1 -0
- package/dist/types.d.ts +1243 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/utility-learner.d.ts +59 -0
- package/dist/utility-learner.js +17 -0
- package/dist/utility-learner.js.map +1 -0
- package/dist/utility-runtime.d.ts +21 -0
- package/dist/utility-runtime.js +16 -0
- package/dist/utility-runtime.js.map +1 -0
- package/dist/utility-telemetry.d.ts +68 -0
- package/dist/utility-telemetry.js +17 -0
- package/dist/utility-telemetry.js.map +1 -0
- package/dist/verified-recall.d.ts +17 -0
- package/dist/verified-recall.js +19 -0
- package/dist/verified-recall.js.map +1 -0
- package/dist/version-utils.d.ts +4 -0
- package/dist/version-utils.js +7 -0
- package/dist/version-utils.js.map +1 -0
- package/dist/work-product-ledger.d.ts +65 -0
- package/dist/work-product-ledger.js +18 -0
- package/dist/work-product-ledger.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,1431 @@
|
|
|
1
|
+
import {
|
|
2
|
+
launchProcess
|
|
3
|
+
} from "./chunk-LK6SGL53.js";
|
|
4
|
+
import {
|
|
5
|
+
mergeEnv
|
|
6
|
+
} from "./chunk-MARWOCVP.js";
|
|
7
|
+
import {
|
|
8
|
+
getCachedQmdSearch,
|
|
9
|
+
setCachedQmdSearch
|
|
10
|
+
} from "./chunk-ESSMF2FR.js";
|
|
11
|
+
import {
|
|
12
|
+
log
|
|
13
|
+
} from "./chunk-KWBU5S5U.js";
|
|
14
|
+
|
|
15
|
+
// src/qmd.ts
|
|
16
|
+
import { createHash } from "crypto";
|
|
17
|
+
import os from "os";
|
|
18
|
+
import path from "path";
|
|
19
|
+
var QMD_TIMEOUT_MS = 3e4;
|
|
20
|
+
var QMD_DAEMON_TIMEOUT_MS = 8e3;
|
|
21
|
+
var QMD_PROBE_TIMEOUT_MS = 8e3;
|
|
22
|
+
var QMD_UPDATE_BACKOFF_MS = 15 * 60 * 1e3;
|
|
23
|
+
var QMD_EMBED_BACKOFF_MS = 60 * 60 * 1e3;
|
|
24
|
+
var QMD_CLI_WARN_THROTTLE_MS = 15 * 60 * 1e3;
|
|
25
|
+
var QMD_FALLBACK_PATHS = [
|
|
26
|
+
path.join(os.homedir(), ".bun", "bin", "qmd"),
|
|
27
|
+
"/usr/local/bin/qmd",
|
|
28
|
+
"/opt/homebrew/bin/qmd"
|
|
29
|
+
];
|
|
30
|
+
var QMD_GLOBAL_STATE_KEY = "__openclawEngramQmdGlobalState";
|
|
31
|
+
function getGlobalQmdState() {
|
|
32
|
+
const g = globalThis;
|
|
33
|
+
if (!g[QMD_GLOBAL_STATE_KEY]) {
|
|
34
|
+
g[QMD_GLOBAL_STATE_KEY] = {
|
|
35
|
+
warnedGlobalUpdateBehavior: false,
|
|
36
|
+
lastGlobalUpdateRunAtMs: null,
|
|
37
|
+
lastGlobalUpdateFailAtMs: null,
|
|
38
|
+
lastGlobalEmbedRunAtMs: null,
|
|
39
|
+
lastGlobalEmbedFailAtMs: null,
|
|
40
|
+
lastCliWarnAtMs: null,
|
|
41
|
+
lastUpdateByCollectionMs: {},
|
|
42
|
+
lastUpdateFailByCollectionMs: {},
|
|
43
|
+
lastEmbedByCollectionMs: {},
|
|
44
|
+
lastEmbedFailByCollectionMs: {}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return g[QMD_GLOBAL_STATE_KEY];
|
|
48
|
+
}
|
|
49
|
+
function abortError(message) {
|
|
50
|
+
const err = new Error(message);
|
|
51
|
+
Object.defineProperty(err, "name", { value: "AbortError" });
|
|
52
|
+
return err;
|
|
53
|
+
}
|
|
54
|
+
function isAbortError(err) {
|
|
55
|
+
return err instanceof Error && err.name === "AbortError";
|
|
56
|
+
}
|
|
57
|
+
function errorMessage(err) {
|
|
58
|
+
if (typeof err === "string") return err;
|
|
59
|
+
if (err instanceof Error) return err.message;
|
|
60
|
+
if (err && typeof err === "object" && "message" in err && typeof err.message === "string") {
|
|
61
|
+
return err.message;
|
|
62
|
+
}
|
|
63
|
+
return String(err);
|
|
64
|
+
}
|
|
65
|
+
function isCallerCancellation(err, signal) {
|
|
66
|
+
if (signal?.aborted) return true;
|
|
67
|
+
if (isAbortError(err)) return true;
|
|
68
|
+
if (err && typeof err === "object") {
|
|
69
|
+
const code = "code" in err ? err.code : void 0;
|
|
70
|
+
if (code === "ABORT_ERR" || code === "ERR_CANCELED") return true;
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
function isDaemonTimeoutError(err) {
|
|
75
|
+
return /timed out/i.test(errorMessage(err));
|
|
76
|
+
}
|
|
77
|
+
function throwIfAborted(signal, message = "operation aborted") {
|
|
78
|
+
if (signal?.aborted) {
|
|
79
|
+
throw abortError(message);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function sleepWithSignal(ms, signal) {
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
throwIfAborted(signal);
|
|
85
|
+
const timer = setTimeout(() => {
|
|
86
|
+
cleanup();
|
|
87
|
+
resolve();
|
|
88
|
+
}, ms);
|
|
89
|
+
const onAbort = () => {
|
|
90
|
+
clearTimeout(timer);
|
|
91
|
+
cleanup();
|
|
92
|
+
reject(abortError("operation aborted while waiting"));
|
|
93
|
+
};
|
|
94
|
+
const cleanup = () => {
|
|
95
|
+
signal?.removeEventListener("abort", onAbort);
|
|
96
|
+
};
|
|
97
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function isSqliteBusyError(msg) {
|
|
101
|
+
const lower = msg.toLowerCase();
|
|
102
|
+
return lower.includes("database is locked") || lower.includes("sqlite_busy") || lower.includes("sqlite_busy_recovery") || lower.includes("sqliterror: database is locked");
|
|
103
|
+
}
|
|
104
|
+
function stripControlChars(s) {
|
|
105
|
+
return s.replace(/\x1b\[[0-9;]*[A-Za-z]/g, "").replace(/[\u0000-\u001f\u007f]/g, "");
|
|
106
|
+
}
|
|
107
|
+
function truncateForLog(s, max = 2e3) {
|
|
108
|
+
const cleaned = stripControlChars(s);
|
|
109
|
+
return cleaned.length > max ? cleaned.slice(0, max) + "\u2026(truncated)" : cleaned;
|
|
110
|
+
}
|
|
111
|
+
function isVectorDimensionMismatchError(err) {
|
|
112
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
113
|
+
return /dimension mismatch/i.test(msg) || /vectors?_vec/i.test(msg) && /float\[\d+\]/i.test(msg) || /embedding/i.test(msg) && /dimensions?/i.test(msg);
|
|
114
|
+
}
|
|
115
|
+
function parseQmdVersion(version) {
|
|
116
|
+
if (!version) return null;
|
|
117
|
+
const match = version.match(/v?(\d{1,10})\.(\d{1,10})\.(\d{1,10})/i);
|
|
118
|
+
if (!match) return null;
|
|
119
|
+
return [
|
|
120
|
+
Number.parseInt(match[1] ?? "0", 10),
|
|
121
|
+
Number.parseInt(match[2] ?? "0", 10),
|
|
122
|
+
Number.parseInt(match[3] ?? "0", 10)
|
|
123
|
+
];
|
|
124
|
+
}
|
|
125
|
+
function versionAtLeast(current, target) {
|
|
126
|
+
if (!current) return false;
|
|
127
|
+
for (let i = 0; i < 3; i += 1) {
|
|
128
|
+
if ((current[i] ?? 0) > target[i]) return true;
|
|
129
|
+
if ((current[i] ?? 0) < target[i]) return false;
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
function normalizeSearchOptions(options) {
|
|
134
|
+
if (!options) return void 0;
|
|
135
|
+
const intent = typeof options.intent === "string" ? options.intent.trim() : "";
|
|
136
|
+
const normalized = {};
|
|
137
|
+
if (intent.length > 0) {
|
|
138
|
+
normalized.intent = intent;
|
|
139
|
+
}
|
|
140
|
+
if (options.explain === true) {
|
|
141
|
+
normalized.explain = true;
|
|
142
|
+
}
|
|
143
|
+
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
144
|
+
}
|
|
145
|
+
function parseExplainScores(value) {
|
|
146
|
+
if (!Array.isArray(value)) return void 0;
|
|
147
|
+
const scores = value.filter((entry) => typeof entry === "number");
|
|
148
|
+
return scores.length > 0 ? scores : void 0;
|
|
149
|
+
}
|
|
150
|
+
function parseQmdExplain(value) {
|
|
151
|
+
if (!value || typeof value !== "object") return void 0;
|
|
152
|
+
const candidate = value;
|
|
153
|
+
const parsed = {
|
|
154
|
+
ftsScores: parseExplainScores(candidate.ftsScores),
|
|
155
|
+
vectorScores: parseExplainScores(candidate.vectorScores),
|
|
156
|
+
rrf: typeof candidate.rrf === "number" ? candidate.rrf : void 0,
|
|
157
|
+
rerankScore: typeof candidate.rerankScore === "number" ? candidate.rerankScore : void 0,
|
|
158
|
+
blendedScore: typeof candidate.blendedScore === "number" ? candidate.blendedScore : void 0
|
|
159
|
+
};
|
|
160
|
+
return Object.values(parsed).some((entry) => entry !== void 0) ? parsed : void 0;
|
|
161
|
+
}
|
|
162
|
+
var AsyncMutex = class {
|
|
163
|
+
locked = false;
|
|
164
|
+
queue = [];
|
|
165
|
+
async runExclusive(fn, signal) {
|
|
166
|
+
const release = await this.acquire(signal);
|
|
167
|
+
try {
|
|
168
|
+
throwIfAborted(signal);
|
|
169
|
+
return await fn();
|
|
170
|
+
} finally {
|
|
171
|
+
release();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
acquire(signal) {
|
|
175
|
+
throwIfAborted(signal);
|
|
176
|
+
if (!this.locked) {
|
|
177
|
+
this.locked = true;
|
|
178
|
+
return Promise.resolve(() => this.release());
|
|
179
|
+
}
|
|
180
|
+
return new Promise((resolve, reject) => {
|
|
181
|
+
const waiter = {
|
|
182
|
+
resolve: (release) => {
|
|
183
|
+
signal?.removeEventListener("abort", waiter.onAbort);
|
|
184
|
+
resolve(release);
|
|
185
|
+
},
|
|
186
|
+
reject: (reason) => {
|
|
187
|
+
signal?.removeEventListener("abort", waiter.onAbort);
|
|
188
|
+
reject(reason);
|
|
189
|
+
},
|
|
190
|
+
signal,
|
|
191
|
+
onAbort: () => {
|
|
192
|
+
this.queue = this.queue.filter((entry) => entry !== waiter);
|
|
193
|
+
reject(abortError("operation aborted while waiting for qmd mutex"));
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
signal?.addEventListener("abort", waiter.onAbort, { once: true });
|
|
197
|
+
this.queue.push(waiter);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
release() {
|
|
201
|
+
while (this.queue.length > 0) {
|
|
202
|
+
const next = this.queue.shift();
|
|
203
|
+
if (!next) break;
|
|
204
|
+
if (next.signal?.aborted) {
|
|
205
|
+
next.reject(abortError("operation aborted while waiting for qmd mutex"));
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
this.locked = true;
|
|
209
|
+
next.resolve(() => this.release());
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
this.locked = false;
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
var QMD_MUTEX = new AsyncMutex();
|
|
216
|
+
function runQmd(args, timeoutMs = QMD_TIMEOUT_MS, qmdPath = "qmd", signal) {
|
|
217
|
+
return QMD_MUTEX.runExclusive(async () => {
|
|
218
|
+
throwIfAborted(signal, `qmd ${args.join(" ")} aborted before start`);
|
|
219
|
+
const maxAttempts = isLikelyWriteCommand(args) ? 3 : 1;
|
|
220
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
221
|
+
try {
|
|
222
|
+
return await runQmdOnce(args, timeoutMs, qmdPath, signal);
|
|
223
|
+
} catch (err) {
|
|
224
|
+
if (isAbortError(err)) throw err;
|
|
225
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
226
|
+
if (attempt < maxAttempts && isSqliteBusyError(msg)) {
|
|
227
|
+
await sleepWithSignal(1500 * attempt, signal);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
throw err;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
throw new Error("qmd command failed");
|
|
234
|
+
}, signal);
|
|
235
|
+
}
|
|
236
|
+
function isLikelyWriteCommand(args) {
|
|
237
|
+
const cmd = args[0] ?? "";
|
|
238
|
+
return cmd === "update" || cmd === "embed" || cmd === "cleanup" || cmd === "collection";
|
|
239
|
+
}
|
|
240
|
+
function runQmdOnce(args, timeoutMs, qmdPath, signal) {
|
|
241
|
+
return new Promise((resolve, reject) => {
|
|
242
|
+
throwIfAborted(signal, `qmd ${args.join(" ")} aborted before spawn`);
|
|
243
|
+
const child = launchProcess(qmdPath, args, {
|
|
244
|
+
env: mergeEnv({ NO_COLOR: "1" }),
|
|
245
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
246
|
+
});
|
|
247
|
+
if (!child.stdout || !child.stderr) {
|
|
248
|
+
reject(new Error(`qmd ${args.join(" ")} failed to open stdio pipes`));
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
let stdout = "";
|
|
252
|
+
let stderr = "";
|
|
253
|
+
let settled = false;
|
|
254
|
+
const timer = setTimeout(() => {
|
|
255
|
+
settled = true;
|
|
256
|
+
cleanup();
|
|
257
|
+
child.kill("SIGKILL");
|
|
258
|
+
reject(new Error(`qmd ${args.join(" ")} timed out after ${timeoutMs}ms`));
|
|
259
|
+
}, timeoutMs);
|
|
260
|
+
const onAbort = () => {
|
|
261
|
+
if (settled) return;
|
|
262
|
+
settled = true;
|
|
263
|
+
clearTimeout(timer);
|
|
264
|
+
cleanup();
|
|
265
|
+
child.kill("SIGKILL");
|
|
266
|
+
reject(abortError(`qmd ${args.join(" ")} aborted`));
|
|
267
|
+
};
|
|
268
|
+
const cleanup = () => {
|
|
269
|
+
signal?.removeEventListener("abort", onAbort);
|
|
270
|
+
};
|
|
271
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
272
|
+
child.stdout.on("data", (data) => {
|
|
273
|
+
stdout += data.toString();
|
|
274
|
+
});
|
|
275
|
+
child.stderr.on("data", (data) => {
|
|
276
|
+
stderr += data.toString();
|
|
277
|
+
});
|
|
278
|
+
child.on("error", (err) => {
|
|
279
|
+
if (settled) return;
|
|
280
|
+
settled = true;
|
|
281
|
+
clearTimeout(timer);
|
|
282
|
+
cleanup();
|
|
283
|
+
reject(err);
|
|
284
|
+
});
|
|
285
|
+
child.on("close", (code) => {
|
|
286
|
+
if (settled) return;
|
|
287
|
+
settled = true;
|
|
288
|
+
clearTimeout(timer);
|
|
289
|
+
cleanup();
|
|
290
|
+
const isVersionCheck = args.length === 1 && args[0] === "--version";
|
|
291
|
+
if (code === 0 || isVersionCheck && code === 1) {
|
|
292
|
+
resolve({ stdout, stderr });
|
|
293
|
+
} else {
|
|
294
|
+
reject(
|
|
295
|
+
new Error(
|
|
296
|
+
`qmd ${args.join(" ")} failed (code ${code}): ${truncateForLog(stderr || stdout)}`
|
|
297
|
+
)
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
var nextJsonRpcId = 1;
|
|
304
|
+
var QmdDaemonSession = class {
|
|
305
|
+
child = null;
|
|
306
|
+
initialized = false;
|
|
307
|
+
buffer = "";
|
|
308
|
+
startPromise = null;
|
|
309
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
310
|
+
qmdPath;
|
|
311
|
+
constructor(qmdPath) {
|
|
312
|
+
this.qmdPath = qmdPath;
|
|
313
|
+
}
|
|
314
|
+
/** Spawn the qmd mcp child process and perform MCP handshake. */
|
|
315
|
+
async start() {
|
|
316
|
+
if (this.child && !this.child.killed && this.initialized) {
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
if (this.startPromise) {
|
|
320
|
+
return this.startPromise;
|
|
321
|
+
}
|
|
322
|
+
this.startPromise = (async () => {
|
|
323
|
+
const processAlreadyRunning = this.child != null && !this.child.killed;
|
|
324
|
+
if (!processAlreadyRunning) {
|
|
325
|
+
if (this.child) {
|
|
326
|
+
this.cleanup({ killChild: true });
|
|
327
|
+
}
|
|
328
|
+
try {
|
|
329
|
+
const child = launchProcess(this.qmdPath, ["mcp"], {
|
|
330
|
+
env: mergeEnv({ NO_COLOR: "1" }),
|
|
331
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
332
|
+
});
|
|
333
|
+
this.child = child;
|
|
334
|
+
this.buffer = "";
|
|
335
|
+
child.stdout?.on("data", (data) => {
|
|
336
|
+
if (this.child !== child) return;
|
|
337
|
+
this.handleStdoutData(data);
|
|
338
|
+
});
|
|
339
|
+
child.stderr?.on("data", (data) => {
|
|
340
|
+
if (this.child !== child) return;
|
|
341
|
+
const msg = data.toString().trim();
|
|
342
|
+
if (msg) log.debug(`QMD mcp stderr: ${stripControlChars(msg)}`);
|
|
343
|
+
});
|
|
344
|
+
child.stdin?.on("error", (err) => {
|
|
345
|
+
log.debug(`QMD mcp stdin error (suppressed): ${err.message}`);
|
|
346
|
+
});
|
|
347
|
+
child.on("error", (err) => {
|
|
348
|
+
if (this.child !== child) return;
|
|
349
|
+
log.debug(`QMD mcp process error: ${err.message}`);
|
|
350
|
+
this.cleanup({ child });
|
|
351
|
+
});
|
|
352
|
+
child.on("close", (code) => {
|
|
353
|
+
if (this.child !== child) return;
|
|
354
|
+
log.debug(`QMD mcp process exited (code ${code})`);
|
|
355
|
+
this.cleanup({ child });
|
|
356
|
+
});
|
|
357
|
+
} catch (err) {
|
|
358
|
+
log.debug(`QMD mcp: failed to spawn process: ${err}`);
|
|
359
|
+
this.cleanup({ killChild: true });
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
} else {
|
|
363
|
+
log.debug("QMD mcp: process already running, retrying handshake");
|
|
364
|
+
}
|
|
365
|
+
try {
|
|
366
|
+
const result = await this.sendRequest(
|
|
367
|
+
"initialize",
|
|
368
|
+
{
|
|
369
|
+
protocolVersion: "2024-11-05",
|
|
370
|
+
capabilities: {},
|
|
371
|
+
clientInfo: { name: "openclaw-engram", version: "1.0.0" }
|
|
372
|
+
},
|
|
373
|
+
6e4
|
|
374
|
+
);
|
|
375
|
+
if (!result) {
|
|
376
|
+
this.cleanup({ killChild: true });
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
this.sendNotification("notifications/initialized");
|
|
380
|
+
this.initialized = true;
|
|
381
|
+
log.info("QMD mcp: stdio session initialized");
|
|
382
|
+
return true;
|
|
383
|
+
} catch (err) {
|
|
384
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
385
|
+
if (/timed out/i.test(msg)) {
|
|
386
|
+
log.debug(`QMD mcp: handshake timed out \u2014 process still loading, will retry later`);
|
|
387
|
+
this.initialized = false;
|
|
388
|
+
} else {
|
|
389
|
+
log.debug(`QMD mcp: failed to start stdio session: ${err}`);
|
|
390
|
+
this.cleanup({ killChild: true });
|
|
391
|
+
}
|
|
392
|
+
return false;
|
|
393
|
+
} finally {
|
|
394
|
+
this.startPromise = null;
|
|
395
|
+
}
|
|
396
|
+
})();
|
|
397
|
+
return this.startPromise;
|
|
398
|
+
}
|
|
399
|
+
/** Call an MCP tool and return the parsed result. */
|
|
400
|
+
async callTool(name, args, timeoutMs = 3e4, signal) {
|
|
401
|
+
if (!this.child || this.child.killed || !this.initialized) {
|
|
402
|
+
throw new Error("QMD mcp process not running");
|
|
403
|
+
}
|
|
404
|
+
return this.sendRequest("tools/call", { name, arguments: args }, timeoutMs, signal);
|
|
405
|
+
}
|
|
406
|
+
/** Kill stdio process and clear state so the next probe can restart. */
|
|
407
|
+
invalidate() {
|
|
408
|
+
this.cleanup({ killChild: true });
|
|
409
|
+
}
|
|
410
|
+
isActive() {
|
|
411
|
+
return this.child !== null && !this.child.killed && this.initialized;
|
|
412
|
+
}
|
|
413
|
+
/** True while the process is spawned but the MCP handshake has not yet completed. */
|
|
414
|
+
isLoading() {
|
|
415
|
+
return this.child !== null && !this.child.killed && !this.initialized;
|
|
416
|
+
}
|
|
417
|
+
sendRequest(method, params, timeoutMs, signal) {
|
|
418
|
+
return new Promise((resolve, reject) => {
|
|
419
|
+
throwIfAborted(signal, `QMD mcp ${method} aborted before request`);
|
|
420
|
+
if (!this.child || !this.child.stdin || this.child.killed) {
|
|
421
|
+
reject(new Error("QMD mcp process not available"));
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const id = nextJsonRpcId++;
|
|
425
|
+
const timer = setTimeout(() => {
|
|
426
|
+
this.pendingRequests.delete(id);
|
|
427
|
+
cleanup();
|
|
428
|
+
reject(new Error(`QMD mcp ${method} timed out after ${timeoutMs}ms`));
|
|
429
|
+
}, timeoutMs);
|
|
430
|
+
const onAbort = () => {
|
|
431
|
+
clearTimeout(timer);
|
|
432
|
+
this.pendingRequests.delete(id);
|
|
433
|
+
cleanup();
|
|
434
|
+
reject(abortError(`QMD mcp ${method} aborted`));
|
|
435
|
+
};
|
|
436
|
+
const cleanup = () => {
|
|
437
|
+
signal?.removeEventListener("abort", onAbort);
|
|
438
|
+
};
|
|
439
|
+
this.pendingRequests.set(id, { resolve, reject, timer, cleanup });
|
|
440
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
441
|
+
const message = JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n";
|
|
442
|
+
this.child.stdin.write(message, (err) => {
|
|
443
|
+
if (err) {
|
|
444
|
+
clearTimeout(timer);
|
|
445
|
+
this.pendingRequests.delete(id);
|
|
446
|
+
cleanup();
|
|
447
|
+
reject(new Error(`Failed to write to QMD mcp stdin: ${err.message}`));
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
sendNotification(method, params) {
|
|
453
|
+
if (!this.child || !this.child.stdin || this.child.killed) return;
|
|
454
|
+
if (this.child.stdin.destroyed) return;
|
|
455
|
+
const msg = { jsonrpc: "2.0", method };
|
|
456
|
+
if (params) msg.params = params;
|
|
457
|
+
try {
|
|
458
|
+
this.child.stdin.write(JSON.stringify(msg) + "\n");
|
|
459
|
+
} catch {
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
handleStdoutData(data) {
|
|
463
|
+
this.buffer += data.toString();
|
|
464
|
+
let newlineIdx;
|
|
465
|
+
while ((newlineIdx = this.buffer.indexOf("\n")) !== -1) {
|
|
466
|
+
const line = this.buffer.slice(0, newlineIdx).trim();
|
|
467
|
+
this.buffer = this.buffer.slice(newlineIdx + 1);
|
|
468
|
+
if (!line) continue;
|
|
469
|
+
try {
|
|
470
|
+
const msg = JSON.parse(line);
|
|
471
|
+
this.handleMessage(msg);
|
|
472
|
+
} catch {
|
|
473
|
+
log.debug(`QMD mcp: unparseable stdout: ${truncateForLog(line, 200)}`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
handleMessage(msg) {
|
|
478
|
+
if (msg.id !== void 0 && msg.id !== null) {
|
|
479
|
+
const pending = this.pendingRequests.get(msg.id);
|
|
480
|
+
if (pending) {
|
|
481
|
+
clearTimeout(pending.timer);
|
|
482
|
+
this.pendingRequests.delete(msg.id);
|
|
483
|
+
pending.cleanup();
|
|
484
|
+
if (msg.error) {
|
|
485
|
+
pending.reject(new Error(JSON.stringify(msg.error)));
|
|
486
|
+
} else {
|
|
487
|
+
pending.resolve(msg.result);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
if (msg.method) {
|
|
493
|
+
log.debug(`QMD mcp notification: ${msg.method}`);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
cleanup(opts) {
|
|
497
|
+
const target = opts?.child ?? this.child;
|
|
498
|
+
if (!target) return;
|
|
499
|
+
if (opts?.child && this.child !== opts.child) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
if (opts?.killChild && !target.killed) {
|
|
503
|
+
target.kill("SIGTERM");
|
|
504
|
+
}
|
|
505
|
+
this.initialized = false;
|
|
506
|
+
for (const [, pending] of this.pendingRequests) {
|
|
507
|
+
clearTimeout(pending.timer);
|
|
508
|
+
pending.cleanup();
|
|
509
|
+
pending.reject(new Error("QMD mcp process terminated"));
|
|
510
|
+
}
|
|
511
|
+
this.pendingRequests.clear();
|
|
512
|
+
this.startPromise = null;
|
|
513
|
+
this.child = null;
|
|
514
|
+
this.buffer = "";
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
function parseMcpSearchResult(result, transport = "daemon") {
|
|
518
|
+
const resultObj = result;
|
|
519
|
+
if (!resultObj) return [];
|
|
520
|
+
const results = [];
|
|
521
|
+
const pushDocs = (docs) => {
|
|
522
|
+
for (const doc of docs) {
|
|
523
|
+
const d = doc;
|
|
524
|
+
results.push({
|
|
525
|
+
docid: typeof d.docid === "string" ? d.docid.replace(/^#/, "") : "",
|
|
526
|
+
path: typeof d.file === "string" ? d.file : typeof d.path === "string" ? d.path : typeof d.docid === "string" ? d.docid.replace(/^#/, "") : "unknown",
|
|
527
|
+
snippet: typeof d.snippet === "string" ? d.snippet : "",
|
|
528
|
+
score: typeof d.score === "number" ? d.score : 0,
|
|
529
|
+
explain: parseQmdExplain(d.explain),
|
|
530
|
+
transport
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
const topStructured = resultObj.structuredContent;
|
|
535
|
+
const topDocs = topStructured?.results ?? topStructured?.documents;
|
|
536
|
+
if (Array.isArray(topDocs)) pushDocs(topDocs);
|
|
537
|
+
const content = resultObj.content;
|
|
538
|
+
if (Array.isArray(content)) {
|
|
539
|
+
for (const item of content) {
|
|
540
|
+
const structured = item?.structuredContent;
|
|
541
|
+
const docResults = structured?.results ?? structured?.documents;
|
|
542
|
+
if (Array.isArray(docResults)) pushDocs(docResults);
|
|
543
|
+
if (typeof item?.text === "string") {
|
|
544
|
+
try {
|
|
545
|
+
const parsed = JSON.parse(item.text);
|
|
546
|
+
const textResults = parsed?.results ?? parsed?.documents;
|
|
547
|
+
if (Array.isArray(textResults)) pushDocs(textResults);
|
|
548
|
+
} catch {
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return results;
|
|
554
|
+
}
|
|
555
|
+
function parseQmdSearchStdout(stdout, transport = "subprocess") {
|
|
556
|
+
const trimmedOut = stdout.trim();
|
|
557
|
+
if (!trimmedOut || trimmedOut === "No results found.") return [];
|
|
558
|
+
const parsed = JSON.parse(trimmedOut);
|
|
559
|
+
if (!Array.isArray(parsed)) return [];
|
|
560
|
+
return parsed.map(
|
|
561
|
+
(entry) => ({
|
|
562
|
+
docid: entry.docid ?? "",
|
|
563
|
+
path: entry.file ?? entry.path ?? entry.docid ?? "unknown",
|
|
564
|
+
snippet: entry.snippet ?? "",
|
|
565
|
+
score: typeof entry.score === "number" ? entry.score : 0,
|
|
566
|
+
explain: parseQmdExplain(entry.explain),
|
|
567
|
+
transport
|
|
568
|
+
})
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
var _sharedDaemonSession = null;
|
|
572
|
+
var _sharedDaemonSessionPath = null;
|
|
573
|
+
function getSharedDaemonSession(qmdPath) {
|
|
574
|
+
const normalizedPath = qmdPath.trim() || "qmd";
|
|
575
|
+
if (_sharedDaemonSession && _sharedDaemonSessionPath !== normalizedPath) {
|
|
576
|
+
_sharedDaemonSession.invalidate();
|
|
577
|
+
_sharedDaemonSession = null;
|
|
578
|
+
_sharedDaemonSessionPath = null;
|
|
579
|
+
}
|
|
580
|
+
if (!_sharedDaemonSession) {
|
|
581
|
+
_sharedDaemonSession = new QmdDaemonSession(normalizedPath);
|
|
582
|
+
_sharedDaemonSessionPath = normalizedPath;
|
|
583
|
+
}
|
|
584
|
+
return _sharedDaemonSession;
|
|
585
|
+
}
|
|
586
|
+
var QmdClient = class _QmdClient {
|
|
587
|
+
constructor(collection, maxResults, opts) {
|
|
588
|
+
this.collection = collection;
|
|
589
|
+
this.maxResults = maxResults;
|
|
590
|
+
this.slowLog = opts?.slowLog;
|
|
591
|
+
this.updateTimeoutMs = opts?.updateTimeoutMs ?? 12e4;
|
|
592
|
+
this.updateMinIntervalMs = Math.max(0, opts?.updateMinIntervalMs ?? 15 * 6e4);
|
|
593
|
+
this.configuredQmdPath = opts?.qmdPath?.trim() ? opts.qmdPath.trim() : void 0;
|
|
594
|
+
if (this.configuredQmdPath) {
|
|
595
|
+
this.qmdPath = this.configuredQmdPath;
|
|
596
|
+
this.qmdPathSource = "configured";
|
|
597
|
+
}
|
|
598
|
+
this.daemonEnabled = Boolean(opts?.daemonUrl);
|
|
599
|
+
this.daemonRecheckIntervalMs = opts?.daemonRecheckIntervalMs ?? 15e3;
|
|
600
|
+
}
|
|
601
|
+
collection;
|
|
602
|
+
maxResults;
|
|
603
|
+
available = null;
|
|
604
|
+
lastUpdateFailAtMs = null;
|
|
605
|
+
lastEmbedFailAtMs = null;
|
|
606
|
+
lastUpdateRunAtMs = null;
|
|
607
|
+
updateTimeoutMs;
|
|
608
|
+
updateMinIntervalMs;
|
|
609
|
+
slowLog;
|
|
610
|
+
configuredQmdPath;
|
|
611
|
+
qmdPathSource = "auto-path";
|
|
612
|
+
cliVersion = null;
|
|
613
|
+
lastCliProbeError = null;
|
|
614
|
+
// Daemon mode fields
|
|
615
|
+
daemonSession = null;
|
|
616
|
+
daemonAvailable = false;
|
|
617
|
+
lastDaemonCheckAtMs = 0;
|
|
618
|
+
daemonEnabled;
|
|
619
|
+
daemonRecheckIntervalMs;
|
|
620
|
+
/** Consecutive transient daemon failures before invalidating the session. */
|
|
621
|
+
daemonTransientFailures = 0;
|
|
622
|
+
static DAEMON_MAX_TRANSIENT_FAILURES = 3;
|
|
623
|
+
qmdPath = "qmd";
|
|
624
|
+
async probe() {
|
|
625
|
+
const cliOk = await this.probeCli();
|
|
626
|
+
if (this.daemonEnabled) {
|
|
627
|
+
await this.probeDaemon();
|
|
628
|
+
}
|
|
629
|
+
return cliOk || this.daemonAvailable;
|
|
630
|
+
}
|
|
631
|
+
async probeDaemon() {
|
|
632
|
+
this.lastDaemonCheckAtMs = Date.now();
|
|
633
|
+
this.daemonSession = getSharedDaemonSession(this.qmdPath);
|
|
634
|
+
try {
|
|
635
|
+
const PROBE_QUICK_TIMEOUT_MS = 3e3;
|
|
636
|
+
const ok = await Promise.race([
|
|
637
|
+
this.daemonSession.start(),
|
|
638
|
+
new Promise((resolve) => setTimeout(() => resolve(false), PROBE_QUICK_TIMEOUT_MS))
|
|
639
|
+
]);
|
|
640
|
+
if (!ok) {
|
|
641
|
+
const loading = this.daemonSession.isLoading();
|
|
642
|
+
log.debug(`QMD daemon: stdio session not ready within ${PROBE_QUICK_TIMEOUT_MS}ms probe window${loading ? " (still loading)" : ""}`);
|
|
643
|
+
this.daemonAvailable = false;
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
646
|
+
log.info(`QMD daemon: stdio session active (collection=${this.collection})`);
|
|
647
|
+
this.daemonAvailable = true;
|
|
648
|
+
this.daemonTransientFailures = 0;
|
|
649
|
+
return true;
|
|
650
|
+
} catch (err) {
|
|
651
|
+
log.debug(`QMD daemon: probe failed: ${err}`);
|
|
652
|
+
this.daemonAvailable = false;
|
|
653
|
+
return false;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
async probeCli() {
|
|
657
|
+
const parseVersion = (stdout, stderr) => {
|
|
658
|
+
const lines = `${stdout}
|
|
659
|
+
${stderr}`.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
660
|
+
if (lines.length === 0) return null;
|
|
661
|
+
const semanticLines = lines.filter((line) => parseQmdVersion(line) !== null);
|
|
662
|
+
if (semanticLines.length === 0) return lines[0] ?? null;
|
|
663
|
+
return semanticLines.find((line) => /\bqmd\b/i.test(line)) ?? semanticLines[0] ?? null;
|
|
664
|
+
};
|
|
665
|
+
const markProbeFailure = (err) => {
|
|
666
|
+
this.lastCliProbeError = err instanceof Error ? err.message : String(err);
|
|
667
|
+
};
|
|
668
|
+
if (this.configuredQmdPath) {
|
|
669
|
+
try {
|
|
670
|
+
const result = await runQmd(["--version"], QMD_PROBE_TIMEOUT_MS, this.configuredQmdPath);
|
|
671
|
+
this.available = true;
|
|
672
|
+
this.qmdPath = this.configuredQmdPath;
|
|
673
|
+
this.qmdPathSource = "configured";
|
|
674
|
+
this.cliVersion = parseVersion(result.stdout, result.stderr);
|
|
675
|
+
this.lastCliProbeError = null;
|
|
676
|
+
return true;
|
|
677
|
+
} catch (err) {
|
|
678
|
+
markProbeFailure(err);
|
|
679
|
+
this.logCliProbeWarning(
|
|
680
|
+
`QMD: configured qmdPath failed (${this.configuredQmdPath}): ${this.lastCliProbeError}`
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
try {
|
|
685
|
+
const result = await runQmd(["--version"], QMD_PROBE_TIMEOUT_MS, "qmd");
|
|
686
|
+
this.available = true;
|
|
687
|
+
this.qmdPath = "qmd";
|
|
688
|
+
this.qmdPathSource = "auto-path";
|
|
689
|
+
this.cliVersion = parseVersion(result.stdout, result.stderr);
|
|
690
|
+
this.lastCliProbeError = null;
|
|
691
|
+
return true;
|
|
692
|
+
} catch (err) {
|
|
693
|
+
markProbeFailure(err);
|
|
694
|
+
for (const fallbackPath of QMD_FALLBACK_PATHS) {
|
|
695
|
+
try {
|
|
696
|
+
const result = await runQmd(["--version"], QMD_PROBE_TIMEOUT_MS, fallbackPath);
|
|
697
|
+
this.available = true;
|
|
698
|
+
this.qmdPath = fallbackPath;
|
|
699
|
+
this.qmdPathSource = "auto-fallback";
|
|
700
|
+
this.cliVersion = parseVersion(result.stdout, result.stderr);
|
|
701
|
+
this.lastCliProbeError = null;
|
|
702
|
+
log.info(`QMD: found at ${fallbackPath}`);
|
|
703
|
+
return true;
|
|
704
|
+
} catch (fallbackErr) {
|
|
705
|
+
markProbeFailure(fallbackErr);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
this.available = false;
|
|
709
|
+
return false;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
logCliProbeWarning(message) {
|
|
713
|
+
const state = getGlobalQmdState();
|
|
714
|
+
const now = Date.now();
|
|
715
|
+
const canWarn = state.lastCliWarnAtMs === null || now - state.lastCliWarnAtMs >= QMD_CLI_WARN_THROTTLE_MS;
|
|
716
|
+
if (!canWarn) {
|
|
717
|
+
log.debug(message);
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
state.lastCliWarnAtMs = now;
|
|
721
|
+
if (this.daemonAvailable) {
|
|
722
|
+
log.debug(message);
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
log.warn(message);
|
|
726
|
+
}
|
|
727
|
+
/** Re-probe daemon if it was down and recheck interval has elapsed. */
|
|
728
|
+
async maybeProbeDaemon() {
|
|
729
|
+
if (!this.daemonEnabled) return;
|
|
730
|
+
if (this.daemonAvailable && this.daemonSession?.isActive()) return;
|
|
731
|
+
if (this.daemonAvailable === false) {
|
|
732
|
+
const elapsed = Date.now() - this.lastDaemonCheckAtMs;
|
|
733
|
+
if (elapsed < this.daemonRecheckIntervalMs) return;
|
|
734
|
+
}
|
|
735
|
+
this.daemonAvailable = false;
|
|
736
|
+
await this.probeDaemon();
|
|
737
|
+
}
|
|
738
|
+
isAvailable() {
|
|
739
|
+
return this.available === true || this.daemonAvailable;
|
|
740
|
+
}
|
|
741
|
+
/** Debug string for troubleshooting availability issues. */
|
|
742
|
+
debugStatus() {
|
|
743
|
+
const cliPath = this.available ? this.qmdPath : this.configuredQmdPath ?? "unavailable";
|
|
744
|
+
const cliVersion = this.cliVersion ?? "unknown";
|
|
745
|
+
const probeError = this.lastCliProbeError ? ` cliProbeError=${this.lastCliProbeError}` : "";
|
|
746
|
+
return `cli=${this.available} daemon=${this.daemonAvailable} session=${!!this.daemonSession} cliPath=${cliPath} cliPathSource=${this.qmdPathSource} cliVersion=${cliVersion}${probeError}`;
|
|
747
|
+
}
|
|
748
|
+
isDaemonMode() {
|
|
749
|
+
return this.daemonAvailable;
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Record a daemon search success — resets the transient failure counter.
|
|
753
|
+
*/
|
|
754
|
+
recordDaemonSuccess() {
|
|
755
|
+
this.daemonTransientFailures = 0;
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Handle a non-timeout, non-cancellation daemon error.
|
|
759
|
+
* Tolerates up to DAEMON_MAX_TRANSIENT_FAILURES consecutive failures
|
|
760
|
+
* before invalidating the session. This prevents a single transient
|
|
761
|
+
* error from pushing all concurrent searches through the subprocess
|
|
762
|
+
* mutex for the full recheck interval.
|
|
763
|
+
*/
|
|
764
|
+
handleDaemonTransientError(label, err, durationMs) {
|
|
765
|
+
if (!this.daemonAvailable) {
|
|
766
|
+
log.debug(`QMD daemon ${label} failed after ${durationMs}ms (daemon already unavailable, ignoring): ${err}`);
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
this.daemonTransientFailures += 1;
|
|
770
|
+
if (this.daemonTransientFailures >= _QmdClient.DAEMON_MAX_TRANSIENT_FAILURES) {
|
|
771
|
+
log.debug(`QMD daemon ${label} failed after ${durationMs}ms (${this.daemonTransientFailures} consecutive failures, invalidating): ${err}`);
|
|
772
|
+
this.daemonSession?.invalidate();
|
|
773
|
+
this.daemonAvailable = false;
|
|
774
|
+
this.daemonTransientFailures = 0;
|
|
775
|
+
} else {
|
|
776
|
+
log.debug(`QMD daemon ${label} failed after ${durationMs}ms (transient ${this.daemonTransientFailures}/${_QmdClient.DAEMON_MAX_TRANSIENT_FAILURES}): ${err}`);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
async runQmdCommand(args, timeoutMs, signal) {
|
|
780
|
+
return runQmd(args, timeoutMs, this.qmdPath, signal);
|
|
781
|
+
}
|
|
782
|
+
supportsIntentHints() {
|
|
783
|
+
return versionAtLeast(parseQmdVersion(this.cliVersion), [1, 1, 5]);
|
|
784
|
+
}
|
|
785
|
+
supportsExplainTraces() {
|
|
786
|
+
return versionAtLeast(parseQmdVersion(this.cliVersion), [1, 1, 2]);
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* QMD v2 (>= 2.0.0) uses a new MCP tool API:
|
|
790
|
+
* - `search` and `vsearch` tools removed; only `query` tool exists
|
|
791
|
+
* - `query` accepts `{ searches: [{ type, query }], collections?: string[] }`
|
|
792
|
+
* instead of `{ query: string, collection?: string }`
|
|
793
|
+
* - `collection` (singular) → `collections` (plural array)
|
|
794
|
+
*/
|
|
795
|
+
isQmdV2() {
|
|
796
|
+
return versionAtLeast(parseQmdVersion(this.cliVersion), [2, 0, 0]);
|
|
797
|
+
}
|
|
798
|
+
resolveSearchOptions(options) {
|
|
799
|
+
const normalized = normalizeSearchOptions(options);
|
|
800
|
+
if (!normalized) return void 0;
|
|
801
|
+
const resolved = {};
|
|
802
|
+
if (normalized.intent && this.supportsIntentHints()) {
|
|
803
|
+
resolved.intent = normalized.intent;
|
|
804
|
+
}
|
|
805
|
+
if (normalized.explain === true && this.supportsExplainTraces()) {
|
|
806
|
+
resolved.explain = true;
|
|
807
|
+
}
|
|
808
|
+
return Object.keys(resolved).length > 0 ? resolved : void 0;
|
|
809
|
+
}
|
|
810
|
+
resolveSupportedSearchOptions(options) {
|
|
811
|
+
return this.resolveSearchOptions(options);
|
|
812
|
+
}
|
|
813
|
+
async search(query, collection, maxResults, options, execution) {
|
|
814
|
+
if (!this.isAvailable()) return [];
|
|
815
|
+
const trimmed = query.trim();
|
|
816
|
+
if (!trimmed) return [];
|
|
817
|
+
const col = collection ?? this.collection;
|
|
818
|
+
const n = maxResults ?? this.maxResults;
|
|
819
|
+
const searchOptions = this.resolveSearchOptions(options);
|
|
820
|
+
const optionsFingerprint = searchOptions ? JSON.stringify(searchOptions) : "";
|
|
821
|
+
const cacheKey = createHash("sha256").update(`${col}:${n}:${optionsFingerprint}:${trimmed}`).digest("hex");
|
|
822
|
+
const cached = getCachedQmdSearch(cacheKey);
|
|
823
|
+
if (cached) {
|
|
824
|
+
log.debug(`QMD search cache hit (${cached.length} results)`);
|
|
825
|
+
return cached;
|
|
826
|
+
}
|
|
827
|
+
await this.maybeProbeDaemon();
|
|
828
|
+
if (this.daemonAvailable) {
|
|
829
|
+
let results;
|
|
830
|
+
try {
|
|
831
|
+
results = await this.searchViaDaemon(trimmed, col, n, searchOptions, execution?.signal);
|
|
832
|
+
} catch (err) {
|
|
833
|
+
if (isCallerCancellation(err, execution?.signal)) {
|
|
834
|
+
throw isAbortError(err) ? err : abortError("QMD daemon search aborted");
|
|
835
|
+
}
|
|
836
|
+
throw err;
|
|
837
|
+
}
|
|
838
|
+
if (results !== null) {
|
|
839
|
+
if (results.length === 0) {
|
|
840
|
+
log.debug("QMD daemon search returned 0 results; skipping subprocess");
|
|
841
|
+
}
|
|
842
|
+
setCachedQmdSearch(cacheKey, results);
|
|
843
|
+
return results;
|
|
844
|
+
}
|
|
845
|
+
log.debug("QMD daemon search timed out/failed; skipping subprocess (daemon-only mode)");
|
|
846
|
+
return [];
|
|
847
|
+
}
|
|
848
|
+
if (this.daemonSession?.isLoading()) {
|
|
849
|
+
log.debug("QMD search: daemon loading, skipping subprocess");
|
|
850
|
+
return [];
|
|
851
|
+
}
|
|
852
|
+
const subprocessResults = await this.searchViaSubprocess(trimmed, col, n, searchOptions, execution?.signal);
|
|
853
|
+
setCachedQmdSearch(cacheKey, subprocessResults);
|
|
854
|
+
return subprocessResults;
|
|
855
|
+
}
|
|
856
|
+
async searchGlobal(query, maxResults, execution) {
|
|
857
|
+
if (!this.isAvailable()) return [];
|
|
858
|
+
const trimmed = query.trim();
|
|
859
|
+
if (!trimmed) return [];
|
|
860
|
+
const n = maxResults ?? 6;
|
|
861
|
+
await this.maybeProbeDaemon();
|
|
862
|
+
if (this.daemonAvailable) {
|
|
863
|
+
let results;
|
|
864
|
+
try {
|
|
865
|
+
results = await this.searchViaDaemon(trimmed, void 0, n, void 0, execution?.signal);
|
|
866
|
+
} catch (err) {
|
|
867
|
+
if (isCallerCancellation(err, execution?.signal)) {
|
|
868
|
+
throw isAbortError(err) ? err : abortError("QMD daemon global search aborted");
|
|
869
|
+
}
|
|
870
|
+
throw err;
|
|
871
|
+
}
|
|
872
|
+
if (results !== null) {
|
|
873
|
+
if (results.length === 0) {
|
|
874
|
+
log.debug("QMD daemon global search returned 0 results; skipping subprocess");
|
|
875
|
+
}
|
|
876
|
+
return results;
|
|
877
|
+
}
|
|
878
|
+
log.debug("QMD daemon global search timed out/failed; skipping subprocess (daemon-only mode)");
|
|
879
|
+
return [];
|
|
880
|
+
}
|
|
881
|
+
if (this.daemonSession?.isLoading()) {
|
|
882
|
+
log.debug("QMD searchGlobal: daemon loading, skipping subprocess");
|
|
883
|
+
return [];
|
|
884
|
+
}
|
|
885
|
+
return this.searchGlobalViaSubprocess(trimmed, n, execution?.signal);
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* BM25 keyword search (fast, ~0.3s). Uses `qmd search`.
|
|
889
|
+
*/
|
|
890
|
+
async bm25Search(query, collection, maxResults, execution) {
|
|
891
|
+
if (!this.isAvailable()) return [];
|
|
892
|
+
const trimmed = query.trim();
|
|
893
|
+
if (!trimmed) return [];
|
|
894
|
+
const col = collection ?? this.collection;
|
|
895
|
+
const n = maxResults ?? this.maxResults;
|
|
896
|
+
await this.maybeProbeDaemon();
|
|
897
|
+
if (this.daemonAvailable && this.daemonSession) {
|
|
898
|
+
let results;
|
|
899
|
+
try {
|
|
900
|
+
results = await this.bm25SearchViaDaemon(trimmed, col, n, execution?.signal);
|
|
901
|
+
} catch (err) {
|
|
902
|
+
if (isCallerCancellation(err, execution?.signal)) {
|
|
903
|
+
throw isAbortError(err) ? err : abortError("QMD daemon bm25 aborted");
|
|
904
|
+
}
|
|
905
|
+
throw err;
|
|
906
|
+
}
|
|
907
|
+
if (results !== null) {
|
|
908
|
+
if (results.length === 0) {
|
|
909
|
+
log.debug("QMD daemon bm25 returned 0 results; skipping subprocess");
|
|
910
|
+
}
|
|
911
|
+
return results;
|
|
912
|
+
}
|
|
913
|
+
log.debug("QMD daemon bm25 timed out/failed; skipping subprocess (daemon-only mode)");
|
|
914
|
+
return [];
|
|
915
|
+
}
|
|
916
|
+
if (this.daemonSession?.isLoading()) {
|
|
917
|
+
log.debug("QMD bm25: daemon loading, skipping subprocess");
|
|
918
|
+
return [];
|
|
919
|
+
}
|
|
920
|
+
return this.bm25SearchViaSubprocess(trimmed, col, n, execution?.signal);
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Vector similarity search (~3-4s). Uses `qmd vsearch`.
|
|
924
|
+
*/
|
|
925
|
+
async vectorSearch(query, collection, maxResults, execution) {
|
|
926
|
+
if (!this.isAvailable()) return [];
|
|
927
|
+
const trimmed = query.trim();
|
|
928
|
+
if (!trimmed) return [];
|
|
929
|
+
const col = collection ?? this.collection;
|
|
930
|
+
const n = maxResults ?? this.maxResults;
|
|
931
|
+
await this.maybeProbeDaemon();
|
|
932
|
+
if (this.daemonAvailable && this.daemonSession) {
|
|
933
|
+
let results;
|
|
934
|
+
try {
|
|
935
|
+
results = await this.vsearchViaDaemon(trimmed, col, n, execution?.signal);
|
|
936
|
+
} catch (err) {
|
|
937
|
+
if (isCallerCancellation(err, execution?.signal)) {
|
|
938
|
+
throw isAbortError(err) ? err : abortError("QMD daemon vsearch aborted");
|
|
939
|
+
}
|
|
940
|
+
throw err;
|
|
941
|
+
}
|
|
942
|
+
if (results !== null) {
|
|
943
|
+
if (results.length === 0) {
|
|
944
|
+
log.debug("QMD daemon vsearch returned 0 results; skipping subprocess");
|
|
945
|
+
}
|
|
946
|
+
return results;
|
|
947
|
+
}
|
|
948
|
+
log.debug("QMD daemon vsearch timed out/failed; skipping subprocess (daemon-only mode)");
|
|
949
|
+
return [];
|
|
950
|
+
}
|
|
951
|
+
if (this.daemonSession?.isLoading()) {
|
|
952
|
+
log.debug("QMD vsearch: daemon loading, skipping subprocess");
|
|
953
|
+
return [];
|
|
954
|
+
}
|
|
955
|
+
return this.vsearchViaSubprocess(trimmed, col, n, execution?.signal);
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Hybrid search: runs BM25 + vector in parallel, merges/dedupes by path
|
|
959
|
+
* keeping the best score and first non-empty snippet.
|
|
960
|
+
*/
|
|
961
|
+
async hybridSearch(query, collection, maxResults, execution) {
|
|
962
|
+
const n = maxResults ?? this.maxResults;
|
|
963
|
+
const trimmed = query.trim();
|
|
964
|
+
if (!trimmed) return [];
|
|
965
|
+
const [bm25Results, vectorResults] = await Promise.all([
|
|
966
|
+
this.bm25Search(trimmed, collection, n, execution),
|
|
967
|
+
this.vectorSearch(trimmed, collection, n, execution)
|
|
968
|
+
]);
|
|
969
|
+
const merged = /* @__PURE__ */ new Map();
|
|
970
|
+
for (const r of [...bm25Results, ...vectorResults]) {
|
|
971
|
+
const key = r.path || r.docid;
|
|
972
|
+
const existing = merged.get(key);
|
|
973
|
+
if (!existing || r.score > existing.score) {
|
|
974
|
+
merged.set(key, {
|
|
975
|
+
...r,
|
|
976
|
+
snippet: r.snippet || existing?.snippet || ""
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
return [...merged.values()].sort((a, b) => b.score - a.score).slice(0, n);
|
|
981
|
+
}
|
|
982
|
+
async searchViaDaemon(query, collection, maxResults, options, signal) {
|
|
983
|
+
if (!this.daemonSession || !this.daemonAvailable) return null;
|
|
984
|
+
const startedAtMs = Date.now();
|
|
985
|
+
const v2 = this.isQmdV2();
|
|
986
|
+
try {
|
|
987
|
+
let args;
|
|
988
|
+
if (v2) {
|
|
989
|
+
const searches = [{ type: "lex", query }];
|
|
990
|
+
searches.push({ type: "vec", query });
|
|
991
|
+
args = { searches, limit: maxResults };
|
|
992
|
+
if (collection) {
|
|
993
|
+
args.collections = [collection];
|
|
994
|
+
}
|
|
995
|
+
if (options?.intent) {
|
|
996
|
+
args.intent = options.intent;
|
|
997
|
+
}
|
|
998
|
+
if (options?.explain === true) {
|
|
999
|
+
args.explain = true;
|
|
1000
|
+
}
|
|
1001
|
+
} else {
|
|
1002
|
+
args = { query, limit: maxResults };
|
|
1003
|
+
if (collection) {
|
|
1004
|
+
args.collection = collection;
|
|
1005
|
+
}
|
|
1006
|
+
if (options?.intent) {
|
|
1007
|
+
args.intent = options.intent;
|
|
1008
|
+
}
|
|
1009
|
+
if (options?.explain === true) {
|
|
1010
|
+
args.explain = true;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
const result = await this.daemonSession.callTool("query", args, QMD_DAEMON_TIMEOUT_MS, signal);
|
|
1014
|
+
const durationMs = Date.now() - startedAtMs;
|
|
1015
|
+
if (this.slowLog?.enabled && durationMs >= this.slowLog.thresholdMs) {
|
|
1016
|
+
log.warn(
|
|
1017
|
+
`SLOW QMD daemon query: durationMs=${durationMs} collection=${collection ?? "global"} maxResults=${maxResults} queryChars=${query.length} v2=${v2}`
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
const results = parseMcpSearchResult(result, "daemon");
|
|
1021
|
+
log.debug(`QMD daemon search: ${results.length} results in ${durationMs}ms (v2=${v2})`);
|
|
1022
|
+
this.recordDaemonSuccess();
|
|
1023
|
+
return results;
|
|
1024
|
+
} catch (err) {
|
|
1025
|
+
const durationMs = Date.now() - startedAtMs;
|
|
1026
|
+
if (isCallerCancellation(err, signal)) {
|
|
1027
|
+
log.debug(`QMD daemon search aborted/cancelled after ${durationMs}ms`);
|
|
1028
|
+
throw isAbortError(err) ? err : abortError("QMD daemon search aborted");
|
|
1029
|
+
}
|
|
1030
|
+
if (isDaemonTimeoutError(err)) {
|
|
1031
|
+
log.debug(`QMD daemon search timed out after ${durationMs}ms, falling back to subprocess`);
|
|
1032
|
+
return null;
|
|
1033
|
+
}
|
|
1034
|
+
this.handleDaemonTransientError("search", err, durationMs);
|
|
1035
|
+
return null;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
async bm25SearchViaDaemon(query, collection, maxResults, signal) {
|
|
1039
|
+
if (!this.daemonSession || !this.daemonAvailable) return null;
|
|
1040
|
+
const startedAtMs = Date.now();
|
|
1041
|
+
const v2 = this.isQmdV2();
|
|
1042
|
+
try {
|
|
1043
|
+
let result;
|
|
1044
|
+
if (v2) {
|
|
1045
|
+
result = await this.daemonSession.callTool(
|
|
1046
|
+
"query",
|
|
1047
|
+
{
|
|
1048
|
+
searches: [{ type: "lex", query }],
|
|
1049
|
+
collections: [collection],
|
|
1050
|
+
limit: maxResults
|
|
1051
|
+
},
|
|
1052
|
+
QMD_DAEMON_TIMEOUT_MS,
|
|
1053
|
+
signal
|
|
1054
|
+
);
|
|
1055
|
+
} else {
|
|
1056
|
+
result = await this.daemonSession.callTool(
|
|
1057
|
+
"search",
|
|
1058
|
+
{ query, limit: maxResults, collection },
|
|
1059
|
+
QMD_DAEMON_TIMEOUT_MS,
|
|
1060
|
+
signal
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
const durationMs = Date.now() - startedAtMs;
|
|
1064
|
+
const results = parseMcpSearchResult(result);
|
|
1065
|
+
log.debug(`QMD daemon bm25: ${results.length} results in ${durationMs}ms (v2=${v2})`);
|
|
1066
|
+
this.recordDaemonSuccess();
|
|
1067
|
+
return results;
|
|
1068
|
+
} catch (err) {
|
|
1069
|
+
const durationMs = Date.now() - startedAtMs;
|
|
1070
|
+
if (isCallerCancellation(err, signal)) {
|
|
1071
|
+
log.debug(`QMD daemon bm25 aborted/cancelled after ${durationMs}ms`);
|
|
1072
|
+
throw isAbortError(err) ? err : abortError("QMD daemon bm25 aborted");
|
|
1073
|
+
}
|
|
1074
|
+
if (isDaemonTimeoutError(err)) {
|
|
1075
|
+
log.debug(`QMD daemon bm25 timed out after ${durationMs}ms, falling back to subprocess`);
|
|
1076
|
+
return null;
|
|
1077
|
+
}
|
|
1078
|
+
this.handleDaemonTransientError("bm25", err, durationMs);
|
|
1079
|
+
return null;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
async vsearchViaDaemon(query, collection, maxResults, signal) {
|
|
1083
|
+
if (!this.daemonSession || !this.daemonAvailable) return null;
|
|
1084
|
+
const startedAtMs = Date.now();
|
|
1085
|
+
const v2 = this.isQmdV2();
|
|
1086
|
+
try {
|
|
1087
|
+
let result;
|
|
1088
|
+
if (v2) {
|
|
1089
|
+
result = await this.daemonSession.callTool(
|
|
1090
|
+
"query",
|
|
1091
|
+
{
|
|
1092
|
+
searches: [{ type: "vec", query }],
|
|
1093
|
+
collections: [collection],
|
|
1094
|
+
limit: maxResults
|
|
1095
|
+
},
|
|
1096
|
+
QMD_DAEMON_TIMEOUT_MS,
|
|
1097
|
+
signal
|
|
1098
|
+
);
|
|
1099
|
+
} else {
|
|
1100
|
+
result = await this.daemonSession.callTool(
|
|
1101
|
+
"vsearch",
|
|
1102
|
+
{ query, limit: maxResults, collection },
|
|
1103
|
+
QMD_DAEMON_TIMEOUT_MS,
|
|
1104
|
+
signal
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
const durationMs = Date.now() - startedAtMs;
|
|
1108
|
+
const results = parseMcpSearchResult(result);
|
|
1109
|
+
log.debug(`QMD daemon vsearch: ${results.length} results in ${durationMs}ms (v2=${v2})`);
|
|
1110
|
+
this.recordDaemonSuccess();
|
|
1111
|
+
return results;
|
|
1112
|
+
} catch (err) {
|
|
1113
|
+
const durationMs = Date.now() - startedAtMs;
|
|
1114
|
+
if (isCallerCancellation(err, signal)) {
|
|
1115
|
+
log.debug(`QMD daemon vsearch aborted/cancelled after ${durationMs}ms`);
|
|
1116
|
+
throw isAbortError(err) ? err : abortError("QMD daemon vsearch aborted");
|
|
1117
|
+
}
|
|
1118
|
+
if (isDaemonTimeoutError(err)) {
|
|
1119
|
+
log.debug(`QMD daemon vsearch timed out after ${durationMs}ms, falling back to subprocess`);
|
|
1120
|
+
return null;
|
|
1121
|
+
}
|
|
1122
|
+
this.handleDaemonTransientError("vsearch", err, durationMs);
|
|
1123
|
+
return null;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
async searchViaSubprocess(query, collection, maxResults, options, signal) {
|
|
1127
|
+
if (this.available === false) return [];
|
|
1128
|
+
const startedAtMs = Date.now();
|
|
1129
|
+
try {
|
|
1130
|
+
const args = ["query", query, "-c", collection, "--json", "-n", String(maxResults)];
|
|
1131
|
+
if (options?.intent) {
|
|
1132
|
+
args.push("--intent", options.intent);
|
|
1133
|
+
}
|
|
1134
|
+
if (options?.explain === true) {
|
|
1135
|
+
args.push("--explain");
|
|
1136
|
+
}
|
|
1137
|
+
const { stdout } = await runQmd(
|
|
1138
|
+
args,
|
|
1139
|
+
QMD_TIMEOUT_MS,
|
|
1140
|
+
this.qmdPath,
|
|
1141
|
+
signal
|
|
1142
|
+
);
|
|
1143
|
+
const durationMs = Date.now() - startedAtMs;
|
|
1144
|
+
if (this.slowLog?.enabled && durationMs >= this.slowLog.thresholdMs) {
|
|
1145
|
+
log.warn(
|
|
1146
|
+
`SLOW QMD query: durationMs=${durationMs} collection=${collection} maxResults=${maxResults} queryChars=${query.length}`
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
return parseQmdSearchStdout(stdout, "subprocess");
|
|
1150
|
+
} catch (err) {
|
|
1151
|
+
if (isCallerCancellation(err, signal)) {
|
|
1152
|
+
throw isAbortError(err) ? err : abortError("QMD subprocess search aborted");
|
|
1153
|
+
}
|
|
1154
|
+
log.debug(`QMD search failed: ${err}`);
|
|
1155
|
+
return [];
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
async bm25SearchViaSubprocess(query, collection, maxResults, signal) {
|
|
1159
|
+
if (this.available === false) return [];
|
|
1160
|
+
const startedAtMs = Date.now();
|
|
1161
|
+
try {
|
|
1162
|
+
const { stdout } = await runQmd(
|
|
1163
|
+
["search", query, "-c", collection, "--json", "-n", String(maxResults)],
|
|
1164
|
+
QMD_TIMEOUT_MS,
|
|
1165
|
+
this.qmdPath,
|
|
1166
|
+
signal
|
|
1167
|
+
);
|
|
1168
|
+
log.debug(`QMD bm25: ${Date.now() - startedAtMs}ms`);
|
|
1169
|
+
return parseQmdSearchStdout(stdout);
|
|
1170
|
+
} catch (err) {
|
|
1171
|
+
if (isCallerCancellation(err, signal)) {
|
|
1172
|
+
throw isAbortError(err) ? err : abortError("QMD subprocess bm25 aborted");
|
|
1173
|
+
}
|
|
1174
|
+
log.debug(`QMD bm25 search failed: ${err}`);
|
|
1175
|
+
return [];
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
async vsearchViaSubprocess(query, collection, maxResults, signal) {
|
|
1179
|
+
if (this.available === false) return [];
|
|
1180
|
+
const startedAtMs = Date.now();
|
|
1181
|
+
try {
|
|
1182
|
+
const { stdout } = await runQmd(
|
|
1183
|
+
["vsearch", query, "-c", collection, "--json", "-n", String(maxResults)],
|
|
1184
|
+
QMD_TIMEOUT_MS,
|
|
1185
|
+
this.qmdPath,
|
|
1186
|
+
signal
|
|
1187
|
+
);
|
|
1188
|
+
log.debug(`QMD vsearch: ${Date.now() - startedAtMs}ms`);
|
|
1189
|
+
return parseQmdSearchStdout(stdout);
|
|
1190
|
+
} catch (err) {
|
|
1191
|
+
if (isCallerCancellation(err, signal)) {
|
|
1192
|
+
throw isAbortError(err) ? err : abortError("QMD subprocess vsearch aborted");
|
|
1193
|
+
}
|
|
1194
|
+
log.debug(`QMD vsearch failed: ${err}`);
|
|
1195
|
+
return [];
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
async searchGlobalViaSubprocess(query, maxResults, signal) {
|
|
1199
|
+
if (this.available === false) return [];
|
|
1200
|
+
const startedAtMs = Date.now();
|
|
1201
|
+
try {
|
|
1202
|
+
const { stdout } = await runQmd(
|
|
1203
|
+
["query", query, "--json", "-n", String(maxResults)],
|
|
1204
|
+
QMD_TIMEOUT_MS,
|
|
1205
|
+
this.qmdPath,
|
|
1206
|
+
signal
|
|
1207
|
+
);
|
|
1208
|
+
const durationMs = Date.now() - startedAtMs;
|
|
1209
|
+
if (this.slowLog?.enabled && durationMs >= this.slowLog.thresholdMs) {
|
|
1210
|
+
log.warn(
|
|
1211
|
+
`SLOW QMD global query: durationMs=${durationMs} maxResults=${maxResults} queryChars=${query.length}`
|
|
1212
|
+
);
|
|
1213
|
+
}
|
|
1214
|
+
return parseQmdSearchStdout(stdout);
|
|
1215
|
+
} catch (err) {
|
|
1216
|
+
if (isCallerCancellation(err, signal)) {
|
|
1217
|
+
throw isAbortError(err) ? err : abortError("QMD subprocess global search aborted");
|
|
1218
|
+
}
|
|
1219
|
+
log.debug(`QMD global search failed: ${err}`);
|
|
1220
|
+
return [];
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
async update() {
|
|
1224
|
+
await this.runUpdateForCollection(this.collection, { perCollectionThrottle: false });
|
|
1225
|
+
}
|
|
1226
|
+
async updateCollection(collection) {
|
|
1227
|
+
await this.runUpdateForCollection(collection, { perCollectionThrottle: true });
|
|
1228
|
+
}
|
|
1229
|
+
async runUpdateForCollection(collection, options) {
|
|
1230
|
+
if (this.available === false) return;
|
|
1231
|
+
const name = collection.trim();
|
|
1232
|
+
if (!name) return;
|
|
1233
|
+
const globalState = getGlobalQmdState();
|
|
1234
|
+
const now = Date.now();
|
|
1235
|
+
if (options.perCollectionThrottle) {
|
|
1236
|
+
if (globalState.lastGlobalUpdateFailAtMs && now - globalState.lastGlobalUpdateFailAtMs < QMD_UPDATE_BACKOFF_MS) {
|
|
1237
|
+
log.debug("QMD update: suppressed by global failure backoff");
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
const lastCollectionRun = globalState.lastUpdateByCollectionMs[name];
|
|
1241
|
+
if (Number.isFinite(lastCollectionRun) && now - lastCollectionRun < this.updateMinIntervalMs) {
|
|
1242
|
+
log.debug(`QMD update: suppressed by per-collection min-interval gate (${name})`);
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
const lastCollectionFail = globalState.lastUpdateFailByCollectionMs[name];
|
|
1246
|
+
if (Number.isFinite(lastCollectionFail) && now - lastCollectionFail < QMD_UPDATE_BACKOFF_MS) {
|
|
1247
|
+
log.debug(`QMD update: suppressed by per-collection failure backoff (${name})`);
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
} else {
|
|
1251
|
+
if (this.lastUpdateRunAtMs && now - this.lastUpdateRunAtMs < this.updateMinIntervalMs) {
|
|
1252
|
+
log.debug("QMD update: suppressed due to min-interval gate");
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
if (this.lastUpdateFailAtMs && now - this.lastUpdateFailAtMs < QMD_UPDATE_BACKOFF_MS) {
|
|
1256
|
+
log.debug("QMD update: suppressed due to recent failures (backoff)");
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
if (globalState.lastGlobalUpdateRunAtMs && now - globalState.lastGlobalUpdateRunAtMs < this.updateMinIntervalMs) {
|
|
1260
|
+
log.debug("QMD update: suppressed by global min-interval gate");
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
if (globalState.lastGlobalUpdateFailAtMs && now - globalState.lastGlobalUpdateFailAtMs < QMD_UPDATE_BACKOFF_MS) {
|
|
1264
|
+
log.debug("QMD update: suppressed by global failure backoff");
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
try {
|
|
1269
|
+
if (!globalState.warnedGlobalUpdateBehavior) {
|
|
1270
|
+
globalState.warnedGlobalUpdateBehavior = true;
|
|
1271
|
+
log.warn(
|
|
1272
|
+
"QMD update runs globally across collections in current CLI versions; Engram now rate-limits update calls to reduce gateway load."
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
const startedAtMs = Date.now();
|
|
1276
|
+
await this.runQmdCommand(["update", "-c", name], this.updateTimeoutMs);
|
|
1277
|
+
const durationMs = Date.now() - startedAtMs;
|
|
1278
|
+
if (this.slowLog?.enabled && durationMs >= this.slowLog.thresholdMs) {
|
|
1279
|
+
log.warn(`SLOW QMD update: durationMs=${durationMs}`);
|
|
1280
|
+
}
|
|
1281
|
+
const at = Date.now();
|
|
1282
|
+
if (options.perCollectionThrottle) {
|
|
1283
|
+
globalState.lastUpdateByCollectionMs[name] = at;
|
|
1284
|
+
globalState.lastGlobalUpdateRunAtMs = at;
|
|
1285
|
+
} else {
|
|
1286
|
+
this.lastUpdateRunAtMs = at;
|
|
1287
|
+
globalState.lastGlobalUpdateRunAtMs = at;
|
|
1288
|
+
}
|
|
1289
|
+
log.debug(`QMD update completed for collection=${name}`);
|
|
1290
|
+
} catch (err) {
|
|
1291
|
+
const at = Date.now();
|
|
1292
|
+
if (options.perCollectionThrottle) {
|
|
1293
|
+
globalState.lastUpdateFailByCollectionMs[name] = at;
|
|
1294
|
+
globalState.lastGlobalUpdateFailAtMs = at;
|
|
1295
|
+
} else {
|
|
1296
|
+
this.lastUpdateFailAtMs = at;
|
|
1297
|
+
globalState.lastGlobalUpdateFailAtMs = at;
|
|
1298
|
+
}
|
|
1299
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1300
|
+
log.warn(`QMD update failed for collection ${name}: ${msg}`);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
async embed() {
|
|
1304
|
+
if (this.available === false) return;
|
|
1305
|
+
const globalState = getGlobalQmdState();
|
|
1306
|
+
if (this.lastEmbedFailAtMs && Date.now() - this.lastEmbedFailAtMs < QMD_EMBED_BACKOFF_MS) {
|
|
1307
|
+
log.debug("QMD embed: suppressed due to recent failures (backoff)");
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
if (globalState.lastGlobalEmbedRunAtMs && Date.now() - globalState.lastGlobalEmbedRunAtMs < this.updateMinIntervalMs) {
|
|
1311
|
+
log.debug("QMD embed: suppressed by global min-interval gate");
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1314
|
+
if (globalState.lastGlobalEmbedFailAtMs && Date.now() - globalState.lastGlobalEmbedFailAtMs < QMD_EMBED_BACKOFF_MS) {
|
|
1315
|
+
log.debug("QMD embed: suppressed by global failure backoff");
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
try {
|
|
1319
|
+
const startedAtMs = Date.now();
|
|
1320
|
+
await this.runQmdCommand(["embed", "-c", this.collection], 3e5);
|
|
1321
|
+
const durationMs = Date.now() - startedAtMs;
|
|
1322
|
+
if (this.slowLog?.enabled && durationMs >= this.slowLog.thresholdMs) {
|
|
1323
|
+
log.warn(`SLOW QMD embed: durationMs=${durationMs}`);
|
|
1324
|
+
}
|
|
1325
|
+
globalState.lastGlobalEmbedRunAtMs = Date.now();
|
|
1326
|
+
log.debug("QMD embed completed");
|
|
1327
|
+
} catch (err) {
|
|
1328
|
+
if (isVectorDimensionMismatchError(err)) {
|
|
1329
|
+
try {
|
|
1330
|
+
log.warn("QMD embed hit a vector dimension mismatch; retrying with force re-embed");
|
|
1331
|
+
await this.runQmdCommand(["embed", "-f", "-c", this.collection], 3e5);
|
|
1332
|
+
globalState.lastGlobalEmbedRunAtMs = Date.now();
|
|
1333
|
+
this.lastEmbedFailAtMs = null;
|
|
1334
|
+
globalState.lastGlobalEmbedFailAtMs = null;
|
|
1335
|
+
log.warn("QMD embed recovered by forcing a full vector rebuild");
|
|
1336
|
+
return;
|
|
1337
|
+
} catch (retryErr) {
|
|
1338
|
+
const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
|
|
1339
|
+
log.warn(`QMD force re-embed failed after dimension mismatch: ${retryMsg}`);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
const now = Date.now();
|
|
1343
|
+
this.lastEmbedFailAtMs = now;
|
|
1344
|
+
globalState.lastGlobalEmbedFailAtMs = now;
|
|
1345
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1346
|
+
log.warn(`QMD embed failed: ${msg}`);
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
async embedCollection(collection) {
|
|
1350
|
+
if (this.available === false) return;
|
|
1351
|
+
const name = collection.trim();
|
|
1352
|
+
if (!name) return;
|
|
1353
|
+
const globalState = getGlobalQmdState();
|
|
1354
|
+
const now = Date.now();
|
|
1355
|
+
if (globalState.lastGlobalEmbedFailAtMs && now - globalState.lastGlobalEmbedFailAtMs < QMD_EMBED_BACKOFF_MS) {
|
|
1356
|
+
log.debug(`QMD embed: suppressed by global failure backoff (${name})`);
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
const lastCollectionRun = globalState.lastEmbedByCollectionMs[name];
|
|
1360
|
+
if (Number.isFinite(lastCollectionRun) && now - lastCollectionRun < this.updateMinIntervalMs) {
|
|
1361
|
+
log.debug(`QMD embed: suppressed by per-collection min-interval gate (${name})`);
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
const lastCollectionFail = globalState.lastEmbedFailByCollectionMs[name];
|
|
1365
|
+
if (Number.isFinite(lastCollectionFail) && now - lastCollectionFail < QMD_EMBED_BACKOFF_MS) {
|
|
1366
|
+
log.debug(`QMD embed: suppressed by per-collection failure backoff (${name})`);
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
try {
|
|
1370
|
+
await this.runQmdCommand(["embed", "-c", name], 3e5);
|
|
1371
|
+
const at = Date.now();
|
|
1372
|
+
globalState.lastEmbedByCollectionMs[name] = at;
|
|
1373
|
+
globalState.lastGlobalEmbedRunAtMs = at;
|
|
1374
|
+
} catch (err) {
|
|
1375
|
+
if (isVectorDimensionMismatchError(err)) {
|
|
1376
|
+
try {
|
|
1377
|
+
log.warn(`QMD embed for collection ${name} hit a vector dimension mismatch; retrying with force re-embed`);
|
|
1378
|
+
await this.runQmdCommand(["embed", "-f", "-c", name], 3e5);
|
|
1379
|
+
const recoveredAt = Date.now();
|
|
1380
|
+
globalState.lastEmbedByCollectionMs[name] = recoveredAt;
|
|
1381
|
+
globalState.lastGlobalEmbedRunAtMs = recoveredAt;
|
|
1382
|
+
delete globalState.lastEmbedFailByCollectionMs[name];
|
|
1383
|
+
globalState.lastGlobalEmbedFailAtMs = null;
|
|
1384
|
+
log.warn(`QMD embed for collection ${name} recovered by forcing a full vector rebuild`);
|
|
1385
|
+
return;
|
|
1386
|
+
} catch (retryErr) {
|
|
1387
|
+
const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
|
|
1388
|
+
log.warn(`QMD force re-embed failed for collection ${name}: ${retryMsg}`);
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
const at = Date.now();
|
|
1392
|
+
globalState.lastEmbedFailByCollectionMs[name] = at;
|
|
1393
|
+
globalState.lastGlobalEmbedFailAtMs = at;
|
|
1394
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1395
|
+
log.warn(`QMD embed failed for collection ${name}: ${msg}`);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
async ensureCollection(memoryDir) {
|
|
1399
|
+
if (this.available === false && !this.daemonAvailable) return "unknown";
|
|
1400
|
+
if (this.available === false) return "skipped";
|
|
1401
|
+
try {
|
|
1402
|
+
const { stdout } = await runQmd(
|
|
1403
|
+
["collection", "list"],
|
|
1404
|
+
QMD_TIMEOUT_MS,
|
|
1405
|
+
this.qmdPath
|
|
1406
|
+
);
|
|
1407
|
+
const collectionRegex = new RegExp(
|
|
1408
|
+
`^${this.collection}\\s+\\(qmd://`,
|
|
1409
|
+
"m"
|
|
1410
|
+
);
|
|
1411
|
+
if (collectionRegex.test(stdout)) {
|
|
1412
|
+
return "present";
|
|
1413
|
+
}
|
|
1414
|
+
} catch (err) {
|
|
1415
|
+
log.debug(
|
|
1416
|
+
`QMD collection check unavailable for "${this.collection}" (will not disable features): ${err instanceof Error ? err.message : String(err)}`
|
|
1417
|
+
);
|
|
1418
|
+
return "unknown";
|
|
1419
|
+
}
|
|
1420
|
+
log.info(
|
|
1421
|
+
`QMD collection "${this.collection}" not found. Add it to ~/.config/qmd/index.yml pointing at ${memoryDir}`
|
|
1422
|
+
);
|
|
1423
|
+
return "missing";
|
|
1424
|
+
}
|
|
1425
|
+
};
|
|
1426
|
+
|
|
1427
|
+
export {
|
|
1428
|
+
parseQmdExplain,
|
|
1429
|
+
QmdClient
|
|
1430
|
+
};
|
|
1431
|
+
//# sourceMappingURL=chunk-TVVVQQAK.js.map
|