@qwen-code/qwen-code 0.19.8 → 0.19.9-preview.0

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 (451) hide show
  1. package/bundled/qc-helper/SKILL.md +1 -0
  2. package/bundled/qc-helper/docs/configuration/auth.md +40 -55
  3. package/bundled/qc-helper/docs/configuration/model-providers.md +224 -218
  4. package/bundled/qc-helper/docs/configuration/settings.md +68 -51
  5. package/bundled/qc-helper/docs/features/_meta.ts +1 -0
  6. package/bundled/qc-helper/docs/features/channels/dingtalk.md +12 -2
  7. package/bundled/qc-helper/docs/features/channels/overview.md +82 -7
  8. package/bundled/qc-helper/docs/features/channels/wecom.md +6 -0
  9. package/bundled/qc-helper/docs/features/code-review.md +109 -41
  10. package/bundled/qc-helper/docs/features/commands.md +45 -44
  11. package/bundled/qc-helper/docs/features/computer-use.md +77 -0
  12. package/bundled/qc-helper/docs/features/hooks.md +29 -0
  13. package/bundled/qc-helper/docs/features/sub-agents.md +21 -0
  14. package/bundled/qc-helper/docs/features/tool-use-summaries.md +19 -23
  15. package/bundled/qc-helper/docs/integration-vscode.md +2 -2
  16. package/bundled/qc-helper/docs/qwen-serve.md +66 -23
  17. package/bundled/qc-helper/docs/reference/keyboard-shortcuts.md +2 -2
  18. package/bundled/review/DESIGN.md +303 -46
  19. package/bundled/review/SKILL.md +670 -165
  20. package/chunks/MaxSizedBox-BEVSAWZ6.js +70 -0
  21. package/chunks/{StandaloneSessionPicker-EYRGH6PJ.js → StandaloneSessionPicker-2DA2MDDE.js} +81 -79
  22. package/chunks/{acpAgent-GRFTTRHW.js → acpAgent-G3WJU3PS.js} +1318 -465
  23. package/chunks/agent-DD3WSHXE.js +68 -0
  24. package/chunks/agent-headless-MHI7U2CU.js +62 -0
  25. package/chunks/{anthropicContentGenerator-CPF22MA7.js → anthropicContentGenerator-56U3YCP2.js} +187 -31
  26. package/chunks/{artifact-tool-D233YJFH.js → artifact-tool-MIZLXBLF.js} +5 -5
  27. package/chunks/{askUserQuestion-MKL5ZTMC.js → askUserQuestion-XMJVPNWM.js} +6 -6
  28. package/chunks/bridge-HKEWPNB5.js +78 -0
  29. package/chunks/{ca-MKD32FQL.js → ca-C2JTT2N5.js} +68 -6
  30. package/chunks/channel-worker-group-K7QKH6IG.js +13 -0
  31. package/chunks/channel-worker-manager-YSFB4HUU.js +418 -0
  32. package/chunks/channel-worker-supervisor-QCKIOYZD.js +20 -0
  33. package/chunks/chunk-2FMQQXMM.js +38 -0
  34. package/chunks/{chunk-CSOTA7KX.js → chunk-2FO5GFKZ.js} +2 -2
  35. package/chunks/{chunk-QQDPRDVW.js → chunk-2HRYPZT5.js} +1 -1
  36. package/chunks/{chunk-UXEW557D.js → chunk-2J4ZWME4.js} +3 -3
  37. package/chunks/{chunk-P3XEEI3X.js → chunk-2TQXXQMI.js} +36 -7
  38. package/chunks/{chunk-GSHQYYIV.js → chunk-2UW5RFRP.js} +6 -26
  39. package/chunks/{chunk-6IAWPIQ6.js → chunk-2W3OOD4W.js} +5 -3
  40. package/chunks/{chunk-2PSWA5ID.js → chunk-2X56YUSZ.js} +1 -1
  41. package/chunks/{chunk-NQKLOAVJ.js → chunk-2Z5UX4KI.js} +2 -2
  42. package/chunks/chunk-3MX7D6QN.js +516 -0
  43. package/chunks/{chunk-EEN2TKST.js → chunk-43RXPU5Q.js} +2 -2
  44. package/chunks/{chunk-55ZMG67I.js → chunk-4LD5APFL.js} +4 -4
  45. package/chunks/{chunk-WF4FAXDL.js → chunk-4QZ3XHWE.js} +62 -62
  46. package/chunks/chunk-4QZADKJS.js +34 -0
  47. package/chunks/{chunk-UIDXQNMV.js → chunk-4WGODMVR.js} +1 -1
  48. package/chunks/{chunk-E6US47KI.js → chunk-4WKAA4KS.js} +1 -1
  49. package/chunks/{chunk-TI3BWD4D.js → chunk-54UZQ57N.js} +39 -19
  50. package/chunks/{chunk-3GONHQOA.js → chunk-57OAHC2Y.js} +1 -1
  51. package/chunks/{chunk-JZSY4WP3.js → chunk-5DTTYNB7.js} +1 -1
  52. package/chunks/chunk-5HBA2Z7V.js +28 -0
  53. package/chunks/{chunk-SKAZWEV5.js → chunk-5M6IDOMF.js} +35 -98
  54. package/chunks/{chunk-A4BMJM77.js → chunk-5O2XNYP6.js} +1 -0
  55. package/chunks/{chunk-FQNC62B2.js → chunk-5VHASII5.js} +11 -11
  56. package/chunks/{chunk-GO6LNQXT.js → chunk-6AM5D4ME.js} +1 -1
  57. package/chunks/{chunk-BJBWRCSK.js → chunk-6H67XLET.js} +1 -1
  58. package/chunks/{chunk-O7FIRWJV.js → chunk-6JPDCEDB.js} +3 -3
  59. package/chunks/{chunk-3UP777ZS.js → chunk-6RLROE6G.js} +1 -1
  60. package/chunks/{chunk-GN3F6PIU.js → chunk-6WPJRYLZ.js} +5 -5
  61. package/chunks/{chunk-ZERZSAZL.js → chunk-75DOP5OR.js} +2 -2
  62. package/chunks/chunk-7DKX4A5Z.js +91 -0
  63. package/chunks/{chunk-M7S4GK5Z.js → chunk-7IDCQ35Q.js} +147 -30
  64. package/chunks/{chunk-22MNNNXE.js → chunk-7NFB7LBQ.js} +11 -3
  65. package/chunks/{chunk-ZL5HN75Q.js → chunk-7R4DPSRA.js} +37 -3
  66. package/chunks/{chunk-VZ7HY3YU.js → chunk-7STBROEV.js} +3 -3
  67. package/chunks/{chunk-SAIILPQ3.js → chunk-7X7TEQNX.js} +5 -3
  68. package/chunks/{chunk-RMLZAUWH.js → chunk-A3U2TQUL.js} +10 -12
  69. package/chunks/{chunk-IRJWHZWU.js → chunk-A4UCTEB5.js} +2 -2
  70. package/chunks/{chunk-BOBRZZKD.js → chunk-A7W4GQRW.js} +5 -5
  71. package/chunks/{chunk-O33TBKDC.js → chunk-AAUZBE5Y.js} +155 -19
  72. package/chunks/{chunk-SQ2UATK7.js → chunk-ADLYZJYZ.js} +11 -11
  73. package/chunks/{chunk-QYUE6W3T.js → chunk-ALGGS7UH.js} +2 -39
  74. package/chunks/{chunk-WMPVYQ4P.js → chunk-ARKANCNX.js} +3 -2
  75. package/chunks/{chunk-IBY3Q3HG.js → chunk-AU4R7ACM.js} +967 -181
  76. package/chunks/{channel-worker-supervisor-NZQUOEFJ.js → chunk-B7MFJBKE.js} +190 -34
  77. package/chunks/{chunk-HW4N45EE.js → chunk-B7NUUEXC.js} +38 -11
  78. package/chunks/{chunk-3B26UVCX.js → chunk-BLPMRN4B.js} +39 -10
  79. package/chunks/{chunk-ZN5T4BHI.js → chunk-BPZALHVR.js} +2 -2
  80. package/chunks/{chunk-BVYU3ZTI.js → chunk-BXT6PQ73.js} +1020 -942
  81. package/chunks/{chunk-OFEVLU4C.js → chunk-CARU2RR2.js} +1 -1
  82. package/chunks/{chunk-HDOH3QYE.js → chunk-CEGKYZDB.js} +2 -2
  83. package/chunks/{chunk-GLE2YUPU.js → chunk-COH43MRJ.js} +1 -1
  84. package/chunks/{chunk-E5A7LHNN.js → chunk-CPBF7KYF.js} +1 -1
  85. package/chunks/{chunk-UWCTAVOD.js → chunk-CR3C7WXL.js} +1 -1
  86. package/chunks/{chunk-IWKSG2AR.js → chunk-CXAOG665.js} +1 -1
  87. package/chunks/{chunk-64WXLC72.js → chunk-DJ2GSRLV.js} +1 -1
  88. package/chunks/{chunk-QN5NZ3UQ.js → chunk-DMTGGOSA.js} +2 -2
  89. package/chunks/{chunk-27BHARDE.js → chunk-DMVA55W5.js} +2124 -30
  90. package/chunks/{chunk-JPALE3PA.js → chunk-DQCCLVQK.js} +6 -6
  91. package/chunks/{chunk-X2XNQQJI.js → chunk-E2UVIFI2.js} +186 -399
  92. package/chunks/{chunk-KHDZHZMH.js → chunk-E5Z2AVNV.js} +1 -1
  93. package/chunks/{chunk-E7EMCSUG.js → chunk-EAHMOXLL.js} +64 -42
  94. package/chunks/{chunk-VGGOPZUL.js → chunk-EJ3KGYWL.js} +36 -8
  95. package/chunks/{chunk-DIWVZ3VM.js → chunk-ERTHKDKN.js} +5 -5
  96. package/chunks/{chunk-UX6OTBVD.js → chunk-EX5OULP3.js} +2 -2
  97. package/chunks/{chunk-5MBO76SL.js → chunk-F333XEAU.js} +3 -3
  98. package/chunks/{chunk-OACJLMLD.js → chunk-FBU7WRZI.js} +18 -2
  99. package/chunks/{chunk-5IFG2VC4.js → chunk-FCMNKVXT.js} +1 -1
  100. package/chunks/chunk-FEOBU3FA.js +253 -0
  101. package/chunks/{chunk-LAXNEOBG.js → chunk-FI2WSOWX.js} +19 -3
  102. package/chunks/{chunk-E7KNELIZ.js → chunk-FSA7ERJ2.js} +2 -2
  103. package/chunks/{chunk-44NROQYV.js → chunk-GDQQRV43.js} +4 -4
  104. package/chunks/{chunk-ZE6E424U.js → chunk-GN5HJJBO.js} +2 -2
  105. package/chunks/{chunk-O2YT7VB4.js → chunk-GOXKNSDZ.js} +2 -65
  106. package/chunks/{chunk-V35MD7KN.js → chunk-GRC5HGPI.js} +1 -1
  107. package/chunks/{chunk-ON3JA3RU.js → chunk-GTENQOEJ.js} +7 -9
  108. package/chunks/{chunk-S6RLAIUR.js → chunk-GYTKQYDB.js} +1 -1
  109. package/chunks/{chunk-A2X3KLC3.js → chunk-GZXCZHCT.js} +3 -3
  110. package/chunks/{chunk-K5PGHDBN.js → chunk-H6XPXXMH.js} +1 -1
  111. package/chunks/{chunk-PMFULMLX.js → chunk-HCJTURZD.js} +2262 -384
  112. package/chunks/chunk-HGG2RRXD.js +445 -0
  113. package/chunks/{chunk-II73RK2S.js → chunk-HGT6JR3U.js} +20 -1
  114. package/chunks/{chunk-DICGXFAI.js → chunk-HJCVNZLQ.js} +11 -9
  115. package/chunks/{chunk-3GNOQZDC.js → chunk-HJUDVNCO.js} +17663 -14877
  116. package/chunks/{chunk-AYNCLGTW.js → chunk-HRUGYOJN.js} +8 -51
  117. package/chunks/{chunk-KYMBIKIW.js → chunk-HTHUX2T5.js} +1 -1
  118. package/chunks/{chunk-LQ7TMOCE.js → chunk-HTO4JFDZ.js} +1 -1
  119. package/chunks/{chunk-BUZ6HGL4.js → chunk-HWTL6MPR.js} +357 -59
  120. package/chunks/chunk-IDS7MSUP.js +74 -0
  121. package/chunks/{chunk-4CGOWHKM.js → chunk-J4MOFEOK.js} +2 -2
  122. package/chunks/chunk-JCD4FM7K.js +296 -0
  123. package/chunks/{chunk-22IFUCVR.js → chunk-JESCGQM3.js} +1 -1
  124. package/chunks/{chunk-JZHIQJOE.js → chunk-JGO6RFI2.js} +6 -6
  125. package/chunks/{chunk-X3YGAX7V.js → chunk-JGUT3LWZ.js} +9 -2
  126. package/chunks/{chunk-JRQ7EVGE.js → chunk-JW7OOJC6.js} +2 -2
  127. package/chunks/{chunk-OQXDAMKY.js → chunk-KF5MMGG4.js} +8 -8
  128. package/chunks/{chunk-42XGFRJS.js → chunk-KKRTYONM.js} +8 -6
  129. package/chunks/{chunk-GGQQA4JW.js → chunk-KSO42X3Z.js} +15 -2
  130. package/chunks/{chunk-MRO43B25.js → chunk-KW7NOTN6.js} +1 -1
  131. package/chunks/{chunk-54YZOP7P.js → chunk-L573Z2QZ.js} +11894 -5190
  132. package/chunks/{chunk-TLWA7TPR.js → chunk-L5U3ELZX.js} +2 -2
  133. package/chunks/{chunk-YHKAK72D.js → chunk-LHEOLOXU.js} +251 -63
  134. package/chunks/{chunk-H6EMK6QK.js → chunk-LQIDP6K5.js} +11913 -5207
  135. package/chunks/{chunk-I6WGFJJ3.js → chunk-LSEJG6AM.js} +73 -19
  136. package/chunks/{chunk-MZ7BABX3.js → chunk-LXLTBIDP.js} +44 -7
  137. package/chunks/{chunk-HLKG6CZN.js → chunk-M5GB774H.js} +1 -1
  138. package/chunks/{chunk-JCH35JB3.js → chunk-MKK4XGLF.js} +2 -2
  139. package/chunks/{chunk-YKZIAK7C.js → chunk-MO7O5722.js} +424 -40
  140. package/chunks/chunk-MR3PXB6E.js +48 -0
  141. package/chunks/{chunk-A5JOJ77H.js → chunk-MRYWYBQ4.js} +38 -15
  142. package/chunks/{chunk-332PWN27.js → chunk-NHDKSOEY.js} +1 -1
  143. package/chunks/{chunk-TSPB77TG.js → chunk-NRAFASYX.js} +1016 -2967
  144. package/chunks/{chunk-BMFMJINR.js → chunk-OA4JVLSA.js} +28 -2
  145. package/chunks/{chunk-P7M4GA57.js → chunk-OCPBI7J5.js} +1 -1
  146. package/chunks/chunk-OJAMDVF5.js +66 -0
  147. package/chunks/{chunk-IJRL2S3D.js → chunk-OKAIGAYW.js} +6 -6
  148. package/chunks/{chunk-FBET5XZ2.js → chunk-PEJWA3QQ.js} +29 -10
  149. package/chunks/{chunk-L4AMBVQG.js → chunk-PMKW35AT.js} +54 -22
  150. package/chunks/{chunk-AGHU5VRK.js → chunk-PZL23GTM.js} +1 -1
  151. package/chunks/{chunk-PC4ZXHU2.js → chunk-Q6TUALBE.js} +3 -3
  152. package/chunks/{chunk-FXGLL2HL.js → chunk-Q7YDKOGY.js} +1 -1
  153. package/chunks/{chunk-XOHNA2S6.js → chunk-QAW7PIHT.js} +3 -3
  154. package/chunks/{chunk-XYR5RUA5.js → chunk-QE2WJ7FS.js} +10 -3
  155. package/chunks/{chunk-LGFAYIKX.js → chunk-QHTIBUWB.js} +1 -1
  156. package/chunks/{chunk-P3MYAZBY.js → chunk-QMGX2KO2.js} +1 -1
  157. package/chunks/{chunk-Z3I3QJK3.js → chunk-QRUIS3DN.js} +1 -1
  158. package/chunks/{chunk-ITOGNELQ.js → chunk-QSWDGAIC.js} +2 -2
  159. package/chunks/{chunk-PUVCVELY.js → chunk-QUJOI7SR.js} +70 -8
  160. package/chunks/{chunk-GHOTR7HL.js → chunk-QUPXZXLV.js} +1 -1
  161. package/chunks/{chunk-6VK6FIMQ.js → chunk-R5D5JOAM.js} +415 -379
  162. package/chunks/{chunk-CD6USWHZ.js → chunk-R5XLNDE2.js} +2 -2
  163. package/chunks/{chunk-W7EEOR7D.js → chunk-RCEEXIOO.js} +8 -6
  164. package/chunks/{chunk-TLRYABYP.js → chunk-RKUWKYED.js} +1 -1
  165. package/chunks/{chunk-RNBYOUGV.js → chunk-RXFQM6FQ.js} +1 -1
  166. package/chunks/{chunk-QXPI7FEC.js → chunk-S3TWXGKA.js} +4 -4
  167. package/chunks/{chunk-2EM7ECZD.js → chunk-SMSFWUFY.js} +309 -80
  168. package/chunks/{chunk-FV7425LN.js → chunk-SPJQO2CE.js} +18 -5
  169. package/chunks/{chunk-MLZQVCF3.js → chunk-SZRN75WQ.js} +1 -1
  170. package/chunks/{chunk-7RYW5LQV.js → chunk-T2NCM2ET.js} +2 -2
  171. package/chunks/{chunk-Z2Z3GUXZ.js → chunk-TB6UDU4T.js} +1 -1
  172. package/chunks/{chunk-EXPMGZZV.js → chunk-TEGEBB2I.js} +1 -1
  173. package/chunks/{chunk-MX2YXRER.js → chunk-TJTKSVIV.js} +1 -1
  174. package/chunks/{chunk-CCGOFQV3.js → chunk-TQBODCFD.js} +8 -7
  175. package/chunks/chunk-TV6K2FB4.js +224 -0
  176. package/chunks/{chunk-H6BD2ELD.js → chunk-TYAMGABM.js} +2 -2
  177. package/chunks/{chunk-QYNNN7BK.js → chunk-TYLOFD2U.js} +2 -2
  178. package/chunks/{chunk-VRDMOSQS.js → chunk-UDXSXLER.js} +2 -2
  179. package/chunks/{chunk-DHOLNQLC.js → chunk-UFDX27PL.js} +60 -40
  180. package/chunks/{chunk-WHJQ3JUS.js → chunk-UGN6CTNZ.js} +10 -10
  181. package/chunks/{chunk-CHB5SLZZ.js → chunk-UNLRKDMW.js} +4 -4
  182. package/chunks/{chunk-CJCKDMWO.js → chunk-URK542T4.js} +1 -1
  183. package/chunks/{chunk-AHNXYU4O.js → chunk-V6WHFGST.js} +1 -1
  184. package/chunks/{chunk-ZTQ26VBE.js → chunk-VDDGDZMA.js} +1 -1
  185. package/chunks/{chunk-BOBOCR5H.js → chunk-VEETSXYX.js} +4 -4
  186. package/chunks/{chunk-OMX7CUOE.js → chunk-VGC4I5JJ.js} +1 -1
  187. package/chunks/{chunk-43W4YSO6.js → chunk-VJNBRRZG.js} +2 -2
  188. package/chunks/{chunk-WQTHLB5T.js → chunk-VKS7EIOA.js} +7 -7
  189. package/chunks/{chunk-J7RCB6N5.js → chunk-VVDNGM6X.js} +2 -2
  190. package/chunks/{chunk-ER3BKOLB.js → chunk-W7FJ3N32.js} +1 -1
  191. package/chunks/{chunk-3HX5LZ6R.js → chunk-WBL3FJEU.js} +2 -2
  192. package/chunks/{chunk-7LTB54MK.js → chunk-WIEO4CWB.js} +2 -2
  193. package/chunks/chunk-WMC6HH7Y.js +493 -0
  194. package/chunks/{chunk-FEJ2FZ3U.js → chunk-WTNZ6DXB.js} +2 -2
  195. package/chunks/{chunk-L46XKEGM.js → chunk-X6ODDESK.js} +5 -8
  196. package/chunks/chunk-XDPYM5DE.js +71 -0
  197. package/chunks/{chunk-3RW4AZJV.js → chunk-XFSFZCM5.js} +1 -1
  198. package/chunks/{chunk-2MPVVENX.js → chunk-XU457J4V.js} +2 -2
  199. package/chunks/{chunk-SIUQ3YYX.js → chunk-XVNQMZ2I.js} +1 -1
  200. package/chunks/{chunk-SYCJMSIJ.js → chunk-XWRJCPHC.js} +1 -1
  201. package/chunks/{chunk-SUAKHHK7.js → chunk-Y66VOBRP.js} +3 -3
  202. package/chunks/chunk-YAF2MQZ6.js +19 -0
  203. package/chunks/chunk-YHN5SUIJ.js +219 -0
  204. package/chunks/{chunk-BR4QREVK.js → chunk-YQ3U5MUC.js} +1 -1
  205. package/chunks/{chunk-Y6Z2O3WR.js → chunk-YUZI3WAC.js} +1 -1
  206. package/chunks/{chunk-YDOXPLYL.js → chunk-ZA74ZTQU.js} +4564 -1326
  207. package/chunks/{chunk-DCZWC4PF.js → chunk-ZGE3222J.js} +5 -5
  208. package/chunks/chunk-ZP4ZZJJG.js +448 -0
  209. package/chunks/{chunk-AHNJGLFV.js → chunk-ZPAHLFZU.js} +3 -3
  210. package/chunks/cli-entry-path-4VJ3Y6T2.js +10 -0
  211. package/chunks/{computer-use-WMY3UOBX.js → computer-use-SHA6C3ES.js} +50 -50
  212. package/chunks/config-utils-XSHOMMK4.js +24 -0
  213. package/chunks/contextCommand-HOAF5LUC.js +68 -0
  214. package/chunks/create-sub-session-EAB2U5XW.js +207 -0
  215. package/chunks/create-sub-session-WJOWBFEP.js +375 -0
  216. package/chunks/{cron-create-JSXEIPVS.js → cron-create-GMVKSXZT.js} +9 -9
  217. package/chunks/{cron-delete-OIFVEPGU.js → cron-delete-VDZKUAVK.js} +7 -7
  218. package/chunks/{cron-list-D4ZX4IOE.js → cron-list-UG7C7RAR.js} +14 -10
  219. package/chunks/{daemon-WPAENEBW.js → daemon-MI5HF26N.js} +1074 -61
  220. package/chunks/daemon-status-provider-KFL3LRTP.js +76 -0
  221. package/chunks/{de-ZFPPQVSF.js → de-2VCDRDGU.js} +68 -6
  222. package/chunks/{devtools-FM6GJPYG.js → devtools-4QFYJT6U.js} +3 -3
  223. package/chunks/{dist-O2IRTRPP.js → dist-IEXZC5ZR.js} +10 -10
  224. package/chunks/{dist-SCFLLYMK.js → dist-PQK4GCKA.js} +12 -11
  225. package/chunks/{dist-63IS3ZMI.js → dist-PZV5RKM6.js} +4 -4
  226. package/chunks/{dist-E6UANPIM.js → dist-U75JK3PF.js} +1011 -197
  227. package/chunks/{dist-H2AEJCZ5.js → dist-VXO7QBON.js} +5 -5
  228. package/chunks/{dist-Z34QPAQW.js → dist-WH4TZSH3.js} +529 -79
  229. package/chunks/{dist-BEXOZXLQ.js → dist-X2ABIW5R.js} +36 -15
  230. package/chunks/earlyInputCapture-PKAQJMIP.js +69 -0
  231. package/chunks/{edit-OTYWAXGP.js → edit-6TXLYHJ2.js} +51 -51
  232. package/chunks/{en-N4EPZAQI.js → en-NJCA3TAA.js} +78 -6
  233. package/chunks/{enter-worktree-HNYBDVCX.js → enter-worktree-ZTILGPJX.js} +50 -50
  234. package/chunks/{enterPlanMode-CJCNGT63.js → enterPlanMode-62HRAMCH.js} +66 -51
  235. package/chunks/environment-OYA3ZVEL.js +89 -0
  236. package/chunks/errors-BFXOUUUR.js +75 -0
  237. package/chunks/{exit-worktree-DUUKOE3X.js → exit-worktree-HHJ5M6MV.js} +50 -50
  238. package/chunks/{exitPlanMode-AMMSXOAJ.js → exitPlanMode-M3SWHCL7.js} +51 -51
  239. package/chunks/{fast-path-OV6NIP3K.js → fast-path-3VMPLM74.js} +8 -8
  240. package/chunks/{fast-path-settings-7RIFI3PK.js → fast-path-settings-DAOVFADC.js} +8 -6
  241. package/chunks/{fileFromPath-IBEHA3CO.js → fileFromPath-UDALK7FM.js} +3 -3
  242. package/chunks/{fr-5WWMY53B.js → fr-5F3E7WKD.js} +68 -6
  243. package/chunks/{gemini-NQ3WWBKB.js → gemini-ZCGVYOJL.js} +181 -110
  244. package/chunks/{geminiContentGenerator-CDUNEJQE.js → geminiContentGenerator-XBYBAIDQ.js} +9 -9
  245. package/chunks/{getMachineId-bsd-4CASPIU4.js → getMachineId-bsd-EXV7SWPA.js} +3 -3
  246. package/chunks/{getMachineId-darwin-HPQPEMZR.js → getMachineId-darwin-IUOMFXU3.js} +3 -3
  247. package/chunks/{getMachineId-linux-AUARKYHL.js → getMachineId-linux-K7XJYFHL.js} +2 -2
  248. package/chunks/{getMachineId-unsupported-S32ZDA2T.js → getMachineId-unsupported-EA5FDRJ6.js} +2 -2
  249. package/chunks/{getMachineId-win-4EFLHYIJ.js → getMachineId-win-KBA6RAI5.js} +3 -3
  250. package/chunks/{glob-ROV3YS4R.js → glob-UOEYQNHN.js} +97 -75
  251. package/chunks/{grep-NRITKDCD.js → grep-OQM3CH7W.js} +50 -50
  252. package/chunks/handleAutoUpdate-ORBYVJHZ.js +69 -0
  253. package/chunks/i18n-NBP574CO.js +84 -0
  254. package/chunks/initializer-CWD7TCCD.js +73 -0
  255. package/chunks/installationInfo-CHPDUPWC.js +65 -0
  256. package/chunks/{ja-CUHZOLEJ.js → ja-66J6B53L.js} +69 -7
  257. package/chunks/{keychain-token-storage-UJV5XP4Z.js → keychain-token-storage-AU22CQI6.js} +4 -4
  258. package/chunks/{kittyProtocolDetector-OJRCJLIU.js → kittyProtocolDetector-HCNNKJTI.js} +2 -2
  259. package/chunks/list-ZTTO362W.js +76 -0
  260. package/chunks/loadedSettingsAdapter-V4ZTRKUO.js +70 -0
  261. package/chunks/{loop-wakeup-OHWEMFP5.js → loop-wakeup-C4EXLYVA.js} +10 -10
  262. package/chunks/{lowlight-FYAAUU5J.js → lowlight-J2OZNPCA.js} +1 -1
  263. package/chunks/{ls-7MFU2QYW.js → ls-2OUTG3IZ.js} +6 -6
  264. package/chunks/{lsp-XHDTPTGV.js → lsp-4VMNXZW6.js} +4 -4
  265. package/chunks/mcp-UH5PSGIK.js +70 -0
  266. package/chunks/{monitor-FL2ZO33W.js → monitor-F4NYEU5J.js} +50 -50
  267. package/chunks/{multipart-parser-AJ4WASWR.js → multipart-parser-AWZKXSQN.js} +3 -3
  268. package/chunks/nonInteractiveCli-7IHPXVNH.js +129 -0
  269. package/chunks/{notebook-edit-6PULTHL5.js → notebook-edit-HPDXNGQH.js} +51 -51
  270. package/chunks/openaiContentGenerator-OBDNNOE2.js +56 -0
  271. package/chunks/pidfile-AUMGYIOK.js +73 -0
  272. package/chunks/{pt-OLYN42DS.js → pt-TFZO5Y6T.js} +68 -6
  273. package/chunks/{qwenContentGenerator-NX2E7PB7.js → qwenContentGenerator-LH2U2YRG.js} +52 -52
  274. package/chunks/{qwenOAuth2-ZV7YLMND.js → qwenOAuth2-LEYOU7RU.js} +9 -9
  275. package/chunks/read-file-HUUFBLEO.js +30 -0
  276. package/chunks/{read-mcp-resource-CNQHBSJM.js → read-mcp-resource-JUFHCKSC.js} +5 -5
  277. package/chunks/{read-package-up-ER5OJUGP.js → read-package-up-TCM6I7S2.js} +2 -2
  278. package/chunks/{record-artifact-SQ2OKRJW.js → record-artifact-DWSMBXYY.js} +4 -4
  279. package/chunks/ripGrep-C7X33LU6.js +60 -0
  280. package/chunks/{ru-LDM3GWVQ.js → ru-4L4LFHIT.js} +68 -6
  281. package/chunks/{run-qwen-serve-JK2DL7PI.js → run-qwen-serve-DGW5AFDJ.js} +1465 -283
  282. package/chunks/runtime-NYUSLBEZ.js +99 -0
  283. package/chunks/{scheduler-O4IGVZKK.js → scheduler-5VKDXULT.js} +51 -51
  284. package/chunks/{send-message-76GT3JMC.js → send-message-JZ752NWD.js} +7 -7
  285. package/chunks/serve-UTAHSFXH.js +76 -0
  286. package/chunks/{server-SGNPKX2O.js → server-VUHSYSUV.js} +9088 -1817
  287. package/chunks/{session-5BNM25EZ.js → session-QSGSSNFB.js} +340 -135
  288. package/chunks/settings-X24RYEED.js +120 -0
  289. package/chunks/shell-4MSI2R4O.js +70 -0
  290. package/chunks/{skill-I22QK5XS.js → skill-JR3GTNVZ.js} +24 -24
  291. package/chunks/spawnChannel-RFAAO44J.js +70 -0
  292. package/chunks/{src-7XL4G4DC.js → src-VLI3TKEY.js} +4 -4
  293. package/chunks/{src-BBQSM46M.js → src-YDBNMIIM.js} +183 -53
  294. package/chunks/standalone-update-4CY4HKGC.js +79 -0
  295. package/chunks/{startInteractiveUI-JII36UGN.js → startInteractiveUI-FOD7MMVB.js} +2368 -1923
  296. package/chunks/stdioHelpers-UMY72QRP.js +14 -0
  297. package/chunks/{syntheticOutput-F7LSDCAR.js → syntheticOutput-LELY6HAI.js} +5 -5
  298. package/chunks/task-create-QY3BIMNM.js +23 -0
  299. package/chunks/{task-list-TGGJX7EA.js → task-list-D2U47WBJ.js} +11 -10
  300. package/chunks/{task-stop-W7NGTMYQ.js → task-stop-N6E5IYLH.js} +4 -4
  301. package/chunks/{task-update-XYWMHJGJ.js → task-update-HJCQILLB.js} +13 -12
  302. package/chunks/{team-create-A7NUU5AQ.js → team-create-ZLQSU2E3.js} +50 -50
  303. package/chunks/{team-delete-UHQQ2QQX.js → team-delete-RCWYDRF4.js} +11 -10
  304. package/chunks/{team-plan-approval-TOQRJ2KI.js → team-plan-approval-YUAYBYLS.js} +50 -50
  305. package/chunks/theme-manager-SKNEBMOG.js +64 -0
  306. package/chunks/{todoWrite-C4ZAMSR3.js → todoWrite-FKCGATPJ.js} +6 -6
  307. package/chunks/{tool-search-SZ4EFS2T.js → tool-search-V5J3TGWY.js} +24 -24
  308. package/chunks/total-session-admission-2WIH72I3.js +72 -0
  309. package/chunks/tree-sitter-YJVE2ZUK.js +2980 -0
  310. package/chunks/trustedFolders-JF265CRI.js +80 -0
  311. package/chunks/types-ML3TRJQ5.js +16 -0
  312. package/chunks/updateCheck-AMXNPBP6.js +69 -0
  313. package/chunks/updateEventEmitter-5MKC7BLQ.js +10 -0
  314. package/chunks/validateNonInterActiveAuth-6YHQO7PD.js +194 -0
  315. package/chunks/{version-NO2XSNNP.js → version-UEJJK7PE.js} +3 -3
  316. package/chunks/{web-fetch-UYGMOHW4.js → web-fetch-QGODBB6S.js} +8 -8
  317. package/chunks/{workflow-WO2H6NRI.js → workflow-5YFA5V4M.js} +51 -51
  318. package/chunks/workspace-providers-status-LMBLBNR7.js +73 -0
  319. package/chunks/workspace-registration-store-VV2S4MSV.js +27 -0
  320. package/chunks/workspace-registry-BVMPYRLN.js +76 -0
  321. package/chunks/workspace-service-PUYIZ3J4.js +86 -0
  322. package/chunks/workspace-skills-status-CJ7J2KPP.js +72 -0
  323. package/chunks/{write-file-PX2Z7IBE.js → write-file-OV77M6X7.js} +51 -51
  324. package/chunks/{yargs-B64EYTCF.js → yargs-6H2AUULL.js} +2 -2
  325. package/chunks/{zh-J4F6QWQV.js → zh-FSAH32JB.js} +79 -7
  326. package/chunks/{zh-TW-XVDWOLSW.js → zh-TW-D2YIE2LO.js} +79 -7
  327. package/cli.js +18 -17
  328. package/locales/ca.js +91 -7
  329. package/locales/de.js +90 -7
  330. package/locales/en.js +109 -7
  331. package/locales/fr.js +91 -7
  332. package/locales/ja.js +93 -7
  333. package/locales/pt.js +90 -7
  334. package/locales/ru.js +90 -7
  335. package/locales/zh-TW.js +106 -7
  336. package/locales/zh.js +106 -7
  337. package/package.json +3 -6
  338. package/web-shell/assets/{arc-BWyoJpY2.js → arc-CObt_TK9.js} +1 -1
  339. package/web-shell/assets/{architectureDiagram-3BPJPVTR-BT0mGrz2.js → architectureDiagram-3BPJPVTR-B7aiJ6CK.js} +1 -1
  340. package/web-shell/assets/{blockDiagram-GPEHLZMM-BLgUWvyf.js → blockDiagram-GPEHLZMM-BSK2CrID.js} +1 -1
  341. package/web-shell/assets/{c4Diagram-AAUBKEIU-DYx9M7oU.js → c4Diagram-AAUBKEIU-Bhx2p7T_.js} +1 -1
  342. package/web-shell/assets/channel-BBaLq8MV.js +1 -0
  343. package/web-shell/assets/{chunk-2J33WTMH-CfMHWbuZ.js → chunk-2J33WTMH-c3Hzv0Ni.js} +1 -1
  344. package/web-shell/assets/{chunk-4BX2VUAB-B1URoqEV.js → chunk-4BX2VUAB-DNXaK7ZF.js} +1 -1
  345. package/web-shell/assets/{chunk-55IACEB6-DAIaM4QF.js → chunk-55IACEB6-BXjvDj_i.js} +1 -1
  346. package/web-shell/assets/{chunk-727SXJPM-B2X34knw.js → chunk-727SXJPM-B6CaKFp-.js} +1 -1
  347. package/web-shell/assets/{chunk-AQP2D5EJ-Cif-zLTQ.js → chunk-AQP2D5EJ-tij6Mz9B.js} +1 -1
  348. package/web-shell/assets/{chunk-FMBD7UC4-H18WCXdY.js → chunk-FMBD7UC4-DdlsGk2h.js} +1 -1
  349. package/web-shell/assets/{chunk-ND2GUHAM-YFtetHi0.js → chunk-ND2GUHAM-CdgGZSHE.js} +1 -1
  350. package/web-shell/assets/{chunk-QZHKN3VN-By2hiH3r.js → chunk-QZHKN3VN-D8oJn1C0.js} +1 -1
  351. package/web-shell/assets/classDiagram-4FO5ZUOK-B2ZK6sWE.js +1 -0
  352. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-B2ZK6sWE.js +1 -0
  353. package/web-shell/assets/{cose-bilkent-S5V4N54A-VrcnnLZu.js → cose-bilkent-S5V4N54A-BUVmNp1Y.js} +1 -1
  354. package/web-shell/assets/{dagre-BM42HDAG-CGo-IaVV.js → dagre-BM42HDAG-CO4q310O.js} +1 -1
  355. package/web-shell/assets/{diagram-2AECGRRQ-Dl4QjzIB.js → diagram-2AECGRRQ-C6gFvWkg.js} +1 -1
  356. package/web-shell/assets/{diagram-5GNKFQAL-qMLmsO0u.js → diagram-5GNKFQAL-NG2I_c0h.js} +1 -1
  357. package/web-shell/assets/{diagram-KO2AKTUF-CHlmuxPI.js → diagram-KO2AKTUF-DFKMS6I9.js} +1 -1
  358. package/web-shell/assets/{diagram-LMA3HP47-BUwU3GwT.js → diagram-LMA3HP47-BiElfsfP.js} +1 -1
  359. package/web-shell/assets/{diagram-OG6HWLK6-HEhwPDUM.js → diagram-OG6HWLK6-zLWDYUqu.js} +1 -1
  360. package/web-shell/assets/{erDiagram-TEJ5UH35-1n0llBmM.js → erDiagram-TEJ5UH35-DoVaazhK.js} +1 -1
  361. package/web-shell/assets/{flowDiagram-I6XJVG4X-BN74uMnt.js → flowDiagram-I6XJVG4X-0RLz-mXg.js} +1 -1
  362. package/web-shell/assets/{ganttDiagram-6RSMTGT7-BoOq78i3.js → ganttDiagram-6RSMTGT7-C-eX1A39.js} +3 -3
  363. package/web-shell/assets/{gitGraphDiagram-PVQCEYII-CN8b00c8.js → gitGraphDiagram-PVQCEYII-BauQu1Bu.js} +1 -1
  364. package/web-shell/assets/index-3uAT3ehO.css +5 -0
  365. package/web-shell/assets/index-CVHa2QH7.js +3 -0
  366. package/web-shell/assets/index-D1KigWat.js +1128 -0
  367. package/web-shell/assets/{infoDiagram-5YYISTIA-CCurnIna.js → infoDiagram-5YYISTIA-C800cFvZ.js} +1 -1
  368. package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-AwuRqdUP.js → ishikawaDiagram-YF4QCWOH-CFK1Jk6e.js} +1 -1
  369. package/web-shell/assets/{journeyDiagram-JHISSGLW-B7IICk-E.js → journeyDiagram-JHISSGLW-Cjx0WAC4.js} +1 -1
  370. package/web-shell/assets/{kanban-definition-UN3LZRKU-BYF0OKhx.js → kanban-definition-UN3LZRKU-DWMDHbiv.js} +1 -1
  371. package/web-shell/assets/{linear-GWWguGfD.js → linear-vpW4FrZs.js} +1 -1
  372. package/web-shell/assets/{mermaid.core-JqtkxvCl.js → mermaid.core-DsHRUnDd.js} +5 -5
  373. package/web-shell/assets/{mindmap-definition-RKZ34NQL-B0MmHsZ6.js → mindmap-definition-RKZ34NQL-D3Eb1cAK.js} +1 -1
  374. package/web-shell/assets/{pieDiagram-4H26LBE5-DhImUZLQ.js → pieDiagram-4H26LBE5-Cr2-6sWt.js} +1 -1
  375. package/web-shell/assets/{quadrantDiagram-W4KKPZXB-DGeSC3DT.js → quadrantDiagram-W4KKPZXB-Yd7yUgVp.js} +1 -1
  376. package/web-shell/assets/{requirementDiagram-4Y6WPE33-DkspY6Re.js → requirementDiagram-4Y6WPE33-BE6SprL8.js} +1 -1
  377. package/web-shell/assets/{sankeyDiagram-5OEKKPKP-CaOofS2x.js → sankeyDiagram-5OEKKPKP-DzkuMK8d.js} +1 -1
  378. package/web-shell/assets/{sequenceDiagram-3UESZ5HK-DFQ_kFnN.js → sequenceDiagram-3UESZ5HK-wD9A3Dhi.js} +1 -1
  379. package/web-shell/assets/{stateDiagram-AJRCARHV-D3snw0Tz.js → stateDiagram-AJRCARHV-BOp5q1Nm.js} +1 -1
  380. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-BDsWUmPG.js +1 -0
  381. package/web-shell/assets/{timeline-definition-PNZ67QCA-l4zH8nLM.js → timeline-definition-PNZ67QCA-Btc08sUA.js} +1 -1
  382. package/web-shell/assets/{vennDiagram-CIIHVFJN-BnRlA98M.js → vennDiagram-CIIHVFJN-Bv6upsOA.js} +1 -1
  383. package/web-shell/assets/{wardley-L42UT6IY-BS0xUdmd.js → wardley-L42UT6IY-D-XamnCK.js} +1 -1
  384. package/web-shell/assets/{wardleyDiagram-YWT4CUSO-DKGBUWyQ.js → wardleyDiagram-YWT4CUSO-BbP_5Wfp.js} +1 -1
  385. package/web-shell/assets/{xychartDiagram-2RQKCTM6-DVb2HuOB.js → xychartDiagram-2RQKCTM6-CO5Hh28b.js} +1 -1
  386. package/web-shell/index.html +16 -4
  387. package/chunks/MaxSizedBox-5DT2SD7B.js +0 -71
  388. package/chunks/agent-H2DGKS77.js +0 -68
  389. package/chunks/agent-headless-KFMFIT6J.js +0 -62
  390. package/chunks/bridge-NA3ZQQKJ.js +0 -78
  391. package/chunks/chunk-5P5XGNYH.js +0 -93
  392. package/chunks/chunk-6SHO7WFF.js +0 -79
  393. package/chunks/chunk-IFEWJXFS.js +0 -37
  394. package/chunks/chunk-XNXVCNAT.js +0 -88
  395. package/chunks/contextCommand-LXFZRP4L.js +0 -68
  396. package/chunks/daemon-status-provider-GJRXY5EN.js +0 -76
  397. package/chunks/earlyInputCapture-KNOYZ4MJ.js +0 -69
  398. package/chunks/environment-ATFTYMLZ.js +0 -88
  399. package/chunks/errors-A54HAADA.js +0 -75
  400. package/chunks/handleAutoUpdate-CEZJAARW.js +0 -65
  401. package/chunks/initializer-BRFJKRRX.js +0 -71
  402. package/chunks/list-KVHF7O4X.js +0 -73
  403. package/chunks/loadedSettingsAdapter-LL5PSFKX.js +0 -68
  404. package/chunks/mcp-3GCXRTOQ.js +0 -68
  405. package/chunks/nonInteractiveCli-PTQI3DV6.js +0 -119
  406. package/chunks/openaiContentGenerator-YE42PKCY.js +0 -56
  407. package/chunks/pidfile-NZYSCFRK.js +0 -73
  408. package/chunks/read-file-33XGP5HF.js +0 -30
  409. package/chunks/ripGrep-QWZLGTAN.js +0 -60
  410. package/chunks/serve-JTMIGANH.js +0 -74
  411. package/chunks/settings-3MI4XXP6.js +0 -118
  412. package/chunks/shell-YJKBE67T.js +0 -68
  413. package/chunks/spawnChannel-UNV6W4DU.js +0 -70
  414. package/chunks/task-create-WCHTB435.js +0 -22
  415. package/chunks/theme-manager-WZ4RRMMB.js +0 -64
  416. package/chunks/total-session-admission-ULXJWXFV.js +0 -72
  417. package/chunks/trustedFolders-LAZZPZ3D.js +0 -79
  418. package/chunks/types-QX5C3CHJ.js +0 -12
  419. package/chunks/updateCheck-KONLM73X.js +0 -65
  420. package/chunks/validateNonInterActiveAuth-MPKSRCXD.js +0 -184
  421. package/chunks/workspace-providers-status-3W7E7DFW.js +0 -71
  422. package/chunks/workspace-registry-SXJZNN3O.js +0 -74
  423. package/chunks/workspace-service-CZRQZ4Q4.js +0 -80
  424. package/chunks/workspace-skills-status-K7WFREPD.js +0 -70
  425. package/node_modules/@qwen-code/audio-capture/dist/index.d.ts +0 -35
  426. package/node_modules/@qwen-code/audio-capture/dist/index.js +0 -48
  427. package/node_modules/@qwen-code/audio-capture/dist/index.js.map +0 -1
  428. package/node_modules/@qwen-code/audio-capture/dist/platform.d.ts +0 -7
  429. package/node_modules/@qwen-code/audio-capture/dist/platform.js +0 -18
  430. package/node_modules/@qwen-code/audio-capture/dist/platform.js.map +0 -1
  431. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/LICENSE +0 -21
  432. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/README.md +0 -58
  433. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/SECURITY.md +0 -5
  434. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/bin.js +0 -84
  435. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/build-test.js +0 -19
  436. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/index.js +0 -6
  437. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/node-gyp-build.js +0 -207
  438. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/optional.js +0 -7
  439. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/package.json +0 -43
  440. package/node_modules/@qwen-code/audio-capture/package.json +0 -30
  441. package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-arm64/@qwen-code+audio-capture.node +0 -0
  442. package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-x64/@qwen-code+audio-capture.node +0 -0
  443. package/node_modules/@qwen-code/audio-capture/prebuilds/linux-arm64/@qwen-code+audio-capture.node +0 -0
  444. package/node_modules/@qwen-code/audio-capture/prebuilds/linux-x64/@qwen-code+audio-capture.node +0 -0
  445. package/node_modules/@qwen-code/audio-capture/prebuilds/win32-x64/@qwen-code+audio-capture.node +0 -0
  446. package/web-shell/assets/channel-wmPUGHtg.js +0 -1
  447. package/web-shell/assets/classDiagram-4FO5ZUOK-BA2ljEjW.js +0 -1
  448. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-BA2ljEjW.js +0 -1
  449. package/web-shell/assets/index-CNwoO1Cn.js +0 -783
  450. package/web-shell/assets/index-CWQ0z6kZ.css +0 -5
  451. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-BbSm_9d0.js +0 -1
