@remnic/core 9.4.0 → 9.4.2

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.
Files changed (329) hide show
  1. package/dist/access-admin-ops-surface.d.ts +5 -5
  2. package/dist/access-admin-ops-surface.js +22 -21
  3. package/dist/access-boundary.d.ts +5 -5
  4. package/dist/access-boundary.js +23 -22
  5. package/dist/access-cli.js +63 -62
  6. package/dist/access-cli.js.map +1 -1
  7. package/dist/access-http.d.ts +5 -5
  8. package/dist/access-http.js +31 -30
  9. package/dist/access-identity-continuity-surface.d.ts +4 -4
  10. package/dist/access-identity-continuity-surface.js +22 -21
  11. package/dist/access-lcm-surface.d.ts +5 -5
  12. package/dist/access-lcm-surface.js +22 -21
  13. package/dist/access-mcp.d.ts +5 -5
  14. package/dist/access-mcp.js +27 -26
  15. package/dist/access-observe-write-surface.d.ts +5 -5
  16. package/dist/access-observe-write-surface.js +22 -21
  17. package/dist/access-operations-batch.js +24 -23
  18. package/dist/access-operations.d.ts +9 -9
  19. package/dist/access-operations.js +26 -25
  20. package/dist/access-recall-surface.d.ts +5 -5
  21. package/dist/access-recall-surface.js +22 -21
  22. package/dist/access-schema.d.ts +68 -68
  23. package/dist/access-schema.js +3 -3
  24. package/dist/{access-service-CdW6B2qQ.d.ts → access-service-m-2VO_Ig.d.ts} +3 -3
  25. package/dist/access-service.d.ts +5 -5
  26. package/dist/access-service.js +22 -21
  27. package/dist/access-surface-catalog.d.ts +5 -5
  28. package/dist/action-confidence.d.ts +1 -1
  29. package/dist/active-memory-bridge.d.ts +1 -1
  30. package/dist/active-recall.d.ts +1 -1
  31. package/dist/active-recall.js +1 -1
  32. package/dist/adapters/index.js +4 -4
  33. package/dist/adapters/registry.js +2 -2
  34. package/dist/{auto-sync-M47VTCFI.js → auto-sync-HMHQHUAG.js} +4 -4
  35. package/dist/behavior-learner.d.ts +1 -1
  36. package/dist/behavior-signals.d.ts +1 -1
  37. package/dist/bootstrap.d.ts +4 -4
  38. package/dist/briefing.d.ts +1 -1
  39. package/dist/briefing.js +5 -5
  40. package/dist/buffer-surprise-report.d.ts +1 -1
  41. package/dist/buffer.d.ts +1 -1
  42. package/dist/calibration.d.ts +1 -1
  43. package/dist/capabilities.d.ts +1 -1
  44. package/dist/{catalog-bflYNWsM.d.ts → catalog-BxWokdC2.d.ts} +1 -1
  45. package/dist/causal-behavior.d.ts +1 -1
  46. package/dist/causal-consolidation.d.ts +1 -1
  47. package/dist/causal-consolidation.js +6 -6
  48. package/dist/causal-trajectory-graph.d.ts +1 -1
  49. package/dist/{chunk-TKUQSXU4.js → chunk-27FLVMDC.js} +12 -12
  50. package/dist/{chunk-XXEOGY77.js → chunk-2FOD2YY3.js} +7 -7
  51. package/dist/{chunk-6Y4JTNX7.js → chunk-2MR3MFQB.js} +2 -2
  52. package/dist/{chunk-AU7Q3LSC.js → chunk-2QSZNTDO.js} +4 -4
  53. package/dist/{chunk-64ZYXSCO.js → chunk-2U2A5HF4.js} +10 -4
  54. package/dist/chunk-2U2A5HF4.js.map +1 -0
  55. package/dist/{chunk-RBGL4MKQ.js → chunk-3F4N4WDH.js} +75 -110
  56. package/dist/chunk-3F4N4WDH.js.map +1 -0
  57. package/dist/{chunk-4NMATTXV.js → chunk-4BFRBP45.js} +2 -2
  58. package/dist/{chunk-L4UZBJNM.js → chunk-4P4626RY.js} +4 -4
  59. package/dist/{chunk-XZJMIJLW.js → chunk-5QZDMSSN.js} +29 -29
  60. package/dist/chunk-64UDOIMI.js +121 -0
  61. package/dist/chunk-64UDOIMI.js.map +1 -0
  62. package/dist/{chunk-H2EJNV2Z.js → chunk-66FLU5BT.js} +2 -2
  63. package/dist/{chunk-MWH5MAFP.js → chunk-6BISDEN6.js} +5 -5
  64. package/dist/{chunk-VBVSN2TV.js → chunk-6ZIETXZ2.js} +2 -2
  65. package/dist/{chunk-42BBIR33.js → chunk-7N2RUBAP.js} +2 -2
  66. package/dist/{chunk-UQI2NHTY.js → chunk-7QWMTOIF.js} +37 -37
  67. package/dist/{chunk-QICQ35K5.js → chunk-7Z2DZNWN.js} +17 -8
  68. package/dist/{chunk-QICQ35K5.js.map → chunk-7Z2DZNWN.js.map} +1 -1
  69. package/dist/{chunk-JI5AVXYO.js → chunk-AKUCU7CI.js} +5 -5
  70. package/dist/{chunk-FT37KV7R.js → chunk-BGTAL46Y.js} +2 -2
  71. package/dist/{chunk-7VFZTJ7K.js → chunk-BV2TIZ4F.js} +2 -2
  72. package/dist/{chunk-FWPHPN3V.js → chunk-CLX5VDNY.js} +5 -5
  73. package/dist/{chunk-TH2KIPP7.js → chunk-CZAA5N5S.js} +2 -2
  74. package/dist/{chunk-7BAJAXGB.js → chunk-CZAZX7JP.js} +4 -4
  75. package/dist/{chunk-APMGGGTY.js → chunk-DGYCWCQ7.js} +2 -2
  76. package/dist/{chunk-GEVIE5NV.js → chunk-DR6PEJU3.js} +4 -4
  77. package/dist/{chunk-2TCZE3C2.js → chunk-EPGWWCZF.js} +2 -2
  78. package/dist/{chunk-OUXPVWTZ.js → chunk-FLVL4L3L.js} +5 -5
  79. package/dist/{chunk-YPL66L3U.js → chunk-FZGGZAAU.js} +2 -2
  80. package/dist/{chunk-XOE5DYQL.js → chunk-HRCACVHL.js} +2 -2
  81. package/dist/{chunk-KS4PRHRD.js → chunk-IC6QX5TC.js} +2 -2
  82. package/dist/{chunk-H25TAODC.js → chunk-ILFJ7AVS.js} +4 -4
  83. package/dist/{chunk-UQMCR5RW.js → chunk-JLR3JSTL.js} +3 -3
  84. package/dist/{chunk-5H56JAHW.js → chunk-KCJ5H6XQ.js} +2 -2
  85. package/dist/{chunk-H5IT5JL7.js → chunk-LOR6ZM5R.js} +3 -3
  86. package/dist/{chunk-VBBMTSOV.js → chunk-M7GGMGMZ.js} +6 -6
  87. package/dist/{chunk-G47FEGGK.js → chunk-NG7YBCJO.js} +4 -4
  88. package/dist/{chunk-QGS7Z7YU.js → chunk-OHDJ34CC.js} +2 -2
  89. package/dist/{chunk-UAOZJX5J.js → chunk-P6SYWE4G.js} +53 -6
  90. package/dist/chunk-P6SYWE4G.js.map +1 -0
  91. package/dist/{chunk-LLMZK53N.js → chunk-PUBTHW5O.js} +2 -2
  92. package/dist/{chunk-AGNBY3VG.js → chunk-PYHOAXTI.js} +2 -2
  93. package/dist/{chunk-SXN3LCUT.js → chunk-RHOZ5LLO.js} +100 -4
  94. package/dist/chunk-RHOZ5LLO.js.map +1 -0
  95. package/dist/{chunk-6KYMPV2O.js → chunk-RL6NEVXT.js} +45 -2
  96. package/dist/chunk-RL6NEVXT.js.map +1 -0
  97. package/dist/{chunk-7WV3F5DQ.js → chunk-V7IZKJI3.js} +2 -2
  98. package/dist/{chunk-IGF4DII6.js → chunk-X4A62ET7.js} +9 -9
  99. package/dist/{chunk-OH6BKCPB.js → chunk-XN3TUUX2.js} +2 -2
  100. package/dist/{chunk-5K5VEDFY.js → chunk-YDSWRD33.js} +4 -4
  101. package/dist/{chunk-ABGDGBDT.js → chunk-YPEMNNNY.js} +2 -2
  102. package/dist/{chunk-YM7IEUO7.js → chunk-ZPOROUTX.js} +2 -2
  103. package/dist/{cli-Dfnc66gs.d.ts → cli-EiBBOt3r.d.ts} +3 -3
  104. package/dist/cli.d.ts +6 -6
  105. package/dist/cli.js +51 -50
  106. package/dist/compounding/engine.d.ts +1 -1
  107. package/dist/compounding/engine.js +5 -5
  108. package/dist/compounding/preference-consolidator.d.ts +1 -1
  109. package/dist/compression-optimizer.d.ts +1 -1
  110. package/dist/config.d.ts +1 -1
  111. package/dist/config.js +1 -1
  112. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  113. package/dist/connectors/codex-materialize-runner.js +5 -5
  114. package/dist/connectors/codex-materialize.d.ts +1 -1
  115. package/dist/connectors/index.d.ts +1 -1
  116. package/dist/connectors/index.js +5 -5
  117. package/dist/consolidation-provenance-check.d.ts +1 -1
  118. package/dist/consolidation-undo.d.ts +1 -1
  119. package/dist/contradiction/index.d.ts +1 -1
  120. package/dist/conversation-index/backend.d.ts +1 -1
  121. package/dist/conversation-index/backend.js +2 -2
  122. package/dist/conversation-index/chunker.d.ts +1 -1
  123. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  124. package/dist/conversation-index/indexer.d.ts +1 -1
  125. package/dist/conversation-index/search.d.ts +1 -1
  126. package/dist/day-summary.d.ts +1 -1
  127. package/dist/delinearize.d.ts +1 -1
  128. package/dist/direct-answer-wiring.d.ts +1 -1
  129. package/dist/direct-answer.d.ts +1 -1
  130. package/dist/embedding-fallback.d.ts +1 -1
  131. package/dist/enrichment/index.d.ts +1 -1
  132. package/dist/entity-retrieval.d.ts +1 -1
  133. package/dist/entity-retrieval.js +5 -5
  134. package/dist/entity-schema.d.ts +1 -1
  135. package/dist/explicit-capture.d.ts +4 -4
  136. package/dist/extraction-faithfulness.d.ts +1 -1
  137. package/dist/extraction-judge-telemetry.d.ts +1 -1
  138. package/dist/extraction-judge-training.d.ts +1 -1
  139. package/dist/extraction-judge.d.ts +1 -1
  140. package/dist/extraction.d.ts +1 -1
  141. package/dist/fallback-llm.d.ts +1 -1
  142. package/dist/graph-dashboard-diff.d.ts +1 -1
  143. package/dist/graph-dashboard-key.d.ts +1 -1
  144. package/dist/graph-dashboard-parser.d.ts +1 -1
  145. package/dist/graph-edge-reinforcement.d.ts +1 -1
  146. package/dist/graph-snapshot.d.ts +1 -1
  147. package/dist/graph.d.ts +1 -1
  148. package/dist/identity-continuity.d.ts +1 -1
  149. package/dist/importance.d.ts +1 -1
  150. package/dist/index.d.ts +17 -12
  151. package/dist/index.js +79 -78
  152. package/dist/intent.d.ts +1 -1
  153. package/dist/lcm/engine.d.ts +1 -1
  154. package/dist/lcm/engine.js +4 -4
  155. package/dist/lcm/index.d.ts +1 -1
  156. package/dist/lcm/index.js +6 -6
  157. package/dist/lcm/schema.js +2 -2
  158. package/dist/lcm/tools.d.ts +1 -1
  159. package/dist/lifecycle.d.ts +1 -1
  160. package/dist/live-connectors-runner.d.ts +1 -1
  161. package/dist/local-llm.d.ts +1 -1
  162. package/dist/local-model-endpoint.d.ts +1 -1
  163. package/dist/maintenance/memory-governance.d.ts +1 -1
  164. package/dist/maintenance/memory-governance.js +5 -5
  165. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +5 -5
  166. package/dist/maintenance/rebuild-memory-projection.js +6 -6
  167. package/dist/mcp-memory-inspector-app.d.ts +5 -5
  168. package/dist/memory-action-policy.d.ts +1 -1
  169. package/dist/memory-cache.d.ts +1 -1
  170. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  171. package/dist/memory-projection-store.d.ts +23 -3
  172. package/dist/memory-projection-store.js +9 -2
  173. package/dist/memory-provenance.d.ts +1 -1
  174. package/dist/memory-worth-outcomes.d.ts +1 -1
  175. package/dist/models-json.d.ts +1 -1
  176. package/dist/namespaces/migrate.d.ts +2 -2
  177. package/dist/namespaces/migrate.js +17 -16
  178. package/dist/namespaces/principal.d.ts +1 -1
  179. package/dist/namespaces/search.d.ts +1 -1
  180. package/dist/namespaces/search.js +11 -10
  181. package/dist/namespaces/storage.d.ts +2 -2
  182. package/dist/namespaces/storage.js +5 -5
  183. package/dist/native-knowledge.d.ts +1 -1
  184. package/dist/operator-toolkit.d.ts +11 -2
  185. package/dist/operator-toolkit.js +23 -20
  186. package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
  187. package/dist/orchestration/maintenance.d.ts +2 -2
  188. package/dist/orchestration/maintenance.js +7 -7
  189. package/dist/{orchestrator-Y9wyG4jK.d.ts → orchestrator-B2Y28Z9t.d.ts} +3 -3
  190. package/dist/orchestrator.d.ts +4 -4
  191. package/dist/orchestrator.js +63 -62
  192. package/dist/patterns-cli.d.ts +1 -1
  193. package/dist/policy-runtime.d.ts +1 -1
  194. package/dist/provenance.d.ts +1 -1
  195. package/dist/{purge-IHRX4Z7X.js → purge-NDWV6WYD.js} +4 -3
  196. package/dist/{purge-IHRX4Z7X.js.map → purge-NDWV6WYD.js.map} +1 -1
  197. package/dist/qmd-Db9mOkzB.d.ts +330 -0
  198. package/dist/qmd-preflight.d.ts +5 -0
  199. package/dist/qmd-preflight.js +31 -0
  200. package/dist/qmd-preflight.js.map +1 -0
  201. package/dist/qmd-recall-cache.d.ts +1 -1
  202. package/dist/qmd.d.ts +3 -305
  203. package/dist/qmd.js +10 -8
  204. package/dist/recall-disclosure-escalation.d.ts +1 -1
  205. package/dist/recall-explain-renderer.d.ts +1 -1
  206. package/dist/recall-planner-llm.d.ts +1 -1
  207. package/dist/recall-state.d.ts +1 -1
  208. package/dist/recall-tag-filter.d.ts +1 -1
  209. package/dist/recall-timings.d.ts +1 -1
  210. package/dist/recall-xray-cli.d.ts +1 -1
  211. package/dist/recall-xray-renderer.d.ts +1 -1
  212. package/dist/recall-xray.d.ts +1 -1
  213. package/dist/resolve-auth-token.d.ts +1 -1
  214. package/dist/resume-bundles.js +2 -2
  215. package/dist/retrieval-agents.d.ts +2 -2
  216. package/dist/retrieval-tiers.d.ts +1 -1
  217. package/dist/routing/engine.d.ts +1 -1
  218. package/dist/routing/store.d.ts +1 -1
  219. package/dist/runtime/better-sqlite.d.ts +17 -1
  220. package/dist/runtime/better-sqlite.js +5 -3
  221. package/dist/schemas.d.ts +84 -84
  222. package/dist/search/embed-helper.d.ts +1 -1
  223. package/dist/search/factory.d.ts +1 -1
  224. package/dist/search/factory.js +10 -9
  225. package/dist/search/index.d.ts +1 -1
  226. package/dist/search/index.js +15 -14
  227. package/dist/search/lancedb-backend.d.ts +1 -1
  228. package/dist/search/lancedb-backend.js +2 -2
  229. package/dist/search/meilisearch-backend.d.ts +1 -1
  230. package/dist/search/meilisearch-backend.js +2 -2
  231. package/dist/search/noop-backend.d.ts +1 -1
  232. package/dist/search/orama-backend.d.ts +1 -1
  233. package/dist/search/orama-backend.js +2 -2
  234. package/dist/search/port.d.ts +1 -1
  235. package/dist/search/remote-backend.d.ts +1 -1
  236. package/dist/{semantic-consolidation-bfXJNKVf.d.ts → semantic-consolidation-BDVIbLRw.d.ts} +1 -1
  237. package/dist/semantic-consolidation.d.ts +2 -2
  238. package/dist/semantic-consolidation.js +6 -6
  239. package/dist/semantic-rule-promotion.js +5 -5
  240. package/dist/semantic-rule-verifier.d.ts +1 -1
  241. package/dist/semantic-rule-verifier.js +5 -5
  242. package/dist/session-observer-bands.d.ts +1 -1
  243. package/dist/session-observer-state.d.ts +1 -1
  244. package/dist/session-transcript-migration.js +2 -2
  245. package/dist/shared-context/manager.d.ts +9 -9
  246. package/dist/signal.d.ts +1 -1
  247. package/dist/storage.d.ts +1 -1
  248. package/dist/storage.js +4 -4
  249. package/dist/summarizer.d.ts +1 -1
  250. package/dist/summary-snapshot.d.ts +1 -1
  251. package/dist/temporal-supersession.d.ts +1 -1
  252. package/dist/temporal-validity.d.ts +1 -1
  253. package/dist/threading.d.ts +1 -1
  254. package/dist/tier-migration.d.ts +1 -1
  255. package/dist/tier-routing.d.ts +1 -1
  256. package/dist/topics.d.ts +1 -1
  257. package/dist/transcript.d.ts +1 -1
  258. package/dist/transfer/autodetect.js +1 -1
  259. package/dist/transfer/backup.js +1 -1
  260. package/dist/transfer/capsule-export.js +2 -2
  261. package/dist/transfer/export-sqlite.js +2 -2
  262. package/dist/transfer/import-sqlite.js +2 -2
  263. package/dist/transfer/types.d.ts +66 -66
  264. package/dist/trust-score-stage.d.ts +1 -1
  265. package/dist/trust-score.d.ts +1 -1
  266. package/dist/{types-D1iBqhJA.d.ts → types-ljkd82rn.d.ts} +13 -1
  267. package/dist/types.d.ts +1 -1
  268. package/dist/utility-runtime.d.ts +1 -1
  269. package/dist/verified-recall.js +5 -5
  270. package/package.json +2 -2
  271. package/src/memory-projection-open-failure.test.ts +322 -0
  272. package/src/memory-projection-store.ts +179 -2
  273. package/src/operator-toolkit.ts +64 -0
  274. package/src/qmd-preflight.test.ts +197 -0
  275. package/src/qmd-preflight.ts +167 -0
  276. package/src/qmd.ts +115 -129
  277. package/src/runtime/better-sqlite.test.ts +54 -0
  278. package/src/runtime/better-sqlite.ts +83 -3
  279. package/src/wearables/cleanup.test.ts +56 -0
  280. package/src/wearables/cleanup.ts +22 -1
  281. package/src/wearables/config.test.ts +25 -0
  282. package/src/wearables/config.ts +31 -4
  283. package/src/wearables/types.ts +12 -1
  284. package/dist/chunk-64ZYXSCO.js.map +0 -1
  285. package/dist/chunk-6KYMPV2O.js.map +0 -1
  286. package/dist/chunk-RBGL4MKQ.js.map +0 -1
  287. package/dist/chunk-SXN3LCUT.js.map +0 -1
  288. package/dist/chunk-UAOZJX5J.js.map +0 -1
  289. /package/dist/{auto-sync-M47VTCFI.js.map → auto-sync-HMHQHUAG.js.map} +0 -0
  290. /package/dist/{chunk-TKUQSXU4.js.map → chunk-27FLVMDC.js.map} +0 -0
  291. /package/dist/{chunk-XXEOGY77.js.map → chunk-2FOD2YY3.js.map} +0 -0
  292. /package/dist/{chunk-6Y4JTNX7.js.map → chunk-2MR3MFQB.js.map} +0 -0
  293. /package/dist/{chunk-AU7Q3LSC.js.map → chunk-2QSZNTDO.js.map} +0 -0
  294. /package/dist/{chunk-4NMATTXV.js.map → chunk-4BFRBP45.js.map} +0 -0
  295. /package/dist/{chunk-L4UZBJNM.js.map → chunk-4P4626RY.js.map} +0 -0
  296. /package/dist/{chunk-XZJMIJLW.js.map → chunk-5QZDMSSN.js.map} +0 -0
  297. /package/dist/{chunk-H2EJNV2Z.js.map → chunk-66FLU5BT.js.map} +0 -0
  298. /package/dist/{chunk-MWH5MAFP.js.map → chunk-6BISDEN6.js.map} +0 -0
  299. /package/dist/{chunk-VBVSN2TV.js.map → chunk-6ZIETXZ2.js.map} +0 -0
  300. /package/dist/{chunk-42BBIR33.js.map → chunk-7N2RUBAP.js.map} +0 -0
  301. /package/dist/{chunk-UQI2NHTY.js.map → chunk-7QWMTOIF.js.map} +0 -0
  302. /package/dist/{chunk-JI5AVXYO.js.map → chunk-AKUCU7CI.js.map} +0 -0
  303. /package/dist/{chunk-FT37KV7R.js.map → chunk-BGTAL46Y.js.map} +0 -0
  304. /package/dist/{chunk-7VFZTJ7K.js.map → chunk-BV2TIZ4F.js.map} +0 -0
  305. /package/dist/{chunk-FWPHPN3V.js.map → chunk-CLX5VDNY.js.map} +0 -0
  306. /package/dist/{chunk-TH2KIPP7.js.map → chunk-CZAA5N5S.js.map} +0 -0
  307. /package/dist/{chunk-7BAJAXGB.js.map → chunk-CZAZX7JP.js.map} +0 -0
  308. /package/dist/{chunk-APMGGGTY.js.map → chunk-DGYCWCQ7.js.map} +0 -0
  309. /package/dist/{chunk-GEVIE5NV.js.map → chunk-DR6PEJU3.js.map} +0 -0
  310. /package/dist/{chunk-2TCZE3C2.js.map → chunk-EPGWWCZF.js.map} +0 -0
  311. /package/dist/{chunk-OUXPVWTZ.js.map → chunk-FLVL4L3L.js.map} +0 -0
  312. /package/dist/{chunk-YPL66L3U.js.map → chunk-FZGGZAAU.js.map} +0 -0
  313. /package/dist/{chunk-XOE5DYQL.js.map → chunk-HRCACVHL.js.map} +0 -0
  314. /package/dist/{chunk-KS4PRHRD.js.map → chunk-IC6QX5TC.js.map} +0 -0
  315. /package/dist/{chunk-H25TAODC.js.map → chunk-ILFJ7AVS.js.map} +0 -0
  316. /package/dist/{chunk-UQMCR5RW.js.map → chunk-JLR3JSTL.js.map} +0 -0
  317. /package/dist/{chunk-5H56JAHW.js.map → chunk-KCJ5H6XQ.js.map} +0 -0
  318. /package/dist/{chunk-H5IT5JL7.js.map → chunk-LOR6ZM5R.js.map} +0 -0
  319. /package/dist/{chunk-VBBMTSOV.js.map → chunk-M7GGMGMZ.js.map} +0 -0
  320. /package/dist/{chunk-G47FEGGK.js.map → chunk-NG7YBCJO.js.map} +0 -0
  321. /package/dist/{chunk-QGS7Z7YU.js.map → chunk-OHDJ34CC.js.map} +0 -0
  322. /package/dist/{chunk-LLMZK53N.js.map → chunk-PUBTHW5O.js.map} +0 -0
  323. /package/dist/{chunk-AGNBY3VG.js.map → chunk-PYHOAXTI.js.map} +0 -0
  324. /package/dist/{chunk-7WV3F5DQ.js.map → chunk-V7IZKJI3.js.map} +0 -0
  325. /package/dist/{chunk-IGF4DII6.js.map → chunk-X4A62ET7.js.map} +0 -0
  326. /package/dist/{chunk-OH6BKCPB.js.map → chunk-XN3TUUX2.js.map} +0 -0
  327. /package/dist/{chunk-5K5VEDFY.js.map → chunk-YDSWRD33.js.map} +0 -0
  328. /package/dist/{chunk-ABGDGBDT.js.map → chunk-YPEMNNNY.js.map} +0 -0
  329. /package/dist/{chunk-YM7IEUO7.js.map → chunk-ZPOROUTX.js.map} +0 -0
