@qwen-code/qwen-code 0.21.2 → 0.21.3

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 (351) hide show
  1. package/bundled/qc-helper/docs/configuration/settings.md +41 -38
  2. package/bundled/qc-helper/docs/features/channels/github.md +7 -0
  3. package/bundled/qc-helper/docs/features/channels/gitlab.md +6 -0
  4. package/bundled/qc-helper/docs/features/code-review.md +78 -23
  5. package/bundled/qc-helper/docs/features/commands.md +2 -0
  6. package/bundled/qc-helper/docs/features/hooks.md +115 -18
  7. package/bundled/qc-helper/docs/features/memory.md +27 -0
  8. package/bundled/qc-helper/docs/features/skills.md +46 -1
  9. package/bundled/qc-helper/docs/features/sub-agents.md +42 -9
  10. package/bundled/qc-helper/docs/features/tool-use-summaries.md +7 -7
  11. package/bundled/qc-helper/docs/qwen-serve.md +21 -1
  12. package/bundled/qc-helper/docs/reference/keyboard-shortcuts.md +13 -13
  13. package/bundled/qc-helper/docs/support/troubleshooting.md +6 -1
  14. package/bundled/review/DESIGN.md +167 -32
  15. package/bundled/review/SKILL.md +156 -32
  16. package/chunks/{MaxSizedBox-TQBQ247P.js → MaxSizedBox-OJR636GP.js} +40 -38
  17. package/chunks/{StandaloneSessionPicker-2HPTKCLX.js → StandaloneSessionPicker-AMMBF5UY.js} +62 -59
  18. package/chunks/{acp-startup-profiler-2C4A5ZTJ.js → acp-startup-profiler-3RICG2YW.js} +2 -2
  19. package/chunks/{acpAgent-PS6EDGH3.js → acpAgent-M57LVIQA.js} +662 -560
  20. package/chunks/{agent-VDOUB35F.js → agent-PE37ARLM.js} +36 -34
  21. package/chunks/{agent-headless-EO53EFS2.js → agent-headless-45ASME4R.js} +36 -34
  22. package/chunks/{anthropicContentGenerator-OFHSZJBF.js → anthropicContentGenerator-WCBYB6JG.js} +337 -27
  23. package/chunks/{artifact-tool-RDN2W7ND.js → artifact-tool-TBOYEKTY.js} +2 -2
  24. package/chunks/{askUserQuestion-OZERWH5R.js → askUserQuestion-23Y5WZFJ.js} +2 -2
  25. package/chunks/{bridge-W7AZSRZQ.js → bridge-K3ZRPO4D.js} +42 -40
  26. package/chunks/{ca-6W3SK7OQ.js → ca-4OJN75WT.js} +46 -5
  27. package/chunks/{channel-management-service-5S7YCWX5.js → channel-management-service-IXJ2MZTS.js} +7 -7
  28. package/chunks/{channel-settings-store-JFVRSNER.js → channel-settings-store-PEAR3IME.js} +43 -41
  29. package/chunks/{channel-worker-group-OVWGYMZS.js → channel-worker-group-NDG4WOOZ.js} +8 -8
  30. package/chunks/{channel-worker-manager-H55GKOQA.js → channel-worker-manager-ZCW6UW34.js} +8 -8
  31. package/chunks/{channel-worker-supervisor-CABBXYHK.js → channel-worker-supervisor-ATGFTZRN.js} +4 -4
  32. package/chunks/{chunk-W3UKBMSI.js → chunk-22YFMJBW.js} +84 -8
  33. package/chunks/{chunk-6MPDY6ZG.js → chunk-24ER7UV4.js} +1 -1
  34. package/chunks/{chunk-QDK4F2N5.js → chunk-2DNKEBXY.js} +1 -1
  35. package/chunks/{chunk-D6MDRSZ6.js → chunk-2LZCLQIS.js} +22 -11
  36. package/chunks/{chunk-JYBU6YUE.js → chunk-2M5N3DHY.js} +1 -1
  37. package/chunks/{chunk-KR32NIAN.js → chunk-2MI7ZTRT.js} +10 -10
  38. package/chunks/{chunk-LHKCY7DY.js → chunk-2UISUFHQ.js} +3 -3
  39. package/chunks/{chunk-JXVC2PL3.js → chunk-33FNCQSY.js} +2 -2
  40. package/chunks/{chunk-NBD76HUW.js → chunk-3LGZEYOF.js} +2 -2
  41. package/chunks/{chunk-CO2U743O.js → chunk-3MWNROHB.js} +4 -4
  42. package/chunks/{chunk-E2BHWIDT.js → chunk-3RXUYQJI.js} +6 -2
  43. package/chunks/{chunk-E5Z2AVNV.js → chunk-3SH53ANB.js} +0 -11
  44. package/chunks/{chunk-LYI2PZDK.js → chunk-3XF4WT3B.js} +2 -2
  45. package/chunks/{chunk-N4ACHWNH.js → chunk-432XCUHF.js} +1 -1
  46. package/chunks/{chunk-FNDXMN7I.js → chunk-4AGGSHCH.js} +774 -205
  47. package/chunks/{chunk-DUNI4EBW.js → chunk-4K3RV3OA.js} +80 -50
  48. package/chunks/{chunk-L2E5GFAG.js → chunk-4LKVQFHM.js} +2 -2
  49. package/chunks/{chunk-YDLJGZHL.js → chunk-4NFY2S7N.js} +0 -33
  50. package/chunks/{chunk-JLZ3RBQG.js → chunk-5JPVOYWA.js} +144 -11
  51. package/chunks/{chunk-6TAASXWL.js → chunk-5VYFXHY2.js} +3 -3
  52. package/chunks/{chunk-EU5TBFJI.js → chunk-6NE5NTHJ.js} +2 -2
  53. package/chunks/{chunk-XQK4XXTN.js → chunk-6O5DVH27.js} +1 -1
  54. package/chunks/{chunk-2RFKHEXL.js → chunk-6PLDPT2C.js} +1 -1
  55. package/chunks/{chunk-3APUUXRF.js → chunk-A5YZ3FA2.js} +2 -1
  56. package/chunks/{chunk-SHN4KHDK.js → chunk-A6SX5TAH.js} +3 -3
  57. package/chunks/{chunk-SZ3KHTDR.js → chunk-AKA24BGO.js} +1 -1
  58. package/chunks/{chunk-Q3T5KA2V.js → chunk-BFHXDAR7.js} +1 -1
  59. package/chunks/{chunk-L55JWS76.js → chunk-BGXZBI5B.js} +1 -1
  60. package/chunks/{chunk-P5VC5USY.js → chunk-BKGA75MU.js} +1 -1
  61. package/chunks/{chunk-2KKCCLPT.js → chunk-BKXTHMOZ.js} +1 -1
  62. package/chunks/{chunk-V4XNUDPE.js → chunk-BSKUTHLO.js} +15 -7
  63. package/chunks/{chunk-MM7TH7ZM.js → chunk-CFDR3GNL.js} +18 -3
  64. package/chunks/{chunk-E2FN6SM7.js → chunk-CIUUYB23.js} +5 -5
  65. package/chunks/{chunk-A7Y5H4TX.js → chunk-D2GPKXCQ.js} +7 -7
  66. package/chunks/{chunk-JCJ7LKNA.js → chunk-DIEFUBV6.js} +1644 -1570
  67. package/chunks/{chunk-GL4JOZXU.js → chunk-DSGJLHM3.js} +4 -4
  68. package/chunks/{chunk-675H6PLP.js → chunk-DYA25Y7U.js} +1 -1
  69. package/chunks/{chunk-3I6DUGQD.js → chunk-E4WHKETF.js} +32 -4
  70. package/chunks/{chunk-CU47KXB5.js → chunk-EGYJRIDF.js} +6 -6
  71. package/chunks/{chunk-6CQWRWUC.js → chunk-EIMKVOSF.js} +3 -3
  72. package/chunks/{chunk-QTERJVPZ.js → chunk-EKTCKZV5.js} +1 -0
  73. package/chunks/{chunk-NHFEMWAZ.js → chunk-FF75UUB2.js} +1 -1
  74. package/chunks/{chunk-L32VV3ZJ.js → chunk-FG63DZQA.js} +12 -12
  75. package/chunks/{chunk-FCYYAB4S.js → chunk-FHPKHXHT.js} +3 -3
  76. package/chunks/{chunk-GJ2GBPBD.js → chunk-FXVHVCNA.js} +5 -5
  77. package/chunks/{chunk-56Z7QEAQ.js → chunk-GMSBXEH4.js} +16 -16
  78. package/chunks/chunk-GOFAQQZA.js +132 -0
  79. package/chunks/{chunk-YUFIEIWC.js → chunk-GTM6IHBB.js} +2 -2
  80. package/chunks/{chunk-HIVKI6O6.js → chunk-H35Q5CEG.js} +9 -7
  81. package/chunks/{chunk-FJ5WUMUQ.js → chunk-H36SETQS.js} +1 -1
  82. package/chunks/{chunk-AUOULH6Q.js → chunk-H6FIBBXK.js} +1610 -2196
  83. package/chunks/{chunk-VEMNQMHA.js → chunk-H6ZYP74A.js} +5 -13
  84. package/chunks/{chunk-TTJZAIFP.js → chunk-HEBW74ZW.js} +3 -3
  85. package/chunks/{chunk-6FMJLI5M.js → chunk-HFVW54NL.js} +41 -6
  86. package/chunks/{chunk-44F4LBWZ.js → chunk-HIXVWF7I.js} +56 -18
  87. package/chunks/{chunk-EQNUDTL6.js → chunk-HUIQ4FYC.js} +2 -2
  88. package/chunks/{chunk-5NNHICSA.js → chunk-I7JDGNG6.js} +3 -3
  89. package/chunks/{chunk-PVNQRENX.js → chunk-IF6K42YJ.js} +1 -1
  90. package/chunks/{chunk-PJOHNFIY.js → chunk-IWGTGH4G.js} +5 -5
  91. package/chunks/{chunk-B4AJNNOL.js → chunk-J26HHFI5.js} +1 -1
  92. package/chunks/{chunk-CNTR4GR6.js → chunk-JELIWHXD.js} +1 -1
  93. package/chunks/{chunk-V25K5ID7.js → chunk-K2J5JZ6B.js} +1 -1
  94. package/chunks/{chunk-IGTHU3T4.js → chunk-KKN2IE7Q.js} +1 -1
  95. package/chunks/{chunk-TZTAV5RH.js → chunk-KVUBYJDM.js} +2 -2
  96. package/chunks/{chunk-3F2WDAV6.js → chunk-KXWERR6P.js} +14 -14
  97. package/chunks/{chunk-JJPKFJBT.js → chunk-L7FTDKCD.js} +3 -3
  98. package/chunks/chunk-LBOVL47Y.js +29 -0
  99. package/chunks/{chunk-FS674JW4.js → chunk-LBWNSLJK.js} +3 -3
  100. package/chunks/{chunk-I5BT7ADR.js → chunk-LGT3YMLN.js} +4 -4
  101. package/chunks/{chunk-UV6KF7T3.js → chunk-LZV2BZPL.js} +3 -3
  102. package/chunks/{chunk-P755N4CS.js → chunk-M5LIY7Y6.js} +6 -6
  103. package/chunks/{chunk-YGE3IXJP.js → chunk-M744OFVE.js} +1 -1
  104. package/chunks/{chunk-NTDH4IML.js → chunk-MCXMWLFT.js} +7 -7
  105. package/chunks/chunk-MISUEWB4.js +8749 -0
  106. package/chunks/{chunk-TEGJSJDF.js → chunk-MOHAWIAW.js} +2 -2
  107. package/chunks/{chunk-KXDONCPU.js → chunk-MTR4JWQJ.js} +4 -4
  108. package/chunks/{chunk-RSVPYAGK.js → chunk-MUEJ4R3E.js} +5 -5
  109. package/chunks/{chunk-PCK4B6LR.js → chunk-N42C2FU3.js} +1 -1
  110. package/chunks/{chunk-WWCMSZII.js → chunk-NW57GHNL.js} +71 -7
  111. package/chunks/{chunk-PQNHRFJC.js → chunk-NYX53SV4.js} +0 -2
  112. package/chunks/{chunk-QARJNL2V.js → chunk-O2ZYHG33.js} +5 -4
  113. package/chunks/{chunk-V7RNNPGC.js → chunk-P5Y23G2L.js} +18 -1
  114. package/chunks/{chunk-GURGAJTX.js → chunk-PDDYAXL2.js} +4 -1
  115. package/chunks/chunk-PWCQRLM5.js +1170 -0
  116. package/chunks/{chunk-VMX5RZQH.js → chunk-Q3G5L5KF.js} +18 -9
  117. package/chunks/{chunk-DQ2O5QOJ.js → chunk-QDWLUXKP.js} +1 -1
  118. package/chunks/chunk-QL4TN4HS.js +59 -0
  119. package/chunks/{chunk-E6CJ2SMM.js → chunk-QMBEW7EW.js} +2 -2
  120. package/chunks/{chunk-U4VTUMMK.js → chunk-QONSRAEV.js} +1 -1
  121. package/chunks/{chunk-DEEABGH2.js → chunk-QPSYIV2O.js} +3320 -536
  122. package/chunks/{chunk-Q5WZTTWE.js → chunk-QWDVZH24.js} +6 -8
  123. package/chunks/{chunk-2LEOZTBT.js → chunk-QWW6I2UO.js} +111 -26
  124. package/chunks/{chunk-LOY7RWQG.js → chunk-QYPT3QUD.js} +3 -3
  125. package/chunks/{chunk-6PQPKTXP.js → chunk-R7XNRHYD.js} +1 -1
  126. package/chunks/{chunk-7SJN2IWT.js → chunk-RCXCGZG6.js} +5 -5
  127. package/chunks/{chunk-HX6RLYUO.js → chunk-RL6J3BPE.js} +4 -4
  128. package/chunks/{chunk-Z3S5EMOW.js → chunk-RPYYTL7E.js} +60 -12
  129. package/chunks/{chunk-MB32MS4U.js → chunk-RR224BUP.js} +215 -174
  130. package/chunks/{chunk-H6I4PGRV.js → chunk-SKSF3LGQ.js} +4 -4
  131. package/chunks/{chunk-77V3VIKL.js → chunk-SQ3YD5MI.js} +9 -9
  132. package/chunks/{chunk-CBBP4BMC.js → chunk-SRY26X4H.js} +72 -206
  133. package/chunks/{chunk-RDJOB6S3.js → chunk-TXABHVZD.js} +1 -1
  134. package/chunks/{chunk-R2XWGR75.js → chunk-TYERNYH4.js} +2 -2
  135. package/chunks/chunk-TZTBZ25J.js +222 -0
  136. package/chunks/{chunk-45IOTAUS.js → chunk-U25CMJYY.js} +1 -1
  137. package/chunks/{chunk-7ULQIS27.js → chunk-UEJESCS7.js} +1 -1
  138. package/chunks/{chunk-HC45LTEN.js → chunk-UH7Z7Y45.js} +1 -1
  139. package/chunks/{chunk-CPZAH673.js → chunk-UKZEXS6K.js} +2 -0
  140. package/chunks/{chunk-54YCNSK5.js → chunk-UQDFEU47.js} +4113 -10152
  141. package/chunks/{chunk-XP4L6KS3.js → chunk-URN76TKR.js} +2 -2
  142. package/chunks/{chunk-XUGZYLDH.js → chunk-UWSXFI6E.js} +1 -1
  143. package/chunks/{chunk-AOHY3QJ3.js → chunk-VA5NLVPT.js} +3 -3
  144. package/chunks/{chunk-PZEXDV3H.js → chunk-VUNRNRN6.js} +71 -43
  145. package/chunks/{chunk-FANRZEAJ.js → chunk-VWOVHOTT.js} +1 -1
  146. package/chunks/{chunk-QTHQ6Z5X.js → chunk-WK4TJUEA.js} +1 -1
  147. package/chunks/{chunk-ZPZ7ZJEE.js → chunk-WUC6ZFXO.js} +1 -1
  148. package/chunks/{chunk-7KAXXBER.js → chunk-WY2ENRDN.js} +3 -3
  149. package/chunks/{chunk-TIRRPCFS.js → chunk-X6QNZOTC.js} +1 -1
  150. package/chunks/{chunk-KWHKHSIS.js → chunk-X6WUYMOV.js} +8 -8
  151. package/chunks/{chunk-IIUGIWWY.js → chunk-XC2VAFVZ.js} +1 -1
  152. package/chunks/{chunk-D4HTUMGX.js → chunk-XC656O42.js} +3 -3
  153. package/chunks/{chunk-RG64CDT2.js → chunk-XMJO6C44.js} +131 -48
  154. package/chunks/{chunk-ZMOJ7ISP.js → chunk-XUMAD5IT.js} +1 -1
  155. package/chunks/{chunk-THVU3RNH.js → chunk-YAVY33G4.js} +1 -1
  156. package/chunks/{chunk-YANIWTRZ.js → chunk-YPUHMQXV.js} +1 -1
  157. package/chunks/{chunk-QCXGC7QB.js → chunk-YU6TBRTA.js} +3 -3
  158. package/chunks/{chunk-OFZKFPYG.js → chunk-YWMDTMAU.js} +3 -3
  159. package/chunks/{chunk-DSFHVTWD.js → chunk-YXEVDA66.js} +2 -2
  160. package/chunks/{chunk-JE2CEOBO.js → chunk-ZGSUFIL5.js} +4 -4
  161. package/chunks/{chunk-BBIQAX4V.js → chunk-ZJES4C3I.js} +30 -18
  162. package/chunks/{computer-use-5QWCFNKD.js → computer-use-UJMK6JQU.js} +36 -34
  163. package/chunks/{config-utils-WADIDWGG.js → config-utils-7COTJMWK.js} +4 -4
  164. package/chunks/{contextCommand-4Z7T7TUM.js → contextCommand-4YABGF4S.js} +40 -38
  165. package/chunks/{core-runtime-6N4EFC2Q.js → core-runtime-7M4VM3DA.js} +36 -34
  166. package/chunks/{create-sub-session-AEREHZBQ.js → create-sub-session-HOFDW4N7.js} +39 -37
  167. package/chunks/{create-sub-session-62YZWVAY.js → create-sub-session-J32WC64U.js} +2 -2
  168. package/chunks/{cron-create-6E7GIQTG.js → cron-create-HMFSHIAR.js} +4 -4
  169. package/chunks/{cron-delete-JD26XY3W.js → cron-delete-ZJFHOX57.js} +4 -4
  170. package/chunks/{cron-list-CUWFB44G.js → cron-list-ZEX2NBMN.js} +4 -4
  171. package/chunks/{daemon-PCNCI5YU.js → daemon-CCIX4BMG.js} +47 -6
  172. package/chunks/{daemon-status-provider-DDF7C5M7.js → daemon-status-provider-W2U5Q3J5.js} +47 -45
  173. package/chunks/{daemon-trust-policy-Q4KSOJTR.js → daemon-trust-policy-IFICWPXP.js} +42 -40
  174. package/chunks/{daemon-trust-policy-monitor-V6FTE3IY.js → daemon-trust-policy-monitor-GX7EO6FW.js} +42 -40
  175. package/chunks/{de-SY6O76BF.js → de-A6TI4LBB.js} +46 -5
  176. package/chunks/{deferred-core-runtime-GHJ5GQHK.js → deferred-core-runtime-U36OOOKE.js} +36 -34
  177. package/chunks/display-image-VS3TXYQH.js +184 -0
  178. package/chunks/{dist-ZHXOBXDN.js → dist-3BMEUGTG.js} +1 -1
  179. package/chunks/{dist-26BIMQT6.js → dist-4KAX7CCL.js} +289 -27
  180. package/chunks/{dist-OAOC5OV4.js → dist-7JGAAQSC.js} +2 -2
  181. package/chunks/{dist-SL2LUMML.js → dist-A7H2KKUC.js} +1 -1
  182. package/chunks/{dist-2PERFI23.js → dist-DPPHJAVL.js} +3 -3
  183. package/chunks/{dist-PVEAFLHY.js → dist-HVBMEYWK.js} +1 -1
  184. package/chunks/{dist-ULIG4M5H.js → dist-NEBRI7WO.js} +67 -15
  185. package/chunks/{dist-WGMNT3JQ.js → dist-SFQ34F4M.js} +24 -6
  186. package/chunks/{earlyInputCapture-TQXLLISD.js → earlyInputCapture-5ZWDH3SB.js} +37 -35
  187. package/chunks/{edit-FJEUV6KZ.js → edit-J7SW6UP6.js} +36 -34
  188. package/chunks/{en-NBK3JKCE.js → en-L4UQDLWW.js} +57 -6
  189. package/chunks/{enter-worktree-7GE3TZ4P.js → enter-worktree-NRLOHRB6.js} +36 -34
  190. package/chunks/{enterPlanMode-KR5TMPCJ.js → enterPlanMode-GDSTEFSM.js} +36 -34
  191. package/chunks/{environment-SWL3CMB3.js → environment-KHYEKG5W.js} +39 -37
  192. package/chunks/{errors-XQE7BGZV.js → errors-VNLAJV3O.js} +39 -37
  193. package/chunks/{exit-worktree-CHKTYNCV.js → exit-worktree-ARVID7DF.js} +36 -34
  194. package/chunks/{exitPlanMode-QYAG6LWX.js → exitPlanMode-4ZFY7KUH.js} +36 -34
  195. package/chunks/{fast-path-Z2K7W6DC.js → fast-path-KA65VREJ.js} +9 -5
  196. package/chunks/{fr-4KG5V7UT.js → fr-QF7RI5ZE.js} +46 -5
  197. package/chunks/{gemini-YU4XKEDG.js → gemini-5PMCNPF7.js} +149 -90
  198. package/chunks/{geminiContentGenerator-ZZX6NBO4.js → geminiContentGenerator-NFSYJ4HA.js} +6 -6
  199. package/chunks/{glob-VYF5CXSX.js → glob-F3EAUDAX.js} +36 -34
  200. package/chunks/{goal-tools-HGO6VXPB.js → goal-tools-SPQ5M6D3.js} +3 -3
  201. package/chunks/{grep-5DP5ZUTW.js → grep-UJMPVZER.js} +36 -34
  202. package/chunks/{handleAutoUpdate-UVBJ56DL.js → handleAutoUpdate-MG4FRZPN.js} +42 -40
  203. package/chunks/{i18n-UQNYHEUD.js → i18n-EVHV4ZF3.js} +38 -36
  204. package/chunks/{image-gen-WHPIZNWF.js → image-gen-UC7XVHI7.js} +9 -9
  205. package/chunks/{initializer-GMIICF4W.js → initializer-O5B6HUBI.js} +43 -41
  206. package/chunks/{installationInfo-4MGG4WVI.js → installationInfo-2LIKCNVX.js} +37 -35
  207. package/chunks/{ja-F3GSQXMF.js → ja-URQESVGW.js} +46 -5
  208. package/chunks/{keychain-token-storage-IKO4G53C.js → keychain-token-storage-7BT4TQ2A.js} +2 -2
  209. package/chunks/{kittyProtocolDetector-MBRTDRBK.js → kittyProtocolDetector-BFLAKYXH.js} +1 -1
  210. package/chunks/{list-CPN2W334.js → list-BPKIE3Z4.js} +45 -43
  211. package/chunks/{list-agents-PXJ7ZUFB.js → list-agents-TWEGRJWO.js} +2 -2
  212. package/chunks/{loadedSettingsAdapter-ZQAV5NDO.js → loadedSettingsAdapter-NGLFF4V4.js} +42 -40
  213. package/chunks/{loggingContentGenerator-5E7W4IEP.js → loggingContentGenerator-7TFESRG5.js} +52 -37
  214. package/chunks/{loop-wakeup-Y26NQZ5I.js → loop-wakeup-6LLEOQYV.js} +5 -5
  215. package/chunks/{ls-ONQTTMQR.js → ls-H5WX22VK.js} +4 -4
  216. package/chunks/{lsp-YGHHRGMD.js → lsp-G6FXYEIS.js} +2 -2
  217. package/chunks/{managed-npm-update-MKKAXYXA.js → managed-npm-update-CLZGGV7F.js} +39 -37
  218. package/chunks/{mcp-V5NTJF6X.js → mcp-X5OZOOXX.js} +42 -40
  219. package/chunks/{monitor-CO3ASY6E.js → monitor-TGSVSAZZ.js} +36 -34
  220. package/chunks/nonInteractiveCli-GUMJ5JLG.js +150 -0
  221. package/chunks/{notebook-edit-Z7AEE6FM.js → notebook-edit-EZISE7IC.js} +36 -34
  222. package/chunks/{openaiContentGenerator-CA3MQOP3.js → openaiContentGenerator-ZAKS5A6P.js} +18 -18
  223. package/chunks/{pidfile-NL7MKDRU.js → pidfile-PCMBX4LR.js} +37 -35
  224. package/chunks/{processUtils-PB5K2DYH.js → processUtils-2CSVE5BW.js} +2 -2
  225. package/chunks/{pt-EQ33KWEN.js → pt-EEEQXRUI.js} +46 -5
  226. package/chunks/{qwenContentGenerator-JYFNDCOS.js → qwenContentGenerator-S3H6PIC4.js} +42 -40
  227. package/chunks/{qwenOAuth2-W2ALKQDD.js → qwenOAuth2-MNU425VE.js} +8 -8
  228. package/chunks/{read-file-IEOR5H5W.js → read-file-HXJXHBI6.js} +10 -10
  229. package/chunks/{read-mcp-resource-U2IJN5HM.js → read-mcp-resource-DRZSI3HE.js} +2 -2
  230. package/chunks/{record-artifact-MGPJ7EG4.js → record-artifact-IPIRWMFY.js} +3 -3
  231. package/chunks/{resumeHistoryUtils-BNBTP5AS.js → resumeHistoryUtils-4QAMC2S6.js} +44 -42
  232. package/chunks/{ripGrep-UMQODKEB.js → ripGrep-MCETPF2E.js} +36 -34
  233. package/chunks/{ru-7OZAUKGG.js → ru-WGZDYQAB.js} +46 -5
  234. package/chunks/{run-qwen-serve-RN2R6QQW.js → run-qwen-serve-3NMODTXV.js} +219 -55
  235. package/chunks/{runtime-QFJ2MJF5.js → runtime-AQOV7DVP.js} +49 -45
  236. package/chunks/{scheduler-AMYUFOZI.js → scheduler-DN7SZ6ZE.js} +36 -34
  237. package/chunks/{sdk-exporters-http-4BQANSJ6.js → sdk-exporters-http-IKNWQJNV.js} +2 -2
  238. package/chunks/{sdk-impl-Z4VXWVFX.js → sdk-impl-7CUUMQDB.js} +2 -2
  239. package/chunks/{send-message-PMP4ATVM.js → send-message-HGCVVDTZ.js} +5 -4
  240. package/chunks/{serve-EIIYJVX3.js → serve-WZB45HVT.js} +42 -40
  241. package/chunks/{server-EP2LLAPD.js → server-GOTZFEEZ.js} +616 -387
  242. package/chunks/{session-GMPZBLOW.js → session-23LOFLWN.js} +149 -87
  243. package/chunks/{settings-LN7RNKBS.js → settings-LI2ORVXC.js} +41 -39
  244. package/chunks/{shell-EM2OWU5X.js → shell-GLC6OP3C.js} +36 -34
  245. package/chunks/{skill-T5VPSK73.js → skill-C7W6M4GO.js} +27 -14
  246. package/chunks/{skill-settings-GMR3GMNY.js → skill-settings-W25NCK5I.js} +41 -39
  247. package/chunks/{spawnChannel-YPCU62DI.js → spawnChannel-OHJLEBLD.js} +38 -36
  248. package/chunks/{standalone-update-JAKIM2NF.js → standalone-update-E4V5V7WT.js} +39 -37
  249. package/chunks/{startInteractiveUI-YETQWGYF.js → startInteractiveUI-FKILLW7Q.js} +1815 -1034
  250. package/chunks/{syntheticOutput-D7E6ZFKR.js → syntheticOutput-EYL7DWO7.js} +3 -3
  251. package/chunks/{task-create-NFCYD43L.js → task-create-CSCEG4SM.js} +10 -9
  252. package/chunks/{task-list-TAVMGEVD.js → task-list-6CVQPORX.js} +6 -6
  253. package/chunks/{task-stop-ZDF2UJRM.js → task-stop-Y5ZTFVRX.js} +2 -2
  254. package/chunks/{task-update-6L7LHGEX.js → task-update-C52LNFU5.js} +10 -9
  255. package/chunks/{team-create-LFJ6HGNY.js → team-create-HBIJ7NHT.js} +36 -34
  256. package/chunks/{team-delete-DPGGRQBU.js → team-delete-CE7DC53R.js} +6 -6
  257. package/chunks/{team-plan-approval-C2KLSDGO.js → team-plan-approval-IBJOKS4J.js} +36 -34
  258. package/chunks/terminal-image-renderer-BOQJPVLB.js +89 -0
  259. package/chunks/{theme-manager-JQ3ZGBP5.js → theme-manager-NSS7VLUD.js} +37 -35
  260. package/chunks/{todoWrite-2UO5G6G6.js → todoWrite-YKAA57W2.js} +107 -36
  261. package/chunks/{tool-search-FAZBFRJZ.js → tool-search-DEAS54VG.js} +13 -12
  262. package/chunks/{total-session-admission-5CALIVYO.js → total-session-admission-4LWTX3WD.js} +43 -41
  263. package/chunks/{trustedFolders-KOPSP44S.js → trustedFolders-NF3J2TA7.js} +38 -36
  264. package/chunks/{update-relaunch-6QL2TICS.js → update-relaunch-WUHRH5V3.js} +5 -5
  265. package/chunks/{updateCheck-4Q6RIKFW.js → updateCheck-7EG4BLBH.js} +41 -39
  266. package/chunks/{useAutoAcceptIndicator-Q5BMKZV3.js → useAutoAcceptIndicator-ZRV56GVP.js} +46 -44
  267. package/chunks/{validateNonInterActiveAuth-GKOC5F6Z.js → validateNonInterActiveAuth-WC2FPE3I.js} +86 -84
  268. package/chunks/{version-VSATY2LD.js → version-6SS7KCUP.js} +1 -1
  269. package/chunks/{web-fetch-JCLKL6NL.js → web-fetch-Y5QQMPKA.js} +13 -13
  270. package/chunks/{web-search-DSQTFUBX.js → web-search-QKKEU723.js} +8 -8
  271. package/chunks/{workflow-M6QAVKVJ.js → workflow-PWIU6Q2M.js} +418 -304
  272. package/chunks/{workspace-providers-status-YKLTLMJA.js → workspace-providers-status-B2DM6FNK.js} +45 -43
  273. package/chunks/{workspace-registration-store-5DMGKU64.js → workspace-registration-store-7Y33TTHJ.js} +2 -2
  274. package/chunks/{workspace-registry-PTYUSYXX.js → workspace-registry-TSNJKLPC.js} +43 -41
  275. package/chunks/{workspace-service-25ML3T47.js → workspace-service-4I2QHDX4.js} +50 -48
  276. package/chunks/{workspace-skills-status-YKZWOYRX.js → workspace-skills-status-HVXNWVGR.js} +44 -42
  277. package/chunks/{workspace-trust-reconciler-5FI2S3OS.js → workspace-trust-reconciler-RDBBGMWP.js} +49 -47
  278. package/chunks/{write-file-AY7VLP5X.js → write-file-RQ4DG5RY.js} +36 -34
  279. package/chunks/{zh-H3YDK5QW.js → zh-72XPUG5P.js} +54 -6
  280. package/chunks/{zh-TW-JYCP7XUA.js → zh-TW-LTB4SRH3.js} +54 -6
  281. package/chunks/{zoom-image-S4LJZMD5.js → zoom-image-O5YCB7WN.js} +10 -10
  282. package/cli.js +32 -48
  283. package/locales/ca.js +60 -4
  284. package/locales/de.js +62 -4
  285. package/locales/en.js +76 -6
  286. package/locales/fr.js +64 -4
  287. package/locales/ja.js +61 -4
  288. package/locales/pt.js +60 -4
  289. package/locales/ru.js +63 -4
  290. package/locales/zh-TW.js +67 -6
  291. package/locales/zh.js +67 -6
  292. package/package.json +3 -3
  293. package/web-shell/assets/{arc-xjArKHU2.js → arc-Bd7d781i.js} +1 -1
  294. package/web-shell/assets/{architectureDiagram-3BPJPVTR-BgrQDNYG.js → architectureDiagram-3BPJPVTR-CpnOJqNE.js} +1 -1
  295. package/web-shell/assets/{blockDiagram-GPEHLZMM-CXxWC-1_.js → blockDiagram-GPEHLZMM-NxVTiWnk.js} +1 -1
  296. package/web-shell/assets/{c4Diagram-AAUBKEIU-Dx1tLm3v.js → c4Diagram-AAUBKEIU-BWT75l82.js} +1 -1
  297. package/web-shell/assets/channel-A434sWK5.js +1 -0
  298. package/web-shell/assets/{chunk-2J33WTMH-CBxjogVR.js → chunk-2J33WTMH-Dzz03hk6.js} +1 -1
  299. package/web-shell/assets/{chunk-4BX2VUAB-izb-rY5W.js → chunk-4BX2VUAB-DRF-egfm.js} +1 -1
  300. package/web-shell/assets/{chunk-55IACEB6-NzLyvSb8.js → chunk-55IACEB6-Co15vq5x.js} +1 -1
  301. package/web-shell/assets/{chunk-727SXJPM-DVyKb0dl.js → chunk-727SXJPM-DHSUk1J7.js} +1 -1
  302. package/web-shell/assets/{chunk-AQP2D5EJ-BtqL7NzB.js → chunk-AQP2D5EJ-Br8u_E2R.js} +1 -1
  303. package/web-shell/assets/{chunk-FMBD7UC4-iNpdKIO0.js → chunk-FMBD7UC4-DXbPTnqF.js} +1 -1
  304. package/web-shell/assets/{chunk-ND2GUHAM-DI7cbFmX.js → chunk-ND2GUHAM-BleQdi4d.js} +1 -1
  305. package/web-shell/assets/{chunk-QZHKN3VN-3Y_-xFYs.js → chunk-QZHKN3VN-CqgBf-95.js} +1 -1
  306. package/web-shell/assets/classDiagram-4FO5ZUOK-CwPuhldB.js +1 -0
  307. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-CwPuhldB.js +1 -0
  308. package/web-shell/assets/{cose-bilkent-S5V4N54A--raRatZn.js → cose-bilkent-S5V4N54A-WS7b13Yg.js} +1 -1
  309. package/web-shell/assets/{dagre-BM42HDAG-B8SU70mO.js → dagre-BM42HDAG-v_55ki8o.js} +1 -1
  310. package/web-shell/assets/{diagram-2AECGRRQ-ewgoakXo.js → diagram-2AECGRRQ-DaYAjbdK.js} +1 -1
  311. package/web-shell/assets/{diagram-5GNKFQAL-Bm_JLTnS.js → diagram-5GNKFQAL-D_Hy-L64.js} +1 -1
  312. package/web-shell/assets/{diagram-KO2AKTUF-DEIUjUy9.js → diagram-KO2AKTUF-dB06cZyD.js} +1 -1
  313. package/web-shell/assets/{diagram-LMA3HP47-C2Wmrpj-.js → diagram-LMA3HP47-DzSluD4F.js} +1 -1
  314. package/web-shell/assets/{diagram-OG6HWLK6-CtYe6bFx.js → diagram-OG6HWLK6-Cvb6CeXQ.js} +1 -1
  315. package/web-shell/assets/{erDiagram-TEJ5UH35-DfpHcWLV.js → erDiagram-TEJ5UH35-7eB2Hmov.js} +1 -1
  316. package/web-shell/assets/{flowDiagram-I6XJVG4X-CAiUhio5.js → flowDiagram-I6XJVG4X-B08iGCw_.js} +1 -1
  317. package/web-shell/assets/{ganttDiagram-6RSMTGT7-CYc7ezr4.js → ganttDiagram-6RSMTGT7-BgsrCHq-.js} +1 -1
  318. package/web-shell/assets/{gitGraphDiagram-PVQCEYII-70lbkcRS.js → gitGraphDiagram-PVQCEYII-DQLurQ2i.js} +1 -1
  319. package/web-shell/assets/{index-Ww86nT9f.js → index-B_1Z0Mgr.js} +1 -1
  320. package/web-shell/assets/index-Bi1dP2mU.css +5 -0
  321. package/web-shell/assets/index-DJ-z1Oba.js +1756 -0
  322. package/web-shell/assets/{infoDiagram-5YYISTIA-C7e8DWPa.js → infoDiagram-5YYISTIA-DXZGO8qB.js} +1 -1
  323. package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-BYLr2op-.js → ishikawaDiagram-YF4QCWOH-g7gbuH2w.js} +1 -1
  324. package/web-shell/assets/{journeyDiagram-JHISSGLW-hEibAHKX.js → journeyDiagram-JHISSGLW-euefICGn.js} +1 -1
  325. package/web-shell/assets/{kanban-definition-UN3LZRKU-F2rt8Uhp.js → kanban-definition-UN3LZRKU-Dws8-mjG.js} +1 -1
  326. package/web-shell/assets/{linear-DPhyNezd.js → linear-C63F0tyh.js} +1 -1
  327. package/web-shell/assets/{mermaid.core-ZUtnug0n.js → mermaid.core-fLrhp_4-.js} +5 -5
  328. package/web-shell/assets/{mindmap-definition-RKZ34NQL-DDWcMoi8.js → mindmap-definition-RKZ34NQL-qRkydfT-.js} +1 -1
  329. package/web-shell/assets/{pieDiagram-4H26LBE5-CTRKzw1_.js → pieDiagram-4H26LBE5-BZ0wQaKN.js} +1 -1
  330. package/web-shell/assets/{quadrantDiagram-W4KKPZXB-CAEwYG_z.js → quadrantDiagram-W4KKPZXB-Fjq6GbSZ.js} +1 -1
  331. package/web-shell/assets/{requirementDiagram-4Y6WPE33-CRjnH9D9.js → requirementDiagram-4Y6WPE33-CqWvjLHU.js} +1 -1
  332. package/web-shell/assets/{sankeyDiagram-5OEKKPKP-LPQT_sOd.js → sankeyDiagram-5OEKKPKP-VcXX6PVZ.js} +1 -1
  333. package/web-shell/assets/{sequenceDiagram-3UESZ5HK-Ht2Iqvb0.js → sequenceDiagram-3UESZ5HK-BJSRtxFr.js} +1 -1
  334. package/web-shell/assets/{stateDiagram-AJRCARHV-CIUdqOXu.js → stateDiagram-AJRCARHV-Dt9zUwmZ.js} +1 -1
  335. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-CCxQt56w.js +1 -0
  336. package/web-shell/assets/{timeline-definition-PNZ67QCA-BcilneZF.js → timeline-definition-PNZ67QCA-DQwlNRws.js} +1 -1
  337. package/web-shell/assets/{vennDiagram-CIIHVFJN-BljwiiH1.js → vennDiagram-CIIHVFJN-DwxjUEtQ.js} +1 -1
  338. package/web-shell/assets/{wardley-L42UT6IY-DeKjkYjq.js → wardley-L42UT6IY-D3QdMSpJ.js} +1 -1
  339. package/web-shell/assets/{wardleyDiagram-YWT4CUSO-L3U1H7KG.js → wardleyDiagram-YWT4CUSO-BddN8oK8.js} +1 -1
  340. package/web-shell/assets/{xychartDiagram-2RQKCTM6-IJnWsgMT.js → xychartDiagram-2RQKCTM6-BUNuBrRJ.js} +1 -1
  341. package/web-shell/index.html +2 -2
  342. package/chunks/chunk-4KP2F2TE.js +0 -82
  343. package/chunks/chunk-HGT6JR3U.js +0 -177
  344. package/chunks/nonInteractiveCli-ZJYEOO7U.js +0 -148
  345. package/web-shell/assets/channel-B1cyk94G.js +0 -1
  346. package/web-shell/assets/classDiagram-4FO5ZUOK-BpBYZoRE.js +0 -1
  347. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-BpBYZoRE.js +0 -1
  348. package/web-shell/assets/index-C1k3PDuU.css +0 -5
  349. package/web-shell/assets/index-DZLIXILO.js +0 -1750
  350. package/web-shell/assets/insert-DmRbh9Rv.svg +0 -1
  351. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-BmqqQZ6w.js +0 -1