@@ -7,16 +7,16 @@ import {
7
7
  sanitizeLogText,
8
8
  sanitizePromptText,
9
9
  sanitizeSenderName
10
- } from "./chunk-IBY3Q3HG.js";
11
- import "./chunk-VRDMOSQS.js";
12
- import "./chunk-K5PGHDBN.js";
10
+ } from "./chunk-AU4R7ACM.js";
11
+ import "./chunk-UDXSXLER.js";
12
+ import "./chunk-H6XPXXMH.js";
13
13
  import {
14
14
  wrapper_default
15
- } from "./chunk-H6BD2ELD.js";
16
- import "./chunk-5IFG2VC4.js";
15
+ } from "./chunk-TYAMGABM.js";
16
+ import "./chunk-FCMNKVXT.js";
17
17
  import {
18
18
  init_esbuild_shims
19
- } from "./chunk-A4BMJM77.js";
19
+ } from "./chunk-5O2XNYP6.js";
20
20
  import {
21
21
  __commonJS,
22
22
  __name,
@@ -1132,8 +1132,10 @@ var OpCode = {
1132
1132
  var Intent = {
1133
1133
  C2C_MESSAGE: 1 << 12,
1134
1134
  // C2C 消息
1135
- GROUP_AT_MESSAGE: 1 << 25
1135
+ GROUP_AT_MESSAGE: 1 << 25,
1136
1136
  // 群聊 @ 消息事件
1137
+ GROUP_MESSAGE: 1 << 26
1138
+ // 群聊全量消息事件 (GROUP_MESSAGE_CREATE)
1137
1139
  };
1138
1140
 
1139
1141
  // packages/channels/qqbot/dist/accounts.js
@@ -1424,6 +1426,17 @@ async function sendQQMessage(base, path, accessToken, body) {
1424
1426
  __name(sendQQMessage, "sendQQMessage");
1425
1427
 
1426
1428
  // packages/channels/qqbot/dist/QQChannel.js
1429
+ var DeliveryError = class extends Error {
1430
+ static {
1431
+ __name(this, "DeliveryError");
1432
+ }
1433
+ code;
1434
+ constructor(code, message) {
1435
+ super(message);
1436
+ this.code = code;
1437
+ this.name = "DeliveryError";
1438
+ }
1439
+ };
1427
1440
  function isValidChatId(id) {
1428
1441
  return /^[A-Za-z0-9_-]+$/.test(id) && id.length <= 128;
1429
1442
  }
@@ -1440,7 +1453,7 @@ var QQChannel = class _QQChannel extends ChannelBase {
1440
1453
  heartbeatInterval = 45e3;
1441
1454
  seq = 0;
1442
1455
  reconnectAttempts = 0;
1443
- maxReconnectAttempts = 20;
1456
+ maxReconnectAttempts;
1444
1457
  /** QQ Bot session_id from READY, used for RESUME on reconnect. */
1445
1458
  sessionId = "";
1446
1459
  /** Whether this connection attempt should try RESUME first. */
@@ -1481,11 +1494,35 @@ var QQChannel = class _QQChannel extends ChannelBase {
1481
1494
  /** Idle-flush timeout: buffer is sent after this many ms of silence. */
1482
1495
  static IDLE_FLUSH_MS = 2e3;
1483
1496
  /** Max consecutive send failures before the stream is abandoned. */
1484
- static MAX_FLUSH_RETRIES = 3;
1497
+ maxFlushRetries;
1485
1498
  /** Retry delay for subsequent attempts (backoff beyond first retry). */
1486
1499
  static IDLE_FLUSH_BACKOFF_MS = 4e3;
1487
1500
  /** Max buffer length before forcing an immediate flush. */
1488
1501
  static MAX_BUFFER_LENGTH = 4096;
1502
+ // ── Group / cron fields ────────────────────────────────────────
1503
+ /** Per-group bot OPENID map for multi-group support. */
1504
+ botOpenIdByGroup = /* @__PURE__ */ new Map();
1505
+ /** Guard: set to true after first READY + session restore completes. */
1506
+ _ready = false;
1507
+ /** Whether this process has never received READY (cold start). */
1508
+ coldStart = true;
1509
+ /** Track per-group active message permission. */
1510
+ groupActiveMsgEnabled = /* @__PURE__ */ new Map();
1511
+ /** Lazy cache for compiled keyword trigger RegExp patterns.
1512
+ * Built lazily on first access; never invalidated — keywordTriggers is not modified at runtime. */
1513
+ _keywordTriggerCache = null;
1514
+ /** Rate-limit timestamps for keyword non-match log entries (chatId → last log ms). */
1515
+ _lastKeywordNoMatchLog = /* @__PURE__ */ new Map();
1516
+ /** Accumulation buffer for cron/non-prompt textChunk events. */
1517
+ cronBuffer = /* @__PURE__ */ new Map();
1518
+ /** Named handler for permanent textChunk listener (cron/non-prompt). */
1519
+ _cronTextHandler = null;
1520
+ /** Gate: depth counter for cron-scheduled message flows. >0 means in-flow.
1521
+ Prevents phantom cronBuffer entries when textChunk fires during normal
1522
+ bridge.prompt() calls (ChannelBase has its own listener there).
1523
+ Using a counter instead of a boolean supports concurrent cron flows. */
1524
+ _inCronFlow = 0;
1525
+ cronTextHandlerAttached = false;
1489
1526
  /** Path to persisted QQ routing state: chatTypeMap, replyMsgId, msgSeqMap. */
1490
1527
  /**
1491
1528
  * Streaming state machine with per-session buffers.
@@ -1509,14 +1546,13 @@ var QQChannel = class _QQChannel extends ChannelBase {
1509
1546
  streamState = /* @__PURE__ */ new Map();
1510
1547
  flushingSessions = /* @__PURE__ */ new Set();
1511
1548
  pendingStreamDelete = /* @__PURE__ */ new Set();
1512
- reconnectId = 0;
1549
+ _reconnectId = 0;
1513
1550
  blockStreaming = false;
1514
1551
  flushedSessions = /* @__PURE__ */ new Set();
1515
1552
  qqStatePath;
1516
1553
  /**
1517
- * Path to the SessionRouter persistence file we back up before shutdown.
1518
- * start.ts passes a shared router; standalone QQChannel instances use a
1519
- * per-channel router file.
1554
+ * Path to the global sessions.json managed by start.ts.
1555
+ * start.ts deletes it on shutdown, so we back it up.
1520
1556
  */
1521
1557
  globalSessionsPath;
1522
1558
  /** Backup of sessions.json so conversations survive daemon restarts. */
@@ -1526,30 +1562,243 @@ var QQChannel = class _QQChannel extends ChannelBase {
1526
1562
  const stateDir = join2(getGlobalQwenDir(), "channels");
1527
1563
  mkdirSync2(stateDir, { recursive: true });
1528
1564
  const sessionsPath = join2(stateDir, `${safeName}-sessions.json`);
1529
- const hasExternalRouter = Boolean(options?.router);
1565
+ const qqCfg = config;
1566
+ if ((qqCfg.groupAllPolicy === "keyword" || qqCfg.groupAllPolicy === "all") && config.sessionScope !== "single") {
1567
+ const originalScope = config.sessionScope;
1568
+ process.stderr.write(`[QQ:${name}] WARNING: groupAllPolicy is '${qqCfg.groupAllPolicy}' but sessionScope is '${originalScope}' (not 'single'). Forcing sessionScope to 'single' to ensure shared group context.
1569
+ `);
1570
+ config = { ...config, sessionScope: "single" };
1571
+ }
1530
1572
  const router = options?.router ?? new SessionRouter(bridge, config.cwd, config.sessionScope, sessionsPath);
1531
1573
  super(name, config, bridge, {
1532
1574
  ...options,
1533
1575
  router,
1534
- registerBridgeEvents: options?.registerBridgeEvents ?? !hasExternalRouter
1576
+ registerBridgeEvents: options?.registerBridgeEvents ?? !options?.router
1535
1577
  });
1536
1578
  this.qqConfig = config;
1579
+ this.maxReconnectAttempts = this.qqConfig.maxReconnectAttempts ?? 20;
1580
+ this.maxFlushRetries = this.qqConfig.maxFlushRetries ?? 3;
1581
+ const raw = this.qqConfig.bufferFlushLength;
1582
+ if (raw !== void 0 && (!Number.isInteger(raw) || raw <= 0 || raw > _QQChannel.MAX_BUFFER_LENGTH)) {
1583
+ process.stderr.write(`[QQ:${this.name}] WARNING: invalid bufferFlushLength=${raw}, using default ${_QQChannel.MAX_BUFFER_LENGTH}
1584
+ `);
1585
+ this.qqConfig.bufferFlushLength = _QQChannel.MAX_BUFFER_LENGTH;
1586
+ }
1537
1587
  this.blockStreaming = this.config.blockStreaming === "on";
1538
1588
  this.qqStatePath = join2(stateDir, `${safeName}-state.json`);
1539
- this.globalSessionsPath = hasExternalRouter ? join2(stateDir, "sessions.json") : sessionsPath;
1589
+ this.globalSessionsPath = options?.router ? join2(stateDir, "sessions.json") : sessionsPath;
1540
1590
  this.sessionsBackupPath = join2(stateDir, `${safeName}-sessions-backup.json`);
1591
+ if (this.qqConfig["cron-msg-experimental"]) {
1592
+ this._cronTextHandler = (sid, t) => this.handleCronTextChunk(sid, t);
1593
+ this.attachCronHandler();
1594
+ }
1595
+ }
1596
+ handleCronTextChunk(sessionId, text) {
1597
+ const wasInCronFlow = this._inCronFlow > 0;
1598
+ setImmediate(() => {
1599
+ if (!this._ready) {
1600
+ process.stderr.write(`[QQ:${this.name}] Cron text chunk dropped (not ready): ${sanitizeLogText(text, 64)} for session ${sanitizeLogText(sessionId, 32)}
1601
+ `);
1602
+ return;
1603
+ }
1604
+ if (!wasInCronFlow)
1605
+ return;
1606
+ if (this.streamState.has(sessionId))
1607
+ return;
1608
+ let entry = this.cronBuffer.get(sessionId);
1609
+ if (!entry) {
1610
+ entry = { buffer: "", timer: null };
1611
+ this.cronBuffer.set(sessionId, entry);
1612
+ }
1613
+ if (entry.timer) {
1614
+ clearTimeout(entry.timer);
1615
+ entry.timer = null;
1616
+ if (entry.pendingRetry) {
1617
+ entry.buffer = entry.pendingRetry + entry.buffer;
1618
+ entry.pendingRetry = "";
1619
+ }
1620
+ }
1621
+ entry.buffer += text;
1622
+ const limit = this.qqConfig.bufferFlushLength ?? _QQChannel.MAX_BUFFER_LENGTH;
1623
+ const delay = entry.buffer.length >= limit ? 0 : 2e3;
1624
+ entry.timer = setTimeout(() => {
1625
+ const toFlush = entry.buffer;
1626
+ entry.buffer = "";
1627
+ entry.timer = null;
1628
+ if (toFlush) {
1629
+ const target = this.router.getTarget(sessionId);
1630
+ if (target) {
1631
+ this.sendMessage(target.chatId, toFlush).then(() => {
1632
+ if (!entry.buffer && this.cronBuffer.get(sessionId) === entry)
1633
+ this.cronBuffer.delete(sessionId);
1634
+ }).catch((err) => {
1635
+ const code = err instanceof DeliveryError ? err.code : null;
1636
+ const codeStr = code ? ` (${code})` : "";
1637
+ process.stderr.write(`[QQ:${this.name}] Cron flush send error${codeStr}: ${sanitizeLogText(err instanceof Error ? err.message : String(err), 200)}
1638
+ `);
1639
+ if (code === "RETRY_EXHAUSTED" || code === "ACTIVE_MSG_DISABLED" || code === "FALLBACK_FAILED") {
1640
+ this.cronBuffer.delete(sessionId);
1641
+ return;
1642
+ }
1643
+ entry.pendingRetry = toFlush;
1644
+ entry.retryCount = 1;
1645
+ if (entry.timer) {
1646
+ clearTimeout(entry.timer);
1647
+ }
1648
+ entry.timer = setTimeout(() => {
1649
+ entry.timer = null;
1650
+ entry.pendingRetry = "";
1651
+ if (this.cronBuffer.get(sessionId) !== entry) {
1652
+ return;
1653
+ }
1654
+ const retryTarget = this.router.getTarget(sessionId);
1655
+ if (!retryTarget) {
1656
+ process.stderr.write(`[QQ:${this.name}] Cron flush dropped after retry: no target for session ${sanitizeLogText(sessionId, 32)}
1657
+ `);
1658
+ this.cronBuffer.delete(sessionId);
1659
+ return;
1660
+ }
1661
+ this.sendMessage(retryTarget.chatId, toFlush).then(() => {
1662
+ entry.pendingRetry = "";
1663
+ if (!entry.buffer && this.cronBuffer.get(sessionId) === entry)
1664
+ this.cronBuffer.delete(sessionId);
1665
+ }).catch((retryErr) => {
1666
+ const retryCode = retryErr instanceof DeliveryError ? retryErr.code : null;
1667
+ const retryCodeStr = retryCode ? ` (${retryCode})` : "";
1668
+ process.stderr.write(`[QQ:${this.name}] Cron flush retry failed${retryCodeStr}: ${sanitizeLogText(retryErr instanceof Error ? retryErr.message : String(retryErr), 200)}
1669
+ `);
1670
+ entry.pendingRetry = "";
1671
+ if (retryCode === "RETRY_EXHAUSTED" || retryCode === "ACTIVE_MSG_DISABLED" || retryCode === "FALLBACK_FAILED") {
1672
+ if (!entry.buffer && this.cronBuffer.get(sessionId) === entry) {
1673
+ this.cronBuffer.delete(sessionId);
1674
+ }
1675
+ return;
1676
+ }
1677
+ entry.retryCount = (entry.retryCount ?? 1) + 1;
1678
+ if (entry.timer) {
1679
+ clearTimeout(entry.timer);
1680
+ }
1681
+ entry.pendingRetry = toFlush;
1682
+ entry.timer = setTimeout(() => {
1683
+ entry.timer = null;
1684
+ entry.pendingRetry = "";
1685
+ if (this.cronBuffer.get(sessionId) !== entry) {
1686
+ return;
1687
+ }
1688
+ const retryTarget2 = this.router.getTarget(sessionId);
1689
+ if (!retryTarget2) {
1690
+ process.stderr.write(`[QQ:${this.name}] Cron flush dropped after retry: no target for session ${sanitizeLogText(sessionId, 32)}
1691
+ `);
1692
+ this.cronBuffer.delete(sessionId);
1693
+ return;
1694
+ }
1695
+ this.sendMessage(retryTarget2.chatId, toFlush).then(() => {
1696
+ entry.pendingRetry = "";
1697
+ if (!entry.buffer && this.cronBuffer.get(sessionId) === entry)
1698
+ this.cronBuffer.delete(sessionId);
1699
+ }).catch((err2) => {
1700
+ const code2 = err2 instanceof DeliveryError ? err2.code : null;
1701
+ const code2Str = code2 ? ` (${code2})` : "";
1702
+ process.stderr.write(`[QQ:${this.name}] Cron flush re-retry failed${code2Str}: ${sanitizeLogText(err2 instanceof Error ? err2.message : String(err2), 200)}, toFlush=${toFlush.length}, session=${sanitizeLogText(sessionId, 32)}
1703
+ `);
1704
+ entry.pendingRetry = "";
1705
+ if (code2 === "RETRY_EXHAUSTED" || code2 === "ACTIVE_MSG_DISABLED" || code2 === "FALLBACK_FAILED") {
1706
+ this.cronBuffer.delete(sessionId);
1707
+ return;
1708
+ }
1709
+ process.stderr.write(`[QQ:${this.name}] Cron flush retries exhausted, dropped ${toFlush.length} chars for session ${sanitizeLogText(sessionId, 32)}
1710
+ `);
1711
+ this.cronBuffer.delete(sessionId);
1712
+ });
1713
+ }, entry.retryCount === 2 ? 1e4 : 5e3);
1714
+ entry.timer.unref();
1715
+ });
1716
+ }, 5e3);
1717
+ entry.timer.unref();
1718
+ });
1719
+ return;
1720
+ }
1721
+ }
1722
+ process.stderr.write(`[QQ:${this.name}] Cron flush dropped: no target for session ${sanitizeLogText(sessionId, 32)}, lost ${toFlush.length} chars
1723
+ `);
1724
+ this.cronBuffer.delete(sessionId);
1725
+ }, delay).unref();
1726
+ });
1727
+ }
1728
+ /**
1729
+ * Public gate for external cron/scheduler integration.
1730
+ * Wraps a cron message flow to activate `_inCronFlow` so that
1731
+ * `textChunk` events are captured into the cron accumulation buffer.
1732
+ * Uses a depth counter (not boolean) so concurrent cron flows
1733
+ * don't stomp each other's `_inCronFlow` state.
1734
+ * Always decrements `_inCronFlow` in a `finally` block.
1735
+ */
1736
+ async runCronFlow(fn) {
1737
+ this._inCronFlow++;
1738
+ try {
1739
+ await fn();
1740
+ } finally {
1741
+ if (this._inCronFlow > 0)
1742
+ this._inCronFlow--;
1743
+ }
1744
+ }
1745
+ /**
1746
+ * Override setBridge to re-attach the permanent `_cronTextHandler`
1747
+ * after bridge crash-recovery.
1748
+ */
1749
+ setBridge(bridge) {
1750
+ this.detachCronHandler();
1751
+ super.setBridge(bridge);
1752
+ this.attachCronHandler();
1541
1753
  }
1542
1754
  // ── ChannelBase interface ──────────────────────────────────────
1543
1755
  async connect() {
1756
+ if (this.reconnectTimer) {
1757
+ clearTimeout(this.reconnectTimer);
1758
+ this.reconnectTimer = null;
1759
+ }
1760
+ this._reconnectId++;
1544
1761
  this.disposed = false;
1545
- this.reconnectId++;
1762
+ this.reconnectAttempts = 0;
1763
+ this.serverRequestedReconnect = false;
1764
+ this.tryResume = false;
1546
1765
  if (!this.config.instructions) {
1547
- this.config.instructions = [
1766
+ const parts = [
1548
1767
  "## QQ Bot Channel",
1549
1768
  "",
1550
1769
  "\u4F60\u662F\u901A\u8FC7 QQ Bot \u4E0E\u7528\u6237\u5BF9\u8BDD\u7684 AI \u52A9\u624B\u3002",
1551
- "\u56DE\u590D\u63A7\u5236\u5728 2000 \u5B57\u7B26\u4EE5\u5185\uFF0C\u652F\u6301 Markdown \u683C\u5F0F\u3002"
1552
- ].join("\n");
1770
+ "\u652F\u6301 Markdown \u683C\u5F0F\uFF0C\u56DE\u590D\u81EA\u7136\u6D41\u7545\u5373\u53EF\u3002",
1771
+ "\u6D88\u606F\u524D\u7F00 [atMention=true] \u8868\u793A\u8BE5\u6D88\u606F @\u4E86\u4F60\uFF0C[atMention=false] \u8868\u793A\u672A @\u4F60\u3002",
1772
+ "\u4E0D\u60F3\u56DE\u590D\u65F6\u53EA\u8F93\u51FA <noreply> \u5373\u53EF\uFF0C\u6D88\u606F\u4E0D\u4F1A\u53D1\u51FA\u3002",
1773
+ "",
1774
+ "\u4EE5\u4E0B\u89C4\u5219\u4EC5\u9002\u7528\u4E8E\u7FA4\u804A\u6D88\u606F\u3002C2C \u79C1\u804A\u4E2D\u8BF7\u59CB\u7EC8\u6B63\u5E38\u56DE\u590D\u3002",
1775
+ "## \u7FA4\u804A\u5524\u9192\u4E0E\u9759\u9ED8\u89C4\u5219",
1776
+ "",
1777
+ "### \u5F53 [atMention=false] \u2014 \u672A @\u4F60",
1778
+ "\u7531\u4F60\u81EA\u4E3B\u5224\u65AD\u5F53\u524D\u804A\u5929\u6C1B\u56F4\u662F\u5426\u9002\u5408\u63D2\u5634\uFF1A",
1779
+ "- \u95F2\u804A/\u8C03\u4F83/\u73A9\u6897 \u2192 \u53EF\u4EE5\u63A5\u832C\uFF0C\u98CE\u8DA3\u5373\u53EF",
1780
+ "- \u4E25\u8083\u8BA8\u8BBA/\u4E8B\u52A1\u534F\u5546 \u2192 \u4FDD\u6301\u6C89\u9ED8",
1781
+ "- \u4E0D\u786E\u5B9A \u2192 \u6C89\u9ED8",
1782
+ "",
1783
+ "### \u5F53 [atMention=true] \u2014 @\u4E86\u4F60",
1784
+ "\u5148\u53BB\u6389 @\u6807\u7B7E\u548C\u4F60\u7684\u540D\u5B57\uFF0C\u5269\u4E0B\u7684\u5185\u5BB9\u662F\u5BF9\u4F60\u7684\u63D0\u95EE\u6216\u6307\u4EE4\u5417\uFF1F",
1785
+ "",
1786
+ "\u4EE5\u4E0B\u573A\u666F\u5373\u4F7F @\u4E86\u4F60\u4E5F\u5FC5\u987B\u6C89\u9ED8\uFF1A",
1787
+ '1. \u7EAF\u63D0\u53CA/\u9648\u8FF0 \u2014 "QwenCode \u597D\u50CF\u53D8\u806A\u660E\u4E86"',
1788
+ '2. \u8F6C\u8FF0/\u5F15\u7528 \u2014 "\u521A\u624D QwenCode \u7ED9\u7684\u65B9\u6848\u53EF\u4EE5"',
1789
+ '3. \u95F4\u63A5\u547C\u53EB \u2014 "@\u674E\u56DB \u4F60\u8BA9 QwenCode \u67E5\u4E0B"',
1790
+ '4. \u8C03\u4F83/\u8BD5\u63A2 \u2014 "\u8FD9\u4E8B QwenCode \u80AF\u5B9A\u4E0D\u77E5\u9053"',
1791
+ "",
1792
+ "### \u56DE\u590D\u51C6\u5219",
1793
+ '- \u88AB\u5524\u9192\u540E\u76F4\u63A5\u505A\u4E8B\uFF0C\u7981\u6B62"\u6211\u5728"\u7B49\u5360\u4F4D\u56DE\u590D',
1794
+ "- \u4E00\u6761\u6D88\u606F @\u591A\u4EBA\u65F6\uFF0C\u53EA\u6709\u660E\u786E\u6307\u6D3E\u7ED9\u4F60\u624D\u63A5",
1795
+ "- \u4E0D\u786E\u8BA4\u65F6\u5148\u6C89\u9ED8",
1796
+ "- \u5B8C\u6210\u5BF9\u8BDD\u540E\u7ACB\u523B\u56DE\u5F52\u9759\u9ED8"
1797
+ ];
1798
+ if (this.qqConfig.allowMention !== false) {
1799
+ parts.push("", "## @\u63D0\u53CA\u683C\u5F0F", "", "\u6D88\u606F\u5185\u5BB9\u4E2D\u7684 <@OPENID> \u6807\u7B7E\u4EE3\u8868\u7FA4\u6210\u5458\u7684 QQ \u6807\u8BC6\u3002", "\u5F53\u5176\u4ED6\u7FA4\u6210\u5458 @\u4F60\uFF08\u673A\u5668\u4EBA\uFF09\u65F6\uFF0C\u6D88\u606F\u5185\u5BB9\u4E2D\u4F1A\u51FA\u73B0 <@\u4F60\u7684BotOPENID> \u6807\u7B7E\uFF0C\u8FD9\u4EE3\u8868\u8BE5\u6D88\u606F\u662F @\u7ED9\u4F60\u7684\u3002\u673A\u5668\u4EBA\u81EA\u5DF1\u7684 OPENID \u5C06\u5728\u8FDE\u63A5\u5EFA\u7ACB\u540E\u544A\u77E5\u3002", "\u4F60\u53EF\u4EE5\u5728\u56DE\u590D\u4E2D\u4F7F\u7528 <@OPENID> \u683C\u5F0F\u6765 @\u63D0\u53CA\u7279\u5B9A\u7684\u7FA4\u6210\u5458\u3002", '\u4F8B\u5982\uFF1A\u56DE\u590D "<@ABC123DEF456> \u4F60\u597D" \u4F1A\u5728\u7FA4\u91CC @\u8BE5\u6210\u5458\u3002');
1800
+ }
1801
+ this.config.instructions = parts.join("\n");
1553
1802
  }
1554
1803
  for (let attempt = 0; attempt < 3; attempt++) {
1555
1804
  try {
@@ -1592,24 +1841,33 @@ var QQChannel = class _QQChannel extends ChannelBase {
1592
1841
  this.replyMsgId.delete(chatId);
1593
1842
  this.saveQQState();
1594
1843
  }
1844
+ if (!msgId && this.groupActiveMsgEnabled.get(chatId) === false) {
1845
+ const cronCtx = this._inCronFlow ? " (cron flow discarded)" : "";
1846
+ process.stderr.write(`[QQ:${this.name}] sendMessage blocked: active messages disabled for ${sanitizeLogText(chatId, 64)}${cronCtx}
1847
+ `);
1848
+ throw new DeliveryError("ACTIVE_MSG_DISABLED", `Active messages disabled for ${sanitizeLogText(chatId, 64)}`);
1849
+ }
1595
1850
  let nextSeq = 0;
1596
1851
  let rollbackApplied = false;
1597
1852
  try {
1598
- const body = {
1853
+ const passiveBody = {
1599
1854
  msg_type: 2,
1600
1855
  markdown: { content: text }
1601
1856
  };
1602
1857
  nextSeq = msgId ? (this.msgSeqMap.get(msgId) ?? 0) + 1 : 0;
1603
1858
  if (msgId) {
1604
1859
  this.msgSeqMap.set(msgId, nextSeq);
1605
- body["msg_id"] = msgId;
1606
- body["msg_seq"] = nextSeq;
1860
+ passiveBody["msg_id"] = msgId;
1861
+ passiveBody["msg_seq"] = nextSeq;
1607
1862
  }
1608
- const resp = await sendQQMessage(route.base, route.path, this.accessToken, body);
1863
+ const resp = await sendQQMessage(route.base, route.path, this.accessToken, passiveBody);
1609
1864
  if (!resp.ok) {
1610
- const errBody = await resp.text().catch(() => "");
1611
- process.stderr.write(`[QQ:${this.name}] Send failed (HTTP ${resp.status}: ${sanitizeLogText(errBody, 200)})
1865
+ const errBody = sanitizeLogText(await resp.text().catch(() => ""), 200);
1866
+ if (resp.status !== 429) {
1867
+ process.stderr.write(`[QQ:${this.name}] Send failed (HTTP ${resp.status}: ${errBody})
1868
+
1612
1869
  `);
1870
+ }
1613
1871
  if (resp.status === 429) {
1614
1872
  process.stderr.write(`[QQ:${this.name}] MESSAGE DROPPED: rate-limited (429) on markdown attempt for ${sanitizeLogText(chatId, 64)}
1615
1873
  `);
@@ -1617,41 +1875,61 @@ var QQChannel = class _QQChannel extends ChannelBase {
1617
1875
  this.msgSeqMap.set(msgId, nextSeq - 1);
1618
1876
  this.saveQQState();
1619
1877
  }
1620
- return;
1878
+ throw new DeliveryError("RATE_LIMITED", `Message blocked by rate limit for ${sanitizeLogText(chatId, 64)}`);
1621
1879
  }
1622
1880
  if (msgId) {
1623
1881
  this.msgSeqMap.set(msgId, nextSeq - 1);
1624
1882
  rollbackApplied = true;
1625
- const activeBody = {
1626
- content: text,
1627
- msg_type: 0,
1628
- msg_id: msgId,
1629
- msg_seq: nextSeq
1883
+ if (this.groupActiveMsgEnabled.get(chatId) === false) {
1884
+ process.stderr.write(`[QQ:${this.name}] MESSAGE DROPPED: active messages disabled for ${sanitizeLogText(chatId, 64)}, cannot retry
1885
+ `);
1886
+ this.saveQQState();
1887
+ throw new DeliveryError("ACTIVE_MSG_DISABLED", `Active messages disabled for ${sanitizeLogText(chatId, 64)}`);
1888
+ }
1889
+ const activeMdBody = {
1890
+ msg_type: 2,
1891
+ markdown: { content: text }
1630
1892
  };
1631
- const activeResp = await sendQQMessage(route.base, route.path, this.accessToken, activeBody);
1632
- if (activeResp.ok) {
1633
- process.stderr.write(`[QQ:${this.name}] Active retry succeeded for ${sanitizeLogText(chatId, 64)}
1893
+ const activeMdResp = await sendQQMessage(route.base, route.path, this.accessToken, activeMdBody);
1894
+ if (activeMdResp.ok) {
1895
+ process.stderr.write(`[QQ:${this.name}] Active markdown retry succeeded for ${sanitizeLogText(chatId, 64)}
1634
1896
  `);
1635
- const current = this.replyMsgId.get(chatId);
1636
- if (current?.msgId === msgId) {
1637
- this.msgSeqMap.set(msgId, nextSeq);
1638
- }
1639
1897
  this.saveQQState();
1640
- await activeResp.text().catch(() => "");
1898
+ await activeMdResp.text().catch(() => "");
1641
1899
  return;
1642
1900
  }
1643
- process.stderr.write(`[QQ:${this.name}] Active retry also failed (HTTP ${activeResp.status}: ${sanitizeLogText(await activeResp.text().catch(() => ""), 200)})
1901
+ const mdErrBody = sanitizeLogText(await activeMdResp.text().catch(() => ""), 200);
1902
+ if (activeMdResp.status === 429) {
1903
+ process.stderr.write(`[QQ:${this.name}] MESSAGE DROPPED: rate-limited (429) on active markdown retry for ${sanitizeLogText(chatId, 64)}
1644
1904
  `);
1645
- if (activeResp.status === 429) {
1646
- process.stderr.write(`[QQ:${this.name}] MESSAGE DROPPED: active retry rate-limited (HTTP 429) for ${sanitizeLogText(chatId, 64)}
1905
+ this.saveQQState();
1906
+ throw new DeliveryError("RATE_LIMITED", `Message blocked by rate limit for ${sanitizeLogText(chatId, 64)}`);
1907
+ }
1908
+ process.stderr.write(`[QQ:${this.name}] Active markdown retry failed (HTTP ${activeMdResp.status}: ${mdErrBody}) for ${sanitizeLogText(chatId, 64)}
1909
+ `);
1910
+ const activeTextBody = {
1911
+ content: text,
1912
+ msg_type: 0
1913
+ };
1914
+ const activeTextResp = await sendQQMessage(route.base, route.path, this.accessToken, activeTextBody);
1915
+ if (activeTextResp.ok) {
1916
+ process.stderr.write(`[QQ:${this.name}] Active text fallback succeeded for ${sanitizeLogText(chatId, 64)}
1647
1917
  `);
1648
1918
  this.saveQQState();
1919
+ await activeTextResp.text().catch(() => "");
1649
1920
  return;
1650
1921
  }
1651
- process.stderr.write(`[QQ:${this.name}] MESSAGE DROPPED: both passive and active send failed for ${sanitizeLogText(chatId, 64)}
1922
+ const textErrBody = sanitizeLogText(await activeTextResp.text().catch(() => ""), 200);
1923
+ if (activeTextResp.status === 429) {
1924
+ process.stderr.write(`[QQ:${this.name}] MESSAGE DROPPED: rate-limited (429) on active text fallback for ${sanitizeLogText(chatId, 64)}
1925
+ `);
1926
+ this.saveQQState();
1927
+ throw new DeliveryError("RATE_LIMITED", `Message blocked by rate limit for ${sanitizeLogText(chatId, 64)}`);
1928
+ }
1929
+ process.stderr.write(`[QQ:${this.name}] MESSAGE DROPPED: active text fallback failed (HTTP ${activeTextResp.status}: ${textErrBody}) for ${sanitizeLogText(chatId, 64)}
1652
1930
  `);
1653
1931
  this.saveQQState();
1654
- return;
1932
+ throw new DeliveryError("FALLBACK_FAILED", `All delivery attempts exhausted for ${sanitizeLogText(chatId, 64)}`);
1655
1933
  }
1656
1934
  const plainBody = {
1657
1935
  content: text,
@@ -1663,11 +1941,11 @@ var QQChannel = class _QQChannel extends ChannelBase {
1663
1941
  if (fallbackRes.status === 429) {
1664
1942
  process.stderr.write(`[QQ:${this.name}] MESSAGE DROPPED: rate-limited (429) on plain-text fallback for ${sanitizeLogText(chatId, 64)}
1665
1943
  `);
1666
- return;
1944
+ throw new DeliveryError("RATE_LIMITED", `Message blocked by rate limit for ${sanitizeLogText(chatId, 64)}`);
1667
1945
  }
1668
1946
  process.stderr.write(`[QQ:${this.name}] MESSAGE DROPPED: plain-text fallback failed (HTTP ${fallbackRes.status}: ${sanitizeLogText(fbErrBody, 200)}) for ${sanitizeLogText(chatId, 64)}
1669
1947
  `);
1670
- return;
1948
+ throw new DeliveryError("FALLBACK_FAILED", `Plain-text fallback delivery failed for ${sanitizeLogText(chatId, 64)}`);
1671
1949
  }
1672
1950
  process.stderr.write(`[QQ:${this.name}] Plain-text fallback succeeded for ${sanitizeLogText(chatId, 64)}
1673
1951
  `);
@@ -1683,8 +1961,10 @@ var QQChannel = class _QQChannel extends ChannelBase {
1683
1961
  }
1684
1962
  if (msgId)
1685
1963
  this.saveQQState();
1686
- process.stderr.write(`[QQ:${this.name}] Send error: ${sanitizeLogText(e instanceof Error ? e.message : String(e), 200)}
1964
+ if (!(e instanceof DeliveryError)) {
1965
+ process.stderr.write(`[QQ:${this.name}] Send error: ${sanitizeLogText(e instanceof Error ? e.message : String(e), 200)}
1687
1966
  `);
1967
+ }
1688
1968
  throw e;
1689
1969
  }
1690
1970
  }
@@ -1693,23 +1973,44 @@ var QQChannel = class _QQChannel extends ChannelBase {
1693
1973
  * sandbox detection, and C2C/group path selection. Returns null if any guard fails.
1694
1974
  */
1695
1975
  async resolveRoute(chatId) {
1696
- if (this.disposed)
1976
+ if (this.disposed) {
1977
+ process.stderr.write(`[QQ:${this.name}] resolveRoute: channel disposed, dropping message to ${sanitizeLogText(chatId, 64)}
1978
+ `);
1697
1979
  return null;
1980
+ }
1698
1981
  if (Date.now() >= this.tokenExpiresAt) {
1699
1982
  try {
1700
1983
  await this.fetchToken();
1701
- } catch {
1984
+ } catch (_e) {
1985
+ process.stderr.write(`[QQ:${this.name}] resolveRoute: token refresh failed (${sanitizeLogText(_e instanceof Error ? _e.message : String(_e), 120)}), dropping message to ${sanitizeLogText(chatId, 64)}
1986
+ `);
1702
1987
  return null;
1703
1988
  }
1704
1989
  }
1705
- if (!this.accessToken || !isValidChatId(chatId))
1990
+ if (!this.accessToken) {
1991
+ process.stderr.write(`[QQ:${this.name}] resolveRoute: accessToken is empty after fetchToken
1992
+ `);
1993
+ return null;
1994
+ }
1995
+ if (!isValidChatId(chatId)) {
1996
+ process.stderr.write(`[QQ:${this.name}] resolveRoute: invalid chatId rejected (length=${chatId.length})
1997
+ `);
1706
1998
  return null;
1999
+ }
1707
2000
  const base = getApiBase(Boolean(this.qqConfig.sandbox));
1708
- const path = this.chatTypeMap.get(chatId) === "group" ? `/v2/groups/${chatId}/messages` : `/v2/users/${chatId}/messages`;
2001
+ const routeType = this.chatTypeMap.get(chatId) || this.qqConfig.chatTypes?.[chatId];
2002
+ if (routeType !== "group" && routeType !== "c2c") {
2003
+ process.stderr.write(`[QQ:${this.name}] resolveRoute: no chat type for ${sanitizeLogText(chatId, 64)}, dropping message
2004
+ `);
2005
+ return null;
2006
+ }
2007
+ const path = routeType === "group" ? `/v2/groups/${chatId}/messages` : `/v2/users/${chatId}/messages`;
1709
2008
  return { base, path };
1710
2009
  }
1711
2010
  disconnect() {
2011
+ this._reconnectId++;
1712
2012
  this.disposed = true;
2013
+ this._ready = false;
1713
2014
  this.stopHeartbeat();
1714
2015
  this.stopTokenRefresh();
1715
2016
  this.stopReplyMsgIdCleanup();
@@ -1725,6 +2026,19 @@ var QQChannel = class _QQChannel extends ChannelBase {
1725
2026
  process.off("beforeExit", this.beforeExitHook);
1726
2027
  this.beforeExitHook = null;
1727
2028
  }
2029
+ let droppedCount = 0;
2030
+ for (const [, entry] of this.cronBuffer) {
2031
+ if (entry.timer)
2032
+ clearTimeout(entry.timer);
2033
+ if (entry.buffer)
2034
+ droppedCount++;
2035
+ }
2036
+ if (droppedCount > 0) {
2037
+ process.stderr.write(`[QQ:${this.name}] Disconnect: discarding ${droppedCount} buffered cron message(s)
2038
+ `);
2039
+ }
2040
+ this.cronBuffer.clear();
2041
+ this._lastKeywordNoMatchLog.clear();
1728
2042
  this.flushQQState();
1729
2043
  this.backupGlobalSessions();
1730
2044
  if (this.readyTimeout) {
@@ -1739,9 +2053,19 @@ var QQChannel = class _QQChannel extends ChannelBase {
1739
2053
  this.connectReject(new Error("Channel disconnected"));
1740
2054
  this.connectReject = null;
1741
2055
  }
2056
+ this.detachCronHandler();
1742
2057
  this.chatTypeMap.clear();
1743
2058
  this.replyMsgId.clear();
1744
2059
  this.msgSeqMap.clear();
2060
+ this.botOpenIdByGroup.clear();
2061
+ this.groupActiveMsgEnabled.clear();
2062
+ this.seenMessages.clear();
2063
+ this.coldStart = true;
2064
+ if (this._inCronFlow > 0) {
2065
+ process.stderr.write(`[QQ:${this.name}] resetRoutingState: orphaned cron flow (depth=${this._inCronFlow}) during disconnect
2066
+ `);
2067
+ }
2068
+ this._inCronFlow = 0;
1745
2069
  for (const [, state] of this.streamState) {
1746
2070
  if (state.timer)
1747
2071
  clearTimeout(state.timer);
@@ -1750,8 +2074,6 @@ var QQChannel = class _QQChannel extends ChannelBase {
1750
2074
  this.flushingSessions.clear();
1751
2075
  this.pendingStreamDelete.clear();
1752
2076
  this.flushedSessions.clear();
1753
- this.seenMessages.clear();
1754
- this.reconnectId++;
1755
2077
  }
1756
2078
  /**
1757
2079
  * QQ Bot API V2 does not provide a typing indicator endpoint.
@@ -1776,21 +2098,29 @@ var QQChannel = class _QQChannel extends ChannelBase {
1776
2098
  clearTimeout(state.timer);
1777
2099
  state.timer = null;
1778
2100
  }
1779
- if (state.buffer.length >= _QQChannel.MAX_BUFFER_LENGTH) {
2101
+ if (state.buffer.length >= (this.qqConfig.bufferFlushLength ?? _QQChannel.MAX_BUFFER_LENGTH)) {
1780
2102
  const buf = state.buffer;
1781
2103
  state.buffer = "";
2104
+ if (this.flushingSessions.has(sessionId)) {
2105
+ state.buffer = buf + (state.buffer || "");
2106
+ state.timer = setTimeout(() => {
2107
+ this.idleFlush(sessionId, this._reconnectId);
2108
+ }, _QQChannel.IDLE_FLUSH_MS);
2109
+ state.timer.unref?.();
2110
+ return;
2111
+ }
1782
2112
  this.flushAndTrack(sessionId, buf, state, "idleFlush");
1783
2113
  return;
1784
2114
  }
1785
2115
  }
1786
- const reconnectId = this.reconnectId;
2116
+ const reconnectId = this._reconnectId;
1787
2117
  state.timer = setTimeout(() => {
1788
2118
  this.idleFlush(sessionId, reconnectId);
1789
2119
  }, _QQChannel.IDLE_FLUSH_MS);
1790
2120
  state.timer.unref?.();
1791
2121
  }
1792
2122
  idleFlush(sessionId, reconnectId) {
1793
- if (this.reconnectId !== reconnectId) {
2123
+ if (this._reconnectId !== reconnectId) {
1794
2124
  process.stderr.write(`[QQ:${this.name}] idleFlush discarded (reconnect) session=${sanitizeLogText(sessionId, 32)}
1795
2125
  `);
1796
2126
  return;
@@ -1800,7 +2130,7 @@ var QQChannel = class _QQChannel extends ChannelBase {
1800
2130
  return;
1801
2131
  if (this.flushingSessions.has(sessionId)) {
1802
2132
  if (!state.timer) {
1803
- const retryReconnectId = this.reconnectId;
2133
+ const retryReconnectId = this._reconnectId;
1804
2134
  state.timer = setTimeout(() => {
1805
2135
  this.idleFlush(sessionId, retryReconnectId);
1806
2136
  }, _QQChannel.IDLE_FLUSH_MS);
@@ -1830,7 +2160,7 @@ var QQChannel = class _QQChannel extends ChannelBase {
1830
2160
  this.pendingStreamDelete.delete(sessionId);
1831
2161
  const s2 = this.streamState.get(sessionId);
1832
2162
  if (s2 === state && s2.buffer) {
1833
- this.idleFlush(sessionId, this.reconnectId);
2163
+ this.idleFlush(sessionId, this._reconnectId);
1834
2164
  }
1835
2165
  }
1836
2166
  const s = this.streamState.get(sessionId);
@@ -1838,6 +2168,19 @@ var QQChannel = class _QQChannel extends ChannelBase {
1838
2168
  this.streamState.delete(sessionId);
1839
2169
  }
1840
2170
  }).catch((e) => {
2171
+ if (e instanceof DeliveryError && (e.code === "RETRY_EXHAUSTED" || e.code === "ACTIVE_MSG_DISABLED" || e.code === "FALLBACK_FAILED")) {
2172
+ process.stderr.write(`[QQ:${this.name}] ${logLabel} delivery failed (${e.code}): ${sanitizeLogText(e.message, 200)}, dropping ${buffer.length} chars
2173
+ `);
2174
+ const current = this.streamState.get(sessionId);
2175
+ if (current === state) {
2176
+ this.streamState.delete(sessionId);
2177
+ }
2178
+ if (this.pendingStreamDelete.has(sessionId)) {
2179
+ this.pendingStreamDelete.delete(sessionId);
2180
+ this.flushedSessions.delete(sessionId);
2181
+ }
2182
+ return;
2183
+ }
1841
2184
  process.stderr.write(`[QQ:${this.name}] ${logLabel} send failed: ${sanitizeLogText(e instanceof Error ? e.message : String(e), 200)}
1842
2185
  `);
1843
2186
  if (this.pendingStreamDelete.has(sessionId)) {
@@ -1846,8 +2189,8 @@ var QQChannel = class _QQChannel extends ChannelBase {
1846
2189
  if (current === state) {
1847
2190
  current.buffer = buffer;
1848
2191
  current.retryCount++;
1849
- if (current.retryCount < _QQChannel.MAX_FLUSH_RETRIES) {
1850
- const reconnectId = this.reconnectId;
2192
+ if (this.maxFlushRetries <= 0 || current.retryCount < this.maxFlushRetries) {
2193
+ const reconnectId = this._reconnectId;
1851
2194
  const delay = current.retryCount > 1 ? _QQChannel.IDLE_FLUSH_BACKOFF_MS : _QQChannel.IDLE_FLUSH_MS;
1852
2195
  current.timer = setTimeout(() => {
1853
2196
  this.idleFlush(sessionId, reconnectId);
@@ -1864,13 +2207,21 @@ var QQChannel = class _QQChannel extends ChannelBase {
1864
2207
  const current = this.streamState.get(sessionId);
1865
2208
  if (current === state) {
1866
2209
  current.buffer = buffer + (current.buffer || "");
1867
- if (current.buffer.length >= _QQChannel.MAX_BUFFER_LENGTH) {
1868
- this.idleFlush(sessionId, this.reconnectId);
2210
+ if (current.buffer.length >= (this.qqConfig.bufferFlushLength ?? _QQChannel.MAX_BUFFER_LENGTH)) {
2211
+ current.retryCount++;
2212
+ if (this.maxFlushRetries > 0 && current.retryCount >= this.maxFlushRetries) {
2213
+ this.streamState.delete(sessionId);
2214
+ this.flushedSessions.delete(sessionId);
2215
+ process.stderr.write(`[QQ:${this.name}] ${logLabel} retries exhausted (buffer exceeds limit) for ${sanitizeLogText(sessionId, 64)}
2216
+ `);
2217
+ } else {
2218
+ this.idleFlush(sessionId, this._reconnectId);
2219
+ }
1869
2220
  } else {
1870
2221
  current.retryCount++;
1871
- if (current.retryCount < _QQChannel.MAX_FLUSH_RETRIES) {
2222
+ if (this.maxFlushRetries <= 0 || current.retryCount < this.maxFlushRetries) {
1872
2223
  if (!current.timer) {
1873
- const reconnectId = this.reconnectId;
2224
+ const reconnectId = this._reconnectId;
1874
2225
  const delay = current.retryCount > 1 ? _QQChannel.IDLE_FLUSH_BACKOFF_MS : _QQChannel.IDLE_FLUSH_MS;
1875
2226
  current.timer = setTimeout(() => {
1876
2227
  this.idleFlush(sessionId, reconnectId);
@@ -1907,6 +2258,16 @@ var QQChannel = class _QQChannel extends ChannelBase {
1907
2258
  state.buffer = "";
1908
2259
  this.flushAndTrack(event.sessionId, buffer, state, "toolCallFlush");
1909
2260
  }
2261
+ onResponseBoundary(_chatId, sessionId) {
2262
+ const state = this.streamState.get(sessionId);
2263
+ if (state?.timer) {
2264
+ clearTimeout(state.timer);
2265
+ }
2266
+ this.streamState.delete(sessionId);
2267
+ this.flushingSessions.delete(sessionId);
2268
+ this.pendingStreamDelete.delete(sessionId);
2269
+ this.flushedSessions.delete(sessionId);
2270
+ }
1910
2271
  async onResponseComplete(chatId, fullText, sessionId) {
1911
2272
  const state = this.streamState.get(sessionId);
1912
2273
  if (state?.timer) {
@@ -1939,22 +2300,27 @@ var QQChannel = class _QQChannel extends ChannelBase {
1939
2300
  super.onSessionDied(sessionId);
1940
2301
  }
1941
2302
  // ── State Persistence (cross-server context continuation) ──────
1942
- /** Debounced state persistence to avoid blocking event loop. */
2303
+ serializeQQState() {
2304
+ return JSON.stringify({
2305
+ chatTypeMap: Array.from(this.chatTypeMap.entries()),
2306
+ replyMsgId: Array.from(this.replyMsgId.entries()),
2307
+ msgSeqMap: Array.from(this.msgSeqMap.entries()),
2308
+ groupActiveMsgEnabled: Array.from(this.groupActiveMsgEnabled.entries()),
2309
+ botOpenIdByGroup: Array.from(this.botOpenIdByGroup.entries())
2310
+ });
2311
+ }
2312
+ /** Debounced state persistence with atomic write. */
1943
2313
  saveQQState() {
1944
2314
  if (this.disposed)
1945
2315
  return;
1946
2316
  if (this.saveTimer)
1947
2317
  clearTimeout(this.saveTimer);
1948
- const tmpPath = this.qqStatePath + ".tmp";
1949
2318
  this.saveTimer = setTimeout(() => {
1950
2319
  if (this.disposed)
1951
2320
  return;
2321
+ const tmpPath = this.qqStatePath + ".tmp";
1952
2322
  try {
1953
- writeFileSync2(tmpPath, JSON.stringify({
1954
- chatTypeMap: Array.from(this.chatTypeMap.entries()),
1955
- replyMsgId: Array.from(this.replyMsgId.entries()),
1956
- msgSeqMap: Array.from(this.msgSeqMap.entries())
1957
- }), { mode: 384 });
2323
+ writeFileSync2(tmpPath, this.serializeQQState(), { mode: 384 });
1958
2324
  renameSync(tmpPath, this.qqStatePath);
1959
2325
  } catch (e) {
1960
2326
  try {
@@ -1962,11 +2328,43 @@ var QQChannel = class _QQChannel extends ChannelBase {
1962
2328
  } catch {
1963
2329
  }
1964
2330
  process.stderr.write(`[QQ:${this.name}] saveQQState write failed: ${sanitizeLogText(e instanceof Error ? e.message : String(e), 200)}
2331
+
1965
2332
  `);
1966
2333
  }
1967
2334
  }, 500);
1968
2335
  this.saveTimer.unref();
1969
2336
  }
2337
+ /**
2338
+ * Attach the permanent textChunk handler for cron/non-prompt messages
2339
+ * to the current bridge. No-op if already attached or if cron is disabled.
2340
+ */
2341
+ attachCronHandler() {
2342
+ if (this.qqConfig["cron-msg-experimental"] && this._cronTextHandler && !this.cronTextHandlerAttached) {
2343
+ this.bridge.on?.("textChunk", this._cronTextHandler);
2344
+ this.cronTextHandlerAttached = true;
2345
+ }
2346
+ }
2347
+ _checkGroupAllPolicyRequireMention() {
2348
+ const policy = this.qqConfig.groupAllPolicy;
2349
+ if (policy !== "keyword" && policy !== "all")
2350
+ return;
2351
+ const groups = this.config.groups;
2352
+ const anyFalse = groups && Object.values(groups).some((g2) => g2.requireMention === false);
2353
+ if (!anyFalse) {
2354
+ process.stderr.write(`[QQ:${this.name}] WARNING: groupAllPolicy is '${policy}' but requireMention is true (default). Non-@-bot messages passing keyword/all policy will be silently dropped by GroupGate. Set 'groups': { '*': { 'requireMention': false } } in channel config.
2355
+ `);
2356
+ }
2357
+ }
2358
+ /**
2359
+ * Detach the permanent textChunk handler from the current bridge.
2360
+ * No-op if not attached or if cron is disabled.
2361
+ */
2362
+ detachCronHandler() {
2363
+ if (this.qqConfig["cron-msg-experimental"] && this._cronTextHandler && this.cronTextHandlerAttached) {
2364
+ this.bridge.off?.("textChunk", this._cronTextHandler);
2365
+ this.cronTextHandlerAttached = false;
2366
+ }
2367
+ }
1970
2368
  /** Flush pending state writes immediately (called on disconnect). */
1971
2369
  flushQQState() {
1972
2370
  if (this.saveTimer) {
@@ -1975,11 +2373,7 @@ var QQChannel = class _QQChannel extends ChannelBase {
1975
2373
  }
1976
2374
  const tmpPath = this.qqStatePath + ".tmp";
1977
2375
  try {
1978
- writeFileSync2(tmpPath, JSON.stringify({
1979
- chatTypeMap: Array.from(this.chatTypeMap.entries()),
1980
- replyMsgId: Array.from(this.replyMsgId.entries()),
1981
- msgSeqMap: Array.from(this.msgSeqMap.entries())
1982
- }), { mode: 384 });
2376
+ writeFileSync2(tmpPath, this.serializeQQState(), { mode: 384 });
1983
2377
  renameSync(tmpPath, this.qqStatePath);
1984
2378
  } catch (e) {
1985
2379
  try {
@@ -1987,14 +2381,16 @@ var QQChannel = class _QQChannel extends ChannelBase {
1987
2381
  } catch {
1988
2382
  }
1989
2383
  process.stderr.write(`[QQ:${this.name}] flushQQState write failed: ${sanitizeLogText(e instanceof Error ? e.message : String(e), 200)}
2384
+
1990
2385
  `);
1991
2386
  }
1992
2387
  }
1993
2388
  /**
1994
2389
  * Restore QQ routing state from disk.
1995
- * Validates and filters every entry on restore — corrupt or unexpected
1996
- * entries (e.g. unknown chat types, oversized replyMsgIds, negative seqs)
1997
- * are silently dropped so they don't propagate into runtime routing.
2390
+ *
2391
+ * Validates all restored state extensively type checks, length bounds,
2392
+ * and sanity filters so a corrupted file produces clean empty maps
2393
+ * rather than propagating invalid data.
1998
2394
  */
1999
2395
  restoreQQState() {
2000
2396
  try {
@@ -2006,41 +2402,60 @@ var QQChannel = class _QQChannel extends ChannelBase {
2006
2402
  `);
2007
2403
  return false;
2008
2404
  }
2009
- if (raw.chatTypeMap && Array.isArray(raw.chatTypeMap)) {
2010
- const rawCT = raw.chatTypeMap;
2011
- this.chatTypeMap = new Map(rawCT.filter(([k, v]) => typeof k === "string" && k.length <= 256 && (v === "c2c" || v === "group")));
2012
- const dropped = rawCT.length - this.chatTypeMap.size;
2013
- if (dropped > 0)
2014
- process.stderr.write(`[QQ:${this.name}] Dropped ${dropped} invalid chatTypeMap entries during restore
2015
- `);
2016
- }
2017
- if (raw.replyMsgId && Array.isArray(raw.replyMsgId)) {
2018
- const now = Date.now();
2019
- const rawRM = raw.replyMsgId;
2020
- this.replyMsgId = new Map(rawRM.map(([k, v]) => (
2021
- // Old format: string -> { msgId: v, timestamp: now }
2022
- // New format: { msgId, timestamp } -> pass through
2023
- typeof v === "string" ? [k, { msgId: v, timestamp: now }] : [k, v]
2024
- )).filter(([k, v]) => {
2025
- if (typeof k !== "string" || k.length > 256)
2026
- return false;
2027
- if (typeof v !== "object" || v === null)
2405
+ if (raw.chatTypeMap) {
2406
+ const arr = raw.chatTypeMap;
2407
+ const totalRaw = Array.isArray(arr) ? arr.length : 0;
2408
+ this.chatTypeMap = new Map(Array.isArray(arr) ? arr.filter(([k, v]) => typeof k === "string" && k.length <= 256 && (v === "c2c" || v === "group")) : []);
2409
+ if (this.chatTypeMap.size < totalRaw) {
2410
+ process.stderr.write(`[QQ:${this.name}] restoreQQState: accepted ${this.chatTypeMap.size} chatTypeMap entries (rejected ${totalRaw - this.chatTypeMap.size})
2411
+ `);
2412
+ }
2413
+ }
2414
+ if (raw.replyMsgId) {
2415
+ const arr = raw.replyMsgId;
2416
+ const totalRaw = Array.isArray(arr) ? arr.length : 0;
2417
+ this.replyMsgId = new Map(Array.isArray(arr) ? arr.filter(([k]) => typeof k === "string" && k.length <= 256).filter(([, v]) => {
2418
+ if (typeof v === "string" && v.length <= 128)
2419
+ return true;
2420
+ if (v === null || typeof v !== "object")
2028
2421
  return false;
2029
- const entry = v;
2030
- return typeof entry.msgId === "string" && entry.msgId.length <= 128 && typeof entry.timestamp === "number" && Number.isFinite(entry.timestamp) && entry.timestamp <= now + _QQChannel.REPLY_MSG_ID_TTL_MS;
2031
- }));
2032
- const dropped = rawRM.length - this.replyMsgId.size;
2033
- if (dropped > 0)
2034
- process.stderr.write(`[QQ:${this.name}] Dropped ${dropped} invalid replyMsgId entries during restore
2422
+ const o = v;
2423
+ return typeof o["msgId"] === "string" && o["msgId"].length <= 128 && typeof o["timestamp"] === "number" && Number.isFinite(o["timestamp"]) && o["timestamp"] <= Date.now() + _QQChannel.REPLY_MSG_ID_TTL_MS;
2424
+ }).map(([k, v]) => [
2425
+ k,
2426
+ typeof v === "string" ? { msgId: v, timestamp: Date.now() } : v
2427
+ ]) : []);
2428
+ if (this.replyMsgId.size < totalRaw) {
2429
+ process.stderr.write(`[QQ:${this.name}] restoreQQState: accepted ${this.replyMsgId.size} replyMsgId entries (rejected ${totalRaw - this.replyMsgId.size})
2430
+ `);
2431
+ }
2432
+ }
2433
+ if (raw.msgSeqMap) {
2434
+ const arr = raw.msgSeqMap;
2435
+ const totalRaw = Array.isArray(arr) ? arr.length : 0;
2436
+ this.msgSeqMap = new Map(Array.isArray(arr) ? arr.filter(([k, v]) => typeof k === "string" && k.length <= 256 && typeof v === "number" && Number.isSafeInteger(v) && v >= 0) : []);
2437
+ if (this.msgSeqMap.size < totalRaw) {
2438
+ process.stderr.write(`[QQ:${this.name}] restoreQQState: accepted ${this.msgSeqMap.size} msgSeqMap entries (rejected ${totalRaw - this.msgSeqMap.size})
2035
2439
  `);
2440
+ }
2441
+ }
2442
+ if (raw.groupActiveMsgEnabled) {
2443
+ const arr = raw.groupActiveMsgEnabled;
2444
+ const totalRaw = Array.isArray(arr) ? arr.length : 0;
2445
+ this.groupActiveMsgEnabled = new Map(Array.isArray(arr) ? arr.filter(([k, v]) => typeof k === "string" && k.length <= 256 && typeof v === "boolean") : []);
2446
+ if (this.groupActiveMsgEnabled.size < totalRaw) {
2447
+ process.stderr.write(`[QQ:${this.name}] restoreQQState: accepted ${this.groupActiveMsgEnabled.size} groupActiveMsgEnabled entries (rejected ${totalRaw - this.groupActiveMsgEnabled.size})
2448
+ `);
2449
+ }
2036
2450
  }
2037
- if (raw.msgSeqMap && Array.isArray(raw.msgSeqMap)) {
2038
- const rawMS = raw.msgSeqMap;
2039
- this.msgSeqMap = new Map(rawMS.filter(([k, v]) => typeof k === "string" && k.length <= 256 && typeof v === "number" && Number.isSafeInteger(v) && v >= 0));
2040
- const dropped = rawMS.length - this.msgSeqMap.size;
2041
- if (dropped > 0)
2042
- process.stderr.write(`[QQ:${this.name}] Dropped ${dropped} invalid msgSeqMap entries during restore
2451
+ if (raw.botOpenIdByGroup) {
2452
+ const arr = raw.botOpenIdByGroup;
2453
+ const totalRaw = Array.isArray(arr) ? arr.length : 0;
2454
+ this.botOpenIdByGroup = new Map(Array.isArray(arr) ? arr.filter(([k, v]) => typeof k === "string" && k.length <= 256 && typeof v === "string" && /^[A-F0-9]{32}$/i.test(v)) : []);
2455
+ if (this.botOpenIdByGroup.size < totalRaw) {
2456
+ process.stderr.write(`[QQ:${this.name}] restoreQQState: accepted ${this.botOpenIdByGroup.size} botOpenIdByGroup entries (rejected ${totalRaw - this.botOpenIdByGroup.size})
2043
2457
  `);
2458
+ }
2044
2459
  }
2045
2460
  return true;
2046
2461
  } catch (e) {
@@ -2060,7 +2475,10 @@ var QQChannel = class _QQChannel extends ChannelBase {
2060
2475
  if (data.trim())
2061
2476
  writeFileSync2(this.sessionsBackupPath, data, { mode: 384 });
2062
2477
  }
2063
- } catch {
2478
+ } catch (e) {
2479
+ process.stderr.write(`[QQ:${this.name}] backupGlobalSessions failed: ${sanitizeLogText(e instanceof Error ? e.message : String(e), 200)}
2480
+
2481
+ `);
2064
2482
  }
2065
2483
  }
2066
2484
  restoreGlobalSessions() {
@@ -2068,7 +2486,10 @@ var QQChannel = class _QQChannel extends ChannelBase {
2068
2486
  if (!existsSync2(this.globalSessionsPath) && existsSync2(this.sessionsBackupPath)) {
2069
2487
  writeFileSync2(this.globalSessionsPath, readFileSync2(this.sessionsBackupPath, "utf-8"), { mode: 384 });
2070
2488
  }
2071
- } catch {
2489
+ } catch (e) {
2490
+ process.stderr.write(`[QQ:${this.name}] restoreGlobalSessions failed: ${sanitizeLogText(e instanceof Error ? e.message : String(e), 200)}
2491
+
2492
+ `);
2072
2493
  }
2073
2494
  }
2074
2495
  /**
@@ -2110,7 +2531,10 @@ var QQChannel = class _QQChannel extends ChannelBase {
2110
2531
  tc.set(correctId, entry.cwd || "");
2111
2532
  }
2112
2533
  }
2113
- } catch {
2534
+ } catch (e) {
2535
+ process.stderr.write(`[QQ:${this.name}] fixRestoredSessions failed: ${sanitizeLogText(e instanceof Error ? e.message : String(e), 200)}
2536
+
2537
+ `);
2114
2538
  }
2115
2539
  }
2116
2540
  // ── ReplyMsgId helpers ────────────────────────────────────────
@@ -2189,33 +2613,55 @@ var QQChannel = class _QQChannel extends ChannelBase {
2189
2613
  return;
2190
2614
  this.stopTokenRefresh();
2191
2615
  const ttl = Math.max(0, this.tokenExpiresAt - Date.now());
2192
- const delay = Math.max(Math.min(ttl * 0.8, ttl - 6e4), 6e4);
2616
+ const delay = Math.min(ttl * 0.8, Math.max(ttl - 3e4, 1e4));
2193
2617
  if (delay > 0) {
2618
+ const tokenReconnectId = this._reconnectId;
2194
2619
  this.tokenRefreshTimer = setTimeout(() => {
2195
2620
  this.fetchToken().catch((e) => {
2196
- if (this.disposed)
2621
+ if (this.disposed || this._reconnectId !== tokenReconnectId)
2197
2622
  return;
2198
- process.stderr.write(`[QQ:${this.name}] Token refresh failed: ${sanitizeLogText(e instanceof Error ? e.message : String(e), 200)}, retrying in 60s
2623
+ process.stderr.write(`[QQ:${this.name}] Token refresh failed: ${sanitizeLogText(e instanceof Error ? e.message : String(e), 200)}, will retry
2624
+
2625
+ `);
2626
+ let retryCount = 0;
2627
+ const retry = /* @__PURE__ */ __name(() => {
2628
+ if (this.disposed || this._reconnectId !== tokenReconnectId)
2629
+ return;
2630
+ if (++retryCount > 10) {
2631
+ process.stderr.write(`[QQ:${this.name}] FATAL: token refresh exhausted, reconnecting
2632
+
2633
+ `);
2634
+ this.isReconnecting = true;
2635
+ this.disconnect();
2636
+ const postDisconnectReconnectId = this._reconnectId;
2637
+ this.reconnectTimer = setTimeout(() => {
2638
+ if (this._reconnectId !== postDisconnectReconnectId)
2639
+ return;
2640
+ this.isReconnecting = false;
2641
+ this.disposed = false;
2642
+ this.reconnectWithRetry();
2643
+ }, 1e3);
2644
+ this.reconnectTimer.unref?.();
2645
+ return;
2646
+ }
2647
+ this.tokenRefreshTimer = setTimeout(() => {
2648
+ this.fetchToken().catch((e2) => {
2649
+ if (this.disposed || this._reconnectId !== tokenReconnectId)
2650
+ return;
2651
+ process.stderr.write(`[QQ:${this.name}] Token refresh retry failed (attempt ${retryCount}): ${sanitizeLogText(e2 instanceof Error ? e2.message : String(e2), 200)}
2652
+
2199
2653
  `);
2200
- this.scheduleTokenRefreshRetry();
2654
+ retry();
2655
+ });
2656
+ }, 6e4);
2657
+ this.tokenRefreshTimer.unref?.();
2658
+ }, "retry");
2659
+ retry();
2201
2660
  });
2202
2661
  }, delay);
2662
+ this.tokenRefreshTimer.unref?.();
2203
2663
  }
2204
2664
  }
2205
- scheduleTokenRefreshRetry() {
2206
- if (this.disposed)
2207
- return;
2208
- this.stopTokenRefresh();
2209
- this.tokenRefreshTimer = setTimeout(() => {
2210
- this.fetchToken().catch((e) => {
2211
- if (this.disposed)
2212
- return;
2213
- process.stderr.write(`[QQ:${this.name}] Token refresh failed: ${sanitizeLogText(e instanceof Error ? e.message : String(e), 200)}, retrying in 60s
2214
- `);
2215
- this.scheduleTokenRefreshRetry();
2216
- });
2217
- }, 6e4);
2218
- }
2219
2665
  stopTokenRefresh() {
2220
2666
  if (this.tokenRefreshTimer) {
2221
2667
  clearTimeout(this.tokenRefreshTimer);
@@ -2238,7 +2684,9 @@ var QQChannel = class _QQChannel extends ChannelBase {
2238
2684
  this.readyTimeout = setTimeout(() => {
2239
2685
  if (this.ws !== dialed)
2240
2686
  return;
2241
- this.ws.close(4002, "READY timeout");
2687
+ process.stderr.write(`[QQ:${this.name}] READY timeout after 30s, closing
2688
+ `);
2689
+ this.ws?.close(4002, "READY timeout");
2242
2690
  reject(new Error(`[QQ:${this.name}] READY timeout after 30s`));
2243
2691
  }, 3e4);
2244
2692
  this.readyTimeout.unref?.();
@@ -2266,21 +2714,26 @@ var QQChannel = class _QQChannel extends ChannelBase {
2266
2714
  }
2267
2715
  this.stopHeartbeat();
2268
2716
  this.ws = null;
2269
- const shouldReconnect = this.serverRequestedReconnect || code !== 1e3 && this.reconnectAttempts < this.maxReconnectAttempts;
2717
+ this._ready = false;
2718
+ const shouldReconnect = this.serverRequestedReconnect || code !== 1e3 && (this.maxReconnectAttempts <= 0 || this.reconnectAttempts < this.maxReconnectAttempts);
2270
2719
  this.serverRequestedReconnect = false;
2720
+ if (code !== 1e3 && code !== 4e3) {
2721
+ this.tryResume = false;
2722
+ this.flushQQState();
2723
+ this.coldStart = true;
2724
+ }
2271
2725
  if (shouldReconnect && this.connectReject) {
2272
2726
  this.connectReject(new Error(`WebSocket closed before READY (code=${code})`));
2273
2727
  this.connectReject = null;
2274
2728
  } else if (shouldReconnect) {
2275
- this.reconnectAttempts++;
2276
2729
  const delay = Math.min(1e3 * 2 ** this.reconnectAttempts, 3e4);
2277
- process.stderr.write(`[QQ:${this.name}] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})
2730
+ process.stderr.write(`[QQ:${this.name}] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}${this.maxReconnectAttempts > 0 ? `/${this.maxReconnectAttempts}` : ""})
2278
2731
  `);
2279
2732
  if (!this.isReconnecting) {
2280
2733
  this.reconnectTimer = setTimeout(() => this.reconnectWithRetry(), delay);
2281
2734
  this.reconnectTimer.unref();
2282
2735
  }
2283
- } else if (this.reconnectAttempts >= this.maxReconnectAttempts) {
2736
+ } else if (this.maxReconnectAttempts > 0 && this.reconnectAttempts >= this.maxReconnectAttempts) {
2284
2737
  process.stderr.write(`[QQ:${this.name}] FATAL: reconnect exhausted after ${this.maxReconnectAttempts} attempts. Bot is offline until daemon restart.
2285
2738
  `);
2286
2739
  if (this.connectReject) {
@@ -2302,6 +2755,19 @@ var QQChannel = class _QQChannel extends ChannelBase {
2302
2755
  }
2303
2756
  });
2304
2757
  }
2758
+ /**
2759
+ * Finalize READY state across cold-start and warm-reconnect paths.
2760
+ * Extracted to eliminate triplication in the READY handler.
2761
+ */
2762
+ finalizeReady() {
2763
+ if (!this.ws || this.disposed)
2764
+ return;
2765
+ this._ready = true;
2766
+ this.reconnectAttempts = 0;
2767
+ this.isReconnecting = false;
2768
+ this.coldStart = false;
2769
+ this.attachCronHandler();
2770
+ }
2305
2771
  handleGatewayMessage(msg, onReady) {
2306
2772
  const op = msg["op"];
2307
2773
  switch (op) {
@@ -2326,28 +2792,56 @@ var QQChannel = class _QQChannel extends ChannelBase {
2326
2792
  this.tryResume = true;
2327
2793
  this.connectReject = null;
2328
2794
  this.startHeartbeat();
2329
- this.restoreGlobalSessions();
2330
- this.restoreQQState();
2331
- this.router.restoreSessions().then(() => {
2332
- this.fixRestoredSessions();
2333
- const all = this.router.getAll?.();
2334
- const sessions = all?.map((e) => `${e.target?.chatId}:${e.sessionId}`).join(", ") || "none";
2335
- process.stderr.write(`[QQ:${this.name}] Ready (sessions: ${sessions})
2795
+ if (this.coldStart) {
2796
+ this.restoreGlobalSessions();
2797
+ if (!this.restoreQQState()) {
2798
+ process.stderr.write(`[QQ:${this.name}] WARNING: QQ state restore failed \u2014 routing maps are empty, group messages may be misrouted
2799
+ `);
2800
+ }
2801
+ this.router.restoreSessions().then(() => {
2802
+ this.fixRestoredSessions();
2803
+ const all = this.router.getAll?.();
2804
+ const count = all?.length ?? 0;
2805
+ process.stderr.write(`[QQ:${this.name}] Ready (${count} sessions)
2806
+ `);
2807
+ this.finalizeReady();
2808
+ this._checkGroupAllPolicyRequireMention();
2809
+ onReady();
2810
+ }).catch(() => {
2811
+ process.stderr.write(`[QQ:${this.name}] WARNING: router session restore failed \u2014 cron messages will be dropped until sessions re-establish
2812
+ `);
2813
+ this.finalizeReady();
2814
+ this._checkGroupAllPolicyRequireMention();
2815
+ onReady();
2816
+ });
2817
+ } else {
2818
+ process.stderr.write(`[QQ:${this.name}] Ready (warm reconnect, skipping state restore)
2336
2819
  `);
2820
+ this.finalizeReady();
2821
+ this._checkGroupAllPolicyRequireMention();
2337
2822
  onReady();
2338
- }).catch(() => onReady());
2823
+ }
2339
2824
  } else if (t === "C2C_MESSAGE_CREATE") {
2340
2825
  this.handleC2C(msg["d"]);
2341
2826
  } else if (t === "GROUP_AT_MESSAGE_CREATE") {
2342
2827
  this.handleGroup(msg["d"]);
2828
+ } else if (t === "GROUP_MESSAGE_CREATE") {
2829
+ this.handleGroupAll(msg["d"]);
2830
+ } else if (t === "GROUP_ADD_ROBOT") {
2831
+ this.handleGroupAddRobot(msg["d"]);
2832
+ } else if (t === "GROUP_DEL_ROBOT") {
2833
+ this.handleGroupDelRobot(msg["d"]);
2834
+ } else if (t === "GROUP_MSG_REJECT") {
2835
+ this.handleGroupMsgToggle(msg["d"], false);
2836
+ } else if (t === "GROUP_MSG_RECEIVE") {
2837
+ this.handleGroupMsgToggle(msg["d"], true);
2343
2838
  } else if (t === "RESUMED") {
2344
- this.reconnectAttempts = 0;
2345
- this.isReconnecting = false;
2346
2839
  if (this.readyTimeout) {
2347
2840
  clearTimeout(this.readyTimeout);
2348
2841
  this.readyTimeout = null;
2349
2842
  }
2350
2843
  this.connectReject = null;
2844
+ this.finalizeReady();
2351
2845
  this.startHeartbeat();
2352
2846
  onReady();
2353
2847
  }
@@ -2364,7 +2858,28 @@ var QQChannel = class _QQChannel extends ChannelBase {
2364
2858
  process.stderr.write(`[QQ:${this.name}] Server sent INVALID_SESSION, falling back to IDENTIFY
2365
2859
  `);
2366
2860
  this.tryResume = false;
2861
+ if (this.saveTimer) {
2862
+ clearTimeout(this.saveTimer);
2863
+ this.saveTimer = null;
2864
+ }
2865
+ this.flushQQState();
2866
+ this._ready = false;
2867
+ this.coldStart = true;
2367
2868
  this.sendIdentify();
2869
+ if (this.readyTimeout) {
2870
+ clearTimeout(this.readyTimeout);
2871
+ this.readyTimeout = null;
2872
+ }
2873
+ this.readyTimeout = setTimeout(() => {
2874
+ if (this.ws && (this.ws.readyState === wrapper_default.OPEN || this.ws.readyState === wrapper_default.CONNECTING)) {
2875
+ this.ws.close(4002);
2876
+ if (this.connectReject) {
2877
+ this.connectReject(new Error("Timed out waiting for READY"));
2878
+ this.connectReject = null;
2879
+ }
2880
+ }
2881
+ }, 3e4);
2882
+ this.readyTimeout.unref?.();
2368
2883
  break;
2369
2884
  default:
2370
2885
  break;
@@ -2386,11 +2901,12 @@ var QQChannel = class _QQChannel extends ChannelBase {
2386
2901
  }));
2387
2902
  return;
2388
2903
  }
2904
+ const needsGroupMsg = this.qqConfig.groupAllPolicy === "keyword" || this.qqConfig.groupAllPolicy === "all" || this.qqConfig.groupAllPolicy === "log";
2389
2905
  this.ws.send(JSON.stringify({
2390
2906
  op: OpCode.IDENTIFY,
2391
2907
  d: {
2392
2908
  token: `QQBot ${this.accessToken}`,
2393
- intents: Intent.C2C_MESSAGE | Intent.GROUP_AT_MESSAGE,
2909
+ intents: Intent.C2C_MESSAGE | Intent.GROUP_AT_MESSAGE | (needsGroupMsg ? Intent.GROUP_MESSAGE : 0),
2394
2910
  shard: [0, 1],
2395
2911
  properties: {}
2396
2912
  }
@@ -2407,47 +2923,56 @@ var QQChannel = class _QQChannel extends ChannelBase {
2407
2923
  if (this.isReconnecting)
2408
2924
  return;
2409
2925
  this.isReconnecting = true;
2410
- if (this.reconnectAttempts >= this.maxReconnectAttempts) {
2411
- process.stderr.write(`[QQ:${this.name}] RC: reconnect attempts exhausted, giving up
2926
+ try {
2927
+ const myReconnectId = this._reconnectId;
2928
+ const maxGwRetries = this.qqConfig.maxGwRetries ?? 5;
2929
+ const unlimitedRetries = maxGwRetries <= 0;
2930
+ for (let attempt = 0; unlimitedRetries || attempt < maxGwRetries; attempt++) {
2931
+ if (this.disposed || this._reconnectId !== myReconnectId)
2932
+ return;
2933
+ this.reconnectAttempts++;
2934
+ if (this.maxReconnectAttempts > 0 && this.reconnectAttempts >= this.maxReconnectAttempts) {
2935
+ process.stderr.write(`[QQ:${this.name}] RC: reconnect attempts exhausted, giving up
2412
2936
  `);
2413
- this.isReconnecting = false;
2414
- return;
2415
- }
2416
- const maxGwRetries = 5;
2417
- let gatewayAttempted = false;
2418
- for (let attempt = 0; attempt < maxGwRetries; attempt++) {
2419
- try {
2937
+ return;
2938
+ }
2420
2939
  try {
2421
- await this.fetchToken();
2422
- } catch {
2423
- process.stderr.write(`[QQ:${this.name}] RC: token refresh failed, retrying...
2940
+ try {
2941
+ await this.fetchToken();
2942
+ } catch {
2943
+ process.stderr.write(`[QQ:${this.name}] RC: token refresh failed, retrying...
2424
2944
  `);
2425
- await this.sleep(2e3);
2426
- continue;
2427
- }
2428
- gatewayAttempted = true;
2429
- await this.connectGateway();
2430
- return;
2431
- } catch (e) {
2432
- const msg = e instanceof Error ? e.message : String(e);
2433
- const backoff = Math.min(1e3 * 2 ** (attempt + 1), 3e4);
2434
- process.stderr.write(`[QQ:${this.name}] RC: ${sanitizeLogText(msg, 200)} (retry in ${backoff}ms, attempt ${attempt + 1}/${maxGwRetries})
2945
+ await this.sleep(2e3);
2946
+ if (this.disposed)
2947
+ return;
2948
+ continue;
2949
+ }
2950
+ await this.connectGateway();
2951
+ this.startReplyMsgIdCleanup();
2952
+ return;
2953
+ } catch (e) {
2954
+ const msg = e instanceof Error ? e.message : String(e);
2955
+ const backoff = Math.min(1e3 * 2 ** (attempt + 1), 3e4);
2956
+ process.stderr.write(`[QQ:${this.name}] RC: ${sanitizeLogText(msg, 200)} (retry in ${backoff}ms, attempt ${attempt + 1}${unlimitedRetries ? "" : `/${maxGwRetries}`})
2435
2957
  `);
2436
- if (attempt < maxGwRetries - 1)
2437
- await this.sleep(backoff);
2958
+ if (unlimitedRetries || attempt < maxGwRetries - 1)
2959
+ await this.sleep(backoff);
2960
+ }
2438
2961
  }
2439
- }
2440
- process.stderr.write(`[QQ:${this.name}] RC: exhausted ${maxGwRetries} reconnect retries, will retry in 60s
2962
+ process.stderr.write(`[QQ:${this.name}] RC: exhausted ${unlimitedRetries ? "\u221E" : maxGwRetries} reconnect retries, will retry in 60s
2441
2963
  `);
2442
- if (gatewayAttempted)
2443
- this.reconnectAttempts++;
2444
- this.tryResume = false;
2445
- this.isReconnecting = false;
2964
+ this.tryResume = false;
2965
+ } finally {
2966
+ this.isReconnecting = false;
2967
+ }
2446
2968
  this.reconnectTimer = setTimeout(() => this.reconnectWithRetry(), 6e4);
2447
2969
  this.reconnectTimer.unref();
2448
2970
  }
2449
2971
  sleep(ms) {
2450
- return new Promise((r) => setTimeout(r, ms));
2972
+ return new Promise((r) => {
2973
+ const t = setTimeout(r, ms);
2974
+ t.unref?.();
2975
+ });
2451
2976
  }
2452
2977
  startHeartbeat() {
2453
2978
  this.stopHeartbeat();
@@ -2472,6 +2997,23 @@ var QQChannel = class _QQChannel extends ChannelBase {
2472
2997
  this.heartbeatTimer = null;
2473
2998
  }
2474
2999
  }
3000
+ // ── Bot OpenID extraction ──────────────────────────────────────
3001
+ extractBotOpenId(mentions, chatId) {
3002
+ const selfMention = mentions?.find((m3) => m3.is_you);
3003
+ if (!selfMention)
3004
+ return "";
3005
+ const botOpenId = selfMention.member_openid || selfMention.id || "";
3006
+ if (!/^[A-F0-9]{32}$/i.test(botOpenId)) {
3007
+ process.stderr.write(`[QQ:${this.name}] Invalid botOpenId format: ${sanitizeLogText(botOpenId, 64)}
3008
+ `);
3009
+ return "";
3010
+ }
3011
+ if (chatId) {
3012
+ this.botOpenIdByGroup.set(chatId, botOpenId);
3013
+ this.saveQQState();
3014
+ }
3015
+ return botOpenId;
3016
+ }
2475
3017
  // ── Message Handlers ───────────────────────────────────────────
2476
3018
  /** Check if a message ID was already processed (reconnect replay dedup). */
2477
3019
  isDuplicate(eventId) {
@@ -2494,68 +3036,340 @@ var QQChannel = class _QQChannel extends ChannelBase {
2494
3036
  }
2495
3037
  return false;
2496
3038
  }
3039
+ /**
3040
+ * Extract common group-message fields shared by handleGroup and handleGroupAll.
3041
+ * Returns null when the message has no meaningful text after @-tag stripping.
3042
+ */
3043
+ prepareGroupMessage(event, chatId, { forceAtMention } = {}) {
3044
+ const senderName = event.author?.username || event.author?.id || event.author?.member_openid || "QQ User";
3045
+ const safeName = sanitizeSenderName(senderName);
3046
+ const senderOpenId = event.author?.member_openid || event.author?.user_openid || "";
3047
+ const content = (event.content || "").trim();
3048
+ const cleanText = content.replace(/<@[^>]{1,64}>/g, "").trim();
3049
+ const safeContent = content.replace(/\[atMention=[^\]]*]/g, "").replace(/\[botOpenId:[^\]]*]/g, "").replace(/\[bot]/g, "");
3050
+ const safeCleanText = cleanText.replace(/\[atMention=[^\]]*]/g, "").replace(/\[botOpenId:[^\]]*]/g, "").replace(/\[bot]/g, "").trim();
3051
+ const isAtBot = event.mentions?.some((m3) => m3.is_you) ?? false;
3052
+ if (isAtBot && !this.botOpenIdByGroup.has(chatId)) {
3053
+ this.extractBotOpenId(event.mentions, chatId);
3054
+ }
3055
+ if (!cleanText)
3056
+ return null;
3057
+ const effectiveIsAtBot = forceAtMention ?? isAtBot;
3058
+ const isSlash = effectiveIsAtBot && safeCleanText.startsWith("/");
3059
+ const groupBotOpenId = this.botOpenIdByGroup.get(chatId);
3060
+ const openIdSuffix = this.qqConfig.allowMention !== false && groupBotOpenId ? ` [botOpenId:${groupBotOpenId}]` : "";
3061
+ const suffixFromBotOpenId = this.qqConfig.allowMention !== false && groupBotOpenId ? `
3062
+ \u673A\u5668\u4EBA OPENID: ${groupBotOpenId}` : "";
3063
+ const text = isSlash ? sanitizePromptText(safeCleanText) : `[atMention=${effectiveIsAtBot}]${openIdSuffix} [${safeName}${senderOpenId && /^[A-F0-9]+$/i.test(senderOpenId) ? `(${senderOpenId.slice(0, 8)}\u2026)` : ""}]: ${sanitizePromptText(this.qqConfig.allowMention !== false ? safeContent : safeCleanText)}${suffixFromBotOpenId}`;
3064
+ return {
3065
+ isAtBot: effectiveIsAtBot,
3066
+ isSlash,
3067
+ safeName,
3068
+ cleanText,
3069
+ text,
3070
+ senderName
3071
+ };
3072
+ }
2497
3073
  handleC2C(event) {
2498
3074
  if (this.isDuplicate(event.id))
2499
3075
  return;
2500
3076
  if (!event.content?.trim())
2501
3077
  return;
2502
- if (!event.author)
3078
+ if (!event.author) {
3079
+ process.stderr.write(`[QQ:${this.name}] C2C message dropped: missing author
3080
+ `);
3081
+ return;
3082
+ }
3083
+ if (event.author.bot) {
3084
+ process.stderr.write(`[QQ:${this.name}] Bot C2C message dropped
3085
+ `);
2503
3086
  return;
3087
+ }
2504
3088
  const chatId = event.author.user_openid || event.author.id;
3089
+ if (!chatId) {
3090
+ process.stderr.write(`[QQ:${this.name}] C2C message dropped: no chatId for author
3091
+ `);
3092
+ return;
3093
+ }
3094
+ if (!isValidChatId(chatId)) {
3095
+ process.stderr.write(`[QQ:${this.name}] C2C message dropped: invalid chatId (length=${chatId.length})
3096
+ `);
3097
+ return;
3098
+ }
2505
3099
  this.chatTypeMap.set(chatId, "c2c");
2506
3100
  this.setReplyMsgId(chatId, event.id);
3101
+ const senderName = event.author.username || event.author.id || "QQ User";
3102
+ const safeName = sanitizeSenderName(senderName);
3103
+ const cleanText = event.content.trim();
3104
+ const safeContent = cleanText.replace(/\[atMention=[^\]]*]/g, "").replace(/\[botOpenId:[^\]]*]/g, "").replace(/\[bot]/g, "");
3105
+ const isSlash = safeContent.startsWith("/");
3106
+ const text = isSlash ? sanitizePromptText(safeContent) : `[atMention=true] [${safeName}]: ${sanitizePromptText(safeContent)}`;
2507
3107
  this.handleInbound({
2508
3108
  channelName: this.name,
2509
3109
  senderId: chatId,
2510
- senderName: event.author.username || event.author.id || "QQ User",
3110
+ senderName,
2511
3111
  chatId,
2512
- text: event.content,
3112
+ text,
2513
3113
  messageId: event.id,
2514
3114
  isGroup: false,
2515
3115
  isMentioned: true,
2516
- isReplyToBot: false
3116
+ isReplyToBot: false,
3117
+ ...isSlash ? {} : { alreadyPrefixed: true }
2517
3118
  }).catch((e) => process.stderr.write(`[QQ:${this.name}] C2C handler error: ${sanitizeLogText(e instanceof Error ? e.message : String(e), 200)}
2518
3119
  `));
2519
3120
  }
2520
3121
  handleGroup(event) {
2521
- if (this.isDuplicate(event.id))
2522
- return;
2523
3122
  if (!event.group_openid) {
2524
3123
  process.stderr.write(`[QQ:${this.name}] Group message dropped: missing group_openid
3124
+ `);
3125
+ return;
3126
+ }
3127
+ if (!event.author) {
3128
+ process.stderr.write(`[QQ:${this.name}] Group message dropped: missing author
3129
+ `);
3130
+ return;
3131
+ }
3132
+ if (event.author.bot) {
3133
+ process.stderr.write(`[QQ:${this.name}] Bot message dropped in group ${sanitizeLogText(event.group_openid, 64)}
2525
3134
  `);
2526
3135
  return;
2527
3136
  }
2528
3137
  const chatId = event.group_openid;
3138
+ if (!isValidChatId(chatId)) {
3139
+ process.stderr.write(`[QQ:${this.name}] Group message dropped: invalid group_openid
3140
+ `);
3141
+ return;
3142
+ }
3143
+ const isNewGroup = !this.chatTypeMap.has(chatId);
2529
3144
  this.chatTypeMap.set(chatId, "group");
2530
- this.setReplyMsgId(chatId, event.id);
2531
- const senderName = event.author.username || event.author.id || "QQ User";
2532
- const cleanText = (event.content || "").replace(/<@[^>]{1,64}>/g, "").trim();
2533
- if (!cleanText)
3145
+ if (isNewGroup)
3146
+ this.saveQQState();
3147
+ const result = this.prepareGroupMessage(event, chatId, {
3148
+ forceAtMention: true
3149
+ });
3150
+ if (!result)
3151
+ return;
3152
+ const { isSlash, text, senderName, safeName, cleanText } = result;
3153
+ if (this.isDuplicate(event.id))
2534
3154
  return;
2535
- const isSlash = cleanText.startsWith("/");
2536
- const safeName = sanitizeSenderName(senderName);
2537
3155
  if (isSlash) {
2538
- const loggedCmd = sanitizeLogText(cleanText, 80);
2539
- process.stderr.write(`[QQ:${this.name}] Slash cmd from ${safeName} (${chatId}): ${loggedCmd}
3156
+ process.stderr.write(`[QQ:${this.name}] Slash cmd from ${sanitizeLogText(safeName, 64)} (${sanitizeLogText(chatId, 64)}): ${sanitizeLogText(cleanText.split(/\s/)[0], 64)}
3157
+ `);
3158
+ }
3159
+ if (this.groupActiveMsgEnabled.get(chatId) === false) {
3160
+ process.stderr.write(`[QQ:${this.name}] handleGroup: active messages disabled but @-bot allowed through (passive)
3161
+ `);
3162
+ }
3163
+ const senderId = event.author.user_openid || event.author.id || event.author.member_openid;
3164
+ if (!senderId) {
3165
+ process.stderr.write(`[QQ:${this.name}] Group message dropped: no senderId for author
2540
3166
  `);
3167
+ return;
2541
3168
  }
2542
- const text = isSlash ? cleanText : `[${safeName}]: ${sanitizePromptText(cleanText)}`;
3169
+ this.setReplyMsgId(chatId, event.id);
2543
3170
  this.handleInbound({
2544
3171
  channelName: this.name,
2545
- senderId: event.author.user_openid || event.author.id,
3172
+ senderId,
2546
3173
  senderName,
2547
3174
  chatId,
2548
3175
  text,
2549
3176
  messageId: event.id,
2550
3177
  isGroup: true,
2551
3178
  isMentioned: true,
2552
- // QQ Bot only receives group messages when explicitly @mentioned, so
2553
- // every group message is semantically a reply to the bot.
2554
3179
  isReplyToBot: true,
2555
3180
  ...isSlash ? {} : { alreadyPrefixed: true }
2556
3181
  }).catch((e) => process.stderr.write(`[QQ:${this.name}] Group handler error: ${sanitizeLogText(e instanceof Error ? e.message : String(e), 200)}
2557
3182
  `));
2558
3183
  }
3184
+ handleGroupAll(event) {
3185
+ if (!event.group_openid) {
3186
+ process.stderr.write(`[QQ:${this.name}] Group all-message dropped: missing group_openid
3187
+ `);
3188
+ return;
3189
+ }
3190
+ if (!event.author) {
3191
+ process.stderr.write(`[QQ:${this.name}] Group all-message dropped: missing author
3192
+ `);
3193
+ return;
3194
+ }
3195
+ if (event.author.bot) {
3196
+ process.stderr.write(`[QQ:${this.name}] Bot message dropped in group ${sanitizeLogText(event.group_openid, 64)}
3197
+ `);
3198
+ return;
3199
+ }
3200
+ const chatId = event.group_openid;
3201
+ if (!isValidChatId(chatId)) {
3202
+ process.stderr.write(`[QQ:${this.name}] Group all-message dropped: invalid group_openid
3203
+ `);
3204
+ return;
3205
+ }
3206
+ const isNewGroup = !this.chatTypeMap.has(chatId);
3207
+ this.chatTypeMap.set(chatId, "group");
3208
+ if (isNewGroup)
3209
+ this.saveQQState();
3210
+ const result = this.prepareGroupMessage(event, chatId);
3211
+ if (!result)
3212
+ return;
3213
+ const { isSlash, text, senderName, isAtBot, safeName, cleanText } = result;
3214
+ if (!isAtBot) {
3215
+ if (this.groupActiveMsgEnabled.get(chatId) === false) {
3216
+ process.stderr.write(`[QQ:${this.name}] handleGroupAll blocked: active messages disabled for ${sanitizeLogText(chatId, 64)}
3217
+ `);
3218
+ return;
3219
+ }
3220
+ const rawPolicy = this.qqConfig.groupAllPolicy;
3221
+ const policy = rawPolicy === "keyword" || rawPolicy === "all" ? rawPolicy : "log";
3222
+ if (policy === "log") {
3223
+ process.stderr.write(`[QQ:${this.name}] Group ${sanitizeLogText(chatId, 64)}: log policy \u2014 message from ${sanitizeLogText(senderName, 64)} not forwarded
3224
+ `);
3225
+ return;
3226
+ }
3227
+ if (policy === "keyword") {
3228
+ if (!this._keywordTriggerCache) {
3229
+ this._keywordTriggerCache = (this.qqConfig.keywordTriggers ?? []).filter((kw) => kw.length > 0).map((kw) => {
3230
+ const normalized = kw.normalize("NFC");
3231
+ const escaped = normalized.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3232
+ const firstIsAscii = /^[A-Za-z0-9_]/.test(normalized);
3233
+ const lastIsAscii = /[A-Za-z0-9_]$/.test(normalized);
3234
+ const lb = firstIsAscii ? "(?:^|[^\\w])" : "";
3235
+ const la = lastIsAscii ? "(?:[^\\w]|$)" : "";
3236
+ return new RegExp(`${lb}${escaped}${la}`, "i");
3237
+ });
3238
+ }
3239
+ if (this._keywordTriggerCache.length === 0) {
3240
+ process.stderr.write(`[QQ:${this.name}] Group ${sanitizeLogText(chatId, 64)}: keyword policy \u2014 no keywords configured, message from ${sanitizeLogText(senderName, 64)} not forwarded
3241
+ `);
3242
+ return;
3243
+ }
3244
+ const keywordText = result.cleanText.normalize("NFC");
3245
+ const matched = this._keywordTriggerCache.some((re) => re.test(keywordText));
3246
+ if (!matched) {
3247
+ const now = Date.now();
3248
+ const lastLog = this._lastKeywordNoMatchLog.get(chatId) ?? 0;
3249
+ if (now - lastLog >= 6e4) {
3250
+ this._lastKeywordNoMatchLog.set(chatId, now);
3251
+ process.stderr.write(`[QQ:${this.name}] Group ${sanitizeLogText(chatId, 64)}: keyword policy \u2014 no match for message from ${sanitizeLogText(senderName, 64)}
3252
+ `);
3253
+ }
3254
+ return;
3255
+ }
3256
+ }
3257
+ } else if (this.groupActiveMsgEnabled.get(chatId) === false) {
3258
+ process.stderr.write(`[QQ:${this.name}] handleGroupAll: @-bot message allowed through (passive) despite active messages disabled for ${sanitizeLogText(chatId, 64)}
3259
+ `);
3260
+ }
3261
+ if (this.isDuplicate(event.id))
3262
+ return;
3263
+ if (isSlash) {
3264
+ process.stderr.write(`[QQ:${this.name}] Slash cmd from ${sanitizeLogText(safeName, 64)} (${sanitizeLogText(chatId, 64)}): ${sanitizeLogText(cleanText.split(/\s/)[0], 64)}
3265
+ `);
3266
+ }
3267
+ const senderId = event.author.user_openid || event.author.id || event.author.member_openid;
3268
+ if (!senderId) {
3269
+ process.stderr.write(`[QQ:${this.name}] Group all-message dropped: no senderId for author
3270
+ `);
3271
+ return;
3272
+ }
3273
+ this.setReplyMsgId(chatId, event.id);
3274
+ this.handleInbound({
3275
+ channelName: this.name,
3276
+ chatId,
3277
+ text,
3278
+ senderId,
3279
+ senderName,
3280
+ messageId: event.id,
3281
+ isGroup: true,
3282
+ isMentioned: isAtBot,
3283
+ isReplyToBot: isAtBot,
3284
+ ...isSlash ? {} : { alreadyPrefixed: true }
3285
+ }).catch((e) => {
3286
+ process.stderr.write(`[QQ:${this.name}] handleGroupAll error: ${sanitizeLogText(e instanceof Error ? e.message : String(e), 200)}
3287
+ `);
3288
+ });
3289
+ }
3290
+ // ── Group management events ────────────────────────────────────
3291
+ handleGroupAddRobot(event) {
3292
+ const groupId = event.group_openid;
3293
+ if (!groupId) {
3294
+ process.stderr.write(`[QQ:${this.name}] handleGroupAddRobot: missing group_openid
3295
+ `);
3296
+ return;
3297
+ }
3298
+ if (!isValidChatId(groupId)) {
3299
+ process.stderr.write(`[QQ:${this.name}] handleGroupAddRobot: invalid group_openid (length=${groupId.length})
3300
+ `);
3301
+ return;
3302
+ }
3303
+ this.chatTypeMap.set(groupId, "group");
3304
+ this.saveQQState();
3305
+ process.stderr.write(`[QQ:${this.name}] Added to group ${sanitizeLogText(groupId, 64)} by ${sanitizeLogText(event.op_member_openid, 64)}
3306
+ `);
3307
+ }
3308
+ handleGroupDelRobot(event) {
3309
+ const groupId = event.group_openid;
3310
+ if (!groupId) {
3311
+ process.stderr.write(`[QQ:${this.name}] handleGroupDelRobot: missing group_openid
3312
+ `);
3313
+ return;
3314
+ }
3315
+ if (!isValidChatId(groupId)) {
3316
+ process.stderr.write(`[QQ:${this.name}] handleGroupDelRobot: invalid group_openid (length=${groupId.length})
3317
+ `);
3318
+ return;
3319
+ }
3320
+ this.chatTypeMap.delete(groupId);
3321
+ this.groupActiveMsgEnabled.delete(groupId);
3322
+ const replyEntry = this.replyMsgId.get(groupId);
3323
+ if (replyEntry)
3324
+ this.msgSeqMap.delete(replyEntry.msgId);
3325
+ this.replyMsgId.delete(groupId);
3326
+ this.botOpenIdByGroup.delete(groupId);
3327
+ this._lastKeywordNoMatchLog.delete(groupId);
3328
+ let cleanedCron = 0;
3329
+ for (const [sid, entry] of this.cronBuffer) {
3330
+ const state = this.streamState.get(sid);
3331
+ if (state?.chatId === groupId) {
3332
+ if (entry.timer)
3333
+ clearTimeout(entry.timer);
3334
+ this.cronBuffer.delete(sid);
3335
+ cleanedCron++;
3336
+ }
3337
+ }
3338
+ let cleanedStreams = 0;
3339
+ for (const [sid, state] of this.streamState) {
3340
+ if (state.chatId === groupId) {
3341
+ if (state.timer)
3342
+ clearTimeout(state.timer);
3343
+ this.flushingSessions.delete(sid);
3344
+ this.pendingStreamDelete.delete(sid);
3345
+ this.flushedSessions.delete(sid);
3346
+ this.streamState.delete(sid);
3347
+ if (this.config.sessionScope !== "single") {
3348
+ this.onSessionDied(sid);
3349
+ }
3350
+ cleanedStreams++;
3351
+ }
3352
+ }
3353
+ this.saveQQState();
3354
+ process.stderr.write(`[QQ:${this.name}] Removed from group ${sanitizeLogText(groupId, 64)} by ${sanitizeLogText(event.op_member_openid, 64)}, cleaned ${cleanedStreams} stream(s) and ${cleanedCron} cron buffer(s)
3355
+ `);
3356
+ }
3357
+ handleGroupMsgToggle(event, enabled) {
3358
+ if (!event.group_openid) {
3359
+ process.stderr.write(`[QQ:${this.name}] Group msg toggle dropped: missing group_openid
3360
+ `);
3361
+ return;
3362
+ }
3363
+ if (!isValidChatId(event.group_openid)) {
3364
+ process.stderr.write(`[QQ:${this.name}] Group msg toggle dropped: invalid group_openid
3365
+ `);
3366
+ return;
3367
+ }
3368
+ this.groupActiveMsgEnabled.set(event.group_openid, enabled);
3369
+ this.saveQQState();
3370
+ process.stderr.write(`[QQ:${this.name}] Active msg ${enabled ? "enabled" : "disabled"} for group ${sanitizeLogText(event.group_openid, 64)}
3371
+ `);
3372
+ }
2559
3373
  };
2560
3374
 
2561
3375
  // packages/channels/qqbot/dist/index.js