@@ -0,0 +1,197 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { abortError } from "./abort-error.js";
4
+ import { QmdClient } from "./qmd.js";
5
+
6
+ /**
7
+ * Internals exposed only for preflight testing. Cast once to a NAMED type (not
8
+ * an inline shape) so the compiler never fabricates a one-off member type; the
9
+ * unchecked access is scoped to these deliberately-injected seams. Issue #1841.
10
+ */
11
+ type QmdPreflightInternals = {
12
+ configuredQmdPath: string | undefined;
13
+ runVersionProbe: (qmdPath: string, signal?: AbortSignal) => Promise<{ stdout: string; stderr: string }>;
14
+ logCliProbeWarning: (message: string) => void;
15
+ probeCli: (options?: {
16
+ allowAutoUpgrade?: boolean;
17
+ preserveStateOnFailure?: boolean;
18
+ signal?: AbortSignal;
19
+ }) => Promise<boolean>;
20
+ };
21
+
22
+ const CONFIGURED_PATH = "/opt/qmd/bin/qmd";
23
+
24
+ /** Build the Node spawn ENOENT error shape (Error carrying `code: "ENOENT"`). */
25
+ function spawnENOENT(qmdPath: string): Error & { code: string } {
26
+ return Object.assign(new Error(`spawn ${qmdPath} ENOENT`), {
27
+ code: "ENOENT" as const,
28
+ });
29
+ }
30
+
31
+ function timeoutError(): Error & { timedOut: true } {
32
+ // Mirrors the error runCommandWithTimeout rejects with on deadline: a plain
33
+ // Error flagged with `timedOut: true` — the structured signal classifyProbeFailure
34
+ // keys on (never the message text). Issue #1841.
35
+ return Object.assign(new Error("qmd --version timed out after 8000ms"), {
36
+ timedOut: true as const,
37
+ });
38
+ }
39
+
40
+ /** Wire a fresh client whose probe runner + warning sink are fully scripted. */
41
+ function harness(opts: {
42
+ configuredBehavior: () => Promise<{ stdout: string; stderr: string }>;
43
+ }): {
44
+ internals: QmdPreflightInternals;
45
+ warnings: string[];
46
+ configuredProbeCalls: () => number;
47
+ } {
48
+ const client = new QmdClient("memories", 3, {
49
+ qmdPath: CONFIGURED_PATH,
50
+ qmdFallbackPaths: [],
51
+ });
52
+ const internals = client as unknown as QmdPreflightInternals;
53
+ const warnings: string[] = [];
54
+ internals.logCliProbeWarning = (message: string) => {
55
+ warnings.push(message);
56
+ };
57
+ let configuredCalls = 0;
58
+ internals.runVersionProbe = async (qmdPath: string) => {
59
+ if (qmdPath === CONFIGURED_PATH) {
60
+ configuredCalls += 1;
61
+ return opts.configuredBehavior();
62
+ }
63
+ // Any non-configured path (PATH/fallback probing) is a hard miss here so
64
+ // probeCli returns false without spawning a real qmd binary.
65
+ throw spawnENOENT(qmdPath);
66
+ };
67
+ return {
68
+ internals,
69
+ warnings,
70
+ configuredProbeCalls: () => configuredCalls,
71
+ };
72
+ }
73
+
74
+ test("QmdClient preflight: transient timeout retries then succeeds with NO failure warning", async () => {
75
+ // Two timeouts, then success on the second retry.
76
+ const scripted: Array<"timeout" | "ok"> = ["timeout", "timeout", "ok"];
77
+ let i = 0;
78
+ const { internals, warnings, configuredProbeCalls } = harness({
79
+ configuredBehavior: async () => {
80
+ const step = scripted[i++];
81
+ if (step === "timeout") throw timeoutError();
82
+ return { stdout: "qmd 1.2.3\n", stderr: "" };
83
+ },
84
+ });
85
+
86
+ const ok = await internals.probeCli({ allowAutoUpgrade: false });
87
+
88
+ assert.equal(ok, true, "configured probe should succeed after retry");
89
+ assert.equal(configuredProbeCalls(), 3, "initial attempt + 2 retries for transient timeout");
90
+ assert.deepEqual(warnings, [], "no failure warning emitted when retry succeeds");
91
+ });
92
+
93
+ test("QmdClient preflight: persistent timeout emits the distinct under-load warning (not 'configured qmdPath failed')", async () => {
94
+ const { internals, warnings, configuredProbeCalls } = harness({
95
+ configuredBehavior: async () => {
96
+ throw timeoutError();
97
+ },
98
+ });
99
+
100
+ const ok = await internals.probeCli({ allowAutoUpgrade: false });
101
+
102
+ assert.equal(ok, false, "exhausted retries fall through to unavailable");
103
+ assert.equal(configuredProbeCalls(), 3, "initial attempt + 2 retries before declaring transient failure");
104
+ assert.equal(warnings.length, 1, "exactly one failure warning");
105
+ const msg = warnings[0] ?? "";
106
+ assert.match(
107
+ msg,
108
+ /version check timed out \(host may be under load\); retried 2 times/,
109
+ "transient failure uses the under-load wording"
110
+ );
111
+ assert.doesNotMatch(msg, /configured qmdPath failed/, "must not read as a generic misconfiguration");
112
+ });
113
+
114
+ test("QmdClient preflight: genuine ENOENT fails fast (no retry) with the not-found/not-executable warning", async () => {
115
+ const { internals, warnings, configuredProbeCalls } = harness({
116
+ configuredBehavior: async () => {
117
+ throw spawnENOENT(CONFIGURED_PATH);
118
+ },
119
+ });
120
+
121
+ const ok = await internals.probeCli({ allowAutoUpgrade: false });
122
+
123
+ assert.equal(ok, false, "missing binary is unavailable");
124
+ assert.equal(configuredProbeCalls(), 1, "ENOENT is a hard misconfiguration: never retried");
125
+ assert.equal(warnings.length, 1, "exactly one failure warning");
126
+ const msg = warnings[0] ?? "";
127
+ assert.match(msg, /configured qmdPath not found or not executable/, "missing binary uses the not-found wording");
128
+ assert.match(msg, /spawn .* ENOENT/, "underlying error detail preserved");
129
+ assert.doesNotMatch(msg, /timed out|under load/, "must not read as a transient timeout");
130
+ });
131
+
132
+ test("QmdClient preflight: non-timeout exit-code failure keeps the generic configured-failed message and does not retry", async () => {
133
+ const { internals, warnings, configuredProbeCalls } = harness({
134
+ configuredBehavior: async () => {
135
+ throw new Error("qmd --version exited with code 2");
136
+ },
137
+ });
138
+
139
+ const ok = await internals.probeCli({ allowAutoUpgrade: false });
140
+
141
+ assert.equal(ok, false, "exit-code failure is unavailable");
142
+ assert.equal(configuredProbeCalls(), 1, "non-transient, non-missing failures are not retried");
143
+ assert.equal(warnings.length, 1, "exactly one failure warning");
144
+ const msg = warnings[0] ?? "";
145
+ assert.match(msg, /configured qmdPath failed/, "ambiguous exit-code failures keep the historical generic wording");
146
+ assert.doesNotMatch(
147
+ msg,
148
+ /timed out|under load|not found or not executable/,
149
+ "must not be misclassified as timeout or missing"
150
+ );
151
+ });
152
+
153
+ test("QmdClient preflight: caller abort emits NO warning and does not retry", async () => {
154
+ // The caller cancelled (aborted signal + AbortError). Other QMD paths treat
155
+ // this as non-actionable noise via isCallerCancellation; the configured-path
156
+ // probe must do the same — no under-load warning, no transient retry. #1841.
157
+ const controller = new AbortController();
158
+ controller.abort();
159
+ const { internals, warnings, configuredProbeCalls } = harness({
160
+ configuredBehavior: async () => {
161
+ // A real probe rejects with an AbortError when the caller's signal fires.
162
+ throw abortError("qmd --version aborted");
163
+ },
164
+ });
165
+
166
+ const ok = await internals.probeCli({ allowAutoUpgrade: false, signal: controller.signal });
167
+
168
+ assert.equal(ok, false, "aborted configured probe falls through to unavailable");
169
+ assert.equal(configuredProbeCalls(), 1, "caller cancellation never retries");
170
+ assert.deepEqual(warnings, [], "caller cancellation is silent noise: no operator-facing warning");
171
+ });
172
+
173
+ test("QmdClient preflight: non-zero exit whose stderr says 'timed out'/'not found' is classified 'other' (not transient/missing)", async () => {
174
+ // Mirrors runCommandWithTimeout's non-zero-exit rejection: the child's stderr
175
+ // (here containing "timed out" and "not found") is embedded in the message.
176
+ // A healthy binary can emit those words in error text; they must NOT upgrade
177
+ // the failure to transient/missing. classifyProbeFailure keys on structured
178
+ // signals, so this generic exit-code failure stays 'other'. Issue #1841.
179
+ const { internals, warnings, configuredProbeCalls } = harness({
180
+ configuredBehavior: async () => {
181
+ throw new Error("qmd --version failed (code 2): error: model timed out while loading; config not found");
182
+ },
183
+ });
184
+
185
+ const ok = await internals.probeCli({ allowAutoUpgrade: false });
186
+
187
+ assert.equal(ok, false, "exit-code failure is unavailable");
188
+ assert.equal(configuredProbeCalls(), 1, "embedded-stderr failure is not transient — no retry");
189
+ assert.equal(warnings.length, 1, "exactly one failure warning");
190
+ const msg = warnings[0] ?? "";
191
+ assert.match(msg, /configured qmdPath failed/, "classified as generic 'other'");
192
+ assert.doesNotMatch(
193
+ msg,
194
+ /under load|not found or not executable/,
195
+ "must not misclassify embedded-stderr words as transient/missing"
196
+ );
197
+ });
@@ -0,0 +1,167 @@
1
+ /**
2
+ * QMD version-check PREFLIGHT / probe-classification helpers.
3
+ *
4
+ * Pure, self-contained functions extracted from qmd.ts (issue #1841) so the
5
+ * QmdClient class file stays under the structural-ratchet LOC ceiling. These
6
+ * cover two related concerns of the startup preflight:
7
+ *
8
+ * 1. VERSION parsing/comparison — turning `qmd --version` output into a tuple,
9
+ * comparing against the supported target, and resolving feature capability
10
+ * flags from the parsed version.
11
+ * 2. PROBE failure classification — distinguishing a slow/overloaded binary
12
+ * (transient → retry) from a genuine misconfiguration (ENOENT/EACCES → fail
13
+ * fast) using STRUCTURED signals, never the error message.
14
+ *
15
+ * No behavioral change from qmd.ts round 1 (issue #1841): structured-signal
16
+ * classification (code=ENOENT/EACCES=>missing, timedOut flag=>transient,
17
+ * caller-cancel silenced, non-zero-exit=>other); backoff [300,800].
18
+ */
19
+ import { isAbortError } from "./abort-error.js";
20
+ import type { QmdCapabilities, QmdVersionTuple } from "./qmd.js";
21
+
22
+ // Deadline (ms) for a single `qmd --version` probe.
23
+ export const QMD_PROBE_TIMEOUT_MS = 8_000;
24
+
25
+ // Backoff schedule (ms) for retrying a TRANSIENT (timeout/abort) version-check
26
+ // probe of a CONFIGURED qmdPath before declaring it failed. A binary that
27
+ // resolved and ran before is more likely slow under load than gone, so we retry
28
+ // transient failures but NOT ENOENT/EACCES (hard misconfiguration, fail fast).
29
+ // Length == number of retries after the initial attempt. Issue #1841.
30
+ export const QMD_PROBE_RETRY_BACKOFF_MS = [300, 800];
31
+
32
+ export const QMD_SUPPORTED_VERSION = "2.5.3";
33
+
34
+ /**
35
+ * Classify a `qmd --version` probe failure so the preflight can distinguish a
36
+ * slow/overloaded binary (a genuine deadline timeout) from a real
37
+ * misconfiguration (ENOENT/EACCES — the configured path is missing or not
38
+ * executable). Only transient failures are retried; missing binaries fail fast.
39
+ *
40
+ * Classification keys on STRUCTURED signals, never the error *message*: a
41
+ * non-zero-exit failure embeds the child's stderr in its message, so scanning
42
+ * that text for "abort"/"timed out"/"not found" would misclassify a healthy
43
+ * binary. The structured signals are: `code` (ENOENT/EACCES) for missing; the
44
+ * `timedOut` flag set by `runCommandWithTimeout` for a genuine timeout; an
45
+ * AbortError name for a deadline/abort under load. Issue #1841.
46
+ */
47
+ export type QmdProbeFailureKind = "transient" | "missing" | "other";
48
+ export function classifyProbeFailure(err: unknown): QmdProbeFailureKind {
49
+ if (err && typeof err === "object") {
50
+ // Node spawn failures carry a structured `code`: ENOENT (not found) /
51
+ // EACCES (not executable).
52
+ if ("code" in err) {
53
+ const code = err.code;
54
+ if (code === "ENOENT" || code === "EACCES") return "missing";
55
+ }
56
+ // runCommandWithTimeout flags a genuine deadline breach with `timedOut`.
57
+ if ("timedOut" in err && err.timedOut === true) return "transient";
58
+ }
59
+ // An AbortError reaching here is a deadline/abort under load → transient.
60
+ // (Caller cancellation is intercepted by the retry loop before this runs.)
61
+ if (isAbortError(err)) return "transient";
62
+ // Anything else — including a non-zero exit whose message embeds stderr — is
63
+ // a generic failure: never scan the message to upgrade/downgrade it.
64
+ return "other";
65
+ }
66
+
67
+ export function parseQmdVersion(version: string | null): QmdVersionTuple | null {
68
+ if (!version) return null;
69
+ const match = version.match(/v?(\d{1,10})\.(\d{1,10})\.(\d{1,10})/i);
70
+ if (!match) return null;
71
+ return [
72
+ Number.parseInt(match[1] ?? "0", 10),
73
+ Number.parseInt(match[2] ?? "0", 10),
74
+ Number.parseInt(match[3] ?? "0", 10),
75
+ ];
76
+ }
77
+
78
+ export function parseQmdVersionOutput(stdout: string, stderr: string): string | null {
79
+ const lines = `${stdout}\n${stderr}`
80
+ .split("\n")
81
+ .map((s) => s.trim())
82
+ .filter((s) => s.length > 0);
83
+ if (lines.length === 0) return null;
84
+ const semanticLines = lines.filter((line) => parseQmdVersion(line) !== null);
85
+ if (semanticLines.length === 0) return lines[0] ?? null;
86
+ return semanticLines.find((line) => /\bqmd\b/i.test(line)) ?? semanticLines[0] ?? null;
87
+ }
88
+
89
+ export function compareQmdVersions(left: QmdVersionTuple | null, right: QmdVersionTuple | null): number {
90
+ if (!left && !right) return 0;
91
+ if (!left) return -1;
92
+ if (!right) return 1;
93
+ for (let i = 0; i < 3; i += 1) {
94
+ if ((left[i] ?? 0) > (right[i] ?? 0)) return 1;
95
+ if ((left[i] ?? 0) < (right[i] ?? 0)) return -1;
96
+ }
97
+ return 0;
98
+ }
99
+
100
+ export function versionAtLeast(current: QmdVersionTuple | null, target: QmdVersionTuple): boolean {
101
+ return compareQmdVersions(current, target) >= 0;
102
+ }
103
+
104
+ export function resolveQmdCapabilities(version: string | null): QmdCapabilities {
105
+ const parsedVersion = parseQmdVersion(version);
106
+ const atLeast = (target: QmdVersionTuple): boolean => versionAtLeast(parsedVersion, target);
107
+ return {
108
+ version,
109
+ parsedVersion,
110
+ stableSdk: atLeast([2, 0, 0]),
111
+ unifiedSearch: atLeast([2, 0, 0]),
112
+ getDocumentBody: atLeast([2, 0, 0]),
113
+ maintenanceApi: atLeast([2, 0, 0]),
114
+ legacySkillInstall: atLeast([2, 0, 1]),
115
+ intentHints: atLeast([1, 1, 5]),
116
+ explainTraces: atLeast([1, 1, 2]),
117
+ candidateLimit: atLeast([1, 1, 2]),
118
+ v2McpQueryTool: atLeast([2, 0, 0]),
119
+ structuredSearches: atLeast([2, 0, 0]),
120
+ queryRerankToggle: atLeast([2, 1, 0]),
121
+ chunkStrategy: atLeast([2, 1, 0]),
122
+ qmdBench: atLeast([2, 1, 0]),
123
+ perCollectionModels: atLeast([2, 1, 0]),
124
+ jsonLineNumbers: atLeast([2, 1, 0]),
125
+ editorLinks: atLeast([2, 1, 0]),
126
+ doctor: atLeast([2, 5, 0]),
127
+ versionedSkills: atLeast([2, 5, 0]),
128
+ absoluteSnippetLines: atLeast([2, 5, 0]),
129
+ fullQueryOutput: atLeast([2, 5, 0]),
130
+ forceCpu: atLeast([2, 5, 0]),
131
+ gpuBackendOverride: atLeast([2, 5, 0]),
132
+ embedParallelism: atLeast([2, 5, 0]),
133
+ modelEnvConsistency: atLeast([2, 5, 0]),
134
+ scopedEmbed: atLeast([2, 5, 0]),
135
+ safeStatusDeviceProbe: atLeast([2, 5, 0]),
136
+ mcpIndexSelection: atLeast([2, 5, 0]),
137
+ outputFormatFlag: atLeast([2, 5, 3]),
138
+ };
139
+ }
140
+
141
+ export function shouldAutoUpgradeQmd(
142
+ installedVersion: string | null,
143
+ supportedVersion: string = QMD_SUPPORTED_VERSION
144
+ ): boolean {
145
+ const installed = parseQmdVersion(installedVersion);
146
+ const supported = parseQmdVersion(supportedVersion);
147
+ if (!installed || !supported) return false;
148
+ return compareQmdVersions(installed, supported) < 0;
149
+ }
150
+
151
+ export function getQmdPostInstallProbeTargets(
152
+ qmdPath: string,
153
+ qmdPathSource: "configured" | "auto-path" | "auto-fallback"
154
+ ): Array<{ qmdPath: string; source: "auto-path" | "auto-fallback" }> {
155
+ const targets: Array<{ qmdPath: string; source: "auto-path" | "auto-fallback" }> = [
156
+ { qmdPath: "qmd", source: "auto-path" },
157
+ ];
158
+ const normalizedPath = qmdPath.trim();
159
+ if (qmdPathSource === "auto-fallback" && normalizedPath.length > 0 && normalizedPath !== "qmd") {
160
+ targets.push({ qmdPath: normalizedPath, source: "auto-fallback" });
161
+ }
162
+ return targets;
163
+ }
164
+
165
+ export function qmdVersionToString(version: QmdVersionTuple): string {
166
+ return `${version[0]}.${version[1]}.${version[2]}`;
167
+ }
package/src/qmd.ts CHANGED
@@ -18,6 +18,33 @@ import {
18
18
  } from "./search/port.js";