@@ -451,1167 +451,1630 @@ function readId(args) {
451
451
  }
452
452
  __name(readId, "readId");
453
453
 
454
- // packages/channels/base/dist/index.js
455
- init_esbuild_shims();
456
-
457
- // packages/channels/base/dist/PollingChannelBase.js
458
- init_esbuild_shims();
459
- import { createHash as createHash2 } from "node:crypto";
460
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, renameSync as renameSync4, writeFileSync as writeFileSync4 } from "node:fs";
461
- import { join as join6 } from "node:path";
462
- import process3 from "node:process";
463
-
464
- // packages/channels/base/dist/ChannelBase.js
454
+ // packages/channels/base/dist/AcpBridge.js
465
455
  init_esbuild_shims();
466
- import { basename as basename3, join as join5 } from "node:path";
456
+ import { spawn } from "node:child_process";
467
457
  import { randomUUID } from "node:crypto";
468
-
469
- // packages/channels/base/dist/BlockStreamer.js
470
- init_esbuild_shims();
471
- var BlockStreamer = class {
458
+ import { Readable, Writable } from "node:stream";
459
+ import { EventEmitter } from "node:events";
460
+ var MID_TURN_QUEUE_DRAIN_METHOD = "craft/drainMidTurnQueue";
461
+ var TODO_STOP_GUARD_CONTINUATION_CLAIM_METHOD = "craft/claimTodoStopGuardContinuation";
462
+ var ACP_EVENT_LOOP_STALL_RESTART_MS = 5 * 60 * 1e3;
463
+ var ACP_START_TIMEOUT_MS = 30 * 1e3;
464
+ var ACP_PERMISSION_RESPONSE_TIMEOUT_MS = 5 * 60 * 1e3;
465
+ var ACP_EVENT_LOOP_STALL_RE = /^\[perf\] acp agent event loop stall: max=(\d+(?:\.\d+)?)ms/m;
466
+ function readAvailableCommandAltNames(raw) {
467
+ if (typeof raw !== "object" || raw === null)
468
+ return void 0;
469
+ const record = raw;
470
+ const meta = record["_meta"];
471
+ const fromMeta = typeof meta === "object" && meta !== null ? meta["altNames"] : void 0;
472
+ const source = Array.isArray(record["altNames"]) ? record["altNames"] : Array.isArray(fromMeta) ? fromMeta : void 0;
473
+ if (!source)
474
+ return void 0;
475
+ const names = source.filter((n) => typeof n === "string");
476
+ return names.length > 0 ? names : void 0;
477
+ }
478
+ __name(readAvailableCommandAltNames, "readAvailableCommandAltNames");
479
+ var AcpBridge = class extends EventEmitter {
472
480
  static {
473
- __name(this, "BlockStreamer");
481
+ __name(this, "AcpBridge");
474
482
  }
475
- buffer = "";
476
- idleTimer = null;
477
- sending = Promise.resolve();
478
- opts;
479
- /** Number of blocks emitted so far. */
480
- blockCount = 0;
481
- constructor(opts) {
482
- this.opts = opts;
483
+ child = null;
484
+ connection = null;
485
+ options;
486
+ _availableCommands = [];
487
+ channelLoopMcpServer;
488
+ channelLoopToolHandlers = [];
489
+ knownSessionIds = /* @__PURE__ */ new Set();
490
+ sessionBindingTokens = /* @__PURE__ */ new Map();
491
+ channelLoopMcpRegistered = false;
492
+ channelLoopMcpRegistration = null;
493
+ pendingPermissions = /* @__PURE__ */ new Map();
494
+ constructor(options) {
495
+ super();
496
+ this.options = options;
483
497
  }
484
- /** Feed a new text chunk from the agent stream. */
485
- push(chunk) {
486
- this.buffer += chunk;
487
- this.clearIdleTimer();
488
- this.checkEmit();
489
- if (this.buffer.length > 0 && this.opts.idleMs > 0) {
490
- this.idleTimer = setTimeout(() => this.onIdle(), this.opts.idleMs);
498
+ get availableCommands() {
499
+ return this._availableCommands;
500
+ }
501
+ async start() {
502
+ const { cliEntryPath, cwd } = this.options;
503
+ const args = [
504
+ ...process.execArgv.filter((a) => !/^--inspect(-brk)?($|=)/.test(a)),
505
+ cliEntryPath,
506
+ "--acp"
507
+ ];
508
+ if (this.options.model) {
509
+ args.push("--model", this.options.model);
510
+ }
511
+ this.child = spawn(process.execPath, args, {
512
+ cwd,
513
+ stdio: ["pipe", "pipe", "pipe"],
514
+ env: { ...process.env, QWEN_CODE_DISABLE_CRON: "1" },
515
+ shell: false
516
+ });
517
+ this.child.stderr?.on("data", (data) => {
518
+ const msg = data.toString().trim();
519
+ if (msg) {
520
+ process.stderr.write(`[AcpBridge] ${sanitizeLogText(msg, 4096)}
521
+ `);
522
+ this.maybeKillOnEventLoopStall(msg);
523
+ }
524
+ });
525
+ this.child.on("exit", (code, signal) => {
526
+ process.stderr.write(`[AcpBridge] Process exited (code=${code}, signal=${signal})
527
+ `);
528
+ this.resolvePendingPermissions();
529
+ this.knownSessionIds.clear();
530
+ this.sessionBindingTokens.clear();
531
+ this.connection = null;
532
+ this.child = null;
533
+ this.emit("disconnected", code, signal);
534
+ });
535
+ await new Promise((resolve3) => setTimeout(resolve3, 1e3));
536
+ if (!this.child || this.child.killed) {
537
+ throw new Error("ACP process failed to start");
538
+ }
539
+ const stdout = Readable.toWeb(this.child.stdout);
540
+ const stdin = Writable.toWeb(this.child.stdin);
541
+ const stream = ndJsonStream(stdin, stdout);
542
+ this.connection = new ClientSideConnection(() => ({
543
+ sessionUpdate: /* @__PURE__ */ __name((params) => {
544
+ this.handleSessionUpdate(params);
545
+ return Promise.resolve();
546
+ }, "sessionUpdate"),
547
+ requestPermission: /* @__PURE__ */ __name(async (params) => this.requestPermission(params), "requestPermission"),
548
+ extMethod: /* @__PURE__ */ __name(async (method, params) => this.handleExtMethod(method, params), "extMethod"),
549
+ extNotification: /* @__PURE__ */ __name(async () => {
550
+ }, "extNotification")
551
+ }), stream);
552
+ try {
553
+ await withTimeout(this.connection.initialize({
554
+ protocolVersion: PROTOCOL_VERSION,
555
+ clientCapabilities: {}
556
+ }), ACP_START_TIMEOUT_MS, `ACP initialization timed out after ${ACP_START_TIMEOUT_MS}ms`);
557
+ await this.registerChannelLoopMcpServer();
558
+ } catch (error) {
559
+ this.stop();
560
+ throw error;
491
561
  }
492
562
  }
493
- /** Flush all remaining buffered text. Awaits all pending sends. */
494
- async flush() {
495
- this.clearIdleTimer();
496
- if (this.buffer.length > 0) {
497
- this.emitBlock(this.buffer);
498
- this.buffer = "";
563
+ registerChannelLoopToolHandler(handler) {
564
+ if (!this.channelLoopToolHandlers.includes(handler)) {
565
+ this.channelLoopToolHandlers.push(handler);
499
566
  }
500
- await this.sending;
567
+ this.channelLoopMcpServer ??= new ChannelLoopMcpServer({
568
+ create: /* @__PURE__ */ __name((sessionId, input) => this.resolveChannelLoopToolHandler(sessionId).create(sessionId, input), "create"),
569
+ list: /* @__PURE__ */ __name((sessionId) => this.resolveChannelLoopToolHandler(sessionId).list(sessionId), "list"),
570
+ cancel: /* @__PURE__ */ __name((sessionId, id) => this.resolveChannelLoopToolHandler(sessionId).cancel(sessionId, id), "cancel")
571
+ });
572
+ void this.registerChannelLoopMcpServer();
501
573
  }
502
- /** Drop buffered text and cancel future idle emission. */
503
- stop() {
504
- this.clearIdleTimer();
505
- this.buffer = "";
574
+ async newSession(cwd, _options, bindingToken) {
575
+ const conn = this.ensureConnection();
576
+ await this.registerChannelLoopMcpServer();
577
+ const response = await conn.newSession({ cwd, mcpServers: [] });
578
+ this.knownSessionIds.add(response.sessionId);
579
+ this.sessionBindingTokens.set(response.sessionId, bindingToken);
580
+ return response.sessionId;
506
581
  }
507
- // ---------------------------------------------------------------------------
508
- // Internal
509
- // ---------------------------------------------------------------------------
510
- checkEmit() {
511
- while (this.buffer.length >= this.opts.maxChars) {
512
- const bp = this.findBreakPoint(this.buffer, this.opts.maxChars);
513
- this.emitBlock(this.buffer.slice(0, bp));
514
- this.buffer = this.buffer.slice(bp);
515
- }
516
- if (this.buffer.length >= this.opts.minChars) {
517
- const bp = this.findBlockBoundary(this.buffer);
518
- if (bp > 0) {
519
- this.emitBlock(this.buffer.slice(0, bp));
520
- this.buffer = this.buffer.slice(bp);
582
+ async loadSession(sessionId, cwd, _options, bindingToken) {
583
+ const conn = this.ensureConnection();
584
+ await this.registerChannelLoopMcpServer();
585
+ await conn.loadSession({
586
+ sessionId,
587
+ cwd,
588
+ mcpServers: []
589
+ });
590
+ this.knownSessionIds.add(sessionId);
591
+ this.sessionBindingTokens.set(sessionId, bindingToken);
592
+ return sessionId;
593
+ }
594
+ async prompt(sessionId, text, options) {
595
+ const conn = this.ensureConnection();
596
+ const chunks = [];
597
+ let slashCommandOutput = "";
598
+ const onChunk = /* @__PURE__ */ __name((sid, chunk) => {
599
+ if (sid === sessionId)
600
+ chunks.push(chunk);
601
+ }, "onChunk");
602
+ const onSlashCommandOutput = /* @__PURE__ */ __name((sid, chunk) => {
603
+ if (sid === sessionId)
604
+ slashCommandOutput = chunk;
605
+ }, "onSlashCommandOutput");
606
+ const clearChunks = /* @__PURE__ */ __name((sid) => {
607
+ if (sid === sessionId) {
608
+ chunks.length = 0;
609
+ slashCommandOutput = "";
521
610
  }
611
+ }, "clearChunks");
612
+ this.on("textChunk", onChunk);
613
+ this.on("slashCommandOutput", onSlashCommandOutput);
614
+ this.on("responseBoundary", clearChunks);
615
+ const prompt = [];
616
+ if (options?.imageBase64 && options.imageMimeType) {
617
+ prompt.push({
618
+ type: "image",
619
+ data: options.imageBase64,
620
+ mimeType: options.imageMimeType
621
+ });
622
+ }
623
+ prompt.push({ type: "text", text });
624
+ try {
625
+ await conn.prompt({
626
+ sessionId,
627
+ prompt
628
+ });
629
+ } finally {
630
+ this.off("textChunk", onChunk);
631
+ this.off("slashCommandOutput", onSlashCommandOutput);
632
+ this.off("responseBoundary", clearChunks);
522
633
  }
634
+ return chunks.join("") || slashCommandOutput;
523
635
  }
524
- onIdle() {
525
- this.idleTimer = null;
526
- if (this.buffer.length >= this.opts.minChars) {
527
- this.emitBlock(this.buffer);
528
- this.buffer = "";
636
+ async cancelSession(sessionId) {
637
+ const conn = this.ensureConnection();
638
+ try {
639
+ await conn.cancel({ sessionId });
640
+ } finally {
641
+ this.resolvePendingPermissions(sessionId);
529
642
  }
530
643
  }
531
- emitBlock(text) {
532
- const trimmed = text.trim();
533
- if (!trimmed)
644
+ async discardSession(sessionId, expectedBindingToken) {
645
+ if (expectedBindingToken !== void 0 && this.sessionBindingTokens.get(sessionId) !== expectedBindingToken) {
534
646
  return;
535
- this.blockCount++;
536
- this.sending = this.sending.then(() => this.opts.send(trimmed)).catch(() => {
537
- });
647
+ }
648
+ if (!this.knownSessionIds.delete(sessionId))
649
+ return;
650
+ this.sessionBindingTokens.delete(sessionId);
651
+ this.resolvePendingPermissions(sessionId);
652
+ const conn = this.connection;
653
+ if (!conn || !this.isConnected)
654
+ return;
655
+ await conn.extMethod("qwen/control/session/close", { sessionId });
538
656
  }
539
- /**
540
- * Find the last paragraph boundary (\n\n) in the buffer.
541
- * Returns the position after the boundary, or -1 if no suitable boundary
542
- * exists at or after minChars.
543
- */
544
- findBlockBoundary(text) {
545
- const last = text.lastIndexOf("\n\n");
546
- if (last < 0 || last < this.opts.minChars)
547
- return -1;
548
- return last + 2;
549
- }
550
- /**
551
- * Find the best break point at or before maxPos.
552
- * Prefers paragraph break > newline > space > maxPos.
553
- */
554
- findBreakPoint(text, maxPos) {
555
- const sub = text.slice(0, maxPos);
556
- const para = sub.lastIndexOf("\n\n");
557
- if (para > 0)
558
- return para + 2;
559
- const nl = sub.lastIndexOf("\n");
560
- if (nl > 0)
561
- return nl + 1;
562
- const sp = sub.lastIndexOf(" ");
563
- if (sp > 0)
564
- return sp + 1;
565
- return maxPos;
566
- }
567
- clearIdleTimer() {
568
- if (this.idleTimer !== null) {
569
- clearTimeout(this.idleTimer);
570
- this.idleTimer = null;
657
+ async respondToPermission(requestId, response) {
658
+ const pending = this.pendingPermissions.get(requestId);
659
+ if (!pending) {
660
+ return false;
571
661
  }
662
+ clearTimeout(pending.timeout);
663
+ this.pendingPermissions.delete(requestId);
664
+ pending.resolve(response);
665
+ this.emit("permissionResolved", {
666
+ requestId,
667
+ outcome: response.outcome
668
+ });
669
+ return true;
572
670
  }
573
- };
574
-
575
- // packages/channels/base/dist/ChannelProactiveDeliveryError.js
576
- init_esbuild_shims();
577
- var CHANNEL_PROACTIVE_DELIVERY_ERROR_CODE = "channel_proactive_delivery_error";
578
- var ChannelProactiveDeliveryError = class extends Error {
579
- static {
580
- __name(this, "ChannelProactiveDeliveryError");
581
- }
582
- disposition;
583
- code = CHANNEL_PROACTIVE_DELIVERY_ERROR_CODE;
584
- constructor(disposition, message, options) {
585
- super(message, options);
586
- this.disposition = disposition;
587
- this.name = "ChannelProactiveDeliveryError";
588
- }
589
- };
590
- function isChannelProactiveDeliveryError(error) {
591
- if (typeof error !== "object" || error === null)
592
- return false;
593
- const candidate = error;
594
- return candidate.code === CHANNEL_PROACTIVE_DELIVERY_ERROR_CODE && (candidate.disposition === "permanent" || candidate.disposition === "transient") && typeof candidate.message === "string";
595
- }
596
- __name(isChannelProactiveDeliveryError, "isChannelProactiveDeliveryError");
597
-
598
- // packages/channels/base/dist/GroupGate.js
599
- init_esbuild_shims();
600
- var GroupGate = class {
601
- static {
602
- __name(this, "GroupGate");
671
+ stop() {
672
+ this.resolvePendingPermissions();
673
+ this.knownSessionIds.clear();
674
+ this.sessionBindingTokens.clear();
675
+ if (this.child) {
676
+ this.child.kill();
677
+ this.child = null;
678
+ }
679
+ this.connection = null;
603
680
  }
604
- policy;
605
- groups;
606
- constructor(policy = "disabled", groups = {}) {
607
- this.policy = policy;
608
- this.groups = groups;
681
+ get isConnected() {
682
+ return this.child !== null && !this.child.killed && this.child.exitCode === null;
609
683
  }
610
- /**
611
- * Full group check: policy + allowlist + mention gating.
612
- * Evaluation order:
613
- * 1. groupPolicy (disabled → drop)
614
- * 2. group allowlist (allowlist mode, no match → drop)
615
- * 3. mention gating (requireMention + not mentioned → drop silently)
616
- *
617
- * Mention gating runs before sender gate so that unmentioned messages
618
- * in groups don't trigger pairing flows.
619
- */
620
- check(envelope) {
621
- if (!envelope.isGroup) {
622
- return { allowed: true };
623
- }
624
- if (this.policy === "disabled") {
625
- return { allowed: false, reason: "disabled" };
626
- }
627
- if (this.policy === "allowlist") {
628
- if (!this.groups[envelope.chatId]) {
629
- return { allowed: false, reason: "not_allowlisted" };
684
+ handleSessionUpdate(params) {
685
+ const { sessionId } = params;
686
+ const update = params["update"];
687
+ if (!update)
688
+ return;
689
+ const type = update["sessionUpdate"];
690
+ switch (type) {
691
+ case "agent_message_chunk": {
692
+ const meta = update["_meta"];
693
+ if (typeof meta?.["parentToolCallId"] === "string") {
694
+ break;
695
+ }
696
+ const content = update["content"];
697
+ if (meta?.["qwenDiscreteMessage"] === true) {
698
+ if (meta["source"] === "background_notification_response" && meta["rewritten"] !== true && content?.type === "text" && content.text) {
699
+ this.emit("backgroundResponse", sessionId, content.text);
700
+ }
701
+ break;
702
+ }
703
+ if (content?.type === "text" && content.text) {
704
+ this.emit(meta?.["source"] === "slash_command" ? "slashCommandOutput" : "textChunk", sessionId, content.text);
705
+ }
706
+ break;
707
+ }
708
+ case "tool_call": {
709
+ const event = {
710
+ sessionId,
711
+ toolCallId: update["toolCallId"],
712
+ kind: update["kind"] || "",
713
+ title: update["title"] || "",
714
+ status: update["status"] || "pending",
715
+ rawInput: update["rawInput"]
716
+ };
717
+ if (event.status === "pending" || event.status === "in_progress") {
718
+ this.emitResponseBoundary(sessionId);
719
+ }
720
+ this.emit("toolCall", event);
721
+ break;
722
+ }
723
+ case "plan": {
724
+ this.emitResponseBoundary(sessionId);
725
+ break;
726
+ }
727
+ case "available_commands_update": {
728
+ if (Array.isArray(update["availableCommands"])) {
729
+ this._availableCommands = update["availableCommands"].map((cmd) => {
730
+ const altNames = readAvailableCommandAltNames(cmd);
731
+ return altNames ? { ...cmd, altNames } : cmd;
732
+ });
733
+ }
734
+ break;
630
735
  }
736
+ default:
737
+ break;
631
738
  }
632
- const groupConfig = this.groups[envelope.chatId] || this.groups["*"] || {};
633
- const requireMention = groupConfig.requireMention ?? true;
634
- if (requireMention && !envelope.isMentioned && !envelope.isReplyToBot) {
635
- return { allowed: false, reason: "mention_required" };
739
+ this.emit("sessionUpdate", params);
740
+ }
741
+ ensureConnection() {
742
+ if (!this.connection || !this.isConnected) {
743
+ throw new Error("Not connected to ACP agent");
636
744
  }
637
- return { allowed: true };
745
+ return this.connection;
638
746
  }
639
- };
640
-
641
- // packages/channels/base/dist/DmGate.js
642
- init_esbuild_shims();
643
- var DmGate = class {
644
- static {
645
- __name(this, "DmGate");
747
+ requestPermission(request) {
748
+ const requestId = `acp-permission-${randomUUID()}`;
749
+ const sessionId = typeof request.sessionId === "string" && request.sessionId.length > 0 ? request.sessionId : request.toolCall.toolCallId;
750
+ return new Promise((resolve3) => {
751
+ const timeout = setTimeout(() => {
752
+ const pending = this.pendingPermissions.get(requestId);
753
+ if (!pending) {
754
+ return;
755
+ }
756
+ process.stderr.write(`[AcpBridge] permission request ${sanitizeLogText(requestId, 128)} timed out after ${ACP_PERMISSION_RESPONSE_TIMEOUT_MS}ms (session=${sanitizeLogText(pending.sessionId, 128)})
757
+ `);
758
+ this.pendingPermissions.delete(requestId);
759
+ const response = {
760
+ outcome: { outcome: "cancelled" }
761
+ };
762
+ pending.resolve(response);
763
+ this.emit("permissionResolved", {
764
+ requestId,
765
+ outcome: response.outcome
766
+ });
767
+ }, ACP_PERMISSION_RESPONSE_TIMEOUT_MS);
768
+ timeout.unref?.();
769
+ this.pendingPermissions.set(requestId, { sessionId, resolve: resolve3, timeout });
770
+ this.emitResponseBoundary(sessionId);
771
+ this.emit("permissionRequest", {
772
+ requestId,
773
+ sessionId,
774
+ request
775
+ });
776
+ });
646
777
  }
647
- policy;
648
- constructor(policy = "open") {
649
- this.policy = policy;
778
+ emitResponseBoundary(sessionId) {
779
+ this.emit("responseBoundary", sessionId);
650
780
  }
651
- /**
652
- * DM check: policy gating for private/non-group messages.
653
- * Evaluation order:
654
- * 1. Group messages bypass this gate (handled by GroupGate)
655
- * 2. dmPolicy (disabled drop)
656
- *
657
- * Symmetric with GroupGate — GroupGate owns group messages,
658
- * DmGate owns DM messages.
659
- */
660
- check(envelope) {
661
- if (envelope.isGroup) {
662
- return { allowed: true };
781
+ resolvePendingPermissions(sessionId) {
782
+ const response = {
783
+ outcome: { outcome: "cancelled" }
784
+ };
785
+ for (const [requestId, pending] of this.pendingPermissions) {
786
+ if (sessionId !== void 0 && pending.sessionId !== sessionId) {
787
+ continue;
788
+ }
789
+ clearTimeout(pending.timeout);
790
+ this.pendingPermissions.delete(requestId);
791
+ pending.resolve(response);
792
+ this.emit("permissionResolved", {
793
+ requestId,
794
+ outcome: response.outcome
795
+ });
663
796
  }
664
- if (this.policy === "disabled") {
665
- return { allowed: false, reason: "disabled" };
797
+ }
798
+ maybeKillOnEventLoopStall(stderr) {
799
+ const match = ACP_EVENT_LOOP_STALL_RE.exec(stderr);
800
+ if (!match)
801
+ return;
802
+ const maxMs = Number(match[1]);
803
+ if (!Number.isFinite(maxMs) || maxMs < ACP_EVENT_LOOP_STALL_RESTART_MS) {
804
+ return;
666
805
  }
667
- return { allowed: true };
806
+ const child = this.child;
807
+ if (!child || child.killed || child.exitCode !== null) {
808
+ return;
809
+ }
810
+ process.stderr.write(`[AcpBridge] ACP agent event loop stalled for ${Math.round(maxMs)}ms; killing child process to trigger restart
811
+ `);
812
+ child.kill("SIGKILL");
668
813
  }
669
- };
670
-
671
- // packages/channels/base/dist/group-history-store.js
672
- init_esbuild_shims();
673
- import { appendFileSync, chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
674
- import { dirname as dirname2, join as join3 } from "node:path";
675
- var DEFAULT_MAX_KEYS = 1e3;
676
- var DEFAULT_COMPACT_AFTER_RECORDS = 1e3;
677
- var GroupHistoryStore = class {
678
- static {
679
- __name(this, "GroupHistoryStore");
680
- }
681
- filePath;
682
- maxKeys;
683
- compactAfterRecords;
684
- constructor(filePath, options = {}) {
685
- this.filePath = filePath;
686
- this.maxKeys = options.maxKeys ?? DEFAULT_MAX_KEYS;
687
- this.compactAfterRecords = options.compactAfterRecords ?? DEFAULT_COMPACT_AFTER_RECORDS;
688
- }
689
- record(key, entry, limit) {
690
- const normalizedLimit = normalizeLimit(limit);
691
- if (normalizedLimit <= 0) {
814
+ async registerChannelLoopMcpServer() {
815
+ if (!this.connection || !this.channelLoopMcpServer || this.channelLoopMcpRegistered) {
692
816
  return;
693
817
  }
694
- const loaded = this.loadState();
695
- const state = loaded.entries;
696
- const limits = loaded.limits;
697
- const current = state.get(key) ?? [];
698
- current.push(entry);
699
- if (current.length > normalizedLimit) {
700
- current.splice(0, current.length - normalizedLimit);
701
- }
702
- state.delete(key);
703
- state.set(key, current);
704
- limits.set(key, normalizedLimit);
705
- const evicted = evictOldKeys(state, this.maxKeys, limits);
706
- this.append({
707
- type: "message",
708
- key,
709
- limit: normalizedLimit,
710
- entry,
711
- recordedAt: Date.now()
712
- });
713
- if (evicted || loaded.hadInvalidRecords || loaded.recordCount + 1 >= this.compactAfterRecords) {
714
- this.compact(state, limits);
715
- }
716
- }
717
- drain(key, limit) {
718
- const normalizedLimit = normalizeLimit(limit);
719
- const loaded = this.loadState();
720
- const state = loaded.entries;
721
- const entries = normalizedLimit > 0 ? (state.get(key) ?? []).slice(-normalizedLimit) : [];
722
- if (state.has(key)) {
723
- state.delete(key);
724
- loaded.limits.delete(key);
725
- this.append({ type: "clear", key, recordedAt: Date.now() });
726
- }
727
- return entries;
728
- }
729
- clear(key) {
730
- const loaded = this.loadState();
731
- const state = loaded.entries;
732
- if (!state.has(key)) {
818
+ if (this.channelLoopMcpRegistration) {
819
+ await this.channelLoopMcpRegistration;
733
820
  return;
734
821
  }
735
- state.delete(key);
736
- loaded.limits.delete(key);
737
- this.append({ type: "clear", key, recordedAt: Date.now() });
738
- this.compact(state, loaded.limits);
822
+ this.channelLoopMcpRegistration = this.connection.extMethod(WORKSPACE_MCP_RUNTIME_ADD_METHOD, {
823
+ name: CHANNEL_LOOP_MCP_SERVER_NAME,
824
+ originatorClientId: "channel",
825
+ config: {
826
+ type: "sdk",
827
+ [CLIENT_MCP_OVER_WS_CONFIG_FLAG]: true
828
+ }
829
+ }).then((result) => {
830
+ if (isSkippedMcpRegistration(result)) {
831
+ this.channelLoopMcpRegistered = false;
832
+ process.stderr.write(`[AcpBridge] Channel loop MCP server registration skipped${formatSkippedRegistrationReason(result)}
833
+ `);
834
+ return;
835
+ }
836
+ this.channelLoopMcpRegistered = true;
837
+ }).catch((error) => {
838
+ this.channelLoopMcpRegistered = false;
839
+ process.stderr.write(`[AcpBridge] Failed to register channel loop MCP server: ${error instanceof Error ? error.message : String(error)}
840
+ `);
841
+ }).finally(() => {
842
+ this.channelLoopMcpRegistration = null;
843
+ });
844
+ await this.channelLoopMcpRegistration;
739
845
  }
740
- clearAll() {
741
- const loaded = this.loadState();
742
- if (loaded.entries.size === 0) {
743
- return;
846
+ async handleExtMethod(method, params) {
847
+ if (method === CLIENT_MCP_MESSAGE_METHOD) {
848
+ return this.handleClientMcpMessage(params);
744
849
  }
745
- const recordedAt = Date.now();
746
- for (const key of loaded.entries.keys()) {
747
- this.append({ type: "clear", key, recordedAt });
850
+ if (method === MID_TURN_QUEUE_DRAIN_METHOD) {
851
+ return { messages: [], hasQueuedPrompt: false };
748
852
  }
749
- this.compact(/* @__PURE__ */ new Map(), /* @__PURE__ */ new Map());
750
- }
751
- size(key) {
752
- const state = this.loadState().entries;
753
- if (key !== void 0) {
754
- return state.get(key)?.length ?? 0;
853
+ if (method === TODO_STOP_GUARD_CONTINUATION_CLAIM_METHOD) {
854
+ const sessionId = typeof params["sessionId"] === "string" ? params["sessionId"] : "";
855
+ return {
856
+ claimed: this.knownSessionIds.has(sessionId),
857
+ hasQueuedPrompt: false
858
+ };
755
859
  }
756
- return state.size;
860
+ throw new Error(`Method not found: ${method}`);
757
861
  }
758
- loadState() {
759
- const state = /* @__PURE__ */ new Map();
760
- const limits = /* @__PURE__ */ new Map();
761
- const read = this.readRecords();
762
- for (const record of read.records) {
763
- if (record.type === "clear") {
764
- state.delete(record.key);
765
- limits.delete(record.key);
766
- continue;
767
- }
768
- const current = state.get(record.key) ?? [];
769
- current.push(record.entry);
770
- if (current.length > record.limit) {
771
- current.splice(0, current.length - record.limit);
772
- }
773
- state.delete(record.key);
774
- state.set(record.key, current);
775
- limits.set(record.key, record.limit);
776
- evictOldKeys(state, this.maxKeys, limits);
862
+ async handleClientMcpMessage(params) {
863
+ if (!this.channelLoopMcpServer) {
864
+ throw new Error("Channel loop MCP server is not registered.");
777
865
  }
778
- return {
779
- entries: state,
780
- limits,
781
- recordCount: read.records.length,
782
- hadInvalidRecords: read.hadInvalidRecords
783
- };
784
- }
785
- readRecords() {
786
- if (!existsSync2(this.filePath)) {
787
- return { records: [], hadInvalidRecords: false };
866
+ const server = params["server"];
867
+ if (server !== CHANNEL_LOOP_MCP_SERVER_NAME) {
868
+ throw new Error(`Unknown client MCP server: ${String(server)}`);
788
869
  }
789
- let data;
790
- try {
791
- data = readFileSync2(this.filePath, "utf-8");
792
- } catch (err) {
793
- if (isErrnoCode(err, "ENOENT")) {
794
- return { records: [], hadInvalidRecords: false };
795
- }
796
- throw err;
870
+ const payload = params["payload"];
871
+ if (typeof payload !== "object" || payload === null) {
872
+ throw new Error("Invalid client MCP payload.");
797
873
  }
798
- const records = [];
799
- let hadInvalidRecords = false;
800
- for (const line of data.split("\n")) {
801
- if (line.trim().length === 0) {
802
- continue;
803
- }
804
- try {
805
- const parsed = JSON.parse(line);
806
- if (isGroupHistoryRecord(parsed)) {
807
- records.push(parsed);
808
- } else {
809
- hadInvalidRecords = true;
810
- }
811
- } catch {
812
- hadInvalidRecords = true;
813
- }
874
+ const sessionId = typeof params["sessionId"] === "string" ? params["sessionId"] : void 0;
875
+ const response = await this.channelLoopMcpServer.handleMessage(payload, { sessionId });
876
+ if (!response) {
877
+ return { payload: { jsonrpc: "2.0", id: 0, result: {} } };
814
878
  }
815
- return { records, hadInvalidRecords };
816
- }
817
- append(record) {
818
- const dir = dirname2(this.filePath);
819
- mkdirSync2(dir, { recursive: true, mode: 448 });
820
- chmodPrivate(dir, 448);
821
- appendFileSync(this.filePath, `${JSON.stringify(record)}
822
- `, {
823
- encoding: "utf-8",
824
- mode: 384
825
- });
826
- chmodPrivate(this.filePath, 384);
879
+ return { payload: response };
827
880
  }
828
- compact(state, limits) {
829
- const dir = dirname2(this.filePath);
830
- mkdirSync2(dir, { recursive: true, mode: 448 });
831
- chmodPrivate(dir, 448);
832
- const records = [];
833
- const recordedAt = Date.now();
834
- for (const [key, entries] of state) {
835
- for (const entry of entries) {
836
- records.push({
837
- type: "message",
838
- key,
839
- limit: limits.get(key) ?? entries.length,
840
- entry,
841
- recordedAt
842
- });
843
- }
881
+ resolveChannelLoopToolHandler(sessionId) {
882
+ if (this.channelLoopToolHandlers.length === 1 && !this.channelLoopToolHandlers[0].canHandle) {
883
+ return this.channelLoopToolHandlers[0];
844
884
  }
845
- const data = records.length > 0 ? `${records.map((record) => JSON.stringify(record)).join("\n")}
846
- ` : "";
847
- const tempPath = join3(dir, `${Date.now()}-${process.pid}-${Math.random().toString(16).slice(2)}.tmp`);
848
- writeFileSync2(tempPath, data, { encoding: "utf-8", mode: 384 });
849
- chmodPrivate(tempPath, 384);
850
- renameSync2(tempPath, this.filePath);
851
- chmodPrivate(this.filePath, 384);
885
+ const handler = this.channelLoopToolHandlers.find((candidate) => candidate.canHandle?.(sessionId) === true);
886
+ if (handler)
887
+ return handler;
888
+ throw new Error(this.channelLoopToolHandlers.length === 0 ? "No channel loop tool handler is registered." : `No channel loop handler matched session ${sessionId}.`);
852
889
  }
853
890
  };
854
- function normalizeLimit(limit) {
855
- if (!Number.isFinite(limit) || limit <= 0) {
856
- return 0;
891
+ async function withTimeout(operation, timeoutMs, message) {
892
+ let timer;
893
+ const timeout = new Promise((_, reject) => {
894
+ timer = setTimeout(() => reject(new Error(message)), timeoutMs);
895
+ timer.unref?.();
896
+ });
897
+ try {
898
+ return await Promise.race([operation, timeout]);
899
+ } finally {
900
+ if (timer)
901
+ clearTimeout(timer);
857
902
  }
858
- return Math.floor(limit);
859
903
  }
860
- __name(normalizeLimit, "normalizeLimit");
861
- function evictOldKeys(state, maxKeys, limits) {
862
- let evicted = false;
863
- while (state.size > maxKeys) {
864
- const oldest = state.keys().next().value;
865
- if (oldest === void 0) {
866
- return evicted;
867
- }
868
- state.delete(oldest);
869
- limits?.delete(oldest);
870
- evicted = true;
871
- }
872
- return evicted;
904
+ __name(withTimeout, "withTimeout");
905
+ function isSkippedMcpRegistration(result) {
906
+ return typeof result === "object" && result !== null && result.skipped === true;
873
907
  }
874
- __name(evictOldKeys, "evictOldKeys");
875
- function isGroupHistoryRecord(value) {
876
- if (!isRecord(value)) {
877
- return false;
878
- }
879
- if (value["type"] === "clear") {
880
- return typeof value["key"] === "string";
881
- }
882
- if (value["type"] !== "message") {
883
- return false;
884
- }
885
- return typeof value["key"] === "string" && typeof value["limit"] === "number" && isGroupHistoryEntry(value["entry"]);
886
- }
887
- __name(isGroupHistoryRecord, "isGroupHistoryRecord");
888
- function isGroupHistoryEntry(value) {
889
- return isRecord(value) && typeof value["senderId"] === "string" && typeof value["senderName"] === "string" && typeof value["text"] === "string" && typeof value["timestamp"] === "number";
890
- }
891
- __name(isGroupHistoryEntry, "isGroupHistoryEntry");
892
- function isRecord(value) {
893
- return typeof value === "object" && value !== null;
894
- }
895
- __name(isRecord, "isRecord");
896
- function isErrnoCode(err, code) {
897
- return err instanceof Error && "code" in err && err.code === code;
898
- }
899
- __name(isErrnoCode, "isErrnoCode");
900
- function chmodPrivate(path4, mode) {
901
- try {
902
- chmodSync(path4, mode);
903
- } catch {
904
- }
908
+ __name(isSkippedMcpRegistration, "isSkippedMcpRegistration");
909
+ function formatSkippedRegistrationReason(result) {
910
+ if (typeof result !== "object" || result === null)
911
+ return ".";
912
+ const reason = result.reason;
913
+ return typeof reason === "string" && reason.length > 0 ? `: ${sanitizeLogText(reason, 256)}` : ".";
905
914
  }
906
- __name(chmodPrivate, "chmodPrivate");
915
+ __name(formatSkippedRegistrationReason, "formatSkippedRegistrationReason");
907
916
 
908
- // packages/channels/base/dist/SenderGate.js
917
+ // packages/channels/base/dist/index.js
909
918
  init_esbuild_shims();
910
- var SenderGate = class {
919
+
920
+ // packages/channels/base/dist/PollingChannelBase.js
921
+ init_esbuild_shims();
922
+ import { createHash as createHash2 } from "node:crypto";
923
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, renameSync as renameSync4, writeFileSync as writeFileSync4 } from "node:fs";
924
+ import { join as join6 } from "node:path";
925
+ import process3 from "node:process";
926
+
927
+ // packages/channels/base/dist/ChannelBase.js
928
+ init_esbuild_shims();
929
+ import { basename as basename3, join as join5 } from "node:path";
930
+ import { randomUUID as randomUUID2 } from "node:crypto";
931
+
932
+ // packages/channels/base/dist/BlockStreamer.js
933
+ init_esbuild_shims();
934
+ var BlockStreamer = class {
911
935
  static {
912
- __name(this, "SenderGate");
936
+ __name(this, "BlockStreamer");
913
937
  }
914
- policy;
915
- allowedUsers;
916
- pairingStore;
917
- constructor(policy, allowedUsers = [], pairingStore) {
918
- this.policy = policy;
919
- this.allowedUsers = new Set(allowedUsers);
920
- this.pairingStore = pairingStore || null;
938
+ buffer = "";
939
+ idleTimer = null;
940
+ sending = Promise.resolve();
941
+ opts;
942
+ /** Number of blocks emitted so far. */
943
+ blockCount = 0;
944
+ constructor(opts) {
945
+ this.opts = opts;
921
946
  }
922
- replaceAllowedUsers(users) {
923
- this.allowedUsers = new Set(users);
947
+ /** Feed a new text chunk from the agent stream. */
948
+ push(chunk) {
949
+ this.buffer += chunk;
950
+ this.clearIdleTimer();
951
+ this.checkEmit();
952
+ if (this.buffer.length > 0 && this.opts.idleMs > 0) {
953
+ this.idleTimer = setTimeout(() => this.onIdle(), this.opts.idleMs);
954
+ }
924
955
  }
925
- isAllowed(senderId) {
926
- switch (this.policy) {
927
- case "open":
928
- return true;
929
- case "allowlist":
930
- return this.allowedUsers.has(senderId);
931
- case "pairing":
932
- return this.allowedUsers.has(senderId) || this.pairingStore?.isApproved(senderId) === true;
933
- default:
934
- throw new Error(`Unknown sender policy: ${this.policy}`);
956
+ /** Flush all remaining buffered text. Awaits all pending sends. */
957
+ async flush() {
958
+ this.clearIdleTimer();
959
+ if (this.buffer.length > 0) {
960
+ this.emitBlock(this.buffer);
961
+ this.buffer = "";
935
962
  }
963
+ await this.sending;
936
964
  }
937
- check(senderId, senderName) {
938
- switch (this.policy) {
939
- case "open":
940
- return { allowed: true };
941
- case "allowlist":
942
- return { allowed: this.allowedUsers.has(senderId) };
943
- case "pairing": {
944
- if (this.allowedUsers.has(senderId)) {
945
- return { allowed: true };
946
- }
947
- if (this.pairingStore?.isApproved(senderId)) {
948
- return { allowed: true };
949
- }
950
- const code = this.pairingStore?.createRequest(senderId, senderName || senderId);
951
- return { allowed: false, pairingCode: code ?? null };
965
+ /** Drop buffered text and cancel future idle emission. */
966
+ stop() {
967
+ this.clearIdleTimer();
968
+ this.buffer = "";
969
+ }
970
+ // ---------------------------------------------------------------------------
971
+ // Internal
972
+ // ---------------------------------------------------------------------------
973
+ checkEmit() {
974
+ while (this.buffer.length >= this.opts.maxChars) {
975
+ const bp = this.findBreakPoint(this.buffer, this.opts.maxChars);
976
+ this.emitBlock(this.buffer.slice(0, bp));
977
+ this.buffer = this.buffer.slice(bp);
978
+ }
979
+ if (this.buffer.length >= this.opts.minChars) {
980
+ const bp = this.findBlockBoundary(this.buffer);
981
+ if (bp > 0) {
982
+ this.emitBlock(this.buffer.slice(0, bp));
983
+ this.buffer = this.buffer.slice(bp);
952
984
  }
953
- default:
954
- throw new Error(`Unknown sender policy: ${this.policy}`);
955
985
  }
956
986
  }
957
- };
958
-
959
- // packages/channels/base/dist/SessionRouter.js
960
- init_esbuild_shims();
961
- import { chmodSync as chmodSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync3, rmSync, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
962
- import { dirname as dirname3, join as join4 } from "node:path";
963
- import process2 from "node:process";
964
- var SessionRouter = class {
965
- static {
966
- __name(this, "SessionRouter");
987
+ onIdle() {
988
+ this.idleTimer = null;
989
+ if (this.buffer.length >= this.opts.minChars) {
990
+ this.emitBlock(this.buffer);
991
+ this.buffer = "";
992
+ }
967
993
  }
968
- toSession = /* @__PURE__ */ new Map();
969
- // routing key → session ID
970
- toTarget = /* @__PURE__ */ new Map();
971
- // session ID → target
972
- toCwd = /* @__PURE__ */ new Map();
973
- // session ID cwd
974
- creatingSessions = /* @__PURE__ */ new Map();
975
- sessionLoadWindows = /* @__PURE__ */ new Set();
976
- liveSessionIds = /* @__PURE__ */ new Set();
977
- routeTokens = /* @__PURE__ */ new Map();
978
- lifecycleGeneration = 0;
979
- bridge;
980
- defaultCwd;
981
- defaultScope;
982
- channelScopes = /* @__PURE__ */ new Map();
983
- channelApprovalModes = /* @__PURE__ */ new Map();
984
- persistPath;
985
- recoveryMode;
986
- constructor(bridge, defaultCwd, scope = "user", persistPath, options = {}) {
987
- this.bridge = bridge;
988
- this.defaultCwd = defaultCwd;
989
- this.defaultScope = scope;
990
- this.persistPath = persistPath;
991
- this.recoveryMode = options.recoveryMode ?? "eager";
994
+ emitBlock(text) {
995
+ const trimmed = text.trim();
996
+ if (!trimmed)
997
+ return;
998
+ this.blockCount++;
999
+ this.sending = this.sending.then(() => this.opts.send(trimmed)).catch(() => {
1000
+ });
992
1001
  }
993
- /** Replace the bridge instance (used after crash recovery restart). */
994
- setBridge(bridge) {
995
- this.bridge = bridge;
1002
+ /**
1003
+ * Find the last paragraph boundary (\n\n) in the buffer.
1004
+ * Returns the position after the boundary, or -1 if no suitable boundary
1005
+ * exists at or after minChars.
1006
+ */
1007
+ findBlockBoundary(text) {
1008
+ const last = text.lastIndexOf("\n\n");
1009
+ if (last < 0 || last < this.opts.minChars)
1010
+ return -1;
1011
+ return last + 2;
996
1012
  }
997
- /** Set scope override for a specific channel. */
998
- setChannelScope(channelName, scope) {
999
- this.channelScopes.set(channelName, scope);
1013
+ /**
1014
+ * Find the best break point at or before maxPos.
1015
+ * Prefers paragraph break > newline > space > maxPos.
1016
+ */
1017
+ findBreakPoint(text, maxPos) {
1018
+ const sub = text.slice(0, maxPos);
1019
+ const para = sub.lastIndexOf("\n\n");
1020
+ if (para > 0)
1021
+ return para + 2;
1022
+ const nl = sub.lastIndexOf("\n");
1023
+ if (nl > 0)
1024
+ return nl + 1;
1025
+ const sp = sub.lastIndexOf(" ");
1026
+ if (sp > 0)
1027
+ return sp + 1;
1028
+ return maxPos;
1000
1029
  }
1001
- setChannelApprovalMode(channelName, approvalMode) {
1002
- if (approvalMode) {
1003
- this.channelApprovalModes.set(channelName, approvalMode);
1004
- } else {
1005
- this.channelApprovalModes.delete(channelName);
1030
+ clearIdleTimer() {
1031
+ if (this.idleTimer !== null) {
1032
+ clearTimeout(this.idleTimer);
1033
+ this.idleTimer = null;
1006
1034
  }
1007
1035
  }
1008
- routingKey(channelName, senderId, chatId, threadId) {
1009
- const scope = this.channelScopes.get(channelName) || this.defaultScope;
1010
- switch (scope) {
1011
- case "thread":
1012
- return `${channelName}:${threadId || chatId}`;
1013
- case "chat_thread":
1014
- return threadId ? `${channelName}:${chatId}:${threadId}` : `${channelName}:${chatId}`;
1015
- case "single":
1016
- return `${channelName}:__single__`;
1017
- case "user":
1018
- default:
1019
- return `${channelName}:${senderId}:${chatId}`;
1020
- }
1036
+ };
1037
+
1038
+ // packages/channels/base/dist/ChannelProactiveDeliveryError.js
1039
+ init_esbuild_shims();
1040
+ var CHANNEL_PROACTIVE_DELIVERY_ERROR_CODE = "channel_proactive_delivery_error";
1041
+ var ChannelProactiveDeliveryError = class extends Error {
1042
+ static {
1043
+ __name(this, "ChannelProactiveDeliveryError");
1021
1044
  }
1022
- sessionOptions(channelName) {
1023
- const approvalMode = this.channelApprovalModes.get(channelName);
1024
- return approvalMode ? { approvalMode } : void 0;
1045
+ disposition;
1046
+ code = CHANNEL_PROACTIVE_DELIVERY_ERROR_CODE;
1047
+ constructor(disposition, message, options) {
1048
+ super(message, options);
1049
+ this.disposition = disposition;
1050
+ this.name = "ChannelProactiveDeliveryError";
1025
1051
  }
1026
- async resolve(channelName, senderId, chatId, threadId, cwd, isGroup, options) {
1027
- const key = this.routingKey(channelName, senderId, chatId, options?.routingThreadId ?? threadId);
1028
- const input = {
1029
- channelName,
1030
- senderId,
1031
- chatId,
1032
- threadId,
1033
- cwd: cwd || this.defaultCwd,
1034
- isGroup
1035
- };
1036
- let failedWaits = 0;
1037
- for (; ; ) {
1038
- const existing = this.toSession.get(key);
1039
- if (existing && this.isLive(existing)) {
1040
- this.promoteTargetToGroup(existing, isGroup);
1041
- return existing;
1042
- }
1043
- const creating = this.creatingSessions.get(key);
1044
- if (creating) {
1045
- try {
1046
- const sessionId = await creating.promise;
1047
- try {
1048
- this.assertOperationResultCurrent(key, sessionId, creating);
1049
- } catch (error) {
1050
- this.scheduleDiscardInvalidatedSession(sessionId, creating);
1051
- throw error;
1052
- }
1053
- this.promoteTargetToGroup(sessionId, isGroup);
1054
- return sessionId;
1055
- } catch (error) {
1056
- if (creating.invalidationError) {
1057
- throw creating.invalidationError;
1058
- }
1059
- if (this.creatingSessions.get(key) === creating) {
1060
- this.creatingSessions.delete(key);
1061
- }
1062
- this.releaseRouteToken(key, creating);
1063
- failedWaits++;
1064
- if (failedWaits > 3)
1065
- throw error;
1066
- continue;
1067
- }
1068
- }
1069
- const operation = this.createSessionOperation(key, {
1070
- channelName: input.channelName,
1071
- senderId: input.senderId,
1072
- chatId: input.chatId,
1073
- threadId: input.threadId,
1074
- isGroup: input.isGroup
1075
- }, (currentOperation) => existing ? this.loadOrReplaceSession(key, existing, input, currentOperation) : this.createAndStoreSession(key, input, currentOperation));
1076
- this.creatingSessions.set(key, operation);
1077
- try {
1078
- const sessionId = await operation.promise;
1079
- try {
1080
- this.assertOperationResultCurrent(key, sessionId, operation);
1081
- } catch (error) {
1082
- this.scheduleDiscardInvalidatedSession(sessionId, operation);
1083
- throw error;
1084
- }
1085
- this.promoteTargetToGroup(sessionId, isGroup);
1086
- return sessionId;
1087
- } finally {
1088
- if (this.creatingSessions.get(key) === operation) {
1089
- this.creatingSessions.delete(key);
1090
- }
1091
- this.releaseRouteToken(key, operation);
1092
- }
1093
- }
1052
+ };
1053
+ function isChannelProactiveDeliveryError(error) {
1054
+ if (typeof error !== "object" || error === null)
1055
+ return false;
1056
+ const candidate = error;
1057
+ return candidate.code === CHANNEL_PROACTIVE_DELIVERY_ERROR_CODE && (candidate.disposition === "permanent" || candidate.disposition === "transient") && typeof candidate.message === "string";
1058
+ }
1059
+ __name(isChannelProactiveDeliveryError, "isChannelProactiveDeliveryError");
1060
+
1061
+ // packages/channels/base/dist/GroupGate.js
1062
+ init_esbuild_shims();
1063
+ var GroupGate = class {
1064
+ static {
1065
+ __name(this, "GroupGate");
1094
1066
  }
1095
- isLive(sessionId) {
1096
- return this.recoveryMode === "eager" || this.liveSessionIds.has(sessionId);
1067
+ policy;
1068
+ groups;
1069
+ constructor(policy = "disabled", groups = {}) {
1070
+ this.policy = policy;
1071
+ this.groups = groups;
1097
1072
  }
1098
- async createAndStoreSession(key, input, operation) {
1099
- const loadWindow = this.beginSessionLoad();
1100
- try {
1101
- const sessionId = await this.createLiveSession(input.cwd, loadWindow, key, this.sessionOptions(input.channelName), operation, input.channelName);
1102
- try {
1103
- this.assertOperationCurrent(operation);
1104
- } catch (error) {
1105
- this.scheduleDiscardInvalidatedSession(sessionId, operation);
1106
- throw error;
1107
- }
1108
- this.toSession.set(key, sessionId);
1109
- this.toTarget.set(sessionId, {
1110
- channelName: input.channelName,
1111
- senderId: input.senderId,
1112
- chatId: input.chatId,
1113
- threadId: input.threadId,
1114
- isGroup: input.isGroup
1115
- });
1116
- this.toCwd.set(sessionId, input.cwd);
1117
- this.liveSessionIds.add(sessionId);
1118
- this.persist();
1119
- return sessionId;
1120
- } finally {
1121
- this.endSessionLoad(loadWindow);
1073
+ /**
1074
+ * Full group check: policy + allowlist + mention gating.
1075
+ * Evaluation order:
1076
+ * 1. groupPolicy (disabled drop)
1077
+ * 2. group allowlist (allowlist mode, no match → drop)
1078
+ * 3. mention gating (requireMention + not mentioned → drop silently)
1079
+ *
1080
+ * Mention gating runs before sender gate so that unmentioned messages
1081
+ * in groups don't trigger pairing flows.
1082
+ */
1083
+ check(envelope) {
1084
+ if (!envelope.isGroup) {
1085
+ return { allowed: true };
1122
1086
  }
1123
- }
1124
- async loadOrReplaceSession(key, savedSessionId, input, operation) {
1125
- const savedCwd = this.toCwd.get(savedSessionId) ?? input.cwd;
1126
- const loadWindow = this.beginSessionLoad();
1127
- try {
1128
- try {
1129
- const loadedSessionId = await this.bridge.loadSession(savedSessionId, savedCwd, this.sessionOptions(input.channelName), operation);
1130
- try {
1131
- this.assertOperationCurrent(operation);
1132
- if (this.toSession.get(key) !== savedSessionId) {
1133
- this.invalidateOperation(operation);
1134
- this.assertOperationCurrent(operation);
1135
- }
1136
- } catch (error) {
1137
- this.scheduleDiscardInvalidatedSession(loadedSessionId, operation);
1138
- throw error;
1139
- }
1140
- if (typeof loadedSessionId !== "string" || loadedSessionId.length === 0 || loadWindow.delete(loadedSessionId)) {
1141
- throw new Error("Invalid or dead restored session ID");
1142
- }
1143
- if (loadedSessionId !== savedSessionId) {
1144
- const target = this.toTarget.get(savedSessionId);
1145
- this.deleteByKey(key);
1146
- this.toSession.set(key, loadedSessionId);
1147
- if (target)
1148
- this.toTarget.set(loadedSessionId, target);
1149
- this.toCwd.set(loadedSessionId, savedCwd);
1150
- this.persist();
1151
- }
1152
- this.liveSessionIds.add(loadedSessionId);
1153
- return loadedSessionId;
1154
- } catch (loadError) {
1155
- this.assertOperationCurrent(operation);
1156
- try {
1157
- const replacement = await this.createLiveSession(input.cwd, loadWindow, key, this.sessionOptions(input.channelName), operation, input.channelName);
1158
- try {
1159
- this.assertOperationCurrent(operation);
1160
- } catch (error) {
1161
- this.scheduleDiscardInvalidatedSession(replacement, operation);
1162
- throw error;
1163
- }
1164
- this.deleteByKey(key);
1165
- this.toSession.set(key, replacement);
1166
- this.toTarget.set(replacement, {
1167
- channelName: input.channelName,
1168
- senderId: input.senderId,
1169
- chatId: input.chatId,
1170
- threadId: input.threadId,
1171
- isGroup: input.isGroup
1172
- });
1173
- this.toCwd.set(replacement, input.cwd);
1174
- this.liveSessionIds.add(replacement);
1175
- this.persist();
1176
- process2.stderr.write(`[SessionRouter] Replaced unavailable session ${sanitizeLogText(savedSessionId, 128)} for key ${sanitizeLogText(key, 256)} after load failed: ${sanitizeLogText(loadError instanceof Error ? loadError.message : String(loadError), 512)}
1177
- `);
1178
- return replacement;
1179
- } catch (createError) {
1180
- this.assertOperationCurrent(operation);
1181
- process2.stderr.write(`[SessionRouter] Failed to load session ${sanitizeLogText(savedSessionId, 128)} for key ${sanitizeLogText(key, 256)} (${sanitizeLogText(loadError instanceof Error ? loadError.message : String(loadError), 512)}) and failed to create a replacement (${sanitizeLogText(createError instanceof Error ? createError.message : String(createError), 512)})
1182
- `);
1183
- throw createError;
1184
- }
1087
+ if (this.policy === "disabled") {
1088
+ return { allowed: false, reason: "disabled" };
1089
+ }
1090
+ if (this.policy === "allowlist") {
1091
+ if (!this.groups[envelope.chatId]) {
1092
+ return { allowed: false, reason: "not_allowlisted" };
1185
1093
  }
1186
- } finally {
1187
- this.endSessionLoad(loadWindow);
1188
1094
  }
1095
+ const groupConfig = this.groups[envelope.chatId] || this.groups["*"] || {};
1096
+ const requireMention = groupConfig.requireMention ?? true;
1097
+ if (requireMention && !envelope.isMentioned && !envelope.isReplyToBot) {
1098
+ return { allowed: false, reason: "mention_required" };
1099
+ }
1100
+ return { allowed: true };
1189
1101
  }
1190
- getTarget(sessionId) {
1191
- return this.toTarget.get(sessionId);
1192
- }
1193
- getSession(channelName, senderId, chatId, threadId) {
1194
- return this.toSession.get(this.routingKey(channelName, senderId, chatId, threadId));
1102
+ };
1103
+
1104
+ // packages/channels/base/dist/DmGate.js
1105
+ init_esbuild_shims();
1106
+ var DmGate = class {
1107
+ static {
1108
+ __name(this, "DmGate");
1195
1109
  }
1196
- hasSession(channelName, senderId, chatId, threadId) {
1197
- const scope = this.channelScopes.get(channelName) || this.defaultScope;
1198
- if (chatId) {
1199
- return this.toSession.has(this.routingKey(channelName, senderId, chatId, threadId));
1200
- }
1201
- if (scope === "single") {
1202
- return false;
1203
- }
1204
- for (const target of this.toTarget.values()) {
1205
- if (target.channelName === channelName && target.senderId === senderId) {
1206
- return true;
1207
- }
1208
- }
1209
- return false;
1110
+ policy;
1111
+ constructor(policy = "open") {
1112
+ this.policy = policy;
1210
1113
  }
1211
1114
  /**
1212
- * Remove session(s) for the given sender. Returns the removed session IDs.
1115
+ * DM check: policy gating for private/non-group messages.
1116
+ * Evaluation order:
1117
+ * 1. Group messages bypass this gate (handled by GroupGate)
1118
+ * 2. dmPolicy (disabled → drop)
1119
+ *
1120
+ * Symmetric with GroupGate — GroupGate owns group messages,
1121
+ * DmGate owns DM messages.
1213
1122
  */
1214
- removeSession(channelName, senderId, chatId, threadId) {
1215
- const removedIds = [];
1216
- const scope = this.channelScopes.get(channelName) || this.defaultScope;
1217
- if (chatId) {
1218
- const key = this.routingKey(channelName, senderId, chatId, threadId);
1219
- this.invalidateRouteOperation(key);
1220
- const sessionId = this.deleteByKey(key);
1221
- if (sessionId)
1222
- removedIds.push(sessionId);
1223
- } else if (scope === "single") {
1224
- return removedIds;
1225
- } else {
1226
- for (const [k, mappedSessionId] of [...this.toSession.entries()]) {
1227
- const target = this.toTarget.get(mappedSessionId);
1228
- if (target?.channelName === channelName && target.senderId === senderId) {
1229
- this.invalidateRouteOperation(k);
1230
- const sessionId = this.deleteByKey(k);
1231
- if (sessionId)
1232
- removedIds.push(sessionId);
1233
- }
1234
- }
1235
- for (const [key, operation] of [...this.creatingSessions]) {
1236
- if (operation.target.channelName === channelName && operation.target.senderId === senderId) {
1237
- this.invalidateRouteOperation(key);
1238
- }
1239
- }
1123
+ check(envelope) {
1124
+ if (envelope.isGroup) {
1125
+ return { allowed: true };
1240
1126
  }
1241
- if (removedIds.length > 0)
1242
- this.persist();
1243
- return removedIds;
1127
+ if (this.policy === "disabled") {
1128
+ return { allowed: false, reason: "disabled" };
1129
+ }
1130
+ return { allowed: true };
1244
1131
  }
1245
- /** Remove a session mapping by daemon/ACP session ID. */
1246
- removeSessionId(sessionId) {
1247
- let removed = false;
1248
- for (const [key, mappedSessionId] of [...this.toSession.entries()]) {
1249
- if (mappedSessionId === sessionId) {
1250
- this.invalidateRouteOperation(key);
1251
- this.toSession.delete(key);
1252
- removed = true;
1253
- }
1132
+ };
1133
+
1134
+ // packages/channels/base/dist/group-history-store.js
1135
+ init_esbuild_shims();
1136
+ import { appendFileSync, chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
1137
+ import { dirname as dirname2, join as join3 } from "node:path";
1138
+ var DEFAULT_MAX_KEYS = 1e3;
1139
+ var DEFAULT_COMPACT_AFTER_RECORDS = 1e3;
1140
+ var GroupHistoryStore = class {
1141
+ static {
1142
+ __name(this, "GroupHistoryStore");
1143
+ }
1144
+ filePath;
1145
+ maxKeys;
1146
+ compactAfterRecords;
1147
+ constructor(filePath, options = {}) {
1148
+ this.filePath = filePath;
1149
+ this.maxKeys = options.maxKeys ?? DEFAULT_MAX_KEYS;
1150
+ this.compactAfterRecords = options.compactAfterRecords ?? DEFAULT_COMPACT_AFTER_RECORDS;
1151
+ }
1152
+ record(key, entry, limit) {
1153
+ const normalizedLimit = normalizeLimit(limit);
1154
+ if (normalizedLimit <= 0) {
1155
+ return;
1254
1156
  }
1255
- if (this.toTarget.delete(sessionId)) {
1256
- removed = true;
1157
+ const loaded = this.loadState();
1158
+ const state = loaded.entries;
1159
+ const limits = loaded.limits;
1160
+ const current = state.get(key) ?? [];
1161
+ current.push(entry);
1162
+ if (current.length > normalizedLimit) {
1163
+ current.splice(0, current.length - normalizedLimit);
1257
1164
  }
1258
- if (this.toCwd.delete(sessionId)) {
1259
- removed = true;
1165
+ state.delete(key);
1166
+ state.set(key, current);
1167
+ limits.set(key, normalizedLimit);
1168
+ const evicted = evictOldKeys(state, this.maxKeys, limits);
1169
+ this.append({
1170
+ type: "message",
1171
+ key,
1172
+ limit: normalizedLimit,
1173
+ entry,
1174
+ recordedAt: Date.now()
1175
+ });
1176
+ if (evicted || loaded.hadInvalidRecords || loaded.recordCount + 1 >= this.compactAfterRecords) {
1177
+ this.compact(state, limits);
1260
1178
  }
1261
- this.liveSessionIds.delete(sessionId);
1262
- if (!removed && this.sessionLoadWindows.size > 0) {
1263
- for (const loadWindow of this.sessionLoadWindows) {
1264
- loadWindow.add(sessionId);
1265
- }
1179
+ }
1180
+ drain(key, limit) {
1181
+ const normalizedLimit = normalizeLimit(limit);
1182
+ const loaded = this.loadState();
1183
+ const state = loaded.entries;
1184
+ const entries = normalizedLimit > 0 ? (state.get(key) ?? []).slice(-normalizedLimit) : [];
1185
+ if (state.has(key)) {
1186
+ state.delete(key);
1187
+ loaded.limits.delete(key);
1188
+ this.append({ type: "clear", key, recordedAt: Date.now() });
1266
1189
  }
1267
- if (removed) {
1268
- this.persist();
1190
+ return entries;
1191
+ }
1192
+ clear(key) {
1193
+ const loaded = this.loadState();
1194
+ const state = loaded.entries;
1195
+ if (!state.has(key)) {
1196
+ return;
1269
1197
  }
1270
- return removed;
1198
+ state.delete(key);
1199
+ loaded.limits.delete(key);
1200
+ this.append({ type: "clear", key, recordedAt: Date.now() });
1201
+ this.compact(state, loaded.limits);
1271
1202
  }
1272
- handleSessionDied(sessionId) {
1273
- if (this.recoveryMode === "eager") {
1274
- return this.removeSessionId(sessionId);
1203
+ clearAll() {
1204
+ const loaded = this.loadState();
1205
+ if (loaded.entries.size === 0) {
1206
+ return;
1275
1207
  }
1276
- const known = this.toTarget.has(sessionId);
1277
- this.liveSessionIds.delete(sessionId);
1278
- for (const loadWindow of this.sessionLoadWindows) {
1279
- loadWindow.add(sessionId);
1208
+ const recordedAt = Date.now();
1209
+ for (const key of loaded.entries.keys()) {
1210
+ this.append({ type: "clear", key, recordedAt });
1280
1211
  }
1281
- return known;
1282
- }
1283
- deleteByKey(key) {
1284
- const sessionId = this.toSession.get(key);
1285
- if (!sessionId)
1286
- return null;
1287
- this.toSession.delete(key);
1288
- this.toTarget.delete(sessionId);
1289
- this.toCwd.delete(sessionId);
1290
- this.liveSessionIds.delete(sessionId);
1291
- return sessionId;
1212
+ this.compact(/* @__PURE__ */ new Map(), /* @__PURE__ */ new Map());
1292
1213
  }
1293
- promoteTargetToGroup(sessionId, isGroup) {
1294
- const current = this.toTarget.get(sessionId);
1295
- if (!current)
1296
- return;
1297
- if (current.isGroup === true || isGroup !== true)
1298
- return;
1299
- this.toTarget.set(sessionId, { ...current, isGroup: true });
1300
- this.persist();
1214
+ size(key) {
1215
+ const state = this.loadState().entries;
1216
+ if (key !== void 0) {
1217
+ return state.get(key)?.length ?? 0;
1218
+ }
1219
+ return state.size;
1301
1220
  }
1302
- /** Get all session entries for crash recovery. */
1303
- getAll() {
1304
- const entries = [];
1305
- for (const [key, sessionId] of this.toSession) {
1306
- const target = this.toTarget.get(sessionId);
1307
- if (target) {
1308
- entries.push({ key, sessionId, target });
1221
+ loadState() {
1222
+ const state = /* @__PURE__ */ new Map();
1223
+ const limits = /* @__PURE__ */ new Map();
1224
+ const read = this.readRecords();
1225
+ for (const record of read.records) {
1226
+ if (record.type === "clear") {
1227
+ state.delete(record.key);
1228
+ limits.delete(record.key);
1229
+ continue;
1230
+ }
1231
+ const current = state.get(record.key) ?? [];
1232
+ current.push(record.entry);
1233
+ if (current.length > record.limit) {
1234
+ current.splice(0, current.length - record.limit);
1309
1235
  }
1236
+ state.delete(record.key);
1237
+ state.set(record.key, current);
1238
+ limits.set(record.key, record.limit);
1239
+ evictOldKeys(state, this.maxKeys, limits);
1310
1240
  }
1311
- return entries;
1241
+ return {
1242
+ entries: state,
1243
+ limits,
1244
+ recordCount: read.records.length,
1245
+ hadInvalidRecords: read.hadInvalidRecords
1246
+ };
1312
1247
  }
1313
- restoreRoutes() {
1314
- if (this.recoveryMode !== "lazy") {
1315
- throw new Error("restoreRoutes requires lazy recovery mode");
1248
+ readRecords() {
1249
+ if (!existsSync2(this.filePath)) {
1250
+ return { records: [], hadInvalidRecords: false };
1316
1251
  }
1317
- const persisted = this.readPersistedEntries();
1318
- if (!persisted)
1319
- return { restored: 0, dropped: 0 };
1320
- this.dispose();
1321
- let restored = 0;
1322
- for (const [key, entry] of Object.entries(persisted.entries)) {
1323
- this.toSession.set(key, entry.sessionId);
1324
- this.toTarget.set(entry.sessionId, entry.target);
1325
- this.toCwd.set(entry.sessionId, entry.cwd);
1326
- restored++;
1252
+ let data;
1253
+ try {
1254
+ data = readFileSync2(this.filePath, "utf-8");
1255
+ } catch (err) {
1256
+ if (isErrnoCode(err, "ENOENT")) {
1257
+ return { records: [], hadInvalidRecords: false };
1258
+ }
1259
+ throw err;
1327
1260
  }
1328
- if (persisted.dropped > 0)
1329
- this.persist();
1330
- return { restored, dropped: persisted.dropped };
1261
+ const records = [];
1262
+ let hadInvalidRecords = false;
1263
+ for (const line of data.split("\n")) {
1264
+ if (line.trim().length === 0) {
1265
+ continue;
1266
+ }
1267
+ try {
1268
+ const parsed = JSON.parse(line);
1269
+ if (isGroupHistoryRecord(parsed)) {
1270
+ records.push(parsed);
1271
+ } else {
1272
+ hadInvalidRecords = true;
1273
+ }
1274
+ } catch {
1275
+ hadInvalidRecords = true;
1276
+ }
1277
+ }
1278
+ return { records, hadInvalidRecords };
1331
1279
  }
1332
- /**
1333
- * Restore session mappings from a previous bridge.
1334
- * Called after bridge restart attempts loadSession for each saved mapping.
1335
- * Failed loads are dropped (new session on next message).
1336
- */
1337
- async restoreSessions() {
1338
- const persisted = this.readPersistedEntries();
1339
- if (!persisted)
1340
- return { restored: 0, failed: 0 };
1341
- const entries = persisted.entries;
1342
- const restoreGeneration = this.lifecycleGeneration;
1343
- let restored = 0;
1344
- let failed = 0;
1345
- let changed = persisted.dropped > 0;
1346
- const reservations = /* @__PURE__ */ new Map();
1347
- for (const key of persisted.droppedKeys) {
1348
- this.deleteByKey(key);
1280
+ append(record) {
1281
+ const dir = dirname2(this.filePath);
1282
+ mkdirSync2(dir, { recursive: true, mode: 448 });
1283
+ chmodPrivate(dir, 448);
1284
+ appendFileSync(this.filePath, `${JSON.stringify(record)}
1285
+ `, {
1286
+ encoding: "utf-8",
1287
+ mode: 384
1288
+ });
1289
+ chmodPrivate(this.filePath, 384);
1290
+ }
1291
+ compact(state, limits) {
1292
+ const dir = dirname2(this.filePath);
1293
+ mkdirSync2(dir, { recursive: true, mode: 448 });
1294
+ chmodPrivate(dir, 448);
1295
+ const records = [];
1296
+ const recordedAt = Date.now();
1297
+ for (const [key, entries] of state) {
1298
+ for (const entry of entries) {
1299
+ records.push({
1300
+ type: "message",
1301
+ key,
1302
+ limit: limits.get(key) ?? entries.length,
1303
+ entry,
1304
+ recordedAt
1305
+ });
1306
+ }
1349
1307
  }
1350
- for (const key of Object.keys(entries)) {
1351
- this.deleteByKey(key);
1352
- const reservation = this.createSessionReservation();
1353
- reservation.promise.catch(() => void 0);
1354
- const operation = this.createSessionOperation(key, entries[key].target, () => reservation.promise);
1355
- operation.promise.catch(() => void 0);
1356
- this.creatingSessions.set(key, operation);
1357
- reservations.set(key, { reservation, operation });
1308
+ const data = records.length > 0 ? `${records.map((record) => JSON.stringify(record)).join("\n")}
1309
+ ` : "";
1310
+ const tempPath = join3(dir, `${Date.now()}-${process.pid}-${Math.random().toString(16).slice(2)}.tmp`);
1311
+ writeFileSync2(tempPath, data, { encoding: "utf-8", mode: 384 });
1312
+ chmodPrivate(tempPath, 384);
1313
+ renameSync2(tempPath, this.filePath);
1314
+ chmodPrivate(this.filePath, 384);
1315
+ }
1316
+ };
1317
+ function normalizeLimit(limit) {
1318
+ if (!Number.isFinite(limit) || limit <= 0) {
1319
+ return 0;
1320
+ }
1321
+ return Math.floor(limit);
1322
+ }
1323
+ __name(normalizeLimit, "normalizeLimit");
1324
+ function evictOldKeys(state, maxKeys, limits) {
1325
+ let evicted = false;
1326
+ while (state.size > maxKeys) {
1327
+ const oldest = state.keys().next().value;
1328
+ if (oldest === void 0) {
1329
+ return evicted;
1358
1330
  }
1359
- const loadWindow = this.beginSessionLoad();
1360
- try {
1361
- for (const [key, entry] of Object.entries(entries)) {
1362
- const reserved = reservations.get(key);
1363
- if (!reserved)
1364
- continue;
1365
- const { reservation, operation } = reserved;
1331
+ state.delete(oldest);
1332
+ limits?.delete(oldest);
1333
+ evicted = true;
1334
+ }
1335
+ return evicted;
1336
+ }
1337
+ __name(evictOldKeys, "evictOldKeys");
1338
+ function isGroupHistoryRecord(value) {
1339
+ if (!isRecord(value)) {
1340
+ return false;
1341
+ }
1342
+ if (value["type"] === "clear") {
1343
+ return typeof value["key"] === "string";
1344
+ }
1345
+ if (value["type"] !== "message") {
1346
+ return false;
1347
+ }
1348
+ return typeof value["key"] === "string" && typeof value["limit"] === "number" && isGroupHistoryEntry(value["entry"]);
1349
+ }
1350
+ __name(isGroupHistoryRecord, "isGroupHistoryRecord");
1351
+ function isGroupHistoryEntry(value) {
1352
+ return isRecord(value) && typeof value["senderId"] === "string" && typeof value["senderName"] === "string" && typeof value["text"] === "string" && typeof value["timestamp"] === "number";
1353
+ }
1354
+ __name(isGroupHistoryEntry, "isGroupHistoryEntry");
1355
+ function isRecord(value) {
1356
+ return typeof value === "object" && value !== null;
1357
+ }
1358
+ __name(isRecord, "isRecord");
1359
+ function isErrnoCode(err, code) {
1360
+ return err instanceof Error && "code" in err && err.code === code;
1361
+ }
1362
+ __name(isErrnoCode, "isErrnoCode");
1363
+ function chmodPrivate(path4, mode) {
1364
+ try {
1365
+ chmodSync(path4, mode);
1366
+ } catch {
1367
+ }
1368
+ }
1369
+ __name(chmodPrivate, "chmodPrivate");
1370
+
1371
+ // packages/channels/base/dist/SenderGate.js
1372
+ init_esbuild_shims();
1373
+ var SenderGate = class {
1374
+ static {
1375
+ __name(this, "SenderGate");
1376
+ }
1377
+ policy;
1378
+ allowedUsers;
1379
+ pairingStore;
1380
+ constructor(policy, allowedUsers = [], pairingStore) {
1381
+ this.policy = policy;
1382
+ this.allowedUsers = new Set(allowedUsers);
1383
+ this.pairingStore = pairingStore || null;
1384
+ }
1385
+ replaceAllowedUsers(users) {
1386
+ this.allowedUsers = new Set(users);
1387
+ }
1388
+ isAllowed(senderId) {
1389
+ switch (this.policy) {
1390
+ case "open":
1391
+ return true;
1392
+ case "allowlist":
1393
+ return this.allowedUsers.has(senderId);
1394
+ case "pairing":
1395
+ return this.allowedUsers.has(senderId) || this.pairingStore?.isApproved(senderId) === true;
1396
+ default:
1397
+ throw new Error(`Unknown sender policy: ${this.policy}`);
1398
+ }
1399
+ }
1400
+ check(senderId, senderName) {
1401
+ switch (this.policy) {
1402
+ case "open":
1403
+ return { allowed: true };
1404
+ case "allowlist":
1405
+ return { allowed: this.allowedUsers.has(senderId) };
1406
+ case "pairing": {
1407
+ if (this.allowedUsers.has(senderId)) {
1408
+ return { allowed: true };
1409
+ }
1410
+ if (this.pairingStore?.isApproved(senderId)) {
1411
+ return { allowed: true };
1412
+ }
1413
+ const code = this.pairingStore?.createRequest(senderId, senderName || senderId);
1414
+ return { allowed: false, pairingCode: code ?? null };
1415
+ }
1416
+ default:
1417
+ throw new Error(`Unknown sender policy: ${this.policy}`);
1418
+ }
1419
+ }
1420
+ };
1421
+
1422
+ // packages/channels/base/dist/SessionRouter.js
1423
+ init_esbuild_shims();
1424
+ import { chmodSync as chmodSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync3, rmSync, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
1425
+ import { dirname as dirname3, join as join4 } from "node:path";
1426
+ import process2 from "node:process";
1427
+ var SessionRouter = class {
1428
+ static {
1429
+ __name(this, "SessionRouter");
1430
+ }
1431
+ toSession = /* @__PURE__ */ new Map();
1432
+ // routing key → session ID
1433
+ toTarget = /* @__PURE__ */ new Map();
1434
+ // session ID → target
1435
+ toCwd = /* @__PURE__ */ new Map();
1436
+ // session ID → cwd
1437
+ creatingSessions = /* @__PURE__ */ new Map();
1438
+ sessionLoadWindows = /* @__PURE__ */ new Set();
1439
+ liveSessionIds = /* @__PURE__ */ new Set();
1440
+ routeTokens = /* @__PURE__ */ new Map();
1441
+ lifecycleGeneration = 0;
1442
+ bridge;
1443
+ defaultCwd;
1444
+ defaultScope;
1445
+ channelScopes = /* @__PURE__ */ new Map();
1446
+ channelApprovalModes = /* @__PURE__ */ new Map();
1447
+ persistPath;
1448
+ recoveryMode;
1449
+ constructor(bridge, defaultCwd, scope = "user", persistPath, options = {}) {
1450
+ this.bridge = bridge;
1451
+ this.defaultCwd = defaultCwd;
1452
+ this.defaultScope = scope;
1453
+ this.persistPath = persistPath;
1454
+ this.recoveryMode = options.recoveryMode ?? "eager";
1455
+ }
1456
+ /** Replace the bridge instance (used after crash recovery restart). */
1457
+ setBridge(bridge) {
1458
+ this.bridge = bridge;
1459
+ }
1460
+ /** Set scope override for a specific channel. */
1461
+ setChannelScope(channelName, scope) {
1462
+ this.channelScopes.set(channelName, scope);
1463
+ }
1464
+ setChannelApprovalMode(channelName, approvalMode) {
1465
+ if (approvalMode) {
1466
+ this.channelApprovalModes.set(channelName, approvalMode);
1467
+ } else {
1468
+ this.channelApprovalModes.delete(channelName);
1469
+ }
1470
+ }
1471
+ routingKey(channelName, senderId, chatId, threadId) {
1472
+ const scope = this.channelScopes.get(channelName) || this.defaultScope;
1473
+ switch (scope) {
1474
+ case "thread":
1475
+ return `${channelName}:${threadId || chatId}`;
1476
+ case "chat_thread":
1477
+ return threadId ? `${channelName}:${chatId}:${threadId}` : `${channelName}:${chatId}`;
1478
+ case "single":
1479
+ return `${channelName}:__single__`;
1480
+ case "user":
1481
+ default:
1482
+ return `${channelName}:${senderId}:${chatId}`;
1483
+ }
1484
+ }
1485
+ sessionOptions(channelName) {
1486
+ const approvalMode = this.channelApprovalModes.get(channelName);
1487
+ return approvalMode ? { approvalMode } : void 0;
1488
+ }
1489
+ async resolve(channelName, senderId, chatId, threadId, cwd, isGroup, options) {
1490
+ const key = this.routingKey(channelName, senderId, chatId, options?.routingThreadId ?? threadId);
1491
+ const input = {
1492
+ channelName,
1493
+ senderId,
1494
+ chatId,
1495
+ threadId,
1496
+ cwd: cwd || this.defaultCwd,
1497
+ isGroup
1498
+ };
1499
+ let failedWaits = 0;
1500
+ for (; ; ) {
1501
+ const existing = this.toSession.get(key);
1502
+ if (existing && this.isLive(existing)) {
1503
+ this.promoteTargetToGroup(existing, isGroup);
1504
+ return existing;
1505
+ }
1506
+ const creating = this.creatingSessions.get(key);
1507
+ if (creating) {
1366
1508
  try {
1367
- this.assertOperationCurrent(operation);
1368
- const options = this.sessionOptions(entry.target.channelName);
1369
- const sessionId = await this.bridge.loadSession(entry.sessionId, entry.cwd, options, operation);
1509
+ const sessionId = await creating.promise;
1370
1510
  try {
1371
- this.assertOperationCurrent(operation);
1511
+ this.assertOperationResultCurrent(key, sessionId, creating);
1372
1512
  } catch (error) {
1373
- this.scheduleDiscardInvalidatedSession(sessionId, operation);
1513
+ this.scheduleDiscardInvalidatedSession(sessionId, creating);
1374
1514
  throw error;
1375
1515
  }
1376
- if (typeof sessionId !== "string" || sessionId.length === 0) {
1377
- throw new Error("Invalid restored session ID");
1378
- }
1379
- if (loadWindow.delete(sessionId)) {
1380
- throw new Error("Restored session died before routing completed");
1381
- }
1382
- this.toSession.set(key, sessionId);
1383
- this.toTarget.set(sessionId, entry.target);
1384
- this.toCwd.set(sessionId, entry.cwd);
1385
- reservation.resolve(sessionId);
1386
- if (sessionId !== entry.sessionId) {
1387
- changed = true;
1516
+ this.promoteTargetToGroup(sessionId, isGroup);
1517
+ return sessionId;
1518
+ } catch (error) {
1519
+ if (creating.invalidationError) {
1520
+ throw creating.invalidationError;
1388
1521
  }
1389
- restored++;
1390
- } catch (err) {
1391
- const reason = err instanceof Error ? err.message : String(err);
1392
- process2.stderr.write(`[SessionRouter] Failed to restore session ${sanitizeLogText(entry.sessionId, 128)} for key ${sanitizeLogText(key, 256)}: ${sanitizeLogText(reason, 512)}
1393
- `);
1394
- reservation.reject(new Error("Session restore failed", { cause: err }));
1395
- failed++;
1396
- changed = true;
1397
- } finally {
1398
- if (this.creatingSessions.get(key) === operation) {
1522
+ if (this.creatingSessions.get(key) === creating) {
1399
1523
  this.creatingSessions.delete(key);
1400
1524
  }
1401
- this.releaseRouteToken(key, operation);
1525
+ this.releaseRouteToken(key, creating);
1526
+ failedWaits++;
1527
+ if (failedWaits > 3)
1528
+ throw error;
1529
+ continue;
1402
1530
  }
1403
1531
  }
1404
- } finally {
1405
- this.endSessionLoad(loadWindow);
1406
- }
1407
- if (changed && restoreGeneration === this.lifecycleGeneration) {
1408
- this.persist();
1532
+ const operation = this.createSessionOperation(key, {
1533
+ channelName: input.channelName,
1534
+ senderId: input.senderId,
1535
+ chatId: input.chatId,
1536
+ threadId: input.threadId,
1537
+ isGroup: input.isGroup
1538
+ }, (currentOperation) => existing ? this.loadOrReplaceSession(key, existing, input, currentOperation) : this.createAndStoreSession(key, input, currentOperation));
1539
+ this.creatingSessions.set(key, operation);
1540
+ try {
1541
+ const sessionId = await operation.promise;
1542
+ try {
1543
+ this.assertOperationResultCurrent(key, sessionId, operation);
1544
+ } catch (error) {
1545
+ this.scheduleDiscardInvalidatedSession(sessionId, operation);
1546
+ throw error;
1547
+ }
1548
+ this.promoteTargetToGroup(sessionId, isGroup);
1549
+ return sessionId;
1550
+ } finally {
1551
+ if (this.creatingSessions.get(key) === operation) {
1552
+ this.creatingSessions.delete(key);
1553
+ }
1554
+ this.releaseRouteToken(key, operation);
1555
+ }
1409
1556
  }
1410
- return { restored, failed };
1411
1557
  }
1412
- dispose() {
1413
- this.lifecycleGeneration++;
1414
- for (const operation of this.creatingSessions.values()) {
1415
- this.invalidateOperation(operation);
1416
- }
1417
- this.toSession.clear();
1418
- this.toTarget.clear();
1419
- this.toCwd.clear();
1420
- this.creatingSessions.clear();
1421
- this.sessionLoadWindows.clear();
1422
- this.liveSessionIds.clear();
1423
- this.routeTokens.clear();
1558
+ isLive(sessionId) {
1559
+ return this.recoveryMode === "eager" || this.liveSessionIds.has(sessionId);
1424
1560
  }
1425
- /** Clear in-memory state and delete persist file. Used on clean shutdown. */
1426
- clearAll() {
1427
- this.dispose();
1428
- if (this.persistPath && existsSync3(this.persistPath)) {
1561
+ async createAndStoreSession(key, input, operation) {
1562
+ const loadWindow = this.beginSessionLoad();
1563
+ try {
1564
+ const sessionId = await this.createLiveSession(input.cwd, loadWindow, key, this.sessionOptions(input.channelName), operation, input.channelName);
1429
1565
  try {
1430
- unlinkSync(this.persistPath);
1431
- } catch {
1566
+ this.assertOperationCurrent(operation);
1567
+ } catch (error) {
1568
+ this.scheduleDiscardInvalidatedSession(sessionId, operation);
1569
+ throw error;
1432
1570
  }
1571
+ this.toSession.set(key, sessionId);
1572
+ this.toTarget.set(sessionId, {
1573
+ channelName: input.channelName,
1574
+ senderId: input.senderId,
1575
+ chatId: input.chatId,
1576
+ threadId: input.threadId,
1577
+ isGroup: input.isGroup
1578
+ });
1579
+ this.toCwd.set(sessionId, input.cwd);
1580
+ this.liveSessionIds.add(sessionId);
1581
+ this.persist();
1582
+ return sessionId;
1583
+ } finally {
1584
+ this.endSessionLoad(loadWindow);
1433
1585
  }
1434
1586
  }
1435
- readPersistedEntries() {
1436
- const persistPath = this.persistPath;
1437
- if (!persistPath || !existsSync3(persistPath))
1438
- return void 0;
1439
- let parsed;
1587
+ async loadOrReplaceSession(key, savedSessionId, input, operation) {
1588
+ const savedCwd = this.toCwd.get(savedSessionId) ?? input.cwd;
1589
+ const loadWindow = this.beginSessionLoad();
1440
1590
  try {
1441
- parsed = JSON.parse(readFileSync3(persistPath, "utf-8"));
1442
- } catch (error) {
1443
- const quarantinePath = `${persistPath}.corrupt-${Date.now()}`;
1444
1591
  try {
1445
- renameSync3(persistPath, quarantinePath);
1446
- } catch {
1447
- }
1448
- process2.stderr.write(`[SessionRouter] Corrupted persist file at ${sanitizeLogText(persistPath, 1024)}: ${sanitizeLogText(error instanceof Error ? error.message : String(error), 512)}
1592
+ const loadedSessionId = await this.bridge.loadSession(savedSessionId, savedCwd, this.sessionOptions(input.channelName), operation);
1593
+ try {
1594
+ this.assertOperationCurrent(operation);
1595
+ if (this.toSession.get(key) !== savedSessionId) {
1596
+ this.invalidateOperation(operation);
1597
+ this.assertOperationCurrent(operation);
1598
+ }
1599
+ } catch (error) {
1600
+ this.scheduleDiscardInvalidatedSession(loadedSessionId, operation);
1601
+ throw error;
1602
+ }
1603
+ if (typeof loadedSessionId !== "string" || loadedSessionId.length === 0 || loadWindow.delete(loadedSessionId)) {
1604
+ throw new Error("Invalid or dead restored session ID");
1605
+ }
1606
+ if (loadedSessionId !== savedSessionId) {
1607
+ const target = this.toTarget.get(savedSessionId);
1608
+ this.deleteByKey(key);
1609
+ this.toSession.set(key, loadedSessionId);
1610
+ if (target)
1611
+ this.toTarget.set(loadedSessionId, target);
1612
+ this.toCwd.set(loadedSessionId, savedCwd);
1613
+ this.persist();
1614
+ }
1615
+ this.liveSessionIds.add(loadedSessionId);
1616
+ return loadedSessionId;
1617
+ } catch (loadError) {
1618
+ this.assertOperationCurrent(operation);
1619
+ try {
1620
+ const replacement = await this.createLiveSession(input.cwd, loadWindow, key, this.sessionOptions(input.channelName), operation, input.channelName);
1621
+ try {
1622
+ this.assertOperationCurrent(operation);
1623
+ } catch (error) {
1624
+ this.scheduleDiscardInvalidatedSession(replacement, operation);
1625
+ throw error;
1626
+ }
1627
+ this.deleteByKey(key);
1628
+ this.toSession.set(key, replacement);
1629
+ this.toTarget.set(replacement, {
1630
+ channelName: input.channelName,
1631
+ senderId: input.senderId,
1632
+ chatId: input.chatId,
1633
+ threadId: input.threadId,
1634
+ isGroup: input.isGroup
1635
+ });
1636
+ this.toCwd.set(replacement, input.cwd);
1637
+ this.liveSessionIds.add(replacement);
1638
+ this.persist();
1639
+ process2.stderr.write(`[SessionRouter] Replaced unavailable session ${sanitizeLogText(savedSessionId, 128)} for key ${sanitizeLogText(key, 256)} after load failed: ${sanitizeLogText(loadError instanceof Error ? loadError.message : String(loadError), 512)}
1449
1640
  `);
1450
- return void 0;
1451
- }
1452
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1453
- const quarantinePath = `${persistPath}.corrupt-${Date.now()}`;
1454
- try {
1455
- renameSync3(persistPath, quarantinePath);
1456
- } catch {
1457
- }
1458
- process2.stderr.write(`[SessionRouter] Invalid route store at ${sanitizeLogText(persistPath, 1024)}: expected an object
1641
+ return replacement;
1642
+ } catch (createError) {
1643
+ this.assertOperationCurrent(operation);
1644
+ process2.stderr.write(`[SessionRouter] Failed to load session ${sanitizeLogText(savedSessionId, 128)} for key ${sanitizeLogText(key, 256)} (${sanitizeLogText(loadError instanceof Error ? loadError.message : String(loadError), 512)}) and failed to create a replacement (${sanitizeLogText(createError instanceof Error ? createError.message : String(createError), 512)})
1459
1645
  `);
1460
- return void 0;
1461
- }
1462
- const entries = {};
1463
- const droppedKeys = [];
1464
- for (const [key, value] of Object.entries(parsed)) {
1465
- if (this.isPersistedEntry(value))
1466
- entries[key] = value;
1467
- else
1468
- droppedKeys.push(key);
1646
+ throw createError;
1647
+ }
1648
+ }
1649
+ } finally {
1650
+ this.endSessionLoad(loadWindow);
1469
1651
  }
1470
- return { entries, dropped: droppedKeys.length, droppedKeys };
1471
1652
  }
1472
- isPersistedEntry(value) {
1473
- if (typeof value !== "object" || value === null)
1474
- return false;
1475
- const entry = value;
1476
- const target = entry["target"];
1477
- if (typeof target !== "object" || target === null)
1478
- return false;
1479
- const typedTarget = target;
1480
- return typeof entry["sessionId"] === "string" && entry["sessionId"].length > 0 && typeof entry["cwd"] === "string" && entry["cwd"].length > 0 && typeof typedTarget["channelName"] === "string" && typeof typedTarget["senderId"] === "string" && typeof typedTarget["chatId"] === "string" && (typedTarget["threadId"] === void 0 || typeof typedTarget["threadId"] === "string") && (typedTarget["isGroup"] === void 0 || typeof typedTarget["isGroup"] === "boolean");
1653
+ getTarget(sessionId) {
1654
+ return this.toTarget.get(sessionId);
1481
1655
  }
1482
- persist() {
1483
- if (!this.persistPath)
1484
- return;
1485
- const data = {};
1486
- for (const [key, sessionId] of this.toSession) {
1487
- const target = this.toTarget.get(sessionId);
1488
- if (!target)
1489
- continue;
1490
- data[key] = {
1491
- sessionId,
1492
- target,
1493
- cwd: this.toCwd.get(sessionId) ?? this.defaultCwd
1494
- };
1656
+ getSession(channelName, senderId, chatId, threadId) {
1657
+ return this.toSession.get(this.routingKey(channelName, senderId, chatId, threadId));
1658
+ }
1659
+ hasSession(channelName, senderId, chatId, threadId) {
1660
+ const scope = this.channelScopes.get(channelName) || this.defaultScope;
1661
+ if (chatId) {
1662
+ return this.toSession.has(this.routingKey(channelName, senderId, chatId, threadId));
1495
1663
  }
1496
- const dir = dirname3(this.persistPath);
1497
- const tempPath = join4(dir, `${Date.now()}-${process2.pid}-${Math.random().toString(16).slice(2)}.tmp`);
1498
- try {
1499
- mkdirSync3(dir, { recursive: true, mode: 448 });
1500
- try {
1501
- chmodSync2(dir, 448);
1502
- } catch {
1664
+ if (scope === "single") {
1665
+ return false;
1666
+ }
1667
+ for (const target of this.toTarget.values()) {
1668
+ if (target.channelName === channelName && target.senderId === senderId) {
1669
+ return true;
1503
1670
  }
1504
- writeFileSync3(tempPath, JSON.stringify(data, null, 2), {
1505
- encoding: "utf-8",
1506
- mode: 384
1507
- });
1508
- renameSync3(tempPath, this.persistPath);
1509
- try {
1510
- chmodSync2(this.persistPath, 384);
1511
- } catch {
1671
+ }
1672
+ return false;
1673
+ }
1674
+ /**
1675
+ * Remove session(s) for the given sender. Returns the removed session IDs.
1676
+ */
1677
+ removeSession(channelName, senderId, chatId, threadId) {
1678
+ const removedIds = [];
1679
+ const scope = this.channelScopes.get(channelName) || this.defaultScope;
1680
+ if (chatId) {
1681
+ const key = this.routingKey(channelName, senderId, chatId, threadId);
1682
+ this.invalidateRouteOperation(key);
1683
+ const sessionId = this.deleteByKey(key);
1684
+ if (sessionId)
1685
+ removedIds.push(sessionId);
1686
+ } else if (scope === "single") {
1687
+ return removedIds;
1688
+ } else {
1689
+ for (const [k, mappedSessionId] of [...this.toSession.entries()]) {
1690
+ const target = this.toTarget.get(mappedSessionId);
1691
+ if (target?.channelName === channelName && target.senderId === senderId) {
1692
+ this.invalidateRouteOperation(k);
1693
+ const sessionId = this.deleteByKey(k);
1694
+ if (sessionId)
1695
+ removedIds.push(sessionId);
1696
+ }
1512
1697
  }
1513
- } catch (error) {
1514
- process2.stderr.write(`[SessionRouter] Failed to persist routes at ${sanitizeLogText(this.persistPath, 1024)}: ${sanitizeLogText(error instanceof Error ? error.message : String(error), 512)}
1515
- `);
1516
- } finally {
1517
- try {
1518
- rmSync(tempPath, { force: true });
1519
- } catch {
1698
+ for (const [key, operation] of [...this.creatingSessions]) {
1699
+ if (operation.target.channelName === channelName && operation.target.senderId === senderId) {
1700
+ this.invalidateRouteOperation(key);
1701
+ }
1520
1702
  }
1521
1703
  }
1704
+ if (removedIds.length > 0)
1705
+ this.persist();
1706
+ return removedIds;
1522
1707
  }
1523
- async createLiveSession(cwd, loadWindow, routingKey, options, operation, sourceId) {
1524
- const maxAttempts = 2;
1525
- let lastDeadSessionId;
1526
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
1527
- const sessionId = await this.bridge.newSession(cwd, { ...options, sourceId }, operation);
1528
- try {
1529
- this.assertOperationCurrent(operation);
1530
- } catch (error) {
1531
- this.scheduleDiscardInvalidatedSession(sessionId, operation);
1532
- throw error;
1533
- }
1534
- if (typeof sessionId !== "string" || sessionId.length === 0) {
1535
- throw new Error("Invalid session ID from bridge");
1708
+ /** Remove a session mapping by daemon/ACP session ID. */
1709
+ removeSessionId(sessionId) {
1710
+ let removed = false;
1711
+ for (const [key, mappedSessionId] of [...this.toSession.entries()]) {
1712
+ if (mappedSessionId === sessionId) {
1713
+ this.invalidateRouteOperation(key);
1714
+ this.toSession.delete(key);
1715
+ removed = true;
1536
1716
  }
1537
- if (!loadWindow.delete(sessionId)) {
1538
- return sessionId;
1717
+ }
1718
+ if (this.toTarget.delete(sessionId)) {
1719
+ removed = true;
1720
+ }
1721
+ if (this.toCwd.delete(sessionId)) {
1722
+ removed = true;
1723
+ }
1724
+ this.liveSessionIds.delete(sessionId);
1725
+ if (!removed && this.sessionLoadWindows.size > 0) {
1726
+ for (const loadWindow of this.sessionLoadWindows) {
1727
+ loadWindow.add(sessionId);
1539
1728
  }
1540
- lastDeadSessionId = sessionId;
1541
1729
  }
1542
- throw new Error(`Session ${lastDeadSessionId ?? "unknown"} died before routing completed (${maxAttempts}/${maxAttempts} attempts, key ${routingKey})`);
1543
- }
1544
- beginSessionLoad() {
1545
- const loadWindow = /* @__PURE__ */ new Set();
1546
- this.sessionLoadWindows.add(loadWindow);
1547
- return loadWindow;
1730
+ if (removed) {
1731
+ this.persist();
1732
+ }
1733
+ return removed;
1548
1734
  }
1549
- createSessionOperation(key, target, run) {
1550
- let routeToken = this.routeTokens.get(key);
1551
- if (!routeToken) {
1552
- routeToken = {};
1553
- this.routeTokens.set(key, routeToken);
1735
+ handleSessionDied(sessionId) {
1736
+ if (this.recoveryMode === "eager") {
1737
+ return this.removeSessionId(sessionId);
1554
1738
  }
1555
- const operation = {
1556
- promise: Promise.resolve(""),
1557
- target,
1558
- lifecycleGeneration: this.lifecycleGeneration,
1559
- routeToken
1560
- };
1561
- operation.promise = Promise.resolve().then(() => run(operation)).catch((error) => {
1562
- this.assertOperationCurrent(operation);
1563
- throw error;
1564
- });
1565
- return operation;
1739
+ const known = this.toTarget.has(sessionId);
1740
+ this.liveSessionIds.delete(sessionId);
1741
+ for (const loadWindow of this.sessionLoadWindows) {
1742
+ loadWindow.add(sessionId);
1743
+ }
1744
+ return known;
1566
1745
  }
1567
- invalidateRouteOperation(key) {
1568
- this.routeTokens.delete(key);
1569
- const operation = this.creatingSessions.get(key);
1570
- if (!operation)
1746
+ deleteByKey(key) {
1747
+ const sessionId = this.toSession.get(key);
1748
+ if (!sessionId)
1749
+ return null;
1750
+ this.toSession.delete(key);
1751
+ this.toTarget.delete(sessionId);
1752
+ this.toCwd.delete(sessionId);
1753
+ this.liveSessionIds.delete(sessionId);
1754
+ return sessionId;
1755
+ }
1756
+ promoteTargetToGroup(sessionId, isGroup) {
1757
+ const current = this.toTarget.get(sessionId);
1758
+ if (!current)
1571
1759
  return;
1572
- this.invalidateOperation(operation);
1573
- this.creatingSessions.delete(key);
1760
+ if (current.isGroup === true || isGroup !== true)
1761
+ return;
1762
+ this.toTarget.set(sessionId, { ...current, isGroup: true });
1763
+ this.persist();
1574
1764
  }
1575
- invalidateOperation(operation) {
1576
- operation.invalidationError ??= new Error("Session route operation was invalidated");
1765
+ /** Get all session entries for crash recovery. */
1766
+ getAll() {
1767
+ const entries = [];
1768
+ for (const [key, sessionId] of this.toSession) {
1769
+ const target = this.toTarget.get(sessionId);
1770
+ if (target) {
1771
+ entries.push({ key, sessionId, target });
1772
+ }
1773
+ }
1774
+ return entries;
1577
1775
  }
1578
- assertOperationCurrent(operation) {
1579
- if (operation.lifecycleGeneration !== this.lifecycleGeneration) {
1580
- this.invalidateOperation(operation);
1776
+ restoreRoutes() {
1777
+ if (this.recoveryMode !== "lazy") {
1778
+ throw new Error("restoreRoutes requires lazy recovery mode");
1581
1779
  }
1582
- if (operation.invalidationError) {
1583
- throw operation.invalidationError;
1780
+ const persisted = this.readPersistedEntries();
1781
+ if (!persisted)
1782
+ return { restored: 0, dropped: 0 };
1783
+ this.dispose();
1784
+ let restored = 0;
1785
+ for (const [key, entry] of Object.entries(persisted.entries)) {
1786
+ this.toSession.set(key, entry.sessionId);
1787
+ this.toTarget.set(entry.sessionId, entry.target);
1788
+ this.toCwd.set(entry.sessionId, entry.cwd);
1789
+ restored++;
1584
1790
  }
1791
+ if (persisted.dropped > 0)
1792
+ this.persist();
1793
+ return { restored, dropped: persisted.dropped };
1585
1794
  }
1586
- assertOperationResultCurrent(key, sessionId, operation) {
1587
- if (operation.routeToken !== this.routeTokens.get(key)) {
1588
- this.invalidateOperation(operation);
1795
+ /**
1796
+ * Restore session mappings from a previous bridge.
1797
+ * Called after bridge restart — attempts loadSession for each saved mapping.
1798
+ * Failed loads are dropped (new session on next message).
1799
+ */
1800
+ async restoreSessions() {
1801
+ const persisted = this.readPersistedEntries();
1802
+ if (!persisted)
1803
+ return { restored: 0, failed: 0 };
1804
+ const entries = persisted.entries;
1805
+ const restoreGeneration = this.lifecycleGeneration;
1806
+ let restored = 0;
1807
+ let failed = 0;
1808
+ let changed = persisted.dropped > 0;
1809
+ const reservations = /* @__PURE__ */ new Map();
1810
+ for (const key of persisted.droppedKeys) {
1811
+ this.deleteByKey(key);
1589
1812
  }
1590
- if (this.toSession.get(key) !== sessionId) {
1813
+ for (const key of Object.keys(entries)) {
1814
+ this.deleteByKey(key);
1815
+ const reservation = this.createSessionReservation();
1816
+ reservation.promise.catch(() => void 0);
1817
+ const operation = this.createSessionOperation(key, entries[key].target, () => reservation.promise);
1818
+ operation.promise.catch(() => void 0);
1819
+ this.creatingSessions.set(key, operation);
1820
+ reservations.set(key, { reservation, operation });
1821
+ }
1822
+ const loadWindow = this.beginSessionLoad();
1823
+ try {
1824
+ for (const [key, entry] of Object.entries(entries)) {
1825
+ const reserved = reservations.get(key);
1826
+ if (!reserved)
1827
+ continue;
1828
+ const { reservation, operation } = reserved;
1829
+ try {
1830
+ this.assertOperationCurrent(operation);
1831
+ const options = this.sessionOptions(entry.target.channelName);
1832
+ const sessionId = await this.bridge.loadSession(entry.sessionId, entry.cwd, options, operation);
1833
+ try {
1834
+ this.assertOperationCurrent(operation);
1835
+ } catch (error) {
1836
+ this.scheduleDiscardInvalidatedSession(sessionId, operation);
1837
+ throw error;
1838
+ }
1839
+ if (typeof sessionId !== "string" || sessionId.length === 0) {
1840
+ throw new Error("Invalid restored session ID");
1841
+ }
1842
+ if (loadWindow.delete(sessionId)) {
1843
+ throw new Error("Restored session died before routing completed");
1844
+ }
1845
+ this.toSession.set(key, sessionId);
1846
+ this.toTarget.set(sessionId, entry.target);
1847
+ this.toCwd.set(sessionId, entry.cwd);
1848
+ reservation.resolve(sessionId);
1849
+ if (sessionId !== entry.sessionId) {
1850
+ changed = true;
1851
+ }
1852
+ restored++;
1853
+ } catch (err) {
1854
+ const reason = err instanceof Error ? err.message : String(err);
1855
+ process2.stderr.write(`[SessionRouter] Failed to restore session ${sanitizeLogText(entry.sessionId, 128)} for key ${sanitizeLogText(key, 256)}: ${sanitizeLogText(reason, 512)}
1856
+ `);
1857
+ reservation.reject(new Error("Session restore failed", { cause: err }));
1858
+ failed++;
1859
+ changed = true;
1860
+ } finally {
1861
+ if (this.creatingSessions.get(key) === operation) {
1862
+ this.creatingSessions.delete(key);
1863
+ }
1864
+ this.releaseRouteToken(key, operation);
1865
+ }
1866
+ }
1867
+ } finally {
1868
+ this.endSessionLoad(loadWindow);
1869
+ }
1870
+ if (changed && restoreGeneration === this.lifecycleGeneration) {
1871
+ this.persist();
1872
+ }
1873
+ return { restored, failed };
1874
+ }
1875
+ dispose() {
1876
+ this.lifecycleGeneration++;
1877
+ for (const operation of this.creatingSessions.values()) {
1591
1878
  this.invalidateOperation(operation);
1592
1879
  }
1593
- this.assertOperationCurrent(operation);
1880
+ this.toSession.clear();
1881
+ this.toTarget.clear();
1882
+ this.toCwd.clear();
1883
+ this.creatingSessions.clear();
1884
+ this.sessionLoadWindows.clear();
1885
+ this.liveSessionIds.clear();
1886
+ this.routeTokens.clear();
1594
1887
  }
1595
- releaseRouteToken(key, operation) {
1596
- if (this.routeTokens.get(key) === operation.routeToken && !this.toSession.has(key) && !this.creatingSessions.has(key)) {
1597
- this.routeTokens.delete(key);
1888
+ /** Clear in-memory state and delete persist file. Used on clean shutdown. */
1889
+ clearAll() {
1890
+ this.dispose();
1891
+ if (this.persistPath && existsSync3(this.persistPath)) {
1892
+ try {
1893
+ unlinkSync(this.persistPath);
1894
+ } catch {
1895
+ }
1598
1896
  }
1599
1897
  }
1600
- scheduleDiscardInvalidatedSession(sessionId, operation) {
1601
- if ([...this.toSession.values()].includes(sessionId))
1602
- return;
1898
+ readPersistedEntries() {
1899
+ const persistPath = this.persistPath;
1900
+ if (!persistPath || !existsSync3(persistPath))
1901
+ return void 0;
1902
+ let parsed;
1603
1903
  try {
1604
- void this.bridge.discardSession?.(sessionId, operation).catch(() => void 0);
1605
- } catch {
1904
+ parsed = JSON.parse(readFileSync3(persistPath, "utf-8"));
1905
+ } catch (error) {
1906
+ const quarantinePath = `${persistPath}.corrupt-${Date.now()}`;
1907
+ try {
1908
+ renameSync3(persistPath, quarantinePath);
1909
+ } catch {
1910
+ }
1911
+ process2.stderr.write(`[SessionRouter] Corrupted persist file at ${sanitizeLogText(persistPath, 1024)}: ${sanitizeLogText(error instanceof Error ? error.message : String(error), 512)}
1912
+ `);
1913
+ return void 0;
1606
1914
  }
1607
- }
1608
- createSessionReservation() {
1609
- let resolveReservation;
1610
- let rejectReservation;
1611
- const promise = new Promise((resolve3, reject) => {
1612
- resolveReservation = resolve3;
1613
- rejectReservation = reject;
1614
- });
1915
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1916
+ const quarantinePath = `${persistPath}.corrupt-${Date.now()}`;
1917
+ try {
1918
+ renameSync3(persistPath, quarantinePath);
1919
+ } catch {
1920
+ }
1921
+ process2.stderr.write(`[SessionRouter] Invalid route store at ${sanitizeLogText(persistPath, 1024)}: expected an object
1922
+ `);
1923
+ return void 0;
1924
+ }
1925
+ const entries = {};
1926
+ const droppedKeys = [];
1927
+ for (const [key, value] of Object.entries(parsed)) {
1928
+ if (this.isPersistedEntry(value))
1929
+ entries[key] = value;
1930
+ else
1931
+ droppedKeys.push(key);
1932
+ }
1933
+ return { entries, dropped: droppedKeys.length, droppedKeys };
1934
+ }
1935
+ isPersistedEntry(value) {
1936
+ if (typeof value !== "object" || value === null)
1937
+ return false;
1938
+ const entry = value;
1939
+ const target = entry["target"];
1940
+ if (typeof target !== "object" || target === null)
1941
+ return false;
1942
+ const typedTarget = target;
1943
+ return typeof entry["sessionId"] === "string" && entry["sessionId"].length > 0 && typeof entry["cwd"] === "string" && entry["cwd"].length > 0 && typeof typedTarget["channelName"] === "string" && typeof typedTarget["senderId"] === "string" && typeof typedTarget["chatId"] === "string" && (typedTarget["threadId"] === void 0 || typeof typedTarget["threadId"] === "string") && (typedTarget["isGroup"] === void 0 || typeof typedTarget["isGroup"] === "boolean");
1944
+ }
1945
+ persist() {
1946
+ if (!this.persistPath)
1947
+ return;
1948
+ const data = {};
1949
+ for (const [key, sessionId] of this.toSession) {
1950
+ const target = this.toTarget.get(sessionId);
1951
+ if (!target)
1952
+ continue;
1953
+ data[key] = {
1954
+ sessionId,
1955
+ target,
1956
+ cwd: this.toCwd.get(sessionId) ?? this.defaultCwd
1957
+ };
1958
+ }
1959
+ const dir = dirname3(this.persistPath);
1960
+ const tempPath = join4(dir, `${Date.now()}-${process2.pid}-${Math.random().toString(16).slice(2)}.tmp`);
1961
+ try {
1962
+ mkdirSync3(dir, { recursive: true, mode: 448 });
1963
+ try {
1964
+ chmodSync2(dir, 448);
1965
+ } catch {
1966
+ }
1967
+ writeFileSync3(tempPath, JSON.stringify(data, null, 2), {
1968
+ encoding: "utf-8",
1969
+ mode: 384
1970
+ });
1971
+ renameSync3(tempPath, this.persistPath);
1972
+ try {
1973
+ chmodSync2(this.persistPath, 384);
1974
+ } catch {
1975
+ }
1976
+ } catch (error) {
1977
+ process2.stderr.write(`[SessionRouter] Failed to persist routes at ${sanitizeLogText(this.persistPath, 1024)}: ${sanitizeLogText(error instanceof Error ? error.message : String(error), 512)}
1978
+ `);
1979
+ } finally {
1980
+ try {
1981
+ rmSync(tempPath, { force: true });
1982
+ } catch {
1983
+ }
1984
+ }
1985
+ }
1986
+ async createLiveSession(cwd, loadWindow, routingKey, options, operation, sourceId) {
1987
+ const maxAttempts = 2;
1988
+ let lastDeadSessionId;
1989
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
1990
+ const sessionId = await this.bridge.newSession(cwd, { ...options, sourceId }, operation);
1991
+ try {
1992
+ this.assertOperationCurrent(operation);
1993
+ } catch (error) {
1994
+ this.scheduleDiscardInvalidatedSession(sessionId, operation);
1995
+ throw error;
1996
+ }
1997
+ if (typeof sessionId !== "string" || sessionId.length === 0) {
1998
+ throw new Error("Invalid session ID from bridge");
1999
+ }
2000
+ if (!loadWindow.delete(sessionId)) {
2001
+ return sessionId;
2002
+ }
2003
+ lastDeadSessionId = sessionId;
2004
+ }
2005
+ throw new Error(`Session ${lastDeadSessionId ?? "unknown"} died before routing completed (${maxAttempts}/${maxAttempts} attempts, key ${routingKey})`);
2006
+ }
2007
+ beginSessionLoad() {
2008
+ const loadWindow = /* @__PURE__ */ new Set();
2009
+ this.sessionLoadWindows.add(loadWindow);
2010
+ return loadWindow;
2011
+ }
2012
+ createSessionOperation(key, target, run) {
2013
+ let routeToken = this.routeTokens.get(key);
2014
+ if (!routeToken) {
2015
+ routeToken = {};
2016
+ this.routeTokens.set(key, routeToken);
2017
+ }
2018
+ const operation = {
2019
+ promise: Promise.resolve(""),
2020
+ target,
2021
+ lifecycleGeneration: this.lifecycleGeneration,
2022
+ routeToken
2023
+ };
2024
+ operation.promise = Promise.resolve().then(() => run(operation)).catch((error) => {
2025
+ this.assertOperationCurrent(operation);
2026
+ throw error;
2027
+ });
2028
+ return operation;
2029
+ }
2030
+ invalidateRouteOperation(key) {
2031
+ this.routeTokens.delete(key);
2032
+ const operation = this.creatingSessions.get(key);
2033
+ if (!operation)
2034
+ return;
2035
+ this.invalidateOperation(operation);
2036
+ this.creatingSessions.delete(key);
2037
+ }
2038
+ invalidateOperation(operation) {
2039
+ operation.invalidationError ??= new Error("Session route operation was invalidated");
2040
+ }
2041
+ assertOperationCurrent(operation) {
2042
+ if (operation.lifecycleGeneration !== this.lifecycleGeneration) {
2043
+ this.invalidateOperation(operation);
2044
+ }
2045
+ if (operation.invalidationError) {
2046
+ throw operation.invalidationError;
2047
+ }
2048
+ }
2049
+ assertOperationResultCurrent(key, sessionId, operation) {
2050
+ if (operation.routeToken !== this.routeTokens.get(key)) {
2051
+ this.invalidateOperation(operation);
2052
+ }
2053
+ if (this.toSession.get(key) !== sessionId) {
2054
+ this.invalidateOperation(operation);
2055
+ }
2056
+ this.assertOperationCurrent(operation);
2057
+ }
2058
+ releaseRouteToken(key, operation) {
2059
+ if (this.routeTokens.get(key) === operation.routeToken && !this.toSession.has(key) && !this.creatingSessions.has(key)) {
2060
+ this.routeTokens.delete(key);
2061
+ }
2062
+ }
2063
+ scheduleDiscardInvalidatedSession(sessionId, operation) {
2064
+ if ([...this.toSession.values()].includes(sessionId))
2065
+ return;
2066
+ try {
2067
+ void this.bridge.discardSession?.(sessionId, operation).catch(() => void 0);
2068
+ } catch {
2069
+ }
2070
+ }
2071
+ createSessionReservation() {
2072
+ let resolveReservation;
2073
+ let rejectReservation;
2074
+ const promise = new Promise((resolve3, reject) => {
2075
+ resolveReservation = resolve3;
2076
+ rejectReservation = reject;
2077
+ });
1615
2078
  return {
1616
2079
  promise,
1617
2080
  resolve: resolveReservation,
@@ -1652,6 +2115,7 @@ var ChannelLoopScheduler = class {
1652
2115
  runningTick;
1653
2116
  inFlightJobs = /* @__PURE__ */ new Map();
1654
2117
  generation = 0;
2118
+ recoveryEpoch = 0;
1655
2119
  constructor(options) {
1656
2120
  this.store = options.store;
1657
2121
  this.channels = options.channels;
@@ -1691,6 +2155,14 @@ var ChannelLoopScheduler = class {
1691
2155
  this.generation++;
1692
2156
  this.inFlightJobs.clear();
1693
2157
  }
2158
+ /**
2159
+ * Mark that a bridge recovery started. Loop prompts already in flight that
2160
+ * the bridge replacement aborts are cleared instead of counted as agent
2161
+ * failures, so a crash-restart cannot auto-disable a loop.
2162
+ */
2163
+ markBridgeRecovery() {
2164
+ this.recoveryEpoch++;
2165
+ }
1694
2166
  async reconcileStartupState() {
1695
2167
  const jobs = await this.store.list();
1696
2168
  const staleRunning = jobs.filter((job) => job.runningSince);
@@ -1761,6 +2233,7 @@ var ChannelLoopScheduler = class {
1761
2233
  return;
1762
2234
  if (this.generation !== generation)
1763
2235
  return;
2236
+ let recoveryEpoch = this.recoveryEpoch;
1764
2237
  const runningSince = now.toISOString();
1765
2238
  let resultPreview;
1766
2239
  try {
@@ -1772,6 +2245,7 @@ var ChannelLoopScheduler = class {
1772
2245
  await this.clearRunningSince(latestJob.id, runningSince);
1773
2246
  return;
1774
2247
  }
2248
+ recoveryEpoch = this.recoveryEpoch;
1775
2249
  resultPreview = await channel.runLoopPrompt(latestJob, {
1776
2250
  timeoutMs: this.loopTimeoutMs,
1777
2251
  shouldContinue: /* @__PURE__ */ __name(async () => {
@@ -1800,6 +2274,19 @@ var ChannelLoopScheduler = class {
1800
2274
  await this.clearRunningSince(latestJob.id, runningSince);
1801
2275
  return;
1802
2276
  }
2277
+ if (recoveryEpoch !== this.recoveryEpoch) {
2278
+ try {
2279
+ await this.store.update(latestJob.id, {
2280
+ lastFinishedAt: this.now().toISOString(),
2281
+ lastStatus: "error",
2282
+ lastError: truncateError(err instanceof Error ? err.message : String(err)),
2283
+ runningSince: void 0
2284
+ });
2285
+ } catch {
2286
+ await this.clearRunningSince(latestJob.id, runningSince);
2287
+ }
2288
+ return;
2289
+ }
1803
2290
  await this.recordFailure(currentJob2, now, err instanceof Error ? err.message : String(err));
1804
2291
  return;
1805
2292
  }
@@ -2258,6 +2745,10 @@ var ChannelBase = class {
2258
2745
  __name(this, "ChannelBase");
2259
2746
  }
2260
2747
  config;
2748
+ /**
2749
+ * Recovery invariant: session-resolution and prompt-capture paths must await
2750
+ * waitForBridgeRecovery() immediately before that operation.
2751
+ */
2261
2752
  bridge;
2262
2753
  groupGate;
2263
2754
  dmGate;
@@ -2269,6 +2760,8 @@ var ChannelBase = class {
2269
2760
  memoryScope;
2270
2761
  /** Resolved proxy URL, available to subclasses for adapter-specific clients. */
2271
2762
  proxy;
2763
+ /** Adapter-owned persistent state directory, when supplied by the runtime. */
2764
+ stateDir;
2272
2765
  channelMemory;
2273
2766
  memoryIntentClassifier;
2274
2767
  channelMemoryRecallObserver;
@@ -2284,6 +2777,7 @@ var ChannelBase = class {
2284
2777
  /** Per-session promise chain to serialize prompt + send (followup mode). */
2285
2778
  sessionQueues = /* @__PURE__ */ new Map();
2286
2779
  registerBridgeEvents;
2780
+ bridgeRecovery;
2287
2781
  /**
2288
2782
  * Per-session generation, bumped by /clear. A queued followup turn captures the
2289
2783
  * generation when it enqueues and bails if /clear bumped it before the turn ran,
@@ -2564,6 +3058,7 @@ var ChannelBase = class {
2564
3058
  this.config = config;
2565
3059
  this.bridge = bridge;
2566
3060
  this.proxy = options?.proxy;
3061
+ this.stateDir = options?.stateDir;
2567
3062
  this.identity = Object.freeze(this.resolveIdentity(name, config));
2568
3063
  this.memoryScope = Object.freeze(this.resolveMemoryScope(name, config));
2569
3064
  this.channelMemory = options?.channelMemory;
@@ -2572,6 +3067,7 @@ var ChannelBase = class {
2572
3067
  this.groupHistory = new GroupHistoryStore(options?.groupHistoryPath ?? join5(getGlobalQwenDir(), "channels", `${encodeURIComponent(name)}-group-history.jsonl`));
2573
3068
  this.loopController = options?.loopController;
2574
3069
  this.observedContacts = options?.observedContacts;
3070
+ this.bridgeRecovery = options?.bridgeRecovery;
2575
3071
  this.groupGate = new GroupGate(config.groupPolicy, config.groups);
2576
3072
  this.dmGate = new DmGate(config.dmPolicy);
2577
3073
  const pairingStore = config.senderPolicy === "pairing" ? new PairingStore(name, config.cwd) : void 0;
@@ -2740,7 +3236,7 @@ var ChannelBase = class {
2740
3236
  ensureOutputSegment(sessionId, active) {
2741
3237
  if (!active.owner)
2742
3238
  return void 0;
2743
- const segmentId = active.activeSegmentId ?? randomUUID();
3239
+ const segmentId = active.activeSegmentId ?? randomUUID2();
2744
3240
  const context = this.outputSegmentContext(sessionId, active, segmentId);
2745
3241
  if (context)
2746
3242
  active.activeSegmentId = segmentId;
@@ -3005,6 +3501,7 @@ var ChannelBase = class {
3005
3501
  await this.loopController?.disable(job.id);
3006
3502
  throw new Error(`Loop ${job.id} target is no longer authorized.`);
3007
3503
  }
3504
+ await this.waitForBridgeRecovery();
3008
3505
  const sessionId = await this.router.resolve(this.name, job.target.senderId, job.target.chatId, job.target.threadId, job.cwd, job.target.isGroup);
3009
3506
  const label = sanitizeQuotedText(job.label || job.id, 80);
3010
3507
  const createdBy = sanitizeSenderName(job.createdBy || "unknown");
@@ -3062,7 +3559,7 @@ ${promptText}` : promptText;
3062
3559
  doneResolve = resolve3;
3063
3560
  });
3064
3561
  const promptState = {
3065
- runId: randomUUID(),
3562
+ runId: randomUUID2(),
3066
3563
  cancelled: false,
3067
3564
  done,
3068
3565
  resolve: doneResolve,
@@ -3112,6 +3609,7 @@ ${promptText}` : promptText;
3112
3609
  heldChunks.length = 0;
3113
3610
  this.onResponseBoundary(job.target.chatId, sessionId);
3114
3611
  }, "onResponseBoundary");
3612
+ await this.waitForBridgeRecovery();
3115
3613
  const promptBridge = this.bridge;
3116
3614
  promptBridge.on("textChunk", onChunk);
3117
3615
  promptBridge.on("responseBoundary", onResponseBoundary);
@@ -3219,6 +3717,7 @@ ${promptText}` : promptText;
3219
3717
  }
3220
3718
  async runWebhookTask(task, options = {}) {
3221
3719
  const target = this.resolveWebhookTaskTarget(task);
3720
+ await this.waitForBridgeRecovery();
3222
3721
  const sessionId = await this.router.resolve(this.name, target.senderId, target.chatId, target.threadId, this.config.cwd, target.isGroup, {
3223
3722
  routingThreadId: this.webhookRoutingThreadId(task, target)
3224
3723
  });
@@ -3275,7 +3774,7 @@ ${promptText}` : promptText;
3275
3774
  doneResolve = resolve3;
3276
3775
  });
3277
3776
  const promptState = {
3278
- runId: randomUUID(),
3777
+ runId: randomUUID2(),
3279
3778
  cancelled: false,
3280
3779
  done,
3281
3780
  resolve: doneResolve,
@@ -3318,6 +3817,7 @@ ${promptText}` : promptText;
3318
3817
  releaseHeldChunks();
3319
3818
  }
3320
3819
  }, "onChunk");
3820
+ await this.waitForBridgeRecovery();
3321
3821
  const promptBridge = this.bridge;
3322
3822
  promptBridge.on("textChunk", onChunk);
3323
3823
  try {
@@ -5157,6 +5657,17 @@ ${promptText}`;
5157
5657
  markPreflighted(envelope) {
5158
5658
  this.preflightedEnvelopes.add(envelope);
5159
5659
  }
5660
+ /** Wait until the currently active bridge recovery, if any, has completed. */
5661
+ async waitForBridgeRecovery() {
5662
+ let completedRecovery;
5663
+ while (true) {
5664
+ const bridgeRecovery = this.bridgeRecovery?.();
5665
+ if (!bridgeRecovery || bridgeRecovery === completedRecovery)
5666
+ return;
5667
+ await bridgeRecovery;
5668
+ completedRecovery = bridgeRecovery;
5669
+ }
5670
+ }
5160
5671
  /**
5161
5672
  * Process an inbound message after preflight gates have passed.
5162
5673
  *
@@ -5165,6 +5676,7 @@ ${promptText}`;
5165
5676
  * already preflighted, such as during collect-buffer drain.
5166
5677
  */
5167
5678
  async processInbound(envelope) {
5679
+ await this.waitForBridgeRecovery();
5168
5680
  if (!this.preflightedEnvelopes.delete(envelope)) {
5169
5681
  throw new Error("processInbound called without a successful preflightInbound check.");
5170
5682
  }
@@ -5215,6 +5727,7 @@ ${promptText}`;
5215
5727
  return;
5216
5728
  }
5217
5729
  }
5730
+ await this.waitForBridgeRecovery();
5218
5731
  const sessionId = await this.router.resolve(this.name, envelope.senderId, envelope.chatId, envelope.threadId, this.config.cwd, envelope.isGroup);
5219
5732
  if (bangText.startsWith("!")) {
5220
5733
  const cmd = bangText.slice(1).trim();
@@ -5405,7 +5918,7 @@ ${promptToSend}`;
5405
5918
  doneResolve = r;
5406
5919
  });
5407
5920
  const promptState = {
5408
- runId: randomUUID(),
5921
+ runId: randomUUID2(),
5409
5922
  owner: {
5410
5923
  kind: "channel_user",
5411
5924
  id: envelope.senderId
@@ -5473,6 +5986,7 @@ ${promptToSend}`;
5473
5986
  void this.notifyOutputSegmentEnd(envelope.chatId, sessionId, segment, "response_boundary");
5474
5987
  streamer?.stop();
5475
5988
  }, "onResponseBoundary");
5989
+ await this.waitForBridgeRecovery();
5476
5990
  const promptBridge = this.bridge;
5477
5991
  promptBridge.on("textChunk", onChunk);
5478
5992
  promptBridge.on("responseBoundary", onResponseBoundary);
@@ -5594,578 +6108,135 @@ Ask the bot operator to approve you with:
5594
6108
  await this.sendThreadMessage(chatId, threadId, "Too many pending pairing requests. Please try again later.");
5595
6109
  }
5596
6110
  }
5597
- };
5598
- function truncateGroupHistoryField(value) {
5599
- return value.slice(0, GROUP_HISTORY_ENTRY_METADATA_LIMIT);
5600
- }
5601
- __name(truncateGroupHistoryField, "truncateGroupHistoryField");
5602
- function isPromiseLike(value) {
5603
- return value !== null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
5604
- }
5605
- __name(isPromiseLike, "isPromiseLike");
5606
- function isDebugPayloadEnabled(channelName) {
5607
- const raw = process.env[DEBUG_PAYLOAD_ENV]?.trim();
5608
- if (!raw)
5609
- return false;
5610
- if (["1", "true", "yes", "all", "*"].includes(raw.toLowerCase())) {
5611
- return true;
5612
- }
5613
- return raw.split(",").map((value) => value.trim()).filter(Boolean).includes(channelName);
5614
- }
5615
- __name(isDebugPayloadEnabled, "isDebugPayloadEnabled");
5616
- function redactPayloadValue(key, value) {
5617
- if (!key)
5618
- return value;
5619
- return SENSITIVE_PAYLOAD_KEY_PATTERN.test(key) ? "[redacted]" : value;
5620
- }
5621
- __name(redactPayloadValue, "redactPayloadValue");
5622
- function truncateLoopLabel(prompt) {
5623
- const chars = Array.from(prompt);
5624
- return chars.length > 60 ? `${chars.slice(0, 57).join("")}...` : prompt;
5625
- }
5626
- __name(truncateLoopLabel, "truncateLoopLabel");
5627
-
5628
- // packages/channels/base/dist/PollingChannelBase.js
5629
- var INITIAL_BACKOFF = 2e3;
5630
- var MAX_BACKOFF = 3e4;
5631
- var PollingChannelBase = class extends ChannelBase {
5632
- static {
5633
- __name(this, "PollingChannelBase");
5634
- }
5635
- cursor;
5636
- abortController = new AbortController();
5637
- running = false;
5638
- consecutiveErrors = 0;
5639
- abortableSleep(ms) {
5640
- const signal = this.abortController.signal;
5641
- return new Promise((resolve3) => {
5642
- if (signal.aborted) {
5643
- resolve3();
5644
- return;
5645
- }
5646
- const timer = setTimeout(() => {
5647
- signal.removeEventListener("abort", onAbort);
5648
- resolve3();
5649
- }, ms);
5650
- const onAbort = /* @__PURE__ */ __name(() => {
5651
- clearTimeout(timer);
5652
- resolve3();
5653
- }, "onAbort");
5654
- signal.addEventListener("abort", onAbort, { once: true });
5655
- });
5656
- }
5657
- constructor(name, config, bridge, options) {
5658
- super(name, config, bridge, options);
5659
- this.cursor = this.loadCursorFromDisk() ?? this.createInitialCursor();
5660
- }
5661
- validateCursor(parsed) {
5662
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
5663
- return null;
5664
- return parsed;
5665
- }
5666
- get pollInterval() {
5667
- const configured = this.config.pollInterval;
5668
- if (typeof configured === "number" && Number.isFinite(configured) && configured > 0) {
5669
- return configured;
5670
- }
5671
- return 6e4;
5672
- }
5673
- saveCursor() {
5674
- const path4 = this.cursorPath();
5675
- mkdirSync4(join6(getGlobalQwenDir(), "channels"), { recursive: true });
5676
- const tmp = `${path4}.tmp`;
5677
- writeFileSync4(tmp, JSON.stringify(this.cursor) + "\n", "utf-8");
5678
- renameSync4(tmp, path4);
5679
- }
5680
- startPollLoop() {
5681
- if (this.running)
5682
- return;
5683
- this.running = true;
5684
- this.consecutiveErrors = 0;
5685
- this.abortController = new AbortController();
5686
- this.runLoop();
5687
- }
5688
- stopPollLoop() {
5689
- this.running = false;
5690
- this.abortController.abort();
5691
- }
5692
- async runLoop() {
5693
- const signal = this.abortController.signal;
5694
- while (this.running && !signal.aborted) {
5695
- try {
5696
- await this.pollOnce();
5697
- this.saveCursor();
5698
- this.consecutiveErrors = 0;
5699
- } catch (err) {
5700
- this.consecutiveErrors++;
5701
- const backoff = Math.min(INITIAL_BACKOFF * 2 ** (this.consecutiveErrors - 1), MAX_BACKOFF);
5702
- process3.stderr.write(`[Channel:${this.name}] poll error (attempt ${this.consecutiveErrors}), backing off ${backoff}ms: ${err}
5703
- `);
5704
- await this.abortableSleep(backoff);
5705
- continue;
5706
- }
5707
- await this.abortableSleep(this.pollInterval);
5708
- }
5709
- }
5710
- loadCursorFromDisk() {
5711
- try {
5712
- const raw = readFileSync4(this.cursorPath(), "utf-8").trim();
5713
- if (!raw)
5714
- return null;
5715
- return this.validateCursor(JSON.parse(raw));
5716
- } catch {
5717
- return null;
5718
- }
5719
- }
5720
- cursorPath() {
5721
- const encoded = this.name.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 200);
5722
- const hash = createHash2("sha256").update(this.name).digest("hex").slice(0, 16);
5723
- return join6(getGlobalQwenDir(), "channels", `${encoded}-${hash}-poll-cursor.json`);
5724
- }
5725
- };
5726
-
5727
- // packages/channels/base/dist/AcpBridge.js
5728
- init_esbuild_shims();
5729
- import { spawn } from "node:child_process";
5730
- import { randomUUID as randomUUID2 } from "node:crypto";
5731
- import { Readable, Writable } from "node:stream";
5732
- import { EventEmitter } from "node:events";
5733
- var MID_TURN_QUEUE_DRAIN_METHOD = "craft/drainMidTurnQueue";
5734
- var TODO_STOP_GUARD_CONTINUATION_CLAIM_METHOD = "craft/claimTodoStopGuardContinuation";
5735
- var ACP_EVENT_LOOP_STALL_RESTART_MS = 5 * 60 * 1e3;
5736
- var ACP_PERMISSION_RESPONSE_TIMEOUT_MS = 5 * 60 * 1e3;
5737
- var ACP_EVENT_LOOP_STALL_RE = /^\[perf\] acp agent event loop stall: max=(\d+(?:\.\d+)?)ms/m;
5738
- function readAvailableCommandAltNames(raw) {
5739
- if (typeof raw !== "object" || raw === null)
5740
- return void 0;
5741
- const record = raw;
5742
- const meta = record["_meta"];
5743
- const fromMeta = typeof meta === "object" && meta !== null ? meta["altNames"] : void 0;
5744
- const source = Array.isArray(record["altNames"]) ? record["altNames"] : Array.isArray(fromMeta) ? fromMeta : void 0;
5745
- if (!source)
5746
- return void 0;
5747
- const names = source.filter((n) => typeof n === "string");
5748
- return names.length > 0 ? names : void 0;
5749
- }
5750
- __name(readAvailableCommandAltNames, "readAvailableCommandAltNames");
5751
- var AcpBridge = class extends EventEmitter {
5752
- static {
5753
- __name(this, "AcpBridge");
5754
- }
5755
- child = null;
5756
- connection = null;
5757
- options;
5758
- _availableCommands = [];
5759
- channelLoopMcpServer;
5760
- channelLoopToolHandlers = [];
5761
- knownSessionIds = /* @__PURE__ */ new Set();
5762
- sessionBindingTokens = /* @__PURE__ */ new Map();
5763
- channelLoopMcpRegistered = false;
5764
- channelLoopMcpRegistration = null;
5765
- pendingPermissions = /* @__PURE__ */ new Map();
5766
- constructor(options) {
5767
- super();
5768
- this.options = options;
5769
- }
5770
- get availableCommands() {
5771
- return this._availableCommands;
5772
- }
5773
- async start() {
5774
- const { cliEntryPath, cwd } = this.options;
5775
- const args = [
5776
- ...process.execArgv.filter((a) => !/^--inspect(-brk)?($|=)/.test(a)),
5777
- cliEntryPath,
5778
- "--acp"
5779
- ];
5780
- if (this.options.model) {
5781
- args.push("--model", this.options.model);
5782
- }
5783
- this.child = spawn(process.execPath, args, {
5784
- cwd,
5785
- stdio: ["pipe", "pipe", "pipe"],
5786
- env: { ...process.env, QWEN_CODE_DISABLE_CRON: "1" },
5787
- shell: false
5788
- });
5789
- this.child.stderr?.on("data", (data) => {
5790
- const msg = data.toString().trim();
5791
- if (msg) {
5792
- process.stderr.write(`[AcpBridge] ${sanitizeLogText(msg, 4096)}
5793
- `);
5794
- this.maybeKillOnEventLoopStall(msg);
5795
- }
5796
- });
5797
- this.child.on("exit", (code, signal) => {
5798
- process.stderr.write(`[AcpBridge] Process exited (code=${code}, signal=${signal})
5799
- `);
5800
- this.resolvePendingPermissions();
5801
- this.knownSessionIds.clear();
5802
- this.sessionBindingTokens.clear();
5803
- this.connection = null;
5804
- this.child = null;
5805
- this.emit("disconnected", code, signal);
5806
- });
5807
- await new Promise((resolve3) => setTimeout(resolve3, 1e3));
5808
- if (!this.child || this.child.killed) {
5809
- throw new Error("ACP process failed to start");
5810
- }
5811
- const stdout = Readable.toWeb(this.child.stdout);
5812
- const stdin = Writable.toWeb(this.child.stdin);
5813
- const stream = ndJsonStream(stdin, stdout);
5814
- this.connection = new ClientSideConnection(() => ({
5815
- sessionUpdate: /* @__PURE__ */ __name((params) => {
5816
- this.handleSessionUpdate(params);
5817
- return Promise.resolve();
5818
- }, "sessionUpdate"),
5819
- requestPermission: /* @__PURE__ */ __name(async (params) => this.requestPermission(params), "requestPermission"),
5820
- extMethod: /* @__PURE__ */ __name(async (method, params) => this.handleExtMethod(method, params), "extMethod"),
5821
- extNotification: /* @__PURE__ */ __name(async () => {
5822
- }, "extNotification")
5823
- }), stream);
5824
- await this.connection.initialize({
5825
- protocolVersion: PROTOCOL_VERSION,
5826
- clientCapabilities: {}
5827
- });
5828
- await this.registerChannelLoopMcpServer();
5829
- }
5830
- registerChannelLoopToolHandler(handler) {
5831
- if (!this.channelLoopToolHandlers.includes(handler)) {
5832
- this.channelLoopToolHandlers.push(handler);
5833
- }
5834
- this.channelLoopMcpServer ??= new ChannelLoopMcpServer({
5835
- create: /* @__PURE__ */ __name((sessionId, input) => this.resolveChannelLoopToolHandler(sessionId).create(sessionId, input), "create"),
5836
- list: /* @__PURE__ */ __name((sessionId) => this.resolveChannelLoopToolHandler(sessionId).list(sessionId), "list"),
5837
- cancel: /* @__PURE__ */ __name((sessionId, id) => this.resolveChannelLoopToolHandler(sessionId).cancel(sessionId, id), "cancel")
5838
- });
5839
- void this.registerChannelLoopMcpServer();
5840
- }
5841
- async newSession(cwd, _options, bindingToken) {
5842
- const conn = this.ensureConnection();
5843
- await this.registerChannelLoopMcpServer();
5844
- const response = await conn.newSession({ cwd, mcpServers: [] });
5845
- this.knownSessionIds.add(response.sessionId);
5846
- this.sessionBindingTokens.set(response.sessionId, bindingToken);
5847
- return response.sessionId;
5848
- }
5849
- async loadSession(sessionId, cwd, _options, bindingToken) {
5850
- const conn = this.ensureConnection();
5851
- await this.registerChannelLoopMcpServer();
5852
- await conn.loadSession({
5853
- sessionId,
5854
- cwd,
5855
- mcpServers: []
5856
- });
5857
- this.knownSessionIds.add(sessionId);
5858
- this.sessionBindingTokens.set(sessionId, bindingToken);
5859
- return sessionId;
5860
- }
5861
- async prompt(sessionId, text, options) {
5862
- const conn = this.ensureConnection();
5863
- const chunks = [];
5864
- let slashCommandOutput = "";
5865
- const onChunk = /* @__PURE__ */ __name((sid, chunk) => {
5866
- if (sid === sessionId)
5867
- chunks.push(chunk);
5868
- }, "onChunk");
5869
- const onSlashCommandOutput = /* @__PURE__ */ __name((sid, chunk) => {
5870
- if (sid === sessionId)
5871
- slashCommandOutput = chunk;
5872
- }, "onSlashCommandOutput");
5873
- const clearChunks = /* @__PURE__ */ __name((sid) => {
5874
- if (sid === sessionId) {
5875
- chunks.length = 0;
5876
- slashCommandOutput = "";
5877
- }
5878
- }, "clearChunks");
5879
- this.on("textChunk", onChunk);
5880
- this.on("slashCommandOutput", onSlashCommandOutput);
5881
- this.on("responseBoundary", clearChunks);
5882
- const prompt = [];
5883
- if (options?.imageBase64 && options.imageMimeType) {
5884
- prompt.push({
5885
- type: "image",
5886
- data: options.imageBase64,
5887
- mimeType: options.imageMimeType
5888
- });
5889
- }
5890
- prompt.push({ type: "text", text });
5891
- try {
5892
- await conn.prompt({
5893
- sessionId,
5894
- prompt
5895
- });
5896
- } finally {
5897
- this.off("textChunk", onChunk);
5898
- this.off("slashCommandOutput", onSlashCommandOutput);
5899
- this.off("responseBoundary", clearChunks);
5900
- }
5901
- return chunks.join("") || slashCommandOutput;
5902
- }
5903
- async cancelSession(sessionId) {
5904
- const conn = this.ensureConnection();
5905
- try {
5906
- await conn.cancel({ sessionId });
5907
- } finally {
5908
- this.resolvePendingPermissions(sessionId);
5909
- }
5910
- }
5911
- async discardSession(sessionId, expectedBindingToken) {
5912
- if (expectedBindingToken !== void 0 && this.sessionBindingTokens.get(sessionId) !== expectedBindingToken) {
5913
- return;
5914
- }
5915
- if (!this.knownSessionIds.delete(sessionId))
5916
- return;
5917
- this.sessionBindingTokens.delete(sessionId);
5918
- this.resolvePendingPermissions(sessionId);
5919
- const conn = this.connection;
5920
- if (!conn || !this.isConnected)
5921
- return;
5922
- await conn.extMethod("qwen/control/session/close", { sessionId });
5923
- }
5924
- async respondToPermission(requestId, response) {
5925
- const pending = this.pendingPermissions.get(requestId);
5926
- if (!pending) {
5927
- return false;
5928
- }
5929
- clearTimeout(pending.timeout);
5930
- this.pendingPermissions.delete(requestId);
5931
- pending.resolve(response);
5932
- this.emit("permissionResolved", {
5933
- requestId,
5934
- outcome: response.outcome
5935
- });
5936
- return true;
5937
- }
5938
- stop() {
5939
- this.resolvePendingPermissions();
5940
- this.knownSessionIds.clear();
5941
- this.sessionBindingTokens.clear();
5942
- if (this.child) {
5943
- this.child.kill();
5944
- this.child = null;
5945
- }
5946
- this.connection = null;
5947
- }
5948
- get isConnected() {
5949
- return this.child !== null && !this.child.killed && this.child.exitCode === null;
5950
- }
5951
- handleSessionUpdate(params) {
5952
- const { sessionId } = params;
5953
- const update = params["update"];
5954
- if (!update)
5955
- return;
5956
- const type = update["sessionUpdate"];
5957
- switch (type) {
5958
- case "agent_message_chunk": {
5959
- const meta = update["_meta"];
5960
- if (typeof meta?.["parentToolCallId"] === "string") {
5961
- break;
5962
- }
5963
- const content = update["content"];
5964
- if (meta?.["qwenDiscreteMessage"] === true) {
5965
- if (meta["source"] === "background_notification_response" && meta["rewritten"] !== true && content?.type === "text" && content.text) {
5966
- this.emit("backgroundResponse", sessionId, content.text);
5967
- }
5968
- break;
5969
- }
5970
- if (content?.type === "text" && content.text) {
5971
- this.emit(meta?.["source"] === "slash_command" ? "slashCommandOutput" : "textChunk", sessionId, content.text);
5972
- }
5973
- break;
5974
- }
5975
- case "tool_call": {
5976
- const event = {
5977
- sessionId,
5978
- toolCallId: update["toolCallId"],
5979
- kind: update["kind"] || "",
5980
- title: update["title"] || "",
5981
- status: update["status"] || "pending",
5982
- rawInput: update["rawInput"]
5983
- };
5984
- if (event.status === "pending" || event.status === "in_progress") {
5985
- this.emitResponseBoundary(sessionId);
5986
- }
5987
- this.emit("toolCall", event);
5988
- break;
5989
- }
5990
- case "plan": {
5991
- this.emitResponseBoundary(sessionId);
5992
- break;
5993
- }
5994
- case "available_commands_update": {
5995
- if (Array.isArray(update["availableCommands"])) {
5996
- this._availableCommands = update["availableCommands"].map((cmd) => {
5997
- const altNames = readAvailableCommandAltNames(cmd);
5998
- return altNames ? { ...cmd, altNames } : cmd;
5999
- });
6000
- }
6001
- break;
6002
- }
6003
- default:
6004
- break;
6005
- }
6006
- this.emit("sessionUpdate", params);
6007
- }
6008
- ensureConnection() {
6009
- if (!this.connection || !this.isConnected) {
6010
- throw new Error("Not connected to ACP agent");
6011
- }
6012
- return this.connection;
6111
+ };
6112
+ function truncateGroupHistoryField(value) {
6113
+ return value.slice(0, GROUP_HISTORY_ENTRY_METADATA_LIMIT);
6114
+ }
6115
+ __name(truncateGroupHistoryField, "truncateGroupHistoryField");
6116
+ function isPromiseLike(value) {
6117
+ return value !== null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
6118
+ }
6119
+ __name(isPromiseLike, "isPromiseLike");
6120
+ function isDebugPayloadEnabled(channelName) {
6121
+ const raw = process.env[DEBUG_PAYLOAD_ENV]?.trim();
6122
+ if (!raw)
6123
+ return false;
6124
+ if (["1", "true", "yes", "all", "*"].includes(raw.toLowerCase())) {
6125
+ return true;
6013
6126
  }
6014
- requestPermission(request) {
6015
- const requestId = `acp-permission-${randomUUID2()}`;
6016
- const sessionId = typeof request.sessionId === "string" && request.sessionId.length > 0 ? request.sessionId : request.toolCall.toolCallId;
6127
+ return raw.split(",").map((value) => value.trim()).filter(Boolean).includes(channelName);
6128
+ }
6129
+ __name(isDebugPayloadEnabled, "isDebugPayloadEnabled");
6130
+ function redactPayloadValue(key, value) {
6131
+ if (!key)
6132
+ return value;
6133
+ return SENSITIVE_PAYLOAD_KEY_PATTERN.test(key) ? "[redacted]" : value;
6134
+ }
6135
+ __name(redactPayloadValue, "redactPayloadValue");
6136
+ function truncateLoopLabel(prompt) {
6137
+ const chars = Array.from(prompt);
6138
+ return chars.length > 60 ? `${chars.slice(0, 57).join("")}...` : prompt;
6139
+ }
6140
+ __name(truncateLoopLabel, "truncateLoopLabel");
6141
+
6142
+ // packages/channels/base/dist/PollingChannelBase.js
6143
+ var INITIAL_BACKOFF = 2e3;
6144
+ var MAX_BACKOFF = 3e4;
6145
+ var PollingChannelBase = class extends ChannelBase {
6146
+ static {
6147
+ __name(this, "PollingChannelBase");
6148
+ }
6149
+ cursor;
6150
+ abortController = new AbortController();
6151
+ running = false;
6152
+ consecutiveErrors = 0;
6153
+ abortableSleep(ms) {
6154
+ const signal = this.abortController.signal;
6017
6155
  return new Promise((resolve3) => {
6018
- const timeout = setTimeout(() => {
6019
- const pending = this.pendingPermissions.get(requestId);
6020
- if (!pending) {
6021
- return;
6022
- }
6023
- process.stderr.write(`[AcpBridge] permission request ${sanitizeLogText(requestId, 128)} timed out after ${ACP_PERMISSION_RESPONSE_TIMEOUT_MS}ms (session=${sanitizeLogText(pending.sessionId, 128)})
6024
- `);
6025
- this.pendingPermissions.delete(requestId);
6026
- const response = {
6027
- outcome: { outcome: "cancelled" }
6028
- };
6029
- pending.resolve(response);
6030
- this.emit("permissionResolved", {
6031
- requestId,
6032
- outcome: response.outcome
6033
- });
6034
- }, ACP_PERMISSION_RESPONSE_TIMEOUT_MS);
6035
- timeout.unref?.();
6036
- this.pendingPermissions.set(requestId, { sessionId, resolve: resolve3, timeout });
6037
- this.emitResponseBoundary(sessionId);
6038
- this.emit("permissionRequest", {
6039
- requestId,
6040
- sessionId,
6041
- request
6042
- });
6156
+ if (signal.aborted) {
6157
+ resolve3();
6158
+ return;
6159
+ }
6160
+ const timer = setTimeout(() => {
6161
+ signal.removeEventListener("abort", onAbort);
6162
+ resolve3();
6163
+ }, ms);
6164
+ const onAbort = /* @__PURE__ */ __name(() => {
6165
+ clearTimeout(timer);
6166
+ resolve3();
6167
+ }, "onAbort");
6168
+ signal.addEventListener("abort", onAbort, { once: true });
6043
6169
  });
6044
6170
  }
6045
- emitResponseBoundary(sessionId) {
6046
- this.emit("responseBoundary", sessionId);
6171
+ constructor(name, config, bridge, options) {
6172
+ super(name, config, bridge, options);
6173
+ this.cursor = this.loadCursorFromDisk() ?? this.createInitialCursor();
6047
6174
  }
6048
- resolvePendingPermissions(sessionId) {
6049
- const response = {
6050
- outcome: { outcome: "cancelled" }
6051
- };
6052
- for (const [requestId, pending] of this.pendingPermissions) {
6053
- if (sessionId !== void 0 && pending.sessionId !== sessionId) {
6054
- continue;
6055
- }
6056
- clearTimeout(pending.timeout);
6057
- this.pendingPermissions.delete(requestId);
6058
- pending.resolve(response);
6059
- this.emit("permissionResolved", {
6060
- requestId,
6061
- outcome: response.outcome
6062
- });
6063
- }
6175
+ validateCursor(parsed) {
6176
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
6177
+ return null;
6178
+ return parsed;
6064
6179
  }
6065
- maybeKillOnEventLoopStall(stderr) {
6066
- const match = ACP_EVENT_LOOP_STALL_RE.exec(stderr);
6067
- if (!match)
6068
- return;
6069
- const maxMs = Number(match[1]);
6070
- if (!Number.isFinite(maxMs) || maxMs < ACP_EVENT_LOOP_STALL_RESTART_MS) {
6071
- return;
6072
- }
6073
- const child = this.child;
6074
- if (!child || child.killed || child.exitCode !== null) {
6075
- return;
6180
+ get pollInterval() {
6181
+ const configured = this.config.pollInterval;
6182
+ if (typeof configured === "number" && Number.isFinite(configured) && configured > 0) {
6183
+ return configured;
6076
6184
  }
6077
- process.stderr.write(`[AcpBridge] ACP agent event loop stalled for ${Math.round(maxMs)}ms; killing child process to trigger restart
6078
- `);
6079
- child.kill("SIGKILL");
6185
+ return 6e4;
6080
6186
  }
6081
- async registerChannelLoopMcpServer() {
6082
- if (!this.connection || !this.channelLoopMcpServer || this.channelLoopMcpRegistered) {
6083
- return;
6084
- }
6085
- if (this.channelLoopMcpRegistration) {
6086
- await this.channelLoopMcpRegistration;
6187
+ saveCursor() {
6188
+ const path4 = this.cursorPath();
6189
+ mkdirSync4(join6(getGlobalQwenDir(), "channels"), { recursive: true });
6190
+ const tmp = `${path4}.tmp`;
6191
+ writeFileSync4(tmp, JSON.stringify(this.cursor) + "\n", "utf-8");
6192
+ renameSync4(tmp, path4);
6193
+ }
6194
+ startPollLoop() {
6195
+ if (this.running)
6087
6196
  return;
6088
- }
6089
- this.channelLoopMcpRegistration = this.connection.extMethod(WORKSPACE_MCP_RUNTIME_ADD_METHOD, {
6090
- name: CHANNEL_LOOP_MCP_SERVER_NAME,
6091
- originatorClientId: "channel",
6092
- config: {
6093
- type: "sdk",
6094
- [CLIENT_MCP_OVER_WS_CONFIG_FLAG]: true
6095
- }
6096
- }).then((result) => {
6097
- if (isSkippedMcpRegistration(result)) {
6098
- this.channelLoopMcpRegistered = false;
6099
- process.stderr.write(`[AcpBridge] Channel loop MCP server registration skipped${formatSkippedRegistrationReason(result)}
6197
+ this.running = true;
6198
+ this.consecutiveErrors = 0;
6199
+ this.abortController = new AbortController();
6200
+ this.runLoop();
6201
+ }
6202
+ stopPollLoop() {
6203
+ this.running = false;
6204
+ this.abortController.abort();
6205
+ }
6206
+ async runLoop() {
6207
+ const signal = this.abortController.signal;
6208
+ while (this.running && !signal.aborted) {
6209
+ try {
6210
+ await this.pollOnce();
6211
+ this.saveCursor();
6212
+ this.consecutiveErrors = 0;
6213
+ } catch (err) {
6214
+ this.consecutiveErrors++;
6215
+ const backoff = Math.min(INITIAL_BACKOFF * 2 ** (this.consecutiveErrors - 1), MAX_BACKOFF);
6216
+ process3.stderr.write(`[Channel:${this.name}] poll error (attempt ${this.consecutiveErrors}), backing off ${backoff}ms: ${err}
6100
6217
  `);
6101
- return;
6218
+ await this.abortableSleep(backoff);
6219
+ continue;
6102
6220
  }
6103
- this.channelLoopMcpRegistered = true;
6104
- }).catch((error) => {
6105
- this.channelLoopMcpRegistered = false;
6106
- process.stderr.write(`[AcpBridge] Failed to register channel loop MCP server: ${error instanceof Error ? error.message : String(error)}
6107
- `);
6108
- }).finally(() => {
6109
- this.channelLoopMcpRegistration = null;
6110
- });
6111
- await this.channelLoopMcpRegistration;
6112
- }
6113
- async handleExtMethod(method, params) {
6114
- if (method === CLIENT_MCP_MESSAGE_METHOD) {
6115
- return this.handleClientMcpMessage(params);
6116
- }
6117
- if (method === MID_TURN_QUEUE_DRAIN_METHOD) {
6118
- return { messages: [], hasQueuedPrompt: false };
6119
- }
6120
- if (method === TODO_STOP_GUARD_CONTINUATION_CLAIM_METHOD) {
6121
- const sessionId = typeof params["sessionId"] === "string" ? params["sessionId"] : "";
6122
- return {
6123
- claimed: this.knownSessionIds.has(sessionId),
6124
- hasQueuedPrompt: false
6125
- };
6221
+ await this.abortableSleep(this.pollInterval);
6126
6222
  }
6127
- throw new Error(`Method not found: ${method}`);
6128
6223
  }
6129
- async handleClientMcpMessage(params) {
6130
- if (!this.channelLoopMcpServer) {
6131
- throw new Error("Channel loop MCP server is not registered.");
6132
- }
6133
- const server = params["server"];
6134
- if (server !== CHANNEL_LOOP_MCP_SERVER_NAME) {
6135
- throw new Error(`Unknown client MCP server: ${String(server)}`);
6136
- }
6137
- const payload = params["payload"];
6138
- if (typeof payload !== "object" || payload === null) {
6139
- throw new Error("Invalid client MCP payload.");
6140
- }
6141
- const sessionId = typeof params["sessionId"] === "string" ? params["sessionId"] : void 0;
6142
- const response = await this.channelLoopMcpServer.handleMessage(payload, { sessionId });
6143
- if (!response) {
6144
- return { payload: { jsonrpc: "2.0", id: 0, result: {} } };
6224
+ loadCursorFromDisk() {
6225
+ try {
6226
+ const raw = readFileSync4(this.cursorPath(), "utf-8").trim();
6227
+ if (!raw)
6228
+ return null;
6229
+ return this.validateCursor(JSON.parse(raw));
6230
+ } catch {
6231
+ return null;
6145
6232
  }
6146
- return { payload: response };
6147
6233
  }
6148
- resolveChannelLoopToolHandler(sessionId) {
6149
- if (this.channelLoopToolHandlers.length === 1 && !this.channelLoopToolHandlers[0].canHandle) {
6150
- return this.channelLoopToolHandlers[0];
6151
- }
6152
- const handler = this.channelLoopToolHandlers.find((candidate) => candidate.canHandle?.(sessionId) === true);
6153
- if (handler)
6154
- return handler;
6155
- throw new Error(this.channelLoopToolHandlers.length === 0 ? "No channel loop tool handler is registered." : `No channel loop handler matched session ${sessionId}.`);
6234
+ cursorPath() {
6235
+ const encoded = this.name.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 200);
6236
+ const hash = createHash2("sha256").update(this.name).digest("hex").slice(0, 16);
6237
+ return join6(getGlobalQwenDir(), "channels", `${encoded}-${hash}-poll-cursor.json`);
6156
6238
  }
6157
6239
  };
6158
- function isSkippedMcpRegistration(result) {
6159
- return typeof result === "object" && result !== null && result.skipped === true;
6160
- }
6161
- __name(isSkippedMcpRegistration, "isSkippedMcpRegistration");
6162
- function formatSkippedRegistrationReason(result) {
6163
- if (typeof result !== "object" || result === null)
6164
- return ".";
6165
- const reason = result.reason;
6166
- return typeof reason === "string" && reason.length > 0 ? `: ${sanitizeLogText(reason, 256)}` : ".";
6167
- }
6168
- __name(formatSkippedRegistrationReason, "formatSkippedRegistrationReason");
6169
6240
 
6170
6241
  // packages/channels/base/dist/DaemonChannelBridge.js
6171
6242
  init_esbuild_shims();
@@ -7058,9 +7129,11 @@ __name(isTerminalTaskLifecycleType, "isTerminalTaskLifecycleType");
7058
7129
  export {
7059
7130
  resolvePath,
7060
7131
  getGlobalQwenDir,
7132
+ getWorkspaceScopeDirName,
7061
7133
  ChannelProactiveDeliveryError,
7062
7134
  isChannelProactiveDeliveryError,
7063
7135
  PairingStore,
7136
+ truncateCodePoints,
7064
7137
  sanitizeSenderName,
7065
7138
  sanitizePromptText,
7066
7139
  sanitizeLogText,
@@ -7069,6 +7142,7 @@ export {
7069
7142
  ChannelBase,
7070
7143
  PollingChannelBase,
7071
7144
  CHANNEL_LOOP_MCP_SERVER_NAME,
7145
+ ACP_EVENT_LOOP_STALL_RESTART_MS,
7072
7146
  AcpBridge,
7073
7147
  DaemonChannelBridge,
7074
7148
  ChannelLoopStore,