@qwen-code/qwen-code 0.22.2-preview.1 → 0.22.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 (437) hide show
  1. package/bundled/computer-use/SKILL.md +3 -3
  2. package/bundled/goal-draft/SKILL.md +109 -0
  3. package/bundled/qc-helper/docs/configuration/settings.md +37 -36
  4. package/bundled/qc-helper/docs/features/_meta.ts +1 -0
  5. package/bundled/qc-helper/docs/features/channels/dws.md +2 -0
  6. package/bundled/qc-helper/docs/features/channels/overview.md +34 -3
  7. package/bundled/qc-helper/docs/features/code-review.md +3 -3
  8. package/bundled/qc-helper/docs/features/commands.md +2 -1
  9. package/bundled/qc-helper/docs/features/computer-use.md +2 -2
  10. package/bundled/qc-helper/docs/features/goals.md +58 -0
  11. package/bundled/qc-helper/docs/features/headless.md +13 -11
  12. package/bundled/qc-helper/docs/features/status-line.md +3 -3
  13. package/bundled/qc-helper/docs/features/tool-use-summaries.md +1 -1
  14. package/bundled/qc-helper/docs/qwen-serve.md +1 -1
  15. package/bundled/review/SKILL.md +81 -31
  16. package/bundled/review/references/persistence.md +7 -3
  17. package/bundled/review/references/posting.md +10 -4
  18. package/chunks/MaxSizedBox-X42SXBJH.js +110 -0
  19. package/chunks/{StandaloneSessionPicker-5D2L22SF.js → StandaloneSessionPicker-2FJAOLGG.js} +81 -83
  20. package/chunks/{acpAgent-4DCKLA32.js → acpAgent-2ZMIJAFV.js} +1153 -357
  21. package/chunks/{agent-B4AFZL6H.js → agent-X5WX3L2I.js} +44 -46
  22. package/chunks/agent-headless-IZM25LJA.js +85 -0
  23. package/chunks/{anthropicContentGenerator-U76GTVB6.js → anthropicContentGenerator-YVTB6QX7.js} +46 -50
  24. package/chunks/{artifact-tool-XDJCZZCF.js → artifact-tool-XSMPHZBT.js} +11 -3
  25. package/chunks/{askUserQuestion-ZXXERLEV.js → askUserQuestion-4SWJY65V.js} +2 -2
  26. package/chunks/bridge-ME6QXZNA.js +119 -0
  27. package/chunks/{channel-management-service-HXXS3HOZ.js → channel-management-service-HQ6KDS7C.js} +6 -6
  28. package/chunks/channel-settings-store-PEOI7XCG.js +120 -0
  29. package/chunks/{channel-worker-group-EVDC2P4K.js → channel-worker-group-6NA6BBZV.js} +8 -7
  30. package/chunks/{channel-worker-manager-IJJZHGEI.js → channel-worker-manager-NEZ2J6RK.js} +10 -8
  31. package/chunks/{channel-worker-supervisor-YSKB5GVB.js → channel-worker-supervisor-FKU7DIHJ.js} +6 -5
  32. package/chunks/{chunk-7QGEJI7M.js → chunk-23RFD54N.js} +1 -1
  33. package/chunks/{chunk-JLU3FPQR.js → chunk-2HYKTO7V.js} +5 -5
  34. package/chunks/chunk-2LNNVAUV.js +1584 -0
  35. package/chunks/{chunk-FJAITR4L.js → chunk-2OBKDPZZ.js} +2 -2
  36. package/chunks/{chunk-JSY43GMI.js → chunk-2OUD5T67.js} +185 -174
  37. package/chunks/{chunk-LJP2BBMK.js → chunk-2SXSLSQE.js} +4 -4
  38. package/chunks/chunk-2TNL5KPM.js +49 -0
  39. package/chunks/{chunk-XMSMTU6H.js → chunk-2YOHKHYR.js} +4 -4
  40. package/chunks/{chunk-MCKJTBZN.js → chunk-36GSQ3MM.js} +2 -2
  41. package/chunks/chunk-36LGBEM6.js +130 -0
  42. package/chunks/{chunk-ZWKGZB2K.js → chunk-3JXM2CVW.js} +3 -3
  43. package/chunks/{chunk-X3XGCJ32.js → chunk-3SZC4VP4.js} +28 -111
  44. package/chunks/{chunk-ILPXSKGQ.js → chunk-3WK3QDNV.js} +2 -2
  45. package/chunks/{chunk-IKN3IUJP.js → chunk-3XKLXHGK.js} +17 -9
  46. package/chunks/{chunk-ZLTEQYNC.js → chunk-42IDLQWS.js} +26 -20
  47. package/chunks/chunk-4CMJKUND.js +95 -0
  48. package/chunks/{chunk-77TOMRBG.js → chunk-4K7KNVWH.js} +3 -3
  49. package/chunks/{chunk-SQPOJ4ZI.js → chunk-4NDLQAY2.js} +27 -270
  50. package/chunks/{chunk-V3GINY6K.js → chunk-4QHPPXK2.js} +16 -8
  51. package/chunks/{chunk-VBF4KZRQ.js → chunk-4TT3IVGA.js} +1 -1
  52. package/chunks/{chunk-IOGKXPL3.js → chunk-4VPCTGH6.js} +6 -6
  53. package/chunks/chunk-4VQBQLIZ.js +293 -0
  54. package/chunks/{chunk-UCSV2IZW.js → chunk-62GQFYID.js} +3 -3
  55. package/chunks/{chunk-SLEWBQWM.js → chunk-6DIGWMGT.js} +2 -2
  56. package/chunks/{chunk-BRVWYMKV.js → chunk-6MBXY6WM.js} +1 -1
  57. package/chunks/{chunk-PVBT5QPD.js → chunk-6PJOTWAN.js} +1 -1
  58. package/chunks/{chunk-WZ3QKPMQ.js → chunk-6X7EU5TI.js} +19 -31
  59. package/chunks/{chunk-KQYW2GB5.js → chunk-6ZPDLGHN.js} +6 -6
  60. package/chunks/{chunk-AKQA3WCH.js → chunk-734C6JGI.js} +723 -3399
  61. package/chunks/{chunk-F7M6ZFGU.js → chunk-73RM4POK.js} +8 -8
  62. package/chunks/{chunk-ELY4QVIB.js → chunk-7EQQR46F.js} +1 -1
  63. package/chunks/{chunk-KGJGEEVR.js → chunk-7FA2II6K.js} +10 -1
  64. package/chunks/chunk-7J6OTNGO.js +424 -0
  65. package/chunks/{chunk-LWLEPXKK.js → chunk-7LOELM2I.js} +19 -2
  66. package/chunks/{chunk-DIMEPKCW.js → chunk-7S226YTK.js} +1 -1
  67. package/chunks/{chunk-AWFZDYSD.js → chunk-7T52W4SW.js} +1 -1
  68. package/chunks/{chunk-3JKUX4PB.js → chunk-7VMIMAVB.js} +9 -9
  69. package/chunks/{chunk-2MHGD7EE.js → chunk-A3LWD5MK.js} +1 -1
  70. package/chunks/{chunk-E4I5MVBR.js → chunk-ABS25QER.js} +1 -1
  71. package/chunks/{chunk-TDQBVEPK.js → chunk-AIAOIFY4.js} +10 -8
  72. package/chunks/{chunk-NEZZHGEI.js → chunk-AYFA7MZV.js} +6 -6
  73. package/chunks/{chunk-FVDQ5E67.js → chunk-B466ZSHZ.js} +3 -3
  74. package/chunks/{chunk-6US7BF3G.js → chunk-B5PHIJGI.js} +183 -5
  75. package/chunks/{chunk-4TPAG3O7.js → chunk-BFPESSTR.js} +1 -1
  76. package/chunks/{chunk-WBT3STGU.js → chunk-BKWNKLZB.js} +20 -20
  77. package/chunks/{chunk-T45CL2NT.js → chunk-BOWFGCEU.js} +3 -3
  78. package/chunks/{chunk-IPQA4SR5.js → chunk-BWORX6FA.js} +1 -1
  79. package/chunks/{chunk-BKDAF7ES.js → chunk-BXDAPVQU.js} +2 -2
  80. package/chunks/{chunk-ZEADBZLO.js → chunk-C2X7KY45.js} +3 -3
  81. package/chunks/{chunk-PTQF7WRA.js → chunk-C3PEFRKD.js} +6 -6
  82. package/chunks/{chunk-7OBH4AM2.js → chunk-CAJTKR6W.js} +5 -5
  83. package/chunks/{chunk-2FN2HNTI.js → chunk-CFJY4DGX.js} +2 -2
  84. package/chunks/{chunk-SUE2ZMUH.js → chunk-CIZCL2MW.js} +49 -31
  85. package/chunks/{chunk-W2WRNJKX.js → chunk-CLWO3B77.js} +6 -6
  86. package/chunks/{chunk-U45D46O5.js → chunk-CMHFCLBU.js} +1 -1
  87. package/chunks/{chunk-LRKMQPMD.js → chunk-CQH5KTKC.js} +1 -1
  88. package/chunks/{chunk-JECO75XD.js → chunk-D5LUXLUH.js} +2 -2
  89. package/chunks/chunk-DJBQCO2E.js +842 -0
  90. package/chunks/{chunk-BQBUZDDH.js → chunk-DJECZIJX.js} +42 -42
  91. package/chunks/{chunk-GTUZ3R6I.js → chunk-DJEZ55D4.js} +351 -113
  92. package/chunks/{chunk-HPECDZ6X.js → chunk-DJPASAUV.js} +3866 -1077
  93. package/chunks/{chunk-VYWCFWI5.js → chunk-DMLVSW7L.js} +542 -654
  94. package/chunks/{chunk-EB4VDXNI.js → chunk-DYXFD5RO.js} +182 -3
  95. package/chunks/{chunk-ZKAZP2ZG.js → chunk-EB6QRGHM.js} +3 -3
  96. package/chunks/{chunk-UWICO5KN.js → chunk-EFWRMA2I.js} +3 -3
  97. package/chunks/{chunk-3UZCLCXR.js → chunk-EKSCLBBF.js} +3 -3
  98. package/chunks/{chunk-ALTQLFEG.js → chunk-EQSBPBZ3.js} +5 -194
  99. package/chunks/{chunk-624NHDGB.js → chunk-ERFDKH32.js} +11 -0
  100. package/chunks/{chunk-4K44OMLI.js → chunk-FROAFEFB.js} +3 -3
  101. package/chunks/{chunk-MZS7GEC7.js → chunk-FTF2YIZ5.js} +1 -1
  102. package/chunks/{chunk-Z7VOMGDC.js → chunk-FTJMWDND.js} +0 -24
  103. package/chunks/{chunk-ZJG323IA.js → chunk-G6FECKTJ.js} +7 -5
  104. package/chunks/{chunk-TMGAS5TO.js → chunk-GBJBJ4HX.js} +26 -4
  105. package/chunks/{chunk-UCQOSV6A.js → chunk-GPGU4S2N.js} +3 -3
  106. package/chunks/{chunk-BHTJAO36.js → chunk-GVIVASYK.js} +7 -7
  107. package/chunks/chunk-H72BQNM3.js +645 -0
  108. package/chunks/{chunk-Z4X5MTRT.js → chunk-HCOBAG2L.js} +2 -4
  109. package/chunks/{chunk-FCSWZAS3.js → chunk-HP5LR5HL.js} +5 -5
  110. package/chunks/{chunk-7JPW6IYH.js → chunk-HVC3PNVC.js} +1 -1
  111. package/chunks/{chunk-OI2DR6QB.js → chunk-I6LYRJPB.js} +3 -3
  112. package/chunks/{chunk-E55H6BZ7.js → chunk-ICURCKIU.js} +1335 -119
  113. package/chunks/{chunk-PAJGKZAA.js → chunk-ISKH4QX3.js} +14 -12
  114. package/chunks/{chunk-AYVIGUUK.js → chunk-JB4JIVTJ.js} +1 -1
  115. package/chunks/{chunk-FN5C6C7G.js → chunk-JHYH6PYQ.js} +192 -478
  116. package/chunks/{chunk-T6OODF2U.js → chunk-JWALNCLT.js} +1 -1
  117. package/chunks/{chunk-SKNVVYIJ.js → chunk-K5KXUXWC.js} +3 -3
  118. package/chunks/{chunk-XUX6O2ZT.js → chunk-KID24ZFN.js} +4 -1
  119. package/chunks/{chunk-I6DY753S.js → chunk-KJO2LBUK.js} +944 -18
  120. package/chunks/{chunk-5T4LWWNX.js → chunk-KYWTPLBJ.js} +18 -21
  121. package/chunks/{chunk-CZFQ62RM.js → chunk-LHBUKJG2.js} +4 -4
  122. package/chunks/{chunk-TOF4UZHH.js → chunk-LUEHOUHM.js} +3 -3
  123. package/chunks/{chunk-2YDYDDE2.js → chunk-M3QRM3DE.js} +3 -3
  124. package/chunks/{chunk-GNMJNDZE.js → chunk-M6FOJXW3.js} +1 -1
  125. package/chunks/{chunk-ZGWDHSUM.js → chunk-MF7HCQWZ.js} +4 -4
  126. package/chunks/{chunk-66GIFJ63.js → chunk-MK2GIB46.js} +1 -1
  127. package/chunks/{chunk-TFV4RXPU.js → chunk-MO3FC2WT.js} +1 -1
  128. package/chunks/{chunk-TR7G7XFQ.js → chunk-MPNBVAEU.js} +5 -5
  129. package/chunks/{chunk-K3LPAED5.js → chunk-MQM5PJML.js} +24 -4
  130. package/chunks/{chunk-3WBMPR3S.js → chunk-MSBO7X2W.js} +1 -1
  131. package/chunks/{chunk-5PA6UEYA.js → chunk-MZCJWS5Y.js} +1 -1
  132. package/chunks/{chunk-BGXZBI5B.js → chunk-N4X7G4J2.js} +2 -1
  133. package/chunks/{chunk-27ONXAFY.js → chunk-NIFYWDYN.js} +1 -1
  134. package/chunks/{chunk-AP7WZONS.js → chunk-NMOD2FXQ.js} +19 -9
  135. package/chunks/{chunk-IJVDHPKD.js → chunk-NU7RKEZR.js} +2 -2
  136. package/chunks/{chunk-5URK47RN.js → chunk-NV55WWD6.js} +11595 -10375
  137. package/chunks/{chunk-HABT2Z7R.js → chunk-OU5APVWO.js} +4 -4
  138. package/chunks/{chunk-QR4AHMGQ.js → chunk-P2G476QN.js} +13 -13
  139. package/chunks/{chunk-5SM2AK65.js → chunk-PADPMMYM.js} +5 -5
  140. package/chunks/{chunk-RRKNX553.js → chunk-PDQGMSZK.js} +1 -1
  141. package/chunks/{chunk-GTUKW3I6.js → chunk-PPKZ7JOE.js} +1 -1
  142. package/chunks/{chunk-SAH4BD2J.js → chunk-PQEISIKS.js} +67 -0
  143. package/chunks/{chunk-XIRDDFRS.js → chunk-PZ66FRIC.js} +2644 -2296
  144. package/chunks/{chunk-H3HZ4AWW.js → chunk-Q2MSPKIJ.js} +1 -1
  145. package/chunks/{chunk-WEGXPP5E.js → chunk-QFCDF5PL.js} +1 -1
  146. package/chunks/{chunk-OF2DPXUS.js → chunk-QYMAKPY5.js} +119 -24
  147. package/chunks/{chunk-JVPFTDBK.js → chunk-RFIDDFAL.js} +1 -1
  148. package/chunks/chunk-RKFGE7JH.js +93 -0
  149. package/chunks/{chunk-CPPYZHBN.js → chunk-RU7BK6B3.js} +1 -1
  150. package/chunks/chunk-SEONHUS3.js +275 -0
  151. package/chunks/{chunk-DEYPMJPD.js → chunk-SMPR7SPO.js} +1 -1
  152. package/chunks/chunk-SS4MRMKE.js +3525 -0
  153. package/chunks/{chunk-BPMVPWBT.js → chunk-SS5SDCD3.js} +6 -6
  154. package/chunks/{chunk-J3CBIEDS.js → chunk-ST5MDGH3.js} +32 -15
  155. package/chunks/{chunk-J5352S3S.js → chunk-ST5V3ZOC.js} +3 -3
  156. package/chunks/{chunk-7CWEZQ6T.js → chunk-STUJ7IMZ.js} +0 -77
  157. package/chunks/{chunk-4K7NQDCG.js → chunk-T4EFG2P5.js} +18 -9
  158. package/chunks/{chunk-TWSKM647.js → chunk-T7KMERIX.js} +1 -1
  159. package/chunks/{chunk-5CMWFBML.js → chunk-TEBDFQBZ.js} +1 -1
  160. package/chunks/{chunk-EC5BONC3.js → chunk-TPI7O7HR.js} +1 -1
  161. package/chunks/{chunk-T2CRBIDJ.js → chunk-TTX2JUE6.js} +1 -1
  162. package/chunks/{chunk-VOEMVOM4.js → chunk-TVC6HQ2U.js} +2 -2
  163. package/chunks/{chunk-WXDZZFH3.js → chunk-TZBSGJ6C.js} +6 -6
  164. package/chunks/chunk-U54AYGEC.js +337 -0
  165. package/chunks/{chunk-2NNTE2ED.js → chunk-UHQFIS7N.js} +15 -0
  166. package/chunks/{chunk-76WGY6LW.js → chunk-URE3QBBB.js} +3 -3
  167. package/chunks/{chunk-KOJ6MHD5.js → chunk-UTLCH2FK.js} +1 -1
  168. package/chunks/{chunk-7DPKPBOQ.js → chunk-V2NDP3I4.js} +2 -2
  169. package/chunks/{chunk-FJWICLG7.js → chunk-V4264FEG.js} +9 -9
  170. package/chunks/{chunk-XR7OIAQF.js → chunk-V5J4J5TP.js} +2 -2
  171. package/chunks/{chunk-7UJTY5V3.js → chunk-V7EESTJV.js} +1 -1
  172. package/chunks/{chunk-Z6L3C4VF.js → chunk-VBCPNIQD.js} +39 -39
  173. package/chunks/{chunk-ATLGYSYH.js → chunk-VGIFXISB.js} +4 -4
  174. package/chunks/{chunk-X2UOOG5V.js → chunk-VQUGEVGR.js} +1 -1
  175. package/chunks/{chunk-XJS54K4A.js → chunk-VTHREBQM.js} +2 -2
  176. package/chunks/{chunk-AA7VBRBG.js → chunk-W3JH33JE.js} +8 -8
  177. package/chunks/{chunk-FPH6TXD3.js → chunk-WAOIHTJS.js} +1 -1
  178. package/chunks/{chunk-CDUN63IN.js → chunk-WGY3AVGM.js} +3 -3
  179. package/chunks/{chunk-JLGFGPZJ.js → chunk-WNPDGUJO.js} +22 -3
  180. package/chunks/{chunk-O3BUXIPM.js → chunk-WXEA74YB.js} +3 -3
  181. package/chunks/{chunk-YRLW2MSX.js → chunk-XBVNNDPK.js} +11 -4
  182. package/chunks/{chunk-VL4YX3FH.js → chunk-XF63PKEN.js} +1 -1
  183. package/chunks/{chunk-R7G7GKOX.js → chunk-XHF7WQ6B.js} +3 -3
  184. package/chunks/{chunk-3FSBMAYD.js → chunk-XQFT3QUF.js} +1 -1
  185. package/chunks/{chunk-3D6PDLNJ.js → chunk-XSG6G3G6.js} +49 -32
  186. package/chunks/{chunk-5JCZ3UKB.js → chunk-Y5S7LBOZ.js} +44 -7
  187. package/chunks/{chunk-OLS3MAXQ.js → chunk-YLSQLSH7.js} +15 -9
  188. package/chunks/{chunk-2SERYG4T.js → chunk-YTDVNSUQ.js} +1 -1
  189. package/chunks/{chunk-KPF2K55D.js → chunk-ZEYFMJQA.js} +39 -8
  190. package/chunks/{chunk-OT4YKUUV.js → chunk-ZU4UDIWX.js} +2 -2
  191. package/chunks/chunk-ZWJDJ6RU.js +2597 -0
  192. package/chunks/{chunk-R3T33BG5.js → chunk-ZYMLPEZQ.js} +3 -3
  193. package/chunks/{chunk-DID6IRGC.js → chunk-ZZMSC3RG.js} +56 -39
  194. package/chunks/{chunk-L436XFLJ.js → chunk-ZZUKP7HV.js} +1 -1
  195. package/chunks/config-utils-R6M3TA34.js +113 -0
  196. package/chunks/contextCommand-ZCAP23J6.js +106 -0
  197. package/chunks/{core-runtime-CEJRHCRF.js → core-runtime-AXB6SK5D.js} +59 -61
  198. package/chunks/{create-sub-session-RVHSWVAP.js → create-sub-session-23ITIELI.js} +3 -3
  199. package/chunks/{create-sub-session-DFZVIG56.js → create-sub-session-4QNE64MR.js} +164 -85
  200. package/chunks/{cron-create-IJGVR5R2.js → cron-create-KJ4IY6PP.js} +57 -10
  201. package/chunks/{cron-delete-4N5BI6GL.js → cron-delete-MBLQCAYY.js} +4 -4
  202. package/chunks/{cron-list-2CATIPUX.js → cron-list-DWH2PQ47.js} +4 -4
  203. package/chunks/{daemon-NAYK4XXU.js → daemon-3GNHB3M2.js} +523 -8
  204. package/chunks/{daemon-git-worktree-guard-L6ZUFKF7.js → daemon-git-worktree-guard-IVAJJWYK.js} +56 -58
  205. package/chunks/daemon-status-provider-K3SJ5C2U.js +117 -0
  206. package/chunks/daemon-trust-policy-DOR3YG62.js +112 -0
  207. package/chunks/{daemon-trust-policy-monitor-2G3EKQEU.js → daemon-trust-policy-monitor-KGGROSIV.js} +64 -66
  208. package/chunks/deferred-core-runtime-WOYLUQQ7.js +114 -0
  209. package/chunks/{display-image-6KHQNEAE.js → display-image-BE7I6ZB6.js} +5 -5
  210. package/chunks/{dist-DJBNNPLN.js → dist-4WXLCBDR.js} +1 -1
  211. package/chunks/{dist-TJUDJTCB.js → dist-5VXFRTWU.js} +280 -48
  212. package/chunks/{dist-6H6RZ5H6.js → dist-6BPYWHVD.js} +89 -65
  213. package/chunks/{dist-ZBVOXSKF.js → dist-GFN5DCBX.js} +74 -65
  214. package/chunks/{dist-SATKXKUR.js → dist-HZNT6GQS.js} +1 -1
  215. package/chunks/{dist-FVJZSIO4.js → dist-LVZCXVPF.js} +72 -20
  216. package/chunks/{dist-7CKN54NL.js → dist-N6W6E753.js} +71 -64
  217. package/chunks/{dist-4Q7NTUNB.js → dist-NSV4NBPK.js} +31 -14
  218. package/chunks/{dist-UQUFZDIY.js → dist-SWQMYP2Q.js} +18 -16
  219. package/chunks/earlyInputCapture-OHBEVO4K.js +107 -0
  220. package/chunks/{edit-ZERGX3RV.js → edit-LEQLAGPZ.js} +45 -47
  221. package/chunks/{en-6MDBWMTG.js → en-YLXBXM4Z.js} +2 -1
  222. package/chunks/{enter-worktree-UOZ6ZTP7.js → enter-worktree-35QCAAFP.js} +7 -7
  223. package/chunks/{enterPlanMode-W2RP3P5B.js → enterPlanMode-ONQN3WHE.js} +47 -49
  224. package/chunks/{environment-ZBJ23YVR.js → environment-UANTR7A4.js} +60 -62
  225. package/chunks/errors-WAK6KFBC.js +113 -0
  226. package/chunks/{exit-worktree-G2QATUCV.js → exit-worktree-XQD6D543.js} +7 -7
  227. package/chunks/exitPlanMode-UEYBG6P5.js +83 -0
  228. package/chunks/{fast-path-4MWBCEUF.js → fast-path-WUL7UZ6H.js} +7 -7
  229. package/chunks/{fast-path-settings-XMFBYFUH.js → fast-path-settings-XH426LUU.js} +3 -3
  230. package/chunks/{glob-DKTNG32L.js → glob-Y54QUWDY.js} +44 -46
  231. package/chunks/{goal-tools-CNVXMSZD.js → goal-tools-PTD3MBA7.js} +10 -8
  232. package/chunks/{grep-3NLSS32D.js → grep-5GPICQBU.js} +6 -6
  233. package/chunks/handleAutoUpdate-FP3NWNSG.js +109 -0
  234. package/chunks/i18n-INIU5X7A.js +124 -0
  235. package/chunks/{image-gen-63NN2H5N.js → image-gen-J6FDGUZP.js} +8 -10
  236. package/chunks/initializer-TYODV6GE.js +113 -0
  237. package/chunks/installationInfo-AAPKYDSZ.js +107 -0
  238. package/chunks/{keychain-token-storage-3MGFRYGM.js → keychain-token-storage-37XPR34V.js} +2 -2
  239. package/chunks/list-TUODDN6J.js +116 -0
  240. package/chunks/{list-agents-EXRSHIDJ.js → list-agents-DI3YFTDT.js} +2 -2
  241. package/chunks/{gemini-X5GQHQYF.js → llm-NSAVK5V3.js} +117 -114
  242. package/chunks/{geminiContentGenerator-UDSWXZXL.js → llm-content-generator-AY2QKXHE.js} +29 -29
  243. package/chunks/loadedSettingsAdapter-KJCLZ3C4.js +110 -0
  244. package/chunks/{loggingContentGenerator-73C7TKDK.js → loggingContentGenerator-DSFQHXIF.js} +328 -37
  245. package/chunks/{loop-wakeup-XBPB2BRO.js → loop-wakeup-XJFONSQ7.js} +5 -5
  246. package/chunks/{ls-WFONAND2.js → ls-3VAT2J7A.js} +4 -4
  247. package/chunks/{lsp-DUKPDUXN.js → lsp-5W7IRUBH.js} +2 -2
  248. package/chunks/{managed-npm-update-H2KOLQZU.js → managed-npm-update-TSWENTK5.js} +57 -59
  249. package/chunks/mcp-MWSAPFOL.js +110 -0
  250. package/chunks/{monitor-TGCMNJJH.js → monitor-RW4GWJHB.js} +44 -46
  251. package/chunks/nonInteractiveCli-JXKL343Q.js +185 -0
  252. package/chunks/{notebook-edit-YEUNZYDX.js → notebook-edit-CQNYS5OR.js} +45 -47
  253. package/chunks/{open-with-auth-4UI3KHUR.js → open-with-auth-YN3QZHVM.js} +1 -1
  254. package/chunks/{openaiContentGenerator-J7LD4VSL.js → openaiContentGenerator-J6QIF7EX.js} +26 -28
  255. package/chunks/pidfile-QOIQZFT2.js +111 -0
  256. package/chunks/process-registry-OAEG6WGC.js +662 -0
  257. package/chunks/{processUtils-ZHFC7CSL.js → processUtils-GPHNANKZ.js} +2 -2
  258. package/chunks/prompt-terminal-ledger-FOMFTYOM.js +105 -0
  259. package/chunks/{qwenContentGenerator-YEWWK3JQ.js → qwenContentGenerator-WWDXPYQI.js} +49 -51
  260. package/chunks/{qwenOAuth2-JIYAWXTW.js → qwenOAuth2-OF5WRBQT.js} +7 -9
  261. package/chunks/{read-file-ZQI3WPVK.js → read-file-FQUQHC6N.js} +13 -13
  262. package/chunks/{read-mcp-resource-7A7NFSFL.js → read-mcp-resource-XD257PGJ.js} +2 -2
  263. package/chunks/{record-artifact-CPJ4IJSD.js → record-artifact-VW23GS4F.js} +4 -4
  264. package/chunks/{report-findings-VFM5DENC.js → report-findings-AO5VYFLR.js} +9 -5
  265. package/chunks/{request-shutdown-6IW46GD7.js → request-shutdown-BCLA3JV2.js} +7 -7
  266. package/chunks/resumeHistoryUtils-NSGF3UCB.js +118 -0
  267. package/chunks/ripGrep-4CAT6SKV.js +39 -0
  268. package/chunks/{run-qwen-serve-WHWU7NZ6.js → run-qwen-serve-5X6IDZER.js} +371 -357
  269. package/chunks/{runtime-E7BSPWQP.js → runtime-HK27GMPF.js} +69 -71
  270. package/chunks/scheduled-tasks-MN2ZZ5P6.js +127 -0
  271. package/chunks/{scheduler-TJPATFOM.js → scheduler-WD4MHMO5.js} +58 -60
  272. package/chunks/{sdk-exporters-http-XXSKBD2C.js → sdk-exporters-http-TTU2CHBU.js} +2 -2
  273. package/chunks/{sdk-impl-VPNBLV7I.js → sdk-impl-UUMVYMAL.js} +11 -5
  274. package/chunks/{send-message-OXZK6USO.js → send-message-AJ4OCUBV.js} +25 -9
  275. package/chunks/serve-2XCHPPGG.js +118 -0
  276. package/chunks/{server-AXCN7QBJ.js → server-GO2N5DH7.js} +6080 -4964
  277. package/chunks/{session-HO4US3AG.js → session-IFNHTUBT.js} +112 -112
  278. package/chunks/session-pr-refresh-I3UBWMVP.js +364 -0
  279. package/chunks/{settings-B2PFJJN4.js → settings-L6N54CU6.js} +63 -65
  280. package/chunks/{shell-7XMSANI3.js → shell-4Y5LLW6N.js} +44 -46
  281. package/chunks/{skill-NL3VH562.js → skill-YOWTRAKQ.js} +68 -24
  282. package/chunks/skill-settings-ZL4JY5UE.js +118 -0
  283. package/chunks/spawnChannel-ZST6XJVJ.js +112 -0
  284. package/chunks/standalone-update-THVU3OKY.js +118 -0
  285. package/chunks/{startInteractiveUI-G2XKSCQ7.js → startInteractiveUI-XDFSHBZD.js} +3076 -2475
  286. package/chunks/{stdioHelpers-7A2EE64Z.js → stdioHelpers-AY6XCDY2.js} +3 -1
  287. package/chunks/{syntheticOutput-5AJ444QT.js → syntheticOutput-ML43GEFN.js} +3 -3
  288. package/chunks/{task-create-TY7P6UHG.js → task-create-QN4LYRSE.js} +11 -11
  289. package/chunks/{task-list-BELMPA75.js → task-list-JPLGK45U.js} +5 -5
  290. package/chunks/{task-stop-U3S3KZG2.js → task-stop-CXEH5L2D.js} +2 -2
  291. package/chunks/{task-update-O4IGDPCK.js → task-update-IWEOJFV2.js} +11 -11
  292. package/chunks/{team-create-IYBUOORE.js → team-create-OOPXCDBA.js} +44 -46
  293. package/chunks/{team-delete-YWFXX77L.js → team-delete-RZVVSGCY.js} +5 -5
  294. package/chunks/{team-plan-approval-YKU57LBM.js → team-plan-approval-DSX5EWAT.js} +44 -46
  295. package/chunks/terminal-image-renderer-KS25G33E.js +114 -0
  296. package/chunks/theme-manager-YMZNG45F.js +102 -0
  297. package/chunks/{todoWrite-435EO2CS.js → todoWrite-WDJNQEMW.js} +43 -25
  298. package/chunks/{tool-search-4Q3KASIQ.js → tool-search-J5Q6U7DN.js} +22 -22
  299. package/chunks/total-session-admission-PVN64JVV.js +114 -0
  300. package/chunks/trustedFolders-FATSC4YI.js +122 -0
  301. package/chunks/{types-WYCFCEHR.js → types-HKLPXZB3.js} +1 -3
  302. package/chunks/{update-relaunch-4DC2KKFS.js → update-relaunch-SIKHLHVR.js} +6 -6
  303. package/chunks/updateCheck-D3YG2WCD.js +118 -0
  304. package/chunks/useAutoAcceptIndicator-ILINOSAS.js +120 -0
  305. package/chunks/{validateNonInterActiveAuth-YAE4YPO3.js → validateNonInterActiveAuth-JUDQP6V4.js} +105 -105
  306. package/chunks/{version-QFP7OGWX.js → version-C63GDDWN.js} +1 -1
  307. package/chunks/{web-fetch-6T23IK6Q.js → web-fetch-FOQZMNSC.js} +16 -18
  308. package/chunks/{web-search-WBWTCZK2.js → web-search-PUJTBFUC.js} +9 -11
  309. package/chunks/{web-shell-static-A6QMUNNV.js → web-shell-static-HNB4FDOE.js} +2 -2
  310. package/chunks/{workflow-FMAA3D4R.js → workflow-Y5IPINVH.js} +128 -94
  311. package/chunks/workspace-providers-status-IJ4VHFMB.js +114 -0
  312. package/chunks/{workspace-registration-store-CMMX7EPX.js → workspace-registration-store-SMJWI2AI.js} +1 -1
  313. package/chunks/workspace-registry-BTGZIMPK.js +123 -0
  314. package/chunks/workspace-service-SIN67ITC.js +126 -0
  315. package/chunks/workspace-skills-status-F3RKCUIX.js +113 -0
  316. package/chunks/{workspace-trust-reconciler-PJDQUTJL.js → workspace-trust-reconciler-PLIDLJZX.js} +72 -72
  317. package/chunks/write-file-PAIGB33C.js +88 -0
  318. package/chunks/{zh-TW-MV4FWFB6.js → zh-TW-RPTMRTHG.js} +2 -1
  319. package/chunks/{zh-X3WGU6SI.js → zh-UNY63FR3.js} +2 -1
  320. package/chunks/{zoom-image-WVYVFRXF.js → zoom-image-LYQD7BQA.js} +13 -13
  321. package/cli.js +16 -16
  322. package/locales/en.js +2 -0
  323. package/locales/zh-TW.js +2 -0
  324. package/locales/zh.js +2 -0
  325. package/package.json +3 -3
  326. package/web-shell/assets/{abnfDiagram-VCTEODGH-d-8xa8uz.js → abnfDiagram-VCTEODGH-TRCMSA_6.js} +1 -1
  327. package/web-shell/assets/{arc-BhB7CrGK.js → arc-D9Uls5b3.js} +1 -1
  328. package/web-shell/assets/{architectureDiagram-5GKGNRK7-BDCd_IcJ.js → architectureDiagram-5GKGNRK7-Bwb8jPXg.js} +1 -1
  329. package/web-shell/assets/{blockDiagram-NRAW4CY4-C-AHu6mi.js → blockDiagram-NRAW4CY4-DHj71TSX.js} +1 -1
  330. package/web-shell/assets/{c4Diagram-UCG6FXSJ-CFjQt0vl.js → c4Diagram-UCG6FXSJ-uKBBdbyr.js} +1 -1
  331. package/web-shell/assets/channel-CXjVLaBa.js +1 -0
  332. package/web-shell/assets/{chunk-2Q5K7J3B-DRjwqaDq.js → chunk-2Q5K7J3B-BZd0Phrl.js} +1 -1
  333. package/web-shell/assets/{chunk-5VM5RSS4-eHM9noJE.js → chunk-5VM5RSS4-7KNeNBqN.js} +1 -1
  334. package/web-shell/assets/{chunk-F27PBJKO-CcSWUYn1.js → chunk-F27PBJKO-BeboS7Fi.js} +1 -1
  335. package/web-shell/assets/{chunk-G27WJ6UU-DNr-RF5S.js → chunk-G27WJ6UU-RV3JONoj.js} +1 -1
  336. package/web-shell/assets/{chunk-JWPE2WC7-rVJHoNF8.js → chunk-JWPE2WC7-Yw9FCFsP.js} +1 -1
  337. package/web-shell/assets/{chunk-LCL6LL3I-B2aX0Gzy.js → chunk-LCL6LL3I-GIMnydDe.js} +1 -1
  338. package/web-shell/assets/{chunk-POPQ4Y6H-DVp07n32.js → chunk-POPQ4Y6H-Cfrux7bL.js} +1 -1
  339. package/web-shell/assets/{chunk-SVP7TREG-C6QB_KUr.js → chunk-SVP7TREG-BlvIN0CO.js} +1 -1
  340. package/web-shell/assets/{chunk-XXDRQBXY-CLVxDNCy.js → chunk-XXDRQBXY-DMs7Rr4t.js} +1 -1
  341. package/web-shell/assets/classDiagram-DTDB5LWJ-DANEijO3.js +1 -0
  342. package/web-shell/assets/classDiagram-v2-JRS7N3AN-DANEijO3.js +1 -0
  343. package/web-shell/assets/{cose-bilkent-JH36ORCC-B_aKX3A3.js → cose-bilkent-JH36ORCC-DNGqTNwI.js} +1 -1
  344. package/web-shell/assets/{cynefin-OW5HDTMX-BBw2pPUa.js → cynefin-OW5HDTMX-CojMpaoH.js} +1 -1
  345. package/web-shell/assets/{cynefinDiagram-5FMLGOSQ-WWPiBTwk.js → cynefinDiagram-5FMLGOSQ-DF07YXGo.js} +1 -1
  346. package/web-shell/assets/{dagre-3AP2YEHR-CPtIdka-.js → dagre-3AP2YEHR-17-4WtCH.js} +1 -1
  347. package/web-shell/assets/{diagram-S7CK7UJ4-Dj5og1Xi.js → diagram-S7CK7UJ4-G3Wuyz9m.js} +1 -1
  348. package/web-shell/assets/{diagram-UQ7AKVKN-CC_x70YC.js → diagram-UQ7AKVKN-nyWIKZ8P.js} +1 -1
  349. package/web-shell/assets/{diagram-VSXAHHWV-rXMfsO4n.js → diagram-VSXAHHWV-tKHhmYSC.js} +1 -1
  350. package/web-shell/assets/{diagram-VX7I27RA-DEmLYQbh.js → diagram-VX7I27RA-CpDf1hYk.js} +1 -1
  351. package/web-shell/assets/{diagram-Z3DM3KII-B0ys6Vxx.js → diagram-Z3DM3KII-Cah2IFXG.js} +1 -1
  352. package/web-shell/assets/{ebnfDiagram-PWID7BFC-Bq6y6lKH.js → ebnfDiagram-PWID7BFC-DNaCkgxy.js} +1 -1
  353. package/web-shell/assets/{erDiagram-SSCWMZ5O-l69JeBZ4.js → erDiagram-SSCWMZ5O-CAdRgcf1.js} +1 -1
  354. package/web-shell/assets/{flowDiagram-A5DVABFB-Cm-EzGY4.js → flowDiagram-A5DVABFB-CMUJAShL.js} +1 -1
  355. package/web-shell/assets/{ganttDiagram-EL5Y4UJY-BRS9ZOp7.js → ganttDiagram-EL5Y4UJY-o-_MUNey.js} +1 -1
  356. package/web-shell/assets/{gitGraphDiagram-WWUBYQGX-DAMRKgLk.js → gitGraphDiagram-WWUBYQGX-_aG-2Z7O.js} +1 -1
  357. package/web-shell/assets/{index-Bncx8E8M.js → index-BK_NKxiJ.js} +1 -1
  358. package/web-shell/assets/index-Ci-z4zWY.js +1913 -0
  359. package/web-shell/assets/index-LDlXZRHA.css +36 -0
  360. package/web-shell/assets/{infoDiagram-RXCK75RN-DasffaZp.js → infoDiagram-RXCK75RN-qJPeZbGP.js} +1 -1
  361. package/web-shell/assets/{ishikawaDiagram-5VMMS53U-CmRUt3C-.js → ishikawaDiagram-5VMMS53U-7Om4vNo1.js} +1 -1
  362. package/web-shell/assets/{journeyDiagram-EYS64GPL-DzhE_c1Z.js → journeyDiagram-EYS64GPL-BNtXqmBW.js} +1 -1
  363. package/web-shell/assets/{kanban-definition-3QL26DDD-BH5b6nn3.js → kanban-definition-3QL26DDD-COgFec8P.js} +1 -1
  364. package/web-shell/assets/{layout-BxoLK-LY.js → layout-B9n81PC9.js} +1 -1
  365. package/web-shell/assets/{linear-CqDPvVI7.js → linear-Bb6nXZQq.js} +1 -1
  366. package/web-shell/assets/{mermaid.core-DBoN4plo.js → mermaid.core-Cgrm3JVT.js} +6 -6
  367. package/web-shell/assets/{mindmap-definition-FBJOCRG2-DJJtXPEM.js → mindmap-definition-FBJOCRG2-TKwLTjnG.js} +1 -1
  368. package/web-shell/assets/{pegDiagram-XKGWAZYB-DmgPXP5l.js → pegDiagram-XKGWAZYB-BJPuvlpi.js} +1 -1
  369. package/web-shell/assets/{pieDiagram-E7YTZNPT-1ezjzcI5.js → pieDiagram-E7YTZNPT-D01VkNsW.js} +1 -1
  370. package/web-shell/assets/{quadrantDiagram-AXDQQJYC-ChKARz3k.js → quadrantDiagram-AXDQQJYC-C4hHsiv-.js} +1 -1
  371. package/web-shell/assets/{railroadDiagram-O6MQD6OU-k7mNB_QT.js → railroadDiagram-O6MQD6OU-CuEO4xK2.js} +1 -1
  372. package/web-shell/assets/{requirementDiagram-EFPCY7ZU-B0XFsii0.js → requirementDiagram-EFPCY7ZU-D5J_vIo5.js} +1 -1
  373. package/web-shell/assets/{sankeyDiagram-P5KCCOFB-repJ2Ukk.js → sankeyDiagram-P5KCCOFB-DQKY223V.js} +1 -1
  374. package/web-shell/assets/{sequenceDiagram-WJ2MYXX4-D0gUSKGu.js → sequenceDiagram-WJ2MYXX4-D27YV_cm.js} +1 -1
  375. package/web-shell/assets/{sizeCapture-X5ZJPWSS-CE463F74.js → sizeCapture-X5ZJPWSS-TJg8UcwV.js} +1 -1
  376. package/web-shell/assets/{stateDiagram-HBIQ2CUA-003zYtJa.js → stateDiagram-HBIQ2CUA-CmnPzUIx.js} +1 -1
  377. package/web-shell/assets/stateDiagram-v2-4QOOHH4V-DNKPyrDc.js +1 -0
  378. package/web-shell/assets/{swimlanes-XN3QIQJK-DBj8MFpu.js → swimlanes-XN3QIQJK-ByeP3_Nu.js} +1 -1
  379. package/web-shell/assets/swimlanesDiagram-VK2B7HYN-ZL1HPC6w.js +8 -0
  380. package/web-shell/assets/{timeline-definition-24CTP7MA-D2aCh4a9.js → timeline-definition-24CTP7MA-BsFA5juR.js} +1 -1
  381. package/web-shell/assets/{vennDiagram-4TSXK5OY-BHMfWBpl.js → vennDiagram-4TSXK5OY-DREP2ECo.js} +1 -1
  382. package/web-shell/assets/{wardleyDiagram-VM6X3IG4-RdHAj66j.js → wardleyDiagram-VM6X3IG4-iyd9lKi2.js} +1 -1
  383. package/web-shell/assets/{xychartDiagram-S5SC5T6Z-BgJWOvq0.js → xychartDiagram-S5SC5T6Z-CVr1_j9q.js} +1 -1
  384. package/web-shell/index.html +3 -3
  385. package/chunks/MaxSizedBox-BFVLZP2E.js +0 -112
  386. package/chunks/agent-headless-UCBCS4V4.js +0 -87
  387. package/chunks/bridge-FRNYLYY7.js +0 -119
  388. package/chunks/channel-settings-store-IV4KDL7N.js +0 -117
  389. package/chunks/chunk-I6VVOZDK.js +0 -202
  390. package/chunks/chunk-JS73NNWF.js +0 -30
  391. package/chunks/chunk-K2OJUPOE.js +0 -78
  392. package/chunks/chunk-P3QQPMQA.js +0 -19
  393. package/chunks/chunk-SDVF6A42.js +0 -945
  394. package/chunks/chunk-VGHZT6JR.js +0 -51
  395. package/chunks/config-utils-PMX4QZ5Y.js +0 -113
  396. package/chunks/contextCommand-NZRLVAFC.js +0 -108
  397. package/chunks/daemon-status-provider-FTTYZEKN.js +0 -118
  398. package/chunks/daemon-trust-policy-WMGFPA3Z.js +0 -114
  399. package/chunks/deferred-core-runtime-BUFEFYNA.js +0 -116
  400. package/chunks/earlyInputCapture-ROPLMXA7.js +0 -109
  401. package/chunks/errors-U5KNAADB.js +0 -115
  402. package/chunks/exitPlanMode-VFJ5R5DD.js +0 -85
  403. package/chunks/handleAutoUpdate-W4BUYFT7.js +0 -111
  404. package/chunks/i18n-KX7FEKJJ.js +0 -126
  405. package/chunks/initializer-SNVVLS7Y.js +0 -115
  406. package/chunks/installationInfo-D4XGQNHJ.js +0 -109
  407. package/chunks/list-WYECMLXZ.js +0 -118
  408. package/chunks/loadedSettingsAdapter-5WZBXBCR.js +0 -112
  409. package/chunks/mcp-CBWAROY3.js +0 -112
  410. package/chunks/nonInteractiveCli-O232UUV7.js +0 -185
  411. package/chunks/pidfile-MSPQ255Y.js +0 -113
  412. package/chunks/process-registry-PMJOA5CO.js +0 -172
  413. package/chunks/prompt-terminal-ledger-YPGMNDXT.js +0 -107
  414. package/chunks/resumeHistoryUtils-35D4KAVT.js +0 -120
  415. package/chunks/ripGrep-46N4FLOS.js +0 -39
  416. package/chunks/serve-WQ54UIYG.js +0 -120
  417. package/chunks/skill-settings-SKMN5YXC.js +0 -120
  418. package/chunks/spawnChannel-NE43EEO3.js +0 -113
  419. package/chunks/standalone-update-XOCWGXAH.js +0 -120
  420. package/chunks/terminal-image-renderer-NREKYTAD.js +0 -116
  421. package/chunks/theme-manager-IJS6OICX.js +0 -104
  422. package/chunks/total-session-admission-L5RPEFZ2.js +0 -114
  423. package/chunks/trustedFolders-OILZCHRD.js +0 -124
  424. package/chunks/updateCheck-AK4COHNK.js +0 -120
  425. package/chunks/useAutoAcceptIndicator-O2N2Q6KG.js +0 -122
  426. package/chunks/workspace-providers-status-OCFEWIPQ.js +0 -116
  427. package/chunks/workspace-registry-WJLZPRTG.js +0 -123
  428. package/chunks/workspace-service-5ZOBMMIB.js +0 -131
  429. package/chunks/workspace-skills-status-B6NJZD4B.js +0 -115
  430. package/chunks/write-file-B2RNX6SU.js +0 -90
  431. package/web-shell/assets/channel-CU-Zyv0r.js +0 -1
  432. package/web-shell/assets/classDiagram-DTDB5LWJ-C0J2_LKs.js +0 -1
  433. package/web-shell/assets/classDiagram-v2-JRS7N3AN-C0J2_LKs.js +0 -1
  434. package/web-shell/assets/index-C-XlQn24.css +0 -5
  435. package/web-shell/assets/index-gdvMN4J1.js +0 -1860
  436. package/web-shell/assets/stateDiagram-v2-4QOOHH4V-2uACDdJN.js +0 -1
  437. package/web-shell/assets/swimlanesDiagram-VK2B7HYN-C4JJkavU.js +0 -8