19
19
  import { launchProcess, type CommandChildProcess } from "./runtime/child-process.js";
20
20
  import { mergeEnv } from "./runtime/env.js";
21
+ import {
22
+ classifyProbeFailure,
23
+ compareQmdVersions,
24
+ getQmdPostInstallProbeTargets,
25
+ parseQmdVersion,
26
+ parseQmdVersionOutput,
27
+ QMD_PROBE_RETRY_BACKOFF_MS,
28
+ QMD_PROBE_TIMEOUT_MS,
29
+ QMD_SUPPORTED_VERSION,
30
+ qmdVersionToString,
31
+ resolveQmdCapabilities,
32
+ shouldAutoUpgradeQmd,
33
+ versionAtLeast,
34
+ type QmdProbeFailureKind,
35
+ } from "./qmd-preflight.js";
36
+ // Re-export version-check / probe-preflight helpers (moved to qmd-preflight.ts,
37
+ // issue #1841) so existing imports from "./qmd.js" keep resolving.
38
+ export {
39
+ compareQmdVersions,
40
+ getQmdPostInstallProbeTargets,
41
+ parseQmdVersion,
42
+ parseQmdVersionOutput,
43
+ QMD_SUPPORTED_VERSION,
44
+ resolveQmdCapabilities,
45
+ shouldAutoUpgradeQmd,
46
+ versionAtLeast,
47
+ };
21
48
 
