@remnic/core 9.63.0 → 9.63.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 (302) hide show
  1. package/dist/access-admin-ops-surface.d.ts +1 -1
  2. package/dist/access-admin-ops-surface.js +34 -33
  3. package/dist/access-audit.js +2 -2
  4. package/dist/access-authorization-probe.d.ts +1 -1
  5. package/dist/access-authorization-probe.js +35 -34
  6. package/dist/access-boundary.d.ts +1 -1
  7. package/dist/access-boundary.js +34 -33
  8. package/dist/access-cli.js +99 -97
  9. package/dist/access-cli.js.map +1 -1
  10. package/dist/access-extraction-force-flush.d.ts +1 -1
  11. package/dist/access-extraction-force-flush.js +34 -33
  12. package/dist/access-http-lcm-compaction.d.ts +1 -1
  13. package/dist/access-http-lifecycle-flush.d.ts +1 -1
  14. package/dist/access-http-offline-stream.d.ts +1 -1
  15. package/dist/access-http-query.js +34 -33
  16. package/dist/access-http.d.ts +2 -2
  17. package/dist/access-http.js +47 -46
  18. package/dist/access-identity-continuity-surface.js +34 -33
  19. package/dist/access-lcm-surface.d.ts +1 -1
  20. package/dist/access-lcm-surface.js +34 -33
  21. package/dist/access-mcp.d.ts +1 -1
  22. package/dist/access-mcp.js +40 -39
  23. package/dist/access-namespace-preflight.js +34 -33
  24. package/dist/access-observe-write-surface.d.ts +1 -1
  25. package/dist/access-observe-write-surface.js +34 -33
  26. package/dist/access-operations-batch.js +35 -34
  27. package/dist/access-operations.d.ts +4 -4
  28. package/dist/access-operations.js +39 -38
  29. package/dist/access-recall-concurrency.d.ts +1 -1
  30. package/dist/access-recall-concurrency.js +34 -33
  31. package/dist/access-recall-response.d.ts +1 -1
  32. package/dist/access-recall-response.js +34 -33
  33. package/dist/access-recall-surface.d.ts +1 -1
  34. package/dist/access-recall-surface.js +34 -33
  35. package/dist/access-schema.d.ts +88 -88
  36. package/dist/access-schema.js +3 -3
  37. package/dist/access-service-helpers.d.ts +1 -1
  38. package/dist/access-service.d.ts +1 -1
  39. package/dist/access-service.js +34 -33
  40. package/dist/access-surface-catalog.d.ts +1 -1
  41. package/dist/access-surface-catalog.js +36 -35
  42. package/dist/access-surface-catalog.js.map +1 -1
  43. package/dist/adapters/index.js +7 -7
  44. package/dist/adapters/registry.js +3 -3
  45. package/dist/{auto-sync-Z52VWMNC.js → auto-sync-BKRJDMKX.js} +3 -3
  46. package/dist/behavior-learner.js +2 -2
  47. package/dist/briefing.js +15 -15
  48. package/dist/buffer.js +5 -4
  49. package/dist/causal-behavior.js +3 -3
  50. package/dist/causal-chain.js +3 -3
  51. package/dist/causal-consolidation.js +18 -18
  52. package/dist/causal-retrieval.js +3 -3
  53. package/dist/causal-trajectory.js +2 -2
  54. package/dist/{chunk-5F6HMNMZ.js → chunk-2LA3H54D.js} +2 -2
  55. package/dist/{chunk-IRLH4NM2.js → chunk-2PSX5BJU.js} +3 -3
  56. package/dist/{chunk-JBPKEARU.js → chunk-2QSZNTDO.js} +7 -7
  57. package/dist/{chunk-IZRNVNPV.js → chunk-3LJ2L3T3.js} +2 -2
  58. package/dist/{chunk-CLJ5QS6J.js → chunk-44KHFMCM.js} +72 -64
  59. package/dist/{chunk-CLJ5QS6J.js.map → chunk-44KHFMCM.js.map} +1 -1
  60. package/dist/{chunk-JDI7G6EA.js → chunk-4NMVX5U7.js} +2 -2
  61. package/dist/{chunk-WFLBP5GC.js → chunk-4NZHHGVL.js} +25 -6
  62. package/dist/chunk-4NZHHGVL.js.map +1 -0
  63. package/dist/{chunk-ANMIOQPU.js → chunk-4XN35YQD.js} +478 -164
  64. package/dist/chunk-4XN35YQD.js.map +1 -0
  65. package/dist/{chunk-LJOIZPJH.js → chunk-5QZR2RA3.js} +2 -2
  66. package/dist/{chunk-FF6E6HOT.js → chunk-5UFQCU6M.js} +32 -32
  67. package/dist/{chunk-4HO3542O.js → chunk-5W5N7XJZ.js} +2 -2
  68. package/dist/{chunk-4Y2RHVCV.js → chunk-5W62VEFH.js} +4 -4
  69. package/dist/{chunk-DEOYTFA6.js → chunk-645WOU5I.js} +4 -52
  70. package/dist/chunk-645WOU5I.js.map +1 -0
  71. package/dist/{chunk-LTJTP5YQ.js → chunk-6D7PZKCJ.js} +10 -10
  72. package/dist/{chunk-BBHFAVMP.js → chunk-6F2UMP4H.js} +7 -7
  73. package/dist/{chunk-IIPJKCZB.js → chunk-6NAPOLRO.js} +3 -3
  74. package/dist/{chunk-CK3NQUSQ.js → chunk-6UZR7KEK.js} +2 -2
  75. package/dist/{chunk-2TYIAKU5.js → chunk-6YMEU6OQ.js} +2 -2
  76. package/dist/{chunk-H23GIXJC.js → chunk-A73QHOZP.js} +2 -2
  77. package/dist/{chunk-LLGDVG52.js → chunk-AJSZ2EK6.js} +3 -3
  78. package/dist/{chunk-HVKMJAEF.js → chunk-B2OOJCUX.js} +2 -2
  79. package/dist/chunk-BUYVPUEM.js +66 -0
  80. package/dist/chunk-BUYVPUEM.js.map +1 -0
  81. package/dist/{chunk-YTHQHH5U.js → chunk-C6WMSXCJ.js} +2 -2
  82. package/dist/{chunk-JKW663ZN.js → chunk-CIAI24TQ.js} +2 -2
  83. package/dist/{chunk-PD6O7AXF.js → chunk-CSAKJVXW.js} +14 -17
  84. package/dist/chunk-CSAKJVXW.js.map +1 -0
  85. package/dist/{chunk-EFWHCTYJ.js → chunk-DNRTKO2U.js} +2 -2
  86. package/dist/{chunk-QCECHYZL.js → chunk-E2GYBTSP.js} +24 -4
  87. package/dist/chunk-E2GYBTSP.js.map +1 -0
  88. package/dist/{chunk-4IZ2IT7D.js → chunk-EJLTNPRY.js} +3 -3
  89. package/dist/{chunk-JCJGYNIS.js → chunk-EJQXDKYK.js} +4 -4
  90. package/dist/{chunk-347ZR6D4.js → chunk-FKJVEW6O.js} +2 -2
  91. package/dist/{chunk-DROAPP6N.js → chunk-GFNS3BHV.js} +3 -3
  92. package/dist/{chunk-ALNZEWGN.js → chunk-GPFZFCRN.js} +2 -2
  93. package/dist/{chunk-KPML4Q3M.js → chunk-GTRGH4ZT.js} +3 -3
  94. package/dist/{chunk-TIRJV2LW.js → chunk-I6OQCT3Y.js} +2 -2
  95. package/dist/{chunk-CH627RRV.js → chunk-IKXUG6KC.js} +8 -8
  96. package/dist/{chunk-HSBQJRV2.js → chunk-IM56VPKO.js} +3 -3
  97. package/dist/{chunk-KQAFEZQX.js → chunk-IPLYGWQF.js} +5 -5
  98. package/dist/{chunk-BP6WMYLP.js → chunk-ITYAXU4S.js} +3 -3
  99. package/dist/{chunk-4FFROSFG.js → chunk-JJ7JW62R.js} +23 -23
  100. package/dist/{chunk-RFH7U5LT.js → chunk-JJX5XEIG.js} +2 -2
  101. package/dist/{chunk-CALURYWR.js → chunk-JWPG65LH.js} +14 -14
  102. package/dist/chunk-K2XUWZBM.js +417 -0
  103. package/dist/chunk-K2XUWZBM.js.map +1 -0
  104. package/dist/{chunk-G2FCSILA.js → chunk-KWIGDKM2.js} +2 -2
  105. package/dist/{chunk-WR2YKUIW.js → chunk-KWN6NOHQ.js} +2 -2
  106. package/dist/{chunk-227UFSEQ.js → chunk-LEPOP46W.js} +4 -4
  107. package/dist/{chunk-WMOJIOIG.js → chunk-LZCSOUJ7.js} +2 -2
  108. package/dist/{chunk-256FBCVX.js → chunk-M4AHBM72.js} +7 -7
  109. package/dist/{chunk-5PFGMO33.js → chunk-MEW53VQJ.js} +2 -2
  110. package/dist/{chunk-DCUMFQYM.js → chunk-ML3U4KPR.js} +2 -2
  111. package/dist/{chunk-X6E7TT3A.js → chunk-MR5HWQHS.js} +2 -2
  112. package/dist/{chunk-H7PRHUBD.js → chunk-NRLELHAY.js} +2 -2
  113. package/dist/{chunk-ZS4JADYY.js → chunk-OW7NVMGQ.js} +2 -2
  114. package/dist/{chunk-TGSMU3F2.js → chunk-PADG72LJ.js} +3 -3
  115. package/dist/{chunk-6GGTATZF.js → chunk-PYIR4SSB.js} +4 -4
  116. package/dist/chunk-PYIR4SSB.js.map +1 -0
  117. package/dist/{chunk-UDTME32W.js → chunk-Q35XGIEM.js} +2 -2
  118. package/dist/{chunk-XPRJSHNI.js → chunk-QJ622LTI.js} +6 -3
  119. package/dist/chunk-QJ622LTI.js.map +1 -0
  120. package/dist/{chunk-AYIHDC5S.js → chunk-QSIQVC32.js} +2 -2
  121. package/dist/{chunk-3MA7JVXN.js → chunk-R74NCKEY.js} +4 -4
  122. package/dist/{chunk-QCI73Z7C.js → chunk-RYGJGXL6.js} +2 -2
  123. package/dist/{chunk-4OW2T32G.js → chunk-SQ3EGGII.js} +2 -2
  124. package/dist/{chunk-ZPGHA7EO.js → chunk-TRPX6F2G.js} +10 -10
  125. package/dist/{chunk-7K5Q6COX.js → chunk-TVVEYCNW.js} +4 -4
  126. package/dist/{chunk-7BJYTX6Q.js → chunk-VAMQU4W7.js} +2 -2
  127. package/dist/{chunk-YSR3DXGC.js → chunk-VN7IIQGS.js} +2 -2
  128. package/dist/{chunk-DTLF2SLZ.js → chunk-WDUG2CH3.js} +2 -2
  129. package/dist/{chunk-YIIC5IDU.js → chunk-X4MGNVOO.js} +2 -2
  130. package/dist/{chunk-BHO6RHE5.js → chunk-XFCQIQQU.js} +3 -3
  131. package/dist/{chunk-PSNN25DW.js → chunk-XZPSBGHA.js} +2 -2
  132. package/dist/{chunk-PMRUMGK6.js → chunk-Y7COCXY2.js} +2 -2
  133. package/dist/{chunk-DJ7HMNSL.js → chunk-YCVB7X56.js} +4 -2
  134. package/dist/chunk-YCVB7X56.js.map +1 -0
  135. package/dist/{chunk-EZ25VE3G.js → chunk-YNDLCWXS.js} +4 -4
  136. package/dist/{chunk-JFLKSD5V.js → chunk-Z334TJRB.js} +2 -2
  137. package/dist/{cli-Bw9z-KqX.d.ts → cli-DJwNNDL1.d.ts} +1 -1
  138. package/dist/cli.d.ts +2 -2
  139. package/dist/cli.js +68 -67
  140. package/dist/coding/pre-action-gate.js +3 -3
  141. package/dist/compounding/engine.js +15 -15
  142. package/dist/connectors/codex-materialize-runner.js +15 -15
  143. package/dist/connectors/index.d.ts +45 -1
  144. package/dist/connectors/index.js +24 -16
  145. package/dist/convergence-refresh.js +17 -17
  146. package/dist/conversation-index/backend.js +2 -2
  147. package/dist/corpus-watermark.js +17 -17
  148. package/dist/dashboard-runtime.js +2 -2
  149. package/dist/direct-answer-wiring.js +3 -3
  150. package/dist/direct-answer.js +2 -2
  151. package/dist/entity-retrieval.js +15 -15
  152. package/dist/event-order-query.d.ts +3 -0
  153. package/dist/event-order-query.js +9 -0
  154. package/dist/event-order-query.js.map +1 -0
  155. package/dist/event-order-recall.d.ts +2 -2
  156. package/dist/event-order-recall.js +5 -2
  157. package/dist/external-wiki-access.d.ts +1 -1
  158. package/dist/external-wiki-access.js +35 -34
  159. package/dist/external-wiki-mcp-tools.d.ts +1 -1
  160. package/dist/extraction-normalization.js +2 -2
  161. package/dist/extraction.js +25 -24
  162. package/dist/harmonic-construction.js +2 -2
  163. package/dist/harmonic-retrieval.js +2 -2
  164. package/dist/index.d.ts +683 -682
  165. package/dist/index.js +108 -105
  166. package/dist/intent.js +2 -1
  167. package/dist/lcm/engine.js +3 -3
  168. package/dist/lcm/index.js +6 -6
  169. package/dist/maintenance/memory-governance.js +15 -15
  170. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +15 -15
  171. package/dist/maintenance/rebuild-memory-projection.js +16 -16
  172. package/dist/mcp-memory-inspector-app.d.ts +1 -1
  173. package/dist/namespaces/migrate.js +16 -16
  174. package/dist/namespaces/search.js +10 -10
  175. package/dist/namespaces/storage.js +15 -15
  176. package/dist/operator-doctor-corpus.js +18 -18
  177. package/dist/operator-toolkit.js +22 -22
  178. package/dist/orchestration/maintenance.js +20 -20
  179. package/dist/orchestrator.js +101 -99
  180. package/dist/policy-runtime.js +3 -3
  181. package/dist/{public-http-DK-QwaBu.d.ts → public-http-43z1YT-k.d.ts} +1 -1
  182. package/dist/{qmd-D_62bYNl.d.ts → qmd-CBSxevyb.d.ts} +12 -1
  183. package/dist/qmd-preflight.d.ts +1 -1
  184. package/dist/qmd.d.ts +1 -1
  185. package/dist/qmd.js +3 -1
  186. package/dist/recall-planner-i18n.d.ts +31 -0
  187. package/dist/recall-planner-i18n.js +30 -0
  188. package/dist/recall-planner-i18n.js.map +1 -0
  189. package/dist/recall-planner-llm.js +2 -1
  190. package/dist/recall-planner-llm.js.map +1 -1
  191. package/dist/recall-query-policy.js +2 -2
  192. package/dist/recall-tokenization.js +1 -1
  193. package/dist/resume-bundles.js +3 -3
  194. package/dist/retrieval-agents.d.ts +1 -1
  195. package/dist/retrieval-agents.js +2 -1
  196. package/dist/schemas.d.ts +117 -434
  197. package/dist/schemas.js +1 -1
  198. package/dist/search/factory.js +9 -9
  199. package/dist/search/index.js +15 -15
  200. package/dist/search/orama-backend.js +2 -2
  201. package/dist/semantic-consolidation.js +16 -16
  202. package/dist/semantic-rule-promotion.js +15 -15
  203. package/dist/semantic-rule-verifier.js +15 -15
  204. package/dist/shared-context/manager.d.ts +8 -8
  205. package/dist/source-agent-qualifier.js +21 -20
  206. package/dist/storage.js +14 -14
  207. package/dist/support-passport/index.d.ts +4 -4
  208. package/dist/support-passport/index.js +37 -36
  209. package/dist/transfer/autodetect.js +1 -1
  210. package/dist/transfer/backup.js +1 -1
  211. package/dist/transfer/capsule-export.js +2 -2
  212. package/dist/transfer/types.d.ts +66 -66
  213. package/dist/trust-zones.js +2 -2
  214. package/dist/verified-recall.js +15 -15
  215. package/dist/work-product-ledger.js +2 -2
  216. package/package.json +2 -2
  217. package/src/connectors/droid-mcp.ts +434 -0
  218. package/src/connectors/index.ts +70 -0
  219. package/src/direct-answer.ts +6 -3
  220. package/src/event-order-query.ts +78 -0
  221. package/src/event-order-recall.ts +4 -90
  222. package/src/intent.ts +19 -19
  223. package/src/orchestration/turn-ingestion.ts +24 -1
  224. package/src/orchestrator-flush.test.ts +142 -1
  225. package/src/qmd-process-group.test.ts +125 -0
  226. package/src/qmd.ts +30 -2
  227. package/src/recall-planner-i18n.ts +193 -0
  228. package/src/recall-tokenization.ts +3 -1
  229. package/src/retrieval-agents.ts +6 -10
  230. package/src/schemas.test.ts +158 -0
  231. package/src/schemas.ts +45 -22
  232. package/dist/chunk-6GGTATZF.js.map +0 -1
  233. package/dist/chunk-ANMIOQPU.js.map +0 -1
  234. package/dist/chunk-DEOYTFA6.js.map +0 -1
  235. package/dist/chunk-DJ7HMNSL.js.map +0 -1
  236. package/dist/chunk-PD6O7AXF.js.map +0 -1
  237. package/dist/chunk-QCECHYZL.js.map +0 -1
  238. package/dist/chunk-WFLBP5GC.js.map +0 -1
  239. package/dist/chunk-XPRJSHNI.js.map +0 -1
  240. package/dist/{access-service-M4LgO141.d.ts → access-service-DBpEnlzA.d.ts} +64 -64
  241. /package/dist/{auto-sync-Z52VWMNC.js.map → auto-sync-BKRJDMKX.js.map} +0 -0
  242. /package/dist/{chunk-5F6HMNMZ.js.map → chunk-2LA3H54D.js.map} +0 -0
  243. /package/dist/{chunk-IRLH4NM2.js.map → chunk-2PSX5BJU.js.map} +0 -0
  244. /package/dist/{chunk-JBPKEARU.js.map → chunk-2QSZNTDO.js.map} +0 -0
  245. /package/dist/{chunk-IZRNVNPV.js.map → chunk-3LJ2L3T3.js.map} +0 -0
  246. /package/dist/{chunk-JDI7G6EA.js.map → chunk-4NMVX5U7.js.map} +0 -0
  247. /package/dist/{chunk-LJOIZPJH.js.map → chunk-5QZR2RA3.js.map} +0 -0
  248. /package/dist/{chunk-FF6E6HOT.js.map → chunk-5UFQCU6M.js.map} +0 -0
  249. /package/dist/{chunk-4HO3542O.js.map → chunk-5W5N7XJZ.js.map} +0 -0
  250. /package/dist/{chunk-4Y2RHVCV.js.map → chunk-5W62VEFH.js.map} +0 -0
  251. /package/dist/{chunk-LTJTP5YQ.js.map → chunk-6D7PZKCJ.js.map} +0 -0
  252. /package/dist/{chunk-BBHFAVMP.js.map → chunk-6F2UMP4H.js.map} +0 -0
  253. /package/dist/{chunk-IIPJKCZB.js.map → chunk-6NAPOLRO.js.map} +0 -0
  254. /package/dist/{chunk-CK3NQUSQ.js.map → chunk-6UZR7KEK.js.map} +0 -0
  255. /package/dist/{chunk-2TYIAKU5.js.map → chunk-6YMEU6OQ.js.map} +0 -0
  256. /package/dist/{chunk-H23GIXJC.js.map → chunk-A73QHOZP.js.map} +0 -0
  257. /package/dist/{chunk-LLGDVG52.js.map → chunk-AJSZ2EK6.js.map} +0 -0
  258. /package/dist/{chunk-HVKMJAEF.js.map → chunk-B2OOJCUX.js.map} +0 -0
  259. /package/dist/{chunk-YTHQHH5U.js.map → chunk-C6WMSXCJ.js.map} +0 -0
  260. /package/dist/{chunk-JKW663ZN.js.map → chunk-CIAI24TQ.js.map} +0 -0
  261. /package/dist/{chunk-EFWHCTYJ.js.map → chunk-DNRTKO2U.js.map} +0 -0
  262. /package/dist/{chunk-4IZ2IT7D.js.map → chunk-EJLTNPRY.js.map} +0 -0
  263. /package/dist/{chunk-JCJGYNIS.js.map → chunk-EJQXDKYK.js.map} +0 -0
  264. /package/dist/{chunk-347ZR6D4.js.map → chunk-FKJVEW6O.js.map} +0 -0
  265. /package/dist/{chunk-DROAPP6N.js.map → chunk-GFNS3BHV.js.map} +0 -0
  266. /package/dist/{chunk-ALNZEWGN.js.map → chunk-GPFZFCRN.js.map} +0 -0
  267. /package/dist/{chunk-KPML4Q3M.js.map → chunk-GTRGH4ZT.js.map} +0 -0
  268. /package/dist/{chunk-TIRJV2LW.js.map → chunk-I6OQCT3Y.js.map} +0 -0
  269. /package/dist/{chunk-CH627RRV.js.map → chunk-IKXUG6KC.js.map} +0 -0
  270. /package/dist/{chunk-HSBQJRV2.js.map → chunk-IM56VPKO.js.map} +0 -0
  271. /package/dist/{chunk-KQAFEZQX.js.map → chunk-IPLYGWQF.js.map} +0 -0
  272. /package/dist/{chunk-BP6WMYLP.js.map → chunk-ITYAXU4S.js.map} +0 -0
  273. /package/dist/{chunk-4FFROSFG.js.map → chunk-JJ7JW62R.js.map} +0 -0
  274. /package/dist/{chunk-RFH7U5LT.js.map → chunk-JJX5XEIG.js.map} +0 -0
  275. /package/dist/{chunk-CALURYWR.js.map → chunk-JWPG65LH.js.map} +0 -0
  276. /package/dist/{chunk-G2FCSILA.js.map → chunk-KWIGDKM2.js.map} +0 -0
  277. /package/dist/{chunk-WR2YKUIW.js.map → chunk-KWN6NOHQ.js.map} +0 -0
  278. /package/dist/{chunk-227UFSEQ.js.map → chunk-LEPOP46W.js.map} +0 -0
  279. /package/dist/{chunk-WMOJIOIG.js.map → chunk-LZCSOUJ7.js.map} +0 -0
  280. /package/dist/{chunk-256FBCVX.js.map → chunk-M4AHBM72.js.map} +0 -0
  281. /package/dist/{chunk-5PFGMO33.js.map → chunk-MEW53VQJ.js.map} +0 -0
  282. /package/dist/{chunk-DCUMFQYM.js.map → chunk-ML3U4KPR.js.map} +0 -0
  283. /package/dist/{chunk-X6E7TT3A.js.map → chunk-MR5HWQHS.js.map} +0 -0
  284. /package/dist/{chunk-H7PRHUBD.js.map → chunk-NRLELHAY.js.map} +0 -0
  285. /package/dist/{chunk-ZS4JADYY.js.map → chunk-OW7NVMGQ.js.map} +0 -0
  286. /package/dist/{chunk-TGSMU3F2.js.map → chunk-PADG72LJ.js.map} +0 -0
  287. /package/dist/{chunk-UDTME32W.js.map → chunk-Q35XGIEM.js.map} +0 -0
  288. /package/dist/{chunk-AYIHDC5S.js.map → chunk-QSIQVC32.js.map} +0 -0
  289. /package/dist/{chunk-3MA7JVXN.js.map → chunk-R74NCKEY.js.map} +0 -0
  290. /package/dist/{chunk-QCI73Z7C.js.map → chunk-RYGJGXL6.js.map} +0 -0
  291. /package/dist/{chunk-4OW2T32G.js.map → chunk-SQ3EGGII.js.map} +0 -0
  292. /package/dist/{chunk-ZPGHA7EO.js.map → chunk-TRPX6F2G.js.map} +0 -0
  293. /package/dist/{chunk-7K5Q6COX.js.map → chunk-TVVEYCNW.js.map} +0 -0
  294. /package/dist/{chunk-7BJYTX6Q.js.map → chunk-VAMQU4W7.js.map} +0 -0
  295. /package/dist/{chunk-YSR3DXGC.js.map → chunk-VN7IIQGS.js.map} +0 -0
  296. /package/dist/{chunk-DTLF2SLZ.js.map → chunk-WDUG2CH3.js.map} +0 -0
  297. /package/dist/{chunk-YIIC5IDU.js.map → chunk-X4MGNVOO.js.map} +0 -0
  298. /package/dist/{chunk-BHO6RHE5.js.map → chunk-XFCQIQQU.js.map} +0 -0
  299. /package/dist/{chunk-PSNN25DW.js.map → chunk-XZPSBGHA.js.map} +0 -0
  300. /package/dist/{chunk-PMRUMGK6.js.map → chunk-Y7COCXY2.js.map} +0 -0
  301. /package/dist/{chunk-EZ25VE3G.js.map → chunk-YNDLCWXS.js.map} +0 -0
  302. /package/dist/{chunk-JFLKSD5V.js.map → chunk-Z334TJRB.js.map} +0 -0