@@ -0,0 +1,3525 @@
1
+ // Force strict mode and setup for ESM
2
+ "use strict";
3
+ import {
4
+ SERVE_CONTROL_EXT_METHODS
5
+ } from "./chunk-KID24ZFN.js";
6
+ import {
7
+ ACTIVE_WORK_HEARTBEAT_VERSION,
8
+ ACTIVE_WORK_HOLD_CATEGORIES,
9
+ ACTIVE_WORK_MAX_SESSION_HOLDS,
10
+ ACTIVE_WORK_MAX_SNAPSHOT_SESSIONS,
11
+ ACTIVE_WORK_NOTIFICATION_METHOD,
12
+ DAEMON_PERMISSION_CANCEL_REASON_META_KEY,
13
+ MID_TURN_QUEUE_DRAIN_METHOD,
14
+ MID_TURN_RECONCILIATION_RING_SIZE,
15
+ TODO_STOP_GUARD_CONTINUATION_CLAIM_METHOD
16
+ } from "./chunk-OZ6KS6KW.js";
17
+ import {
18
+ CancelSentinelCollisionError,
19
+ InvalidPermissionOptionError
20
+ } from "./chunk-ERFDKH32.js";
21
+ import {
22
+ CHANNEL_DELIVERY_ERROR_CODES,
23
+ LIVE_TASK_TOOL_NAMES,
24
+ MAX_LIVE_SCREEN_CONTEXT_TEXT_CHARS,
25
+ MAX_LIVE_SPEAK_TO_USER_MESSAGE_CHARS,
26
+ MAX_SUB_SESSION_NAME_CHARS
27
+ } from "./chunk-UDG5EZJI.js";
28
+ import {
29
+ isValidExternalToolGuardDenialReason
30
+ } from "./chunk-3VUENPWF.js";
31
+ import {
32
+ RequestError
33
+ } from "./chunk-IW6RQPQB.js";
34
+ import {
35
+ MAX_SUB_SESSION_PROMPT_CHARS
36
+ } from "./chunk-LJZSMWOH.js";
37
+ import {
38
+ APPROVAL_MODES
39
+ } from "./chunk-V5J4J5TP.js";
40
+ import {
41
+ getSpecificMimeType
42
+ } from "./chunk-BKWNKLZB.js";
43
+ import {
44
+ init_esbuild_shims
45
+ } from "./chunk-5O2XNYP6.js";
46
+ import {
47
+ __name
48
+ } from "./chunk-J2S4EL5Y.js";
49
+
50
+ // packages/acp-bridge/src/permissionMediator.ts
51
+ init_esbuild_shims();
52
+
53
+ // packages/acp-bridge/src/permission.ts
54
+ init_esbuild_shims();
55
+
56
+ // packages/acp-bridge/src/permissionMediator.ts
57
+ var CANCEL_VOTE_SENTINEL = "__cancelled__";
58
+ var MAX_RESOLVED_PERMISSION_RECORDS = 512;
59
+ function stringifyError(err) {
60
+ try {
61
+ if (err instanceof Error) return `${err.name}: ${err.message}`;
62
+ return String(err);
63
+ } catch {
64
+ return "[unstringifiable error]";
65
+ }
66
+ }
67
+ __name(stringifyError, "stringifyError");
68
+ function createNoOpPermissionAuditPublisher() {
69
+ return {
70
+ recordRequested() {
71
+ },
72
+ recordVoted() {
73
+ },
74
+ recordForbidden() {
75
+ },
76
+ recordResolved() {
77
+ },
78
+ recordTimeout() {
79
+ }
80
+ };
81
+ }
82
+ __name(createNoOpPermissionAuditPublisher, "createNoOpPermissionAuditPublisher");
83
+ var MultiClientPermissionMediator = class {
84
+ static {
85
+ __name(this, "MultiClientPermissionMediator");
86
+ }
87
+ policy;
88
+ deps;
89
+ pending = /* @__PURE__ */ new Map();
90
+ resolved = /* @__PURE__ */ new Map();
91
+ resolvedOrder = [];
92
+ /**
93
+ * Dedup flag for the
94
+ * unanimity-required stderr breadcrumb. Without this, every
95
+ * permission request on a 2-client consensus session would emit
96
+ * an identical line (the unanimity condition is the NORMAL
97
+ * operating mode for M=2, not a rare edge); a busy session with
98
+ * many tool calls would produce dozens of duplicate stderr lines
99
+ * within seconds. One emit per mediator (= per bridge/runtime lifetime
100
+ * since each bridge constructs one) is enough to make the
101
+ * configuration visible without spam.
102
+ */
103
+ unanimityBreadcrumbEmitted = false;
104
+ constructor(policy, deps) {
105
+ this.policy = policy;
106
+ this.deps = deps;
107
+ }
108
+ /**
109
+ * Register a fresh permission request from the agent.
110
+ *
111
+ * **Promise contract — once the Promise is returned, it never
112
+ * rejects.** All runtime failure modes (timeout, session closure,
113
+ * voter cancel, emit/audit publisher exceptions) are encoded as
114
+ * `PermissionResolution { kind:'cancelled', reason:... }`.
115
+ * Consumers can `await` the returned Promise and forward the
116
+ * result without a `.catch()` block.
117
+ *
118
+ * **Synchronous-throw exception**:
119
+ * when the agent's `allowedOptionIds` contains the
120
+ * cancel-vote sentinel string, this method throws
121
+ * `CancelSentinelCollisionError` synchronously BEFORE constructing
122
+ * the Promise. The synchronous shape is intentional — a
123
+ * never-settling Promise alongside a thrown error would be worse
124
+ * than a clean fail-fast — but callers must wrap this method
125
+ * itself in `try/catch` (or call it from an `async` function so
126
+ * the throw bubbles via the function's own Promise machinery).
127
+ * `bridgeClient.ts` currently has its own pre-check at the bridge
128
+ * layer; embedded callers must do the same. See `@throws` below.
129
+ *
130
+ * **Synchronous-register invariant**: pending entry, audit
131
+ * record, and timer setup all happen inside the Promise executor
132
+ * without `await`. The bridge's `publish → mediator.request → await`
133
+ * sequence relies on this — a `forgetSession` between publish and
134
+ * await would otherwise miss the new pending and leak it until
135
+ * timeout.
136
+ *
137
+ * @throws `CancelSentinelCollisionError` SYNCHRONOUSLY (not as a
138
+ * Promise rejection) if `record.allowedOptionIds` contains the
139
+ * cancel-vote sentinel string. This is a contract violation
140
+ * between agent and daemon and fails loudly at issue time
141
+ * rather than silently miscounting votes downstream. Callers
142
+ * inside an `async` function get the thrown error through the
143
+ * function's own Promise; synchronous callers must use
144
+ * `try/catch`.
145
+ */
146
+ request(record, timeoutMs) {
147
+ if (record.allowedOptionIds.has(CANCEL_VOTE_SENTINEL)) {
148
+ throw new CancelSentinelCollisionError(
149
+ record.requestId,
150
+ CANCEL_VOTE_SENTINEL
151
+ );
152
+ }
153
+ return new Promise((resolve2) => {
154
+ const policy = this.policy;
155
+ const votersAtIssue = this.deps.votersForSession(record.sessionId);
156
+ const pending = {
157
+ requestId: record.requestId,
158
+ sessionId: record.sessionId,
159
+ promptId: record.promptId,
160
+ policy,
161
+ originatorClientId: record.originatorClientId,
162
+ allowedOptionIds: record.allowedOptionIds,
163
+ issuedAtMs: record.issuedAtMs,
164
+ timeoutMs,
165
+ resolve: resolve2,
166
+ tallies: /* @__PURE__ */ new Map(),
167
+ votersAtIssue,
168
+ timer: void 0,
169
+ consensusQuorumCapNoted: false
170
+ };
171
+ this.pending.set(record.requestId, pending);
172
+ this.safeAudit(
173
+ () => this.deps.audit.recordRequested(record, policy, votersAtIssue)
174
+ );
175
+ const unresolvedWithoutDecision = timeoutMs > 0 ? `voter cancellation, session cancellation, or permissionTimeoutMs (${timeoutMs}ms)` : "voter or session cancellation because permissionTimeoutMs is disabled";
176
+ if (policy === "consensus" && votersAtIssue.size === 0) {
177
+ try {
178
+ process.stderr.write(
179
+ `permissionMediator: consensus request ${record.requestId} for session ${record.sessionId} issued with empty votersAtIssue; can only resolve via ${unresolvedWithoutDecision}
180
+ `
181
+ );
182
+ } catch {
183
+ }
184
+ }
185
+ if (policy === "consensus" && this.deps.consensusQuorum === void 0 && votersAtIssue.size >= 2 && Math.floor(votersAtIssue.size / 2) + 1 === votersAtIssue.size && !this.unanimityBreadcrumbEmitted) {
186
+ this.unanimityBreadcrumbEmitted = true;
187
+ try {
188
+ process.stderr.write(
189
+ `permissionMediator: consensus request ${record.requestId} for session ${record.sessionId} requires unanimity (votersAtIssue.size=${votersAtIssue.size}, default quorum=floor(M/2)+1=${votersAtIssue.size}); split votes will only resolve via ${unresolvedWithoutDecision}. This breadcrumb fires once per mediator lifetime; subsequent unanimity-required requests are silent.
190
+ `
191
+ );
192
+ } catch {
193
+ }
194
+ }
195
+ if (timeoutMs > 0) {
196
+ pending.timer = setTimeout(() => {
197
+ if (this.pending.get(record.requestId) !== pending) return;
198
+ const firedAtMs = this.deps.now();
199
+ try {
200
+ process.stderr.write(
201
+ `qwen serve: permission ${record.requestId} (session ${record.sessionId}) timed out after ${timeoutMs}ms
202
+ `
203
+ );
204
+ } catch {
205
+ }
206
+ this.safeAudit(() => this.deps.audit.recordTimeout(record));
207
+ this.resolveEntry(
208
+ pending,
209
+ { kind: "cancelled", reason: "timeout" },
210
+ {
211
+ type: "timeout",
212
+ issuedAtMs: pending.issuedAtMs,
213
+ timeoutMs: pending.timeoutMs,
214
+ firedAtMs
215
+ },
216
+ void 0
217
+ );
218
+ }, timeoutMs);
219
+ const t = pending.timer;
220
+ if (t && typeof t === "object" && "unref" in t) {
221
+ t.unref();
222
+ }
223
+ }
224
+ });
225
+ }
226
+ vote(vote) {
227
+ const pending = this.pending.get(vote.requestId);
228
+ if (!pending) {
229
+ const prior = this.resolved.get(vote.requestId);
230
+ if (prior && prior.sessionId === vote.sessionId) {
231
+ const optionId = prior.resolution.kind === "option" ? prior.resolution.optionId : CANCEL_VOTE_SENTINEL;
232
+ this.safeEmit(prior.sessionId, {
233
+ type: "permission_already_resolved",
234
+ ...prior.promptId ? { promptId: prior.promptId } : {},
235
+ data: {
236
+ requestId: prior.requestId,
237
+ sessionId: prior.sessionId,
238
+ outcome: this.toAcpOutcome(prior.resolution)
239
+ }
240
+ });
241
+ return { kind: "already_resolved", resolvedOptionId: optionId };
242
+ }
243
+ return { kind: "unknown_request" };
244
+ }
245
+ if (pending.sessionId !== vote.sessionId) {
246
+ return { kind: "unknown_request" };
247
+ }
248
+ if (vote.optionId === CANCEL_VOTE_SENTINEL) {
249
+ const outcome = {
250
+ kind: "resolved",
251
+ resolvedOptionId: CANCEL_VOTE_SENTINEL
252
+ };
253
+ this.safeAudit(
254
+ () => this.deps.audit.recordVoted(this.toRecord(pending), vote, outcome)
255
+ );
256
+ this.resolveEntry(
257
+ pending,
258
+ { kind: "cancelled", reason: "agent_cancelled" },
259
+ {
260
+ type: "voter-cancelled",
261
+ resolverClientId: vote.clientId
262
+ },
263
+ vote.clientId
264
+ );
265
+ return outcome;
266
+ }
267
+ if (!pending.allowedOptionIds.has(vote.optionId)) {
268
+ throw new InvalidPermissionOptionError(vote.requestId, vote.optionId);
269
+ }
270
+ switch (pending.policy) {
271
+ case "first-responder":
272
+ return this.voteFirstResponder(pending, vote);
273
+ case "designated":
274
+ return this.voteDesignated(pending, vote);
275
+ case "consensus":
276
+ return this.voteConsensus(pending, vote);
277
+ case "local-only":
278
+ return this.voteLocalOnly(pending, vote);
279
+ default: {
280
+ const _exhaustive = pending.policy;
281
+ void _exhaustive;
282
+ throw new Error(`Unknown permission policy "${pending.policy}"`);
283
+ }
284
+ }
285
+ }
286
+ forgetSession(sessionId) {
287
+ const requestIds = [];
288
+ for (const [id, pending] of this.pending) {
289
+ if (pending.sessionId === sessionId) requestIds.push(id);
290
+ }
291
+ for (const id of requestIds) {
292
+ const pending = this.pending.get(id);
293
+ if (!pending) continue;
294
+ this.resolveEntry(
295
+ pending,
296
+ { kind: "cancelled", reason: "session_closed" },
297
+ { type: "session-closed" },
298
+ void 0
299
+ );
300
+ }
301
+ }
302
+ /**
303
+ * Lookup the sessionId for a given requestId. Used by the legacy
304
+ * `bridge.respondToPermission(requestId, ...)` route which doesn't
305
+ * carry a sessionId in the URL. NOT part of the
306
+ * `PermissionMediator` interface contract — bridge holds the
307
+ * concrete class reference and calls this directly.
308
+ */
309
+ peekSessionFor(requestId) {
310
+ const pending = this.pending.get(requestId);
311
+ if (pending) return pending.sessionId;
312
+ const prior = this.resolved.get(requestId);
313
+ return prior?.sessionId;
314
+ }
315
+ /**
316
+ * Daemon-wide in-flight pending count for diagnostics. The bridge
317
+ * exposes this through its `pendingPermissionCount` getter so
318
+ * operators can spot stuck FIFOs without reaching into mediator
319
+ * internals. NOT part of the `PermissionMediator` interface
320
+ * contract.
321
+ */
322
+ get pendingCount() {
323
+ return this.pending.size;
324
+ }
325
+ // ===========================================================
326
+ // Per-policy vote handlers
327
+ // ===========================================================
328
+ voteFirstResponder(pending, vote) {
329
+ return this.resolveWithVote(pending, vote, {
330
+ type: "first-responder",
331
+ resolverClientId: vote.clientId
332
+ });
333
+ }
334
+ voteDesignated(pending, vote) {
335
+ if (pending.originatorClientId === void 0) {
336
+ return this.voteFirstResponder(pending, vote);
337
+ }
338
+ if (vote.clientId !== pending.originatorClientId) {
339
+ return this.rejectForbidden(
340
+ pending,
341
+ vote,
342
+ "designated_mismatch",
343
+ "designated_mismatch (voter is not the prompt originator)"
344
+ );
345
+ }
346
+ return this.resolveWithVote(pending, vote, {
347
+ type: "designated-originator",
348
+ originatorClientId: pending.originatorClientId
349
+ });
350
+ }
351
+ voteConsensus(pending, vote) {
352
+ if (vote.clientId === void 0 || !pending.votersAtIssue.has(vote.clientId)) {
353
+ return this.rejectForbidden(
354
+ pending,
355
+ vote,
356
+ "designated_mismatch",
357
+ "designated_mismatch (voter not in consensus votersAtIssue snapshot)"
358
+ );
359
+ }
360
+ for (const [originalOptionId, set] of pending.tallies.entries()) {
361
+ if (vote.clientId !== void 0 && set.has(vote.clientId)) {
362
+ const outcome2 = {
363
+ kind: "recorded",
364
+ votesNeeded: this.votesNeededFor(pending)
365
+ };
366
+ this.safeAudit(
367
+ () => this.deps.audit.recordVoted(
368
+ this.toRecord(pending),
369
+ { ...vote, optionId: originalOptionId },
370
+ outcome2
371
+ )
372
+ );
373
+ return outcome2;
374
+ }
375
+ }
376
+ let bucket = pending.tallies.get(vote.optionId);
377
+ if (!bucket) {
378
+ bucket = /* @__PURE__ */ new Set();
379
+ pending.tallies.set(vote.optionId, bucket);
380
+ }
381
+ bucket.add(vote.clientId);
382
+ const quorum = this.consensusQuorumFor(pending);
383
+ if (bucket.size >= quorum) {
384
+ return this.resolveWithVote(pending, vote, {
385
+ type: "consensus-quorum",
386
+ resolvedOptionId: vote.optionId,
387
+ quorum,
388
+ tally: bucket.size
389
+ });
390
+ }
391
+ const outcome = {
392
+ kind: "recorded",
393
+ votesNeeded: this.votesNeededFor(pending)
394
+ };
395
+ this.safeAudit(
396
+ () => this.deps.audit.recordVoted(this.toRecord(pending), vote, outcome)
397
+ );
398
+ this.safeEmit(pending.sessionId, {
399
+ type: "permission_partial_vote",
400
+ ...pending.promptId ? { promptId: pending.promptId } : {},
401
+ data: {
402
+ requestId: pending.requestId,
403
+ sessionId: pending.sessionId,
404
+ votesReceived: this.totalTalliedFor(pending),
405
+ votesNeeded: outcome.votesNeeded,
406
+ quorum,
407
+ optionTallies: this.optionTalliesFor(pending)
408
+ },
409
+ ...pending.originatorClientId !== void 0 ? { originatorClientId: pending.originatorClientId } : {}
410
+ });
411
+ return outcome;
412
+ }
413
+ /**
414
+ * Vote dispatch for `local-only` policy: only `fromLoopback: true`
415
+ * voters can resolve a permission.
416
+ *
417
+ * **Cancel-sentinel asymmetry** (cancel-sentinel note).
418
+ * `vote()` recognizes the cancel sentinel BEFORE calling this
419
+ * method (cross-policy escape hatch — see the
420
+ * `CANCEL_VOTE_SENTINEL` JSDoc for the rationale), so a remote
421
+ * voter under `local-only` CAN abort a pending permission via
422
+ * `{outcome:'cancelled'}` even though they cannot RESOLVE one. The
423
+ * settings-side description for `local-only` and the design doc call
424
+ * out this gap explicitly. Operators who want strict-cancel-too
425
+ * semantics must (a) deploy a dedicated daemon process at
426
+ * loopback bind, OR (b) wait for the follow-up PR that lifts
427
+ * cancel into per-policy gating; This version keeps the current
428
+ * cross-policy cancel for consistency with first-responder /
429
+ * designated / consensus.
430
+ */
431
+ voteLocalOnly(pending, vote) {
432
+ if (!vote.fromLoopback) {
433
+ return this.rejectForbidden(
434
+ pending,
435
+ vote,
436
+ "remote_not_allowed",
437
+ "remote_not_allowed (local-only policy; vote not from loopback)"
438
+ );
439
+ }
440
+ return this.resolveWithVote(pending, vote, {
441
+ type: "local-only-loopback",
442
+ resolverClientId: vote.clientId
443
+ });
444
+ }
445
+ // ===========================================================
446
+ // Shared vote-resolution helpers
447
+ // ===========================================================
448
+ resolveWithVote(pending, vote, decisionReason) {
449
+ const outcome = {
450
+ kind: "resolved",
451
+ resolvedOptionId: vote.optionId
452
+ };
453
+ this.safeAudit(
454
+ () => this.deps.audit.recordVoted(this.toRecord(pending), vote, outcome)
455
+ );
456
+ this.resolveEntry(
457
+ pending,
458
+ {
459
+ kind: "option",
460
+ optionId: vote.optionId,
461
+ ...vote.metadata ? { metadata: vote.metadata } : {}
462
+ },
463
+ decisionReason,
464
+ vote.clientId
465
+ );
466
+ return outcome;
467
+ }
468
+ rejectForbidden(pending, vote, reason, stderrDetail) {
469
+ this.safeAudit(
470
+ () => this.deps.audit.recordForbidden(this.toRecord(pending), vote, reason)
471
+ );
472
+ this.safeEmit(pending.sessionId, {
473
+ type: "permission_forbidden",
474
+ ...pending.promptId ? { promptId: pending.promptId } : {},
475
+ data: {
476
+ requestId: pending.requestId,
477
+ sessionId: pending.sessionId,
478
+ ...vote.clientId !== void 0 ? { clientId: vote.clientId } : {},
479
+ reason
480
+ },
481
+ ...pending.originatorClientId !== void 0 ? { originatorClientId: pending.originatorClientId } : {}
482
+ });
483
+ this.writeForbiddenStderr(pending, vote, stderrDetail);
484
+ return { kind: "forbidden", reason };
485
+ }
486
+ // ===========================================================
487
+ // Consensus tally helpers
488
+ // ===========================================================
489
+ /**
490
+ * Compute the quorum size for a `consensus` request. Default
491
+ * `floor(M/2) + 1` of `votersAtIssue.size`; overridden by
492
+ * `deps.consensusQuorum` when set, capped to `M` so an operator
493
+ * misconfig (N > M) can't deadlock.
494
+ *
495
+ * When the cap fires, write
496
+ * a one-time stderr breadcrumb per request so operators don't
497
+ * have to diff their `policy.consensusQuorum` against
498
+ * `votersAtIssue.size` manually to understand why a quorum
499
+ * resolved sooner than configured. Tracked on `MediatorPending`
500
+ * so the breadcrumb fires once even though `consensusQuorumFor`
501
+ * may be called multiple times per request (vote tally + final
502
+ * resolution).
503
+ */
504
+ consensusQuorumFor(pending) {
505
+ const m = pending.votersAtIssue.size;
506
+ const override = this.deps.consensusQuorum;
507
+ if (override !== void 0) {
508
+ const capped = Math.min(override, Math.max(m, 1));
509
+ if (capped < override && !pending.consensusQuorumCapNoted) {
510
+ pending.consensusQuorumCapNoted = true;
511
+ try {
512
+ process.stderr.write(
513
+ `permissionMediator: consensusQuorum override ${override} capped to ${capped} (votersAtIssue.size=${m}) for request ${pending.requestId} session ${pending.sessionId}
514
+ `
515
+ );
516
+ } catch {
517
+ }
518
+ }
519
+ return capped;
520
+ }
521
+ return Math.max(1, Math.floor(m / 2) + 1);
522
+ }
523
+ totalTalliedFor(pending) {
524
+ let total = 0;
525
+ for (const set of pending.tallies.values()) total += set.size;
526
+ return total;
527
+ }
528
+ /**
529
+ * `votesNeeded` = `quorum - max(tally per option)`. When no
530
+ * option has any votes (degenerate; `permission_partial_vote`
531
+ * is only emitted AFTER the first vote, so this should never
532
+ * appear on the wire), returns `quorum` itself. Always ≥ 1
533
+ * because the resolved-on-quorum path returns before this
534
+ * helper runs.
535
+ */
536
+ votesNeededFor(pending) {
537
+ const quorum = this.consensusQuorumFor(pending);
538
+ let max = 0;
539
+ for (const set of pending.tallies.values()) {
540
+ if (set.size > max) max = set.size;
541
+ }
542
+ return Math.max(quorum - max, 1);
543
+ }
544
+ optionTalliesFor(pending) {
545
+ const out = {};
546
+ for (const [optionId, set] of pending.tallies) {
547
+ out[optionId] = set.size;
548
+ }
549
+ return out;
550
+ }
551
+ // ===========================================================
552
+ // Resolution + cleanup
553
+ // ===========================================================
554
+ /**
555
+ * Settle a pending entry. Cleanup order is hardened (cleanup-order invariant):
556
+ * 1. clearTimeout (so a timer can never fire on a half-cleaned entry).
557
+ * 2. Delete from `pending` (state-first half — entry no longer
558
+ * reachable for new votes).
559
+ * 3. emit wire `permission_resolved` (best-effort — emit failures
560
+ * do not block the Promise settle). MUST come before step 4
561
+ * so a re-entrant subscriber synchronously casting another
562
+ * vote during emit sees `pending === undefined && resolved
563
+ * === undefined` (silent false), matching the previous ordering.
564
+ *
565
+ * 4. write to `resolved` (the second half of state move — late
566
+ * voters arriving after this see `permission_already_resolved`).
567
+ * 5. audit.recordResolved (best-effort, same).
568
+ * 6. Settle the Promise (LAST — callbacks running re-entrantly
569
+ * see consistent state).
570
+ *
571
+ * Previously the spec bundled
572
+ * "delete pending + write resolved" into step 2 ahead of emit,
573
+ * which contradicted the code. The fix
574
+ * splits the two halves of the state move around the emit so
575
+ * the spec faithfully describes the ordering invariant.
576
+ *
577
+ * @param resolverClientId wire compat: the
578
+ * `permission_resolved` SSE frame stamps this as
579
+ * `originatorClientId`. The previous `resolvePending` in
580
+ * `httpAcpBridge.ts:1518-1523` filled it from the voter's
581
+ * trusted clientId. We preserve byte-for-byte; vote-driven
582
+ * paths pass `vote.clientId` (which may be undefined for
583
+ * loopback no-header voters); timer + session-closed paths
584
+ * pass undefined (no voter).
585
+ */
586
+ resolveEntry(pending, resolution, decisionReason, resolverClientId) {
587
+ if (this.pending.get(pending.requestId) !== pending) {
588
+ return;
589
+ }
590
+ if (pending.timer !== void 0) {
591
+ clearTimeout(pending.timer);
592
+ pending.timer = void 0;
593
+ }
594
+ this.pending.delete(pending.requestId);
595
+ this.safeEmit(pending.sessionId, {
596
+ type: "permission_resolved",
597
+ ...pending.promptId ? { promptId: pending.promptId } : {},
598
+ data: {
599
+ requestId: pending.requestId,
600
+ outcome: this.toAcpOutcome(resolution),
601
+ // Note: `voterClientId` is the canonical,
602
+ // unambiguous name for "who cast the resolving vote". The envelope
603
+ // `originatorClientId` below carries the SAME value for wire
604
+ // compat (it is semantically the voter on `permission_resolved`,
605
+ // unlike on `permission_request` where it is the prompt originator).
606
+ // Both are optional and omitted together for no-voter resolutions
607
+ // (timer expiry / session-closed / loopback voter with no clientId).
608
+ ...resolverClientId !== void 0 ? { voterClientId: resolverClientId } : {}
609
+ },
610
+ // Preserve pre-extraction behavior: voter's clientId is stamped
611
+ // here (not the prompt originator's). Documented inconsistency
612
+ // with `permission_request.originatorClientId` (which IS the
613
+ // prompt originator); we do not fix the inconsistency to
614
+ // avoid breaking the wire shape. A4 keeps it as a deprecated
615
+ // alias of `data.voterClientId`.
616
+ ...resolverClientId !== void 0 ? { originatorClientId: resolverClientId } : {}
617
+ });
618
+ this.rememberResolved({
619
+ requestId: pending.requestId,
620
+ sessionId: pending.sessionId,
621
+ promptId: pending.promptId,
622
+ resolution,
623
+ resolverClientId
624
+ });
625
+ if (decisionReason !== void 0) {
626
+ this.safeAudit(
627
+ () => this.deps.audit.recordResolved(
628
+ this.toRecord(pending),
629
+ resolution,
630
+ decisionReason
631
+ )
632
+ );
633
+ }
634
+ pending.resolve(resolution);
635
+ }
636
+ rememberResolved(record) {
637
+ if (!this.resolved.has(record.requestId)) {
638
+ this.resolvedOrder.push(record.requestId);
639
+ }
640
+ this.resolved.set(record.requestId, record);
641
+ while (this.resolvedOrder.length > MAX_RESOLVED_PERMISSION_RECORDS) {
642
+ const oldest = this.resolvedOrder.shift();
643
+ if (oldest !== void 0) this.resolved.delete(oldest);
644
+ }
645
+ }
646
+ safeEmit(sessionId, event) {
647
+ try {
648
+ this.deps.emit(sessionId, event);
649
+ } catch (err) {
650
+ try {
651
+ process.stderr.write(
652
+ `permissionMediator: emit failed for session=${JSON.stringify(sessionId)} type=${JSON.stringify(event.type)}: ${stringifyError(err)}
653
+ `
654
+ );
655
+ } catch {
656
+ }
657
+ }
658
+ }
659
+ /**
660
+ * Emit a stderr breadcrumb
661
+ * for every vote rejection (the three forbidden paths in
662
+ * voteDesignated / voteConsensus / voteLocalOnly). Mirrors the
663
+ * timeout breadcrumb pattern: audit ring + SSE event are
664
+ * transient observability surfaces (no v1 query route, SSE drops
665
+ * on disconnect), so an operator tailing daemon stderr would see
666
+ * zero indication of permission rejections without this.
667
+ *
668
+ * Wrapped in `try/catch` because `process.stderr.write` can
669
+ * synchronously throw on EPIPE during shutdown — a stderr
670
+ * unavailability must not propagate up through `safeEmit` /
671
+ * `safeAudit` and break the resolveEntry cleanup ladder. Mirrors
672
+ * the safeEmit/safeAudit defensive posture (see the
673
+ * matching hang scenario in safeEmit).
674
+ */
675
+ writeForbiddenStderr(pending, vote, reasonDetail) {
676
+ try {
677
+ const voterDescriptor = vote.clientId === void 0 ? "<anonymous>" : JSON.stringify(vote.clientId);
678
+ process.stderr.write(
679
+ `qwen serve: permission ${pending.requestId} (session ${pending.sessionId}): vote rejected (${reasonDetail}) by client ${voterDescriptor}
680
+ `
681
+ );
682
+ } catch {
683
+ }
684
+ }
685
+ /**
686
+ * Run an audit-publisher call defensively. The audit ring is
687
+ * best-effort observability — a publisher exception (ring full,
688
+ * host bug, transient I/O) MUST NOT throw out of `request()`,
689
+ * `vote()`, or the timer callback. Without this guard, the
690
+ * Promise the agent is awaiting would be left unsettled and the
691
+ * pending entry would leak.
692
+ *
693
+ * Single helper used at all five audit call sites so the
694
+ * "audit is best-effort" invariant is uniformly enforced (the
695
+ * pre-fix asymmetric `try/catch` at 2 of 5 sites was a real
696
+ * silent-failure hole.
697
+ *
698
+ * doc placement — JSDoc was previously
699
+ * stacked above `writeForbiddenStderr` so IDE hover and API
700
+ * doc generation showed the wrong attribution. Moved adjacent
701
+ * to its actual definition.
702
+ */
703
+ safeAudit(fn) {
704
+ try {
705
+ fn();
706
+ } catch (err) {
707
+ try {
708
+ process.stderr.write(
709
+ `permissionMediator: audit publisher threw: ${stringifyError(err)}
710
+ `
711
+ );
712
+ } catch {
713
+ }
714
+ }
715
+ }
716
+ toRecord(pending) {
717
+ return {
718
+ requestId: pending.requestId,
719
+ sessionId: pending.sessionId,
720
+ ...pending.promptId ? { promptId: pending.promptId } : {},
721
+ originatorClientId: pending.originatorClientId,
722
+ allowedOptionIds: pending.allowedOptionIds,
723
+ issuedAtMs: pending.issuedAtMs
724
+ };
725
+ }
726
+ toAcpOutcome(resolution) {
727
+ if (resolution.kind === "option") {
728
+ return { outcome: "selected", optionId: resolution.optionId };
729
+ }
730
+ return { outcome: "cancelled" };
731
+ }
732
+ };
733
+
734
+ // packages/acp-bridge/src/internal/stderrLine.ts
735
+ init_esbuild_shims();
736
+ function writeStderrLine(message) {
737
+ process.stderr.write(message.endsWith("\n") ? message : `${message}
738
+ `);
739
+ }
740
+ __name(writeStderrLine, "writeStderrLine");
741
+
742
+ // packages/acp-bridge/src/sessionAttachments.ts
743
+ init_esbuild_shims();
744
+ import { constants as fsConstants, promises as fs, statSync } from "node:fs";
745
+ import { tmpdir } from "node:os";
746
+ import * as path from "node:path";
747
+ var SESSION_ATTACHMENT_MAX_ITEM_BYTES = 8 * 1024 * 1024;
748
+ var SESSION_ATTACHMENT_MAX_NAME_BYTES = 255;
749
+ var SUPPORTED_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
750
+ "image/bmp",
751
+ "image/gif",
752
+ "image/jpeg",
753
+ "image/png",
754
+ "image/webp"
755
+ ]);
756
+ function hasVerifiableInode(ino) {
757
+ return Number.isSafeInteger(ino) && ino > 0;
758
+ }
759
+ __name(hasVerifiableInode, "hasVerifiableInode");
760
+ async function openDurableAttachmentDirectory(directory, openedHandle) {
761
+ const handle = openedHandle ?? await fs.open(
762
+ directory,
763
+ fsConstants.O_RDONLY | (process.platform === "win32" ? 0 : fsConstants.O_NOFOLLOW ?? 0)
764
+ );
765
+ try {
766
+ const opened = await handle.stat();
767
+ const current = await fs.stat(directory);
768
+ const openedInodeVerifiable = hasVerifiableInode(opened.ino);
769
+ const currentInodeVerifiable = hasVerifiableInode(current.ino);
770
+ if (!opened.isDirectory() || !current.isDirectory() || opened.dev !== current.dev || openedInodeVerifiable !== currentInodeVerifiable || openedInodeVerifiable && opened.ino !== current.ino) {
771
+ throw new Error("Session attachment parent directory changed.");
772
+ }
773
+ return {
774
+ path: directory,
775
+ handle,
776
+ dev: opened.dev,
777
+ ino: opened.ino,
778
+ inodeVerifiable: openedInodeVerifiable
779
+ };
780
+ } catch (error) {
781
+ await handle.close().catch(() => void 0);
782
+ throw error;
783
+ }
784
+ }
785
+ __name(openDurableAttachmentDirectory, "openDurableAttachmentDirectory");
786
+ async function openDurableAttachmentDirectoryIfPresent(directory) {
787
+ let handle;
788
+ try {
789
+ handle = await fs.open(
790
+ directory,
791
+ fsConstants.O_RDONLY | (process.platform === "win32" ? 0 : fsConstants.O_NOFOLLOW ?? 0)
792
+ );
793
+ } catch (error) {
794
+ if (error.code === "ENOENT") return void 0;
795
+ throw error;
796
+ }
797
+ return openDurableAttachmentDirectory(directory, handle);
798
+ }
799
+ __name(openDurableAttachmentDirectoryIfPresent, "openDurableAttachmentDirectoryIfPresent");
800
+ async function syncDurableAttachmentDirectory(expected) {
801
+ const opened = await expected.handle.stat();
802
+ const openedInodeVerifiable = hasVerifiableInode(opened.ino);
803
+ const current = await fs.stat(expected.path);
804
+ const currentInodeVerifiable = hasVerifiableInode(current.ino);
805
+ if (!opened.isDirectory() || !current.isDirectory() || opened.dev !== expected.dev || openedInodeVerifiable !== expected.inodeVerifiable || expected.inodeVerifiable && opened.ino !== expected.ino || current.dev !== expected.dev || currentInodeVerifiable !== expected.inodeVerifiable || expected.inodeVerifiable && current.ino !== expected.ino) {
806
+ throw new Error("Session attachment parent directory changed.");
807
+ }
808
+ try {
809
+ await expected.handle.sync();
810
+ } catch (error) {
811
+ if (process.platform !== "win32" || !["EACCES", "EINVAL", "EPERM"].includes(
812
+ error.code ?? ""
813
+ )) {
814
+ throw error;
815
+ }
816
+ }
817
+ const after = await fs.stat(expected.path);
818
+ const afterInodeVerifiable = hasVerifiableInode(after.ino);
819
+ if (!after.isDirectory() || after.dev !== expected.dev || afterInodeVerifiable !== expected.inodeVerifiable || expected.inodeVerifiable && after.ino !== expected.ino) {
820
+ throw new Error("Session attachment parent directory changed.");
821
+ }
822
+ }
823
+ __name(syncDurableAttachmentDirectory, "syncDurableAttachmentDirectory");
824
+ var SESSION_ATTACHMENT_UNAVAILABLE_TEXT = "[Attachment is no longer available]";
825
+ var SessionAttachmentReferenceError = class extends Error {
826
+ constructor(message, code) {
827
+ super(message);
828
+ this.code = code;
829
+ this.name = "SessionAttachmentReferenceError";
830
+ }
831
+ static {
832
+ __name(this, "SessionAttachmentReferenceError");
833
+ }
834
+ };
835
+ function isSessionAttachmentReference(value) {
836
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
837
+ const record = value;
838
+ return (record["type"] === "image" || record["type"] === "resource") && typeof record["attachmentId"] === "string" && record["attachmentId"].length > 0 && typeof record["mimeType"] === "string" && record["mimeType"].length > 0 && (record["type"] !== "image" || record["mimeType"].startsWith("image/")) && typeof record["size"] === "number" && Number.isSafeInteger(record["size"]) && record["size"] >= 0 && (record["type"] !== "image" || record["size"] > 0);
839
+ }
840
+ __name(isSessionAttachmentReference, "isSessionAttachmentReference");
841
+ function safeAttachmentName(name) {
842
+ const safeName = path.basename(name.replaceAll("\\", "/")).trim();
843
+ const isWindowsReserved = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(safeName);
844
+ const hasInvalidCharacter = Array.from(safeName).some((character) => {
845
+ const codePoint = character.codePointAt(0);
846
+ return codePoint !== void 0 && (codePoint <= 31 || codePoint === 127 || codePoint >= 55296 && codePoint <= 57343);
847
+ });
848
+ return !safeName || safeName === "." || safeName === ".." || safeName.endsWith(".") || isWindowsReserved || /[<>:"|?*]/.test(safeName) || hasInvalidCharacter || Buffer.byteLength(safeName) > SESSION_ATTACHMENT_MAX_NAME_BYTES ? void 0 : safeName;
849
+ }
850
+ __name(safeAttachmentName, "safeAttachmentName");
851
+ function truncateUtf8(value, maxBytes) {
852
+ let bytes = 0;
853
+ let result = "";
854
+ for (const character of value) {
855
+ const characterBytes = Buffer.byteLength(character);
856
+ if (bytes + characterBytes > maxBytes) break;
857
+ result += character;
858
+ bytes += characterBytes;
859
+ }
860
+ return result;
861
+ }
862
+ __name(truncateUtf8, "truncateUtf8");
863
+ function deduplicatedName(name, suffix) {
864
+ if (suffix === 0) return name;
865
+ const extension = path.extname(name);
866
+ const suffixText = ` (${suffix})`;
867
+ const stem = name.slice(0, -extension.length || void 0);
868
+ const extensionBudget = SESSION_ATTACHMENT_MAX_NAME_BYTES - Buffer.byteLength(suffixText) - 1;
869
+ const safeExtension = truncateUtf8(extension, extensionBudget).replace(
870
+ /[. ]+$/u,
871
+ ""
872
+ );
873
+ const stemBudget = SESSION_ATTACHMENT_MAX_NAME_BYTES - Buffer.byteLength(suffixText) - Buffer.byteLength(safeExtension);
874
+ return `${truncateUtf8(stem, stemBudget)}${suffixText}${safeExtension}`;
875
+ }
876
+ __name(deduplicatedName, "deduplicatedName");
877
+ function imageName(mimeType) {
878
+ const extension = mimeType.slice("image/".length).split(/[;+]/, 1)[0];
879
+ return `image.${extension === "jpg" ? "jpeg" : extension || "img"}`;
880
+ }
881
+ __name(imageName, "imageName");
882
+ function mimeTypeForName(name) {
883
+ if ([".ts", ".mts", ".cts", ".tsx"].includes(path.extname(name).toLowerCase())) {
884
+ return "text/plain";
885
+ }
886
+ return getSpecificMimeType(name) ?? "application/octet-stream";
887
+ }
888
+ __name(mimeTypeForName, "mimeTypeForName");
889
+ function isSupportedImageMimeType(mimeType) {
890
+ return SUPPORTED_IMAGE_MIME_TYPES.has(mimeType);
891
+ }
892
+ __name(isSupportedImageMimeType, "isSupportedImageMimeType");
893
+ function isTextMimeType(mimeType) {
894
+ return mimeType.startsWith("text/") || mimeType === "application/json" || mimeType.endsWith("+json") || mimeType === "application/xml" || mimeType.endsWith("+xml") || mimeType === "application/javascript" || mimeType === "application/typescript" || mimeType === "application/yaml" || mimeType === "application/x-yaml" || mimeType === "application/toml";
895
+ }
896
+ __name(isTextMimeType, "isTextMimeType");
897
+ function isTextAttachment(data, mimeType) {
898
+ if (isTextMimeType(mimeType)) return true;
899
+ if (mimeType.startsWith("image/") || mimeType.startsWith("audio/") || mimeType.startsWith("video/") || mimeType.startsWith("font/") || mimeType === "application/pdf" || data.includes(0)) {
900
+ return false;
901
+ }
902
+ return Buffer.from(data.toString("utf8"), "utf8").equals(data);
903
+ }
904
+ __name(isTextAttachment, "isTextAttachment");
905
+ function withAttachmentDegradationMarker(blocks) {
906
+ for (let i = blocks.length - 1; i >= 0; i--) {
907
+ const block = blocks[i];
908
+ if (block.type === "text") {
909
+ if (block.text.endsWith(SESSION_ATTACHMENT_UNAVAILABLE_TEXT)) {
910
+ return [...blocks];
911
+ }
912
+ const next = [...blocks];
913
+ next[i] = {
914
+ type: "text",
915
+ text: `${block.text}
916
+ ${SESSION_ATTACHMENT_UNAVAILABLE_TEXT}`
917
+ };
918
+ return next;
919
+ }
920
+ }
921
+ return [
922
+ ...blocks,
923
+ { type: "text", text: SESSION_ATTACHMENT_UNAVAILABLE_TEXT }
924
+ ];
925
+ }
926
+ __name(withAttachmentDegradationMarker, "withAttachmentDegradationMarker");
927
+ var SessionAttachmentStore = class {
928
+ constructor(directoryRoot, sessionId) {
929
+ this.directoryRoot = directoryRoot;
930
+ if (!directoryRoot || !sessionId) return;
931
+ this.persistentDirectory = path.join(
932
+ directoryRoot,
933
+ `session-${encodeURIComponent(sessionId)}`
934
+ );
935
+ }
936
+ static {
937
+ __name(this, "SessionAttachmentStore");
938
+ }
939
+ directoryPromise;
940
+ persistentDirectory;
941
+ activeDirectory;
942
+ pendingItems = 0;
943
+ pendingNames = /* @__PURE__ */ new Map();
944
+ pendingDrainWaiters = [];
945
+ copyDrainWaiters = [];
946
+ copying = false;
947
+ closing = false;
948
+ closed = false;
949
+ async putAttachment(data, mimeType, name) {
950
+ const isImage = isSupportedImageMimeType(mimeType);
951
+ const safeName = safeAttachmentName(
952
+ name ?? (isImage ? imageName(mimeType) : "")
953
+ );
954
+ if (!safeName) {
955
+ throw new TypeError("Session attachment name is invalid");
956
+ }
957
+ const storedMimeType = mimeTypeForName(safeName);
958
+ if (isImage && storedMimeType !== mimeType || isSupportedImageMimeType(storedMimeType) && !isImage) {
959
+ throw new TypeError("Attachment name and Content-Type do not match");
960
+ }
961
+ if (this.closed || this.closing) {
962
+ throw new Error("Session attachment store is closed");
963
+ }
964
+ if (this.copying) throw new Error("Session attachments are being copied");
965
+ if (isImage && data.byteLength === 0 || data.byteLength > SESSION_ATTACHMENT_MAX_ITEM_BYTES) {
966
+ throw new RangeError(
967
+ `Session attachment must be at most ${SESSION_ATTACHMENT_MAX_ITEM_BYTES} bytes and images cannot be empty`
968
+ );
969
+ }
970
+ let filePath;
971
+ let pendingName = safeName;
972
+ let removeFileOnFailure = false;
973
+ this.pendingItems += 1;
974
+ this.reservePendingName(safeName);
975
+ try {
976
+ const directory = await this.directory();
977
+ let suffix = 0;
978
+ for (; ; ) {
979
+ const candidateName = deduplicatedName(safeName, suffix);
980
+ if (safeAttachmentName(candidateName) !== candidateName) {
981
+ throw new TypeError("Session attachment name is invalid");
982
+ }
983
+ if (pendingName !== candidateName) {
984
+ if (pendingName) this.releasePendingName(pendingName);
985
+ this.reservePendingName(candidateName);
986
+ pendingName = candidateName;
987
+ }
988
+ filePath = path.join(directory, candidateName);
989
+ removeFileOnFailure = true;
990
+ try {
991
+ await fs.writeFile(filePath, data, { flag: "wx" });
992
+ break;
993
+ } catch (error) {
994
+ if (error.code !== "EEXIST") throw error;
995
+ removeFileOnFailure = false;
996
+ this.releasePendingName(candidateName);
997
+ pendingName = void 0;
998
+ filePath = void 0;
999
+ suffix += 1;
1000
+ }
1001
+ }
1002
+ if (this.closed || this.closing) {
1003
+ throw new Error("Session attachment store is closed");
1004
+ }
1005
+ const name2 = path.basename(filePath);
1006
+ const storedMimeType2 = mimeTypeForName(name2);
1007
+ const reference = {
1008
+ type: isSupportedImageMimeType(storedMimeType2) ? "image" : "resource",
1009
+ attachmentId: name2,
1010
+ mimeType: storedMimeType2,
1011
+ size: data.byteLength
1012
+ };
1013
+ return reference;
1014
+ } catch (error) {
1015
+ if (removeFileOnFailure && filePath) {
1016
+ await fs.rm(filePath, { force: true }).catch(() => {
1017
+ });
1018
+ }
1019
+ throw error;
1020
+ } finally {
1021
+ if (pendingName) this.releasePendingName(pendingName);
1022
+ if (!this.closed) {
1023
+ this.pendingItems -= 1;
1024
+ if (this.pendingItems === 0) {
1025
+ this.resolvePendingDrainWaiters();
1026
+ }
1027
+ }
1028
+ }
1029
+ }
1030
+ // Validate one block against the store. Ordinary ACP content passes through
1031
+ // untouched, matching `assertReferences`.
1032
+ assertReference(block) {
1033
+ if (!block || typeof block !== "object" || Array.isArray(block) || !("attachmentId" in block)) {
1034
+ return;
1035
+ }
1036
+ if (!isSessionAttachmentReference(block)) {
1037
+ throw new SessionAttachmentReferenceError(
1038
+ "Invalid session attachment reference",
1039
+ "invalid_session_attachment_reference"
1040
+ );
1041
+ }
1042
+ this.assertStored(block);
1043
+ }
1044
+ assertReferences(content) {
1045
+ const seenIds = /* @__PURE__ */ new Set();
1046
+ for (const block of content) {
1047
+ if (!block || typeof block !== "object" || Array.isArray(block) || !("attachmentId" in block)) {
1048
+ continue;
1049
+ }
1050
+ if (!isSessionAttachmentReference(block)) {
1051
+ throw new SessionAttachmentReferenceError(
1052
+ "Invalid session attachment reference",
1053
+ "invalid_session_attachment_reference"
1054
+ );
1055
+ }
1056
+ const id = block.attachmentId;
1057
+ if (seenIds.has(id)) {
1058
+ throw new SessionAttachmentReferenceError(
1059
+ `Session attachment referenced more than once: ${id}`,
1060
+ "invalid_session_attachment_reference"
1061
+ );
1062
+ }
1063
+ seenIds.add(id);
1064
+ this.assertStored(block);
1065
+ }
1066
+ }
1067
+ async resolveContent(content, memo) {
1068
+ const pendingById = memo ?? /* @__PURE__ */ new Map();
1069
+ return await Promise.all(
1070
+ content.map(async (block) => {
1071
+ if (!isSessionAttachmentReference(block)) return block;
1072
+ const id = block.attachmentId;
1073
+ let pending = pendingById.get(id);
1074
+ if (!pending) {
1075
+ const created = this.resolve(block);
1076
+ pendingById.set(id, created);
1077
+ void created.catch(() => {
1078
+ if (pendingById.get(id) === created) {
1079
+ pendingById.delete(id);
1080
+ }
1081
+ });
1082
+ pending = created;
1083
+ }
1084
+ return await pending;
1085
+ })
1086
+ );
1087
+ }
1088
+ // Per-block variant of `resolveContent` for degrade paths: one unresolvable
1089
+ // reference drops only itself, keeping the sibling blocks a wholesale
1090
+ // fallback would discard. Other errors still propagate.
1091
+ async resolveContentDegrading(content, memo) {
1092
+ const retainedBlocks = [];
1093
+ const resolvedBlocks = [];
1094
+ let degraded = 0;
1095
+ for (const block of content) {
1096
+ if (!isSessionAttachmentReference(block)) {
1097
+ retainedBlocks.push(block);
1098
+ resolvedBlocks.push(block);
1099
+ continue;
1100
+ }
1101
+ try {
1102
+ const [resolved] = await this.resolveContent([block], memo);
1103
+ if (resolved) resolvedBlocks.push(resolved);
1104
+ retainedBlocks.push(block);
1105
+ } catch (error) {
1106
+ if (!(error instanceof SessionAttachmentReferenceError)) throw error;
1107
+ degraded += 1;
1108
+ }
1109
+ }
1110
+ return { retainedBlocks, resolvedBlocks, degraded };
1111
+ }
1112
+ async read(attachmentId) {
1113
+ const name = safeAttachmentName(attachmentId);
1114
+ if (!name || name !== attachmentId) return void 0;
1115
+ const filePath = path.join(await this.directory(), name);
1116
+ try {
1117
+ return {
1118
+ data: await fs.readFile(filePath),
1119
+ mimeType: mimeTypeForName(name)
1120
+ };
1121
+ } catch (error) {
1122
+ if (error.code === "ENOENT") {
1123
+ return void 0;
1124
+ }
1125
+ throw error;
1126
+ }
1127
+ }
1128
+ async copyFrom(source) {
1129
+ if (source === this) return;
1130
+ if (this.closed || this.closing) {
1131
+ throw new Error("Session attachment store is closed");
1132
+ }
1133
+ if (this.copying) throw new Error("Session attachments are being copied");
1134
+ if (source.closed || source.closing) {
1135
+ throw new Error("Session attachment store is closed");
1136
+ }
1137
+ if (source.copying) {
1138
+ throw new Error("Session attachments are being copied");
1139
+ }
1140
+ this.copying = true;
1141
+ source.copying = true;
1142
+ try {
1143
+ if (this.pendingItems > 0) {
1144
+ await new Promise(
1145
+ (resolve2) => this.pendingDrainWaiters.push(resolve2)
1146
+ );
1147
+ }
1148
+ if (source.pendingItems > 0) {
1149
+ await new Promise(
1150
+ (resolve2) => source.pendingDrainWaiters.push(resolve2)
1151
+ );
1152
+ }
1153
+ if (this.closed) throw new Error("Session attachment store is closed");
1154
+ const sourceDirectory = source.persistentDirectory ?? source.activeDirectory;
1155
+ if (!sourceDirectory) return;
1156
+ let entries;
1157
+ try {
1158
+ entries = await fs.readdir(sourceDirectory, { withFileTypes: true });
1159
+ } catch (error) {
1160
+ if (error.code === "ENOENT") return;
1161
+ throw error;
1162
+ }
1163
+ const targetDirectory = await this.directory();
1164
+ await Promise.all(
1165
+ entries.filter(
1166
+ (entry) => entry.isFile() && !source.pendingNames.has(entry.name)
1167
+ ).map(async (entry) => {
1168
+ const sourcePath = path.join(sourceDirectory, entry.name);
1169
+ try {
1170
+ await fs.copyFile(
1171
+ sourcePath,
1172
+ path.join(targetDirectory, entry.name)
1173
+ );
1174
+ } catch (error) {
1175
+ if (error.code === "ENOENT") {
1176
+ try {
1177
+ await fs.stat(sourcePath);
1178
+ } catch (sourceError) {
1179
+ if (sourceError.code === "ENOENT") {
1180
+ return;
1181
+ }
1182
+ }
1183
+ }
1184
+ throw error;
1185
+ }
1186
+ })
1187
+ );
1188
+ } finally {
1189
+ source.copying = false;
1190
+ this.copying = false;
1191
+ source.resolveCopyDrainWaiters();
1192
+ this.resolveCopyDrainWaiters();
1193
+ }
1194
+ }
1195
+ async remove(attachmentId) {
1196
+ const name = safeAttachmentName(attachmentId);
1197
+ if (!name || name !== attachmentId || this.copying || this.pendingNames.has(name)) {
1198
+ return false;
1199
+ }
1200
+ const directory = await this.directory();
1201
+ const filePath = path.join(directory, name);
1202
+ try {
1203
+ await fs.unlink(filePath);
1204
+ return true;
1205
+ } catch (error) {
1206
+ if (error.code === "ENOENT") {
1207
+ return false;
1208
+ }
1209
+ throw error;
1210
+ }
1211
+ }
1212
+ async close() {
1213
+ this.closing = true;
1214
+ await this.waitForCopy();
1215
+ if (this.closed) return;
1216
+ this.closed = true;
1217
+ this.pendingItems = 0;
1218
+ this.pendingNames.clear();
1219
+ this.resolvePendingDrainWaiters();
1220
+ if (this.persistentDirectory || !this.directoryPromise) return;
1221
+ const directory = await this.directoryPromise.catch(() => void 0);
1222
+ if (!directory) return;
1223
+ await fs.rm(directory, { recursive: true, force: true });
1224
+ }
1225
+ async delete(options = {}) {
1226
+ options.assertCanCommit?.();
1227
+ this.closing = true;
1228
+ await this.waitForCopy();
1229
+ options.assertCanCommit?.();
1230
+ if (!this.closed) {
1231
+ this.closed = true;
1232
+ this.pendingItems = 0;
1233
+ this.pendingNames.clear();
1234
+ this.resolvePendingDrainWaiters();
1235
+ }
1236
+ const directory = this.persistentDirectory ?? await this.directoryPromise?.catch(() => void 0);
1237
+ if (directory) {
1238
+ options.assertCanCommit?.();
1239
+ const parent = await openDurableAttachmentDirectoryIfPresent(
1240
+ path.dirname(directory)
1241
+ );
1242
+ if (!parent) return;
1243
+ const tombstone = path.join(
1244
+ path.dirname(directory),
1245
+ `.${path.basename(directory)}.deleting`
1246
+ );
1247
+ try {
1248
+ options.assertCanCommit?.();
1249
+ let tombstoneExists = false;
1250
+ try {
1251
+ const stats = await fs.lstat(tombstone);
1252
+ if (!stats.isDirectory() || stats.isSymbolicLink()) {
1253
+ throw new Error("Session attachment tombstone is not a directory.");
1254
+ }
1255
+ tombstoneExists = true;
1256
+ } catch (error) {
1257
+ if (error.code !== "ENOENT") throw error;
1258
+ }
1259
+ if (tombstoneExists) {
1260
+ options.assertCanCommit?.();
1261
+ await fs.rm(tombstone, { recursive: true, force: true });
1262
+ await syncDurableAttachmentDirectory(parent);
1263
+ return;
1264
+ }
1265
+ options.assertCanCommit?.();
1266
+ try {
1267
+ await fs.rename(directory, tombstone);
1268
+ } catch (error) {
1269
+ if (error.code !== "ENOENT") throw error;
1270
+ try {
1271
+ const stats = await fs.lstat(tombstone);
1272
+ if (!stats.isDirectory() || stats.isSymbolicLink()) {
1273
+ throw new Error(
1274
+ "Session attachment tombstone is not a directory."
1275
+ );
1276
+ }
1277
+ options.assertCanCommit?.();
1278
+ await fs.rm(tombstone, { recursive: true, force: true });
1279
+ } catch (tombstoneError) {
1280
+ if (tombstoneError.code !== "ENOENT") {
1281
+ throw tombstoneError;
1282
+ }
1283
+ }
1284
+ await syncDurableAttachmentDirectory(parent);
1285
+ return;
1286
+ }
1287
+ await fs.rm(tombstone, { recursive: true, force: true });
1288
+ await syncDurableAttachmentDirectory(parent);
1289
+ } finally {
1290
+ await parent.handle.close().catch(() => void 0);
1291
+ }
1292
+ }
1293
+ }
1294
+ assertStored(reference) {
1295
+ const id = reference.attachmentId;
1296
+ const name = safeAttachmentName(id);
1297
+ let size;
1298
+ const directory = this.persistentDirectory ?? this.activeDirectory;
1299
+ if (name && name === id && directory) {
1300
+ try {
1301
+ size = statSync(path.join(directory, name)).size;
1302
+ } catch {
1303
+ size = void 0;
1304
+ }
1305
+ }
1306
+ const storedMimeType = name ? mimeTypeForName(name) : void 0;
1307
+ const storedType = storedMimeType ? isSupportedImageMimeType(storedMimeType) ? "image" : "resource" : void 0;
1308
+ if (size !== reference.size || storedMimeType !== reference.mimeType || storedType !== reference.type) {
1309
+ throw new SessionAttachmentReferenceError(
1310
+ `Unknown or unavailable session attachment: ${id}`,
1311
+ "session_attachment_gone"
1312
+ );
1313
+ }
1314
+ }
1315
+ releasePendingName(name) {
1316
+ const count = this.pendingNames.get(name) ?? 0;
1317
+ if (count <= 1) this.pendingNames.delete(name);
1318
+ else this.pendingNames.set(name, count - 1);
1319
+ }
1320
+ reservePendingName(name) {
1321
+ this.pendingNames.set(name, (this.pendingNames.get(name) ?? 0) + 1);
1322
+ }
1323
+ async waitForCopy() {
1324
+ while (this.copying) {
1325
+ await new Promise((resolve2) => this.copyDrainWaiters.push(resolve2));
1326
+ }
1327
+ }
1328
+ resolveCopyDrainWaiters() {
1329
+ for (const resolve2 of this.copyDrainWaiters.splice(0)) resolve2();
1330
+ }
1331
+ resolvePendingDrainWaiters() {
1332
+ for (const resolve2 of this.pendingDrainWaiters.splice(0)) resolve2();
1333
+ }
1334
+ async resolve(reference) {
1335
+ const id = reference.attachmentId;
1336
+ const attachment = await this.read(id);
1337
+ if (!attachment) {
1338
+ throw new SessionAttachmentReferenceError(
1339
+ `Unknown or unavailable session attachment: ${id}`,
1340
+ "session_attachment_gone"
1341
+ );
1342
+ }
1343
+ if (reference.type === "resource") {
1344
+ const resource = {
1345
+ uri: `attachment:///${encodeURIComponent(reference.attachmentId)}`,
1346
+ mimeType: attachment.mimeType,
1347
+ ...isTextAttachment(attachment.data, attachment.mimeType) ? { text: attachment.data.toString("utf8") } : { blob: attachment.data.toString("base64") }
1348
+ };
1349
+ return {
1350
+ type: "resource",
1351
+ resource
1352
+ };
1353
+ }
1354
+ return {
1355
+ type: "image",
1356
+ data: attachment.data.toString("base64"),
1357
+ mimeType: attachment.mimeType
1358
+ };
1359
+ }
1360
+ async directory() {
1361
+ if (!this.directoryPromise) {
1362
+ const pending = this.persistentDirectory ? fs.mkdir(this.persistentDirectory, {
1363
+ recursive: true,
1364
+ mode: 448
1365
+ }).then(() => this.persistentDirectory) : this.directoryRoot ? fs.mkdir(this.directoryRoot, { recursive: true, mode: 448 }).then(
1366
+ () => fs.mkdtemp(
1367
+ path.join(this.directoryRoot, "session-attachment-")
1368
+ )
1369
+ ) : fs.mkdtemp(path.join(tmpdir(), "qwen-session-attachment-"));
1370
+ const directoryPromise = pending.then((directory) => {
1371
+ this.activeDirectory = directory;
1372
+ return directory;
1373
+ });
1374
+ this.directoryPromise = directoryPromise;
1375
+ void directoryPromise.catch(() => {
1376
+ if (this.directoryPromise === directoryPromise)
1377
+ this.directoryPromise = void 0;
1378
+ });
1379
+ }
1380
+ return await this.directoryPromise;
1381
+ }
1382
+ };
1383
+
1384
+ // packages/acp-bridge/src/bridgeClient.ts
1385
+ init_esbuild_shims();
1386
+ import { randomUUID } from "node:crypto";
1387
+ import { promises as fs2 } from "node:fs";
1388
+ import * as path2 from "node:path";
1389
+
1390
+ // packages/acp-bridge/src/daemonEventTypes.ts
1391
+ init_esbuild_shims();
1392
+ var MID_TURN_MESSAGE_INJECTED_EVENT = "mid_turn_message_injected";
1393
+
1394
+ // packages/acp-bridge/src/bridgeClient.ts
1395
+ var MAX_SCHEDULED_TASK_CRON_CHARS = 200;
1396
+ var MAX_SCHEDULED_TASK_PROMPT_CHARS = 1e5;
1397
+ function parseActiveWorkSnapshot(params) {
1398
+ const seq = params["seq"];
1399
+ const sessions = params["sessions"];
1400
+ if (params["v"] !== ACTIVE_WORK_HEARTBEAT_VERSION || typeof seq !== "number" || !Number.isSafeInteger(seq) || seq <= 0 || !Array.isArray(sessions) || sessions.length > ACTIVE_WORK_MAX_SNAPSHOT_SESSIONS) {
1401
+ return void 0;
1402
+ }
1403
+ const parsed = [];
1404
+ for (const raw of sessions) {
1405
+ if (typeof raw !== "object" || raw === null) return void 0;
1406
+ const entry = raw;
1407
+ const sessionId = entry["sessionId"];
1408
+ const holds = entry["holds"];
1409
+ if (typeof sessionId !== "string" || !Array.isArray(holds) || holds.length > ACTIVE_WORK_MAX_SESSION_HOLDS) {
1410
+ return void 0;
1411
+ }
1412
+ const parsedHolds = [];
1413
+ for (const rawHold of holds) {
1414
+ if (typeof rawHold !== "object" || rawHold === null) return void 0;
1415
+ const hold = rawHold;
1416
+ const category = hold["category"];
1417
+ const id = hold["id"];
1418
+ if (typeof id !== "string" || typeof category !== "string" || !ACTIVE_WORK_HOLD_CATEGORIES.includes(
1419
+ category
1420
+ )) {
1421
+ return void 0;
1422
+ }
1423
+ parsedHolds.push({
1424
+ category,
1425
+ id
1426
+ });
1427
+ }
1428
+ parsed.push({ sessionId, holds: parsedHolds });
1429
+ }
1430
+ return { v: ACTIVE_WORK_HEARTBEAT_VERSION, seq, sessions: parsed };
1431
+ }
1432
+ __name(parseActiveWorkSnapshot, "parseActiveWorkSnapshot");
1433
+ var PUBLISH_ARTIFACT_TOOL_NAME = "artifact";
1434
+ var MAX_CHANNEL_DELIVERY_TEXT_CHARS = 1e5;
1435
+ var MAX_CHANNEL_DELIVERY_FIELD_CHARS = 2048;
1436
+ var MAX_CHANNEL_DELIVERY_ERROR_CHARS = 500;
1437
+ function isFsErrorShape(err) {
1438
+ return err instanceof Error && err.name === "FsError" && typeof err.kind === "string";
1439
+ }
1440
+ __name(isFsErrorShape, "isFsErrorShape");
1441
+ function isExistingSessionScheduledTaskCreateErrorShape(err) {
1442
+ if (!(err instanceof Error)) return false;
1443
+ const status = err.status;
1444
+ const code = err.code;
1445
+ return err.name === "ExistingSessionScheduledTaskCreateError" && typeof status === "number" && Number.isFinite(status) && typeof code === "string" && code.length > 0;
1446
+ }
1447
+ __name(isExistingSessionScheduledTaskCreateErrorShape, "isExistingSessionScheduledTaskCreateErrorShape");
1448
+ function isRecord(value) {
1449
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1450
+ }
1451
+ __name(isRecord, "isRecord");
1452
+ function normalizeExternalToolGuardResult(value) {
1453
+ if (!isRecord(value)) {
1454
+ throw new Error("External tool guard handler returned an invalid result.");
1455
+ }
1456
+ const keys = Object.keys(value);
1457
+ if (value["allowed"] === true && keys.length === 1 && keys[0] === "allowed") {
1458
+ return { allowed: true };
1459
+ }
1460
+ if (value["allowed"] !== false || keys.some((key) => key !== "allowed" && key !== "reason")) {
1461
+ throw new Error("External tool guard handler returned an invalid result.");
1462
+ }
1463
+ if (!Object.hasOwn(value, "reason")) return { allowed: false };
1464
+ const reason = value["reason"];
1465
+ if (!isValidExternalToolGuardDenialReason(reason)) {
1466
+ throw new Error("External tool guard handler returned an invalid result.");
1467
+ }
1468
+ return { allowed: false, reason };
1469
+ }
1470
+ __name(normalizeExternalToolGuardResult, "normalizeExternalToolGuardResult");
1471
+ function isBoundedChannelDeliveryString(value) {
1472
+ return typeof value === "string" && value.trim().length > 0 && value.length <= MAX_CHANNEL_DELIVERY_FIELD_CHARS;
1473
+ }
1474
+ __name(isBoundedChannelDeliveryString, "isBoundedChannelDeliveryString");
1475
+ function isChannelDeliveryTarget(value) {
1476
+ if (!isRecord(value)) return false;
1477
+ return isBoundedChannelDeliveryString(value["channelName"]) && (value["type"] === "user" || value["type"] === "chat") && isBoundedChannelDeliveryString(value["id"]) && Object.keys(value).every(
1478
+ (key) => key === "channelName" || key === "type" || key === "id"
1479
+ );
1480
+ }
1481
+ __name(isChannelDeliveryTarget, "isChannelDeliveryTarget");
1482
+ function normalizeChannelDeliveryHostResult(value) {
1483
+ if (value.status === "delivered") return { status: "delivered" };
1484
+ if (value.status === "skipped") return { status: "skipped" };
1485
+ const code = CHANNEL_DELIVERY_ERROR_CODES.has(value.code) ? value.code : "channel_delivery_failed";
1486
+ const error = typeof value.error === "string" && value.error.length > 0 ? value.error.slice(0, MAX_CHANNEL_DELIVERY_ERROR_CHARS) : "Channel delivery failed.";
1487
+ return { status: "failed", code, error };
1488
+ }
1489
+ __name(normalizeChannelDeliveryHostResult, "normalizeChannelDeliveryHostResult");
1490
+ function pendingInteractionOptions(options) {
1491
+ return options.map((option) => ({
1492
+ optionId: String(option.optionId ?? ""),
1493
+ ...typeof option.name === "string" ? { label: option.name } : {},
1494
+ ...typeof option.kind === "string" ? { kind: option.kind } : {}
1495
+ }));
1496
+ }
1497
+ __name(pendingInteractionOptions, "pendingInteractionOptions");
1498
+ function pendingInteractionFromRequest(requestId, params) {
1499
+ const toolCall = params.toolCall;
1500
+ const meta = isRecord(toolCall["_meta"]) ? toolCall["_meta"] : void 0;
1501
+ const rawInput = toolCall["rawInput"];
1502
+ const options = pendingInteractionOptions(
1503
+ Array.isArray(params.options) ? params.options : []
1504
+ );
1505
+ const isUserQuestion = meta?.["qwenInteractionKind"] === "user_question";
1506
+ if (isUserQuestion) {
1507
+ const rawQuestions = Array.isArray(meta?.["qwenQuestions"]) ? meta["qwenQuestions"] : isRecord(rawInput) && Array.isArray(rawInput["questions"]) ? rawInput["questions"] : [];
1508
+ return {
1509
+ requestId,
1510
+ kind: "user_question",
1511
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1512
+ ...typeof toolCall["title"] === "string" ? { title: toolCall["title"] } : {},
1513
+ questions: rawQuestions.flatMap(
1514
+ (question, index) => isRecord(question) ? [{ ...question, answerKey: String(index) }] : []
1515
+ ),
1516
+ options
1517
+ };
1518
+ }
1519
+ return {
1520
+ requestId,
1521
+ kind: "permission",
1522
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1523
+ action: {
1524
+ ...typeof toolCall["kind"] === "string" ? { type: toolCall["kind"] } : {},
1525
+ ...typeof toolCall["title"] === "string" ? { title: toolCall["title"] } : {},
1526
+ ...toolCall["content"] !== void 0 ? { content: toolCall["content"] } : {},
1527
+ ...toolCall["locations"] !== void 0 ? { locations: toolCall["locations"] } : {},
1528
+ ...rawInput !== void 0 ? { input: rawInput } : {}
1529
+ },
1530
+ options
1531
+ };
1532
+ }
1533
+ __name(pendingInteractionFromRequest, "pendingInteractionFromRequest");
1534
+ function fallbackPendingPermissionInteraction(requestId, options) {
1535
+ return {
1536
+ requestId,
1537
+ kind: "permission",
1538
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1539
+ action: {},
1540
+ options: pendingInteractionOptions(options)
1541
+ };
1542
+ }
1543
+ __name(fallbackPendingPermissionInteraction, "fallbackPendingPermissionInteraction");
1544
+ function artifactPayloadFields(artifact) {
1545
+ return {
1546
+ title: artifact["title"],
1547
+ kind: artifact["kind"],
1548
+ storage: artifact["storage"],
1549
+ description: artifact["description"],
1550
+ workspacePath: artifact["workspacePath"],
1551
+ managedId: artifact["managedId"],
1552
+ url: artifact["url"],
1553
+ mimeType: artifact["mimeType"],
1554
+ sizeBytes: artifact["sizeBytes"],
1555
+ metadata: artifact["metadata"],
1556
+ retention: artifact["retention"]
1557
+ };
1558
+ }
1559
+ __name(artifactPayloadFields, "artifactPayloadFields");
1560
+ function extractCappedArtifactInputs(rawArtifacts, limit, sessionId, source, toInput) {
1561
+ const artifacts = [];
1562
+ for (let index = 0; index < rawArtifacts.length; index++) {
1563
+ const artifact = rawArtifacts[index];
1564
+ if (!isRecord(artifact)) {
1565
+ writeStderrLine(
1566
+ `[artifacts] session=${sessionId} action=dropped reason=malformed source=${source} index=${index}`
1567
+ );
1568
+ continue;
1569
+ }
1570
+ if (artifacts.length >= limit) {
1571
+ writeStderrLine(
1572
+ `[artifacts] session=${sessionId} action=dropped reason="artifact batch limit exceeded" source=${source} dropped=${rawArtifacts.length - index}`
1573
+ );
1574
+ break;
1575
+ }
1576
+ artifacts.push(toInput(artifact));
1577
+ }
1578
+ return artifacts;
1579
+ }
1580
+ __name(extractCappedArtifactInputs, "extractCappedArtifactInputs");
1581
+ function artifactIngestionErrorReason(error) {
1582
+ if (!(error instanceof Error)) {
1583
+ return String(error);
1584
+ }
1585
+ return {
1586
+ name: error.name,
1587
+ message: error.message,
1588
+ stack: error.stack?.split("\n").slice(0, 4).join("\n")
1589
+ };
1590
+ }
1591
+ __name(artifactIngestionErrorReason, "artifactIngestionErrorReason");
1592
+ function extractSessionUpdateArtifacts(params, updateMeta, limit, sessionId) {
1593
+ const rawArtifacts = updateMeta?.["artifacts"];
1594
+ if (!Array.isArray(rawArtifacts)) {
1595
+ return [];
1596
+ }
1597
+ const update = params.update;
1598
+ if (update.sessionUpdate !== "tool_call_update" || update.status !== "completed" && update.status !== "failed" && update.status !== "cancelled") {
1599
+ return [];
1600
+ }
1601
+ const toolCallId = typeof update.toolCallId === "string" ? update.toolCallId : void 0;
1602
+ const toolName = typeof updateMeta?.["toolName"] === "string" ? updateMeta["toolName"] : void 0;
1603
+ return extractCappedArtifactInputs(
1604
+ rawArtifacts,
1605
+ limit,
1606
+ sessionId,
1607
+ "tool",
1608
+ (artifact) => ({
1609
+ ...artifactPayloadFields(artifact),
1610
+ source: "tool",
1611
+ toolCallId,
1612
+ toolName
1613
+ })
1614
+ );
1615
+ }
1616
+ __name(extractSessionUpdateArtifacts, "extractSessionUpdateArtifacts");
1617
+ function sanitizeSessionUpdateArtifacts(params, updateMeta) {
1618
+ if (!Array.isArray(updateMeta?.["artifacts"])) {
1619
+ return params;
1620
+ }
1621
+ const sanitizedMeta = { ...updateMeta };
1622
+ delete sanitizedMeta["artifacts"];
1623
+ const update = {
1624
+ ...params.update,
1625
+ _meta: sanitizedMeta
1626
+ };
1627
+ return {
1628
+ ...params,
1629
+ update
1630
+ };
1631
+ }
1632
+ __name(sanitizeSessionUpdateArtifacts, "sanitizeSessionUpdateArtifacts");
1633
+ function isTrustedArtifactToolUpdate(params, updateMeta) {
1634
+ const update = params.update;
1635
+ return update.sessionUpdate === "tool_call_update" && update.status === "completed" && updateMeta?.["toolName"] === PUBLISH_ARTIFACT_TOOL_NAME;
1636
+ }
1637
+ __name(isTrustedArtifactToolUpdate, "isTrustedArtifactToolUpdate");
1638
+ function preserveFsErrorOverAcp(err) {
1639
+ if (isFsErrorShape(err)) {
1640
+ const code = err.kind === "parse_error" ? -32602 : -32603;
1641
+ throw new RequestError(code, err.message, {
1642
+ errorKind: err.kind,
1643
+ ...err.hint !== void 0 ? { hint: err.hint } : {},
1644
+ ...err.status !== void 0 ? { status: err.status } : {}
1645
+ });
1646
+ }
1647
+ throw err;
1648
+ }
1649
+ __name(preserveFsErrorOverAcp, "preserveFsErrorOverAcp");
1650
+ function preserveScheduledTaskCreateErrorOverAcp(err) {
1651
+ if (isExistingSessionScheduledTaskCreateErrorShape(err)) {
1652
+ throw new RequestError(err.status >= 500 ? -32603 : -32602, err.message, {
1653
+ errorKind: err.code,
1654
+ status: err.status,
1655
+ hint: err.message
1656
+ });
1657
+ }
1658
+ throw err;
1659
+ }
1660
+ __name(preserveScheduledTaskCreateErrorOverAcp, "preserveScheduledTaskCreateErrorOverAcp");
1661
+ function resolutionToAcpResponse(resolution) {
1662
+ if (resolution.kind === "option") {
1663
+ return {
1664
+ outcome: { outcome: "selected", optionId: resolution.optionId },
1665
+ ...resolution.metadata ?? {}
1666
+ };
1667
+ }
1668
+ return {
1669
+ outcome: { outcome: "cancelled" },
1670
+ _meta: {
1671
+ [DAEMON_PERMISSION_CANCEL_REASON_META_KEY]: resolution.reason
1672
+ }
1673
+ };
1674
+ }
1675
+ __name(resolutionToAcpResponse, "resolutionToAcpResponse");
1676
+ var MAX_EARLY_EVENT_SESSIONS = 64;
1677
+ var MAX_EARLY_EVENTS_PER_SESSION = 32;
1678
+ var MAX_SUGGESTION_LENGTH = 500;
1679
+ var EARLY_EVENT_TTL_MS = 6e4;
1680
+ var KNOWN_APPROVAL_MODES = new Set(
1681
+ APPROVAL_MODES
1682
+ );
1683
+ function describeStatKind(stats) {
1684
+ if (stats.isDirectory()) return "directory";
1685
+ if (stats.isSymbolicLink()) return "symlink";
1686
+ if (stats.isCharacterDevice()) return "character device";
1687
+ if (stats.isBlockDevice()) return "block device";
1688
+ if (stats.isFIFO()) return "named pipe (FIFO)";
1689
+ if (stats.isSocket()) return "socket";
1690
+ return "non-regular file";
1691
+ }
1692
+ __name(describeStatKind, "describeStatKind");
1693
+ function sliceLineRange(content, startLine, endLine) {
1694
+ let offset = 0;
1695
+ for (let i = 0; i < startLine; i++) {
1696
+ const nl = content.indexOf("\n", offset);
1697
+ if (nl === -1) return "";
1698
+ offset = nl + 1;
1699
+ }
1700
+ if (endLine === void 0) return content.slice(offset);
1701
+ let end = offset;
1702
+ const want = endLine - startLine;
1703
+ for (let i = 0; i < want; i++) {
1704
+ const nl = content.indexOf("\n", end);
1705
+ if (nl === -1) return content.slice(offset);
1706
+ end = nl + 1;
1707
+ }
1708
+ return content.slice(offset, end > offset ? end - 1 : end);
1709
+ }
1710
+ __name(sliceLineRange, "sliceLineRange");
1711
+ var BridgeClient = class {
1712
+ constructor(resolveEntry, resolvePendingRestoreEvents, mediator, permissionTimeoutMs, maxPendingPerSession, fileSystem, onModelPromoted, onModePromoted, clientMcpSender, ownsSession = () => true, onTokenUsage, onCreateSubSession, onGenerationEvent, onWorkspaceGenerationEvent, onChannelDelivery, hasSessionSpawnInFlight = () => false, getLiveScreenContextCaptureHandler = () => void 0, getLiveTaskToolRequestHandler = () => void 0, getLiveSpeakToUserHandler = () => void 0, externalToolGuard, onActiveWork, onSessionCatalogChanged, onGoalTurnEnded, onCreateCurrentSessionScheduledTask) {
1713
+ this.resolveEntry = resolveEntry;
1714
+ this.resolvePendingRestoreEvents = resolvePendingRestoreEvents;
1715
+ this.mediator = mediator;
1716
+ this.permissionTimeoutMs = permissionTimeoutMs;
1717
+ this.maxPendingPerSession = maxPendingPerSession;
1718
+ this.fileSystem = fileSystem;
1719
+ this.onModelPromoted = onModelPromoted;
1720
+ this.onModePromoted = onModePromoted;
1721
+ this.clientMcpSender = clientMcpSender;
1722
+ this.ownsSession = ownsSession;
1723
+ this.onTokenUsage = onTokenUsage;
1724
+ this.onCreateSubSession = onCreateSubSession;
1725
+ this.onGenerationEvent = onGenerationEvent;
1726
+ this.onWorkspaceGenerationEvent = onWorkspaceGenerationEvent;
1727
+ this.onChannelDelivery = onChannelDelivery;
1728
+ this.hasSessionSpawnInFlight = hasSessionSpawnInFlight;
1729
+ this.getLiveScreenContextCaptureHandler = getLiveScreenContextCaptureHandler;
1730
+ this.getLiveTaskToolRequestHandler = getLiveTaskToolRequestHandler;
1731
+ this.getLiveSpeakToUserHandler = getLiveSpeakToUserHandler;
1732
+ this.externalToolGuard = externalToolGuard;
1733
+ this.onActiveWork = onActiveWork;
1734
+ this.onSessionCatalogChanged = onSessionCatalogChanged;
1735
+ this.onGoalTurnEnded = onGoalTurnEnded;
1736
+ this.onCreateCurrentSessionScheduledTask = onCreateCurrentSessionScheduledTask;
1737
+ }
1738
+ static {
1739
+ __name(this, "BridgeClient");
1740
+ }
1741
+ async requestPermission(params) {
1742
+ const entry = this.resolveEntry(params.sessionId);
1743
+ if (!entry) return { outcome: { outcome: "cancelled" } };
1744
+ if (entry.pendingPermissionIds.size >= this.maxPendingPerSession) {
1745
+ writeStderrLine(
1746
+ `qwen serve: session ${entry.sessionId} exceeded maxPendingPermissionsPerSession (${this.maxPendingPerSession}) \u2014 resolving new permission as cancelled.`
1747
+ );
1748
+ return { outcome: { outcome: "cancelled" } };
1749
+ }
1750
+ const options = Array.isArray(params.options) ? params.options : [];
1751
+ const allowedOptionIds = new Set(
1752
+ options.map((o) => String(o.optionId ?? ""))
1753
+ );
1754
+ allowedOptionIds.delete("");
1755
+ const requestId = randomUUID();
1756
+ if (allowedOptionIds.has(CANCEL_VOTE_SENTINEL)) {
1757
+ throw new CancelSentinelCollisionError(requestId, CANCEL_VOTE_SENTINEL);
1758
+ }
1759
+ const published = entry.events.publish({
1760
+ type: "permission_request",
1761
+ ...entry.activePromptId ? { promptId: entry.activePromptId } : {},
1762
+ data: {
1763
+ requestId,
1764
+ sessionId: entry.sessionId,
1765
+ toolCall: params.toolCall,
1766
+ options
1767
+ },
1768
+ ...entry.activePromptOriginatorClientId ? { originatorClientId: entry.activePromptOriginatorClientId } : {}
1769
+ });
1770
+ if (!published) return { outcome: { outcome: "cancelled" } };
1771
+ let interaction;
1772
+ try {
1773
+ interaction = pendingInteractionFromRequest(requestId, {
1774
+ ...params,
1775
+ options
1776
+ });
1777
+ } catch (error) {
1778
+ writeStderrLine(
1779
+ `qwen serve: failed to snapshot pending interaction ${requestId}: ${error instanceof Error ? error.message : String(error)}`
1780
+ );
1781
+ interaction = fallbackPendingPermissionInteraction(requestId, options);
1782
+ }
1783
+ entry.pendingPermissionIds.add(requestId);
1784
+ if (interaction) entry.pendingInteractions.set(requestId, interaction);
1785
+ try {
1786
+ const record = {
1787
+ requestId,
1788
+ sessionId: entry.sessionId,
1789
+ promptId: entry.activePromptId,
1790
+ originatorClientId: entry.activePromptOriginatorClientId,
1791
+ allowedOptionIds,
1792
+ issuedAtMs: Date.now()
1793
+ };
1794
+ const resolution = await this.mediator.request(
1795
+ record,
1796
+ this.permissionTimeoutMs
1797
+ );
1798
+ return resolutionToAcpResponse(resolution);
1799
+ } finally {
1800
+ entry.pendingPermissionIds.delete(requestId);
1801
+ entry.pendingInteractions.delete(requestId);
1802
+ }
1803
+ }
1804
+ async sessionUpdate(params) {
1805
+ if (this.abandonedRestoreIds.has(params.sessionId)) {
1806
+ return;
1807
+ }
1808
+ if (!this.ownsSession(params.sessionId) && !this.inFlightRestoreIds.has(params.sessionId)) {
1809
+ writeStderrLine(
1810
+ `[demux] session=${params.sessionId} type=session_update action=dropped reason=session_not_owned`
1811
+ );
1812
+ return;
1813
+ }
1814
+ const entry = this.resolveEntry(params.sessionId);
1815
+ const events = entry?.events ?? this.resolvePendingRestoreEvents(params.sessionId);
1816
+ if (!events) return;
1817
+ const prepared = this.prepareSessionUpdateFrames(params, entry);
1818
+ for (const frame of prepared.frames) {
1819
+ events.publish(frame);
1820
+ }
1821
+ try {
1822
+ this.recordLiveTokenUsage(params, entry);
1823
+ } catch {
1824
+ }
1825
+ if (entry && prepared.artifacts.length > 0) {
1826
+ await this.upsertAndPublishArtifacts(
1827
+ entry,
1828
+ prepared.artifacts,
1829
+ {
1830
+ trustedPublisher: prepared.trustedPublisher
1831
+ },
1832
+ prepared.turn
1833
+ );
1834
+ }
1835
+ }
1836
+ prepareSessionUpdateFrames(params, entry) {
1837
+ const turn = {
1838
+ ...entry?.activePromptId ? { promptId: entry.activePromptId } : {},
1839
+ ...entry?.activePromptOriginatorClientId ? { originatorClientId: entry.activePromptOriginatorClientId } : {}
1840
+ };
1841
+ const frames = [];
1842
+ const a2ui = extractA2uiToolUpdate(params);
1843
+ if (a2ui) {
1844
+ for (const surface of a2ui.surfaces) {
1845
+ frames.push({
1846
+ type: "session_update",
1847
+ data: {
1848
+ sessionId: params.sessionId,
1849
+ update: {
1850
+ sessionUpdate: "a2ui",
1851
+ a2ui: {
1852
+ surfaceId: surface.surfaceId,
1853
+ callId: a2ui.callId,
1854
+ commands: surface.commands
1855
+ },
1856
+ _meta: { serverTimestamp: Date.now(), source: "a2ui-bridge" }
1857
+ }
1858
+ },
1859
+ ...turn
1860
+ });
1861
+ }
1862
+ params = a2ui.sanitizedParams;
1863
+ }
1864
+ const updateMeta = params.update._meta;
1865
+ const originalTs = updateMeta?.["serverTimestamp"] ?? updateMeta?.["timestamp"];
1866
+ const serverTimestamp = typeof originalTs === "number" && Number.isFinite(originalTs) ? originalTs : void 0;
1867
+ const artifacts = entry?.artifacts ? extractSessionUpdateArtifacts(
1868
+ params,
1869
+ updateMeta,
1870
+ entry.artifacts.inputBatchLimit(),
1871
+ entry.sessionId
1872
+ ) : [];
1873
+ const publishParams = sanitizeSessionUpdateArtifacts(params, updateMeta);
1874
+ frames.push({
1875
+ type: "session_update",
1876
+ data: publishParams,
1877
+ ...turn,
1878
+ ...serverTimestamp !== void 0 ? { _meta: { serverTimestamp } } : {}
1879
+ });
1880
+ return {
1881
+ frames,
1882
+ artifacts,
1883
+ trustedPublisher: isTrustedArtifactToolUpdate(params, updateMeta),
1884
+ turn
1885
+ };
1886
+ }
1887
+ async seedSessionUpdates(entry, updates, options = {}) {
1888
+ const frames = [];
1889
+ const artifactBatches = [];
1890
+ for (const update of updates) {
1891
+ const prepared = this.prepareSessionUpdateFrames(
1892
+ { sessionId: entry.sessionId, update },
1893
+ entry
1894
+ );
1895
+ frames.push(...prepared.frames);
1896
+ if (options.ingestArtifacts !== false && prepared.artifacts.length > 0) {
1897
+ artifactBatches.push({
1898
+ artifacts: prepared.artifacts,
1899
+ trustedPublisher: prepared.trustedPublisher,
1900
+ turn: prepared.turn
1901
+ });
1902
+ }
1903
+ }
1904
+ entry.events.seedReplayEvents(frames);
1905
+ for (const batch of artifactBatches) {
1906
+ await this.upsertAndPublishArtifacts(
1907
+ entry,
1908
+ batch.artifacts,
1909
+ {
1910
+ trustedPublisher: batch.trustedPublisher
1911
+ },
1912
+ batch.turn
1913
+ );
1914
+ }
1915
+ }
1916
+ async ingestSessionUpdateArtifactsReady(entry, updates) {
1917
+ for (const update of updates) {
1918
+ const prepared = this.prepareSessionUpdateFrames(
1919
+ { sessionId: entry.sessionId, update },
1920
+ entry
1921
+ );
1922
+ if (prepared.artifacts.length === 0) continue;
1923
+ const ingested = await this.upsertAndPublishArtifactsReady(
1924
+ entry,
1925
+ prepared.artifacts,
1926
+ { trustedPublisher: prepared.trustedPublisher },
1927
+ prepared.turn
1928
+ );
1929
+ if (!ingested) throw new Error("Artifact ingestion failed.");
1930
+ }
1931
+ }
1932
+ async drainDeferredSessionArtifacts(entry) {
1933
+ while (entry.deferredArtifactBatches.length > 0) {
1934
+ const batch = entry.deferredArtifactBatches[0];
1935
+ const ingested = await this.upsertAndPublishArtifactsReady(
1936
+ entry,
1937
+ batch.artifacts,
1938
+ batch.options,
1939
+ batch.turn
1940
+ );
1941
+ if (!ingested) throw new Error("Deferred artifact ingestion failed.");
1942
+ entry.deferredArtifactBatches.shift();
1943
+ entry.deferredArtifactInputCount -= batch.artifacts.length;
1944
+ }
1945
+ }
1946
+ /**
1947
+ * Daemon token-burn accounting for LIVE model turns. Called only from
1948
+ * `sessionUpdate` (the live session/update fan-in), never from
1949
+ * `seedSessionUpdates` — so batch load-replay never lands historical usage in
1950
+ * the current metrics window. Additionally guarded on a live `entry`: a stray
1951
+ * pending-restore frame (entry not yet registered) is skipped too, so replayed
1952
+ * history can't post a phantom burn spike with no model call.
1953
+ *
1954
+ * Usage rides an otherwise-empty `agent_message_chunk` as `update._meta.usage`
1955
+ * with per-round camelCase increments; subagent frames carry their own usage
1956
+ * (tagged `parentToolCallId`) and are independent turns, so counting each
1957
+ * frame once is the correct total. `_meta`/`usage` are optional and untyped.
1958
+ */
1959
+ recordLiveTokenUsage(params, entry) {
1960
+ if (!this.onTokenUsage || !entry) return;
1961
+ const updateMeta = params.update._meta;
1962
+ const usage = updateMeta?.["usage"];
1963
+ if (usage === null || typeof usage !== "object") return;
1964
+ const inputTokens = usage.inputTokens;
1965
+ const outputTokens = usage.outputTokens;
1966
+ if (typeof inputTokens !== "number" && typeof outputTokens !== "number") {
1967
+ return;
1968
+ }
1969
+ const durationMs = updateMeta?.["durationMs"];
1970
+ const apiErrors = updateMeta?.["apiErrors"];
1971
+ const apiRetries = updateMeta?.["apiRetries"];
1972
+ this.onTokenUsage(
1973
+ typeof inputTokens === "number" ? inputTokens : 0,
1974
+ typeof outputTokens === "number" ? outputTokens : 0,
1975
+ typeof durationMs === "number" ? durationMs : void 0,
1976
+ typeof apiErrors === "number" ? apiErrors : 0,
1977
+ typeof apiRetries === "number" ? apiRetries : 0
1978
+ );
1979
+ }
1980
+ /**
1981
+ * Bounded early-event buffer. The map is scoped to this BridgeClient and
1982
+ * therefore to one channel; a stale channel cannot seed a fresh channel's
1983
+ * future session. Frames are keyed by sessionId; each entry tracks its
1984
+ * `expiresAt` for lazy TTL-based eviction in `bufferEarlyEvent`. Drained by
1985
+ * `drainEarlyEvents` whenever the bridge registers a session with a matching
1986
+ * id. See MAX_EARLY_EVENT_* constants for capacity bounds.
1987
+ */
1988
+ earlyEvents = /* @__PURE__ */ new Map();
1989
+ /**
1990
+ * Tombstone for closed/killed session ids. Prevents late
1991
+ * `extNotification` from a dying child from leaking into the
1992
+ * early-event buffer and being replayed onto a future session
1993
+ * that reuses the same id via `session/load` or `session/resume`.
1994
+ *
1995
+ * Tombstone semantics:
1996
+ * - Marked when the bridge removes a sessionId from `byId` (kill
1997
+ * path, channel.exited handler, closeSession).
1998
+ * - Concurrently purges any in-flight `earlyEvents[id]`.
1999
+ * - `bufferEarlyEvent` rejects tombstoned ids.
2000
+ * - `drainEarlyEvents` clears the tombstone — a fresh
2001
+ * `createSessionEntry` for the same id is a legitimate
2002
+ * "load/resume of a persisted session id" case.
2003
+ * - TTL = `EARLY_EVENT_TTL_MS` (60s) — same as the early-event
2004
+ * buffer, so by the time a tombstone expires there can be no
2005
+ * stale frame for that id anywhere in the system.
2006
+ */
2007
+ tombstonedSessionIds = /* @__PURE__ */ new Map();
2008
+ /** Restore ownership for `sessionUpdate` and artifact demultiplexing. */
2009
+ inFlightRestoreIds = /* @__PURE__ */ new Set();
2010
+ /**
2011
+ * Registrations allowed to buffer early extended notifications through an
2012
+ * ordinary close tombstone. This includes restores and caller-supplied-id
2013
+ * spawns, and lasts only until their ACP registration attempt settles.
2014
+ */
2015
+ inFlightSessionRegistrationIds = /* @__PURE__ */ new Set();
2016
+ /**
2017
+ * Restore ids whose caller timed out while the non-cancellable ACP request
2018
+ * continues.
2019
+ */
2020
+ abandonedRestoreIds = /* @__PURE__ */ new Set();
2021
+ /**
2022
+ * Handle child->bridge ACP `extMethod` requests (calls that expect a
2023
+ * response, unlike `extNotification`). Served methods:
2024
+ * `qwen/control/client_mcp/message` (reverse tool channel),
2025
+ * `qwen/control/create-sub-session` (the `create_sub_session` tool → daemon
2026
+ * spawns a sub-session and, for `'first-turn'`, returns its first-turn
2027
+ * result), and `craft/drainMidTurnQueue`: the ACP child calls the last one
2028
+ * between tool batches to pull any messages the browser queued mid-turn. We splice the per-session
2029
+ * queue, return them to the child as the response, and — when non-empty —
2030
+ * publish a `mid_turn_message_injected` SSE frame so the browser can move
2031
+ * those messages out of its pending queue and render the immediate echo.
2032
+ * Unknown methods reject with ACP `methodNotFound` (-32601), matching
2033
+ * the SDK's
2034
+ * default for an unimplemented client surface; the child's drain caller
2035
+ * treats that as "drain unsupported" and stops asking.
2036
+ */
2037
+ async extMethod(method, params) {
2038
+ if (method === SERVE_CONTROL_EXT_METHODS.clientMcpMessage) {
2039
+ return this.handleClientMcpMessage(params);
2040
+ }
2041
+ if (method === SERVE_CONTROL_EXT_METHODS.createSubSession) {
2042
+ return this.handleCreateSubSession(params);
2043
+ }
2044
+ if (method === SERVE_CONTROL_EXT_METHODS.createCurrentSessionScheduledTask) {
2045
+ return this.handleCreateCurrentSessionScheduledTask(params);
2046
+ }
2047
+ if (method === SERVE_CONTROL_EXT_METHODS.liveCaptureScreenContext) {
2048
+ return this.handleLiveScreenContextCapture(params);
2049
+ }
2050
+ if (method === SERVE_CONTROL_EXT_METHODS.liveTaskTool) {
2051
+ return this.handleLiveTaskTool(params);
2052
+ }
2053
+ if (method === SERVE_CONTROL_EXT_METHODS.liveSpeakToUser) {
2054
+ return this.handleLiveSpeakToUser(params);
2055
+ }
2056
+ if (method === SERVE_CONTROL_EXT_METHODS.channelDelivery) {
2057
+ return this.handleChannelDelivery(params);
2058
+ }
2059
+ if (method === SERVE_CONTROL_EXT_METHODS.externalToolGuardPrepare) {
2060
+ return this.handleExternalToolGuardPrepare(params);
2061
+ }
2062
+ if (method === TODO_STOP_GUARD_CONTINUATION_CLAIM_METHOD) {
2063
+ return this.handleTodoStopGuardContinuationClaim(params);
2064
+ }
2065
+ if (method !== MID_TURN_QUEUE_DRAIN_METHOD) {
2066
+ throw RequestError.methodNotFound(method);
2067
+ }
2068
+ const sessionId = typeof params["sessionId"] === "string" ? params["sessionId"] : void 0;
2069
+ if (!sessionId) return { messages: [], items: [], hasQueuedPrompt: false };
2070
+ if (!this.ownsSession(sessionId)) {
2071
+ return { messages: [], items: [], hasQueuedPrompt: false };
2072
+ }
2073
+ const entry = this.resolveEntry(sessionId);
2074
+ if (!entry) return { messages: [], items: [], hasQueuedPrompt: false };
2075
+ const drained = entry.midTurnMessageQueue.splice(0);
2076
+ if (drained.length > 0) {
2077
+ for (const item of drained) {
2078
+ entry.settledMidTurnMessageIds.push(item.messageId);
2079
+ }
2080
+ if (entry.settledMidTurnMessageIds.length > MID_TURN_RECONCILIATION_RING_SIZE) {
2081
+ entry.settledMidTurnMessageIds.splice(
2082
+ 0,
2083
+ entry.settledMidTurnMessageIds.length - MID_TURN_RECONCILIATION_RING_SIZE
2084
+ );
2085
+ }
2086
+ }
2087
+ const attachmentMemo = /* @__PURE__ */ new Map();
2088
+ const serializedAttachmentIds = /* @__PURE__ */ new Set();
2089
+ const items = [];
2090
+ try {
2091
+ for (const item of drained) {
2092
+ let degraded = 0;
2093
+ const planned = (item.content ?? []).filter((block) => {
2094
+ if (!isSessionAttachmentReference(block)) return true;
2095
+ if (serializedAttachmentIds.has(block.attachmentId)) {
2096
+ degraded += 1;
2097
+ return false;
2098
+ }
2099
+ serializedAttachmentIds.add(block.attachmentId);
2100
+ return true;
2101
+ });
2102
+ let resolvedBlocks;
2103
+ let attachmentReferences;
2104
+ try {
2105
+ resolvedBlocks = await entry.attachments.resolveContent(
2106
+ planned,
2107
+ attachmentMemo
2108
+ );
2109
+ attachmentReferences = planned.filter(isSessionAttachmentReference);
2110
+ } catch (error) {
2111
+ if (!(error instanceof SessionAttachmentReferenceError)) throw error;
2112
+ writeStderrLine(
2113
+ `[mid-turn] session=${JSON.stringify(entry.sessionId)} degraded attachment for message ${JSON.stringify(item.messageId)}: ${JSON.stringify(error instanceof Error ? error.message : String(error))}`
2114
+ );
2115
+ const perBlock = await entry.attachments.resolveContentDegrading(
2116
+ planned,
2117
+ attachmentMemo
2118
+ );
2119
+ resolvedBlocks = perBlock.resolvedBlocks;
2120
+ attachmentReferences = perBlock.retainedBlocks.filter(
2121
+ isSessionAttachmentReference
2122
+ );
2123
+ degraded += perBlock.degraded;
2124
+ }
2125
+ let content = [
2126
+ ...item.text ? [{ type: "text", text: item.text }] : [],
2127
+ ...resolvedBlocks
2128
+ ];
2129
+ if (degraded > 0) content = withAttachmentDegradationMarker(content);
2130
+ items.push({
2131
+ messageId: item.messageId,
2132
+ displayText: item.text,
2133
+ content,
2134
+ ...attachmentReferences.length > 0 ? { attachmentReferences } : {}
2135
+ });
2136
+ }
2137
+ } catch (error) {
2138
+ const requeued = new Set(drained.map((queued) => queued.messageId));
2139
+ const ring = entry.settledMidTurnMessageIds;
2140
+ const kept = ring.filter((id) => !requeued.has(id));
2141
+ ring.splice(0, ring.length, ...kept);
2142
+ entry.midTurnMessageQueue.unshift(...drained);
2143
+ writeStderrLine(
2144
+ `[mid-turn] session=${JSON.stringify(entry.sessionId)} drain failed, requeued ${drained.length} message(s): ${JSON.stringify(error instanceof Error ? error.message : String(error))}`
2145
+ );
2146
+ throw error;
2147
+ }
2148
+ const echoed = drained.filter((item) => !item.queueOnly);
2149
+ const messages = drained.map((item) => item.text);
2150
+ const hasQueuedPrompt = entry.pendingPromptList.some(
2151
+ (prompt) => prompt.state === "queued" && !prompt.abortController.signal.aborted
2152
+ );
2153
+ if (echoed.length > 0) {
2154
+ const published = entry.events.publish({
2155
+ type: MID_TURN_MESSAGE_INJECTED_EVENT,
2156
+ ...entry.activePromptId ? { promptId: entry.activePromptId } : {},
2157
+ data: {
2158
+ sessionId: entry.sessionId,
2159
+ messages: echoed.map((item) => item.text),
2160
+ messageIds: echoed.map((item) => item.messageId),
2161
+ // Carry the structured `items` twin (content blocks per message) so
2162
+ // the browser-side echo renderer can show attached images alongside
2163
+ // the message text. Older consumers that don't read this field keep
2164
+ // working unchanged.
2165
+ items: echoed.map((item) => ({
2166
+ ...item.content && item.content.length > 0 ? { content: item.content } : {}
2167
+ }))
2168
+ }
2169
+ });
2170
+ writeStderrLine(
2171
+ published ? `[mid-turn] session=${entry.sessionId} drained=${messages.length} echoed=${echoed.length} injected into running turn` : `[mid-turn] session=${entry.sessionId} drained=${messages.length} echoed=${echoed.length} echo frame dropped (bus closed); reconciliation required`
2172
+ );
2173
+ }
2174
+ return { messages, items, hasQueuedPrompt };
2175
+ }
2176
+ async handleExternalToolGuardPrepare(params) {
2177
+ if (!this.externalToolGuard) {
2178
+ throw RequestError.methodNotFound(
2179
+ SERVE_CONTROL_EXT_METHODS.externalToolGuardPrepare
2180
+ );
2181
+ }
2182
+ const sessionId = params["sessionId"];
2183
+ const promptId = params["promptId"];
2184
+ const toolCallId = params["toolCallId"];
2185
+ const toolName = params["toolName"];
2186
+ const args = params["arguments"];
2187
+ if (typeof sessionId !== "string" || sessionId.length === 0 || promptId !== void 0 && (typeof promptId !== "string" || promptId.length === 0) || typeof toolCallId !== "string" || toolCallId.length === 0 || typeof toolName !== "string" || toolName.length === 0 || !isRecord(args)) {
2188
+ throw RequestError.invalidParams(
2189
+ void 0,
2190
+ "Invalid external tool guard request"
2191
+ );
2192
+ }
2193
+ const promptScoped = promptId !== void 0;
2194
+ if (!this.ownsSession(sessionId)) {
2195
+ throw RequestError.invalidParams(
2196
+ void 0,
2197
+ "External tool guard session is not owned by this connection"
2198
+ );
2199
+ }
2200
+ const entry = this.resolveEntry(sessionId);
2201
+ if (!entry || promptScoped && (!entry.promptActive || entry.activePromptId !== promptId)) {
2202
+ throw RequestError.invalidParams(
2203
+ void 0,
2204
+ "External tool guard prompt is not the active prompt"
2205
+ );
2206
+ }
2207
+ const invocationCwd = params["invocationCwd"];
2208
+ const decision = await this.externalToolGuard({
2209
+ sessionId: entry.sessionId,
2210
+ ...promptScoped ? { promptId } : {},
2211
+ toolCallId,
2212
+ toolName,
2213
+ arguments: args,
2214
+ effectiveCwd: entry.effectiveCwd,
2215
+ // Forwarded verbatim and explicitly untrusted: the host policy decides
2216
+ // whether it can establish this scope from state it owns.
2217
+ ...typeof invocationCwd === "string" && invocationCwd.length > 0 ? { invocationCwd } : {}
2218
+ });
2219
+ const currentEntry = this.resolveEntry(sessionId);
2220
+ if (!this.ownsSession(sessionId) || currentEntry !== entry || promptScoped && (!currentEntry.promptActive || currentEntry.activePromptId !== promptId)) {
2221
+ throw RequestError.invalidParams(
2222
+ void 0,
2223
+ "External tool guard prompt is no longer active"
2224
+ );
2225
+ }
2226
+ return normalizeExternalToolGuardResult(decision);
2227
+ }
2228
+ handleTodoStopGuardContinuationClaim(params) {
2229
+ const sessionId = typeof params["sessionId"] === "string" && params["sessionId"].length > 0 ? params["sessionId"] : void 0;
2230
+ if (!sessionId) return { claimed: false, hasQueuedPrompt: false };
2231
+ if (!this.ownsSession(sessionId)) {
2232
+ return { claimed: false, hasQueuedPrompt: false };
2233
+ }
2234
+ const entry = this.resolveEntry(sessionId);
2235
+ if (!entry) return { claimed: false, hasQueuedPrompt: false };
2236
+ const livePrompts = entry.pendingPromptList.filter(
2237
+ (prompt) => !prompt.abortController.signal.aborted
2238
+ );
2239
+ const promptId = typeof params["promptId"] === "string" && params["promptId"].length > 0 ? params["promptId"] : void 0;
2240
+ if (promptId) {
2241
+ const ownsRunningPrompt = entry.activePromptId === promptId && livePrompts.some(
2242
+ (prompt) => prompt.promptId === promptId && prompt.state === "running"
2243
+ );
2244
+ const hasCompetingRunningPrompt = livePrompts.some(
2245
+ (prompt) => prompt.promptId !== promptId && prompt.state === "running"
2246
+ );
2247
+ if (!ownsRunningPrompt || hasCompetingRunningPrompt) {
2248
+ return { claimed: false, hasQueuedPrompt: false };
2249
+ }
2250
+ const hasQueuedPrompt = livePrompts.some(
2251
+ (prompt) => prompt.state === "queued"
2252
+ );
2253
+ if (hasQueuedPrompt) {
2254
+ entry.todoStopGuardAwaitingQueuedPromptOwnerPromptId = promptId;
2255
+ return { claimed: false, hasQueuedPrompt: true };
2256
+ }
2257
+ if (entry.todoStopGuardAwaitingQueuedPromptOwnerPromptId === promptId) {
2258
+ delete entry.todoStopGuardAwaitingQueuedPromptOwnerPromptId;
2259
+ }
2260
+ return { claimed: true, hasQueuedPrompt: false };
2261
+ }
2262
+ if (entry.promptActive || livePrompts.length > 0) {
2263
+ return { claimed: false, hasQueuedPrompt: false };
2264
+ }
2265
+ return { claimed: true, hasQueuedPrompt: false };
2266
+ }
2267
+ async handleChannelDelivery(params) {
2268
+ if (!this.onChannelDelivery) {
2269
+ throw RequestError.methodNotFound(
2270
+ SERVE_CONTROL_EXT_METHODS.channelDelivery
2271
+ );
2272
+ }
2273
+ const sessionId = params["sessionId"];
2274
+ if (typeof sessionId !== "string" || sessionId.length === 0 || !this.ownsSession(sessionId)) {
2275
+ throw RequestError.invalidParams(
2276
+ void 0,
2277
+ "`sessionId` must name a session owned by this connection"
2278
+ );
2279
+ }
2280
+ const entry = this.resolveEntry(sessionId);
2281
+ if (!entry) {
2282
+ throw RequestError.invalidParams(void 0, "Unknown `sessionId`");
2283
+ }
2284
+ const deliveryId = params["deliveryId"];
2285
+ const source = params["source"];
2286
+ const text = params["text"];
2287
+ const rawTarget = params["target"];
2288
+ if (!isBoundedChannelDeliveryString(deliveryId) || source !== "prompt" && source !== "scheduled" || typeof text !== "string" || text.length > MAX_CHANNEL_DELIVERY_TEXT_CHARS || !isChannelDeliveryTarget(rawTarget)) {
2289
+ throw RequestError.invalidParams(
2290
+ void 0,
2291
+ "Invalid channel delivery request"
2292
+ );
2293
+ }
2294
+ const promptId = params["promptId"];
2295
+ const taskId = params["taskId"];
2296
+ const firedAt = params["firedAt"];
2297
+ const allowedKeys = source === "prompt" ? /* @__PURE__ */ new Set([
2298
+ "sessionId",
2299
+ "deliveryId",
2300
+ "source",
2301
+ "target",
2302
+ "text",
2303
+ "promptId"
2304
+ ]) : /* @__PURE__ */ new Set([
2305
+ "sessionId",
2306
+ "deliveryId",
2307
+ "source",
2308
+ "target",
2309
+ "text",
2310
+ "taskId",
2311
+ "firedAt"
2312
+ ]);
2313
+ const correlationValid = source === "prompt" ? isBoundedChannelDeliveryString(promptId) && promptId === deliveryId : isBoundedChannelDeliveryString(taskId) && typeof firedAt === "number" && Number.isFinite(firedAt) && deliveryId === `${taskId}:${firedAt}`;
2314
+ if (!correlationValid || !Object.keys(params).every((key) => allowedKeys.has(key))) {
2315
+ throw RequestError.invalidParams(
2316
+ void 0,
2317
+ "Invalid channel delivery correlation"
2318
+ );
2319
+ }
2320
+ const info = {
2321
+ sessionId,
2322
+ deliveryId,
2323
+ source,
2324
+ target: rawTarget,
2325
+ text,
2326
+ ...source === "prompt" ? { promptId } : {},
2327
+ ...source === "scheduled" ? { taskId, firedAt } : {}
2328
+ };
2329
+ let result;
2330
+ try {
2331
+ result = normalizeChannelDeliveryHostResult(
2332
+ await this.onChannelDelivery(info)
2333
+ );
2334
+ } catch {
2335
+ result = {
2336
+ status: "failed",
2337
+ code: "channel_delivery_failed",
2338
+ error: "Channel delivery failed."
2339
+ };
2340
+ }
2341
+ try {
2342
+ entry.events.publish({
2343
+ type: "channel_delivery_result",
2344
+ ...source === "prompt" ? { promptId } : {},
2345
+ data: {
2346
+ sessionId,
2347
+ deliveryId,
2348
+ source,
2349
+ status: result.status,
2350
+ ...source === "prompt" ? { promptId } : {},
2351
+ ...source === "scheduled" ? { taskId, firedAt } : {},
2352
+ ...result.status === "failed" ? { code: result.code, error: result.error } : {}
2353
+ }
2354
+ });
2355
+ } catch {
2356
+ }
2357
+ return result;
2358
+ }
2359
+ /**
2360
+ * Reverse tool channel (issue #5626, Phase 2) — answer the child's
2361
+ * `qwen/control/client_mcp/message` ext-method. The child's session
2362
+ * `McpClientManager` calls this when its agent drives a client-hosted
2363
+ * (extension) MCP server: `params` carries the advertised `server` name and
2364
+ * the JSON-RPC `payload` (initialize / tools/list / tools/call / a
2365
+ * notification). We resolve the per-WS-connection sender via the injected
2366
+ * `clientMcpSender` lookup, deliver the payload over the daemon WS, and
2367
+ * return the correlated response as `{ payload }`.
2368
+ *
2369
+ * Rejects with ACP `methodNotFound` when no `clientMcpSender` is wired (Mode
2370
+ * A / tests can't host a client MCP server), and `invalidParams` when the
2371
+ * frame is malformed or the named server is no longer hosted (e.g. the
2372
+ * extension disconnected mid-turn) — the agent's `SdkControlClientTransport`
2373
+ * surfaces that as a transport error rather than hanging.
2374
+ */
2375
+ async handleClientMcpMessage(params) {
2376
+ if (!this.clientMcpSender) {
2377
+ throw RequestError.methodNotFound(
2378
+ SERVE_CONTROL_EXT_METHODS.clientMcpMessage
2379
+ );
2380
+ }
2381
+ const server = params["server"];
2382
+ if (typeof server !== "string" || server.length === 0) {
2383
+ throw RequestError.invalidParams(
2384
+ void 0,
2385
+ "`server` must be a non-empty string"
2386
+ );
2387
+ }
2388
+ const payload = params["payload"];
2389
+ if (payload === null || typeof payload !== "object") {
2390
+ throw RequestError.invalidParams(
2391
+ void 0,
2392
+ "`payload` must be a JSON-RPC message object"
2393
+ );
2394
+ }
2395
+ const send = this.clientMcpSender(server);
2396
+ if (!send) {
2397
+ throw RequestError.invalidParams(
2398
+ void 0,
2399
+ `client-hosted MCP server '${server}' is not currently connected`
2400
+ );
2401
+ }
2402
+ const sessionId = params["sessionId"];
2403
+ if (sessionId !== void 0 && (typeof sessionId !== "string" || sessionId.length === 0)) {
2404
+ throw RequestError.invalidParams(
2405
+ void 0,
2406
+ "`sessionId` must be a non-empty string when provided"
2407
+ );
2408
+ }
2409
+ const ownsSession = typeof sessionId === "string" && this.ownsSession(sessionId);
2410
+ if (typeof sessionId === "string" && !ownsSession && !this.hasSessionSpawnInFlight()) {
2411
+ throw RequestError.invalidParams(
2412
+ void 0,
2413
+ `Session not owned by this channel: ${sessionId}`
2414
+ );
2415
+ }
2416
+ if (typeof sessionId === "string" && !ownsSession) {
2417
+ writeStderrLine(
2418
+ `[demux] session=${sessionId} type=client_mcp_message action=forwarded_without_session reason=session_registration_pending`
2419
+ );
2420
+ }
2421
+ const response = await send(
2422
+ payload,
2423
+ typeof sessionId === "string" && ownsSession ? { sessionId } : void 0
2424
+ );
2425
+ return { payload: response };
2426
+ }
2427
+ /**
2428
+ * Handle the `create_sub_session` tool's request: validate, then forward to
2429
+ * the daemon-host `onCreateSubSession` callback (which spawns a fresh
2430
+ * top-level sub-session and, for `'first-turn'`, waits for its first turn and
2431
+ * returns the result). No host wired → `methodNotFound`, which the tool
2432
+ * surfaces as "daemon-only".
2433
+ */
2434
+ async handleCreateSubSession(params) {
2435
+ if (!this.onCreateSubSession) {
2436
+ throw RequestError.methodNotFound(
2437
+ SERVE_CONTROL_EXT_METHODS.createSubSession
2438
+ );
2439
+ }
2440
+ const prompt = params["prompt"];
2441
+ if (typeof prompt !== "string" || prompt.length === 0) {
2442
+ throw RequestError.invalidParams(
2443
+ void 0,
2444
+ "`prompt` must be a non-empty string"
2445
+ );
2446
+ }
2447
+ if (prompt.length > MAX_SUB_SESSION_PROMPT_CHARS) {
2448
+ throw RequestError.invalidParams(
2449
+ void 0,
2450
+ `\`prompt\` exceeds the ${MAX_SUB_SESSION_PROMPT_CHARS}-character limit`
2451
+ );
2452
+ }
2453
+ const completion = params["completion"];
2454
+ if (completion !== "sent" && completion !== "first-turn") {
2455
+ throw RequestError.invalidParams(
2456
+ void 0,
2457
+ "`completion` must be 'sent' or 'first-turn'"
2458
+ );
2459
+ }
2460
+ const name = params["name"];
2461
+ if (typeof name === "string" && name.length > MAX_SUB_SESSION_NAME_CHARS) {
2462
+ throw RequestError.invalidParams(
2463
+ void 0,
2464
+ `\`name\` exceeds the ${MAX_SUB_SESSION_NAME_CHARS}-character limit`
2465
+ );
2466
+ }
2467
+ const callerSessionId = params["callerSessionId"];
2468
+ if (typeof callerSessionId !== "string" || callerSessionId.length === 0 || !this.ownsSession(callerSessionId)) {
2469
+ throw RequestError.invalidParams(
2470
+ void 0,
2471
+ "`callerSessionId` is required and must name a session owned by this connection"
2472
+ );
2473
+ }
2474
+ const model = params["model"];
2475
+ const result = await this.onCreateSubSession({
2476
+ prompt,
2477
+ completion,
2478
+ ...typeof model === "string" && model.length > 0 && model.length <= 128 ? { model } : {},
2479
+ ...typeof name === "string" && name.length > 0 ? { name } : {},
2480
+ callerSessionId
2481
+ });
2482
+ return {
2483
+ sessionId: result.sessionId,
2484
+ ...result.result !== void 0 ? { result: result.result } : {},
2485
+ ...result.stopReason !== void 0 ? { stopReason: result.stopReason } : {},
2486
+ ...result.parentSessionPersisted !== void 0 ? { parentSessionPersisted: result.parentSessionPersisted } : {}
2487
+ };
2488
+ }
2489
+ async handleCreateCurrentSessionScheduledTask(params) {
2490
+ if (!this.onCreateCurrentSessionScheduledTask) {
2491
+ throw RequestError.methodNotFound(
2492
+ SERVE_CONTROL_EXT_METHODS.createCurrentSessionScheduledTask
2493
+ );
2494
+ }
2495
+ const callerSessionId = params["callerSessionId"];
2496
+ const promptId = params["promptId"];
2497
+ const cron = params["cron"];
2498
+ const prompt = params["prompt"];
2499
+ const recurring = params["recurring"];
2500
+ if (typeof callerSessionId !== "string" || callerSessionId.length === 0 || !this.ownsSession(callerSessionId)) {
2501
+ throw RequestError.invalidParams(
2502
+ void 0,
2503
+ "`callerSessionId` must name a session owned by this connection"
2504
+ );
2505
+ }
2506
+ if (typeof promptId !== "string" || promptId.length === 0) {
2507
+ throw RequestError.invalidParams(
2508
+ void 0,
2509
+ "`promptId` must be a non-empty string"
2510
+ );
2511
+ }
2512
+ if (typeof cron !== "string" || cron.length === 0 || cron.length > MAX_SCHEDULED_TASK_CRON_CHARS) {
2513
+ throw RequestError.invalidParams(
2514
+ void 0,
2515
+ `\`cron\` must be a non-empty string within the ${MAX_SCHEDULED_TASK_CRON_CHARS}-character limit`
2516
+ );
2517
+ }
2518
+ if (typeof prompt !== "string" || prompt.length === 0 || prompt.length > MAX_SCHEDULED_TASK_PROMPT_CHARS) {
2519
+ throw RequestError.invalidParams(
2520
+ void 0,
2521
+ `\`prompt\` must be non-empty and within the ${MAX_SCHEDULED_TASK_PROMPT_CHARS}-character limit`
2522
+ );
2523
+ }
2524
+ if (typeof recurring !== "boolean") {
2525
+ throw RequestError.invalidParams(
2526
+ void 0,
2527
+ "`recurring` must be a boolean"
2528
+ );
2529
+ }
2530
+ const entry = this.resolveEntry(callerSessionId);
2531
+ if (!entry || entry.sessionId !== callerSessionId || entry.promptActive !== true || entry.activePromptId !== promptId) {
2532
+ throw RequestError.invalidParams(
2533
+ void 0,
2534
+ "The caller session does not own the active prompt"
2535
+ );
2536
+ }
2537
+ if (entry.parentSessionId !== void 0 || entry.sourceId !== void 0 || entry.sourceType !== void 0 && entry.sourceType !== "default") {
2538
+ throw RequestError.invalidParams(
2539
+ void 0,
2540
+ "The caller session source cannot own a scheduled task"
2541
+ );
2542
+ }
2543
+ const result = await this.onCreateCurrentSessionScheduledTask({
2544
+ callerSessionId,
2545
+ promptId,
2546
+ cron,
2547
+ prompt,
2548
+ recurring,
2549
+ assertCallerPromptActive: /* @__PURE__ */ __name(() => {
2550
+ const currentEntry = this.resolveEntry(callerSessionId);
2551
+ if (currentEntry !== entry || currentEntry.promptActive !== true || currentEntry.activePromptId !== promptId) {
2552
+ throw RequestError.invalidParams(
2553
+ void 0,
2554
+ "The caller session no longer owns the active prompt"
2555
+ );
2556
+ }
2557
+ }, "assertCallerPromptActive")
2558
+ }).catch(
2559
+ (error) => preserveScheduledTaskCreateErrorOverAcp(error)
2560
+ );
2561
+ if (typeof result.id !== "string" || result.id.length === 0 || typeof result.cron !== "string" || result.cron.length === 0) {
2562
+ throw RequestError.internalError(
2563
+ void 0,
2564
+ "Scheduled-task host returned an invalid result"
2565
+ );
2566
+ }
2567
+ return { id: result.id, cron: result.cron };
2568
+ }
2569
+ async handleLiveScreenContextCapture(params) {
2570
+ const handler = this.getLiveScreenContextCaptureHandler();
2571
+ if (!handler) {
2572
+ throw RequestError.methodNotFound(
2573
+ SERVE_CONTROL_EXT_METHODS.liveCaptureScreenContext
2574
+ );
2575
+ }
2576
+ const callerSessionId = params["callerSessionId"];
2577
+ if (typeof callerSessionId !== "string" || callerSessionId.length === 0 || !this.ownsSession(callerSessionId)) {
2578
+ throw RequestError.invalidParams(
2579
+ void 0,
2580
+ "`callerSessionId` is required and must name a session owned by this connection"
2581
+ );
2582
+ }
2583
+ const result = await handler({ callerSessionId });
2584
+ if (result.appName.length === 0 || result.appName.length > 512 || result.windowTitle !== void 0 && result.windowTitle.length > 2048 || result.accessibilityText.length > MAX_LIVE_SCREEN_CONTEXT_TEXT_CHARS || result.screenshotPath.length === 0 || result.screenshotPath.length > 4096) {
2585
+ throw RequestError.internalError(void 0, "Invalid Appshot result.");
2586
+ }
2587
+ return {
2588
+ appName: result.appName,
2589
+ ...result.windowTitle ? { windowTitle: result.windowTitle } : {},
2590
+ accessibilityText: result.accessibilityText,
2591
+ screenshotPath: result.screenshotPath
2592
+ };
2593
+ }
2594
+ async handleLiveTaskTool(params) {
2595
+ const handler = this.getLiveTaskToolRequestHandler();
2596
+ if (!handler) {
2597
+ throw RequestError.methodNotFound(SERVE_CONTROL_EXT_METHODS.liveTaskTool);
2598
+ }
2599
+ const callerSessionId = params["callerSessionId"];
2600
+ const name = params["name"];
2601
+ const args = params["arguments"];
2602
+ if (typeof callerSessionId !== "string" || callerSessionId.length === 0 || !this.ownsSession(callerSessionId) || typeof name !== "string" || !LIVE_TASK_TOOL_NAMES.includes(
2603
+ name
2604
+ ) || typeof args !== "object" || args === null || Array.isArray(args)) {
2605
+ throw RequestError.invalidParams(
2606
+ void 0,
2607
+ "Invalid Live task-tool request."
2608
+ );
2609
+ }
2610
+ return handler({
2611
+ callerSessionId,
2612
+ name,
2613
+ arguments: args
2614
+ });
2615
+ }
2616
+ async handleLiveSpeakToUser(params) {
2617
+ const handler = this.getLiveSpeakToUserHandler();
2618
+ if (!handler) {
2619
+ throw RequestError.methodNotFound(
2620
+ SERVE_CONTROL_EXT_METHODS.liveSpeakToUser
2621
+ );
2622
+ }
2623
+ const callerSessionId = params["callerSessionId"];
2624
+ const message = params["message"];
2625
+ if (typeof callerSessionId !== "string" || callerSessionId.length === 0 || !this.ownsSession(callerSessionId) || typeof message !== "string" || message.trim().length === 0 || message.length > MAX_LIVE_SPEAK_TO_USER_MESSAGE_CHARS) {
2626
+ throw RequestError.invalidParams(
2627
+ void 0,
2628
+ "Invalid Live speak-to-user request."
2629
+ );
2630
+ }
2631
+ await handler({ callerSessionId, message });
2632
+ return { accepted: true };
2633
+ }
2634
+ /**
2635
+ * Handle child->bridge ACP `extNotification` calls. Recognized methods are
2636
+ * `qwen/notify/session/model-update`,
2637
+ * `qwen/notify/session/mode-update`,
2638
+ * `qwen/notify/session/title-update` (auto/in-process session titles),
2639
+ * `qwen/notify/session/recording-degraded`,
2640
+ * `qwen/notify/session/prompt-suggestion` (followup assist),
2641
+ * `qwen/notify/session/artifact-event` (hook artifacts),
2642
+ * `qwen/notify/session/terminal-sequence`, and
2643
+ * `_qwencode/end_turn` (background-notification and goal turns), and
2644
+ * `qwen/notify/session/mcp-budget-event` — each translated into a
2645
+ * session-scoped SSE frame. Unknown methods are dropped silently for
2646
+ * forward-compat.
2647
+ */
2648
+ async extNotification(method, params) {
2649
+ const notificationSessionId = params["sessionId"];
2650
+ if (typeof notificationSessionId === "string" && this.abandonedRestoreIds.has(notificationSessionId)) {
2651
+ return;
2652
+ }
2653
+ if (method === ACTIVE_WORK_NOTIFICATION_METHOD) {
2654
+ const snapshot = parseActiveWorkSnapshot(params);
2655
+ if (snapshot) {
2656
+ this.onActiveWork?.({
2657
+ v: ACTIVE_WORK_HEARTBEAT_VERSION,
2658
+ seq: snapshot.seq,
2659
+ sessions: snapshot.sessions.filter(
2660
+ (session) => this.ownsSession(session.sessionId)
2661
+ )
2662
+ });
2663
+ }
2664
+ return;
2665
+ }
2666
+ if (method === "_qwencode/start_turn") {
2667
+ const sessionId2 = params["sessionId"];
2668
+ if (typeof sessionId2 !== "string" || sessionId2.length === 0 || params["source"] !== "goal") {
2669
+ return;
2670
+ }
2671
+ const entry = this.resolveEntry(sessionId2);
2672
+ if (!entry || !this.ownsSession(sessionId2)) return;
2673
+ entry.goalTurnActive = true;
2674
+ return;
2675
+ }
2676
+ if (method === "_qwencode/end_turn") {
2677
+ const sessionId2 = params["sessionId"];
2678
+ const reason = params["reason"];
2679
+ const source = params["source"];
2680
+ if (typeof sessionId2 !== "string" || sessionId2.length === 0 || typeof reason !== "string" || reason.length === 0 || reason.length > 128 || source !== "background_notification" && source !== "goal") {
2681
+ return;
2682
+ }
2683
+ const entry = this.resolveEntry(sessionId2);
2684
+ if (!entry || !this.ownsSession(sessionId2)) return;
2685
+ if (source === "goal") {
2686
+ entry.goalTurnActive = false;
2687
+ this.onGoalTurnEnded?.(sessionId2);
2688
+ const promptId = params["promptId"];
2689
+ if (typeof promptId !== "string" || promptId.length === 0 || promptId.length > 256) {
2690
+ return;
2691
+ }
2692
+ entry.events.publish({
2693
+ type: "turn_complete",
2694
+ promptId,
2695
+ data: { sessionId: sessionId2, stopReason: reason, promptId }
2696
+ });
2697
+ return;
2698
+ }
2699
+ entry.events.publish({
2700
+ type: "background_notification_turn_complete",
2701
+ data: { sessionId: sessionId2, reason }
2702
+ });
2703
+ return;
2704
+ }
2705
+ if (method === "qwen/notify/session/generation/event") {
2706
+ const sessionId2 = params["sessionId"];
2707
+ const requestId = params["requestId"];
2708
+ const event = params["event"];
2709
+ if (params["v"] !== 1 || typeof sessionId2 !== "string" || typeof requestId !== "string" || !event || typeof event !== "object" || Array.isArray(event)) {
2710
+ return;
2711
+ }
2712
+ const record = event;
2713
+ if (record["type"] === "started") {
2714
+ const model = record["model"];
2715
+ const modelSource = record["modelSource"];
2716
+ if (typeof model !== "string" || modelSource !== "fast" && modelSource !== "main") {
2717
+ return;
2718
+ }
2719
+ this.onGenerationEvent?.(sessionId2, {
2720
+ type: "started",
2721
+ requestId,
2722
+ model,
2723
+ modelSource
2724
+ });
2725
+ return;
2726
+ }
2727
+ if (record["type"] === "thinking") {
2728
+ this.onGenerationEvent?.(sessionId2, {
2729
+ type: "thinking",
2730
+ requestId
2731
+ });
2732
+ return;
2733
+ }
2734
+ if (record["type"] === "delta") {
2735
+ const seq = record["seq"];
2736
+ const text = record["text"];
2737
+ if (typeof seq !== "number" || !Number.isSafeInteger(seq) || seq < 0 || typeof text !== "string" || text.length === 0) {
2738
+ return;
2739
+ }
2740
+ this.onGenerationEvent?.(sessionId2, {
2741
+ type: "delta",
2742
+ requestId,
2743
+ seq,
2744
+ text
2745
+ });
2746
+ return;
2747
+ }
2748
+ return;
2749
+ }
2750
+ if (method === "qwen/notify/workspace/generation/event") {
2751
+ const requestId = params["requestId"];
2752
+ const event = params["event"];
2753
+ if (params["v"] !== 1 || typeof requestId !== "string" || !event || typeof event !== "object" || Array.isArray(event)) {
2754
+ return;
2755
+ }
2756
+ const record = event;
2757
+ if (record["type"] === "started") {
2758
+ const model = record["model"];
2759
+ const modelSource = record["modelSource"];
2760
+ if (typeof model !== "string" || modelSource !== "fast" && modelSource !== "main") {
2761
+ return;
2762
+ }
2763
+ this.onWorkspaceGenerationEvent?.({
2764
+ type: "started",
2765
+ requestId,
2766
+ model,
2767
+ modelSource
2768
+ });
2769
+ return;
2770
+ }
2771
+ if (record["type"] === "thinking") {
2772
+ this.onWorkspaceGenerationEvent?.({ type: "thinking", requestId });
2773
+ return;
2774
+ }
2775
+ if (record["type"] === "delta") {
2776
+ const seq = record["seq"];
2777
+ const text = record["text"];
2778
+ if (typeof seq !== "number" || !Number.isSafeInteger(seq) || seq < 0 || typeof text !== "string" || text.length === 0) {
2779
+ return;
2780
+ }
2781
+ this.onWorkspaceGenerationEvent?.({
2782
+ type: "delta",
2783
+ requestId,
2784
+ seq,
2785
+ text
2786
+ });
2787
+ return;
2788
+ }
2789
+ return;
2790
+ }
2791
+ if (method === "qwen/notify/session/model-update") {
2792
+ this.handleInSessionModelUpdate(params);
2793
+ return;
2794
+ }
2795
+ if (method === "qwen/notify/session/mode-update") {
2796
+ this.handleInSessionModeUpdate(params);
2797
+ return;
2798
+ }
2799
+ if (method === "qwen/notify/session/title-update") {
2800
+ const sessionId2 = params["sessionId"];
2801
+ const title = params["title"];
2802
+ if (typeof sessionId2 !== "string" || typeof title !== "string" || !title)
2803
+ return;
2804
+ const entry = this.resolveEntry(sessionId2);
2805
+ if (!entry) return;
2806
+ this.onSessionCatalogChanged?.();
2807
+ try {
2808
+ entry.events.publish({
2809
+ type: "session_metadata_updated",
2810
+ data: {
2811
+ sessionId: sessionId2,
2812
+ displayName: title,
2813
+ ...typeof params["titleSource"] === "string" ? { titleSource: params["titleSource"] } : {}
2814
+ }
2815
+ });
2816
+ } catch {
2817
+ }
2818
+ return;
2819
+ }
2820
+ if (method === "qwen/notify/session/recording-degraded") {
2821
+ const sessionId2 = params["sessionId"];
2822
+ if (params["v"] !== 1 || typeof sessionId2 !== "string" || sessionId2.length === 0 || params["reason"] !== "write_failed") {
2823
+ writeStderrLine(
2824
+ `[demux] session=${typeof sessionId2 === "string" ? sessionId2 : "<missing>"} type=session_recording_degraded action=dropped reason=malformed`
2825
+ );
2826
+ return;
2827
+ }
2828
+ const entry = this.resolveEntry(sessionId2);
2829
+ if (entry && !this.ownsSession(sessionId2)) {
2830
+ writeStderrLine(
2831
+ `[demux] session=${sessionId2} type=session_recording_degraded action=dropped reason=session_not_owned`
2832
+ );
2833
+ return;
2834
+ }
2835
+ if (entry) entry.recordingDegraded = true;
2836
+ this.publishExtNotification(sessionId2, "session_recording_degraded", {
2837
+ sessionId: sessionId2,
2838
+ reason: "write_failed"
2839
+ });
2840
+ return;
2841
+ }
2842
+ if (method === "qwen/notify/session/prompt-suggestion") {
2843
+ const sessionId2 = params["sessionId"];
2844
+ const suggestion = params["suggestion"];
2845
+ const promptId = params["promptId"];
2846
+ if (typeof sessionId2 !== "string" || typeof suggestion !== "string" || suggestion.length === 0 || suggestion.length > MAX_SUGGESTION_LENGTH || typeof promptId !== "string") {
2847
+ writeStderrLine(
2848
+ `[demux] session=${typeof sessionId2 === "string" ? sessionId2 : "<missing>"} type=prompt_suggestion action=dropped reason=malformed`
2849
+ );
2850
+ return;
2851
+ }
2852
+ const entry = this.resolveEntry(sessionId2);
2853
+ if (!entry) return;
2854
+ entry.events.publish({
2855
+ type: "followup_suggestion",
2856
+ data: { sessionId: sessionId2, suggestion, promptId }
2857
+ });
2858
+ return;
2859
+ }
2860
+ if (method === "qwen/notify/session/terminal-sequence") {
2861
+ const sessionId2 = params["sessionId"];
2862
+ if (typeof sessionId2 !== "string") return;
2863
+ const { v: _v2, sessionId: _sid2, ...rest2 } = params;
2864
+ void _v2;
2865
+ void _sid2;
2866
+ this.publishExtNotification(sessionId2, "terminal_sequence", rest2, true);
2867
+ return;
2868
+ }
2869
+ if (method === "qwen/notify/session/artifact-event") {
2870
+ await this.handleArtifactEvent(params);
2871
+ return;
2872
+ }
2873
+ if (method !== "qwen/notify/session/mcp-budget-event") return;
2874
+ const sessionId = params["sessionId"];
2875
+ if (typeof sessionId !== "string") return;
2876
+ const kind = params["kind"];
2877
+ let type;
2878
+ if (kind === "budget_warning") {
2879
+ type = "mcp_budget_warning";
2880
+ } else if (kind === "refused_batch") {
2881
+ type = "mcp_child_refused_batch";
2882
+ } else {
2883
+ return;
2884
+ }
2885
+ const { v: _v, sessionId: _sid, kind: _kind, ...rest } = params;
2886
+ void _v;
2887
+ void _sid;
2888
+ void _kind;
2889
+ this.publishExtNotification(sessionId, type, rest);
2890
+ }
2891
+ async handleArtifactEvent(params) {
2892
+ const sessionId = params["sessionId"];
2893
+ const rawArtifacts = params["artifacts"];
2894
+ if (typeof sessionId !== "string" || !Array.isArray(rawArtifacts)) {
2895
+ writeStderrLine(
2896
+ `[demux] session=${typeof sessionId === "string" ? sessionId : "<missing>"} type=artifact_event action=dropped reason=malformed`
2897
+ );
2898
+ return;
2899
+ }
2900
+ if (!this.ownsSession(sessionId) && !this.inFlightRestoreIds.has(sessionId)) {
2901
+ writeStderrLine(
2902
+ `[demux] session=${sessionId} type=artifact_event action=dropped reason=session_not_owned`
2903
+ );
2904
+ return;
2905
+ }
2906
+ const entry = this.resolveEntry(sessionId);
2907
+ if (!entry) {
2908
+ writeStderrLine(
2909
+ `[demux] session=${sessionId} type=artifact_event action=dropped reason=session_not_found`
2910
+ );
2911
+ return;
2912
+ }
2913
+ const hookEventName = typeof params["hookEventName"] === "string" ? params["hookEventName"] : void 0;
2914
+ const toolName = typeof params["toolName"] === "string" ? params["toolName"] : void 0;
2915
+ const toolCallId = typeof params["toolCallId"] === "string" ? params["toolCallId"] : void 0;
2916
+ const artifacts = extractCappedArtifactInputs(
2917
+ rawArtifacts,
2918
+ entry.artifacts.inputBatchLimit(),
2919
+ entry.sessionId,
2920
+ "hook",
2921
+ (artifact) => ({
2922
+ ...artifactPayloadFields(artifact),
2923
+ source: "hook",
2924
+ hookEventName,
2925
+ toolName,
2926
+ toolCallId
2927
+ })
2928
+ );
2929
+ const turn = {
2930
+ ...entry.activePromptId ? { promptId: entry.activePromptId } : {},
2931
+ ...entry.activePromptOriginatorClientId ? { originatorClientId: entry.activePromptOriginatorClientId } : {}
2932
+ };
2933
+ await this.upsertAndPublishArtifacts(entry, artifacts, void 0, turn);
2934
+ }
2935
+ async upsertAndPublishArtifacts(entry, artifacts, options, turn = {}) {
2936
+ if (!entry.artifactWorkspaceReady) {
2937
+ this.deferArtifactBatch(entry, artifacts, options, turn);
2938
+ writeStderrLine(
2939
+ `[artifacts] session=${entry.sessionId} action=deferred reason=workspace_not_bound`
2940
+ );
2941
+ return;
2942
+ }
2943
+ try {
2944
+ await entry.prepareArtifactWorkspace?.();
2945
+ } catch {
2946
+ this.deferArtifactBatch(entry, artifacts, options, turn);
2947
+ writeStderrLine(
2948
+ `[artifacts] session=${entry.sessionId} action=deferred reason=workspace_prepare_failed`
2949
+ );
2950
+ return;
2951
+ }
2952
+ await this.upsertAndPublishArtifactsReady(entry, artifacts, options, turn);
2953
+ }
2954
+ deferArtifactBatch(entry, artifacts, options, turn) {
2955
+ if (artifacts.length === 0) return;
2956
+ entry.deferredArtifactBatches.push({ artifacts, options, turn });
2957
+ entry.deferredArtifactInputCount += artifacts.length;
2958
+ const limit = entry.artifacts.inputBatchLimit();
2959
+ let truncated = false;
2960
+ while (entry.deferredArtifactInputCount > limit) {
2961
+ const first = entry.deferredArtifactBatches[0];
2962
+ if (!first) break;
2963
+ const overflow = entry.deferredArtifactInputCount - limit;
2964
+ if (first.artifacts.length <= overflow) {
2965
+ entry.deferredArtifactBatches.shift();
2966
+ entry.deferredArtifactInputCount -= first.artifacts.length;
2967
+ } else {
2968
+ first.artifacts = first.artifacts.slice(overflow);
2969
+ entry.deferredArtifactInputCount -= overflow;
2970
+ }
2971
+ truncated = true;
2972
+ }
2973
+ if (truncated) {
2974
+ writeStderrLine(
2975
+ `[artifacts] session=${entry.sessionId} action=deferred_truncated reason=workspace_not_bound`
2976
+ );
2977
+ }
2978
+ }
2979
+ async upsertAndPublishArtifactsReady(entry, artifacts, options, turn = {}) {
2980
+ try {
2981
+ const result = await entry.artifacts.upsertMany(artifacts, options);
2982
+ for (const warning of result.warnings ?? []) {
2983
+ writeStderrLine(
2984
+ `[artifacts] session=${entry.sessionId} action=warning reason=${JSON.stringify(
2985
+ warning
2986
+ )}`
2987
+ );
2988
+ }
2989
+ this.publishArtifactChanges(entry, result.changes, turn);
2990
+ return true;
2991
+ } catch (error) {
2992
+ writeStderrLine(
2993
+ `[artifacts] session=${entry.sessionId} action=dropped reason=${JSON.stringify(
2994
+ artifactIngestionErrorReason(error)
2995
+ )}`
2996
+ );
2997
+ return false;
2998
+ }
2999
+ }
3000
+ publishArtifactChanges(entry, changes, turn) {
3001
+ for (const change of changes) {
3002
+ entry.events.publish({
3003
+ type: "artifact_changed",
3004
+ data: { sessionId: entry.sessionId, change },
3005
+ ...turn
3006
+ });
3007
+ }
3008
+ }
3009
+ publishExtNotification(sessionId, type, data, turnScoped = false) {
3010
+ if (this.abandonedRestoreIds.has(sessionId)) {
3011
+ return;
3012
+ }
3013
+ const entry = this.resolveEntry(sessionId);
3014
+ const frame = {
3015
+ type,
3016
+ data,
3017
+ ...turnScoped && entry?.activePromptId ? { promptId: entry.activePromptId } : {},
3018
+ ...entry?.activePromptOriginatorClientId ? { originatorClientId: entry.activePromptOriginatorClientId } : {}
3019
+ };
3020
+ if (entry) {
3021
+ entry.events.publish(frame);
3022
+ return;
3023
+ }
3024
+ this.bufferEarlyEvent(sessionId, frame);
3025
+ }
3026
+ /**
3027
+ * Promote an in-session `current_model_update` extNotification to a
3028
+ * `model_switched` bus event. Suppressed while the bridge is driving
3029
+ * its own model roundtrip (`entry.modelRoundtripInFlight`) — there the
3030
+ * bridge publishes the authoritative `model_switched`, so promoting
3031
+ * here too would double-publish. A structured log records the decision
3032
+ * so the `dropped` case is observable.
3033
+ */
3034
+ handleInSessionModelUpdate(params) {
3035
+ const sessionId = params["sessionId"];
3036
+ const currentModelId = params["currentModelId"];
3037
+ if (typeof sessionId !== "string" || typeof currentModelId !== "string") {
3038
+ return;
3039
+ }
3040
+ const entry = this.resolveEntry(sessionId);
3041
+ if (!entry) {
3042
+ writeStderrLine(
3043
+ `[demux] session=${sessionId} type=current_model_update action=dropped reason=no_entry`
3044
+ );
3045
+ return;
3046
+ }
3047
+ if (entry.modelRoundtripInFlight) {
3048
+ writeStderrLine(
3049
+ `[demux] session=${sessionId} type=current_model_update action=suppressed reason=bridge_roundtrip_in_flight`
3050
+ );
3051
+ return;
3052
+ }
3053
+ if (this.onModelPromoted) {
3054
+ this.onModelPromoted(
3055
+ entry,
3056
+ currentModelId,
3057
+ entry.activePromptOriginatorClientId
3058
+ );
3059
+ } else {
3060
+ entry.events.publish({
3061
+ type: "model_switched",
3062
+ ...entry.activePromptId ? { promptId: entry.activePromptId } : {},
3063
+ data: { sessionId, modelId: currentModelId },
3064
+ ...entry.activePromptOriginatorClientId ? { originatorClientId: entry.activePromptOriginatorClientId } : {}
3065
+ });
3066
+ }
3067
+ writeStderrLine(
3068
+ `[demux] session=${sessionId} type=current_model_update action=promoted model=${currentModelId}`
3069
+ );
3070
+ }
3071
+ /**
3072
+ * A2: promote an in-session `current_mode_update` extNotification to
3073
+ * `approval_mode_changed`. Uses the same suppression pattern as
3074
+ * `handleInSessionModelUpdate` — suppressed while the bridge is driving
3075
+ * its own approval-mode roundtrip (`entry.approvalModeRoundtripInFlight`)
3076
+ * — but diverges with two additions the model handler lacks: enum
3077
+ * validation against `KNOWN_APPROVAL_MODES`, and a legacy
3078
+ * `session_update{current_mode_update}` dual-emit for IDE companion
3079
+ * compat (transition — see §6 of the design doc), itself deduped via the
3080
+ * `legacyFrameSent` flag.
3081
+ */
3082
+ handleInSessionModeUpdate(params) {
3083
+ const sessionId = params["sessionId"];
3084
+ const currentModeId = params["currentModeId"];
3085
+ if (typeof sessionId !== "string" || typeof currentModeId !== "string") {
3086
+ return;
3087
+ }
3088
+ if (!KNOWN_APPROVAL_MODES.has(currentModeId)) {
3089
+ writeStderrLine(
3090
+ `[demux] session=${sessionId} type=current_mode_update action=dropped reason=unknown_mode mode=${currentModeId}`
3091
+ );
3092
+ return;
3093
+ }
3094
+ const entry = this.resolveEntry(sessionId);
3095
+ if (!entry) {
3096
+ writeStderrLine(
3097
+ `[demux] session=${sessionId} type=current_mode_update action=dropped reason=no_entry`
3098
+ );
3099
+ return;
3100
+ }
3101
+ if (entry.approvalModeRoundtripInFlight) {
3102
+ writeStderrLine(
3103
+ `[demux] session=${sessionId} type=current_mode_update action=suppressed reason=bridge_roundtrip_in_flight`
3104
+ );
3105
+ return;
3106
+ }
3107
+ if (this.onModePromoted) {
3108
+ this.onModePromoted(
3109
+ entry,
3110
+ currentModeId,
3111
+ entry.activePromptOriginatorClientId
3112
+ );
3113
+ } else {
3114
+ entry.events.publish({
3115
+ type: "approval_mode_changed",
3116
+ ...entry.activePromptId ? { promptId: entry.activePromptId } : {},
3117
+ data: {
3118
+ sessionId,
3119
+ previous: "default",
3120
+ next: currentModeId,
3121
+ persisted: false
3122
+ },
3123
+ ...entry.activePromptOriginatorClientId ? { originatorClientId: entry.activePromptOriginatorClientId } : {}
3124
+ });
3125
+ }
3126
+ if (params["legacyFrameSent"] === true) {
3127
+ writeStderrLine(
3128
+ `[demux] session=${sessionId} type=current_mode_update action=promoted mode=${currentModeId} legacy_frame=skipped`
3129
+ );
3130
+ return;
3131
+ }
3132
+ entry.events.publish({
3133
+ type: "session_update",
3134
+ ...entry.activePromptId ? { promptId: entry.activePromptId } : {},
3135
+ data: {
3136
+ sessionId,
3137
+ update: {
3138
+ sessionUpdate: "current_mode_update",
3139
+ currentModeId
3140
+ }
3141
+ },
3142
+ ...entry.activePromptOriginatorClientId ? { originatorClientId: entry.activePromptOriginatorClientId } : {}
3143
+ });
3144
+ writeStderrLine(
3145
+ `[demux] session=${sessionId} type=current_mode_update action=promoted mode=${currentModeId}`
3146
+ );
3147
+ }
3148
+ /**
3149
+ * Enqueue `frame` for `sessionId`. Lazy TTL sweep runs first so
3150
+ * caller doesn't pay for stale entries before deciding whether
3151
+ * the session-cap is reached. New sessionIds past
3152
+ * `MAX_EARLY_EVENT_SESSIONS` are dropped (defense against a
3153
+ * malicious / buggy child fanning out fake sessionIds); same-
3154
+ * sessionId frames past `MAX_EARLY_EVENTS_PER_SESSION` are
3155
+ * dropped to bound per-session memory.
3156
+ */
3157
+ bufferEarlyEvent(sessionId, frame) {
3158
+ if (this.abandonedRestoreIds.has(sessionId)) {
3159
+ return;
3160
+ }
3161
+ const now = Date.now();
3162
+ this.sweepExpiredTombstones(now);
3163
+ if (this.tombstonedSessionIds.has(sessionId) && !this.inFlightSessionRegistrationIds.has(sessionId)) {
3164
+ writeStderrLine(
3165
+ `qwen serve: dropping early extNotification for tombstoned session ${JSON.stringify(sessionId)} (post-close stale event)`
3166
+ );
3167
+ return;
3168
+ }
3169
+ this.sweepExpiredEarlyEvents(now);
3170
+ let buf = this.earlyEvents.get(sessionId);
3171
+ if (!buf) {
3172
+ if (this.earlyEvents.size >= MAX_EARLY_EVENT_SESSIONS) {
3173
+ writeStderrLine(
3174
+ `qwen serve: dropping early extNotification \u2014 early-event buffer at MAX_EARLY_EVENT_SESSIONS (${MAX_EARLY_EVENT_SESSIONS}); possible session-id fanout abuse`
3175
+ );
3176
+ return;
3177
+ }
3178
+ buf = { frames: [], expiresAt: now + EARLY_EVENT_TTL_MS };
3179
+ this.earlyEvents.set(sessionId, buf);
3180
+ }
3181
+ if (buf.frames.length >= MAX_EARLY_EVENTS_PER_SESSION) {
3182
+ writeStderrLine(
3183
+ `qwen serve: dropping early extNotification for session ${JSON.stringify(sessionId)} \u2014 per-session cap (${MAX_EARLY_EVENTS_PER_SESSION}) reached`
3184
+ );
3185
+ return;
3186
+ }
3187
+ buf.frames.push(frame);
3188
+ }
3189
+ sweepExpiredEarlyEvents(now) {
3190
+ for (const [sid, buf] of this.earlyEvents) {
3191
+ if (buf.expiresAt <= now) this.earlyEvents.delete(sid);
3192
+ }
3193
+ }
3194
+ sweepExpiredTombstones(now) {
3195
+ for (const [sid, expiresAt] of this.tombstonedSessionIds) {
3196
+ if (expiresAt <= now) this.tombstonedSessionIds.delete(sid);
3197
+ }
3198
+ }
3199
+ /**
3200
+ * Mark a sessionId as closed so a late `extNotification` from the
3201
+ * dying child can't leak into the early-event buffer. Bridge factory
3202
+ * calls this from every `byId.delete(sid)` site (kill path,
3203
+ * channel.exited handler, closeSession). Idempotent on already-
3204
+ * tombstoned ids — refreshes the TTL so a recently-killed id stays
3205
+ * dead long enough for any in-flight stale frames to expire.
3206
+ */
3207
+ markSessionClosed(sessionId) {
3208
+ const now = Date.now();
3209
+ this.sweepExpiredTombstones(now);
3210
+ this.tombstonedSessionIds.set(sessionId, now + EARLY_EVENT_TTL_MS);
3211
+ this.earlyEvents.delete(sessionId);
3212
+ }
3213
+ /**
3214
+ * Mark a sessionId as currently being restored via `session/load` /
3215
+ * `session/resume`. While in this set, `bufferEarlyEvent` accepts
3216
+ * frames for the id even if it's tombstoned — so restore-time
3217
+ * early events from the freshly-restored child reach
3218
+ * `drainEarlyEvents` instead of being rejected by the tombstone.
3219
+ *
3220
+ * Bridge factory calls this BEFORE awaiting the ACP restore call.
3221
+ * `clearRestoreInFlight` is paired in the matching `finally` so a
3222
+ * failed restore doesn't leave a dangling allow-list entry.
3223
+ */
3224
+ markRestoreInFlight(sessionId) {
3225
+ this.markSessionRegistrationInFlight(sessionId);
3226
+ this.inFlightRestoreIds.add(sessionId);
3227
+ }
3228
+ /**
3229
+ * Transfer an id from closed/abandoned ownership to a new registration
3230
+ * attempt before its ACP call starts. The in-flight allow-list is what lets
3231
+ * legitimate early notifications bypass the ordinary close tombstone until
3232
+ * `createSessionEntry` can drain them.
3233
+ */
3234
+ markSessionRegistrationInFlight(sessionId) {
3235
+ this.clearAbandonedRestoreFence(sessionId);
3236
+ this.inFlightSessionRegistrationIds.add(sessionId);
3237
+ }
3238
+ /**
3239
+ * Drop the abandoned-restore fence for `sessionId`.
3240
+ *
3241
+ * The fence has no TTL and suppresses session updates, guardrail events,
3242
+ * and child notifications, so it must not outlive the abandoned attempt it
3243
+ * was raised for. The bridge clears it whenever a legitimate owner takes
3244
+ * the id — a new restore, or `createSessionEntry` registering a session
3245
+ * from any other route.
3246
+ */
3247
+ clearAbandonedRestoreFence(sessionId) {
3248
+ this.abandonedRestoreIds.delete(sessionId);
3249
+ }
3250
+ /**
3251
+ * Companion to `markRestoreInFlight`. Bridge factory calls this when
3252
+ * the restore IIFE settles — after `createSessionEntry` runs
3253
+ * (success) or after the ACP restore call fails (error). Cleared to
3254
+ * prevent the Set from growing forever under high restore churn.
3255
+ */
3256
+ clearRestoreInFlight(sessionId) {
3257
+ this.inFlightRestoreIds.delete(sessionId);
3258
+ this.clearSessionRegistrationInFlight(sessionId);
3259
+ }
3260
+ clearSessionRegistrationInFlight(sessionId) {
3261
+ this.inFlightSessionRegistrationIds.delete(sessionId);
3262
+ }
3263
+ markRestoreAbandoned(sessionId) {
3264
+ this.inFlightRestoreIds.delete(sessionId);
3265
+ this.inFlightSessionRegistrationIds.delete(sessionId);
3266
+ this.abandonedRestoreIds.add(sessionId);
3267
+ this.earlyEvents.delete(sessionId);
3268
+ }
3269
+ /**
3270
+ * Drain any frames buffered for `sessionId` onto `entry.events`.
3271
+ * Bridge calls this immediately after `byId.set(sessionId, entry)`
3272
+ * in `createSessionEntry`. The frames were captured before the
3273
+ * entry existed (e.g. MCP discovery during the child's `newSession`
3274
+ * handler), so draining them now lands them in the replay ring as
3275
+ * the FIRST events of this session.
3276
+ *
3277
+ * Public so the bridge factory can call it directly. Idempotent on
3278
+ * unknown sessionIds.
3279
+ */
3280
+ drainEarlyEvents(sessionId, entry) {
3281
+ this.tombstonedSessionIds.delete(sessionId);
3282
+ const buf = this.earlyEvents.get(sessionId);
3283
+ if (!buf) return;
3284
+ for (const frame of buf.frames) {
3285
+ if (frame.type === "session_recording_degraded") {
3286
+ entry.recordingDegraded = true;
3287
+ }
3288
+ entry.events.publish(frame);
3289
+ }
3290
+ this.earlyEvents.delete(sessionId);
3291
+ }
3292
+ async writeTextFile(params) {
3293
+ if (this.fileSystem) {
3294
+ try {
3295
+ return await this.fileSystem.writeText(params);
3296
+ } catch (err) {
3297
+ preserveFsErrorOverAcp(err);
3298
+ }
3299
+ }
3300
+ let realTarget = params.path;
3301
+ try {
3302
+ realTarget = await fs2.realpath(params.path);
3303
+ } catch (err) {
3304
+ const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
3305
+ if (code !== "ENOENT") throw err;
3306
+ try {
3307
+ const linkTarget = await fs2.readlink(params.path);
3308
+ realTarget = path2.resolve(path2.dirname(params.path), linkTarget);
3309
+ } catch {
3310
+ }
3311
+ }
3312
+ const tmp = `${realTarget}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
3313
+ let preserveMode;
3314
+ try {
3315
+ const targetStat = await fs2.stat(realTarget);
3316
+ preserveMode = {
3317
+ mode: targetStat.mode & 4095,
3318
+ uid: targetStat.uid,
3319
+ gid: targetStat.gid
3320
+ };
3321
+ } catch (err) {
3322
+ const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
3323
+ if (code !== "ENOENT") throw err;
3324
+ }
3325
+ try {
3326
+ await fs2.writeFile(tmp, params.content, {
3327
+ encoding: "utf8",
3328
+ flag: "wx",
3329
+ mode: preserveMode?.mode ?? 384
3330
+ });
3331
+ if (preserveMode) {
3332
+ await fs2.chmod(tmp, preserveMode.mode).catch(() => {
3333
+ });
3334
+ await fs2.chown(tmp, preserveMode.uid, preserveMode.gid).catch(() => {
3335
+ });
3336
+ }
3337
+ await fs2.rename(tmp, realTarget);
3338
+ } catch (err) {
3339
+ await fs2.unlink(tmp).catch(() => {
3340
+ });
3341
+ throw err;
3342
+ }
3343
+ return {};
3344
+ }
3345
+ async readTextFile(params) {
3346
+ if (this.fileSystem) {
3347
+ try {
3348
+ return await this.fileSystem.readText(params);
3349
+ } catch (err) {
3350
+ preserveFsErrorOverAcp(err);
3351
+ }
3352
+ }
3353
+ if (typeof params.limit === "number" && params.limit <= 0) {
3354
+ return { content: "" };
3355
+ }
3356
+ if (typeof params.limit === "number" && params.limit > 0 && !Number.isSafeInteger(params.limit)) {
3357
+ throw RequestError.invalidParams(
3358
+ void 0,
3359
+ `\`limit\` must be a positive integer, got ${params.limit}`
3360
+ );
3361
+ }
3362
+ if (typeof params.line === "number" && params.line > 0 && !Number.isSafeInteger(params.line)) {
3363
+ throw RequestError.invalidParams(
3364
+ void 0,
3365
+ `\`line\` must be a positive integer, got ${params.line}`
3366
+ );
3367
+ }
3368
+ const READ_FILE_SIZE_CAP = 100 * 1024 * 1024;
3369
+ const stats = await fs2.stat(params.path);
3370
+ if (!stats.isFile()) {
3371
+ throw new Error(
3372
+ `readTextFile: ${params.path} is not a regular file (reported as ${describeStatKind(stats)}). Pipe / device / proc-like inputs can produce unbounded data and aren't supported by the bridge fs proxy.`
3373
+ );
3374
+ }
3375
+ if (stats.size > READ_FILE_SIZE_CAP) {
3376
+ throw new Error(
3377
+ `readTextFile: ${params.path} is ${stats.size} bytes, exceeds the ${READ_FILE_SIZE_CAP}-byte daemon cap. Tail/grep externally and feed the relevant slice instead.`
3378
+ );
3379
+ }
3380
+ const content = await fs2.readFile(params.path, "utf8");
3381
+ if (typeof params.line === "number" || typeof params.limit === "number") {
3382
+ const startLine = params.line ?? 1;
3383
+ const start = startLine > 0 ? startLine - 1 : 0;
3384
+ const end = params.limit != null ? start + params.limit : void 0;
3385
+ return { content: sliceLineRange(content, start, end) };
3386
+ }
3387
+ return { content };
3388
+ }
3389
+ };
3390
+ var A2UI_TOOL_RE = /(^|__)(present_ui|present_choices|a2ui_action)$/;
3391
+ function isA2uiToolMeta(meta) {
3392
+ if (!meta) return false;
3393
+ if (typeof meta.serverId === "string" && meta.serverId.toLowerCase().includes("a2ui"))
3394
+ return true;
3395
+ return typeof meta.toolName === "string" && A2UI_TOOL_RE.test(meta.toolName);
3396
+ }
3397
+ __name(isA2uiToolMeta, "isA2uiToolMeta");
3398
+ function splitA2uiText(raw) {
3399
+ const s = raw.replace(/^\s+/, "");
3400
+ if (s[0] !== "[") return null;
3401
+ let depth = 0;
3402
+ let inStr = false;
3403
+ let esc = false;
3404
+ let end = -1;
3405
+ for (let i = 0; i < s.length; i++) {
3406
+ const c = s[i];
3407
+ if (inStr) {
3408
+ if (esc) esc = false;
3409
+ else if (c === "\\") esc = true;
3410
+ else if (c === '"') inStr = false;
3411
+ continue;
3412
+ }
3413
+ if (c === '"') inStr = true;
3414
+ else if (c === "[") depth++;
3415
+ else if (c === "]") {
3416
+ depth--;
3417
+ if (depth === 0) {
3418
+ end = i + 1;
3419
+ break;
3420
+ }
3421
+ }
3422
+ }
3423
+ if (end < 0) return null;
3424
+ try {
3425
+ const arr = JSON.parse(s.slice(0, end));
3426
+ if (!Array.isArray(arr) || arr.length === 0) return null;
3427
+ return [arr, s.slice(end).trim()];
3428
+ } catch {
3429
+ return null;
3430
+ }
3431
+ }
3432
+ __name(splitA2uiText, "splitA2uiText");
3433
+ function surfaceIdOf(c) {
3434
+ const cmd = c;
3435
+ return cmd?.createSurface?.surfaceId ?? cmd?.updateComponents?.surfaceId ?? cmd?.updateDataModel?.surfaceId ?? cmd?.deleteSurface?.surfaceId;
3436
+ }
3437
+ __name(surfaceIdOf, "surfaceIdOf");
3438
+ function extractA2uiToolUpdate(params) {
3439
+ const update = params.update;
3440
+ if (!update || update["sessionUpdate"] !== "tool_call_update") return null;
3441
+ const meta = update["_meta"];
3442
+ if (!isA2uiToolMeta(meta)) return null;
3443
+ const content = update["content"];
3444
+ if (!Array.isArray(content)) return null;
3445
+ let split = null;
3446
+ let hitIndex = -1;
3447
+ for (let i = 0; i < content.length; i++) {
3448
+ const inner = content[i]?.content;
3449
+ if (typeof inner?.text === "string") {
3450
+ split = splitA2uiText(inner.text);
3451
+ if (split) {
3452
+ hitIndex = i;
3453
+ break;
3454
+ }
3455
+ }
3456
+ }
3457
+ if (!split) return null;
3458
+ const [commands, fallback] = split;
3459
+ const order = [];
3460
+ const grouped = /* @__PURE__ */ new Map();
3461
+ for (const c of commands) {
3462
+ const sid = surfaceIdOf(c);
3463
+ if (!sid) {
3464
+ const shape = c && typeof c === "object" ? Object.keys(c).join(",") || "empty object" : typeof c;
3465
+ writeStderrLine(
3466
+ `a2ui: dropping command with unrecognized shape (${shape})`
3467
+ );
3468
+ continue;
3469
+ }
3470
+ if (!grouped.has(sid)) {
3471
+ grouped.set(sid, []);
3472
+ order.push(sid);
3473
+ }
3474
+ grouped.get(sid).push(c);
3475
+ }
3476
+ const surfaces = order.map((sid) => ({
3477
+ surfaceId: sid,
3478
+ commands: grouped.get(sid)
3479
+ }));
3480
+ const sanitizedText = fallback || "[A2UI surface rendered]";
3481
+ const sanitizedContent = content.map(
3482
+ (block, i) => i === hitIndex ? {
3483
+ ...block,
3484
+ content: {
3485
+ ...block.content ?? {},
3486
+ text: sanitizedText
3487
+ }
3488
+ } : block
3489
+ );
3490
+ const sanitizedUpdate = {
3491
+ ...update,
3492
+ content: sanitizedContent
3493
+ };
3494
+ if (typeof update["rawOutput"] === "string") {
3495
+ sanitizedUpdate["rawOutput"] = sanitizedText;
3496
+ }
3497
+ return {
3498
+ surfaces,
3499
+ callId: typeof update["toolCallId"] === "string" ? update["toolCallId"] : void 0,
3500
+ sanitizedParams: {
3501
+ ...params,
3502
+ update: sanitizedUpdate
3503
+ }
3504
+ };
3505
+ }
3506
+ __name(extractA2uiToolUpdate, "extractA2uiToolUpdate");
3507
+
3508
+ export {
3509
+ CANCEL_VOTE_SENTINEL,
3510
+ createNoOpPermissionAuditPublisher,
3511
+ MultiClientPermissionMediator,
3512
+ writeStderrLine,
3513
+ SessionAttachmentReferenceError,
3514
+ isSessionAttachmentReference,
3515
+ withAttachmentDegradationMarker,
3516
+ SessionAttachmentStore,
3517
+ KNOWN_APPROVAL_MODES,
3518
+ BridgeClient,
3519
+ isA2uiToolMeta
3520
+ };
3521
+ /**
3522
+ * @license
3523
+ * Copyright 2025 Qwen Team
3524
+ * SPDX-License-Identifier: Apache-2.0
3525
+ */