22
49
  export interface QmdClientOptions {
23
50
  slowLog?: { enabled: boolean; thresholdMs: number };
@@ -133,11 +160,9 @@ const QMD_TIMEOUT_MS = 30_000;
133
160
  // `qmdDaemonTimeoutMs` config knob (issue #1335), e.g. to give CPU-only HyDE
134
161
  // queries more headroom. The effective value lives in `this.daemonTimeoutMs`.
135
162
  const QMD_DAEMON_TIMEOUT_MS = 8_000;
136
- const QMD_PROBE_TIMEOUT_MS = 8_000;
137
163
  const QMD_UPDATE_BACKOFF_MS = 15 * 60 * 1000; // 15m
138
164
  const QMD_EMBED_BACKOFF_MS = 60 * 60 * 1000; // 60m
139
165
  const QMD_CLI_WARN_THROTTLE_MS = 15 * 60 * 1000; // 15m
140
- export const QMD_SUPPORTED_VERSION = "2.5.3";
141
166
  const QMD_PACKAGE_NAME = "@tobilu/qmd";
142
167
  const QMD_AUTO_UPGRADE_TIMEOUT_MS = 120_000;
143
168
  const QMD_AUTO_UPGRADE_CHECK_INTERVAL_MS = 24 * 60 * 60_000;
@@ -269,118 +294,6 @@ function isVectorDimensionMismatchError(err: unknown): boolean {
269
294
  );
270
295
  }