@@ -0,0 +1,78 @@
1
+ import { EVENT_ORDER_CUES, containsRecallCue } from "./recall-planner-i18n.js";
2
+
3
+ function isTimelineSummaryQuery(normalized: string): boolean {
4
+ return (
5
+ /\b(?:summarize|summary|major progress|what happened|develop(?:ed|ment)?|approached)\b/.test(
6
+ normalized,
7
+ ) &&
8
+ (/\b(?:over time|throughout|between|project|progress|timeline|journey|sessions?|along the way)\b/.test(
9
+ normalized,
10
+ ) ||
11
+ /\bfrom\b.+\bthrough\b/.test(normalized))
12
+ );
13
+ }
14
+
15
+ export function shouldRecallEventOrderEvidence(query: string): boolean {
16
+ const normalized = query.toLowerCase();
17
+
18
+ if (containsRecallCue(normalized, EVENT_ORDER_CUES)) {
19
+ return true;
20
+ }
21
+
22
+ if (
23
+ /\border in which\b/.test(normalized) ||
24
+ /\bsequence in which\b/.test(normalized) ||
25
+ /\breconstruct\b.*\btimeline\b/.test(normalized) ||
26
+ /\btimeline\b.*\bin order\b/.test(normalized) ||
27
+ /\bsequence\b.*\bin order\b/.test(normalized) ||
28
+ /\bintroduced\b.*\bin order\b/.test(normalized) ||
29
+ (/\bwalk me through\b/.test(normalized) && /\bin order\b/.test(normalized)) ||
30
+ (/\bin order\b/.test(normalized) &&
31
+ /\b(?:develop(?:ed|ment)?|evolv(?:e|ed|ing)?|progress|throughout|conversations?|sessions?)\b/.test(
32
+ normalized,
33
+ )) ||
34
+ /\bprogress\b.*\bin order\b/.test(normalized) ||
35
+ /\bchronological(?:ly| order)?\b/.test(normalized) ||
36
+ isTimelineSummaryQuery(normalized)
37
+ ) {
38
+ return true;
39
+ }
40
+
41
+ if (/\bwhich\b.*\bfirst\b/.test(normalized) || /\bwhat was the first\b/.test(normalized)) {
42
+ return true;
43
+ }
44
+ if (/\bhappened first\b/.test(normalized) || /\bwhich\b.*\bhappened\b/.test(normalized)) {
45
+ return true;
46
+ }
47
+ if (/\bhow many days\b/.test(normalized)) {
48
+ return true;
49
+ }
50
+ if (/\bwhen did\b/.test(normalized)) {
51
+ return true;
52
+ }
53
+ if (/\bwhich\b.*\blast\b/.test(normalized) || /\bthe last\b.*\bi\b/.test(normalized)) {
54
+ return true;
55
+ }
56
+ if (/\bwho\b.*\bfirst\b/.test(normalized)) {
57
+ return true;
58
+ }
59
+ if (/\bmost recent(?:ly)?\b/.test(normalized)) {
60
+ return true;
61
+ }
62
+ if (/\border of\b/.test(normalized) || /\bfrom earliest to latest\b/.test(normalized)) {
63
+ return true;
64
+ }
65
+ if (/\bhow long\b/.test(normalized)) {
66
+ return true;
67
+ }
68
+ if (/\bhow many (months|weeks|years)\b/.test(normalized)) {
69
+ return true;
70
+ }
71
+ if (/\bhow many\b.*\bbefore\b/.test(normalized)) {
72
+ return true;
73
+ }
74
+ if (/\bhow old\b.*\bwhen\b/.test(normalized)) {
75
+ return true;
76
+ }
77
+ return false;
78
+ }
@@ -7,6 +7,10 @@ import {
7
7
  type RankedEvidenceItem,
8
8
  } from "./recall-pipeline-stages.js";
