@remnic/core 10.0.73 → 10.0.75

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 (326) hide show
  1. package/dist/access-admin-ops-surface.js +49 -48
  2. package/dist/access-authorization-probe.js +50 -49
  3. package/dist/access-boundary.js +49 -48
  4. package/dist/access-cli.js +105 -104
  5. package/dist/access-cli.js.map +1 -1
  6. package/dist/access-extraction-force-flush.js +49 -48
  7. package/dist/access-http-briefing.js +50 -49
  8. package/dist/access-http-query.js +51 -50
  9. package/dist/access-http.js +68 -67
  10. package/dist/access-identity-continuity-surface.js +49 -48
  11. package/dist/access-lcm-surface.js +49 -48
  12. package/dist/access-mcp.js +58 -57
  13. package/dist/access-namespace-preflight.js +49 -48
  14. package/dist/access-observe-helpers.d.ts.map +1 -1
  15. package/dist/access-observe-helpers.js +1 -1
  16. package/dist/access-observe-write-category.js +6 -6
  17. package/dist/access-observe-write-surface.js +49 -48
  18. package/dist/access-offline-manifest.js +5 -5
  19. package/dist/access-operations-batch.js +50 -49
  20. package/dist/access-operations.js +57 -56
  21. package/dist/access-recall-concurrency.js +49 -48
  22. package/dist/access-recall-response.js +49 -48
  23. package/dist/access-recall-surface.js +49 -48
  24. package/dist/access-schema.js +5 -5
  25. package/dist/access-service.js +49 -48
  26. package/dist/access-surface-catalog.js +51 -50
  27. package/dist/access-surface-catalog.js.map +1 -1
  28. package/dist/active-recall.js +18 -18
  29. package/dist/adapters/index.js +4 -4
  30. package/dist/adapters/registry.js +2 -2
  31. package/dist/{auto-sync-ANOJDQD4.js → auto-sync-XHSS3XPL.js} +5 -5
  32. package/dist/briefing.js +15 -15
  33. package/dist/buffer-turn-helpers.d.ts +18 -0
  34. package/dist/buffer-turn-helpers.d.ts.map +1 -1
  35. package/dist/buffer-turn-helpers.js +5 -1
  36. package/dist/buffer.js +4 -4
  37. package/dist/calibration.js +5 -4
  38. package/dist/calibration.js.map +1 -1
  39. package/dist/causal-consolidation.js +16 -16
  40. package/dist/{chunk-7WFY2L5J.js → chunk-233S3MXC.js} +3 -3
  41. package/dist/{chunk-N7NAGECH.js → chunk-36B5VPRU.js} +2 -2
  42. package/dist/{chunk-5ORJHH43.js → chunk-3GGCMRO7.js} +2 -2
  43. package/dist/{chunk-RZED7FAU.js → chunk-3M7YP42P.js} +2 -2
  44. package/dist/{chunk-ICBUTKLL.js → chunk-3MBSD53O.js} +2 -2
  45. package/dist/{chunk-J72ZY7NU.js → chunk-4DLZ2ITE.js} +2 -2
  46. package/dist/{chunk-OUBEJWVI.js → chunk-4KLQI3L2.js} +42 -1
  47. package/dist/chunk-4KLQI3L2.js.map +1 -0
  48. package/dist/{chunk-WN2DASTS.js → chunk-5BE3FLKL.js} +4 -4
  49. package/dist/{chunk-S73ZVU37.js → chunk-5IQL4N37.js} +10 -10
  50. package/dist/{chunk-CIL4GHMD.js → chunk-6Q6AQ36B.js} +2 -2
  51. package/dist/{chunk-W4V7P5HP.js → chunk-6RPPZL7W.js} +2 -2
  52. package/dist/{chunk-EQ6EMJRG.js → chunk-6Y5ANLDH.js} +2 -2
  53. package/dist/{chunk-YPYSC3TX.js → chunk-7FFGWIBN.js} +2 -2
  54. package/dist/{chunk-VNTQU6KB.js → chunk-7PMOCQOL.js} +8 -13
  55. package/dist/chunk-7PMOCQOL.js.map +1 -0
  56. package/dist/{chunk-5RW247F5.js → chunk-AY2UK5XK.js} +14 -9
  57. package/dist/chunk-AY2UK5XK.js.map +1 -0
  58. package/dist/{chunk-5QV6CE6K.js → chunk-B36ACJG7.js} +2 -2
  59. package/dist/{chunk-ZBET3W26.js → chunk-BZTA7DR4.js} +3 -3
  60. package/dist/{chunk-JY4LXMZR.js → chunk-C44DRDZM.js} +2 -2
  61. package/dist/{chunk-UIFWZMO7.js → chunk-CLVILSBR.js} +7 -7
  62. package/dist/{chunk-OGQGDOYK.js → chunk-COIQ5N4J.js} +2 -2
  63. package/dist/{chunk-UAOJVQXC.js → chunk-DANFOVQG.js} +2 -2
  64. package/dist/{chunk-2R44BBPK.js → chunk-DEGE32C3.js} +2 -2
  65. package/dist/{chunk-YIJECSIX.js → chunk-DJDJBMFX.js} +9 -2
  66. package/dist/chunk-DJDJBMFX.js.map +1 -0
  67. package/dist/{chunk-LHMCZ6VW.js → chunk-E22CQZ3H.js} +14 -14
  68. package/dist/{chunk-JGLZ46JS.js → chunk-ELUG6NKQ.js} +2 -2
  69. package/dist/{chunk-X4DHAXFO.js → chunk-F3FNXE7C.js} +3 -3
  70. package/dist/{chunk-YIXWY4OZ.js → chunk-FETPTCQZ.js} +2 -2
  71. package/dist/{chunk-N2Y6COEM.js → chunk-FGUWCE5I.js} +2 -2
  72. package/dist/{chunk-FDUCIUQ5.js → chunk-FJ2VJTGA.js} +2 -2
  73. package/dist/{chunk-ENPJX3BB.js → chunk-FMFCQRDM.js} +3 -3
  74. package/dist/{chunk-BNU5TQLR.js → chunk-FOQ7TW2T.js} +2 -2
  75. package/dist/{chunk-4PMFSYH7.js → chunk-FP2AT4WL.js} +2 -2
  76. package/dist/{chunk-DLZGSZVC.js → chunk-FYQUUIPI.js} +4 -4
  77. package/dist/{chunk-XTN54NML.js → chunk-G3T6YMBO.js} +4 -4
  78. package/dist/{chunk-MUH7RR6T.js → chunk-G5NH3SSX.js} +5 -5
  79. package/dist/{chunk-DC3DWF2M.js → chunk-GCJA5KKT.js} +2 -2
  80. package/dist/{chunk-U6IL4724.js → chunk-GWOFCHPN.js} +2 -2
  81. package/dist/{chunk-TJ5YGCPV.js → chunk-GXVK754R.js} +2 -2
  82. package/dist/{chunk-FGGLVCHI.js → chunk-HB5TNQMM.js} +2 -2
  83. package/dist/{chunk-GXAZ4Q2S.js → chunk-HCFNZWFB.js} +2 -2
  84. package/dist/{chunk-F4ZHKDBW.js → chunk-HLWXCDUQ.js} +2 -2
  85. package/dist/{chunk-IQXQ3KWR.js → chunk-HTDXNIO6.js} +2 -2
  86. package/dist/{chunk-4RASUAN3.js → chunk-HXFIILGF.js} +4 -4
  87. package/dist/{chunk-S37O4S6E.js → chunk-I4LJJ44E.js} +2 -2
  88. package/dist/{chunk-USX5YIKB.js → chunk-IBQ7LGUJ.js} +6 -6
  89. package/dist/{chunk-UKQFCOGT.js → chunk-IPHIIRDN.js} +3 -3
  90. package/dist/chunk-IPHIIRDN.js.map +1 -0
  91. package/dist/{chunk-VSJ3ARFS.js → chunk-IRFQKR4Y.js} +2 -2
  92. package/dist/{chunk-BCBY5GYZ.js → chunk-IVXOYE43.js} +2 -2
  93. package/dist/{chunk-WXPBDEMW.js → chunk-JDQEQE2R.js} +2 -2
  94. package/dist/{chunk-2WPWEVAH.js → chunk-KAED7IBB.js} +2 -2
  95. package/dist/{chunk-OWZXDBV4.js → chunk-KNZDGZSY.js} +2 -2
  96. package/dist/{chunk-6KBCUIZP.js → chunk-L4QYW2HO.js} +2 -2
  97. package/dist/{chunk-LYOCAUI6.js → chunk-LJUQPT5G.js} +3 -3
  98. package/dist/{chunk-XPZM7GP6.js → chunk-MBENRPMY.js} +10 -10
  99. package/dist/chunk-MJFL75J6.js +42 -0
  100. package/dist/chunk-MJFL75J6.js.map +1 -0
  101. package/dist/{chunk-VK5HKVQ7.js → chunk-N6GEGRCT.js} +2 -2
  102. package/dist/{chunk-U2A56LLF.js → chunk-OBQ2NZAR.js} +10 -10
  103. package/dist/{chunk-WGRAB3KB.js → chunk-ORVPS4ZW.js} +2 -2
  104. package/dist/{chunk-PFUXPTBT.js → chunk-P3AQ2YSX.js} +4 -4
  105. package/dist/{chunk-DE4U5PTV.js → chunk-P4TEQSIZ.js} +2 -2
  106. package/dist/{chunk-UE5JYPU6.js → chunk-P7HNMNN2.js} +7 -7
  107. package/dist/{chunk-ZG4GNPRE.js → chunk-PBE7NFB5.js} +5 -5
  108. package/dist/{chunk-LN7CWLAU.js → chunk-PLGBPFEO.js} +2 -2
  109. package/dist/{chunk-5MA7ZLPD.js → chunk-QNUNAXBE.js} +35 -1
  110. package/dist/chunk-QNUNAXBE.js.map +1 -0
  111. package/dist/{chunk-23BA3USG.js → chunk-R24RYSQX.js} +43 -43
  112. package/dist/{chunk-WYOHQJKC.js → chunk-RXGFITWS.js} +4 -4
  113. package/dist/{chunk-JEX2JL3U.js → chunk-SHVLNI4L.js} +2 -2
  114. package/dist/{chunk-QUPTDKSX.js → chunk-SY4AFWUZ.js} +31 -31
  115. package/dist/{chunk-H2X32F2K.js → chunk-SYDPJUFR.js} +2 -2
  116. package/dist/{chunk-QIOQI7QZ.js → chunk-SZBZOUHR.js} +2 -2
  117. package/dist/{chunk-EGKDONTC.js → chunk-T6C3G67T.js} +10 -10
  118. package/dist/chunk-T6C3G67T.js.map +1 -0
  119. package/dist/{chunk-E3NBMTTX.js → chunk-TLTBACYX.js} +2 -2
  120. package/dist/{chunk-AVVCPA7U.js → chunk-UCPH56YX.js} +2 -2
  121. package/dist/{chunk-ZODKV27J.js → chunk-URS56HGW.js} +6 -6
  122. package/dist/{chunk-PJAMFIDE.js → chunk-VEQVXNWR.js} +4 -4
  123. package/dist/{chunk-AGRMXXVA.js → chunk-VEWTHVX2.js} +2 -2
  124. package/dist/{chunk-WNQ3B3Q5.js → chunk-VPEMGBTI.js} +2 -2
  125. package/dist/{chunk-DSSTOV5M.js → chunk-VYOAYFDV.js} +66 -49
  126. package/dist/chunk-VYOAYFDV.js.map +1 -0
  127. package/dist/{chunk-5JBVPJCT.js → chunk-WIAXVBQH.js} +2 -2
  128. package/dist/{chunk-BTU77RB5.js → chunk-X53HJENJ.js} +2 -2
  129. package/dist/{chunk-KY3KJUNN.js → chunk-XC7OYOQC.js} +6 -6
  130. package/dist/{chunk-43FLJLT2.js → chunk-Y5HZFIQO.js} +1 -1
  131. package/dist/{chunk-DOJBV6WR.js → chunk-YK6SFDP2.js} +4 -4
  132. package/dist/{chunk-QVZZE6VH.js → chunk-Z3CKK5RR.js} +3 -3
  133. package/dist/{chunk-AZNE2H6D.js → chunk-ZG57BEUS.js} +2 -2
  134. package/dist/{chunk-7P5VBRBS.js → chunk-ZZSFRWKD.js} +2 -2
  135. package/dist/cli.js +81 -80
  136. package/dist/coding/export-okf-codegraph.js +14 -14
  137. package/dist/compounding/engine.js +16 -16
  138. package/dist/config.js +18 -18
  139. package/dist/connectors/codex-materialize-runner.js +15 -15
  140. package/dist/connectors/index.js +15 -15
  141. package/dist/convergence-refresh.js +17 -17
  142. package/dist/corpus-watermark.js +17 -17
  143. package/dist/deep-recall-http-glue.js +6 -6
  144. package/dist/deep-recall-policy-llm.js +3 -3
  145. package/dist/deep-recall.js +2 -2
  146. package/dist/entity-retrieval.js +15 -15
  147. package/dist/external-wiki-access.js +50 -49
  148. package/dist/extraction-faithfulness.js +3 -3
  149. package/dist/extraction-judge.js +3 -3
  150. package/dist/extraction.js +23 -22
  151. package/dist/fallback-llm-codex-subscription.d.ts.map +1 -1
  152. package/dist/fallback-llm-codex-subscription.js +1 -1
  153. package/dist/fallback-llm.js +2 -2
  154. package/dist/{first-start-migration-A6PHIBUQ.js → first-start-migration-QVD22WWS.js} +4 -4
  155. package/dist/harmonic-retrieval.js +15 -15
  156. package/dist/index.js +130 -129
  157. package/dist/lcm/engine.js +2 -2
  158. package/dist/lcm/index.js +5 -5
  159. package/dist/local-llm-native-schema.d.ts +50 -0
  160. package/dist/local-llm-native-schema.d.ts.map +1 -0
  161. package/dist/local-llm-native-schema.js +8 -0
  162. package/dist/local-llm-native-schema.js.map +1 -0
  163. package/dist/local-llm.d.ts +1 -0
  164. package/dist/local-llm.d.ts.map +1 -1
  165. package/dist/local-llm.js +4 -3
  166. package/dist/location/index.js +17 -17
  167. package/dist/maintenance/memory-governance.js +15 -15
  168. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +15 -15
  169. package/dist/maintenance/rebuild-memory-projection-runner.js +17 -17
  170. package/dist/maintenance/rebuild-memory-projection.js +16 -16
  171. package/dist/memory-browse-operations.js +50 -49
  172. package/dist/memory-browse.js +3 -3
  173. package/dist/model-registry.d.ts +11 -0
  174. package/dist/model-registry.d.ts.map +1 -1
  175. package/dist/model-registry.js +1 -1
  176. package/dist/namespaces/migrate.js +16 -16
  177. package/dist/namespaces/search.js +7 -7
  178. package/dist/namespaces/storage.js +15 -15
  179. package/dist/offline-sync-changeset-generations.js +3 -3
  180. package/dist/offline-sync-embedding-generation.js +2 -2
  181. package/dist/offline-sync-snapshot-stream.js +5 -5
  182. package/dist/offline-sync.js +7 -7
  183. package/dist/operator-doctor-corpus.js +18 -18
  184. package/dist/operator-toolkit.js +27 -27
  185. package/dist/orchestration/backlog-scheduler.d.ts +8 -0
  186. package/dist/orchestration/backlog-scheduler.d.ts.map +1 -1
  187. package/dist/orchestration/backlog-scheduler.js +118 -106
  188. package/dist/orchestration/backlog-scheduler.js.map +1 -1
  189. package/dist/orchestration/buffer-drain-runner.js +106 -105
  190. package/dist/orchestration/extraction-queue-coordinator.d.ts +6 -0
  191. package/dist/orchestration/extraction-queue-coordinator.d.ts.map +1 -1
  192. package/dist/orchestration/maintenance.js +28 -27
  193. package/dist/orchestrator.js +105 -104
  194. package/dist/recall-navigation-operations.js +50 -49
  195. package/dist/recall-navigation.js +49 -48
  196. package/dist/recall-planner-llm.js +2 -2
  197. package/dist/recall-state-view-admission.js +3 -3
  198. package/dist/recall-why-service.js +3 -3
  199. package/dist/resume-bundles.js +18 -18
  200. package/dist/search/factory.js +6 -6
  201. package/dist/search/index.js +11 -11
  202. package/dist/search/lancedb-backend.js +2 -2
  203. package/dist/search/meilisearch-backend.js +2 -2
  204. package/dist/search/orama-backend.js +2 -2
  205. package/dist/semantic-consolidation.js +16 -16
  206. package/dist/semantic-rule-promotion.js +15 -15
  207. package/dist/semantic-rule-verifier.js +15 -15
  208. package/dist/source-agent-qualifier.js +18 -18
  209. package/dist/standup/build.js +15 -15
  210. package/dist/storage/buffer-drain-claims.d.ts.map +1 -1
  211. package/dist/storage/buffer-drain-claims.js +2 -2
  212. package/dist/storage/buffer-session-files.js +2 -2
  213. package/dist/storage.js +14 -14
  214. package/dist/summarizer.js +7 -6
  215. package/dist/support-passport/index.js +52 -51
  216. package/dist/transfer/export-okf.js +15 -15
  217. package/dist/verified-recall.js +15 -15
  218. package/dist/who-knows-cli.js +16 -16
  219. package/dist/who-knows.js +15 -15
  220. package/package.json +2 -2
  221. package/src/access-observe-acceptance-boundary.test.ts +24 -18
  222. package/src/access-observe-helpers.test.ts +72 -0
  223. package/src/access-observe-helpers.ts +5 -2
  224. package/src/access-service-observe-lcm-parity.test.ts +9 -2
  225. package/src/access-service-observe-scope.test.ts +8 -1
  226. package/src/backlog-scheduler.test.ts +104 -0
  227. package/src/buffer-record-snapshots.test.ts +195 -0
  228. package/src/buffer-turn-helpers.test.ts +205 -0
  229. package/src/buffer-turn-helpers.ts +62 -0
  230. package/src/buffer.ts +8 -8
  231. package/src/fallback-llm-codex-subscription.ts +10 -1
  232. package/src/local-llm-json-schema-fallback.test.ts +315 -0
  233. package/src/local-llm-native-schema.ts +78 -0
  234. package/src/local-llm.ts +8 -9
  235. package/src/model-registry.ts +40 -0
  236. package/src/orchestration/backlog-scheduler.ts +32 -0
  237. package/src/orchestration/extraction-queue-coordinator.ts +18 -1
  238. package/src/orchestrator-extraction-queue.test.ts +142 -0
  239. package/src/storage/buffer-drain-claims.ts +5 -11
  240. package/dist/chunk-5MA7ZLPD.js.map +0 -1
  241. package/dist/chunk-5RW247F5.js.map +0 -1
  242. package/dist/chunk-DSSTOV5M.js.map +0 -1
  243. package/dist/chunk-EGKDONTC.js.map +0 -1
  244. package/dist/chunk-OUBEJWVI.js.map +0 -1
  245. package/dist/chunk-UKQFCOGT.js.map +0 -1
  246. package/dist/chunk-VNTQU6KB.js.map +0 -1
  247. package/dist/chunk-YIJECSIX.js.map +0 -1
  248. /package/dist/{auto-sync-ANOJDQD4.js.map → auto-sync-XHSS3XPL.js.map} +0 -0
  249. /package/dist/{chunk-7WFY2L5J.js.map → chunk-233S3MXC.js.map} +0 -0
  250. /package/dist/{chunk-N7NAGECH.js.map → chunk-36B5VPRU.js.map} +0 -0
  251. /package/dist/{chunk-5ORJHH43.js.map → chunk-3GGCMRO7.js.map} +0 -0
  252. /package/dist/{chunk-RZED7FAU.js.map → chunk-3M7YP42P.js.map} +0 -0
  253. /package/dist/{chunk-ICBUTKLL.js.map → chunk-3MBSD53O.js.map} +0 -0
  254. /package/dist/{chunk-J72ZY7NU.js.map → chunk-4DLZ2ITE.js.map} +0 -0
  255. /package/dist/{chunk-WN2DASTS.js.map → chunk-5BE3FLKL.js.map} +0 -0
  256. /package/dist/{chunk-S73ZVU37.js.map → chunk-5IQL4N37.js.map} +0 -0
  257. /package/dist/{chunk-CIL4GHMD.js.map → chunk-6Q6AQ36B.js.map} +0 -0
  258. /package/dist/{chunk-W4V7P5HP.js.map → chunk-6RPPZL7W.js.map} +0 -0
  259. /package/dist/{chunk-EQ6EMJRG.js.map → chunk-6Y5ANLDH.js.map} +0 -0
  260. /package/dist/{chunk-YPYSC3TX.js.map → chunk-7FFGWIBN.js.map} +0 -0
  261. /package/dist/{chunk-5QV6CE6K.js.map → chunk-B36ACJG7.js.map} +0 -0
  262. /package/dist/{chunk-ZBET3W26.js.map → chunk-BZTA7DR4.js.map} +0 -0
  263. /package/dist/{chunk-JY4LXMZR.js.map → chunk-C44DRDZM.js.map} +0 -0
  264. /package/dist/{chunk-UIFWZMO7.js.map → chunk-CLVILSBR.js.map} +0 -0
  265. /package/dist/{chunk-OGQGDOYK.js.map → chunk-COIQ5N4J.js.map} +0 -0
  266. /package/dist/{chunk-UAOJVQXC.js.map → chunk-DANFOVQG.js.map} +0 -0
  267. /package/dist/{chunk-2R44BBPK.js.map → chunk-DEGE32C3.js.map} +0 -0
  268. /package/dist/{chunk-LHMCZ6VW.js.map → chunk-E22CQZ3H.js.map} +0 -0
  269. /package/dist/{chunk-JGLZ46JS.js.map → chunk-ELUG6NKQ.js.map} +0 -0
  270. /package/dist/{chunk-X4DHAXFO.js.map → chunk-F3FNXE7C.js.map} +0 -0
  271. /package/dist/{chunk-YIXWY4OZ.js.map → chunk-FETPTCQZ.js.map} +0 -0
  272. /package/dist/{chunk-N2Y6COEM.js.map → chunk-FGUWCE5I.js.map} +0 -0
  273. /package/dist/{chunk-FDUCIUQ5.js.map → chunk-FJ2VJTGA.js.map} +0 -0
  274. /package/dist/{chunk-ENPJX3BB.js.map → chunk-FMFCQRDM.js.map} +0 -0
  275. /package/dist/{chunk-BNU5TQLR.js.map → chunk-FOQ7TW2T.js.map} +0 -0
  276. /package/dist/{chunk-4PMFSYH7.js.map → chunk-FP2AT4WL.js.map} +0 -0
  277. /package/dist/{chunk-DLZGSZVC.js.map → chunk-FYQUUIPI.js.map} +0 -0
  278. /package/dist/{chunk-XTN54NML.js.map → chunk-G3T6YMBO.js.map} +0 -0
  279. /package/dist/{chunk-MUH7RR6T.js.map → chunk-G5NH3SSX.js.map} +0 -0
  280. /package/dist/{chunk-DC3DWF2M.js.map → chunk-GCJA5KKT.js.map} +0 -0
  281. /package/dist/{chunk-U6IL4724.js.map → chunk-GWOFCHPN.js.map} +0 -0
  282. /package/dist/{chunk-TJ5YGCPV.js.map → chunk-GXVK754R.js.map} +0 -0
  283. /package/dist/{chunk-FGGLVCHI.js.map → chunk-HB5TNQMM.js.map} +0 -0
  284. /package/dist/{chunk-GXAZ4Q2S.js.map → chunk-HCFNZWFB.js.map} +0 -0
  285. /package/dist/{chunk-F4ZHKDBW.js.map → chunk-HLWXCDUQ.js.map} +0 -0
  286. /package/dist/{chunk-IQXQ3KWR.js.map → chunk-HTDXNIO6.js.map} +0 -0
  287. /package/dist/{chunk-4RASUAN3.js.map → chunk-HXFIILGF.js.map} +0 -0
  288. /package/dist/{chunk-S37O4S6E.js.map → chunk-I4LJJ44E.js.map} +0 -0
  289. /package/dist/{chunk-USX5YIKB.js.map → chunk-IBQ7LGUJ.js.map} +0 -0
  290. /package/dist/{chunk-VSJ3ARFS.js.map → chunk-IRFQKR4Y.js.map} +0 -0
  291. /package/dist/{chunk-BCBY5GYZ.js.map → chunk-IVXOYE43.js.map} +0 -0
  292. /package/dist/{chunk-WXPBDEMW.js.map → chunk-JDQEQE2R.js.map} +0 -0
  293. /package/dist/{chunk-2WPWEVAH.js.map → chunk-KAED7IBB.js.map} +0 -0
  294. /package/dist/{chunk-OWZXDBV4.js.map → chunk-KNZDGZSY.js.map} +0 -0
  295. /package/dist/{chunk-6KBCUIZP.js.map → chunk-L4QYW2HO.js.map} +0 -0
  296. /package/dist/{chunk-LYOCAUI6.js.map → chunk-LJUQPT5G.js.map} +0 -0
  297. /package/dist/{chunk-XPZM7GP6.js.map → chunk-MBENRPMY.js.map} +0 -0
  298. /package/dist/{chunk-VK5HKVQ7.js.map → chunk-N6GEGRCT.js.map} +0 -0
  299. /package/dist/{chunk-U2A56LLF.js.map → chunk-OBQ2NZAR.js.map} +0 -0
  300. /package/dist/{chunk-WGRAB3KB.js.map → chunk-ORVPS4ZW.js.map} +0 -0
  301. /package/dist/{chunk-PFUXPTBT.js.map → chunk-P3AQ2YSX.js.map} +0 -0
  302. /package/dist/{chunk-DE4U5PTV.js.map → chunk-P4TEQSIZ.js.map} +0 -0
  303. /package/dist/{chunk-UE5JYPU6.js.map → chunk-P7HNMNN2.js.map} +0 -0
  304. /package/dist/{chunk-ZG4GNPRE.js.map → chunk-PBE7NFB5.js.map} +0 -0
  305. /package/dist/{chunk-LN7CWLAU.js.map → chunk-PLGBPFEO.js.map} +0 -0
  306. /package/dist/{chunk-23BA3USG.js.map → chunk-R24RYSQX.js.map} +0 -0
  307. /package/dist/{chunk-WYOHQJKC.js.map → chunk-RXGFITWS.js.map} +0 -0
  308. /package/dist/{chunk-JEX2JL3U.js.map → chunk-SHVLNI4L.js.map} +0 -0
  309. /package/dist/{chunk-QUPTDKSX.js.map → chunk-SY4AFWUZ.js.map} +0 -0
  310. /package/dist/{chunk-H2X32F2K.js.map → chunk-SYDPJUFR.js.map} +0 -0
  311. /package/dist/{chunk-QIOQI7QZ.js.map → chunk-SZBZOUHR.js.map} +0 -0
  312. /package/dist/{chunk-E3NBMTTX.js.map → chunk-TLTBACYX.js.map} +0 -0
  313. /package/dist/{chunk-AVVCPA7U.js.map → chunk-UCPH56YX.js.map} +0 -0
  314. /package/dist/{chunk-ZODKV27J.js.map → chunk-URS56HGW.js.map} +0 -0
  315. /package/dist/{chunk-PJAMFIDE.js.map → chunk-VEQVXNWR.js.map} +0 -0
  316. /package/dist/{chunk-AGRMXXVA.js.map → chunk-VEWTHVX2.js.map} +0 -0
  317. /package/dist/{chunk-WNQ3B3Q5.js.map → chunk-VPEMGBTI.js.map} +0 -0
  318. /package/dist/{chunk-5JBVPJCT.js.map → chunk-WIAXVBQH.js.map} +0 -0
  319. /package/dist/{chunk-BTU77RB5.js.map → chunk-X53HJENJ.js.map} +0 -0
  320. /package/dist/{chunk-KY3KJUNN.js.map → chunk-XC7OYOQC.js.map} +0 -0
  321. /package/dist/{chunk-43FLJLT2.js.map → chunk-Y5HZFIQO.js.map} +0 -0
  322. /package/dist/{chunk-DOJBV6WR.js.map → chunk-YK6SFDP2.js.map} +0 -0
  323. /package/dist/{chunk-QVZZE6VH.js.map → chunk-Z3CKK5RR.js.map} +0 -0
  324. /package/dist/{chunk-AZNE2H6D.js.map → chunk-ZG57BEUS.js.map} +0 -0
  325. /package/dist/{chunk-7P5VBRBS.js.map → chunk-ZZSFRWKD.js.map} +0 -0
  326. /package/dist/{first-start-migration-A6PHIBUQ.js.map → first-start-migration-QVD22WWS.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/buffer.ts"],"sourcesContent":["import { log } from \"./logger.js\";\nimport { throwIfAborted } from \"./abort-error.js\";\nimport { scanSignals } from \"./signal.js\";\nimport { BufferSessionPersistence, type BufferStorage } from \"./buffer-persistence.js\";\nimport { selectEvictedBufferKeys } from \"./buffer-eviction.js\";\nimport type {\n BufferEntryState,\n BufferState,\n BufferSurpriseEvent,\n BufferTurn,\n PluginConfig,\n SignalLevel,\n} from \"./types.js\";\nimport { resolvePresentationCapabilities, resolveNamespaceCapabilities } from \"./capabilities.js\";\nimport { resolvePrincipal } from \"./namespaces/principal.js\";\nimport { scopeProfilePlanIdentity, subtractBufferTurns } from \"./buffer-turn-helpers.js\";\nimport { ExtractionDeadlineError } from \"./orchestration/extraction-run.js\";\nimport type { ExtractionBufferSnapshot } from \"./extraction-liveness.js\";\nimport {\n bufferEntryCounts,\n bufferTurnArrayIsSuffixOfSnapshot,\n bufferTurnArraysEqual,\n bufferTurnsEqual,\n copyBufferTurn,\n describeError,\n hasPinnedCaptureScope,\n liveTurnsFromExtractionSnapshot,\n matchingQueuedExtractionPrefixLength,\n probeWithTimeout,\n selectRetainedDeferredTurns,\n type BufferEntryCounts,\n} from \"./buffer-turn-helpers.js\";\n\nexport type TriggerDecision = \"extract_now\" | \"extract_batch\" | \"keep_buffering\";\n\nexport type { BufferSurpriseProbe } from \"./buffer-types.js\";\nimport type { BufferSurpriseProbe, SurpriseTelemetryQueueEntry } from \"./buffer-types.js\";\n\nexport interface AddTurnOutcome {\n decision: TriggerDecision;\n extractionTurns?: BufferTurn[];\n}\n\nexport interface RetainedTurnCleanupOptions {\n abortSignal?: AbortSignal;\n deadlineMs?: number;\n /** Keep cleanup within the accepted namespace. */\n writeNamespaceOverride?: string;\n /** Match the original profile and project binding, not just its write namespace. */\n scopeProfileWritePlan?: import(\"./namespaces/scope-profiles.js\").ResolvedScopeProfilePlan | null;\n codingContext?: import(\"./types.js\").CodingContext;\n}\n\n// Raised from 200 in #3303: live captures were being dropped at busy hours\n// while the single extraction worker drained a backlog.\nconst FALLBACK_MAX_BUFFER_SESSIONS = 1000;\nconst isAcceptedTurn = (t: { acceptedCapture?: boolean; writeNamespaceOverride?: string }): boolean =>\n t.acceptedCapture === true || t.writeNamespaceOverride !== undefined;\n\n/**\n * Upper bound on how long a debounced buffer save may be deferred under\n * sustained activity (issue #1909). Each new turn re-arms the trailing-edge\n * timer, so a steady stream of turns could otherwise push the save out\n * indefinitely and widen the crash-loss window without bound. Once a pending\n * save has been deferred this many debounce windows, the next scheduled save\n * forces an inline flush instead of re-arming.\n */\nconst BUFFER_SAVE_MAX_DEFER_MULTIPLIER = 5;\n\n/**\n * Node's maximum 32-bit `setTimeout` delay (2^31-1 ms, ~24.8 days). A larger\n * value is overflow-clamped by Node to 1ms with a TimeoutOverflowWarning, so we\n * clamp defensively at every timer arm (issue #1909 review round 5) even though\n * parseConfig already caps `bufferSaveDebounceMs` — a directly-constructed config\n * could still exceed it.\n */\nconst MAX_SET_TIMEOUT_MS = 2_147_483_647;\n\ninterface AddTurnMutationResult {\n decision: TriggerDecision;\n signalLevel: SignalLevel;\n priorTurns: BufferTurn[];\n activeTurnsSnapshot: BufferTurn[];\n retainedTurnsSnapshot: BufferTurn[];\n turnCountInWindow: number;\n}\n\nexport class SmartBuffer {\n private state: BufferState;\n private loaded = false;\n private loadPromise: Promise<void> | null = null;\n private readonly surpriseProbe: BufferSurpriseProbe | null;\n private mutationChain: Promise<unknown> = Promise.resolve();\n /**\n * Serialized write chain for `BUFFER_SURPRISE` telemetry events.\n *\n * The telemetry path is fire-and-forget (`addTurn` does not await the\n * ledger append), but multiple concurrent appends would still settle\n * out of order under variable filesystem latency. The report path\n * assumes chronological ordering — it slices the tail of the ledger\n * and treats the most recent entry as the current threshold in force.\n * Chaining ensures each append only runs after the previous settles,\n * preserving wall-clock order.\n *\n * We include a `.catch` on every link so a rejected append does not\n * poison the chain (CLAUDE.md rule #40).\n */\n private surpriseTelemetryWriteChain: Promise<unknown> = Promise.resolve();\n\n /**\n * Debounced buffer-save state (issue #1909). Steady-state buffering used to\n * serialize + rewrite the whole multi-session buffer on EVERY turn. We now\n * coalesce those writes onto a trailing-edge timer; correctness boundaries\n * (extraction trigger, extraction clear, shutdown) still force an immediate\n * flush. `bufferSaveDebounceMs: 0` restores save-every-turn exactly.\n */\n private saveTimer: NodeJS.Timeout | null = null;\n private pendingSave = false;\n /** Wall-clock ms when the currently-pending save was first scheduled (issue\n * #1909). Null when nothing is pending. Used to bound deferral under sustained\n * activity (see BUFFER_SAVE_MAX_DEFER_MULTIPLIER). */\n private firstPendingAtMs: number | null = null;\n\n /** Which sessions' persisted files are stale (per-session incremental saves). */\n private readonly persistence = new BufferSessionPersistence();\n\n constructor(\n private readonly config: PluginConfig,\n private readonly storage: BufferStorage,\n surpriseProbe: BufferSurpriseProbe | null = null,\n ) {\n this.state = { turns: [], lastExtractionAt: null, extractionCount: 0 };\n this.surpriseProbe = surpriseProbe;\n }\n\n /** Distinct sessions admitted before backpressure; configurable (#3303, raised from 200). */\n private get maxSessionEntries(): number {\n // parseConfig defaults, validates, and floors bufferMaxSessions at the I/O\n // boundary; FALLBACK_MAX_BUFFER_SESSIONS covers direct constructions with\n // configs from before the knob existed.\n return this.config.bufferMaxSessions ?? FALLBACK_MAX_BUFFER_SESSIONS;\n }\n\n private enqueueMutation<T>(op: () => Promise<T>): Promise<T> {\n const run = this.mutationChain.catch(() => {}).then(op);\n this.mutationChain = run.catch(() => {});\n return run;\n }\n\n private entryFor(key: string): BufferEntryState {\n if (!this.state.entries) {\n this.state.entries = Object.create(null) as NonNullable<BufferState[\"entries\"]>;\n }\n const entries = this.state.entries as NonNullable<BufferState[\"entries\"]>;\n if (Object.hasOwn(entries, key)) {\n const stored = entries[key];\n // Guard against corrupted state/buffer.json — if the stored entry\n // is not a valid object shape, discard it and recreate.\n if (stored && typeof stored === \"object\" && Array.isArray(stored.turns)) {\n return stored;\n }\n // Corrupted — fall through to recreate.\n }\n const created: BufferEntryState = {\n turns: [],\n lastExtractionAt: null,\n extractionCount: 0,\n };\n entries[key] = created;\n return created;\n }\n\n private peekEntry(key: string): BufferEntryState | null {\n const existing = this.state.entries?.[key];\n if (existing) return existing;\n if (key !== \"default\") return null;\n return {\n turns: Array.isArray(this.state.turns) ? this.state.turns : [],\n lastExtractionAt: this.state.lastExtractionAt ?? null,\n extractionCount:\n typeof this.state.extractionCount === \"number\" ? this.state.extractionCount : 0,\n };\n }\n\n private normalizeState(state: BufferState): BufferState {\n const entries = Object.assign(\n Object.create(null),\n state.entries ?? {},\n ) as NonNullable<BufferState[\"entries\"]>;\n if (!entries.default) {\n entries.default = {\n turns: Array.isArray(state.turns) ? [...state.turns] : [],\n lastExtractionAt: state.lastExtractionAt ?? null,\n extractionCount:\n typeof state.extractionCount === \"number\" ? state.extractionCount : 0,\n };\n }\n return {\n turns: entries.default.turns,\n lastExtractionAt: entries.default.lastExtractionAt,\n extractionCount: entries.default.extractionCount,\n entries,\n };\n }\n\n private entryActivityAt(entry: BufferEntryState): number {\n const lastTurnAt = entry.turns.reduce((latest, turn) => {\n const parsed = Date.parse(turn.timestamp);\n return Number.isFinite(parsed) ? Math.max(latest, parsed) : latest;\n }, -1);\n const lastExtractionAt =\n typeof entry.lastExtractionAt === \"string\"\n ? Date.parse(entry.lastExtractionAt)\n : Number.NaN;\n return Math.max(\n lastTurnAt,\n Number.isFinite(lastExtractionAt) ? lastExtractionAt : -1,\n );\n }\n\n /** Evict oldest entries past this.maxSessionEntries and queue their session files for deletion. */\n private pruneEntries(retainKeys: string[], maxCount = this.maxSessionEntries): void {\n const entries = this.state.entries;\n if (!entries) return;\n const evicted = selectEvictedBufferKeys(entries, retainKeys, maxCount, {\n activityAt: (entry) => this.entryActivityAt(entry),\n holdsAcceptedCapture: (entry) => this.entryHoldsAcceptedCapture(entry),\n });\n for (const key of evicted) delete entries[key];\n this.persistence.markEvicted(evicted);\n if (evicted.length > 0) {\n // Loud degradation (extraction hot-loop hardening, Step 7): buffer\n // pressure during a provider outage is bounded and expected, but must\n // be observable, not silent.\n log.warn(\n `buffer: pruned ${evicted.length} oldest session entr${evicted.length === 1 ? \"y\" : \"ies\"} ` +\n `past the session-entry cap (${this.maxSessionEntries}); buffered turn data for those sessions was dropped`,\n );\n }\n }\n\n private async loadUnlocked(): Promise<void> {\n if (this.loaded) return;\n if (!this.loadPromise) {\n this.loadPromise = this.storage.loadBuffer()\n .then((state) => {\n this.state = this.normalizeState(state);\n this.loaded = true;\n })\n .finally(() => {\n this.loadPromise = null;\n });\n }\n await this.loadPromise;\n }\n\n async load(): Promise<void> {\n await this.enqueueMutation(async () => this.loadUnlocked());\n }\n\n /** Snapshot of buffered, not-yet-extracted work across sessions (#2151): session count,\n * pending-turn total, oldest buffered turn. Counts retained (#562) + active turns per entry. */\n async getBufferSnapshot(): Promise<ExtractionBufferSnapshot> {\n await this.load();\n let bufferedSessionCount = 0;\n let pendingTurnCount = 0;\n let oldestMs = Number.POSITIVE_INFINITY;\n let oldestTurnTimestamp: string | null = null;\n const consider = (turns: BufferTurn[] | undefined): void => {\n if (!Array.isArray(turns) || turns.length === 0) return;\n bufferedSessionCount += 1;\n pendingTurnCount += turns.length;\n const first = turns[0]?.timestamp;\n const parsed = typeof first === \"string\" ? Date.parse(first) : Number.NaN;\n if (Number.isFinite(parsed) && parsed < oldestMs) {\n oldestMs = parsed;\n oldestTurnTimestamp = first ?? null;\n }\n };\n const entries = this.state.entries;\n if (entries) {\n // Retained turns (deferred #562) are pending too - count with active (oldest-first), like getTurns().\n for (const entry of Object.values(entries)) consider([...(entry?.retainedTurns ?? []), ...(entry?.turns ?? [])]);\n } else {\n consider(this.state.turns);\n }\n return { bufferedSessionCount, pendingTurnCount, oldestTurnTimestamp };\n }\n\n /**\n * Reset the buffer to an empty, usable state.\n * Called when the persisted buffer file is corrupt and load() fails,\n * so the buffer can still accept new turns for the rest of the session.\n */\n resetToEmpty(): void {\n for (const key of Object.keys(this.state.entries ?? {})) {\n this.persistence.markDeleted(key);\n }\n this.state = { turns: [], lastExtractionAt: null, extractionCount: 0 };\n this.loaded = true;\n }\n\n private async saveUnlocked(): Promise<void> {\n const { handedOver, drainedTurns } = await this.persistence.persist(this.storage, this.state, (key) => this.peekEntry(key) ?? undefined);\n // A drain tombstone: the turns the drain extracted leave this copy too,\n // so a stale owner never extracts (or persists) them a second time.\n for (const [key, drained] of drainedTurns) {\n const entry = this.peekEntry(key);\n if (!entry) continue;\n entry.turns = subtractBufferTurns(entry.turns, drained);\n if (entry.retainedTurns && entry.retainedTurns.length > 0) {\n entry.retainedTurns = subtractBufferTurns(entry.retainedTurns, drained);\n if (entry.retainedTurns.length === 0) delete entry.retainedTurns;\n }\n if (key === \"default\") this.state.turns = entry.turns;\n }\n if (handedOver.length === 0) return;\n // A drain claim now owns these sessions' turns (durable in the claim\n // file): drop the in-memory entries so this process cannot resurrect\n // them. A later observe for the same key starts a fresh entry whose\n // turns flow into the claim at the next handover.\n const entries = this.state.entries;\n if (!entries) return;\n for (const key of handedOver) {\n delete entries[key];\n if (key === \"default\") {\n this.state.turns = [];\n }\n }\n }\n\n /** Sync gate for the extraction queue: is this session claimed by a buffer drain? */\n isSessionClaimed(key: string): boolean {\n return this.storage.isBufferSessionClaimed?.(key) === true;\n }\n\n /** Per-key pending counts over the IN-MEMORY state (counts only — no turn\n * payloads), for the daemon backlog scheduler's bounded candidate scan. */\n entryCounts(): Map<string, BufferEntryCounts> {\n return bufferEntryCounts(this.state);\n }\n\n async save(): Promise<void> {\n await this.enqueueMutation(async () => this.saveUnlocked());\n }\n\n /**\n * Schedule a coalesced, TRUE trailing-edge buffer save (issue #1909). Only\n * used for the debounced (`bufferSaveDebounceMs > 0`) steady-state buffering\n * path; the debounce-off and correctness-boundary paths save inline within the\n * mutation (see `recordTurnUnlocked`). Each call re-arms the timer from now so\n * the write lands one full window after the LAST turn, not the first. To keep\n * sustained activity from deferring the save without bound (which would widen\n * the crash-loss window), once a pending save has been deferred\n * BUFFER_SAVE_MAX_DEFER_MULTIPLIER windows it is flushed inline instead of\n * re-armed. Runs inside the record mutation, so the inline save is awaited.\n */\n private async scheduleSave(): Promise<void> {\n const ms = Math.min(this.config.bufferSaveDebounceMs, MAX_SET_TIMEOUT_MS);\n const now = Date.now();\n this.pendingSave = true;\n if (this.firstPendingAtMs === null) this.firstPendingAtMs = now;\n if (now - this.firstPendingAtMs >= ms * BUFFER_SAVE_MAX_DEFER_MULTIPLIER) {\n // Staleness cap hit: persist now rather than deferring further. Drop the\n // armed timer, attempt the write, and only mark clean on success — a\n // failure keeps the save pending + re-arms so shutdown/the next timer\n // retries (issue #1909 review round 2), never diverging memory from disk.\n clearTimeout(this.saveTimer ?? undefined);\n this.saveTimer = null;\n try {\n await this.saveUnlocked();\n this.pendingSave = false;\n this.firstPendingAtMs = null;\n } catch (err) {\n log.warn(`buffer.scheduleSave: staleness-cap save failed, keeping it pending: ${describeError(err)}`);\n this.saveTimer = setTimeout(() => {\n this.saveTimer = null;\n void this.flushPendingSave();\n }, ms);\n this.saveTimer.unref?.();\n }\n return;\n }\n // True trailing edge: drop any armed timer and re-arm from now.\n clearTimeout(this.saveTimer ?? undefined);\n this.saveTimer = setTimeout(() => {\n this.saveTimer = null;\n void this.flushPendingSave();\n }, ms);\n this.saveTimer.unref?.();\n }\n\n /** Cancel any armed debounced save without persisting. */\n private cancelScheduledSave(): void {\n if (this.saveTimer) {\n clearTimeout(this.saveTimer);\n this.saveTimer = null;\n }\n this.pendingSave = false;\n this.firstPendingAtMs = null;\n }\n\n /**\n * Persist the current in-memory buffer state inline, keeping the global\n * pending-save state armed until the write SUCCEEDS (issue #1909). On failure\n * the save stays pending and — when debounced — a background retry timer is\n * (re-)armed before the error propagates, so a graceful shutdown flush and the\n * next timer tick both retry. A failed post-mutation save therefore can never\n * silently drop buffered turns, including turns from OTHER sessions that were\n * only in the debounced pending state. The pending flag is cleared (and any\n * armed timer dropped) only after a durable write.\n */\n private async saveNowRetainingPendingOnFailure(context: string): Promise<void> {\n this.pendingSave = true;\n if (this.firstPendingAtMs === null) this.firstPendingAtMs = Date.now();\n try {\n await this.saveUnlocked();\n } catch (err) {\n log.warn(\n `buffer.${context}: inline save failed, keeping it pending for retry: ${describeError(err)}`,\n );\n const ms = Math.min(this.config.bufferSaveDebounceMs, MAX_SET_TIMEOUT_MS);\n if (ms > 0 && !this.saveTimer) {\n this.saveTimer = setTimeout(() => {\n this.saveTimer = null;\n void this.flushPendingSave();\n }, ms);\n this.saveTimer.unref?.();\n }\n throw err;\n }\n this.cancelScheduledSave();\n }\n\n /**\n * Force any pending debounced save to land now (issue #1909). Idempotent and\n * safe to call from outside a mutation (timer tick, shutdown/dispose, or the\n * surprise-promotion path in `addTurnWithOutcome`). Do NOT `await` this from\n * inside a mutation — it enqueues its own mutation and would deadlock the\n * serializer.\n *\n * `throwOnFailure` (review round 7 finding 5): when set, a failed write is\n * rethrown AFTER the keep-pending + re-arm bookkeeping, so the surprise\n * extraction boundary gets the same durability guarantee as the built-in\n * extract triggers (which propagate saveUnlocked failures) instead of\n * proceeding to extract_now on a non-durable triggering turn. Default false\n * keeps the fail-open behavior for the timer/shutdown callers.\n */\n async flushPendingSave(opts?: { throwOnFailure?: boolean }): Promise<void> {\n if (this.saveTimer) {\n clearTimeout(this.saveTimer);\n this.saveTimer = null;\n }\n // resetToEmpty-only deletions are pending work too, even with no\n // pending-save flag set.\n if (!this.pendingSave && !this.persistence.hasPending()) return;\n try {\n await this.enqueueMutation(async () => {\n // Re-check under the serializer: a concurrent flush may have already\n // persisted this pending save. Skip the redundant write (no phantom\n // writes). resetToEmpty-only deletions also land here without a\n // pending-save flag.\n if (!this.pendingSave && !this.persistence.hasPending()) return;\n await this.saveUnlocked();\n // Clear the pending flag INSIDE the same serialized mutation as the\n // write (issue #1909 review). Clearing it after the enqueue await\n // resolved raced an addTurn interleaving between the write and the\n // clear: that turn's scheduleSave re-armed pendingSave + a timer, then\n // this clear wiped it and the debounced flush was lost. Atomic\n // write+clear means any addTurn runs wholly before (its turn is in the\n // write) or wholly after (its pendingSave survives and its timer\n // persists it).\n this.pendingSave = false;\n this.firstPendingAtMs = null;\n });\n } catch (err) {\n // The write failed: memory and disk diverge (issue #1909 review round 2).\n // Keep the save PENDING (do NOT clear pendingSave/firstPendingAtMs) so a\n // graceful shutdown flush and the re-armed timer both retry it — clearing\n // the flag here would drop the buffered turns permanently. Re-arm a\n // background retry when debounced.\n log.warn(`buffer.flushPendingSave: save failed, keeping it pending for retry: ${describeError(err)}`);\n const ms = Math.min(this.config.bufferSaveDebounceMs, MAX_SET_TIMEOUT_MS);\n if (ms > 0 && !this.saveTimer) {\n this.saveTimer = setTimeout(() => {\n this.saveTimer = null;\n void this.flushPendingSave();\n }, ms);\n this.saveTimer.unref?.();\n }\n // Surprise-extraction boundary needs the durability guarantee — surface\n // the failure so the caller does not proceed with a non-durable flush.\n if (opts?.throwOnFailure) throw err;\n return; // fail-open for timer/shutdown callers\n }\n }\n\n async addTurn(bufferKey: string, turn: BufferTurn): Promise<TriggerDecision> {\n return (await this.addTurnWithOutcome(bufferKey, turn)).decision;\n }\n\n /** Admit atomically; refuse at capacity rather than evict accepted work. */\n async addTurnBounded(bufferKey: string, turn: BufferTurn): Promise<AddTurnOutcome | null> {\n const mutation = await this.enqueueMutation(async () => {\n await this.loadUnlocked();\n if (this.peekEntry(bufferKey) === null) {\n this.makeRoomForEntryUnlocked();\n if (Object.keys(this.state.entries ?? {}).length >= this.maxSessionEntries) {\n log.warn(\n `buffer[${bufferKey}]: durable capture refused at the session-entry cap (${this.maxSessionEntries})`,\n );\n return null;\n }\n }\n return this.recordTurnUnlocked(bufferKey, turn);\n });\n if (mutation === null) return null;\n return this.completeAddTurnOutcome(bufferKey, turn, mutation);\n }\n\n /** Free one slot for an accepted capture via the native eviction policy (COS-244c). */\n private makeRoomForEntryUnlocked(): void {\n const entries = this.state.entries ?? {};\n const target = Math.max(0, this.maxSessionEntries - 1);\n const pinned = Object.keys(entries).filter((k) => k === \"default\" || this.entryHoldsAcceptedCapture(entries[k]));\n if (pinned.length <= target) this.pruneEntries([], target); // evict only if it frees a slot\n }\n\n private entryHoldsAcceptedCapture(entry: BufferEntryState | undefined): boolean {\n return (entry?.turns ?? []).some(isAcceptedTurn) || (entry?.retainedTurns ?? []).some(isAcceptedTurn);\n }\n\n async addTurnWithOutcome(\n bufferKey: string,\n turn: BufferTurn,\n ): Promise<AddTurnOutcome> {\n const mutation = await this.enqueueMutation(() => this.recordTurnUnlocked(bufferKey, turn));\n return this.completeAddTurnOutcome(bufferKey, turn, mutation);\n }\n\n /** Run probes outside the mutation lock to avoid reentrancy deadlocks. */\n private async completeAddTurnOutcome(\n bufferKey: string,\n turn: BufferTurn,\n mutation: AddTurnMutationResult,\n ): Promise<AddTurnOutcome> {\n let decision = mutation.decision;\n let extractionTurns: BufferTurn[] | undefined;\n\n // Surprise-gated flush (issue #563). Additive only: if the probe is\n // disabled, unavailable, or the score is below threshold, the decision\n // from the existing trigger logic stands. The probe only ever *promotes*\n // `keep_buffering` → `extract_now`; it never suppresses an existing\n // flush. This preserves the invariant that enabling surprise cannot\n // *reduce* extraction frequency.\n if (\n decision === \"keep_buffering\" &&\n resolvePresentationCapabilities(this.config).bufferSurpriseTrigger &&\n this.surpriseProbe !== null &&\n // Matching the existing \"smart\" branch: surprise is a lower-tier\n // novelty signal that should not second-guess a high-signal hit\n // (which already flushes) or fight `every_n` / `time_based` modes.\n this.config.triggerMode === \"smart\" &&\n mutation.signalLevel !== \"high\"\n ) {\n const surprise = await this.computeSurpriseSafe(bufferKey, turn, mutation.priorTurns);\n if (surprise !== null) {\n const shouldPromote = surprise > this.config.bufferSurpriseThreshold;\n let triggered = false;\n if (shouldPromote) {\n const currentTurns = await this.getExtractionTurnsIfBufferSnapshotStillCurrent(\n bufferKey,\n mutation.activeTurnsSnapshot,\n mutation.retainedTurnsSnapshot,\n );\n if (currentTurns) {\n log.debug(\n `buffer[${bufferKey}]: surprise=${surprise.toFixed(3)} > threshold=${this.config.bufferSurpriseThreshold} → extract_now`,\n );\n decision = \"extract_now\";\n triggered = true;\n extractionTurns = currentTurns;\n // Issue #1909 (review): the record mutation for a `keep_buffering`\n // turn only SCHEDULED a debounced save. Surprise now promotes it to\n // extract_now AFTER that mutation, so force the buffer durable before\n // the caller runs extraction — otherwise state/buffer.json lags the\n // extracted turns by up to the debounce window on a crash. Safe to\n // await here: we are outside the record mutation (deadlock-free).\n // throwOnFailure (round 7 finding 5): match the built-in extract\n // triggers — a failed durability write must NOT silently proceed to\n // extract_now on a non-durable turn.\n await this.flushPendingSave({ throwOnFailure: true });\n } else {\n log.debug(\n `buffer[${bufferKey}]: surprise=${surprise.toFixed(3)} ignored because buffer changed before probe resolved`,\n );\n }\n }\n // Emit telemetry on every scored turn — both triggering and\n // non-triggering — so operators can fit the threshold to real\n // traffic distributions. Fire-and-forget: `addTurn` does NOT\n // await the ledger append, so slow/contended filesystems cannot\n // add JSONL-append latency to every `processTurn`. But we DO\n // serialize writes through a promise chain so concurrent\n // appends settle in wall-clock order — the report path assumes\n // chronological tail rows and reads the most recent as the\n // \"current\" threshold.\n //\n // Project only the fields we need into the queue entry rather\n // than capturing the full `BufferTurn` — under slow filesystem\n // latency the chain can back up, and we must not retain the\n // (potentially large) `turn.content` string for every pending\n // append.\n this.queueSurpriseTelemetryWrite({\n bufferKey,\n turnRole: turn.role,\n sessionKey:\n typeof turn.sessionKey === \"string\" ? turn.sessionKey : null,\n surpriseScore: surprise,\n triggered,\n turnCountInWindow: mutation.turnCountInWindow,\n // Stamp at decision time so backpressure on the write chain\n // does not shift the event's apparent moment away from when\n // the turn was actually scored.\n timestamp: new Date().toISOString(),\n // Snapshot the threshold used to compute `triggered` so a\n // concurrent config mutation cannot retroactively change\n // what the ledger row claims the decision was against.\n threshold: this.config.bufferSurpriseThreshold,\n });\n }\n }\n\n log.debug(\n `buffer[${bufferKey}]: ${mutation.turnCountInWindow} turns, signal=${mutation.signalLevel}, decision=${decision}`,\n );\n if (decision !== \"keep_buffering\" && extractionTurns === undefined) {\n extractionTurns = [\n ...mutation.retainedTurnsSnapshot,\n ...mutation.activeTurnsSnapshot,\n ];\n }\n return extractionTurns ? { decision, extractionTurns } : { decision };\n }\n\n private async recordTurnUnlocked(bufferKey: string, turn: BufferTurn): Promise<AddTurnMutationResult> {\n await this.loadUnlocked();\n const entry = this.entryFor(bufferKey);\n const priorTurns = entry.turns.slice();\n entry.turns.push(turn);\n const activeTurnsSnapshot = entry.turns.map(copyBufferTurn);\n const retainedTurnsSnapshot = (entry.retainedTurns ?? []).map(copyBufferTurn);\n if (bufferKey === \"default\") {\n this.state.turns = entry.turns;\n }\n\n const signal = scanSignals(turn.content, this.config.highSignalPatterns);\n const decision = this.evaluate(entry, signal.level);\n const turnCountInWindow = entry.turns.length;\n\n this.persistence.markDirty(bufferKey);\n this.pruneEntries([bufferKey]);\n if (decision === \"keep_buffering\" && this.config.bufferSaveDebounceMs > 0) {\n // Steady-state buffering: coalesce the whole-state serialize onto a\n // trailing-edge timer (issue #1909) instead of rewriting per turn.\n await this.scheduleSave();\n } else {\n // Persist immediately within this mutation (awaited) when either:\n // - the turn triggered extraction (the buffer must be durable), or\n // - debounce is disabled (bufferSaveDebounceMs <= 0), which reproduces\n // the legacy save-every-turn behavior byte-for-byte.\n // Round 8 thread 4: the write is attempted BEFORE the pending state is\n // cleared, and a failed write leaves it pending (+ a re-armed timer) for\n // shutdown/timer retry instead of dropping the in-memory turns.\n await this.saveNowRetainingPendingOnFailure(\"recordTurn\");\n }\n return {\n decision,\n signalLevel: signal.level,\n priorTurns,\n activeTurnsSnapshot,\n retainedTurnsSnapshot,\n turnCountInWindow,\n };\n }\n\n private async getExtractionTurnsIfBufferSnapshotStillCurrent(\n bufferKey: string,\n activeTurnsSnapshot: readonly BufferTurn[],\n retainedTurnsSnapshot: readonly BufferTurn[],\n ): Promise<BufferTurn[] | null> {\n return this.enqueueMutation(async () => {\n await this.loadUnlocked();\n const entry = this.peekEntry(bufferKey);\n if (!entry) return null;\n const retained = entry.retainedTurns ?? [];\n const stillCurrent =\n bufferTurnArrayIsSuffixOfSnapshot(entry.turns, activeTurnsSnapshot) &&\n bufferTurnArraysEqual(retained, retainedTurnsSnapshot);\n if (!stillCurrent) return null;\n return [...retained, ...entry.turns];\n });\n }\n\n /**\n * Enqueue a telemetry append on the serialized write chain.\n *\n * The chain is a classic `writeChain = writeChain.then(fn).catch(...)`\n * — each link waits for the previous to settle before its append\n * starts, so out-of-order chronology cannot happen even under\n * variable filesystem latency. We always attach `.catch` so one\n * rejection does not poison the chain for the rest of the session\n * (CLAUDE.md rule #40). The error is logged through\n * `emitSurpriseEventSafe` itself, which swallows its own rejections.\n *\n * Public surface is deliberately narrow — only `addTurn` should call\n * this, so the surprise telemetry path stays centralized.\n */\n private queueSurpriseTelemetryWrite(params: SurpriseTelemetryQueueEntry): void {\n this.surpriseTelemetryWriteChain = this.surpriseTelemetryWriteChain\n .then(() => this.emitSurpriseEventSafe(params))\n .catch(() => {\n // `emitSurpriseEventSafe` already handles the logging. We\n // swallow here only so one failure does not break the chain\n // for future writes.\n });\n }\n\n /**\n * Append a single `BUFFER_SURPRISE` telemetry row (issue #563 PR 3).\n *\n * Deliberately swallows write errors: the buffer must never fail to\n * record a turn because the observation ledger is read-only, out of\n * disk, or otherwise unhappy. The log line at debug lets operators\n * confirm the path fired without polluting the error channel.\n */\n private async emitSurpriseEventSafe(\n params: SurpriseTelemetryQueueEntry,\n ): Promise<void> {\n const storage = this.storage;\n if (typeof storage.appendBufferSurpriseEvents !== \"function\") {\n // Older StorageManager / test double without the telemetry sink.\n // Silently skip — core path is still covered by the log line above.\n return;\n }\n const event: BufferSurpriseEvent = {\n event: \"BUFFER_SURPRISE\",\n // Use the decision-time stamp captured when the event was\n // queued, NOT `Date.now()` here — backpressure on the write\n // chain could otherwise shift timestamps into the future relative\n // to when the turn was scored.\n timestamp: params.timestamp,\n bufferKey: params.bufferKey,\n sessionKey: params.sessionKey,\n turnRole: params.turnRole,\n surpriseScore: params.surpriseScore,\n // Use the snapshotted threshold from the queue entry, not the\n // live config — see `SurpriseTelemetryQueueEntry.threshold`\n // doc for the rationale.\n threshold: params.threshold,\n triggeredFlush: params.triggered,\n turnCountInWindow: params.turnCountInWindow,\n };\n try {\n await storage.appendBufferSurpriseEvents([event]);\n } catch (err) {\n // Same guard as `computeSurpriseSafe`: non-Error rejections must\n // not crash the telemetry helper, which would defeat the whole\n // point of isolating the ledger write from the hot path.\n log.debug(\n `buffer[${params.bufferKey}]: surprise telemetry write failed, continuing: ${describeError(err)}`,\n );\n }\n }\n\n /**\n * Invoke the injected surprise probe defensively. Any error (probe throws,\n * embedder unavailable, timeout) is swallowed and logged at debug: the\n * surprise path must never crash the happy-path trigger evaluation. A\n * `null` return indicates \"no score available, fall through to existing\n * triggers\".\n */\n private async computeSurpriseSafe(\n bufferKey: string,\n turn: BufferTurn,\n priorTurns: readonly BufferTurn[],\n ): Promise<number | null> {\n if (!this.surpriseProbe) return null;\n try {\n // Hard timeout around the probe so a hung embedder cannot stall\n // `addTurn()` before `save()`. A slow probe would otherwise\n // prevent the just-appended turn from ever being persisted. The\n // timeout is a soft bound — we race it against the probe, take\n // whichever settles first, and treat the timeout as\n // \"probe unavailable, fall through\" rather than an error that\n // surfaces to the caller.\n const score = await probeWithTimeout(\n this.surpriseProbe.scoreTurn(bufferKey, turn, priorTurns),\n this.config.bufferSurpriseProbeTimeoutMs,\n );\n if (score === null) return null;\n if (typeof score !== \"number\" || !Number.isFinite(score)) {\n log.debug(\n `buffer[${bufferKey}]: surprise probe returned non-finite score (${String(score)}), ignoring`,\n );\n return null;\n }\n // Defensive clamp: formula lives in buffer-surprise.ts, but we never\n // want a misbehaving probe to inject an out-of-range value into the\n // threshold comparison.\n if (score < 0) return 0;\n if (score > 1) return 1;\n return score;\n } catch (err) {\n // `err` may be any thrown value — `throw null` and\n // `Promise.reject(\"x\")` are both legal. Accessing `.message` on a\n // non-Error would itself throw and defeat the failure-isolation\n // contract, so describe the value safely.\n log.debug(\n `buffer[${bufferKey}]: surprise probe failed, falling back to existing triggers: ${describeError(err)}`,\n );\n return null;\n }\n }\n\n private evaluate(entry: BufferEntryState, signalLevel: SignalLevel): TriggerDecision {\n if (this.config.triggerMode === \"smart\") {\n if (signalLevel === \"high\") return \"extract_now\";\n\n if (entry.turns.length >= this.config.bufferMaxTurns) {\n return \"extract_batch\";\n }\n\n if (entry.lastExtractionAt) {\n const elapsed =\n Date.now() - new Date(entry.lastExtractionAt).getTime();\n if (elapsed >= this.config.bufferMaxMinutes * 60_000) {\n return \"extract_batch\";\n }\n }\n\n return \"keep_buffering\";\n }\n\n if (this.config.triggerMode === \"every_n\") {\n return entry.turns.length >= this.config.bufferMaxTurns\n ? \"extract_batch\"\n : \"keep_buffering\";\n }\n\n if (this.config.triggerMode === \"time_based\") {\n if (!entry.lastExtractionAt) {\n return entry.turns.length >= this.config.bufferMaxTurns\n ? \"extract_batch\"\n : \"keep_buffering\";\n }\n const elapsed =\n Date.now() - new Date(entry.lastExtractionAt).getTime();\n return elapsed >= this.config.bufferMaxMinutes * 60_000\n ? \"extract_batch\"\n : \"keep_buffering\";\n }\n\n return \"keep_buffering\";\n }\n\n getTurns(bufferKey = \"default\"): BufferTurn[] {\n const entry = this.peekEntry(bufferKey);\n if (!entry) return [];\n const retained = entry.retainedTurns ?? [];\n // Retained turns (from a previous defer verdict, issue #562 PR 2) are\n // prepended so the chronological order — oldest context first — is\n // preserved for the next extraction pass.\n return [...retained, ...entry.turns];\n }\n\n /**\n * Retain a subset of the current turns across `clearAfterExtraction` so a\n * future extraction pass sees the context behind a deferred candidate\n * (issue #562, PR 2). Callers pass the turns that were seen during the\n * current extraction; the buffer keeps the tail (latest `max` turns) as\n * the retention window. Passing an empty array or `max <= 0` clears the\n * retention slot instead.\n */\n async retainDeferredTurns(\n bufferKey: string,\n turns: BufferTurn[],\n max = 10,\n originalTurns?: readonly BufferTurn[],\n ): Promise<void> {\n await this.enqueueMutation(async () => {\n await this.loadUnlocked();\n const entry = this.entryFor(bufferKey);\n const retained = selectRetainedDeferredTurns(\n entry.retainedTurns ?? [], turns, max, originalTurns,\n );\n if (retained.length > 0) entry.retainedTurns = retained;\n else delete entry.retainedTurns;\n this.persistence.markDirty(bufferKey);\n await this.saveUnlocked();\n });\n }\n\n /**\n * Return the current retention window (issue #562, PR 2). Primarily for\n * tests and diagnostics.\n */\n getRetainedDeferredTurns(bufferKey = \"default\"): BufferTurn[] {\n const entry = this.peekEntry(bufferKey);\n return entry?.retainedTurns ? [...entry.retainedTurns] : [];\n }\n\n /**\n * Clear deferred retention copies for a force-flushed session. Normal\n * extraction preserves these copies so a deferred candidate can be retried;\n * an explicit force flush is the caller's request to drain that context.\n */\n async clearRetainedTurnsForSession(\n sessionKey: string,\n ownerPrincipal?: string,\n options: RetainedTurnCleanupOptions = {},\n ): Promise<void> {\n if (typeof sessionKey !== \"string\" || sessionKey.length === 0) return;\n const normalizedOwnerPrincipal =\n typeof ownerPrincipal === \"string\" && ownerPrincipal.trim().length > 0\n ? ownerPrincipal.trim()\n : undefined;\n const namespacesEnabled = resolveNamespaceCapabilities(this.config).namespaces === true;\n const normalizedCleanupNamespace =\n namespacesEnabled && options.writeNamespaceOverride !== undefined && options.writeNamespaceOverride.length > 0\n ? options.writeNamespaceOverride\n : undefined;\n const assertLifecycle = (): void => {\n throwIfAborted(options.abortSignal, \"extraction force-flush aborted\");\n if (typeof options.deadlineMs === \"number\" && Date.now() >= options.deadlineMs) {\n throw new ExtractionDeadlineError(\"retained_turn_cleanup\");\n }\n };\n assertLifecycle();\n await this.enqueueMutation(async () => {\n assertLifecycle();\n const hadPendingSave = this.pendingSave;\n if (this.saveTimer) {\n clearTimeout(this.saveTimer);\n this.saveTimer = null;\n }\n await this.loadUnlocked();\n assertLifecycle();\n const bufferKeys = this.matchingSessionBufferKeysUnlocked(sessionKey);\n if (bufferKeys.length === 0) {\n if (hadPendingSave) {\n assertLifecycle();\n await this.saveNowRetainingPendingOnFailure(\"clearRetainedTurnsForSession\");\n }\n return;\n }\n let changed = false;\n const matchesCleanupScope = (turn: BufferTurn): boolean => {\n // Legacy unpinned turns keep their existing cleanup semantics.\n if (\n turn.writeNamespaceOverride !== undefined &&\n normalizedCleanupNamespace !== undefined &&\n turn.writeNamespaceOverride !== normalizedCleanupNamespace\n ) {\n return false;\n }\n // Equal write namespaces can still belong to different project bindings.\n if (options.scopeProfileWritePlan && hasPinnedCaptureScope(turn)) {\n if (\n scopeProfilePlanIdentity(turn.scopeProfileWritePlan) !== scopeProfilePlanIdentity(options.scopeProfileWritePlan) &&\n !(turn.acceptedCapture === true && turn.scopeProfileWritePlan === undefined)\n ) {\n return false;\n }\n const ctx = options.codingContext;\n if (\n ctx && turn.codingContext &&\n (turn.codingContext.projectId !== ctx.projectId ||\n turn.codingContext.rootPath !== ctx.rootPath ||\n turn.codingContext.branch !== ctx.branch ||\n turn.codingContext.defaultBranch !== ctx.defaultBranch)\n ) {\n return false;\n }\n }\n return true;\n };\n const belongsToOwner = (turn: BufferTurn): boolean => {\n if (turn.sessionKey !== sessionKey) return false;\n if (!matchesCleanupScope(turn)) return false;\n if (normalizedOwnerPrincipal === undefined) return true;\n if (turn.sessionOwnerPrincipal === normalizedOwnerPrincipal) return true;\n return (\n turn.sessionOwnerPrincipal === undefined &&\n resolvePrincipal(turn.sessionKey, this.config) === normalizedOwnerPrincipal\n );\n };\n for (const bufferKey of bufferKeys) {\n const entry = this.entryFor(bufferKey);\n const retainedTurns = entry.retainedTurns ?? [];\n const remainingTurns = retainedTurns.filter((turn) => !belongsToOwner(turn));\n if (remainingTurns.length === retainedTurns.length) continue;\n changed = true;\n if (remainingTurns.length > 0) entry.retainedTurns = remainingTurns;\n else delete entry.retainedTurns;\n if (bufferKey === \"default\") this.state.turns = entry.turns;\n this.persistence.markDirty(bufferKey);\n }\n if (changed || hadPendingSave) {\n assertLifecycle();\n await this.saveNowRetainingPendingOnFailure(\"clearRetainedTurnsForSession\");\n }\n assertLifecycle();\n });\n }\n\n async findBufferKeyForSession(sessionKey: string): Promise<string | null> {\n const bufferKeys = await this.findBufferKeysForSession(sessionKey);\n return bufferKeys[0] ?? null;\n }\n\n private matchingSessionBufferKeysUnlocked(sessionKey: string): string[] {\n const matches: string[] = [];\n const hasSessionTurns = (entry: BufferEntryState | null | undefined): boolean =>\n [...(entry?.turns ?? []), ...(entry?.retainedTurns ?? [])].some(\n (turn) => typeof turn.sessionKey === \"string\" && turn.sessionKey === sessionKey,\n );\n const directEntry = this.peekEntry(sessionKey);\n if (hasSessionTurns(directEntry)) {\n matches.push(sessionKey);\n }\n\n const entries = this.state.entries ?? {};\n for (const [bufferKey, entry] of Object.entries(entries)) {\n if (!matches.includes(bufferKey) && hasSessionTurns(entry)) {\n matches.push(bufferKey);\n }\n }\n\n return matches;\n }\n\n async findBufferKeysForSession(sessionKey: string): Promise<string[]> {\n if (typeof sessionKey !== \"string\" || sessionKey.length === 0) return [];\n await this.mutationChain.catch(() => {});\n await this.load();\n return this.matchingSessionBufferKeysUnlocked(sessionKey);\n }\n\n async clearAfterExtraction(\n bufferKey = \"default\",\n extractedTurns?: readonly BufferTurn[],\n options?: { allowNonPrefix?: boolean },\n ): Promise<void> {\n await this.enqueueMutation(async () => {\n // Drop any armed debounce TIMER so it cannot fire mid-mutation and race\n // our post-clear write, but KEEP the pending-save state (issue #1909\n // review round 13): the post-clear save below can fail, and clearing the\n // pending flag here would strand buffered turns — including turns from\n // OTHER sessions that only ever entered the debounced pending state — with\n // nothing left to retry them. saveNowRetainingPendingOnFailure clears the\n // pending state only after a durable write, or re-arms it on failure.\n if (this.saveTimer) {\n clearTimeout(this.saveTimer);\n this.saveTimer = null;\n }\n await this.loadUnlocked();\n const entry = this.entryFor(bufferKey);\n if (Array.isArray(extractedTurns)) {\n const liveExtractedTurns = liveTurnsFromExtractionSnapshot(\n entry,\n extractedTurns,\n );\n let clearedLiveTurns = false;\n if (liveExtractedTurns.length > 0) {\n if (options?.allowNonPrefix === true) {\n const remainingTurns = [...entry.turns];\n for (const extractedTurn of liveExtractedTurns) {\n const matchingIndex = remainingTurns.findIndex((liveTurn) =>\n bufferTurnsEqual(liveTurn, extractedTurn),\n );\n if (matchingIndex >= 0) {\n remainingTurns.splice(matchingIndex, 1);\n clearedLiveTurns = true;\n }\n }\n if (clearedLiveTurns) entry.turns = remainingTurns;\n } else {\n const matchedCount = matchingQueuedExtractionPrefixLength(\n entry.turns,\n liveExtractedTurns,\n );\n if (matchedCount > 0) {\n entry.turns = entry.turns.slice(matchedCount);\n clearedLiveTurns = true;\n }\n }\n if (!clearedLiveTurns) {\n log.debug(\n `buffer[${bufferKey}]: extraction clear skipped because live turns changed before clear`,\n );\n }\n }\n if (!clearedLiveTurns) {\n await this.saveNowRetainingPendingOnFailure(\"clearAfterExtraction\");\n return;\n }\n } else {\n entry.turns = [];\n }\n entry.lastExtractionAt = new Date().toISOString();\n entry.extractionCount += 1;\n if (bufferKey === \"default\") {\n this.state.turns = entry.turns;\n this.state.lastExtractionAt = entry.lastExtractionAt;\n this.state.extractionCount = entry.extractionCount;\n }\n this.persistence.markDirty(bufferKey);\n this.pruneEntries([bufferKey]);\n await this.saveNowRetainingPendingOnFailure(\"clearAfterExtraction\");\n });\n }\n\n /**\n * Remove an extraction-window chunk from the buffer WITHOUT recording an\n * extraction (trigger-drain drop path). Used when a chunk fails extraction\n * repeatedly and must be dropped so the session can drain; the caller is\n * responsible for the side-file copy and logging. Returns the number of\n * live turns dropped; 0 when the turns no longer match. `allowNonPrefix`\n * matches by identity, like `clearAfterExtraction`, for callers (native\n * groups) whose chunk can sit behind a pinned capture.\n */\n async dropExtractionChunk(\n bufferKey = \"default\",\n turns: readonly BufferTurn[],\n options?: { allowNonPrefix?: boolean },\n ): Promise<number> {\n return await this.enqueueMutation(async () => {\n await this.loadUnlocked();\n const entry = this.entryFor(bufferKey);\n // A window's head can be deferred-retained turns (retainDeferredTurns): take those out of the chunk first.\n const retained = [...(entry.retainedTurns ?? [])];\n const liveTurns: BufferTurn[] = [];\n for (const turn of turns) {\n const at = retained.findIndex((kept) => bufferTurnsEqual(kept, turn));\n if (at >= 0) retained.splice(at, 1);\n else liveTurns.push(turn);\n }\n const retainedDropped = (entry.retainedTurns?.length ?? 0) - retained.length;\n let remaining = entry.turns;\n if (options?.allowNonPrefix === true) {\n remaining = [...entry.turns];\n for (const turn of liveTurns) {\n const at = remaining.findIndex((live) => bufferTurnsEqual(live, turn));\n if (at >= 0) remaining.splice(at, 1);\n }\n } else {\n remaining = entry.turns.slice(matchingQueuedExtractionPrefixLength(entry.turns, liveTurns));\n }\n const matchedCount = entry.turns.length - remaining.length + retainedDropped;\n if (matchedCount === 0) return 0;\n if (this.saveTimer) {\n clearTimeout(this.saveTimer);\n this.saveTimer = null;\n }\n entry.turns = remaining;\n if (retainedDropped > 0) {\n if (retained.length > 0) entry.retainedTurns = retained;\n else delete entry.retainedTurns;\n }\n if (bufferKey === \"default\") {\n this.state.turns = entry.turns;\n }\n this.persistence.markDirty(bufferKey);\n this.pruneEntries([bufferKey]);\n await this.saveNowRetainingPendingOnFailure(\"dropExtractionChunk\");\n return matchedCount;\n });\n }\n\n getExtractionCount(bufferKey = \"default\"): number {\n return this.peekEntry(bufferKey)?.extractionCount ?? 0;\n }\n\n /**\n * Await any pending `BUFFER_SURPRISE` telemetry writes.\n *\n * The telemetry path is fire-and-forget from the hot path's point of\n * view, but tests and before-exit hooks sometimes need to make sure\n * the ledger has been flushed before they assert on its contents or\n * close the process. This method resolves once the current chain\n * head has settled; new writes scheduled after this call return a\n * separate, later settlement.\n *\n * Never throws — the chain already catches its own rejections.\n */\n async flushSurpriseTelemetry(): Promise<void> {\n await this.surpriseTelemetryWriteChain;\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/buffer-turn-helpers.ts"],"sourcesContent":["import type { BufferEntryState, BufferState, BufferTurn } from \"./types.js\";\n\nexport interface BufferTurnOwner {\n /**\n * Server-resolved principal that owns this session at the authenticated\n * observe/write boundary. Persisted with the buffered turn so lifecycle\n * flushes do not have to infer ownership from a client-chosen session key.\n */\n sessionOwnerPrincipal?: string;\n}\n\nexport interface AmbientCaptureProvenance {\n /**\n * True when this turn's content came from an always-on ambient capture\n * device (a wearable recorder, a room microphone) and may therefore carry\n * speech the user never authored: TV, podcasts, music, overheard\n * conversation (issue #2294). Set by the ingesting subsystem, never from\n * tool arguments — extraction reads it to warn the model about media audio\n * and to clamp high-impact personal facts to the speculative tier.\n */\n ambientCapture?: boolean;\n}\n\nexport function describeError(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (err === null) return \"null\";\n if (err === undefined) return \"undefined\";\n if (typeof err === \"string\") return err;\n try {\n return JSON.stringify(err);\n } catch {\n return String(err);\n }\n}\n\nexport function copyBufferTurn(turn: BufferTurn): BufferTurn {\n const copy: BufferTurn = {\n role: turn.role,\n content: turn.content,\n timestamp: turn.timestamp,\n };\n if (typeof turn.sessionKey === \"string\") copy.sessionKey = turn.sessionKey;\n if (typeof turn.logicalSessionKey === \"string\") {\n copy.logicalSessionKey = turn.logicalSessionKey;\n }\n if (turn.providerThreadId === null || typeof turn.providerThreadId === \"string\") {\n copy.providerThreadId = turn.providerThreadId;\n }\n if (typeof turn.sessionOwnerPrincipal === \"string\") {\n copy.sessionOwnerPrincipal = turn.sessionOwnerPrincipal;\n }\n if (typeof turn.turnFingerprint === \"string\") {\n copy.turnFingerprint = turn.turnFingerprint;\n }\n if (typeof turn.persistProcessedFingerprint === \"boolean\") {\n copy.persistProcessedFingerprint = turn.persistProcessedFingerprint;\n }\n if (typeof turn.sourceConnector === \"string\") {\n copy.sourceConnector = turn.sourceConnector;\n }\n if (turn.originRole === \"user\" || turn.originRole === \"assistant\" || turn.originRole === \"tool\") {\n copy.originRole = turn.originRole;\n }\n if (typeof turn.ambientCapture === \"boolean\") {\n copy.ambientCapture = turn.ambientCapture;\n }\n if (turn.sourceValidAt !== undefined) {\n copy.sourceValidAt = turn.sourceValidAt;\n }\n if (turn.parts !== undefined) {\n copy.parts = turn.parts;\n }\n if (turn.rawContent !== undefined) {\n copy.rawContent = turn.rawContent;\n }\n if (turn.sourceFormat !== undefined) {\n copy.sourceFormat = turn.sourceFormat;\n }\n // #3251 scope pins: strings copy by value; the plan object is an immutable\n // config-derived identity passed by reference.\n if (turn.writeNamespaceOverride !== undefined) {\n copy.writeNamespaceOverride = turn.writeNamespaceOverride;\n }\n if (turn.principalOverride !== undefined) {\n copy.principalOverride = turn.principalOverride;\n }\n if (turn.scopeProfileWritePlan !== undefined) {\n copy.scopeProfileWritePlan = turn.scopeProfileWritePlan;\n }\n if (turn.acceptedCapture === true) {\n copy.acceptedCapture = true;\n }\n const ctx = turn.codingContext;\n if (ctx) {\n // All four fields are identity inputs (branchScope uses branch) — copy\n // explicitly as-is; never a spread of external input.\n copy.codingContext = {\n projectId: ctx.projectId,\n branch: ctx.branch,\n rootPath: ctx.rootPath,\n defaultBranch: ctx.defaultBranch,\n };\n }\n return copy;\n}\n\n/**\n * Remove the turns that a drain already extracted (`drained`) from `turns`.\n * Identity-based (role/content/timestamp/session/...): the same comparison\n * the buffer uses to match live turns, so a stale owner can subtract exactly\n * what the drain took and keep everything else.\n */\nexport function subtractBufferTurns(turns: readonly BufferTurn[], drained: readonly BufferTurn[]): BufferTurn[] {\n if (drained.length === 0) return [...turns];\n const kept = [...turns];\n for (const gone of drained) {\n const at = kept.findIndex((live) => bufferTurnsEqual(live, gone));\n if (at >= 0) kept.splice(at, 1);\n }\n return kept;\n}\n\n/** Per-key pending counts over the IN-MEMORY buffer state (counts only — no\n * turn payloads). The claim store's answer stays authoritative for claimed\n * keys; callers must filter through `BufferDrainClaims.isClaimed` before\n * treating a count here as work. */\nexport interface BufferEntryCounts {\n turns: number;\n retainedTurns: number;\n}\n\nexport function bufferEntryCounts(state: BufferState): Map<string, BufferEntryCounts> {\n const out = new Map<string, BufferEntryCounts>();\n const entries = state.entries;\n if (!entries) return out;\n for (const [key, entry] of Object.entries(entries)) {\n out.set(key, { turns: entry.turns.length, retainedTurns: entry.retainedTurns?.length ?? 0 });\n }\n return out;\n}\n\nexport function bufferTurnsEqual(left: BufferTurn | undefined, right: BufferTurn): boolean {\n if (!left) return false;\n return (\n left.role === right.role &&\n left.content === right.content &&\n left.timestamp === right.timestamp &&\n left.sessionKey === right.sessionKey &&\n left.sessionOwnerPrincipal === right.sessionOwnerPrincipal &&\n left.logicalSessionKey === right.logicalSessionKey &&\n left.providerThreadId === right.providerThreadId &&\n left.turnFingerprint === right.turnFingerprint &&\n left.persistProcessedFingerprint === right.persistProcessedFingerprint &&\n left.sourceConnector === right.sourceConnector &&\n left.originRole === right.originRole &&\n left.ambientCapture === right.ambientCapture &&\n left.sourceValidAt === right.sourceValidAt &&\n left.acceptedCapture === right.acceptedCapture &&\n left.codingContext?.projectId === right.codingContext?.projectId &&\n left.codingContext?.rootPath === right.codingContext?.rootPath &&\n left.codingContext?.branch === right.codingContext?.branch &&\n left.codingContext?.defaultBranch === right.codingContext?.defaultBranch &&\n // #3251 scope pins compare by identity triple, not deep plan equality: a\n // restart-rehydrated plan must still match the pin it was accepted under.\n left.writeNamespaceOverride === right.writeNamespaceOverride &&\n left.principalOverride === right.principalOverride &&\n scopeProfilePlanIdentity(left.scopeProfileWritePlan) ===\n scopeProfilePlanIdentity(right.scopeProfileWritePlan)\n );\n}\n\n/** Replace only the processed snapshot; callers without one retain legacy whole-slot semantics. */\nexport function selectRetainedDeferredTurns(\n existing: readonly BufferTurn[],\n turns: BufferTurn[],\n max: number,\n originalTurns?: readonly BufferTurn[],\n): BufferTurn[] {\n const others = originalTurns === undefined ? [] : existing.filter(\n (turn) => !originalTurns.some((original) => bufferTurnsEqual(turn, original)),\n );\n if (!Array.isArray(turns) || max <= 0) return others;\n return [...others, ...turns.slice(-max).map(copyBufferTurn)];\n}\n\nexport function hasPinnedCaptureScope(turn: BufferTurn): boolean {\n return turn.acceptedCapture === true ||\n turn.writeNamespaceOverride !== undefined ||\n turn.scopeProfileWritePlan !== undefined;\n}\n\n/** Stable identity string for a pinned scope-profile plan (#3251). */\nexport function scopeProfilePlanIdentity(\n plan: BufferTurn[\"scopeProfileWritePlan\"],\n): string {\n if (!plan) return \"\";\n return `${plan.profileId}\\u0000${plan.writeLayer}\\u0000${plan.writeNamespace}`;\n}\n\nexport interface ExtractionScopeGroup {\n writeNamespaceOverride?: string;\n principalOverride?: string;\n scopeProfileWritePlan?: BufferTurn[\"scopeProfileWritePlan\"];\n codingContext?: BufferTurn[\"codingContext\"];\n turns: BufferTurn[];\n}\n\n/**\n * Partition extraction turns by their durable scope pins (#3251). Turns\n * captured under different namespaces / principals / scope profiles /\n * project bindings must never be extracted together — each group runs its\n * own extraction under its own scope. Native (unpinned) turns form their own\n * group and keep today's single-store routing.\n */\nexport function groupTurnsByExtractionScope(turns: BufferTurn[]): ExtractionScopeGroup[] {\n const groups = new Map<string, ExtractionScopeGroup>();\n for (const turn of turns) {\n const key = [\n turn.writeNamespaceOverride ?? \"\",\n turn.principalOverride ?? \"\",\n scopeProfilePlanIdentity(turn.scopeProfileWritePlan),\n // Accepted captures are deliberately accepted content: they must never\n // share a scope group with ordinary turns, or a group-level capture\n // policy (the explicit-mode gate, the planless-accepted null pin) is\n // applied to ordinary conversation turns because `.some(acceptedCapture)`\n // only looked at the merged group.\n turn.acceptedCapture === true ? \"accepted\" : \"\",\n turn.codingContext?.projectId ?? \"\",\n turn.codingContext?.rootPath ?? \"\",\n turn.codingContext?.branch ?? \"\",\n turn.codingContext?.defaultBranch ?? \"\",\n ].join(\"\\u0000\");\n const group = groups.get(key);\n if (group) {\n group.turns.push(turn);\n } else {\n const created: ExtractionScopeGroup = { turns: [turn] };\n if (turn.writeNamespaceOverride !== undefined) created.writeNamespaceOverride = turn.writeNamespaceOverride;\n if (turn.principalOverride !== undefined) created.principalOverride = turn.principalOverride;\n if (turn.scopeProfileWritePlan !== undefined) created.scopeProfileWritePlan = turn.scopeProfileWritePlan;\n if (turn.codingContext !== undefined) created.codingContext = turn.codingContext;\n groups.set(key, created);\n }\n }\n return [...groups.values()];\n}\n\nexport function bufferTurnArraysEqual(\n left: readonly BufferTurn[],\n right: readonly BufferTurn[],\n): boolean {\n return left.length === right.length && left.every((turn, index) => bufferTurnsEqual(turn, right[index]));\n}\n\nexport function bufferTurnArrayIsSuffixOfSnapshot(\n liveTurns: readonly BufferTurn[],\n snapshot: readonly BufferTurn[],\n): boolean {\n if (liveTurns.length === 0 || liveTurns.length > snapshot.length) {\n return false;\n }\n const offset = snapshot.length - liveTurns.length;\n return liveTurns.every((turn, index) => bufferTurnsEqual(turn, snapshot[offset + index]));\n}\n\nexport function liveTurnsFromExtractionSnapshot(\n entry: BufferEntryState,\n extractedTurns: readonly BufferTurn[],\n): readonly BufferTurn[] {\n const retainedTurns = entry.retainedTurns ?? [];\n if (\n retainedTurns.length > 0 &&\n extractedTurns.length >= retainedTurns.length &&\n retainedTurns.every((turn, index) => bufferTurnsEqual(extractedTurns[index], turn))\n ) {\n const withoutRetainedPrefix = extractedTurns.slice(retainedTurns.length);\n if (withoutRetainedPrefix.length > 0 && matchingPrefixLength(entry.turns, withoutRetainedPrefix) > 0) {\n return withoutRetainedPrefix;\n }\n }\n return extractedTurns;\n}\n\nfunction matchingPrefixLength(\n liveTurns: readonly BufferTurn[],\n extractedTurns: readonly BufferTurn[],\n): number {\n let index = 0;\n while (\n index < liveTurns.length &&\n index < extractedTurns.length &&\n bufferTurnsEqual(liveTurns[index], extractedTurns[index])\n ) {\n index += 1;\n }\n return index;\n}\n\nexport function matchingQueuedExtractionPrefixLength(\n liveTurns: readonly BufferTurn[],\n extractedTurns: readonly BufferTurn[],\n): number {\n let bestMatchedCount = 0;\n for (let start = 0; start < extractedTurns.length; start += 1) {\n const matchedCount = matchingPrefixLength(liveTurns, extractedTurns.slice(start));\n if (matchedCount > bestMatchedCount) {\n bestMatchedCount = matchedCount;\n if (bestMatchedCount === liveTurns.length) break;\n }\n }\n return bestMatchedCount;\n}\n\nexport class ProbeTimeoutError extends Error {\n constructor(timeoutMs: number) {\n super(`probe exceeded ${timeoutMs}ms`);\n this.name = \"ProbeTimeoutError\";\n }\n}\n\nexport function probeWithTimeout<T>(inflight: Promise<T>, timeoutMs: number): Promise<T> {\n let timer: NodeJS.Timeout | null = null;\n const timeout = new Promise<T>((_, reject) => {\n timer = setTimeout(() => reject(new ProbeTimeoutError(timeoutMs)), timeoutMs);\n if (typeof (timer as NodeJS.Timeout).unref === \"function\") {\n (timer as NodeJS.Timeout).unref();\n }\n });\n return Promise.race([inflight, timeout]).finally(() => {\n if (timer) clearTimeout(timer);\n });\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/access-observe-helpers.ts"],"sourcesContent":["import { isPreemptedExtraction } from \"./abort-error.js\";\n\ntype PendingObservePreparation = {\n cancelled: boolean;\n scopeHint?: string;\n principal?: string;\n namespace?: string;\n scopeResolved: boolean;\n resolvedScopeFences: Array<{ principal?: string; namespace?: string }>;\n released: Promise<void>;\n release: () => void;\n};\n\nfunction resolvedScopesMatch(\n left: { principal?: string; namespace?: string },\n right: { principal?: string; namespace?: string },\n): boolean {\n return left.principal === right.principal && left.namespace === right.namespace;\n}\n\nfunction preparationMatchesScope(\n reservation: PendingObservePreparation,\n principal: string | undefined,\n namespace: string | undefined,\n scopeHint: string | undefined,\n): boolean {\n if (reservation.scopeResolved) {\n return resolvedScopesMatch(reservation, { principal, namespace });\n }\n return reservation.scopeHint === undefined || scopeHint === undefined || reservation.scopeHint === scopeHint;\n}\n\nexport type PendingObservePreparationHandle = {\n isCancelled(): boolean;\n setScope(principal?: string, namespace?: string): void;\n release(): void;\n};\n\nexport function pendingObserveScopeHint(options: {\n namespace?: string;\n projectTag?: string;\n cwd?: string;\n}): string | undefined {\n const namespace = options.namespace?.trim();\n if (namespace) return `namespace:${namespace}`;\n const projectTag = options.projectTag?.trim();\n if (projectTag) return `projectTag:${projectTag}`;\n const cwd = options.cwd?.trim();\n return cwd ? `cwd:${cwd}` : undefined;\n}\n\ntype PendingObserveExtraction = {\n promise: Promise<void>;\n running: Promise<void>;\n controllers: Set<AbortController>;\n};\n\nexport class PendingObserveExtractionTracker {\n private readonly entries = new Map<string, PendingObserveExtraction>();\n private readonly preparations = new Map<string, Set<PendingObservePreparation>>();\n\n reserve(sessionKey: string, scopeHint?: string): PendingObservePreparationHandle {\n let released = false;\n let resolveReleased: () => void = () => {};\n const reservation: PendingObservePreparation = {\n cancelled: false,\n scopeResolved: false,\n resolvedScopeFences: [],\n scopeHint,\n released: new Promise<void>((resolve) => {\n resolveReleased = resolve;\n }),\n release: () => {\n if (released) return;\n released = true;\n const entries = this.preparations.get(sessionKey);\n entries?.delete(reservation);\n if (entries?.size === 0) this.preparations.delete(sessionKey);\n resolveReleased();\n },\n };\n const entries = this.preparations.get(sessionKey) ?? new Set<PendingObservePreparation>();\n entries.add(reservation);\n this.preparations.set(sessionKey, entries);\n return {\n isCancelled: () => reservation.cancelled,\n setScope: (principal, namespace) => {\n reservation.principal = principal;\n reservation.namespace = namespace;\n reservation.scopeResolved = true;\n if (reservation.resolvedScopeFences.some((fence) => resolvedScopesMatch(reservation, fence))) {\n reservation.cancelled = true;\n }\n },\n release: reservation.release,\n };\n }\n\n private async waitForPreparations(\n sessionKey: string,\n principal: string | undefined,\n namespace: string | undefined,\n scopeHint: string | undefined,\n ): Promise<void> {\n while (true) {\n const reservations = [...(this.preparations.get(sessionKey) ?? [])];\n for (const reservation of reservations) {\n if (\n !reservation.cancelled &&\n !reservation.scopeResolved &&\n !preparationMatchesScope(reservation, principal, namespace, scopeHint) &&\n !reservation.resolvedScopeFences.some((fence) =>\n resolvedScopesMatch(fence, { principal, namespace }),\n )\n ) {\n reservation.resolvedScopeFences.push({ principal, namespace });\n }\n }\n const pending = reservations.filter(\n (reservation) =>\n !reservation.cancelled && preparationMatchesScope(reservation, principal, namespace, scopeHint),\n );\n if (pending.length === 0) return;\n await Promise.all(pending.map((reservation) => reservation.released));\n }\n }\n\n\n key(sessionKey: string, principal: string | undefined, namespace: string | undefined): string {\n return `${sessionKey}\\u0000${principal ?? \"\"}\\u0000${namespace ?? \"\"}`;\n }\n\n track(key: string, extraction: Promise<void>, controller: AbortController): void {\n const previous = this.entries.get(key);\n const promise = previous\n ? Promise.allSettled([previous.promise, extraction]).then((results) => {\n const rejected = results.find(\n (result): result is PromiseRejectedResult =>\n result.status === \"rejected\" &&\n !(result.reason instanceof Error && isPreemptedExtraction(result.reason)),\n );\n if (rejected) throw rejected.reason;\n })\n : extraction;\n const entry = {\n promise,\n running: previous?.running ?? extraction,\n controllers: new Set(previous ? [...previous.controllers, controller] : [controller]),\n };\n this.entries.set(key, entry);\n void promise\n .then(() => {\n if (this.entries.get(key) === entry) this.entries.delete(key);\n })\n .catch((error) => {\n if (error instanceof Error && isPreemptedExtraction(error) && this.entries.get(key) === entry) this.entries.delete(key);\n });\n }\n\n cancelPreparations(sessionKey: string, scopeHint?: string): void {\n for (const reservation of this.preparations.get(sessionKey) ?? []) {\n if (\n scopeHint !== undefined &&\n reservation.scopeHint !== undefined &&\n reservation.scopeHint !== scopeHint\n ) {\n continue;\n }\n reservation.cancelled = true;\n }\n }\n\n cancel(\n sessionKey: string,\n principal?: string,\n namespace?: string,\n scopeHint?: string,\n reason?: Error,\n ): void {\n for (const reservation of this.preparations.get(sessionKey) ?? []) {\n if (!preparationMatchesScope(reservation, principal, namespace, scopeHint)) continue;\n reservation.cancelled = true;\n }\n\n this.abortTracked(sessionKey, principal, namespace, reason);\n }\n\n private abortTracked(\n sessionKey: string,\n principal: string | undefined,\n namespace: string | undefined,\n reason?: Error,\n ): void {\n for (const [key, entry] of this.entries) {\n const [entrySessionKey, entryPrincipal, entryNamespace] = key.split(\"\\u0000\");\n if (\n entrySessionKey !== sessionKey ||\n (principal !== undefined && entryPrincipal !== (principal ?? \"\")) ||\n (namespace !== undefined && entryNamespace !== (namespace ?? \"\"))\n ) continue;\n for (const controller of entry.controllers) controller.abort(reason);\n }\n }\n\n async wait(\n sessionKey: string,\n principal: string | undefined,\n namespace: string | undefined,\n abortSignal?: AbortSignal,\n registerCancellation?: (cancel: () => void) => void,\n scopeHint?: string,\n preemptAfterAdmission?: () => void,\n ): Promise<void> {\n const key = this.key(sessionKey, principal, namespace);\n let cancelled = abortSignal?.aborted === true;\n const abortTracked = (): void => {\n cancelled = true;\n this.cancel(sessionKey, principal, namespace, scopeHint);\n };\n registerCancellation?.(abortTracked);\n abortSignal?.addEventListener(\"abort\", abortTracked, { once: true });\n try {\n await this.waitForPreparations(sessionKey, principal, namespace, scopeHint);\n if (cancelled || abortSignal?.aborted) return;\n if (preemptAfterAdmission) {\n preemptAfterAdmission();\n const running = this.entries.get(key)?.running;\n if (!running) return;\n try {\n await running;\n } catch (error) {\n if (this.entries.get(key)?.running === running) this.entries.delete(key);\n if (!(error instanceof Error && isPreemptedExtraction(error))) throw error;\n }\n return;\n }\n while (true) {\n const entry = this.entries.get(key);\n if (!entry) return;\n try {\n await entry.promise;\n } catch (error) {\n if (this.entries.get(key) !== entry) continue;\n this.entries.delete(key);\n if (error instanceof Error && isPreemptedExtraction(error)) return;\n throw error;\n }\n if (this.entries.get(key) !== entry) continue;\n this.entries.delete(key);\n return;\n }\n } finally {\n abortSignal?.removeEventListener(\"abort\", abortTracked);\n }\n }\n}\n"],"mappings":"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","names":["entries"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/storage/buffer-drain-claims.ts","../src/storage/buffer-session-files.ts"],"sourcesContent":["/**\n * Durable drain claims for the extraction buffer (`remnic buffer drain`).\n *\n * A claim is the atomic handover of one session's buffered turns from the\n * live buffer (daemon or CLI process) to a drain worker, so extraction can\n * run out of process without a live buffer owner resurrecting or losing the\n * turns. Files under `<stateDir>/buffer/drain/`:\n *\n * s-<sha256(key)>.json claim content: { key, entry, claimedAt }\n * — the turns NOT yet extracted. Renamed into\n * place from the session file (atomic on one\n * filesystem), rewritten atomically as chunks\n * drain.\n * s-<sha256(key)>.lease.json { ownerId, pid, acquiredAt, heartbeatAt,\n * leaseMs } — created with exclusive-create so\n * exactly one drainer holds a key.\n *\n * Invariants (see the buffer-drain PR for the full protocol):\n * - At every instant the claim content file holds exactly the turns whose\n * extraction is not durably finished, so a kill at any point loses\n * nothing and the next run resumes.\n * - A live buffer owner that must persist a claimed session hands its\n * in-memory entry INTO the claim (merge, identity-deduped) instead of\n * writing a session file — it can never resurrect claimed turns.\n * - Claims are idempotent: re-claiming, double-draining, and two\n * concurrent drainers are all safe (per-key mutation lock + exclusive\n * lease; the lock also fences lease takeover and progress writes).\n *\n * All content reads/writes go through the storage's secure file I/O\n * (`BufferFileIo`); only lease/existence checks touch raw paths, and claim\n * rewrites serialize on a cross-process held file lock.\n */\n\nimport { hostname } from \"node:os\";\nimport { z } from \"zod\";\nimport { existsSync } from \"node:fs\";\nimport { mkdir, readdir, rename, unlink, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { bufferSessionFileName, type BufferFileIo } from \"./buffer-session-files.js\";\nimport { bufferTurnsEqual, subtractBufferTurns } from \"../buffer-turn-helpers.js\";\nimport { log } from \"../logger.js\";\nimport { withHeldFileLock } from \"../utils/serialize-mutations.js\";\nimport type { BufferEntryState, BufferTurn } from \"../types.js\";\n\n/** Result of persisting one session's entry. */\nexport interface BufferSessionSaveOutcome {\n /** \"saved\": the session file holds the entry. \"handed_over\": the entry moved into a drain claim. */\n status: \"saved\" | \"handed_over\";\n /** Turns a drain already extracted (claim tombstone): the owner subtracts them from its copy. */\n drainedTurns?: BufferTurn[];\n}\n\nexport interface DrainLease {\n ownerId: string;\n pid: number;\n /** Host the owner runs on: a takeover may only judge liveness on that host. */\n host: string;\n acquiredAt: string;\n heartbeatAt: string;\n leaseMs: number;\n}\n\nexport interface DrainClaimFile {\n key: string;\n entry: BufferEntryState;\n claimedAt: string;\n /**\n * Turns the drain already extracted. Non-empty means the session was\n * drained and the claim is a TOMBSTONE: a live buffer owner subtracts\n * these from its (possibly stale) copy instead of writing them back, so\n * drained turns can never be resurrected.\n */\n drained?: BufferTurn[];\n /** Same tombstone for turns the drain extracted from `retainedTurns`. */\n drainedRetained?: BufferTurn[];\n}\n\nexport type ClaimFromSessionOutcome = \"claimed\" | \"empty\" | \"already_claimed\";\nexport type AdoptOutcome = \"adopted\" | \"busy\";\nexport type AckOutcome = \"acked\" | \"not_empty\" | \"busy\";\n\nexport interface DrainClaimListing {\n key: string;\n turnCount: number;\n leased: boolean;\n leaseExpired: boolean;\n}\n\n/** Lock while a claim is read-modify-written; released on settle. */\nconst CLAIM_LOCK_STALE_MS = 120_000;\nconst CLAIM_LOCK_MAX_WAIT_MS = 120_000;\n\nfunction emptyEntry(): BufferEntryState {\n return { turns: [], lastExtractionAt: null, extractionCount: 0 };\n}\n\n/** Identity-dedupe `incoming` turns into `existing` (they are the same turns a\n * live owner still held after claiming; only genuinely NEW turns append). */\nfunction mergeTurns<T>(existing: T[], incoming: readonly T[], isEqual: (a: T, b: T) => boolean): T[] {\n const merged = [...existing];\n for (const turn of incoming) {\n if (merged.some((kept) => isEqual(kept, turn))) continue;\n merged.push(turn);\n }\n return merged;\n}\n\nfunction mergeEntries(claim: DrainClaimFile, incoming: BufferEntryState): DrainClaimFile {\n const entry = emptyEntry();\n // Turns already extracted stay out of the pending entry even when the\n // owner's incoming copy is stale and still carries them (either tombstone\n // list counts — a turn in either one left the claim for good).\n const gone = [...(claim.drained ?? []), ...(claim.drainedRetained ?? [])];\n entry.turns = mergeTurns(\n subtractBufferTurns(claim.entry.turns, gone),\n subtractBufferTurns(incoming.turns, gone),\n bufferTurnsEqual,\n );\n const retained = [\n ...subtractBufferTurns(claim.entry.retainedTurns ?? [], gone),\n ...subtractBufferTurns(incoming.retainedTurns ?? [], gone),\n ];\n // SAFETY: an empty accumulator of the same element type.\n const dedupedRetained = mergeTurns([] as typeof retained, retained, bufferTurnsEqual);\n if (dedupedRetained.length > 0) entry.retainedTurns = dedupedRetained;\n const stamps = [claim.entry.lastExtractionAt, incoming.lastExtractionAt].filter(\n (value): value is string => value !== null && value !== undefined,\n );\n entry.lastExtractionAt = stamps.sort().at(-1) ?? null;\n entry.extractionCount = Math.max(claim.entry.extractionCount, incoming.extractionCount);\n return { ...claim, entry };\n}\n\n/**\n * Is the lease owner still running? Only decidable on the owner's host; a\n * lease from another host counts as alive (its expiry decides).\n */\nfunction isOwnerAlive(lease: DrainLease): boolean {\n if (lease.host !== hostname()) return true;\n if (!Number.isInteger(lease.pid) || lease.pid <= 0) return true;\n try {\n process.kill(lease.pid, 0);\n return true;\n } catch (err) {\n // EPERM: the process exists but belongs to another user.\n // SAFETY: the value comes from this module's own write path; the shape is checked above.\n return (err as NodeJS.ErrnoException)?.code === \"EPERM\";\n }\n}\n\nfunction isLeaseExpired(lease: DrainLease, now = Date.now()): boolean {\n const heartbeat = Date.parse(lease.heartbeatAt);\n return !Number.isFinite(heartbeat) || now - heartbeat > lease.leaseMs;\n}\n\nconst claimBodySchema = z.object({\n key: z.string().min(1),\n entry: z.object({\n turns: z.array(z.unknown()),\n retainedTurns: z.array(z.unknown()).optional(),\n lastExtractionAt: z.string().nullable().optional(),\n extractionCount: z.number().optional(),\n }).optional(),\n turns: z.array(z.unknown()).optional(),\n retainedTurns: z.array(z.unknown()).optional(),\n lastExtractionAt: z.string().nullable().optional(),\n extractionCount: z.number().optional(),\n claimedAt: z.string().optional(),\n drained: z.array(z.unknown()).optional(),\n drainedRetained: z.array(z.unknown()).optional(),\n});\n\nfunction parseBody(raw: string): DrainClaimFile | null {\n let body: z.infer<typeof claimBodySchema>;\n try {\n body = claimBodySchema.parse(JSON.parse(raw));\n } catch {\n return null;\n }\n // Two on-disk shapes, one format: a claim born from rename keeps the flat\n // session-file shape ({key, turns, ...}) until its first rewrite, which\n // nests under `entry`. Accept both.\n const source = body.entry && Array.isArray(body.entry.turns) ? body.entry : body;\n if (!Array.isArray(source.turns)) return null;\n const entry = emptyEntry();\n // SAFETY: the schema parsed these as an array of unknown; the claim file\n // is written only by this module (BufferSessionFiles.save), so the entries\n // are BufferTurn values.\n // SAFETY: the claim file is written only by this module, so the parsed\n // turn arrays hold BufferTurn values.\n // SAFETY: the value comes from this module's own write path; the shape is checked above.\n entry.turns = source.turns as BufferTurn[];\n if (source.retainedTurns) {\n // SAFETY: same boundary as the turns above (this module writes the file).\n entry.retainedTurns = source.retainedTurns as BufferTurn[];\n }\n entry.lastExtractionAt = source.lastExtractionAt ?? null;\n entry.extractionCount = Number.isFinite(source.extractionCount) ? Number(source.extractionCount) : 0;\n const claim: DrainClaimFile = {\n key: body.key,\n entry,\n claimedAt: body.claimedAt ?? new Date().toISOString(),\n };\n if (Array.isArray(body.drained)) {\n // SAFETY: same boundary as the turns above (this module writes the file).\n claim.drained = body.drained as BufferTurn[];\n }\n if (Array.isArray(body.drainedRetained)) {\n // SAFETY: same boundary as the turns above (this module writes the file).\n claim.drainedRetained = body.drainedRetained as BufferTurn[];\n }\n return claim;\n}\n\nconst leaseSchema = z.object({\n ownerId: z.string().min(1),\n pid: z.number().optional(),\n host: z.string().optional(),\n acquiredAt: z.string().optional(),\n heartbeatAt: z.string(),\n leaseMs: z.number(),\n});\n\nfunction parseLease(raw: string): DrainLease | null {\n let lease: z.infer<typeof leaseSchema>;\n try {\n lease = leaseSchema.parse(JSON.parse(raw));\n } catch {\n return null;\n }\n return {\n ownerId: lease.ownerId,\n pid: lease.pid ?? 0,\n host: lease.host ?? \"\",\n acquiredAt: lease.acquiredAt ?? lease.heartbeatAt,\n heartbeatAt: lease.heartbeatAt,\n leaseMs: lease.leaseMs,\n };\n}\n\n/** Claim bodies `listPage` may read per requested entry: bounds one scan's\n * I/O even when the directory is mostly empty tombstones. */\nconst LIST_PAGE_SCAN_FACTOR = 8;\n\nexport class BufferDrainClaims {\n constructor(\n private readonly stateDir: string,\n private readonly io: BufferFileIo,\n ) {}\n\n private get dir(): string {\n return path.join(this.stateDir, \"buffer\", \"drain\");\n }\n\n private fileFor(key: string): string {\n return path.join(this.dir, bufferSessionFileName(key));\n }\n\n private leaseFileFor(key: string): string {\n return `${this.fileFor(key)}.lease.json`;\n }\n\n private lockFileFor(key: string): string {\n return `${this.fileFor(key)}.lock`;\n }\n\n /** Sync existence check for hot-path gates (`SmartBuffer.isSessionClaimed`). */\n isClaimed(key: string): boolean {\n return existsSync(this.fileFor(key));\n }\n\n /** Test + CLI seam: the lease file path for direct inspection. */\n leasePathForTest(key: string): string {\n return this.leaseFileFor(key);\n }\n\n private async readClaimBody(key: string): Promise<DrainClaimFile | null> {\n try {\n return parseBody(await this.io.read(this.fileFor(key)));\n } catch {\n return null;\n }\n }\n\n /** The claim content for `key`, or null when the key is not claimed. */\n load(key: string): Promise<DrainClaimFile | null> {\n return this.readClaimBody(key);\n }\n\n private async writeClaimBody(key: string, claim: DrainClaimFile): Promise<void> {\n await mkdir(this.dir, { recursive: true });\n await this.io.write(this.fileFor(key), JSON.stringify({ ...claim, key }));\n }\n\n private async readLease(key: string): Promise<DrainLease | null> {\n try {\n return parseLease(await this.io.read(this.leaseFileFor(key)));\n } catch {\n return null;\n }\n }\n\n private async writeLease(key: string, lease: DrainLease): Promise<void> {\n await mkdir(this.dir, { recursive: true });\n await this.io.write(this.leaseFileFor(key), JSON.stringify(lease));\n }\n\n /**\n * Atomically move a session's persisted file into a claim. The rename is\n * the handover: after it settles the turns exist ONLY as a claim, so no\n * crash window can lose them or leave them in both places. Callers that\n * lose the race (`already_claimed`) simply skip the key.\n *\n * The whole handover runs under the claim's mutation lock, so a claim\n * create can never land on top of an existing claim (POSIX rename would\n * silently replace it) and the lease write cannot race an adopter.\n */\n async claimFromSessionFile(key: string, lease: DrainLease): Promise<ClaimFromSessionOutcome> {\n await mkdir(this.dir, { recursive: true });\n const sessionPath = path.join(this.stateDir, \"buffer\", bufferSessionFileName(key));\n if (existsSync(sessionPath)) {\n // Advisory pre-read: an empty live session belongs to its owner, not\n // the drain. A turn landing between this read and the rename is fine —\n // the rename moves whatever is there and the claim drains it.\n try {\n const seeded = parseBody(await this.io.read(sessionPath));\n if (seeded && seeded.entry.turns.length === 0 && (seeded.entry.retainedTurns?.length ?? 0) === 0) {\n return \"empty\";\n }\n } catch {\n // Unreadable live file: proceed with the rename and let the claim\n // path report it loudly, like the loader skips corrupt files.\n }\n }\n return await withHeldFileLock(\n this.lockFileFor(key),\n { staleMs: CLAIM_LOCK_STALE_MS, maxWaitMs: CLAIM_LOCK_MAX_WAIT_MS },\n async (acquired) => {\n if (!acquired) return \"already_claimed\";\n // Under the lock the claim check is authoritative: an existing claim\n // keeps its content and its lease; the session file stays with its\n // owner (their next save merges into the claim).\n if (this.isClaimed(key)) return \"already_claimed\";\n try {\n await rename(sessionPath, this.fileFor(key));\n } catch (err) {\n // The session file is gone: either it never existed, or another\n // drainer claimed it first.\n if (!existsSync(sessionPath)) {\n if (this.isClaimed(key)) return \"already_claimed\";\n return \"empty\";\n }\n throw err;\n }\n const claim = await this.readClaimBody(key);\n if (!claim || (claim.entry.turns.length === 0 && (claim.entry.retainedTurns?.length ?? 0) === 0)) {\n // Empty content: nothing to drain. Delete the moved file so the\n // empty entry keeps living with its owner, not as a stale claim.\n await unlink(this.fileFor(key)).catch(() => {});\n return \"empty\";\n }\n await this.writeLease(key, lease);\n return \"claimed\";\n },\n );\n }\n\n /**\n * Take over a claim whose lease is missing, expired, or whose owner died.\n * The whole judgement runs under the claim's mutation lock and re-reads\n * the lease inside it, so exactly one concurrent adopter wins: the loser\n * re-reads the winner's fresh lease and reports \"busy\" instead of\n * unlinking it. A lease whose owner died is taken over in the same call.\n */\n async adopt(key: string, lease: DrainLease): Promise<AdoptOutcome> {\n if (!this.isClaimed(key)) return \"busy\";\n return await withHeldFileLock(\n this.lockFileFor(key),\n { staleMs: CLAIM_LOCK_STALE_MS, maxWaitMs: CLAIM_LOCK_MAX_WAIT_MS },\n async (acquired) => {\n if (!acquired) return \"busy\";\n const existing = await this.readLease(key);\n if (existing && !isLeaseExpired(existing)) {\n if (existing.ownerId === lease.ownerId) {\n // The same drainer already holds it: renew the lease.\n await this.writeLease(key, { ...existing, heartbeatAt: new Date().toISOString(), leaseMs: lease.leaseMs });\n return \"adopted\";\n }\n if (isOwnerAlive(existing)) return \"busy\";\n // The owner died without releasing the claim (killed worker):\n // take it over now instead of waiting for the lease to expire.\n await unlink(this.leaseFileFor(key)).catch(() => {});\n } else if (existing) {\n // Expired lease: clear it before the exclusive create. We hold the\n // lock, so no concurrent adopter can interleave its own\n // unlink/create between these steps.\n await unlink(this.leaseFileFor(key)).catch(() => {});\n }\n try {\n await writeFile(this.leaseFileFor(key), JSON.stringify(lease), { flag: \"wx\" });\n } catch (err) {\n // The lease file exists: another drainer took it first.\n if (existsSync(this.leaseFileFor(key))) return \"busy\";\n throw err;\n }\n return \"adopted\";\n },\n );\n }\n\n /**\n * Merge a live owner's in-memory entry INTO the claim (the handover path a\n * buffer owner takes instead of persisting a claimed session). Serialized\n * against drainer progress writes on the same lock.\n */\n async mergeIntoClaim(key: string, incoming: BufferEntryState): Promise<void> {\n await withHeldFileLock(this.lockFileFor(key), { staleMs: CLAIM_LOCK_STALE_MS, maxWaitMs: CLAIM_LOCK_MAX_WAIT_MS }, async (acquired) => {\n if (!acquired) {\n // Never fall back to a session-file write behind a held claim lock:\n // that would resurrect claimed turns. Fail so the owner's save stays\n // pending and retries (SmartBuffer keeps pending state on failure).\n throw new Error(`buffer drain claim for ${key} is busy; retrying the save later`);\n }\n const claim = await this.readClaimBody(key);\n if (!claim) {\n // The claim vanished (acked between the isClaimed check and now):\n // nothing to hand over; the drain already owns everything.\n return;\n }\n await this.writeClaimBody(key, mergeEntries(claim, incoming));\n });\n }\n\n /** Drainer progress: replace the claim content with what is left to do.\n *\n * Fenced: `ownerId` must still match the claim's lease (checked under the\n * same lock), so a drainer that lost its lease cannot write progress that\n * would restore turns the new owner already extracted.\n *\n * `seen` is the entry the drainer LOADED (its snapshot of the claim). Only\n * turns that were in that snapshot and are absent from BOTH of the\n * drainer's lists were actually extracted and move to the tombstones — a\n * turn the drainer moved into `entry.retainedTurns` is retained for retry,\n * not extracted. Turns a live owner merged into the claim afterwards stay\n * pending. Without `seen` the pre-existing behavior is kept (every claim\n * turn missing from `entry` counts as extracted).\n */\n async writeEntry(key: string, ownerId: string, entry: BufferEntryState, seen?: BufferEntryState): Promise<void> {\n await withHeldFileLock(this.lockFileFor(key), { staleMs: CLAIM_LOCK_STALE_MS, maxWaitMs: CLAIM_LOCK_MAX_WAIT_MS }, async (acquired) => {\n if (!acquired) throw new Error(`buffer drain claim for ${key} is busy; progress write skipped`);\n const lease = await this.readLease(key);\n if (!lease || lease.ownerId !== ownerId) {\n throw new Error(`buffer drain claim for ${key} is no longer owned by ${ownerId}; progress write skipped`);\n }\n const claim = await this.readClaimBody(key);\n if (!claim) return;\n const seenTurns = seen?.turns ?? claim.entry.turns;\n const seenRetained = seen?.retainedTurns ?? claim.entry.retainedTurns ?? [];\n const entryRetained = entry.retainedTurns ?? [];\n // Still held by the drainer: in its remaining turns OR retained for retry.\n const kept = (turn: BufferTurn): boolean =>\n entry.turns.some((held) => bufferTurnsEqual(held, turn)) || entryRetained.some((held) => bufferTurnsEqual(held, turn));\n // Progress: the snapshot turns that vanished from the entry were extracted.\n const drained = [...(claim.drained ?? [])];\n for (const turn of claim.entry.turns) {\n const extracted =\n seenTurns.some((snapshot) => bufferTurnsEqual(snapshot, turn)) && !kept(turn);\n if (!extracted) continue;\n if (drained.some((gone) => bufferTurnsEqual(gone, turn))) continue;\n drained.push(turn);\n }\n const drainedRetained = [...(claim.drainedRetained ?? [])];\n for (const turn of claim.entry.retainedTurns ?? []) {\n const extracted =\n seenRetained.some((snapshot) => bufferTurnsEqual(snapshot, turn)) && !kept(turn);\n if (!extracted) continue;\n if (drained.some((gone) => bufferTurnsEqual(gone, turn)) || drainedRetained.some((gone) => bufferTurnsEqual(gone, turn))) continue;\n drainedRetained.push(turn);\n }\n // Turns merged in after the drainer loaded stay pending, in extraction\n // order behind the drainer's own remainder.\n const late = claim.entry.turns.filter(\n (turn) =>\n !seenTurns.some((snapshot) => bufferTurnsEqual(snapshot, turn)) && !kept(turn),\n );\n const next: BufferEntryState = { ...entry, turns: [...entry.turns, ...late] };\n const lateRetained = (claim.entry.retainedTurns ?? []).filter(\n (turn) =>\n !seenRetained.some((snapshot) => bufferTurnsEqual(snapshot, turn)) && !kept(turn),\n );\n if (lateRetained.length > 0) next.retainedTurns = mergeTurns(entryRetained, lateRetained, bufferTurnsEqual);\n await this.writeClaimBody(key, { ...claim, entry: next, drained, drainedRetained });\n });\n }\n\n /** Extend the lease. \"lost\" means another drainer took the claim over.\n * Serialized on the claim lock so a stale heartbeat can never overwrite a\n * new owner's lease mid-takeover; a contended lock just skips this tick\n * (usually the drainer's own progress write holds it). */\n async heartbeat(key: string, ownerId: string, leaseMs: number): Promise<\"ok\" | \"lost\"> {\n return await withHeldFileLock(this.lockFileFor(key), { staleMs: CLAIM_LOCK_STALE_MS, maxWaitMs: CLAIM_LOCK_MAX_WAIT_MS }, async (acquired) => {\n if (!acquired) return \"ok\";\n const lease = await this.readLease(key);\n if (!lease || lease.ownerId !== ownerId) return \"lost\";\n await this.writeLease(key, { ...lease, heartbeatAt: new Date().toISOString(), leaseMs });\n return \"ok\";\n });\n }\n\n /** Give the key back (content stays; the next run resumes it). */\n async releaseLease(key: string, ownerId: string): Promise<void> {\n const lease = await this.readLease(key);\n if (!lease || lease.ownerId !== ownerId) return;\n await unlink(this.leaseFileFor(key)).catch(() => {});\n }\n\n /**\n * Finish a claim: only its owner may ack, and only when nothing is left to\n * extract. Non-empty claims stay (deferred content is never dropped here).\n */\n async ack(key: string, ownerId: string): Promise<AckOutcome> {\n const lease = await this.readLease(key);\n if (!lease || lease.ownerId !== ownerId) return \"busy\";\n const claim = await this.readClaimBody(key);\n if (!claim) {\n await unlink(this.leaseFileFor(key)).catch(() => {});\n return \"acked\";\n }\n if (claim.entry.turns.length > 0 || (claim.entry.retainedTurns?.length ?? 0) > 0) return \"not_empty\";\n // Keep the (empty) claim file as a TOMBSTONE: a live buffer owner that\n // still holds a stale copy subtracts the drained turns instead of\n // writing them back. The owner consumes the tombstone on its next save.\n await unlink(this.leaseFileFor(key)).catch(() => {});\n return \"acked\";\n }\n\n /** The turns a drain already extracted for `key` (tombstone content). */\n async drainedTurns(key: string): Promise<BufferTurn[]> {\n const claim = await this.readClaimBody(key);\n return claim?.drained ?? [];\n }\n\n /**\n * Is the drain for `key` FINISHED (acked): the claim holds extracted turns\n * and nothing pending, and the drainer dropped its lease. A claim with\n * partial progress or a live lease is still being drained — its owner must\n * merge into it, never release it.\n */\n async isDrained(key: string): Promise<boolean> {\n const claim = await this.readClaimBody(key);\n if (!claim) return false;\n if ((claim.drained?.length ?? 0) === 0 && (claim.drainedRetained?.length ?? 0) === 0) return false;\n if (claim.entry.turns.length > 0 || (claim.entry.retainedTurns?.length ?? 0) > 0) return false;\n return (await this.readLease(key)) === null;\n }\n\n /** Drop the claim file entirely (its owner consumed the tombstone). */\n async release(key: string): Promise<void> {\n await unlink(this.fileFor(key)).catch(() => {});\n await unlink(this.leaseFileFor(key)).catch(() => {});\n }\n\n /** Every claim with its lease state (CLI scan + summaries). */\n async list(): Promise<DrainClaimListing[]> {\n let names: string[];\n try {\n names = (await readdir(this.dir)).filter((name) => name.endsWith(\".json\") && !name.endsWith(\".lease.json\"));\n } catch (err) {\n // SAFETY: the value comes from this module's own write path; the shape is checked above.\n if ((err as NodeJS.ErrnoException)?.code === \"ENOENT\") return [];\n throw err;\n }\n const out: DrainClaimListing[] = [];\n for (const name of names) {\n const claim = await this.readClaimBodyByKey(name);\n if (!claim) continue;\n const lease = await this.readLease(claim.key);\n out.push({\n key: claim.key,\n turnCount: claim.entry.turns.length + (claim.entry.retainedTurns?.length ?? 0),\n leased: lease !== null,\n leaseExpired: lease !== null ? isLeaseExpired(lease) : true,\n });\n }\n return out;\n }\n\n /**\n * Paged listing for bounded periodic scans: at most `limit` claims with\n * pending turns, starting AFTER `afterName` (a raw entry name returned as\n * `nextCursor` by a previous page — file-name order is stable, so a\n * rotating cursor visits every claim fairly without ever materializing\n * the whole directory's bodies). Empty tombstones never count toward the\n * limit; the number of bodies read per call is bounded separately\n * (`LIST_PAGE_SCAN_FACTOR` per requested entry), and the cursor advances\n * past every scanned name so a long tombstone run is crossed over several\n * pages instead of starving the pending claims behind it.\n */\n async listPage(afterName: string | undefined, limit: number): Promise<{ entries: DrainClaimListing[]; nextCursor?: string }> {\n let names: string[];\n try {\n names = (await readdir(this.dir)).filter((name) => name.endsWith(\".json\") && !name.endsWith(\".lease.json\")).sort();\n } catch (err) {\n // SAFETY: readdir rejects with NodeJS system errors; only the ENOENT\n // (no claims directory yet) case maps to an empty listing.\n const errno = (err as NodeJS.ErrnoException | undefined)?.code;\n if (errno === \"ENOENT\") return { entries: [] };\n throw err;\n }\n const start = afterName === undefined ? 0 : names.findIndex((name) => name > afterName);\n const out: DrainClaimListing[] = [];\n // The cursor is the LAST CONSUMED entry name: the next page starts\n // strictly after it, so the first unconsumed name is never skipped.\n // Returning the first unconsumed name instead would make every page\n // boundary permanently skip one claim.\n let cursor: string | undefined;\n const scanBudget = Math.max(limit, 1) * LIST_PAGE_SCAN_FACTOR;\n let scanned = 0;\n for (let index = start < 0 ? names.length : start; index < names.length; index += 1) {\n if (out.length >= limit || scanned >= scanBudget) break;\n const name = names[index]!;\n scanned += 1;\n cursor = name;\n const claim = await this.readClaimBodyByKey(name);\n if (!claim) continue;\n const turnCount = claim.entry.turns.length + (claim.entry.retainedTurns?.length ?? 0);\n if (turnCount <= 0) continue;\n const lease = await this.readLease(claim.key);\n out.push({\n key: claim.key,\n turnCount,\n leased: lease !== null,\n leaseExpired: lease !== null ? isLeaseExpired(lease) : true,\n });\n }\n return { entries: out, nextCursor: cursor };\n }\n\n private async readClaimBodyByKey(name: string): Promise<DrainClaimFile | null> {\n try {\n return parseBody(await this.io.read(path.join(this.dir, name)));\n } catch (err) {\n log.debug(`buffer drain: skipping unreadable claim file ${name}: ${err instanceof Error ? err.message : String(err)}`);\n return null;\n }\n }\n}\n","import { BufferDrainClaims, type BufferSessionSaveOutcome } from \"./buffer-drain-claims.js\";\nimport { createHash } from \"node:crypto\";\nimport type { Stats } from \"node:fs\";\nimport { lstat, readdir, unlink } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { subtractBufferTurns } from \"../buffer-turn-helpers.js\";\nimport { log } from \"../logger.js\";\nimport { SecureStoreDecryptError, SecureStoreLockedError } from \"../secure-store/secure-fs.js\";\nimport type { BufferEntryState, BufferState, BufferTurn } from \"../types.js\";\n\n/** The secure-store file I/O the owning StorageManager already provides. */\nexport interface BufferFileIo {\n read(filePath: string): Promise<string>;\n write(filePath: string, content: string): Promise<void>;\n}\n\ntype FsError = NodeJS.ErrnoException;\n\nfunction isMissing(err: FsError | null): boolean {\n return err?.code === \"ENOENT\";\n}\n\nfunction asFsError(err: Error): FsError {\n return err;\n}\n\n/**\n * Case-independent, collision-free file name for a buffer key: the lowercase\n * hex SHA-256 of the key under a fixed prefix. Hex has no case and no path\n * characters, so `Session-A` and `session-a` get different names even on\n * case-insensitive filesystems, every key (short or long) lives in the same\n * naming domain, and the key itself is stored inside the file (loading never\n * inverts the hash).\n */\nexport function bufferSessionFileName(key: string): string {\n return `s-${createHash(\"sha256\").update(key).digest(\"hex\")}.json`;\n}\n\n/** Flatten a legacy BufferState into per-session entries (top-level fields\n * are the legacy mirror of the \"default\" entry). */\nexport function legacyStateToEntries(state: BufferState): Map<string, BufferEntryState> {\n const entries = new Map<string, BufferEntryState>();\n for (const [key, value] of Object.entries(state.entries ?? {})) {\n if (value && Array.isArray(value.turns)) entries.set(key, value);\n }\n if (!entries.has(\"default\")) {\n entries.set(\"default\", {\n turns: Array.isArray(state.turns) ? state.turns : [],\n lastExtractionAt: state.lastExtractionAt ?? null,\n extractionCount: Number.isFinite(state.extractionCount) ? state.extractionCount : 0,\n });\n }\n return entries;\n}\n\n/** What a per-session file contains once parsed (written only by this module). */\ninterface SessionFileBody {\n key?: string;\n turns?: BufferTurn[];\n retainedTurns?: BufferTurn[];\n lastExtractionAt?: string | null;\n extractionCount?: number;\n}\n\nfunction entryFromBody(body: SessionFileBody): { key: string; entry: BufferEntryState } | null {\n if (!body || !Array.isArray(body.turns) || (body.key?.length ?? 0) === 0) return null;\n const entry: BufferEntryState = {\n turns: body.turns,\n lastExtractionAt: body.lastExtractionAt ?? null,\n extractionCount: Number.isFinite(body.extractionCount) ? Number(body.extractionCount) : 0,\n };\n if (Array.isArray(body.retainedTurns)) entry.retainedTurns = body.retainedTurns;\n return { key: String(body.key), entry };\n}\n\n/**\n * One file per buffer session under `<stateDir>/buffer/`, so the observe hot\n * path persists only the session that changed. All writes happen under\n * SmartBuffer's serialized mutation chain (one writer per process).\n *\n * Sessions claimed by `remnic buffer drain` (see buffer-drain-claims.ts) are\n * owned by the drain: `save` hands the in-memory entry INTO the claim\n * (identity-deduped merge) instead of writing a session file, so a live\n * buffer owner can never resurrect claimed turns.\n */\nexport class BufferSessionFiles {\n private dirVerified = false;\n private readonly claimsStore: BufferDrainClaims;\n\n constructor(\n private readonly stateDir: string,\n private readonly io: BufferFileIo,\n ) {\n this.claimsStore = new BufferDrainClaims(stateDir, io);\n }\n\n /** The drain-claim store for this state dir (CLI scan + claim ops). */\n get claims(): BufferDrainClaims {\n return this.claimsStore;\n }\n\n /** Sync gate check: is this session claimed by a buffer drain? */\n isClaimed(key: string): boolean {\n return this.claimsStore.isClaimed(key);\n }\n\n private get dir(): string {\n return path.join(this.stateDir, \"buffer\");\n }\n\n private fileFor(key: string): string {\n return path.join(this.dir, bufferSessionFileName(key));\n }\n\n /**\n * Refuse a symlinked buffer directory: following it would send buffered\n * conversation data (and deletions) outside the memory root. One lstat of\n * one directory, cached after it passes, so the observe path pays nothing\n * per write; the cache is dropped on any failure.\n */\n private async assertDirSafe(): Promise<void> {\n if (this.dirVerified) return;\n let stat: Stats | null = null;\n try {\n stat = await lstat(this.dir);\n } catch (err) {\n if (!(err instanceof Error) || !isMissing(asFsError(err))) throw err;\n }\n if (stat && (stat.isSymbolicLink() || !stat.isDirectory())) {\n throw new Error(`buffer session directory must be a real directory, not a symlink or file: ${this.dir}`);\n }\n this.dirVerified = stat !== null;\n }\n\n /** Persist ONE session atomically. Compact JSON: machine-read state, and\n * pretty-printing a large buffer costs CPU and bytes on every observe.\n * A drain-claimed session is handed over instead (see class doc). */\n async save(key: string, entry: BufferEntryState): Promise<BufferSessionSaveOutcome> {\n if (this.isClaimed(key)) {\n // Consume the tombstone ONLY when the drain finished (acked: extracted\n // turns recorded, nothing pending, no lease). Partial `drained` just\n // means chunks completed — the drainer may still be running, and\n // releasing here would unlink its claim and turn its later writes into\n // no-ops.\n if (await this.claimsStore.isDrained(key)) {\n const drained = await this.claimsStore.drainedTurns(key);\n // The drain finished this session: the claim is a tombstone. Write\n // the owner's fresh remainder back as a session file (the drained\n // turns are subtracted here too, so a caller that did not subtract\n // them cannot resurrect them) and release the tombstone.\n const fresh: BufferEntryState = { ...entry, turns: subtractBufferTurns(entry.turns, drained) };\n if (entry.retainedTurns && entry.retainedTurns.length > 0) {\n fresh.retainedTurns = subtractBufferTurns(entry.retainedTurns, drained);\n if (fresh.retainedTurns.length === 0) delete fresh.retainedTurns;\n }\n await this.assertDirSafe();\n await this.io.write(this.fileFor(key), JSON.stringify({ key, ...fresh }));\n await this.claimsStore.release(key);\n return { status: \"saved\", drainedTurns: drained };\n }\n await this.claimsStore.mergeIntoClaim(key, entry);\n return { status: \"handed_over\" };\n }\n await this.assertDirSafe();\n await this.io.write(this.fileFor(key), JSON.stringify({ key, ...entry }));\n return { status: \"saved\" };\n }\n\n /** Remove one session's file. Idempotent: an absent file is fine. */\n async remove(key: string): Promise<void> {\n await this.assertDirSafe();\n try {\n await unlink(this.fileFor(key));\n } catch (err) {\n if (!(err instanceof Error) || !isMissing(asFsError(err))) throw err;\n }\n }\n\n private async listFiles(): Promise<string[]> {\n await this.assertDirSafe();\n try {\n const dirents = await readdir(this.dir, { withFileTypes: true });\n // Regular files only: a symlink inside the directory is never followed.\n return dirents.filter((d) => d.isFile() && d.name.endsWith(\".json\")).map((d) => d.name);\n } catch (err) {\n if (err instanceof Error && isMissing(asFsError(err))) return [];\n throw err;\n }\n }\n\n /** Read every per-session file. Corrupt files are skipped (and logged) so\n * one bad file cannot nuke the other sessions' buffered turns. */\n private async readAll(): Promise<{ entries: Map<string, BufferEntryState>; fileCount: number }> {\n const names = await this.listFiles();\n const entries = new Map<string, BufferEntryState>();\n for (const name of names) {\n try {\n const body: SessionFileBody = JSON.parse(await this.io.read(path.join(this.dir, name)));\n const parsed = entryFromBody(body);\n if (!parsed) throw new Error(\"invalid buffer session file shape\");\n if (entries.has(parsed.key)) {\n log.warn(`buffer: duplicate session file for key ${parsed.key} (${name}); keeping the first`);\n continue;\n }\n entries.set(parsed.key, parsed.entry);\n } catch (err) {\n // A locked store or a wrong key is not a corrupt file: reading it as empty would let\n // later saves replace the real encrypted session.\n if (err instanceof SecureStoreLockedError || err instanceof SecureStoreDecryptError) throw err;\n log.warn(`buffer: skipping unreadable per-session buffer file ${name}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n return { entries, fileCount: names.length };\n }\n\n /**\n * Read the legacy single-file buffer.json for migration. Null when absent,\n * or unreadable while session files exist (they are authoritative; a\n * corrupt leftover must not block startup). With no session files a corrupt\n * legacy file THROWS, preserving the old contract so callers can reset it.\n */\n private async readLegacy(hasSessionFiles: boolean): Promise<BufferState | null> {\n let raw: string;\n try {\n raw = await this.io.read(path.join(this.stateDir, \"buffer.json\"));\n } catch (err) {\n if (err instanceof SecureStoreLockedError) throw err;\n if (err instanceof Error && isMissing(asFsError(err))) return null;\n throw err;\n }\n try {\n const legacy: BufferState = JSON.parse(raw);\n return legacy;\n } catch (err) {\n if (!hasSessionFiles) throw err;\n log.warn(\n `buffer: legacy buffer.json is unreadable but per-session buffer files exist; ` +\n `ignoring the legacy file (${err instanceof Error ? err.message : String(err)})`,\n );\n return null;\n }\n }\n\n /**\n * Load the whole buffer, migrating a legacy buffer.json once. A per-session\n * file that parsed wins over the legacy snapshot (a crash mid-migration\n * leaves exactly that pair); legacy entries without a usable file are\n * written out, then the legacy file is retired. Both steps are idempotent.\n */\n async load(): Promise<BufferState> {\n const { entries, fileCount } = await this.readAll();\n const legacy = await this.readLegacy(fileCount > 0);\n if (legacy !== null) {\n for (const [key, entry] of legacyStateToEntries(legacy)) {\n if (entries.has(key)) continue;\n entries.set(key, entry);\n await this.save(key, entry);\n }\n try {\n await unlink(path.join(this.stateDir, \"buffer.json\"));\n } catch (err) {\n if (!(err instanceof Error) || !isMissing(asFsError(err))) throw err;\n }\n }\n const out: NonNullable<BufferState[\"entries\"]> = Object.create(null);\n for (const [key, entry] of entries) out[key] = entry;\n const def = entries.get(\"default\");\n return {\n turns: def?.turns ?? [],\n lastExtractionAt: def?.lastExtractionAt ?? null,\n extractionCount: def?.extractionCount ?? 0,\n entries: out,\n };\n }\n\n /**\n * Reconcile the files with a WHOLE buffer state: write every entry, then\n * delete files for keys no longer present. Writes first, deletes last: a\n * crash leaves stale extra files (removed by the next reconcile) but never\n * loses sessions the state says exist. Not on the observe hot path.\n */\n async reconcile(state: BufferState): Promise<void> {\n const entries = legacyStateToEntries(state);\n const existing = await this.listFiles();\n for (const [key, entry] of entries) await this.save(key, entry);\n const live = new Set([...entries.keys()].map((key) => bufferSessionFileName(key)));\n for (const name of existing) {\n if (live.has(name)) continue;\n try {\n await unlink(path.join(this.dir, name));\n } catch (err) {\n if (!(err instanceof Error) || !isMissing(asFsError(err))) throw err;\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiCA,SAAS,gBAAgB;AACzB,SAAS,SAAS;AAClB,SAAS,kBAAkB;AAC3B,SAAS,OAAO,WAAAA,UAAS,QAAQ,UAAAC,SAAQ,iBAAiB;AAC1D,OAAOC,WAAU;;;ACpCjB,SAAS,kBAAkB;AAE3B,SAAS,OAAO,SAAS,cAAc;AACvC,OAAO,UAAU;AAcjB,SAAS,UAAU,KAA8B;AAC/C,SAAO,KAAK,SAAS;AACvB;AAEA,SAAS,UAAU,KAAqB;AACtC,SAAO;AACT;AAUO,SAAS,sBAAsB,KAAqB;AACzD,SAAO,KAAK,WAAW,QAAQ,EAAE,OAAO,GAAG,EAAE,OAAO,KAAK,CAAC;AAC5D;AAIO,SAAS,qBAAqB,OAAmD;AACtF,QAAM,UAAU,oBAAI,IAA8B;AAClD,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,WAAW,CAAC,CAAC,GAAG;AAC9D,QAAI,SAAS,MAAM,QAAQ,MAAM,KAAK,EAAG,SAAQ,IAAI,KAAK,KAAK;AAAA,EACjE;AACA,MAAI,CAAC,QAAQ,IAAI,SAAS,GAAG;AAC3B,YAAQ,IAAI,WAAW;AAAA,MACrB,OAAO,MAAM,QAAQ,MAAM,KAAK,IAAI,MAAM,QAAQ,CAAC;AAAA,MACnD,kBAAkB,MAAM,oBAAoB;AAAA,MAC5C,iBAAiB,OAAO,SAAS,MAAM,eAAe,IAAI,MAAM,kBAAkB;AAAA,IACpF,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAWA,SAAS,cAAc,MAAwE;AAC7F,MAAI,CAAC,QAAQ,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,KAAK,KAAK,UAAU,OAAO,EAAG,QAAO;AACjF,QAAM,QAA0B;AAAA,IAC9B,OAAO,KAAK;AAAA,IACZ,kBAAkB,KAAK,oBAAoB;AAAA,IAC3C,iBAAiB,OAAO,SAAS,KAAK,eAAe,IAAI,OAAO,KAAK,eAAe,IAAI;AAAA,EAC1F;AACA,MAAI,MAAM,QAAQ,KAAK,aAAa,EAAG,OAAM,gBAAgB,KAAK;AAClE,SAAO,EAAE,KAAK,OAAO,KAAK,GAAG,GAAG,MAAM;AACxC;AAYO,IAAM,qBAAN,MAAyB;AAAA,EAI9B,YACmB,UACA,IACjB;AAFiB;AACA;AAEjB,SAAK,cAAc,IAAI,kBAAkB,UAAU,EAAE;AAAA,EACvD;AAAA,EAJmB;AAAA,EACA;AAAA,EALX,cAAc;AAAA,EACL;AAAA;AAAA,EAUjB,IAAI,SAA4B;AAC9B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,UAAU,KAAsB;AAC9B,WAAO,KAAK,YAAY,UAAU,GAAG;AAAA,EACvC;AAAA,EAEA,IAAY,MAAc;AACxB,WAAO,KAAK,KAAK,KAAK,UAAU,QAAQ;AAAA,EAC1C;AAAA,EAEQ,QAAQ,KAAqB;AACnC,WAAO,KAAK,KAAK,KAAK,KAAK,sBAAsB,GAAG,CAAC;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,gBAA+B;AAC3C,QAAI,KAAK,YAAa;AACtB,QAAI,OAAqB;AACzB,QAAI;AACF,aAAO,MAAM,MAAM,KAAK,GAAG;AAAA,IAC7B,SAAS,KAAK;AACZ,UAAI,EAAE,eAAe,UAAU,CAAC,UAAU,UAAU,GAAG,CAAC,EAAG,OAAM;AAAA,IACnE;AACA,QAAI,SAAS,KAAK,eAAe,KAAK,CAAC,KAAK,YAAY,IAAI;AAC1D,YAAM,IAAI,MAAM,6EAA6E,KAAK,GAAG,EAAE;AAAA,IACzG;AACA,SAAK,cAAc,SAAS;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KAAK,KAAa,OAA4D;AAClF,QAAI,KAAK,UAAU,GAAG,GAAG;AAMvB,UAAI,MAAM,KAAK,YAAY,UAAU,GAAG,GAAG;AACzC,cAAM,UAAU,MAAM,KAAK,YAAY,aAAa,GAAG;AAKvD,cAAM,QAA0B,EAAE,GAAG,OAAO,OAAO,oBAAoB,MAAM,OAAO,OAAO,EAAE;AAC7F,YAAI,MAAM,iBAAiB,MAAM,cAAc,SAAS,GAAG;AACzD,gBAAM,gBAAgB,oBAAoB,MAAM,eAAe,OAAO;AACtE,cAAI,MAAM,cAAc,WAAW,EAAG,QAAO,MAAM;AAAA,QACrD;AACA,cAAM,KAAK,cAAc;AACzB,cAAM,KAAK,GAAG,MAAM,KAAK,QAAQ,GAAG,GAAG,KAAK,UAAU,EAAE,KAAK,GAAG,MAAM,CAAC,CAAC;AACxE,cAAM,KAAK,YAAY,QAAQ,GAAG;AAClC,eAAO,EAAE,QAAQ,SAAS,cAAc,QAAQ;AAAA,MAClD;AACA,YAAM,KAAK,YAAY,eAAe,KAAK,KAAK;AAChD,aAAO,EAAE,QAAQ,cAAc;AAAA,IACjC;AACA,UAAM,KAAK,cAAc;AACzB,UAAM,KAAK,GAAG,MAAM,KAAK,QAAQ,GAAG,GAAG,KAAK,UAAU,EAAE,KAAK,GAAG,MAAM,CAAC,CAAC;AACxE,WAAO,EAAE,QAAQ,QAAQ;AAAA,EAC3B;AAAA;AAAA,EAGA,MAAM,OAAO,KAA4B;AACvC,UAAM,KAAK,cAAc;AACzB,QAAI;AACF,YAAM,OAAO,KAAK,QAAQ,GAAG,CAAC;AAAA,IAChC,SAAS,KAAK;AACZ,UAAI,EAAE,eAAe,UAAU,CAAC,UAAU,UAAU,GAAG,CAAC,EAAG,OAAM;AAAA,IACnE;AAAA,EACF;AAAA,EAEA,MAAc,YAA+B;AAC3C,UAAM,KAAK,cAAc;AACzB,QAAI;AACF,YAAM,UAAU,MAAM,QAAQ,KAAK,KAAK,EAAE,eAAe,KAAK,CAAC;AAE/D,aAAO,QAAQ,OAAO,CAAC,MAAM,EAAE,OAAO,KAAK,EAAE,KAAK,SAAS,OAAO,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,IACxF,SAAS,KAAK;AACZ,UAAI,eAAe,SAAS,UAAU,UAAU,GAAG,CAAC,EAAG,QAAO,CAAC;AAC/D,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA,EAIA,MAAc,UAAkF;AAC9F,UAAM,QAAQ,MAAM,KAAK,UAAU;AACnC,UAAM,UAAU,oBAAI,IAA8B;AAClD,eAAW,QAAQ,OAAO;AACxB,UAAI;AACF,cAAM,OAAwB,KAAK,MAAM,MAAM,KAAK,GAAG,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI,CAAC,CAAC;AACtF,cAAM,SAAS,cAAc,IAAI;AACjC,YAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,mCAAmC;AAChE,YAAI,QAAQ,IAAI,OAAO,GAAG,GAAG;AAC3B,cAAI,KAAK,0CAA0C,OAAO,GAAG,KAAK,IAAI,sBAAsB;AAC5F;AAAA,QACF;AACA,gBAAQ,IAAI,OAAO,KAAK,OAAO,KAAK;AAAA,MACtC,SAAS,KAAK;AAGZ,YAAI,eAAe,0BAA0B,eAAe,wBAAyB,OAAM;AAC3F,YAAI,KAAK,uDAAuD,IAAI,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,MAC7H;AAAA,IACF;AACA,WAAO,EAAE,SAAS,WAAW,MAAM,OAAO;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,WAAW,iBAAuD;AAC9E,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,KAAK,GAAG,KAAK,KAAK,KAAK,KAAK,UAAU,aAAa,CAAC;AAAA,IAClE,SAAS,KAAK;AACZ,UAAI,eAAe,uBAAwB,OAAM;AACjD,UAAI,eAAe,SAAS,UAAU,UAAU,GAAG,CAAC,EAAG,QAAO;AAC9D,YAAM;AAAA,IACR;AACA,QAAI;AACF,YAAM,SAAsB,KAAK,MAAM,GAAG;AAC1C,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,UAAI,CAAC,gBAAiB,OAAM;AAC5B,UAAI;AAAA,QACF,0GAC+B,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,MACjF;AACA,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,OAA6B;AACjC,UAAM,EAAE,SAAS,UAAU,IAAI,MAAM,KAAK,QAAQ;AAClD,UAAM,SAAS,MAAM,KAAK,WAAW,YAAY,CAAC;AAClD,QAAI,WAAW,MAAM;AACnB,iBAAW,CAAC,KAAK,KAAK,KAAK,qBAAqB,MAAM,GAAG;AACvD,YAAI,QAAQ,IAAI,GAAG,EAAG;AACtB,gBAAQ,IAAI,KAAK,KAAK;AACtB,cAAM,KAAK,KAAK,KAAK,KAAK;AAAA,MAC5B;AACA,UAAI;AACF,cAAM,OAAO,KAAK,KAAK,KAAK,UAAU,aAAa,CAAC;AAAA,MACtD,SAAS,KAAK;AACZ,YAAI,EAAE,eAAe,UAAU,CAAC,UAAU,UAAU,GAAG,CAAC,EAAG,OAAM;AAAA,MACnE;AAAA,IACF;AACA,UAAM,MAA2C,uBAAO,OAAO,IAAI;AACnE,eAAW,CAAC,KAAK,KAAK,KAAK,QAAS,KAAI,GAAG,IAAI;AAC/C,UAAM,MAAM,QAAQ,IAAI,SAAS;AACjC,WAAO;AAAA,MACL,OAAO,KAAK,SAAS,CAAC;AAAA,MACtB,kBAAkB,KAAK,oBAAoB;AAAA,MAC3C,iBAAiB,KAAK,mBAAmB;AAAA,MACzC,SAAS;AAAA,IACX;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,UAAU,OAAmC;AACjD,UAAM,UAAU,qBAAqB,KAAK;AAC1C,UAAM,WAAW,MAAM,KAAK,UAAU;AACtC,eAAW,CAAC,KAAK,KAAK,KAAK,QAAS,OAAM,KAAK,KAAK,KAAK,KAAK;AAC9D,UAAM,OAAO,IAAI,IAAI,CAAC,GAAG,QAAQ,KAAK,CAAC,EAAE,IAAI,CAAC,QAAQ,sBAAsB,GAAG,CAAC,CAAC;AACjF,eAAW,QAAQ,UAAU;AAC3B,UAAI,KAAK,IAAI,IAAI,EAAG;AACpB,UAAI;AACF,cAAM,OAAO,KAAK,KAAK,KAAK,KAAK,IAAI,CAAC;AAAA,MACxC,SAAS,KAAK;AACZ,YAAI,EAAE,eAAe,UAAU,CAAC,UAAU,UAAU,GAAG,CAAC,EAAG,OAAM;AAAA,MACnE;AAAA,IACF;AAAA,EACF;AACF;;;AD7MA,IAAM,sBAAsB;AAC5B,IAAM,yBAAyB;AAE/B,SAAS,aAA+B;AACtC,SAAO,EAAE,OAAO,CAAC,GAAG,kBAAkB,MAAM,iBAAiB,EAAE;AACjE;AAIA,SAAS,WAAc,UAAe,UAAwB,SAAuC;AACnG,QAAM,SAAS,CAAC,GAAG,QAAQ;AAC3B,aAAW,QAAQ,UAAU;AAC3B,QAAI,OAAO,KAAK,CAAC,SAAS,QAAQ,MAAM,IAAI,CAAC,EAAG;AAChD,WAAO,KAAK,IAAI;AAAA,EAClB;AACA,SAAO;AACT;AAEA,SAAS,aAAa,OAAuB,UAA4C;AACvF,QAAM,QAAQ,WAAW;AAIzB,QAAM,OAAO,CAAC,GAAI,MAAM,WAAW,CAAC,GAAI,GAAI,MAAM,mBAAmB,CAAC,CAAE;AACxE,QAAM,QAAQ;AAAA,IACZ,oBAAoB,MAAM,MAAM,OAAO,IAAI;AAAA,IAC3C,oBAAoB,SAAS,OAAO,IAAI;AAAA,IACxC;AAAA,EACF;AACA,QAAM,WAAW;AAAA,IACf,GAAG,oBAAoB,MAAM,MAAM,iBAAiB,CAAC,GAAG,IAAI;AAAA,IAC5D,GAAG,oBAAoB,SAAS,iBAAiB,CAAC,GAAG,IAAI;AAAA,EAC3D;AAEA,QAAM,kBAAkB,WAAW,CAAC,GAAsB,UAAU,gBAAgB;AACpF,MAAI,gBAAgB,SAAS,EAAG,OAAM,gBAAgB;AACtD,QAAM,SAAS,CAAC,MAAM,MAAM,kBAAkB,SAAS,gBAAgB,EAAE;AAAA,IACvE,CAAC,UAA2B,UAAU,QAAQ,UAAU;AAAA,EAC1D;AACA,QAAM,mBAAmB,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK;AACjD,QAAM,kBAAkB,KAAK,IAAI,MAAM,MAAM,iBAAiB,SAAS,eAAe;AACtF,SAAO,EAAE,GAAG,OAAO,MAAM;AAC3B;AAMA,SAAS,aAAa,OAA4B;AAChD,MAAI,MAAM,SAAS,SAAS,EAAG,QAAO;AACtC,MAAI,CAAC,OAAO,UAAU,MAAM,GAAG,KAAK,MAAM,OAAO,EAAG,QAAO;AAC3D,MAAI;AACF,YAAQ,KAAK,MAAM,KAAK,CAAC;AACzB,WAAO;AAAA,EACT,SAAS,KAAK;AAGZ,WAAQ,KAA+B,SAAS;AAAA,EAClD;AACF;AAEA,SAAS,eAAe,OAAmB,MAAM,KAAK,IAAI,GAAY;AACpE,QAAM,YAAY,KAAK,MAAM,MAAM,WAAW;AAC9C,SAAO,CAAC,OAAO,SAAS,SAAS,KAAK,MAAM,YAAY,MAAM;AAChE;AAEA,IAAM,kBAAkB,EAAE,OAAO;AAAA,EAC/B,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACrB,OAAO,EAAE,OAAO;AAAA,IACd,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC;AAAA,IAC1B,eAAe,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS;AAAA,IAC7C,kBAAkB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACjD,iBAAiB,EAAE,OAAO,EAAE,SAAS;AAAA,EACvC,CAAC,EAAE,SAAS;AAAA,EACZ,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS;AAAA,EACrC,eAAe,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS;AAAA,EAC7C,kBAAkB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EACjD,iBAAiB,EAAE,OAAO,EAAE,SAAS;AAAA,EACrC,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS;AAAA,EACvC,iBAAiB,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS;AACjD,CAAC;AAED,SAAS,UAAU,KAAoC;AACrD,MAAI;AACJ,MAAI;AACF,WAAO,gBAAgB,MAAM,KAAK,MAAM,GAAG,CAAC;AAAA,EAC9C,QAAQ;AACN,WAAO;AAAA,EACT;AAIA,QAAM,SAAS,KAAK,SAAS,MAAM,QAAQ,KAAK,MAAM,KAAK,IAAI,KAAK,QAAQ;AAC5E,MAAI,CAAC,MAAM,QAAQ,OAAO,KAAK,EAAG,QAAO;AACzC,QAAM,QAAQ,WAAW;AAOzB,QAAM,QAAQ,OAAO;AACrB,MAAI,OAAO,eAAe;AAExB,UAAM,gBAAgB,OAAO;AAAA,EAC/B;AACA,QAAM,mBAAmB,OAAO,oBAAoB;AACpD,QAAM,kBAAkB,OAAO,SAAS,OAAO,eAAe,IAAI,OAAO,OAAO,eAAe,IAAI;AACnG,QAAM,QAAwB;AAAA,IAC5B,KAAK,KAAK;AAAA,IACV;AAAA,IACA,WAAW,KAAK,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,EACtD;AACA,MAAI,MAAM,QAAQ,KAAK,OAAO,GAAG;AAE/B,UAAM,UAAU,KAAK;AAAA,EACvB;AACA,MAAI,MAAM,QAAQ,KAAK,eAAe,GAAG;AAEvC,UAAM,kBAAkB,KAAK;AAAA,EAC/B;AACA,SAAO;AACT;AAEA,IAAM,cAAc,EAAE,OAAO;AAAA,EAC3B,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACzB,KAAK,EAAE,OAAO,EAAE,SAAS;AAAA,EACzB,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,EAC1B,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,EAChC,aAAa,EAAE,OAAO;AAAA,EACtB,SAAS,EAAE,OAAO;AACpB,CAAC;AAED,SAAS,WAAW,KAAgC;AAClD,MAAI;AACJ,MAAI;AACF,YAAQ,YAAY,MAAM,KAAK,MAAM,GAAG,CAAC;AAAA,EAC3C,QAAQ;AACN,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,SAAS,MAAM;AAAA,IACf,KAAK,MAAM,OAAO;AAAA,IAClB,MAAM,MAAM,QAAQ;AAAA,IACpB,YAAY,MAAM,cAAc,MAAM;AAAA,IACtC,aAAa,MAAM;AAAA,IACnB,SAAS,MAAM;AAAA,EACjB;AACF;AAIA,IAAM,wBAAwB;AAEvB,IAAM,oBAAN,MAAwB;AAAA,EAC7B,YACmB,UACA,IACjB;AAFiB;AACA;AAAA,EAChB;AAAA,EAFgB;AAAA,EACA;AAAA,EAGnB,IAAY,MAAc;AACxB,WAAOC,MAAK,KAAK,KAAK,UAAU,UAAU,OAAO;AAAA,EACnD;AAAA,EAEQ,QAAQ,KAAqB;AACnC,WAAOA,MAAK,KAAK,KAAK,KAAK,sBAAsB,GAAG,CAAC;AAAA,EACvD;AAAA,EAEQ,aAAa,KAAqB;AACxC,WAAO,GAAG,KAAK,QAAQ,GAAG,CAAC;AAAA,EAC7B;AAAA,EAEQ,YAAY,KAAqB;AACvC,WAAO,GAAG,KAAK,QAAQ,GAAG,CAAC;AAAA,EAC7B;AAAA;AAAA,EAGA,UAAU,KAAsB;AAC9B,WAAO,WAAW,KAAK,QAAQ,GAAG,CAAC;AAAA,EACrC;AAAA;AAAA,EAGA,iBAAiB,KAAqB;AACpC,WAAO,KAAK,aAAa,GAAG;AAAA,EAC9B;AAAA,EAEA,MAAc,cAAc,KAA6C;AACvE,QAAI;AACF,aAAO,UAAU,MAAM,KAAK,GAAG,KAAK,KAAK,QAAQ,GAAG,CAAC,CAAC;AAAA,IACxD,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA,EAGA,KAAK,KAA6C;AAChD,WAAO,KAAK,cAAc,GAAG;AAAA,EAC/B;AAAA,EAEA,MAAc,eAAe,KAAa,OAAsC;AAC9E,UAAM,MAAM,KAAK,KAAK,EAAE,WAAW,KAAK,CAAC;AACzC,UAAM,KAAK,GAAG,MAAM,KAAK,QAAQ,GAAG,GAAG,KAAK,UAAU,EAAE,GAAG,OAAO,IAAI,CAAC,CAAC;AAAA,EAC1E;AAAA,EAEA,MAAc,UAAU,KAAyC;AAC/D,QAAI;AACF,aAAO,WAAW,MAAM,KAAK,GAAG,KAAK,KAAK,aAAa,GAAG,CAAC,CAAC;AAAA,IAC9D,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAc,WAAW,KAAa,OAAkC;AACtE,UAAM,MAAM,KAAK,KAAK,EAAE,WAAW,KAAK,CAAC;AACzC,UAAM,KAAK,GAAG,MAAM,KAAK,aAAa,GAAG,GAAG,KAAK,UAAU,KAAK,CAAC;AAAA,EACnE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,qBAAqB,KAAa,OAAqD;AAC3F,UAAM,MAAM,KAAK,KAAK,EAAE,WAAW,KAAK,CAAC;AACzC,UAAM,cAAcA,MAAK,KAAK,KAAK,UAAU,UAAU,sBAAsB,GAAG,CAAC;AACjF,QAAI,WAAW,WAAW,GAAG;AAI3B,UAAI;AACF,cAAM,SAAS,UAAU,MAAM,KAAK,GAAG,KAAK,WAAW,CAAC;AACxD,YAAI,UAAU,OAAO,MAAM,MAAM,WAAW,MAAM,OAAO,MAAM,eAAe,UAAU,OAAO,GAAG;AAChG,iBAAO;AAAA,QACT;AAAA,MACF,QAAQ;AAAA,MAGR;AAAA,IACF;AACA,WAAO,MAAM;AAAA,MACX,KAAK,YAAY,GAAG;AAAA,MACpB,EAAE,SAAS,qBAAqB,WAAW,uBAAuB;AAAA,MAClE,OAAO,aAAa;AAClB,YAAI,CAAC,SAAU,QAAO;AAItB,YAAI,KAAK,UAAU,GAAG,EAAG,QAAO;AAChC,YAAI;AACF,gBAAM,OAAO,aAAa,KAAK,QAAQ,GAAG,CAAC;AAAA,QAC7C,SAAS,KAAK;AAGZ,cAAI,CAAC,WAAW,WAAW,GAAG;AAC5B,gBAAI,KAAK,UAAU,GAAG,EAAG,QAAO;AAChC,mBAAO;AAAA,UACT;AACA,gBAAM;AAAA,QACR;AACA,cAAM,QAAQ,MAAM,KAAK,cAAc,GAAG;AAC1C,YAAI,CAAC,SAAU,MAAM,MAAM,MAAM,WAAW,MAAM,MAAM,MAAM,eAAe,UAAU,OAAO,GAAI;AAGhG,gBAAMC,QAAO,KAAK,QAAQ,GAAG,CAAC,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAC9C,iBAAO;AAAA,QACT;AACA,cAAM,KAAK,WAAW,KAAK,KAAK;AAChC,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,MAAM,KAAa,OAA0C;AACjE,QAAI,CAAC,KAAK,UAAU,GAAG,EAAG,QAAO;AACjC,WAAO,MAAM;AAAA,MACX,KAAK,YAAY,GAAG;AAAA,MACpB,EAAE,SAAS,qBAAqB,WAAW,uBAAuB;AAAA,MAClE,OAAO,aAAa;AAClB,YAAI,CAAC,SAAU,QAAO;AACtB,cAAM,WAAW,MAAM,KAAK,UAAU,GAAG;AACzC,YAAI,YAAY,CAAC,eAAe,QAAQ,GAAG;AACzC,cAAI,SAAS,YAAY,MAAM,SAAS;AAEtC,kBAAM,KAAK,WAAW,KAAK,EAAE,GAAG,UAAU,cAAa,oBAAI,KAAK,GAAE,YAAY,GAAG,SAAS,MAAM,QAAQ,CAAC;AACzG,mBAAO;AAAA,UACT;AACA,cAAI,aAAa,QAAQ,EAAG,QAAO;AAGnC,gBAAMA,QAAO,KAAK,aAAa,GAAG,CAAC,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QACrD,WAAW,UAAU;AAInB,gBAAMA,QAAO,KAAK,aAAa,GAAG,CAAC,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QACrD;AACA,YAAI;AACF,gBAAM,UAAU,KAAK,aAAa,GAAG,GAAG,KAAK,UAAU,KAAK,GAAG,EAAE,MAAM,KAAK,CAAC;AAAA,QAC/E,SAAS,KAAK;AAEZ,cAAI,WAAW,KAAK,aAAa,GAAG,CAAC,EAAG,QAAO;AAC/C,gBAAM;AAAA,QACR;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,eAAe,KAAa,UAA2C;AAC3E,UAAM,iBAAiB,KAAK,YAAY,GAAG,GAAG,EAAE,SAAS,qBAAqB,WAAW,uBAAuB,GAAG,OAAO,aAAa;AACrI,UAAI,CAAC,UAAU;AAIb,cAAM,IAAI,MAAM,0BAA0B,GAAG,mCAAmC;AAAA,MAClF;AACA,YAAM,QAAQ,MAAM,KAAK,cAAc,GAAG;AAC1C,UAAI,CAAC,OAAO;AAGV;AAAA,MACF;AACA,YAAM,KAAK,eAAe,KAAK,aAAa,OAAO,QAAQ,CAAC;AAAA,IAC9D,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAM,WAAW,KAAa,SAAiB,OAAyB,MAAwC;AAC9G,UAAM,iBAAiB,KAAK,YAAY,GAAG,GAAG,EAAE,SAAS,qBAAqB,WAAW,uBAAuB,GAAG,OAAO,aAAa;AACrI,UAAI,CAAC,SAAU,OAAM,IAAI,MAAM,0BAA0B,GAAG,kCAAkC;AAC9F,YAAM,QAAQ,MAAM,KAAK,UAAU,GAAG;AACtC,UAAI,CAAC,SAAS,MAAM,YAAY,SAAS;AACvC,cAAM,IAAI,MAAM,0BAA0B,GAAG,0BAA0B,OAAO,0BAA0B;AAAA,MAC1G;AACA,YAAM,QAAQ,MAAM,KAAK,cAAc,GAAG;AAC1C,UAAI,CAAC,MAAO;AACZ,YAAM,YAAY,MAAM,SAAS,MAAM,MAAM;AAC7C,YAAM,eAAe,MAAM,iBAAiB,MAAM,MAAM,iBAAiB,CAAC;AAC1E,YAAM,gBAAgB,MAAM,iBAAiB,CAAC;AAE9C,YAAM,OAAO,CAAC,SACZ,MAAM,MAAM,KAAK,CAAC,SAAS,iBAAiB,MAAM,IAAI,CAAC,KAAK,cAAc,KAAK,CAAC,SAAS,iBAAiB,MAAM,IAAI,CAAC;AAEvH,YAAM,UAAU,CAAC,GAAI,MAAM,WAAW,CAAC,CAAE;AACzC,iBAAW,QAAQ,MAAM,MAAM,OAAO;AACpC,cAAM,YACJ,UAAU,KAAK,CAAC,aAAa,iBAAiB,UAAU,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI;AAC9E,YAAI,CAAC,UAAW;AAChB,YAAI,QAAQ,KAAK,CAAC,SAAS,iBAAiB,MAAM,IAAI,CAAC,EAAG;AAC1D,gBAAQ,KAAK,IAAI;AAAA,MACnB;AACA,YAAM,kBAAkB,CAAC,GAAI,MAAM,mBAAmB,CAAC,CAAE;AACzD,iBAAW,QAAQ,MAAM,MAAM,iBAAiB,CAAC,GAAG;AAClD,cAAM,YACJ,aAAa,KAAK,CAAC,aAAa,iBAAiB,UAAU,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI;AACjF,YAAI,CAAC,UAAW;AAChB,YAAI,QAAQ,KAAK,CAAC,SAAS,iBAAiB,MAAM,IAAI,CAAC,KAAK,gBAAgB,KAAK,CAAC,SAAS,iBAAiB,MAAM,IAAI,CAAC,EAAG;AAC1H,wBAAgB,KAAK,IAAI;AAAA,MAC3B;AAGA,YAAM,OAAO,MAAM,MAAM,MAAM;AAAA,QAC7B,CAAC,SACC,CAAC,UAAU,KAAK,CAAC,aAAa,iBAAiB,UAAU,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI;AAAA,MACjF;AACA,YAAM,OAAyB,EAAE,GAAG,OAAO,OAAO,CAAC,GAAG,MAAM,OAAO,GAAG,IAAI,EAAE;AAC5E,YAAM,gBAAgB,MAAM,MAAM,iBAAiB,CAAC,GAAG;AAAA,QACrD,CAAC,SACC,CAAC,aAAa,KAAK,CAAC,aAAa,iBAAiB,UAAU,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI;AAAA,MACpF;AACA,UAAI,aAAa,SAAS,EAAG,MAAK,gBAAgB,WAAW,eAAe,cAAc,gBAAgB;AAC1G,YAAM,KAAK,eAAe,KAAK,EAAE,GAAG,OAAO,OAAO,MAAM,SAAS,gBAAgB,CAAC;AAAA,IACpF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,UAAU,KAAa,SAAiB,SAAyC;AACrF,WAAO,MAAM,iBAAiB,KAAK,YAAY,GAAG,GAAG,EAAE,SAAS,qBAAqB,WAAW,uBAAuB,GAAG,OAAO,aAAa;AAC5I,UAAI,CAAC,SAAU,QAAO;AACtB,YAAM,QAAQ,MAAM,KAAK,UAAU,GAAG;AACtC,UAAI,CAAC,SAAS,MAAM,YAAY,QAAS,QAAO;AAChD,YAAM,KAAK,WAAW,KAAK,EAAE,GAAG,OAAO,cAAa,oBAAI,KAAK,GAAE,YAAY,GAAG,QAAQ,CAAC;AACvF,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,aAAa,KAAa,SAAgC;AAC9D,UAAM,QAAQ,MAAM,KAAK,UAAU,GAAG;AACtC,QAAI,CAAC,SAAS,MAAM,YAAY,QAAS;AACzC,UAAMA,QAAO,KAAK,aAAa,GAAG,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,IAAI,KAAa,SAAsC;AAC3D,UAAM,QAAQ,MAAM,KAAK,UAAU,GAAG;AACtC,QAAI,CAAC,SAAS,MAAM,YAAY,QAAS,QAAO;AAChD,UAAM,QAAQ,MAAM,KAAK,cAAc,GAAG;AAC1C,QAAI,CAAC,OAAO;AACV,YAAMA,QAAO,KAAK,aAAa,GAAG,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AACnD,aAAO;AAAA,IACT;AACA,QAAI,MAAM,MAAM,MAAM,SAAS,MAAM,MAAM,MAAM,eAAe,UAAU,KAAK,EAAG,QAAO;AAIzF,UAAMA,QAAO,KAAK,aAAa,GAAG,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AACnD,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAM,aAAa,KAAoC;AACrD,UAAM,QAAQ,MAAM,KAAK,cAAc,GAAG;AAC1C,WAAO,OAAO,WAAW,CAAC;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,UAAU,KAA+B;AAC7C,UAAM,QAAQ,MAAM,KAAK,cAAc,GAAG;AAC1C,QAAI,CAAC,MAAO,QAAO;AACnB,SAAK,MAAM,SAAS,UAAU,OAAO,MAAM,MAAM,iBAAiB,UAAU,OAAO,EAAG,QAAO;AAC7F,QAAI,MAAM,MAAM,MAAM,SAAS,MAAM,MAAM,MAAM,eAAe,UAAU,KAAK,EAAG,QAAO;AACzF,WAAQ,MAAM,KAAK,UAAU,GAAG,MAAO;AAAA,EACzC;AAAA;AAAA,EAGA,MAAM,QAAQ,KAA4B;AACxC,UAAMA,QAAO,KAAK,QAAQ,GAAG,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC9C,UAAMA,QAAO,KAAK,aAAa,GAAG,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACrD;AAAA;AAAA,EAGA,MAAM,OAAqC;AACzC,QAAI;AACJ,QAAI;AACF,eAAS,MAAMC,SAAQ,KAAK,GAAG,GAAG,OAAO,CAAC,SAAS,KAAK,SAAS,OAAO,KAAK,CAAC,KAAK,SAAS,aAAa,CAAC;AAAA,IAC5G,SAAS,KAAK;AAEZ,UAAK,KAA+B,SAAS,SAAU,QAAO,CAAC;AAC/D,YAAM;AAAA,IACR;AACA,UAAM,MAA2B,CAAC;AAClC,eAAW,QAAQ,OAAO;AACxB,YAAM,QAAQ,MAAM,KAAK,mBAAmB,IAAI;AAChD,UAAI,CAAC,MAAO;AACZ,YAAM,QAAQ,MAAM,KAAK,UAAU,MAAM,GAAG;AAC5C,UAAI,KAAK;AAAA,QACP,KAAK,MAAM;AAAA,QACX,WAAW,MAAM,MAAM,MAAM,UAAU,MAAM,MAAM,eAAe,UAAU;AAAA,QAC5E,QAAQ,UAAU;AAAA,QAClB,cAAc,UAAU,OAAO,eAAe,KAAK,IAAI;AAAA,MACzD,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,SAAS,WAA+B,OAA+E;AAC3H,QAAI;AACJ,QAAI;AACF,eAAS,MAAMA,SAAQ,KAAK,GAAG,GAAG,OAAO,CAAC,SAAS,KAAK,SAAS,OAAO,KAAK,CAAC,KAAK,SAAS,aAAa,CAAC,EAAE,KAAK;AAAA,IACnH,SAAS,KAAK;AAGZ,YAAM,QAAS,KAA2C;AAC1D,UAAI,UAAU,SAAU,QAAO,EAAE,SAAS,CAAC,EAAE;AAC7C,YAAM;AAAA,IACR;AACA,UAAM,QAAQ,cAAc,SAAY,IAAI,MAAM,UAAU,CAAC,SAAS,OAAO,SAAS;AACtF,UAAM,MAA2B,CAAC;AAKlC,QAAI;AACJ,UAAM,aAAa,KAAK,IAAI,OAAO,CAAC,IAAI;AACxC,QAAI,UAAU;AACd,aAAS,QAAQ,QAAQ,IAAI,MAAM,SAAS,OAAO,QAAQ,MAAM,QAAQ,SAAS,GAAG;AACnF,UAAI,IAAI,UAAU,SAAS,WAAW,WAAY;AAClD,YAAM,OAAO,MAAM,KAAK;AACxB,iBAAW;AACX,eAAS;AACT,YAAM,QAAQ,MAAM,KAAK,mBAAmB,IAAI;AAChD,UAAI,CAAC,MAAO;AACZ,YAAM,YAAY,MAAM,MAAM,MAAM,UAAU,MAAM,MAAM,eAAe,UAAU;AACnF,UAAI,aAAa,EAAG;AACpB,YAAM,QAAQ,MAAM,KAAK,UAAU,MAAM,GAAG;AAC5C,UAAI,KAAK;AAAA,QACP,KAAK,MAAM;AAAA,QACX;AAAA,QACA,QAAQ,UAAU;AAAA,QAClB,cAAc,UAAU,OAAO,eAAe,KAAK,IAAI;AAAA,MACzD,CAAC;AAAA,IACH;AACA,WAAO,EAAE,SAAS,KAAK,YAAY,OAAO;AAAA,EAC5C;AAAA,EAEA,MAAc,mBAAmB,MAA8C;AAC7E,QAAI;AACF,aAAO,UAAU,MAAM,KAAK,GAAG,KAAKF,MAAK,KAAK,KAAK,KAAK,IAAI,CAAC,CAAC;AAAA,IAChE,SAAS,KAAK;AACZ,UAAI,MAAM,gDAAgD,IAAI,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AACrH,aAAO;AAAA,IACT;AAAA,EACF;AACF;","names":["readdir","unlink","path","path","unlink","readdir"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/fallback-llm-codex-subscription.ts","../src/providers/codex-subscription.ts"],"sourcesContent":["/**\n * Helpers extracted from FallbackLlmClient so fallback-llm.ts stays under\n * the 1200-line cap: Codex-subscription lifecycle (issue #2833) plus\n * generic chain helpers used by the same client.\n */\n\nimport path from \"node:path\";\nimport { raceAbort } from \"./abort-error.js\";\nimport { callCodexCliFallback, isCodexCliFallbackRunnerRegistered } from \"./cli-fallback.js\";\nimport type { CodexCliFallbackRunner } from \"./cli-fallback.js\";\nimport {\n CODEX_SUBSCRIPTION_PROVIDER_ID,\n CodexSubscriptionAuthError,\n CodexSubscriptionConfigError,\n CodexSubscriptionTimeoutError,\n assertNoApiKeyConfig,\n ensureCodexSubscriptionRunnerRegistered,\n getCodexSubscriptionRunnerForOwner,\n getCodexSubscriptionShutdownSignal,\n isDefaultRegisteredCodexSubscriptionRunner,\n} from \"./providers/codex-subscription.js\";\nimport type { GetRuntimeAuthForModelFn, ResolveApiKeyFn } from \"./resolve-provider-secret.js\";\nimport { resolveHomeDir } from \"./runtime/env.js\";\nimport type { GatewayConfig, ModelProviderConfig } from \"./types.js\";\nimport { expandTildePath } from \"./utils/path.js\";\n\ntype CodexSubscriptionAttemptResult = {\n content: string;\n usage?: {\n inputTokens?: number;\n outputTokens?: number;\n totalTokens?: number;\n };\n};\n\n/**\n * Terminal codex-subscription failures (timeout, auth, config contract).\n * These carry documented caller guidance (`TimeoutError` name, `codex login`\n * steps), so they must survive the chain instead of collapsing into a\n * generic empty/http_error result (issue #2833).\n */\nexport function isTerminalCodexSubscriptionError(error: unknown): boolean {\n return (\n error instanceof CodexSubscriptionTimeoutError ||\n error instanceof CodexSubscriptionAuthError ||\n error instanceof CodexSubscriptionConfigError\n );\n}\n\n/**\n * Single bounded headroom shared by both sides of the Codex deadline\n * (issue #2890): the provider gets it as SIGTERM lead time\n * (`timeoutMs - headroom`) and the outer race as settle grace, so the hard\n * outer bound stays `timeoutMs + headroom` (<= 25 ms past the deadline).\n */\nexport function codexDeadlineHeadroomMs(timeoutMs: number): number {\n return Math.min(25, Math.max(1, Math.floor(timeoutMs / 10)));\n}\n\n/**\n * Settle a fallback-LLM chain at the caller deadline. Abort the in-flight\n * work, then wait a bounded settle grace for the chain to surface a typed\n * provider timeout (default: one event-loop turn). A chain that settles\n * within the grace wins with its own result or error; one that never\n * settles still cannot exceed `timeoutMs + settleGraceMs`. The abandoned\n * promise is observed so a late rejection cannot become unhandled.\n */\nexport async function raceFallbackLlmDeadline<T>(\n work: Promise<T>,\n timeoutMs: number,\n abortOnTimeout: () => void,\n settleGraceMs = 0,\n): Promise<{ timedOut: true } | { timedOut: false; value: T }> {\n const deadline = new AbortController();\n const timer = setTimeout(() => deadline.abort(), timeoutMs);\n deadline.signal.addEventListener(\"abort\", abortOnTimeout, { once: true });\n try {\n const value = await raceAbort(\n work,\n deadline.signal,\n `fallback LLM timed out after ${timeoutMs}ms`,\n );\n return { timedOut: false, value };\n } catch (error) {\n if (!deadline.signal.aborted) throw error;\n let graceTimer: NodeJS.Timeout | undefined;\n const afterAbort = await Promise.race([\n work.then(\n (value) => ({ state: \"value\" as const, value }),\n (lateError) => ({ state: \"error\" as const, error: lateError }),\n ),\n new Promise<{ state: \"pending\" }>((resolve) => {\n if (settleGraceMs > 0) {\n graceTimer = setTimeout(() => resolve({ state: \"pending\" }), settleGraceMs);\n } else {\n setImmediate(() => resolve({ state: \"pending\" }));\n }\n }),\n ]);\n clearTimeout(graceTimer);\n if (afterAbort.state === \"value\") return { timedOut: false, value: afterAbort.value };\n if (afterAbort.state === \"error\") throw afterAbort.error;\n void work.then(\n () => undefined,\n () => undefined,\n );\n return { timedOut: true };\n } finally {\n clearTimeout(timer);\n deadline.signal.removeEventListener(\"abort\", abortOnTimeout);\n }\n}\n\n/**\n * Attempt the Codex CLI / subscription provider. Returns `undefined` when the\n * model is not `api: \"codex-cli\"` so the generic FallbackLlmClient path runs.\n *\n * Subscription ids reject a configured apiKey before generic secret resolution\n * — otherwise an unresolvable ref throws a generic error and the provider's\n * `codex login` guidance never surfaces (issue #2833). Other `codex-cli`\n * providers still resolve a configured key. Runtime auth is skipped: the CLI\n * login is the credential.\n */\nexport async function tryCodexSubscriptionProvider(\n model: {\n providerId: string;\n modelId: string;\n providerConfig: ModelProviderConfig;\n },\n messages: Array<{ role: \"system\" | \"user\" | \"assistant\"; content: string }>,\n options: { timeoutMs?: number; signal?: AbortSignal },\n resolveFallbackApiKey: () => Promise<string | undefined>,\n runner?: CodexCliFallbackRunner,\n): Promise<CodexSubscriptionAttemptResult | undefined> {\n if (model.providerConfig.api !== \"codex-cli\") return undefined;\n\n if (model.providerId === CODEX_SUBSCRIPTION_PROVIDER_ID) {\n assertNoApiKeyConfig(model.providerConfig);\n }\n\n const resolvedApiKey =\n model.providerConfig.apiKey === undefined ? undefined : await resolveFallbackApiKey();\n const rawKey = model.providerConfig.apiKey;\n const needsResolution =\n rawKey === \"secretref-managed\" || (typeof rawKey === \"object\" && rawKey !== null);\n if (needsResolution && !resolvedApiKey) {\n throw new Error(`API key for provider \"${model.providerId}\" could not be resolved from secret ref`);\n }\n\n const effectiveConfig: ModelProviderConfig = {\n ...model.providerConfig,\n ...(resolvedApiKey ? { apiKey: resolvedApiKey } : {}),\n };\n\n // Let the provider surface its typed timeout before the generic outer\n // deadline: the shared bounded headroom is SIGTERM lead time here and\n // settle grace in raceFallbackLlmDeadline (issue #2890).\n const callOptions = {\n timeoutMs: options.timeoutMs === undefined\n ? undefined\n : Math.max(1, options.timeoutMs - codexDeadlineHeadroomMs(options.timeoutMs)),\n signal: options.signal,\n };\n // A host/benchmark runner on the process seam still wins. The core\n // default runner does not: prefer the owning runtime so shutdown\n // terminates the request that runtime started.\n if (isCodexCliFallbackRunnerRegistered() && !(runner && isDefaultRegisteredCodexSubscriptionRunner())) {\n return await callCodexCliFallback(effectiveConfig, model.modelId, messages, callOptions);\n }\n if (runner) {\n return await callCodexCliFallback(effectiveConfig, model.modelId, messages, callOptions, runner);\n }\n ensureCodexSubscriptionRunnerRegistered();\n return await callCodexCliFallback(effectiveConfig, model.modelId, messages, callOptions);\n}\n\nexport function abortReason(signal: AbortSignal | undefined): Error {\n const reason = signal?.reason;\n return reason instanceof Error ? reason : new Error(\"task LLM request aborted\");\n}\n\nexport function withCodexRuntimeShutdown<T extends { signal?: AbortSignal }>(\n options: T,\n runner: CodexCliFallbackRunner | undefined,\n): T {\n if (!runner) return options;\n const shutdown = getCodexSubscriptionShutdownSignal(runner);\n if (!shutdown) return options;\n return {\n ...options,\n signal: options.signal ? AbortSignal.any([options.signal, shutdown]) : shutdown,\n };\n}\n\nexport function isUnsupportedJsonSchemaError(error: unknown): boolean {\n const message = error instanceof Error ? error.message : String(error);\n // An echoed request format is not a capability rejection.\n if (!/\\b(?:400|404|422)\\b/.test(message)) {\n return false;\n }\n return (\n /(?:response[_ ]?format|json[_ ]?schema|structured[_ ]?output)\\b[^;,\\n]{0,60}\\b(?:unsupported|not supported|not implemented|not available)\\b/i.test(message) ||\n /\\b(?:unsupported|unknown|unrecognized)(?:[\\s:\"'=]|parameter|argument|request|supplied|field|type|value){1,40}\\b(?:response[_ ]?format|json[_ ]?schema|structured[_ ]?output|schema|format)\\b/i.test(message) ||\n /\\b(?:does not|doesn't|cannot|can't) support(?:[\\s:\"'=]|the|native|type){1,20}\\b(?:response[_ ]?format|json[_ ]?schema|structured[_ ]?output|schema|format)\\b/i.test(message)\n );\n}\n\nexport function extractResponsesOutputText(data: {\n output_text?: string;\n output?: Array<{\n type?: string;\n text?: string;\n content?: Array<{\n type?: string;\n text?: string;\n }>;\n }>;\n}): string | null {\n if (typeof data.output_text === \"string\" && data.output_text.trim().length > 0) {\n return data.output_text;\n }\n\n const chunks: string[] = [];\n for (const item of data.output ?? []) {\n if (typeof item.text === \"string\" && item.text.trim().length > 0) {\n chunks.push(item.text);\n }\n for (const part of item.content ?? []) {\n if (\n (part.type === \"output_text\" || part.type === \"text\") &&\n typeof part.text === \"string\" &&\n part.text.trim().length > 0\n ) {\n chunks.push(part.text);\n }\n }\n }\n\n const joined = chunks.join(\"\\n\").trim();\n return joined.length > 0 ? joined : null;\n}\n\nexport type StructuredParseFailureReason =\n | \"no_models\"\n | \"empty\"\n | \"http_error\"\n | \"schema_rejection\"\n | \"timeout\";\n\nexport interface StructuredParseFailure {\n result: null;\n failureReason: StructuredParseFailureReason;\n /** Model string of the last attempt, when one was selected. Not `modelUsed`. */\n attemptedModel?: string;\n /** HTTP status when known. Never a response body. */\n httpStatus?: number;\n /** Coarse class: http_4xx / http_5xx / timeout / network / empty / schema_rejection / no_models. */\n errorClass?: string;\n}\n\nexport type StructuredParseResult<T> =\n | { result: T; modelUsed: string }\n | StructuredParseFailure;\n\nexport interface FallbackLlmRuntimeContext {\n agentDir?: string;\n getRuntimeAuthForModel?: GetRuntimeAuthForModelFn | null;\n resolveApiKeyForProvider?: ResolveApiKeyFn | null;\n workspaceDir?: string;\n /** Per-runtime Codex child owner. Shutdown must terminate only this runner. */\n codexSubscriptionRunner?: CodexCliFallbackRunner;\n}\n\ntype GatewayBackedRuntimeConfig = {\n providerApiKeyResolver?: ResolveApiKeyFn | null;\n runtimeAuthForModelResolver?: GetRuntimeAuthForModelFn | null;\n workspaceDir?: string;\n};\n\nexport function fallbackLlmRuntimeContextFromConfig(\n config: Pick<\n GatewayBackedRuntimeConfig,\n \"providerApiKeyResolver\" | \"runtimeAuthForModelResolver\" | \"workspaceDir\"\n >,\n overrides: FallbackLlmRuntimeContext = {},\n): FallbackLlmRuntimeContext {\n return {\n workspaceDir: config.workspaceDir,\n resolveApiKeyForProvider: config.providerApiKeyResolver,\n getRuntimeAuthForModel: config.runtimeAuthForModelResolver,\n codexSubscriptionRunner: getCodexSubscriptionRunnerForOwner(config),\n ...overrides,\n };\n}\n\nconst HTTP_STATUS_IN_MESSAGE = /\\b([1-5]\\d{2})\\b/;\n\nexport function isTimeoutError(err: unknown): boolean {\n const msg = err instanceof Error ? err.message : String(err);\n return /timed out after \\d+ms/i.test(msg) || /timed out before request started/i.test(msg);\n}\n\nfunction isEmptyProviderResponse(err: unknown): boolean {\n const msg = err instanceof Error ? err.message : String(err);\n return /^Empty response from /i.test(msg);\n}\n\nfunction finiteHttpStatus(value: unknown): number | undefined {\n if (typeof value !== \"number\" || !Number.isFinite(value) || !Number.isInteger(value)) return undefined;\n if (value < 100 || value > 599) return undefined;\n return value;\n}\n\nfunction httpStatusFromProviderError(err: unknown): number | undefined {\n if (err && typeof err === \"object\") {\n if (\"status\" in err) {\n const status = finiteHttpStatus(err.status);\n if (status !== undefined) return status;\n }\n if (\"statusCode\" in err) {\n const status = finiteHttpStatus(err.statusCode);\n if (status !== undefined) return status;\n }\n }\n const msg = err instanceof Error ? err.message : String(err);\n const match = HTTP_STATUS_IN_MESSAGE.exec(msg);\n if (!match) return undefined;\n return finiteHttpStatus(Number(match[1]));\n}\n\nfunction errorClassForHttpStatus(status: number): string {\n if (status >= 500) return \"http_5xx\";\n if (status >= 400) return \"http_4xx\";\n return `http_${status}`;\n}\n\nexport function classifyThrownProviderError(err: unknown): Omit<StructuredParseFailure, \"result\"> {\n if (isTimeoutError(err)) {\n return { failureReason: \"timeout\", errorClass: \"timeout\" };\n }\n if (isEmptyProviderResponse(err)) {\n return { failureReason: \"empty\", errorClass: \"empty\" };\n }\n const httpStatus = httpStatusFromProviderError(err);\n if (httpStatus !== undefined) {\n return {\n failureReason: \"http_error\",\n httpStatus,\n errorClass: errorClassForHttpStatus(httpStatus),\n };\n }\n return { failureReason: \"http_error\", errorClass: \"network\" };\n}\n\nexport function normalizeRuntimePath(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const trimmed = value.trim();\n return trimmed.length > 0 ? expandTildePath(trimmed) : undefined;\n}\n\nexport function readGatewayWorkspaceDir(gatewayConfig: GatewayConfig | undefined): string | undefined {\n if (!gatewayConfig || typeof gatewayConfig !== \"object\") return undefined;\n const raw = gatewayConfig as Record<string, unknown>;\n return (\n normalizeRuntimePath(raw.workspaceDir) ??\n normalizeRuntimePath(raw.workspacePath) ??\n normalizeRuntimePath(raw.workspace)\n );\n}\n\nexport function defaultOpenClawWorkspaceDir(): string {\n return path.join(resolveHomeDir(), \".openclaw\", \"workspace\");\n}\n","/**\n * Codex subscription extraction provider (issue #2784).\n *\n * Routes extraction/consolidation LLM calls through the `codex` CLI so the\n * operator's Codex subscription (ChatGPT OAuth login) authenticates the\n * request — no OpenAI API key and no codex-openai-proxy. Credentials stay in\n * the CLI's own auth store; this module never reads, accepts, or logs tokens.\n *\n * Transport contract mirrors `packages/bench/src/providers/codex-cli.ts`\n * (`codex exec --json --output-last-message`, env allowlist, login-status\n * precheck) but lives in core so the standalone daemon and host plugins get\n * a working `api: \"codex-cli\"` transport without importing the optional\n * bench package. Registered through the existing\n * `setCodexCliFallbackRunnerForProcess` seam, and only when no host or\n * benchmark runner has claimed it first.\n */\n\nimport { mkdir, mkdtemp, readFile, rm, stat } from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nimport type {\n CodexCliFallbackConfig,\n CodexCliFallbackMessage,\n CodexCliFallbackRequest,\n CodexCliFallbackResult,\n CodexCliFallbackRunner,\n} from \"../cli-fallback.js\";\nimport { getCodexCliFallbackRunnerForProcess, isCodexCliFallbackRunnerRegistered, normalizeCodexCliTimeoutMs, setCodexCliFallbackRunnerForProcess } from \"../cli-fallback.js\";\nimport { log } from \"../logger.js\";\nimport { launchProcess } from \"../runtime/child-process.js\";\nimport type { CodexCliReasoningEffort, ModelProviderConfig } from \"../types.js\";\nimport { expandTildePath } from \"../utils/path.js\";\n\nexport const CODEX_SUBSCRIPTION_PROVIDER_ID = \"codex-subscription\";\n\n/** Env var overriding the codex executable (mirrors the bench provider). */\nconst CODEX_EXECUTABLE_ENV = \"REMNIC_CODEX_EXECUTABLE\";\n\nconst DEFAULT_REASONING_EFFORT: CodexCliReasoningEffort = \"medium\";\nconst DEFAULT_TIMEOUT_MS = 120_000;\nconst LOGIN_STATUS_TIMEOUT_MS = 10_000;\nconst LOGIN_STATUS_CACHE_TTL_MS = 10 * 60_000;\nconst STDIO_LIMIT = 64_000;\nconst OUTPUT_SUMMARY_LIMIT = 500;\n/**\n * Env keys whose value is a filesystem path. Relative values resolve against\n * the original process cwd before either child launches, because the exec\n * child runs with cwd = the ephemeral workspace (same rule as executables).\n */\nconst PATH_BEARING_ENV_KEYS: Readonly<Record<string, true>> = Object.freeze({ CODEX_HOME: true, HOME: true, USERPROFILE: true });\n\n/**\n * Environment keys forwarded to the codex child. Everything else — including\n * ambient `OPENAI_API_KEY` / `OPENAI_BASE_URL` — is stripped so the CLI cannot\n * silently fall back to metered API-key auth instead of the subscription\n * login. Mirrors the bench provider's allowlist.\n */\nconst CODEX_RUNTIME_ENV_ALLOWLIST: Readonly<Record<string, true>> = Object.freeze({\n ALL_PROXY: true,\n APPDATA: true,\n CODEX_HOME: true,\n COLORTERM: true,\n COMSPEC: true,\n FORCE_COLOR: true,\n HOME: true,\n HOMEDRIVE: true,\n HOMEPATH: true,\n LANG: true,\n LOCALAPPDATA: true,\n LOGNAME: true,\n NO_COLOR: true,\n NODE_EXTRA_CA_CERTS: true,\n NO_PROXY: true,\n NUMBER_OF_PROCESSORS: true,\n OS: true,\n PATH: true,\n PATHEXT: true,\n PROGRAMDATA: true,\n PROCESSOR_ARCHITECTURE: true,\n HTTP_PROXY: true,\n HTTPS_PROXY: true,\n SHELL: true,\n SSL_CERT_DIR: true,\n SSL_CERT_FILE: true,\n SYSTEMDRIVE: true,\n SYSTEMROOT: true,\n TEMP: true,\n TERM: true,\n TMP: true,\n TMPDIR: true,\n USER: true,\n USERNAME: true,\n USERPROFILE: true,\n WINDIR: true,\n XDG_CACHE_HOME: true,\n XDG_CONFIG_HOME: true,\n XDG_DATA_HOME: true,\n XDG_RUNTIME_DIR: true,\n});\n\nconst LOGIN_OK_PATTERN = /logged in using chatgpt/i;\nconst LOGIN_API_KEY_PATTERN = /api key/i;\n/** CLI output shapes that mean the subscription session was rejected. */\nconst AUTH_FAILURE_PATTERN =\n /\\b401\\b|\\b403\\b|unauthorized|forbidden|not logged in|no active|re-?authenticat|token expired|session expired|please log ?in/i;\nconst TIMEOUT_PATTERN = /timed out after \\d+ms/i;\n/** Secret shapes a codex CLI could echo into errors; replaced before surfacing. */\nconst SECRET_ECHO_PATTERNS: readonly RegExp[] = [\n /sk-[A-Za-z0-9_-]{8,}/g,\n /Bearer\\s+[A-Za-z0-9._-]{8,}/g,\n /eyJ[A-Za-z0-9._-]{10,}/g,\n];\n\n/**\n * Built-in provider entry so `\"codex-subscription/<model>\"` chains resolve\n * with no models.json and no apiKey — the transport owns authentication.\n */\nexport function codexSubscriptionBuiltinProviderConfig(): ModelProviderConfig {\n return {\n baseUrl: \"codex-cli://subscription\",\n api: \"codex-cli\",\n models: [],\n };\n}\n\nexport type CodexSubscriptionAuthReason = \"unauthenticated\" | \"expired_or_revoked\";\n\n/** Login/authorization failure with actionable re-auth guidance. */\nexport class CodexSubscriptionAuthError extends Error {\n readonly reason: CodexSubscriptionAuthReason;\n\n constructor(reason: CodexSubscriptionAuthReason, message: string) {\n super(message);\n this.name = \"CodexSubscriptionAuthError\";\n this.reason = reason;\n }\n}\n\n/** Provider config violated the no-raw-credentials contract. */\nexport class CodexSubscriptionConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"CodexSubscriptionConfigError\";\n }\n}\n\n/** Request exceeded its deadline. Abort-driven cancellations rethrow the\n * caller's own abort reason instead, preserving the AbortError contract. */\nexport class CodexSubscriptionTimeoutError extends Error {\n constructor(timeoutMs: number) {\n super(`codex-subscription request timed out after ${timeoutMs}ms`);\n this.name = \"TimeoutError\";\n }\n}\n\nexport interface CodexSubscriptionExecRequest {\n executable: string;\n args: string[];\n input: string;\n env: NodeJS.ProcessEnv;\n outputPath: string;\n workspacePath: string;\n timeoutMs?: number;\n signal?: AbortSignal;\n}\n\nexport interface CodexSubscriptionExecResult {\n status: number | null;\n signal?: NodeJS.Signals | null;\n stdout: string;\n stderr: string;\n outputText: string;\n}\n\nexport interface CodexSubscriptionRunnerDeps {\n /** Runs `codex exec`. Default: isolated subprocess via launchProcess. */\n runCodexExec?: (request: CodexSubscriptionExecRequest) => Promise<CodexSubscriptionExecResult>;\n /** Runs `codex login status`. Default: isolated subprocess. Receives the\n * shared login-status ceiling (not any one caller's deadline) and the\n * shared-check abort signal (fires when the last waiter leaves). */\n runLoginStatus?: (\n executable: string,\n env: NodeJS.ProcessEnv,\n timeoutMs: number,\n signal?: AbortSignal\n ) => Promise<{ status: number | null; stdout: string; stderr: string }>;\n /** Environment the isolated child env is filtered from. Tests inject fakes. */\n env?: NodeJS.ProcessEnv;\n tmpdir?: () => string;\n now?: () => number;\n}\n\ninterface CommandOutput {\n status: number | null;\n signal?: NodeJS.Signals | null;\n stdout: string;\n stderr: string;\n}\n\ninterface LoginStatusCacheEntry {\n at: number;\n promise: Promise<void>;\n /** Auth-store fingerprint at check time; a mismatch invalidates the entry. */\n fingerprint: string | null;\n /** Live waiters. The in-flight check aborts when the last waiter leaves. */\n waiters: number;\n settled: boolean;\n controller: AbortController;\n}\n\nconst loginStatusCache = new Map<string, LoginStatusCacheEntry>();\nconst inFlightLoginByRunner = new WeakMap<CodexCliFallbackRunner, Map<string, LoginStatusCacheEntry>>();\nconst childrenByRunner = new WeakMap<CodexCliFallbackRunner, Set<number>>();\nconst runnerByOwner = new WeakMap<object, CodexCliFallbackRunner>();\nconst shutdownByRunner = new WeakMap<CodexCliFallbackRunner, AbortController>();\nlet defaultRegisteredRunner: CodexCliFallbackRunner | undefined;\nlet coreRunnerRegistered = false;\n\nexport function createCodexSubscriptionRunner(deps: CodexSubscriptionRunnerDeps = {}): CodexCliFallbackRunner {\n const envSource = deps.env ?? process.env;\n const children = new Set<number>();\n const shutdown = new AbortController();\n const runnerDeps: CodexSubscriptionRunnerDeps = {\n ...deps,\n runCodexExec: deps.runCodexExec ?? ((request) => runCodexExecSubprocess(request, children)),\n runLoginStatus:\n deps.runLoginStatus ??\n ((executable, env, timeoutMs, signal) =>\n runLoginStatusSubprocess(executable, env, timeoutMs, signal, children)),\n };\n const runner: CodexCliFallbackRunner = (request) => {\n const signal = request.options.signal\n ? AbortSignal.any([request.options.signal, shutdown.signal])\n : shutdown.signal;\n if (signal.aborted) {\n return Promise.reject(abortErrorOf(signal));\n }\n return runCodexSubscriptionRequest(\n { ...request, options: { ...request.options, signal } },\n runnerDeps,\n envSource,\n runner,\n );\n };\n childrenByRunner.set(runner, children);\n shutdownByRunner.set(runner, shutdown);\n return runner;\n}\n\n/** One runner per owning runtime/config object so teardown cannot cross instances. */\nexport function getCodexSubscriptionRunnerForOwner(owner: object): CodexCliFallbackRunner {\n let runner = runnerByOwner.get(owner);\n const shutdown = runner ? shutdownByRunner.get(runner) : undefined;\n if (!runner || shutdown?.signal.aborted) {\n runner = createCodexSubscriptionRunner();\n runnerByOwner.set(owner, runner);\n }\n return runner;\n}\n\n/** Shutdown signal for the owning runner, if this function is a core runner. */\nexport function getCodexSubscriptionShutdownSignal(\n runner: CodexCliFallbackRunner,\n): AbortSignal | undefined {\n return shutdownByRunner.get(runner)?.signal;\n}\n\n/**\n * Registers the core subprocess transport on the codex-cli seam, but only\n * when no runner is registered yet — a host or benchmark runner that claimed\n * the seam first always wins. If the seam was cleared afterwards\n * (setCodexCliFallbackRunnerForProcess(undefined)), the core runner is\n * re-registered so the next call still routes. Idempotent.\n */\nexport function ensureCodexSubscriptionRunnerRegistered(deps: CodexSubscriptionRunnerDeps = {}): boolean {\n if (isCodexCliFallbackRunnerRegistered()) return false;\n defaultRegisteredRunner = createCodexSubscriptionRunner(deps);\n setCodexCliFallbackRunnerForProcess(defaultRegisteredRunner);\n coreRunnerRegistered = true;\n return true;\n}\n\n/** True when the process seam still holds the core default runner. */\nexport function isDefaultRegisteredCodexSubscriptionRunner(): boolean {\n return defaultRegisteredRunner !== undefined\n && getCodexCliFallbackRunnerForProcess() === defaultRegisteredRunner;\n}\n/** Host shutdown hook: terminate this runtime's detached Codex children. Does not exit. */\nexport function terminateActiveCodexSubscriptionChildren(\n signal: NodeJS.Signals = \"SIGTERM\",\n runner?: CodexCliFallbackRunner,\n): void {\n const target = runner ?? defaultRegisteredRunner;\n if (!target) return;\n const shutdown = shutdownByRunner.get(target);\n if (shutdown && !shutdown.signal.aborted) {\n shutdown.abort(new DOMException(\"The operation was aborted.\", \"AbortError\"));\n }\n const pids = childrenByRunner.get(target);\n if (!pids) return;\n for (const pid of pids) {\n terminateCodexChildPid(pid, signal);\n }\n}\n\n/** SIGTERM now, SIGKILL after a bound, even if the caller later awaits drain. */\nexport function beginCodexSubscriptionShutdown(\n runner?: CodexCliFallbackRunner,\n forceKillAfterMs = 1_000,\n): () => void {\n terminateActiveCodexSubscriptionChildren(\"SIGTERM\", runner);\n const timer = setTimeout(() => {\n terminateActiveCodexSubscriptionChildren(\"SIGKILL\", runner);\n }, forceKillAfterMs);\n timer.unref();\n return () => {\n clearTimeout(timer);\n terminateActiveCodexSubscriptionChildren(\"SIGKILL\", runner);\n };\n}\nasync function runCodexSubscriptionRequest(\n request: CodexCliFallbackRequest,\n deps: CodexSubscriptionRunnerDeps,\n envSource: NodeJS.ProcessEnv,\n runner: CodexCliFallbackRunner,\n): Promise<CodexCliFallbackResult> {\n const { config } = request;\n assertNoApiKeyConfig(config);\n const executable = resolveExecutable(config, envSource);\n const env = buildIsolatedEnv(envSource);\n\n const providerTimeoutMs = config.retryOptions?.timeoutMs !== undefined\n ? normalizeCodexCliTimeoutMs(config.retryOptions.timeoutMs)\n : undefined;\n const timeoutMs = request.options.timeoutMs ?? providerTimeoutMs ?? DEFAULT_TIMEOUT_MS;\n // The effective deadline starts BEFORE the login-status precheck: login\n // consumes the same call/provider timeout budget, it is not additive.\n const now = deps.now ?? Date.now;\n const deadlineStartedAt = now();\n if (request.options.signal?.aborted) {\n throw abortErrorOf(request.options.signal);\n }\n await assertSubscriptionLogin(executable, env, deps, {\n runner,\n callerTimeoutMs: timeoutMs,\n deadlineStartedAt,\n signal: request.options.signal,\n });\n\n if (request.options.signal?.aborted) {\n throw abortErrorOf(request.options.signal);\n }\n\n const remainingTimeoutMs = timeoutMs - (now() - deadlineStartedAt);\n if (remainingTimeoutMs <= 0) {\n throw new CodexSubscriptionTimeoutError(timeoutMs);\n }\n let tempDir: string | undefined;\n try {\n tempDir = await mkdtemp(path.join(deps.tmpdir?.() ?? os.tmpdir(), \"remnic-codex-sub-\"));\n const workspacePath = path.join(tempDir, \"workspace\");\n const outputPath = path.join(tempDir, \"last-message.txt\");\n await mkdir(workspacePath, { recursive: true });\n\n const runCodexExec = deps.runCodexExec!;\n const result = await runCodexExec({\n executable,\n args: buildExecArgs(request.modelId, config, workspacePath, outputPath),\n input: buildPrompt(request.messages),\n env,\n outputPath,\n workspacePath,\n timeoutMs: remainingTimeoutMs,\n signal: request.options.signal,\n });\n\n if (result.status !== 0) {\n const failure = execFailureError(result, remainingTimeoutMs, executable, env);\n if (failure instanceof CodexSubscriptionTimeoutError) {\n throw failure;\n }\n if (request.options.signal?.aborted) {\n throw abortErrorOf(request.options.signal);\n }\n throw failure;\n }\n if (request.options.signal?.aborted) {\n throw abortErrorOf(request.options.signal);\n }\n\n const content = result.outputText.trim();\n if (content.length === 0) {\n throw new Error(\n `codex-subscription: codex exec returned no final message (${summarizeOutput(result.stderr, result.stdout)})`\n );\n }\n return { content, ...usageFrom(result) };\n } finally {\n if (tempDir) {\n await rm(tempDir, { force: true, recursive: true }).catch(() => {});\n }\n }\n}\n\n/**\n * This provider authenticates exclusively through the codex CLI login, so a\n * configured apiKey is a configuration mistake — reject it without echoing\n * the value.\n */\nexport function assertNoApiKeyConfig(config: CodexCliFallbackConfig): void {\n if (config.apiKey !== undefined && config.apiKey !== \"\") {\n throw new CodexSubscriptionConfigError(\n `${CODEX_SUBSCRIPTION_PROVIDER_ID} does not accept apiKey configuration: it authenticates through the Codex CLI login. Remove the apiKey field and run \\`codex login\\` with a ChatGPT account instead.`\n );\n }\n}\n\nfunction resolveExecutable(config: CodexCliFallbackConfig, env: NodeJS.ProcessEnv): string {\n const configured = config.codexCliExecutable ?? config.executable ?? env[CODEX_EXECUTABLE_ENV] ?? \"codex\";\n const expanded = expandTildePath(String(configured).trim());\n if (expanded.trim().length === 0) {\n throw new CodexSubscriptionConfigError(`${CODEX_EXECUTABLE_ENV} / executable must not be empty`);\n }\n // The exec child runs with cwd = the temp workspace, and spawn resolves a\n // relative executable against the CHILD cwd. Resolve relative paths against\n // the original process cwd now so `./codex` stays valid; bare names keep\n // PATH lookup.\n if (expanded.includes(\"/\") || expanded.includes(\"\\\\\")) {\n return path.resolve(expanded);\n }\n return expanded;\n}\n\nfunction buildIsolatedEnv(source: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = {};\n for (const [key, value] of Object.entries(source)) {\n if (value !== undefined && isAllowedEnvKey(key)) {\n env[key] = PATH_BEARING_ENV_KEYS[key.toUpperCase()] === true\n ? normalizePathEnvValue(value)\n : value;\n }\n }\n return env;\n}\n\n/**\n * The exec child runs with cwd = the ephemeral workspace while the login\n * child runs with cwd = this process, so a relative HOME/CODEX_HOME would\n * resolve differently per child. Anchor both to the original process cwd.\n */\nfunction normalizePathEnvValue(value: string): string {\n const trimmed = value.trim();\n if (trimmed.length === 0) {\n return value;\n }\n const expanded = expandTildePath(trimmed);\n return path.isAbsolute(expanded) ? expanded : path.resolve(expanded);\n}\nfunction isAllowedEnvKey(key: string): boolean {\n const normalized = key.toUpperCase();\n return CODEX_RUNTIME_ENV_ALLOWLIST[normalized] === true || normalized.startsWith(\"LC_\");\n}\n\ninterface LoginCheckContext {\n runner: CodexCliFallbackRunner;\n /** The caller's overall effective deadline (login + exec). */\n callerTimeoutMs: number;\n deadlineStartedAt: number;\n signal?: AbortSignal;\n}\n\n/**\n * Verifies the operator's Codex subscription login via `codex login status`.\n * Success is cached briefly; failures are never cached so a re-login takes\n * effect on the next request. A cached success is also invalidated when the\n * auth store changes on disk (e.g. another process switches the same Codex\n * home from a ChatGPT login to an API key), so the cache can never mask a\n * later non-subscription login mode.\n */\nasync function assertSubscriptionLogin(\n executable: string,\n env: NodeJS.ProcessEnv,\n deps: CodexSubscriptionRunnerDeps,\n ctx: LoginCheckContext\n): Promise<void> {\n const now = deps.now ?? Date.now;\n const cacheKey = loginCacheKey(executable, env);\n const inflight = inFlightLoginsFor(ctx.runner).get(cacheKey);\n if (inflight && now() - inflight.at < LOGIN_STATUS_CACHE_TTL_MS) {\n await waitForLoginEntry(inflight, cacheKey, ctx, now);\n return;\n }\n const cached = loginStatusCache.get(cacheKey);\n if (cached && now() - cached.at < LOGIN_STATUS_CACHE_TTL_MS) {\n if (!cached.settled || (await authStoreFingerprint(env)) === cached.fingerprint) {\n await waitForLoginEntry(cached, cacheKey, ctx, now);\n return;\n }\n if (loginStatusCache.get(cacheKey) === cached) {\n loginStatusCache.delete(cacheKey);\n }\n }\n const raced = inFlightLoginsFor(ctx.runner).get(cacheKey);\n if (raced && now() - raced.at < LOGIN_STATUS_CACHE_TTL_MS) {\n await waitForLoginEntry(raced, cacheKey, ctx, now);\n return;\n }\n const published = loginStatusCache.get(cacheKey);\n if (published && now() - published.at < LOGIN_STATUS_CACHE_TTL_MS) {\n await waitForLoginEntry(published, cacheKey, ctx, now);\n return;\n }\n if (ctx.signal?.aborted) {\n throw abortErrorOf(ctx.signal);\n }\n const entry: LoginStatusCacheEntry = {\n at: now(),\n fingerprint: null,\n waiters: 0,\n settled: false,\n controller: new AbortController(),\n promise: Promise.resolve(undefined),\n };\n entry.promise = runLoginStatusCheck(executable, env, deps, entry);\n entry.promise.then(\n () => {\n entry.settled = true;\n loginStatusCache.set(cacheKey, entry);\n dropInFlightLogin(ctx.runner, cacheKey, entry);\n },\n () => {\n entry.settled = true;\n dropInFlightLogin(ctx.runner, cacheKey, entry);\n }\n );\n inFlightLoginsFor(ctx.runner).set(cacheKey, entry);\n await waitForLoginEntry(entry, cacheKey, ctx, now);\n}\n\nasync function runLoginStatusCheck(\n executable: string,\n env: NodeJS.ProcessEnv,\n deps: CodexSubscriptionRunnerDeps,\n entry: LoginStatusCacheEntry\n): Promise<void> {\n const runLoginStatus = deps.runLoginStatus!;\n const result = await runLoginStatus(executable, env, LOGIN_STATUS_TIMEOUT_MS, entry.controller.signal);\n const output = `${result.stdout}\\n${result.stderr}`.trim();\n if (TIMEOUT_PATTERN.test(output)) {\n // A login-status subprocess timeout is a deadline expiry, not an auth\n // failure — never report it as \"unauthenticated\".\n throw new CodexSubscriptionTimeoutError(LOGIN_STATUS_TIMEOUT_MS);\n }\n if (result.status === 0 && LOGIN_OK_PATTERN.test(output)) {\n entry.fingerprint = await authStoreFingerprint(env);\n return;\n }\n const apiKeyNote = LOGIN_API_KEY_PATTERN.test(output)\n ? \" The CLI currently reports an API-key login; subscription extraction needs a ChatGPT login.\"\n : \"\";\n throw new CodexSubscriptionAuthError(\n \"unauthenticated\",\n `codex-subscription: no ChatGPT-backed Codex login found. Run \\`codex login\\` and choose the ChatGPT account option, then retry.${apiKeyNote}`\n );\n}\n\n/**\n * Waits on a shared login check with this caller's own budget. A deadline or\n * caller abort rejects immediately — without cancelling the shared check\n * while other callers still wait on it. When the LAST waiter leaves an\n * in-flight check, the subprocess is aborted and the entry dropped so a later\n * caller starts a fresh check.\n */\nasync function waitForLoginEntry(\n entry: LoginStatusCacheEntry,\n cacheKey: string,\n ctx: LoginCheckContext,\n now: () => number\n): Promise<void> {\n const remainingMs = ctx.callerTimeoutMs - (now() - ctx.deadlineStartedAt);\n if (remainingMs <= 0) {\n throw new CodexSubscriptionTimeoutError(ctx.callerTimeoutMs);\n }\n const signal = ctx.signal;\n let rejectOnAbort!: (reason?: unknown) => void;\n const abortWait = new Promise<never>((_resolve, reject) => {\n rejectOnAbort = reject;\n });\n // Identity matters: the same reference is removed in the finally below.\n const onAbort = (): void => {\n if (signal === undefined) return;\n rejectOnAbort(abortErrorOf(signal));\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n entry.waiters++;\n // The deadline timer intentionally holds the event loop: this caller's\n // await is backed by nothing else while the shared check is in flight.\n let deadlineTimer: ReturnType<typeof setTimeout> | undefined;\n try {\n await Promise.race([\n entry.promise,\n abortWait,\n new Promise<never>((_resolve, reject) => {\n deadlineTimer = setTimeout(\n () => reject(new CodexSubscriptionTimeoutError(ctx.callerTimeoutMs)),\n remainingMs\n );\n }),\n ]);\n } catch (err) {\n if (signal?.aborted) {\n throw abortErrorOf(signal);\n }\n throw err;\n } finally {\n clearTimeout(deadlineTimer);\n signal?.removeEventListener(\"abort\", onAbort);\n entry.waiters--;\n if (entry.waiters <= 0 && !entry.settled) {\n entry.controller.abort();\n dropInFlightLogin(ctx.runner, cacheKey, entry);\n if (loginStatusCache.get(cacheKey) === entry) {\n loginStatusCache.delete(cacheKey);\n }\n }\n }\n}\n\n/**\n * `mtimeMs:size` of the Codex auth store, read as metadata only — the\n * provider never reads the store's contents. A change between the cached\n * check and now invalidates the cached login mode.\n */\nasync function authStoreFingerprint(env: NodeJS.ProcessEnv): Promise<string | null> {\n const home = resolveCodexAuthHome(env);\n if (!home) {\n return null;\n }\n try {\n const stats = await stat(path.join(home, \"auth.json\"));\n return `${stats.mtimeMs}:${stats.size}`;\n } catch {\n return null;\n }\n}\n\nfunction resolveCodexAuthHome(env: NodeJS.ProcessEnv): string | undefined {\n if (env.CODEX_HOME) return env.CODEX_HOME;\n const profile = env.HOME ?? env.USERPROFILE;\n return profile ? path.join(profile, \".codex\") : undefined;\n}\n\nfunction loginCacheKey(executable: string, env: NodeJS.ProcessEnv): string {\n return `${executable}\\0${resolveCodexAuthHome(env) ?? \"\"}`;\n}\n\nfunction inFlightLoginsFor(runner: CodexCliFallbackRunner): Map<string, LoginStatusCacheEntry> {\n let map = inFlightLoginByRunner.get(runner);\n if (!map) {\n map = new Map();\n inFlightLoginByRunner.set(runner, map);\n }\n return map;\n}\n\nfunction dropInFlightLogin(\n runner: CodexCliFallbackRunner,\n cacheKey: string,\n entry: LoginStatusCacheEntry,\n): void {\n const map = inFlightLoginByRunner.get(runner);\n if (map?.get(cacheKey) === entry) map.delete(cacheKey);\n}\n\n/**\n * Args mirror the bench provider: fully sandboxed, ephemeral, no tools/hooks/\n * plugins/memories — extraction must behave as a plain completion endpoint.\n */\nfunction buildExecArgs(\n modelId: string,\n config: CodexCliFallbackConfig,\n workspacePath: string,\n outputPath: string\n): string[] {\n const reasoningEffort = readReasoningEffort(config) ?? DEFAULT_REASONING_EFFORT;\n return [\n \"exec\",\n \"--strict-config\",\n \"--model\",\n modelId,\n \"--config\",\n `model_reasoning_effort=${JSON.stringify(reasoningEffort)}`,\n \"--config\",\n 'approval_policy=\"never\"',\n \"--config\",\n 'web_search=\"disabled\"',\n \"--disable\",\n \"hooks\",\n \"--disable\",\n \"shell_tool\",\n \"--disable\",\n \"unified_exec\",\n \"--disable\",\n \"apps\",\n \"--disable\",\n \"plugins\",\n \"--disable\",\n \"remote_plugin\",\n \"--disable\",\n \"multi_agent\",\n \"--disable\",\n \"browser_use\",\n \"--disable\",\n \"browser_use_external\",\n \"--disable\",\n \"computer_use\",\n \"--disable\",\n \"image_generation\",\n \"--disable\",\n \"in_app_browser\",\n \"--disable\",\n \"goals\",\n \"--disable\",\n \"memories\",\n \"--disable\",\n \"chronicle\",\n \"--disable\",\n \"tool_suggest\",\n \"--disable\",\n \"workspace_dependencies\",\n \"--disable\",\n \"shell_snapshot\",\n \"--ephemeral\",\n \"--ignore-user-config\",\n \"--ignore-rules\",\n \"--sandbox\",\n \"read-only\",\n \"--cd\",\n workspacePath,\n \"--skip-git-repo-check\",\n \"--json\",\n \"--output-last-message\",\n outputPath,\n \"-\",\n ];\n}\n\nfunction readReasoningEffort(config: CodexCliFallbackConfig): CodexCliReasoningEffort | undefined {\n for (const value of [config.codexCliReasoningEffort, config.reasoningEffort]) {\n if (typeof value === \"string\") {\n const normalized = value.trim().toLowerCase();\n if (normalized === \"low\" || normalized === \"medium\" || normalized === \"high\" || normalized === \"xhigh\") {\n return normalized;\n }\n throw new CodexSubscriptionConfigError(\n \"codex-subscription reasoningEffort must be one of low, medium, high, xhigh (got unrecognized value)\"\n );\n }\n }\n return undefined;\n}\n\n/**\n * One JSON object per message: role boundaries are structural (JSON string\n * escaping), so literal `[system]`/role-marker text inside message content\n * can never forge another role's turn. Content stays model-visible verbatim.\n */\nfunction buildPrompt(messages: readonly CodexCliFallbackMessage[]): string {\n const transcript = messages\n .map((message) => JSON.stringify({ role: message.role, content: message.content }))\n .join(\"\\n\");\n return [\n \"You are acting as an LLM completion endpoint for a memory-extraction pipeline, not as a coding agent.\",\n \"Answer the request in the transcript below. Do not inspect files, run commands, browse, or use tools.\",\n \"Return only the final answer text. If the request asks for JSON, return raw JSON only.\",\n \"\",\n 'TRANSCRIPT (JSON Lines: one {\"role\":\"...\",\"content\":\"...\"} object per message; newlines inside content are escaped):',\n transcript,\n ].join(\"\\n\");\n}\n\nfunction execFailureError(\n result: CodexSubscriptionExecResult,\n timeoutMs: number,\n executable: string,\n env: NodeJS.ProcessEnv\n): Error {\n const combined = `${result.stderr}\\n${result.stdout}`;\n // The authoritative timeout marker wins over auth-pattern text: a timed-out\n // exec may have already printed model/diagnostic output containing 401/403\n // before the deadline fired (issue #2833).\n if (TIMEOUT_PATTERN.test(combined)) {\n return new CodexSubscriptionTimeoutError(timeoutMs);\n }\n if (AUTH_FAILURE_PATTERN.test(combined)) {\n // Re-check login on the next request; the cached status is now stale.\n loginStatusCache.delete(loginCacheKey(executable, env));\n return new CodexSubscriptionAuthError(\n \"expired_or_revoked\",\n \"codex-subscription: the Codex CLI rejected the request as unauthenticated \" +\n \"(session expired or revoked). Run `codex login` again to re-authenticate, then retry.\"\n );\n }\n\n const exitLabel = result.signal ? `signal ${result.signal}` : `exit ${result.status ?? \"unknown\"}`;\n return new Error(\n `codex-subscription: codex exec failed (${exitLabel}): ${summarizeOutput(result.stderr, result.stdout)}`\n );\n}\n\n/** Secret-redacted, then bounded tail of child output — the only child text\n * that ever reaches a thrown error. Redaction runs BEFORE bounding so a\n * secret straddling the cutoff cannot survive as an unmatchable fragment. */\nfunction summarizeOutput(stderr: string, stdout: string): string {\n const summary = [stderr.trim(), stdout.trim()].filter((part) => part.length > 0).join(\"\\n\");\n if (summary.length === 0) {\n return \"no process output\";\n }\n return redactSecretEchoes(summary).slice(-OUTPUT_SUMMARY_LIMIT);\n}\n\nexport function redactSecretEchoes(value: string): string {\n let redacted = value;\n for (const pattern of SECRET_ECHO_PATTERNS) {\n redacted = redacted.replace(pattern, \"[redacted]\");\n }\n return redacted;\n}\ntype CodexSubscriptionUsage = Pick<CodexCliFallbackResult, \"usage\">;\n\nfunction usageFrom(result: CodexSubscriptionExecResult): CodexSubscriptionUsage {\n const usage = parseCodexJsonlUsage(`${result.stdout}\\n${result.stderr}`);\n if (!usage) {\n return {};\n }\n return {\n usage: {\n inputTokens: usage.inputTokens,\n outputTokens: usage.outputTokens,\n totalTokens: usage.inputTokens + usage.outputTokens,\n },\n };\n}\n\nexport function parseCodexJsonlUsage(output: string): { inputTokens: number; outputTokens: number } | undefined {\n let usage: { inputTokens: number; outputTokens: number } | undefined;\n for (const line of output.split(/\\r?\\n/)) {\n const trimmed = line.trim();\n if (!trimmed.startsWith(\"{\")) continue;\n try {\n const event = JSON.parse(trimmed) as {\n type?: unknown;\n usage?: Record<string, unknown>;\n };\n if (event.type !== \"turn.completed\" || !event.usage) continue;\n const inputTokens = readCounter(event.usage.input_tokens);\n const outputTokens = readCounter(event.usage.output_tokens);\n if (inputTokens !== undefined && outputTokens !== undefined) {\n usage = { inputTokens, outputTokens };\n }\n } catch {\n // codex exec prints non-JSON status text alongside JSONL; ignore it\n }\n }\n return usage;\n}\n\nfunction readCounter(value: unknown): number | undefined {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value < 0) {\n return undefined;\n }\n return value;\n}\n\nfunction abortErrorOf(signal: AbortSignal): Error {\n if (signal.reason instanceof CodexSubscriptionTimeoutError) return signal.reason;\n const reason =\n signal.reason instanceof Error\n ? signal.reason\n : signal.reason !== undefined\n ? new Error(String(signal.reason))\n : new DOMException(\"The operation was aborted.\", \"AbortError\");\n const match = /fallback LLM timed out after (\\d+)ms/i.exec(reason.message);\n if (match) {\n return new CodexSubscriptionTimeoutError(Number(match[1]));\n }\n return reason;\n}\n\n/** Bounds streamed child output to STDIO_LIMIT, keeping the TAIL — codex\n * prints its final error or turn.completed result last. Secrets are redacted\n * BEFORE bounding so one straddling the cutoff cannot leak a fragment. */\nfunction appendBounded(existing: string, next: string): string {\n const redacted = redactSecretEchoes(existing + next);\n return redacted.length > STDIO_LIMIT ? redacted.slice(-STDIO_LIMIT) : redacted;\n}\n\nasync function runLoginStatusSubprocess(\n executable: string,\n env: NodeJS.ProcessEnv,\n timeoutMs: number,\n signal: AbortSignal | undefined,\n children: Set<number>,\n): Promise<{ status: number | null; stdout: string; stderr: string }> {\n return await runSubprocess(executable, [\"login\", \"status\"], {\n env,\n stdin: undefined,\n timeoutMs,\n timeoutLabel: \"login status\",\n signal,\n children,\n });\n}\n\nasync function runCodexExecSubprocess(\n request: CodexSubscriptionExecRequest,\n children: Set<number>,\n): Promise<CodexSubscriptionExecResult> {\n const result = await runSubprocess(request.executable, request.args, {\n env: request.env,\n stdin: request.input,\n timeoutMs: request.timeoutMs,\n timeoutLabel: \"exec\",\n signal: request.signal,\n cwd: request.workspacePath,\n children,\n });\n if (result.status === 124) return { ...result, outputText: \"\" };\n let outputText = \"\";\n try {\n outputText = (await readFile(request.outputPath, \"utf8\")).trim();\n } catch {\n // Missing output file surfaces as the no-final-message error path.\n }\n return { ...result, outputText };\n}\n\ninterface SubprocessOptions {\n env: NodeJS.ProcessEnv;\n stdin?: string;\n timeoutMs?: number;\n timeoutLabel: string;\n signal?: AbortSignal;\n cwd?: string;\n children: Set<number>;\n}\n\nasync function runSubprocess(executable: string, args: string[], options: SubprocessOptions): Promise<CommandOutput> {\n return await new Promise<CommandOutput>((resolve, reject) => {\n if (options.signal?.aborted) {\n reject(abortErrorOf(options.signal));\n return;\n }\n const child = launchProcess(executable, args, {\n ...(options.cwd ? { cwd: options.cwd } : {}),\n env: options.env,\n stdio: [options.stdin === undefined ? \"ignore\" : \"pipe\", \"pipe\", \"pipe\"],\n detached: process.platform !== \"win32\",\n windowsHide: true,\n });\n registerActiveCodexChild(child.pid, options.children);\n let stdout = \"\";\n let stderr = \"\";\n let timedOut = false;\n let settled = false;\n let killTimeout: ReturnType<typeof setTimeout> | undefined;\n const clearKillTimeout = (): void => {\n if (killTimeout) {\n clearTimeout(killTimeout);\n killTimeout = undefined;\n }\n };\n const terminate = (signal: NodeJS.Signals): void => {\n if (child.pid && process.platform !== \"win32\") {\n try {\n process.kill(-child.pid, signal);\n return;\n } catch {\n // fall through to killing the direct child\n }\n }\n child.kill(signal);\n };\n const scheduleForcedKill = (): void => {\n clearKillTimeout();\n killTimeout = setTimeout(() => terminate(\"SIGKILL\"), 1_000);\n killTimeout.unref();\n };\n const finish = (fn: () => void): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timeout);\n options.signal?.removeEventListener(\"abort\", onAbort);\n fn();\n };\n const settleTimeout = (): void => {\n timedOut = true;\n stderr = appendBounded(\n stderr,\n `\\ncodex-subscription: ${options.timeoutLabel} timed out after ${options.timeoutMs}ms.`\n );\n finish(() => {\n resolve({\n status: 124,\n signal: \"SIGTERM\",\n stdout,\n stderr,\n });\n });\n };\n const onAbort = (): void => {\n const reasonText =\n options.signal?.reason instanceof Error\n ? options.signal.reason.message\n : String(options.signal?.reason ?? \"\");\n terminate(\"SIGTERM\");\n scheduleForcedKill();\n if (/fallback LLM timed out after \\d+ms/i.test(reasonText)) {\n settleTimeout();\n return;\n }\n finish(() => reject(abortErrorOf(options.signal!)));\n };\n options.signal?.addEventListener(\"abort\", onAbort, { once: true });\n const timeout = options.timeoutMs\n ? setTimeout(() => {\n terminate(\"SIGTERM\");\n scheduleForcedKill();\n settleTimeout();\n }, options.timeoutMs)\n : undefined;\n timeout?.unref();\n child.stdout?.setEncoding(\"utf8\");\n child.stderr?.setEncoding(\"utf8\");\n child.stdout?.on(\"data\", (chunk: string) => {\n stdout = appendBounded(stdout, chunk);\n });\n child.stderr?.on(\"data\", (chunk: string) => {\n stderr = appendBounded(stderr, chunk);\n });\n child.stdin?.on(\"error\", (error: NodeJS.ErrnoException) => {\n stderr = appendBounded(stderr, `\\ncodex-subscription: stdin error: ${error.code ?? error.message}`);\n });\n child.on(\"error\", (error) => {\n unregisterActiveCodexChild(child.pid, options.children);\n finish(() => {\n reject(\n child.pid\n ? new Error(`codex-subscription: codex CLI failed after start: ${safeMessage(error)}`, { cause: error })\n : new CodexSubscriptionAuthError(\n \"unauthenticated\",\n `codex-subscription: codex CLI not found at \\`${executable}\\` — install the Codex CLI and run \\`codex login\\` with a ChatGPT account, then retry.`\n )\n );\n });\n });\n child.on(\"close\", (status, signal) => {\n clearKillTimeout();\n unregisterActiveCodexChild(child.pid, options.children);\n finish(() => {\n resolve({\n status: timedOut ? (status === null || status === 0 ? 124 : status) : status,\n signal: timedOut ? (signal ?? \"SIGTERM\") : signal,\n stdout,\n stderr,\n });\n });\n });\n if (options.stdin !== undefined) {\n try {\n child.stdin?.end(options.stdin);\n } catch (error) {\n log.debug(`codex-subscription: failed writing prompt to stdin: ${safeMessage(error)}`);\n }\n }\n });\n}\n\nfunction safeMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nexport const __codexSubscriptionTestHooks = {\n activeCodexChildCount: (runner?: CodexCliFallbackRunner): number => {\n const target = runner ?? defaultRegisteredRunner;\n return target ? (childrenByRunner.get(target)?.size ?? 0) : 0;\n },\n appendBounded,\n resetLoginStatusCache: (): void => {\n loginStatusCache.clear();\n },\n isCoreRunnerRegistered: (): boolean => coreRunnerRegistered,\n resetCoreRunnerRegistered: (): void => {\n coreRunnerRegistered = false;\n defaultRegisteredRunner = undefined;\n },\n};\n\nfunction registerActiveCodexChild(pid: number | undefined, children: Set<number>): void {\n if (pid === undefined) return;\n children.add(pid);\n}\n\nfunction unregisterActiveCodexChild(pid: number | undefined, children: Set<number>): void {\n if (pid === undefined) return;\n children.delete(pid);\n}\n\nfunction terminateCodexChildPid(pid: number, signal: NodeJS.Signals): void {\n if (process.platform !== \"win32\") {\n try {\n process.kill(-pid, signal);\n return;\n } catch {\n // Fall back to killing the direct child below.\n }\n }\n\n try {\n process.kill(pid, signal);\n } catch {\n // The child may already have exited.\n }\n}\n"],"mappings":"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","names":["path","path"]}