271
296
 
272
- export function parseQmdVersion(version: string | null): QmdVersionTuple | null {
273
- if (!version) return null;
274
- const match = version.match(/v?(\d{1,10})\.(\d{1,10})\.(\d{1,10})/i);
275
- if (!match) return null;
276
- return [
277
- Number.parseInt(match[1] ?? "0", 10),
278
- Number.parseInt(match[2] ?? "0", 10),
279
- Number.parseInt(match[3] ?? "0", 10),
280
- ];
281
- }
282
-
283
- export function parseQmdVersionOutput(stdout: string, stderr: string): string | null {
284
- const lines = `${stdout}\n${stderr}`
285
- .split("\n")
286
- .map((s) => s.trim())
287
- .filter((s) => s.length > 0);
288
- if (lines.length === 0) return null;
289
- const semanticLines = lines.filter((line) => parseQmdVersion(line) !== null);
290
- if (semanticLines.length === 0) return lines[0] ?? null;
291
- return semanticLines.find((line) => /\bqmd\b/i.test(line)) ?? semanticLines[0] ?? null;
292
- }
293
-
294
- export function compareQmdVersions(
295
- left: QmdVersionTuple | null,
296
- right: QmdVersionTuple | null,
297
- ): number {
298
- if (!left && !right) return 0;
299
- if (!left) return -1;
300
- if (!right) return 1;
301
- for (let i = 0; i < 3; i += 1) {
302
- if ((left[i] ?? 0) > (right[i] ?? 0)) return 1;
303
- if ((left[i] ?? 0) < (right[i] ?? 0)) return -1;
304
- }
305
- return 0;
306
- }
307
-
308
- export function versionAtLeast(
309
- current: QmdVersionTuple | null,
310
- target: QmdVersionTuple,
311
- ): boolean {
312
- return compareQmdVersions(current, target) >= 0;
313
- }
314
-
315
- export function resolveQmdCapabilities(version: string | null): QmdCapabilities {
316
- const parsedVersion = parseQmdVersion(version);
317
- const atLeast = (target: QmdVersionTuple): boolean => versionAtLeast(parsedVersion, target);
318
- return {
319
- version,
320
- parsedVersion,
321
- stableSdk: atLeast([2, 0, 0]),
322
- unifiedSearch: atLeast([2, 0, 0]),
323
- getDocumentBody: atLeast([2, 0, 0]),
324
- maintenanceApi: atLeast([2, 0, 0]),
325
- legacySkillInstall: atLeast([2, 0, 1]),
326
- intentHints: atLeast([1, 1, 5]),
327
- explainTraces: atLeast([1, 1, 2]),
328
- candidateLimit: atLeast([1, 1, 2]),
329
- v2McpQueryTool: atLeast([2, 0, 0]),
330
- structuredSearches: atLeast([2, 0, 0]),
331
- queryRerankToggle: atLeast([2, 1, 0]),
332
- chunkStrategy: atLeast([2, 1, 0]),
333
- qmdBench: atLeast([2, 1, 0]),
334
- perCollectionModels: atLeast([2, 1, 0]),
335
- jsonLineNumbers: atLeast([2, 1, 0]),
336
- editorLinks: atLeast([2, 1, 0]),
337
- doctor: atLeast([2, 5, 0]),
338
- versionedSkills: atLeast([2, 5, 0]),
339
- absoluteSnippetLines: atLeast([2, 5, 0]),
340
- fullQueryOutput: atLeast([2, 5, 0]),
341
- forceCpu: atLeast([2, 5, 0]),
342
- gpuBackendOverride: atLeast([2, 5, 0]),
343
- embedParallelism: atLeast([2, 5, 0]),
344
- modelEnvConsistency: atLeast([2, 5, 0]),
345
- scopedEmbed: atLeast([2, 5, 0]),
346
- safeStatusDeviceProbe: atLeast([2, 5, 0]),
347
- mcpIndexSelection: atLeast([2, 5, 0]),
348
- outputFormatFlag: atLeast([2, 5, 3]),
349
- };
350
- }
351
-
352
- export function shouldAutoUpgradeQmd(
353
- installedVersion: string | null,
354
- supportedVersion: string = QMD_SUPPORTED_VERSION,
355
- ): boolean {
356
- const installed = parseQmdVersion(installedVersion);
357
- const supported = parseQmdVersion(supportedVersion);
358
- if (!installed || !supported) return false;
359
- return compareQmdVersions(installed, supported) < 0;
360
- }
361
-
362
- export function getQmdPostInstallProbeTargets(
363
- qmdPath: string,
364
- qmdPathSource: "configured" | "auto-path" | "auto-fallback",
365
- ): Array<{ qmdPath: string; source: "auto-path" | "auto-fallback" }> {
366
- const targets: Array<{ qmdPath: string; source: "auto-path" | "auto-fallback" }> = [
367
- { qmdPath: "qmd", source: "auto-path" },
368
- ];
369
- const normalizedPath = qmdPath.trim();
370
- if (
371
- qmdPathSource === "auto-fallback" &&
372
- normalizedPath.length > 0 &&
373
- normalizedPath !== "qmd"
374
- ) {
375
- targets.push({ qmdPath: normalizedPath, source: "auto-fallback" });
376
- }
377
- return targets;
378
- }
379
-
380
- function qmdVersionToString(version: QmdVersionTuple): string {
381
- return `${version[0]}.${version[1]}.${version[2]}`;
382
- }
383
-
384
297
  function normalizeSearchOptions(options?: SearchQueryOptions): SearchQueryOptions | undefined {
385
298
  if (!options) return undefined;
386
299
  const intent = typeof options.intent === "string" ? options.intent.trim() : "";
@@ -671,7 +584,13 @@ function runCommandWithTimeout(
671
584
  settled = true;
672
585
  cleanup();
673
586
  child.kill("SIGKILL");
674
- reject(new Error(`${label} timed out after ${options.timeoutMs}ms`));
587
+ // Flag the deadline breach so classifyProbeFailure can tell a genuine
588
+ // timeout from a non-zero-exit error whose message merely embeds stderr.
589
+ reject(
590
+ Object.assign(new Error(`${label} timed out after ${options.timeoutMs}ms`), {
591
+ timedOut: true as const,
592
+ }),
593
+ );
675
594
  }, options.timeoutMs);