9
9
 
10
+ import { shouldRecallEventOrderEvidence } from "./event-order-query.js";
11
+ export { shouldRecallEventOrderEvidence };
12
+
13
+
10
14
  export interface EventOrderRecallOptions {
11
15
  engine: ExplicitCueRecallEngine | null | undefined;
12
16
  // event-order reads a SINGLE LCM session key. Unlike the relevance-ranked
@@ -28,87 +32,6 @@ const DEFAULT_SCAN_WINDOW_TURNS = 12;
28
32
  const DEFAULT_SCAN_WINDOW_TOKENS = 24_000;
29
33
  const DEFAULT_MAX_ITEMS = 24;
30
34
 
31
- export function shouldRecallEventOrderEvidence(query: string): boolean {
32
- const normalized = query.toLowerCase();
33
-
34
- // --- Existing event-order patterns (LongMemEval order-reconstruction phrasing) ---
35
- if (
36
- /\border in which\b/.test(normalized) ||
37
- /\bsequence in which\b/.test(normalized) ||
38
- /\breconstruct\b.*\btimeline\b/.test(normalized) ||
39
- /\btimeline\b.*\bin order\b/.test(normalized) ||
40
- /\bsequence\b.*\bin order\b/.test(normalized) ||
41
- /\bintroduced\b.*\bin order\b/.test(normalized) ||
42
- (/\bwalk me through\b/.test(normalized) && /\bin order\b/.test(normalized)) ||
43
- (/\bin order\b/.test(normalized) &&
44
- /\b(?:develop(?:ed|ment)?|evolv(?:e|ed|ing)?|progress|throughout|conversations?|sessions?)\b/.test(normalized)) ||
45
- /\bprogress\b.*\bin order\b/.test(normalized) ||
46
- /\bchronological(?:ly| order)?\b/.test(normalized) ||
47
- isTimelineSummaryQuery(normalized)
48
- ) {
49
- return true;
50
- }
51
-
52
- // --- Temporal-reasoning patterns (LongMemEval temporal-reasoning question type) ---
53
- // These question shapes need chronological evidence to answer correctly:
54
- // ordering ("which happened first"), relative time ("how many days
55
- // between"), and recency ("which did I [verb] last"). The event-order tier
56
- // surfaces a chronologically sorted evidence pack so the responder can
57
- // reconstruct the answer from an ordered timeline rather than fragments.
58
- // Verified against the LongMemEval-oracle dataset: 0% of the 133
59
- // temporal-reasoning questions matched the original heuristic above.
60
-
61
- // "Which X did I Y first, A or B?" / "What was the first X ...?"
62
- if (/\bwhich\b.*\bfirst\b/.test(normalized) || /\bwhat was the first\b/.test(normalized)) {
63
- return true;
64
- }
65
- // "Which event happened first, A or B?" / "Which happened first"
66
- if (/\bhappened first\b/.test(normalized) || /\bwhich\b.*\bhappened\b/.test(normalized)) {
67
- return true;
68
- }
69
- // "How many days before/between/after/did it take"
70
- if (/\bhow many days\b/.test(normalized)) {
71
- return true;
72
- }
73
- // "When did I ..." (date-shaped)
74
- if (/\bwhen did\b/.test(normalized)) {
75
- return true;
76
- }
77
- // "Which X did I Y last?" / "the last X I ..."
78
- if (/\bwhich\b.*\blast\b/.test(normalized) || /\bthe last\b.*\bi\b/.test(normalized)) {
79
- return true;
80
- }
81
- // "Who did I meet/become first, X or Y?" (person ordering)
82
- if (/\bwho\b.*\bfirst\b/.test(normalized)) {
83
- return true;
84
- }
85
- // "most recently" / "most recent" (recency)
86
- if (/\bmost recent(?:ly)?\b/.test(normalized)) {
87
- return true;
88
- }
89
- // "What is the order of..." / "...from earliest to latest"
90
- if (/\border of\b/.test(normalized) || /\bfrom earliest to latest\b/.test(normalized)) {
91
- return true;
92
- }
93
-
94
- // "How long have/did/had I been..." (duration)
95
- if (/\bhow long\b/.test(normalized)) {
96
- return true;
97
- }
98
- // "How many months/weeks/years ago/before" (relative time)
99
- if (/\bhow many (months|weeks|years)\b/.test(normalized)) {
100
- return true;
101
- }
102
- // "How many X did I [verb] before Y" (temporal comparison)
103
- if (/\bhow many\b.*\bbefore\b/.test(normalized)) {
104
- return true;
105
- }
106
- // "How old was I when..." (age-at-time)
107
- if (/\bhow old\b.*\bwhen\b/.test(normalized)) {
108
- return true;
109
- }
110
- return false;
111
- }
112
35
 
113
36
  export async function buildEventOrderRecallSection(
114
37
  options: EventOrderRecallOptions,
@@ -1693,15 +1616,6 @@ function deriveChronologicalCueLabels(content: string, query: string): string[]
1693
1616
  return [...labels].slice(0, 5);
1694
1617
  }
1695
1618
 
1696
- function isTimelineSummaryQuery(normalized: string): boolean {
1697
- return /\b(?:summarize|summary|major progress|what happened|develop(?:ed|ment)?|approached)\b/.test(
1698
- normalized,
1699
- ) &&
1700
- (/\b(?:over time|throughout|between|project|progress|timeline|journey|sessions?|along the way)\b/.test(
1701
- normalized,
1702
- ) ||
1703
- /\bfrom\b.+\bthrough\b/.test(normalized));
1704
- }
1705
1619
 
1706
1620
  function addLabelIf(
1707
1621
  labels: Set<string>,
package/src/intent.ts CHANGED
@@ -1,3 +1,11 @@
1
+ import {
2
+ CAUSAL_CHAIN_CUES,
3
+ GRAPH_MODE_CUES,
4
+ containsRecallCue,
5
+ endsWithQuestionMark,
6
+ isMultilingualAck,
7
+ stripTrailingRecallNoise,
8
+ } from "./recall-planner-i18n.js";
1
9
  import type { MemoryIntent, RecallPlanMode } from "./types.js";
2
10
 
3
11
  const GOAL_PATTERNS: Array<{ re: RegExp; goal: string }> = [
@@ -100,35 +108,26 @@ export function intentCompatibilityScore(queryIntent: MemoryIntent, memoryIntent
100
108
 
101
109
  export function planRecallMode(prompt: string): RecallPlanMode {
102
110
  const p = normalizeTextInput(prompt).trim();
103
- let ackCandidate = p;
104
- while (ackCandidate.length > 0) {
105
- const ch = ackCandidate.charCodeAt(ackCandidate.length - 1);
106
- const isDigit = ch >= 48 && ch <= 57;
107
- const isUpper = ch >= 65 && ch <= 90;
108
- const isLower = ch >= 97 && ch <= 122;
109
- // Strip any trailing non-alphanumeric noise (punctuation/emojis/symbols).
110
- if (isDigit || isUpper || isLower) break;
111
- ackCandidate = ackCandidate.slice(0, -1);
112
- }
113
- ackCandidate = ackCandidate.trim();
114
111
  if (p.length === 0) return "no_recall";
115
112
 
116
- if (/\b(timeline|sequence|history|what happened|chain of events|root cause)\b/i.test(p)) {
113
+ if (
114
+ /\b(timeline|sequence|history|what happened|chain of events|root cause)\b/i.test(p) ||
115
+ containsRecallCue(p, GRAPH_MODE_CUES)
116
+ ) {
117
117
  return "graph_mode";
118
118
  }
119
119
 
120
120
  // Reserve no_recall for low-information acknowledgements; avoid broad regressions.
121
- if (
122
- p.length <= 18 &&
123
- /^(ok|okay|kk|thanks|thx|got it|sounds good|yep|yes|nope|no|done|cool|works)$/i.test(ackCandidate)
124
- ) {
121
+ // Multilingual table (issue #2191): Japanese/Chinese/Korean/Russian/etc acks
122
+ // route the same way; the exact-match set cannot match longer English prompts.
123
+ if (p.length <= 18 && isMultilingualAck(stripTrailingRecallNoise(p))) {
125
124
  return "no_recall";
126
125
  }
127
126
 
128
127
  // Full recall for prompts that are explicitly memory-seeking or analytical questions.
129
128
  if (
130
129
  /\b(previous|earlier|remember|last time|did we|what did we decide|context|summarize|summary|recap|key points|decision)\b/i.test(p) ||
131
- /\?$/.test(p) ||
130
+ endsWithQuestionMark(p) ||
132
131
  /^(what|why|how|when|where|who|which)\b/i.test(p.toLowerCase())
133
132
  ) {
134
133
  return "full";
@@ -148,7 +147,8 @@ export function planRecallMode(prompt: string): RecallPlanMode {
148
147
  export function hasBroadGraphIntent(prompt: string): boolean {
149
148
  const p = normalizeTextInput(prompt).trim().toLowerCase();
150
149
  if (!p) return false;
151
- return /\b(what changed|how did we get here|why did this happen|what led to|cause chain|dependency chain|regression chain|failure chain)\b/i.test(
152
- p,
150
+ return (
151
+ /\b(what changed|how did we get here|why did this happen|what led to|cause chain|dependency chain|regression chain|failure chain)\b/i.test(p) ||
152
+ containsRecallCue(p, CAUSAL_CHAIN_CUES)
153
153
  );
154
154
  }
@@ -123,6 +123,25 @@ export interface TurnIngestionOptions extends SourceConnectorProvenance {
123
123
  turnFingerprint?: string;
124
124
  }
125
125
 
126
+ /**
127
+ * Flush-plan recovery imports are compacted material the host already
128
+ * summarized before appending it to the flush-plan file. Feeding those
129
+ * turns through LCM observation again re-summarizes the same content
130
+ * (issue #2457). The producer stamps both a dedicated `sourceLabel` and a
131
+ * `:flush-plan[:chunk]`-shaped `sourceId`, so either marker identifies a
132
+ * recovery turn; ordinary bulk-import turns never carry either.
133
+ */
134
+ function isFlushPlanRecoveryTurn(
135
+ turn: Pick<BufferTurn, "importProvenance">,
136
+ ): boolean {
137
+ const provenance = turn.importProvenance;
138
+ if (provenance?.sourceLabel === "OpenClaw flush plan") return true;
139
+ return (
140
+ typeof provenance?.sourceId === "string" &&
141
+ /:flush-plan(:\d+\/\d+)?$/.test(provenance.sourceId)
142
+ );
143
+ }
144
+
126
145
  export class TurnIngestionCoordinator {
127
146
  constructor(
128
147
  private readonly deps: TurnIngestionDeps,
@@ -456,7 +475,11 @@ export class TurnIngestionCoordinator {
456
475
  };
457
476
  }
458
477
 
459
- if (this.deps.lcmEngine?.enabled) {
478
+ // Skip LCM only when every turn is flush-plan recovery material; a mixed
479
+ // batch keeps the ordinary observe path (issue #2457). Extraction and
480
+ // persistence below are unaffected either way.
481
+ const skipLcmObservation = sessionTurns.every(isFlushPlanRecoveryTurn);
482
+ if (!skipLcmObservation && this.deps.lcmEngine?.enabled) {
460
483
  await this.deps.lcmEngine.observeMessages(
461
484
  sessionKey,
462
485
  sessionTurns.map((turn) => ({
@@ -12,8 +12,10 @@ import { TurnIngestionCoordinator } from "./orchestration/turn-ingestion.js";
12
12
  import { SmartBuffer } from "./buffer.js";
13
13
  import { parseConfig } from "./config.js";
14
14
  import { stableHash } from "./coding/git-context.js";
15
- import type { BufferState, BufferTurn } from "./types.js";
15
+ import type { BufferState, BufferTurn, PluginConfig } from "./types.js";
16
16
  import type { ImportTurn } from "./bulk-import/types.js";
17
+ import type { LcmEngine } from "./lcm/index.js";
18
+ import type { ExtractionRunResult } from "./orchestration/extraction-run.js";
17
19
  import { namespaceIdentityToken } from "./namespaces/identity.js";
18
20
  import { readNamespaceMaintenanceStatuses } from "./maintenance/namespace-planner.js";
19
21
  import { stubPersistExtraction } from "./testing/orchestrator-lite.js";
@@ -28,6 +30,22 @@ function makeTurn(sessionKey: string, content: string, sessionOwnerPrincipal?: s
28
30
  };
29
31
  }
30
32
 
33
+ /**
34
+ * Writable orchestrator double for `ingestBulkImportBatch` slice tests: the
35
+ * real prototype methods run, but the per-test stub surface must stay
36
+ * assignable (the real class marks these fields readonly).
37
+ */
38
+ interface BulkImportTestDouble {
39
+ config: PluginConfig;
40
+ extractionQueueCoordinator: ExtractionQueueCoordinator;
41
+ lcmEngine: LcmEngine | null;
42
+ runExtraction: (
43
+ turns: BufferTurn[],
44
+ options?: Record<string, unknown>,
45
+ ) => Promise<ExtractionRunResult>;
46
+ ingestBulkImportBatch: Orchestrator["ingestBulkImportBatch"];
47
+ }
48
+
31
49
  interface ScopedFlushTestDouble {
32
50
  buffer: {
33
51
  getTurns(bufferKey: string): BufferTurn[];
@@ -804,6 +822,129 @@ test("ingestBulkImportBatch can disable source-valid-at replay context", async (
804
822
  );
805
823
  });
806
824
 
825
+ test("ingestBulkImportBatch skips LCM observation for flush-plan recovery batches (issue #2457)", async () => {
826
+ const orchestrator = Object.create(Orchestrator.prototype) as unknown as BulkImportTestDouble;
827
+ orchestrator.config = parseConfig({});
828
+ orchestrator.extractionQueueCoordinator = new ExtractionQueueCoordinator();
829
+ const observedSessionKeys: string[] = [];
830
+ orchestrator.lcmEngine = {
831
+ enabled: true,
832
+ observeMessages: async (sessionKey: string) => {
833
+ observedSessionKeys.push(sessionKey);
834
+ },
835
+ // Partial engine double for the observe seam.
836
+ } as unknown as LcmEngine;
837
+ const extractedSlices: BufferTurn[][] = [];
838
+ orchestrator.runExtraction = async (turns: BufferTurn[]) => {
839
+ extractedSlices.push(turns);
840
+ return { status: "completed", persistedCount: 1, durableOutputCount: 1 };
841
+ };
842
+
843
+ const result = await orchestrator.ingestBulkImportBatch([
844
+ {
845
+ role: "user",
846
+ timestamp: "2026-06-24T12:00:00.000Z",
847
+ content: "Recovery material the host already compacted once.",
848
+ importProvenance: {
849
+ sourceLabel: "OpenClaw flush plan",
850
+ sourceId: "openclaw-remnic:flush-plan",
851
+ },
852
+ },
853
+ {
854
+ role: "user",
855
+ timestamp: "2026-06-24T12:00:01.000Z",
856
+ content: "Chunked recovery material.",
857
+ importProvenance: {
858
+ sourceLabel: "OpenClaw flush plan",
859
+ sourceId: "openclaw-remnic:flush-plan:2/3",
860
+ },
861
+ },
862
+ ]);
863
+
864
+ assert.deepEqual(observedSessionKeys, []);
865
+ assert.equal(extractedSlices.length, 2);
866
+ assert.equal(result.extractionCount, 2);
867
+ assert.equal(result.persistedCount, 2);
868
+ });
869
+
870
+ test("ingestBulkImportBatch still observes ordinary bulk imports through LCM (issue #2457)", async () => {
871
+ const orchestrator = Object.create(Orchestrator.prototype) as unknown as BulkImportTestDouble;
872
+ orchestrator.config = parseConfig({});
873
+ orchestrator.extractionQueueCoordinator = new ExtractionQueueCoordinator();
874
+ const observed: { sessionKey: string; turnCount: number }[] = [];
875
+ orchestrator.lcmEngine = {
876
+ enabled: true,
877
+ observeMessages: async (sessionKey: string, messages: unknown[]) => {
878
+ observed.push({ sessionKey, turnCount: messages.length });
879
+ },
880
+ // Partial engine double for the observe seam.
881
+ } as unknown as LcmEngine;
882
+ orchestrator.runExtraction = async () => ({
883
+ status: "completed",
884
+ persistedCount: 1,
885
+ durableOutputCount: 1,
886
+ });
887
+
888
+ await orchestrator.ingestBulkImportBatch([
889
+ {
890
+ role: "user",
891
+ timestamp: "2026-06-24T12:00:00.000Z",
892
+ content: "Ordinary chatgpt import turn.",
893
+ importProvenance: { sourceLabel: "chatgpt", sourceId: "cg-1" },
894
+ },
895
+ {
896
+ role: "user",
897
+ timestamp: "2026-06-24T12:00:01.000Z",
898
+ content: "Ordinary turn without provenance.",
899
+ },
900
+ ]);
901
+
902
+ assert.deepEqual(
903
+ observed.map((entry) => entry.turnCount),
904
+ [2],
905
+ );
906
+ assert.ok(observed[0]?.sessionKey.startsWith("bulk-import:batch:"));
907
+ });
908
+
909
+ test("ingestBulkImportBatch observes mixed batches that contain flush-plan turns (issue #2457)", async () => {
910
+ const orchestrator = Object.create(Orchestrator.prototype) as unknown as BulkImportTestDouble;
911
+ orchestrator.config = parseConfig({});
912
+ orchestrator.extractionQueueCoordinator = new ExtractionQueueCoordinator();
913
+ const observed: number[] = [];
914
+ orchestrator.lcmEngine = {
915
+ enabled: true,
916
+ observeMessages: async (_sessionKey: string, messages: unknown[]) => {
917
+ observed.push(messages.length);
918
+ },
919
+ // Partial engine double for the observe seam.
920
+ } as unknown as LcmEngine;
921
+ orchestrator.runExtraction = async () => ({
922
+ status: "completed",
923
+ persistedCount: 1,
924
+ durableOutputCount: 1,
925
+ });
926
+
927
+ await orchestrator.ingestBulkImportBatch([
928
+ {
929
+ role: "user",
930
+ timestamp: "2026-06-24T12:00:00.000Z",
931
+ content: "Recovery material mixed into an ordinary batch.",
932
+ importProvenance: {
933
+ sourceLabel: "OpenClaw flush plan",
934
+ sourceId: "openclaw-remnic:flush-plan",
935
+ },
936
+ },
937
+ {
938
+ role: "user",
939
+ timestamp: "2026-06-24T12:00:01.000Z",
940
+ content: "Ordinary chatgpt import turn.",
941
+ importProvenance: { sourceLabel: "chatgpt", sourceId: "cg-2" },
942
+ },
943
+ ]);
944
+
945
+ assert.deepEqual(observed, [2]);
946
+ });
947
+
807
948
  test("ingestBulkImportBatch preserves partial metadata failure before a later slice rejects", async () => {
808
949
  const orchestrator = Object.create(Orchestrator.prototype) as any;
809
950
  orchestrator.config = parseConfig({});
@@ -0,0 +1,125 @@
1
+ import assert from "node:assert/strict";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import test from "node:test";
6
+
7
+ import { runCommandWithTimeoutForTest } from "./qmd.js";
8
+
9
+ // Regression for issue #2456: on timeout/abort the bounded qmd subprocess
10
+ // launcher must be killed together with its descendants. The launcher runs
11
+ // detached (own process group) so a negative-pid signal reaches grandchildren
12
+ // the old child.kill("SIGKILL") left behind. POSIX only — Windows falls back
13
+ // to a direct child kill by design.
14
+ //
15
+ // Real timers by design: this suite kills real OS processes, so the timeout
16
+ // trigger and the exit polls run against the platform clock. Fake timers
17
+ // cannot deliver signals to a spawned `sleep`. Every poll awaits a condition
18
+ // (pid file exists, process gone), never a fixed sleep.
19
+ const isPosix = process.platform !== "win32";
20
+
21
+ function isAlive(pid: number): boolean {
22
+ try {
23
+ process.kill(pid, 0);
24
+ return true;
25
+ } catch (err) {
26
+ return (err as NodeJS.ErrnoException).code === "EPERM";
27
+ }
28
+ }
29
+
30
+ async function waitForExit(pid: number, deadlineMs = 10_000): Promise<boolean> {
31
+ const deadline = Date.now() + deadlineMs;
32
+ while (Date.now() < deadline) {
33
+ if (!isAlive(pid)) return true;
34
+ await new Promise((resolve) => setTimeout(resolve, 50));
35
+ }
36
+ return !isAlive(pid);
37
+ }
38
+
39
+ async function setupFakeLauncher(): Promise<{ script: string; pidFile: string; dir: string }> {
40
+ const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "remnic-qmd-kill-"));
41
+ const script = path.join(dir, "fake-qmd");
42
+ const pidFile = path.join(dir, "grandchild.pid");
43
+ // Launcher spawns a grandchild that outlives it, then hangs until killed.
44
+ await fs.promises.writeFile(
45
+ script,
46
+ `#!/bin/sh\nsleep 30 &\necho $! > "${pidFile}"\nsleep 30\n`,
47
+ { mode: 0o755 },
48
+ );
49
+ return { script, pidFile, dir };
50
+ }
51
+
52
+ async function readPidFile(pidFile: string, deadlineMs = 5_000): Promise<number> {
53
+ const deadline = Date.now() + deadlineMs;
54
+ for (;;) {
55
+ try {
56
+ const text = await fs.promises.readFile(pidFile, "utf8");
57
+ const pid = Number.parseInt(text.trim(), 10);
58
+ assert.ok(Number.isInteger(pid) && pid > 0, `bad grandchild pid: ${text}`);
59
+ return pid;
60
+ } catch (err) {
61
+ if (Date.now() >= deadline) throw err;
62
+ await new Promise((resolve) => setTimeout(resolve, 25));
63
+ }
64
+ }
65
+ }
66
+
67
+ async function cleanupSurvivors(pids: Array<number | undefined>, dir?: string): Promise<void> {
68
+ for (const pid of pids) {
69
+ if (pid === undefined || !isAlive(pid)) continue;
70
+ try {
71
+ process.kill(pid, "SIGKILL");
72
+ } catch {
73
+ // Already gone.
74
+ }
75
+ }
76
+ if (dir) await fs.promises.rm(dir, { recursive: true, force: true });
77
+ }
78
+
79
+ test(
80
+ "timeout kills qmd launcher process group including grandchildren",
81
+ { skip: isPosix ? false : "POSIX process groups do not exist on Windows" },
82
+ async () => {
83
+ const { script, pidFile, dir } = await setupFakeLauncher();
84
+ let grandchildPid: number | undefined;
85
+ try {
86
+ await assert.rejects(
87
+ runCommandWithTimeoutForTest(script, [], { timeoutMs: 500, label: "qmd fake" }),
88
+ (err: Error & { timedOut?: boolean }) => err.timedOut === true,
89
+ );
90
+ grandchildPid = await readPidFile(pidFile);
91
+ assert.ok(
92
+ await waitForExit(grandchildPid),
93
+ `grandchild ${grandchildPid} survived the group kill`,
94
+ );
95
+ } finally {
96
+ await cleanupSurvivors([grandchildPid], dir);
97
+ }
98
+ },
99
+ );
100
+
101
+ test(
102
+ "abort kills qmd launcher process group including grandchildren",
103
+ { skip: isPosix ? false : "POSIX process groups do not exist on Windows" },
104
+ async () => {
105
+ const { script, pidFile, dir } = await setupFakeLauncher();
106
+ let grandchildPid: number | undefined;
107
+ const controller = new AbortController();
108
+ try {
109
+ const run = runCommandWithTimeoutForTest(script, [], {
110
+ timeoutMs: 60_000,
111
+ signal: controller.signal,
112
+ label: "qmd fake",
113
+ });
114
+ setTimeout(() => controller.abort(), 300);
115
+ await assert.rejects(run, /aborted/);
116
+ grandchildPid = await readPidFile(pidFile);
117
+ assert.ok(
118
+ await waitForExit(grandchildPid),
119
+ `grandchild ${grandchildPid} survived the group kill`,
120
+ );
121
+ } finally {
122
+ await cleanupSurvivors([grandchildPid], dir);
123
+ }
124
+ },
125
+ );
package/src/qmd.ts CHANGED
@@ -562,6 +562,11 @@ function runCommandWithTimeout(
562
562
  const child = launchProcess(command, args, {
563
563
  env: mergeEnv({ NO_COLOR: "1", ...options.env }),
564
564
  stdio: ["ignore", "pipe", "pipe"],
565
+ // POSIX: own process group so a timeout/abort kill can take down qmd's
566
+ // grandchildren too (launcher scripts spawn workers that outlive the
567
+ // launcher itself). Issue #2456. Windows ignores this option here —
568
+ // kill falls back to a direct child.kill.
569
+ detached: process.platform !== "win32",
565
570
  });
566
571
  if (!child.stdout || !child.stderr) {
567
572
  reject(new Error(`${label} failed to open stdio pipes`));
@@ -575,7 +580,7 @@ function runCommandWithTimeout(
575
580
  const timer = setTimeout(() => {
576
581
  settled = true;
577
582
  cleanup();
578
- child.kill("SIGKILL");
583
+ killBoundedChild(child);
579
584
  // Flag the deadline breach so classifyProbeFailure can tell a genuine
580
585
  // timeout from a non-zero-exit error whose message merely embeds stderr.
581
586
  reject(
@@ -589,7 +594,7 @@ function runCommandWithTimeout(
589
594
  settled = true;
590
595
  clearTimeout(timer);
591
596
  cleanup();
592
- child.kill("SIGKILL");
597
+ killBoundedChild(child);
593
598
  reject(abortError(`${label} aborted`));
594
599
  };
595
600
  const cleanup = () => {
@@ -636,6 +641,29 @@ function runProcessCommand(
636
641
  });
637
642
  }
638
643
 
644
+ // Kill a bounded subprocess and its descendants. On POSIX the child runs
645
+ // detached (own process group), so a negative-pid signal reaches the whole
646
+ // tree. Fall back to a direct kill on Windows, missing pid, or when the
647
+ // group is already gone. Never throws — callers use it on best-effort paths.
648
+ function killBoundedChild(child: CommandChildProcess): void {
649
+ const pid = child.pid;
650
+ if (pid !== undefined && process.platform !== "win32") {
651
+ try {
652
+ process.kill(-pid, "SIGKILL");
653
+ return;
654
+ } catch {
655
+ // Group already reaped or never became a leader — fall through.
656
+ }
657
+ }
658
+ try {
659
+ child.kill("SIGKILL");
660
+ } catch {
661
+ // Already exited between the check and the kill.
662
+ }
663
+ }
664
+
665
+ export { runCommandWithTimeout as runCommandWithTimeoutForTest };
666
+
639
667
  // ---------------------------------------------------------------------------
640
668
  // QMD Stdio Daemon Session (MCP over stdio child process)
641
669
  // ---------------------------------------------------------------------------