676
595
  const onAbort = () => {
677
596
  if (settled) return;
@@ -1437,6 +1356,18 @@ export class QmdClient implements SearchBackend {
1437
1356
  }
1438
1357
  }
1439
1358
 
1359
+ /**
1360
+ * Run a single `qmd --version` probe against `qmdPath`. Extracted as its own
1361
+ * method so tests can inject a fake probe runner that exercises the preflight
1362
+ * retry/classification paths without spawning a real qmd binary. Issue #1841.
1363
+ */
1364
+ private runVersionProbe(
1365
+ qmdPath: string,
1366
+ signal?: AbortSignal,
1367
+ ): Promise<{ stdout: string; stderr: string }> {
1368
+ return runQmd(["--version"], QMD_PROBE_TIMEOUT_MS, qmdPath, signal, this.qmdRuntimeEnv);
1369
+ }
1370
+
1440
1371
  private async probeCli(
1441
1372
  options: {
1442
1373
  allowAutoUpgrade?: boolean;
@@ -1490,24 +1421,79 @@ export class QmdClient implements SearchBackend {
1490
1421
  };
1491
1422
 
1492
1423
  if (this.configuredQmdPath) {
1493
- try {
1494
- const result = await runQmd(["--version"], QMD_PROBE_TIMEOUT_MS, this.configuredQmdPath, options.signal, this.qmdRuntimeEnv);
1495
- await recordProbeSuccess(result, this.configuredQmdPath, "configured");
1496
- return true;
1497
- } catch (err) {
1498
- markProbeFailure(err);
1499
- configuredProbeFailure = this.lastCliProbeError;
1500
- // Do not hard-fail here: fall through to PATH/fallback probing.
1501
- // This keeps recall healthy even when configured path is stale.
1502
- this.logCliProbeWarning(
1503
- `QMD: configured qmdPath failed (${this.configuredQmdPath}): ${this.lastCliProbeError}`,
1504
- );
1424
+ const configuredPath = this.configuredQmdPath;
1425
+ // Retry only TRANSIENT (timeout/abort) probes of the configured path: a
1426
+ // binary that resolved and ran before is more likely slow under load than
1427
+ // gone. ENOENT/EACCES is a hard misconfiguration and must fail fast.
1428
+ // Issue #1841.
1429
+ let failureKind: QmdProbeFailureKind = "other";
1430
+ let retries = 0;
1431
+ let callerCancelled = false;
1432
+ for (let attempt = 0; attempt <= QMD_PROBE_RETRY_BACKOFF_MS.length; attempt += 1) {
1433
+ try {
1434
+ const result = await this.runVersionProbe(configuredPath, options.signal);
1435
+ await recordProbeSuccess(result, configuredPath, "configured");
1436
+ return true;
1437
+ } catch (err) {
1438
+ markProbeFailure(err);
1439
+ configuredProbeFailure = this.lastCliProbeError;
1440
+ // Detect CALLER cancellation FIRST: an aborted signal / AbortError is
1441
+ // non-actionable noise — the caller chose to stop. Other QMD paths
1442
+ // treat it via `isCallerCancellation`; do the same here so a cancelled
1443
+ // probe never reads as a transient host-load failure. Issue #1841.
1444
+ if (isCallerCancellation(err, options.signal)) {
1445
+ callerCancelled = true;
1446
+ break;
1447
+ }
1448
+ failureKind = classifyProbeFailure(err);
1449
+ // Never retry a hard misconfiguration (missing/not-executable) — only
1450
+ // transient slowness. (Caller cancellation is handled above.)
1451
+ if (
1452
+ failureKind === "transient" &&
1453
+ attempt < QMD_PROBE_RETRY_BACKOFF_MS.length
1454
+ ) {
1455
+ try {
1456
+ await sleepWithSignal(
1457
+ QMD_PROBE_RETRY_BACKOFF_MS[attempt] ?? 500,
1458
+ options.signal,
1459
+ );
1460
+ } catch {
1461
+ // Aborted mid-backoff: caller cancelled — stop, emit no warning.
1462
+ callerCancelled = true;
1463
+ break;
1464
+ }
1465
+ retries += 1;
1466
+ continue;
1467
+ }
1468
+ break;
1469
+ }
1470
+ }
1471
+ // Distinct operator-facing warnings so a slow binary is not mistaken for
1472
+ // a misconfiguration (and vice versa). Do not hard-fail here: fall through
1473
+ // to PATH/fallback probing so recall stays healthy when config is stale.
1474
+ // Caller cancellation is silent noise (issue #1841): suppress all warnings
1475
+ // for it exactly as the other QMD paths do.
1476
+ if (!callerCancelled) {
1477
+ if (failureKind === "transient") {
1478
+ this.logCliProbeWarning(
1479
+ `QMD: qmdPath ${configuredPath} version check timed out (host may be under load); retried ${retries} time${retries === 1 ? "" : "s"}`,
1480
+ );
1481
+ } else if (failureKind === "missing") {
1482
+ this.logCliProbeWarning(
1483
+ `QMD: configured qmdPath not found or not executable (${configuredPath}): ${this.lastCliProbeError}`,
1484
+ );
1485
+ } else {
1486
+ // Preserve the historical generic message for exit-code/other failures.
1487
+ this.logCliProbeWarning(
1488
+ `QMD: configured qmdPath failed (${configuredPath}): ${this.lastCliProbeError}`,
1489
+ );
1490
+ }
1505
1491
  }
1506
1492
  }
1507
1493
 
1508
1494
  // Try PATH first
1509
1495
  try {
1510
- const result = await runQmd(["--version"], QMD_PROBE_TIMEOUT_MS, "qmd", options.signal, this.qmdRuntimeEnv);
1496
+ const result = await this.runVersionProbe("qmd", options.signal);
1511
1497
  await recordProbeSuccess(result, "qmd", "auto-path");
1512
1498
  return true;
1513
1499
  } catch (err) {
@@ -1515,7 +1501,7 @@ export class QmdClient implements SearchBackend {
1515
1501
  // Try fallback paths
1516
1502
  for (const fallbackPath of this.qmdFallbackPaths) {
1517
1503
  try {
1518
- const result = await runQmd(["--version"], QMD_PROBE_TIMEOUT_MS, fallbackPath, options.signal, this.qmdRuntimeEnv);
1504
+ const result = await this.runVersionProbe(fallbackPath, options.signal);
1519
1505
  await recordProbeSuccess(result, fallbackPath, "auto-fallback");
1520
1506
  log.info(`QMD: found at ${fallbackPath}`);
1521
1507
  return true;