@qwen-code/qwen-code 0.19.5 → 0.19.6-nightly.20260705.015ee4248

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 (264) hide show
  1. package/bundled/dataviz/SKILL.md +50 -0
  2. package/bundled/dataviz/references/anti-patterns.md +12 -0
  3. package/bundled/dataviz/references/choosing-a-form.md +16 -0
  4. package/bundled/dataviz/references/palette.md +57 -0
  5. package/bundled/dataviz/scripts/validate_palette.js +328 -0
  6. package/bundled/loop/SKILL.md +1 -1
  7. package/bundled/qc-helper/docs/configuration/settings.md +14 -8
  8. package/bundled/qc-helper/docs/features/headless.md +2 -2
  9. package/bundled/qc-helper/docs/features/scheduled-tasks.md +4 -2
  10. package/bundled/qc-helper/docs/reference/keyboard-shortcuts.md +1 -1
  11. package/bundled/review/DESIGN.md +23 -0
  12. package/bundled/review/SKILL.md +65 -8
  13. package/chunks/{MaxSizedBox-5JV77KRA.js → MaxSizedBox-PRXTXIGC.js} +31 -30
  14. package/chunks/{StandaloneSessionPicker-MGUGB6PH.js → StandaloneSessionPicker-PV7BSC5D.js} +42 -41
  15. package/chunks/{acpAgent-4F36XM3X.js → acpAgent-F34HNAM3.js} +939 -424
  16. package/chunks/{agent-IWBUZI6A.js → agent-XSQDKLST.js} +26 -25
  17. package/chunks/{agent-headless-JUTICAYO.js → agent-headless-B63XCKWX.js} +26 -25
  18. package/chunks/{anthropicContentGenerator-WL3LJOUB.js → anthropicContentGenerator-INXXI4PW.js} +7 -7
  19. package/chunks/{artifact-tool-W76WC2JX.js → artifact-tool-H2TNPNP5.js} +16 -3
  20. package/chunks/{askUserQuestion-J3P6TH3V.js → askUserQuestion-MKL5ZTMC.js} +2 -2
  21. package/chunks/{bridge-DHZHQ4UV.js → bridge-NCZDJBRF.js} +34 -32
  22. package/chunks/{channel-worker-supervisor-EVPAYNZ5.js → channel-worker-supervisor-JUYQ5Z4R.js} +5 -5
  23. package/chunks/{chunk-AVQDCKNF.js → chunk-22MNNNXE.js} +4 -0
  24. package/chunks/chunk-3RW4AZJV.js +5063 -0
  25. package/chunks/chunk-3VJ7MT3N.js +111 -0
  26. package/chunks/{chunk-JKGCBO3O.js → chunk-3VRMGS3A.js} +4 -4
  27. package/chunks/{chunk-TN6RT6EJ.js → chunk-3WOJO5EJ.js} +16 -4
  28. package/chunks/{chunk-G7OQ3RFJ.js → chunk-42XGFRJS.js} +3 -3
  29. package/chunks/{chunk-MOUXUCXQ.js → chunk-4KJ2BPIT.js} +1 -1
  30. package/chunks/{chunk-PPFWEV7G.js → chunk-4RQIB5UZ.js} +62 -62
  31. package/chunks/{chunk-JJ6B3ZD3.js → chunk-544UZQBD.js} +3 -3
  32. package/chunks/{chunk-B236XPGF.js → chunk-5UDYETKK.js} +1 -1
  33. package/chunks/{chunk-7DMQRPC7.js → chunk-6BEOH4KK.js} +1 -1
  34. package/chunks/{chunk-CXBG27SF.js → chunk-6IAWPIQ6.js} +3 -2
  35. package/chunks/{chunk-MDBNFHIO.js → chunk-6MDY7FX3.js} +5 -5
  36. package/chunks/{chunk-RM3PGSGL.js → chunk-6SHO7WFF.js} +15 -2
  37. package/chunks/{chunk-YCU7QV7Q.js → chunk-73WGF3NW.js} +34 -1
  38. package/chunks/{chunk-T42ACHFI.js → chunk-7LTB54MK.js} +1 -1
  39. package/chunks/{chunk-SJVPSHXU.js → chunk-7U6J224Z.js} +132 -25
  40. package/chunks/{chunk-CSPSJU5R.js → chunk-7WLJPMWU.js} +17 -17
  41. package/chunks/{chunk-R6RI2HIC.js → chunk-ACD5DFUY.js} +41 -14
  42. package/chunks/{chunk-EWALXUAD.js → chunk-AGHU5VRK.js} +8 -0
  43. package/chunks/{chunk-GLPV75ES.js → chunk-AHNJGLFV.js} +2 -2
  44. package/chunks/{chunk-DRS2HTNT.js → chunk-AOAFX4PL.js} +5 -5
  45. package/chunks/{chunk-QHYITL6P.js → chunk-CK7IUFOB.js} +63 -22
  46. package/chunks/{chunk-RSBQS6M4.js → chunk-CODWJXQA.js} +1 -1
  47. package/chunks/{chunk-LWGLBVWA.js → chunk-CSOTA7KX.js} +1 -1
  48. package/chunks/{chunk-UJT42DB6.js → chunk-DFH4T5IQ.js} +6 -6
  49. package/chunks/{chunk-DX3TKXNT.js → chunk-DJACOX4C.js} +2 -2
  50. package/chunks/{chunk-KKQWC6WS.js → chunk-DNV4FSN5.js} +1 -1
  51. package/chunks/{chunk-YPCPZSZB.js → chunk-DT3B47QP.js} +1 -1
  52. package/chunks/{chunk-NIOPRZNN.js → chunk-E7KNELIZ.js} +1 -1
  53. package/chunks/{chunk-DG573EOO.js → chunk-FEJ2FZ3U.js} +1 -1
  54. package/chunks/{chunk-M5ITDKSM.js → chunk-FHGQFG2K.js} +1 -1
  55. package/chunks/{chunk-HISPEX42.js → chunk-FSXFNDTI.js} +7 -7
  56. package/chunks/{chunk-LHMZCIKL.js → chunk-FUW7YGJU.js} +3 -1
  57. package/chunks/{chunk-Y4UQT3PM.js → chunk-FV5SBNNC.js} +1 -1
  58. package/chunks/{chunk-YTFBURQD.js → chunk-GB6HDZIK.js} +10 -2
  59. package/chunks/{chunk-D5KISTLW.js → chunk-H6EMK6QK.js} +1 -1
  60. package/chunks/{chunk-2Z43HUXT.js → chunk-HLKG6CZN.js} +3 -2
  61. package/chunks/{chunk-VXGPYF7U.js → chunk-IFEWJXFS.js} +1 -1
  62. package/chunks/{chunk-TENOUWY2.js → chunk-IJRL2S3D.js} +3 -3
  63. package/chunks/{chunk-OQHLUA2Y.js → chunk-IL6ZEDZ2.js} +4 -4
  64. package/chunks/{chunk-2CAII5HD.js → chunk-J7XUJALJ.js} +1 -1
  65. package/chunks/chunk-JFFOH2XO.js +2472 -0
  66. package/chunks/{chunk-NNEJEBCF.js → chunk-K3FZXSTO.js} +95 -107
  67. package/chunks/{chunk-NDY7OENU.js → chunk-KB4ICCA5.js} +3 -3
  68. package/chunks/{chunk-MNOXKSM2.js → chunk-LFFF2SPV.js} +3 -3
  69. package/chunks/{chunk-J3ERRYYI.js → chunk-LTMDKOZC.js} +2 -2
  70. package/chunks/{chunk-R3UWLLRQ.js → chunk-M7AGQP5D.js} +1 -1
  71. package/chunks/{chunk-CBJE2PLG.js → chunk-MLB5AB2I.js} +4 -4
  72. package/chunks/{chunk-YHTNWGRR.js → chunk-MZ7BABX3.js} +1 -1
  73. package/chunks/{chunk-SJBIFVD2.js → chunk-NFE3DQ4R.js} +7 -3
  74. package/chunks/{chunk-BC4R5C5R.js → chunk-NLD2WKE3.js} +55 -12
  75. package/chunks/{chunk-ALX6BUYW.js → chunk-NUGPZXAZ.js} +35 -1
  76. package/chunks/{chunk-NSSVORTQ.js → chunk-NYKWPUAE.js} +17 -13
  77. package/chunks/{chunk-K4I4STSL.js → chunk-OGEJMKN5.js} +2 -2
  78. package/chunks/{chunk-HTWHAN44.js → chunk-PC4ZXHU2.js} +1 -1
  79. package/chunks/{chunk-2YH27UNF.js → chunk-QLBBV7C6.js} +1 -1
  80. package/chunks/chunk-QT7XKZT7.js +293 -0
  81. package/chunks/{chunk-NPQWQYU6.js → chunk-QYNNN7BK.js} +1 -1
  82. package/chunks/chunk-R4N3OALZ.js +5631 -0
  83. package/chunks/{chunk-G4M7FPNY.js → chunk-RB5GAQR6.js} +2 -2
  84. package/chunks/{chunk-SBMHB6N5.js → chunk-RPHMD5UC.js} +15 -15
  85. package/chunks/{chunk-CHY5C5T4.js → chunk-SJ2CTIYH.js} +185 -88
  86. package/chunks/chunk-SLQXGMYY.js +949 -0
  87. package/chunks/{chunk-BZHLWC6T.js → chunk-SUAKHHK7.js} +2 -2
  88. package/chunks/{chunk-IMAMNLY6.js → chunk-SWV4CVYG.js} +158 -3
  89. package/chunks/{chunk-XKSCTXPK.js → chunk-TDGF35EN.js} +4 -3
  90. package/chunks/{chunk-TOAUDIS7.js → chunk-TLWA7TPR.js} +6 -2
  91. package/chunks/{chunk-WALEWZ44.js → chunk-UVHL5TCO.js} +1 -1
  92. package/chunks/{chunk-ZISMVA6U.js → chunk-VDMM5LVR.js} +1566 -37
  93. package/chunks/{chunk-BPFJQGY7.js → chunk-VEB3NYSO.js} +164 -5
  94. package/chunks/{chunk-L7OJ3ZO3.js → chunk-VZ7HY3YU.js} +1 -1
  95. package/chunks/{chunk-6KPZJJ6U.js → chunk-W4I6PLEG.js} +131 -38
  96. package/chunks/{chunk-QOMAHFYI.js → chunk-W7EEOR7D.js} +3 -3
  97. package/chunks/{chunk-6QVQZAW6.js → chunk-WKP5QZOP.js} +1 -1
  98. package/chunks/{chunk-YXK4GPYO.js → chunk-WMPVYQ4P.js} +30 -1
  99. package/chunks/{chunk-DM4E337C.js → chunk-WNTS5A2R.js} +5 -5
  100. package/chunks/{chunk-6QTCOQX7.js → chunk-WOG7QWIT.js} +3086 -2635
  101. package/chunks/{chunk-UY2TCIZS.js → chunk-X265R5C6.js} +3 -3
  102. package/chunks/{chunk-LKWAVAB5.js → chunk-X2XNQQJI.js} +2 -2
  103. package/chunks/{chunk-MTNTVOJR.js → chunk-X3YGAX7V.js} +6 -3
  104. package/chunks/{chunk-EROWZQN7.js → chunk-XD3OLPII.js} +6 -6
  105. package/chunks/{chunk-RNGAT2Y2.js → chunk-YYL6HROQ.js} +1 -1
  106. package/chunks/{chunk-XKZXGIRM.js → chunk-YZ2LNTHO.js} +2 -2
  107. package/chunks/{chunk-Z7XNVNPM.js → chunk-YZ5ANQMR.js} +3 -3
  108. package/chunks/{chunk-4MCC4XHH.js → chunk-Z7EYROOJ.js} +3568 -5508
  109. package/chunks/{chunk-5B2WNM6V.js → chunk-ZI4MGBB5.js} +1 -1
  110. package/chunks/{chunk-WZ724G6K.js → chunk-ZPGE42V7.js} +6 -6
  111. package/chunks/{computer-use-ON3JQUHW.js → computer-use-5I2UHVNY.js} +26 -25
  112. package/chunks/{contextCommand-OGCG7W6N.js → contextCommand-3NWY7AJG.js} +28 -27
  113. package/chunks/{cron-create-6YTCOX6P.js → cron-create-O6ZHD3JN.js} +22 -7
  114. package/chunks/{cron-delete-E3NIFSUK.js → cron-delete-BFD5B2XX.js} +4 -4
  115. package/chunks/{cron-list-Q64MNQNE.js → cron-list-KI6FV4KN.js} +4 -4
  116. package/chunks/{daemon-EEB2RYOL.js → daemon-DRUMLVFP.js} +140 -0
  117. package/chunks/daemon-status-provider-A2DK3ZTO.js +74 -0
  118. package/chunks/{dist-AP5V4WYU.js → dist-AAO65DPC.js} +2 -2
  119. package/chunks/{dist-X4ZDE726.js → dist-ABQ7VWRR.js} +3 -3
  120. package/chunks/{dist-SNXOUPYS.js → dist-AW5ZYMUI.js} +2 -2
  121. package/chunks/{dist-Z57Q4YF5.js → dist-DN6M6TJ5.js} +525 -95
  122. package/chunks/{dist-IZP2VV4C.js → dist-JPSVN367.js} +2 -2
  123. package/chunks/{earlyInputCapture-DAFHSGVW.js → earlyInputCapture-5TMSSPQ5.js} +27 -26
  124. package/chunks/{edit-3QFNCSZH.js → edit-V4ITLSUD.js} +27 -26
  125. package/chunks/{en-M4EBUBNU.js → en-AXARKOWO.js} +2 -0
  126. package/chunks/{enter-worktree-62EQQMLD.js → enter-worktree-7ULPRMTJ.js} +26 -25
  127. package/chunks/{enterPlanMode-VUQSE4EG.js → enterPlanMode-YSHMLHJZ.js} +26 -25
  128. package/chunks/{errors-PW75PZXE.js → errors-TCUTD7NF.js} +28 -27
  129. package/chunks/{exit-worktree-VVNH7OVY.js → exit-worktree-JZDCKR6A.js} +26 -25
  130. package/chunks/{exitPlanMode-RUYRCYZW.js → exitPlanMode-GOQBN6SG.js} +26 -25
  131. package/chunks/{fast-path-TFRJRTTR.js → fast-path-F2HGAKO2.js} +3 -3
  132. package/chunks/{fast-path-settings-UQLRFDQP.js → fast-path-settings-RCRXH2JG.js} +2 -2
  133. package/chunks/{gemini-XW7ZDUVR.js → gemini-3M7ECKNW.js} +86 -80
  134. package/chunks/{geminiContentGenerator-MZDVHVZH.js → geminiContentGenerator-5EG3QEJM.js} +4 -4
  135. package/chunks/{glob-WAY2PUWA.js → glob-2DBDTLR4.js} +54 -29
  136. package/chunks/{grep-W6CBKSYC.js → grep-R27DLGWT.js} +26 -25
  137. package/chunks/{initializer-HK2XE6P2.js → initializer-OXB6BJZU.js} +32 -31
  138. package/chunks/{keychain-token-storage-GPAKRDKQ.js → keychain-token-storage-UJV5XP4Z.js} +2 -2
  139. package/chunks/{list-RVLIIKKL.js → list-HLCOEOSF.js} +33 -32
  140. package/chunks/{loadedSettingsAdapter-C63OUYZC.js → loadedSettingsAdapter-JHTVH3YO.js} +31 -30
  141. package/chunks/{loop-wakeup-G2EHBRGR.js → loop-wakeup-CUVIY2JY.js} +5 -5
  142. package/chunks/{ls-IDHUVSAG.js → ls-L654WOAT.js} +4 -4
  143. package/chunks/{lsp-FSRYXGFT.js → lsp-M35KE54Y.js} +2 -2
  144. package/chunks/mcp-2JWEFAIN.js +67 -0
  145. package/chunks/{monitor-FOG36ABM.js → monitor-5QKFZFDX.js} +26 -25
  146. package/chunks/nonInteractiveCli-W5W5PIHI.js +112 -0
  147. package/chunks/{notebook-edit-GC6NXRCX.js → notebook-edit-XAFBWORI.js} +27 -26
  148. package/chunks/{openaiContentGenerator-EXR3ZL3Z.js → openaiContentGenerator-NULJROYV.js} +13 -12
  149. package/chunks/{pidfile-FNZM2DDH.js → pidfile-3LT7PZSW.js} +27 -26
  150. package/chunks/{qwenContentGenerator-A7B6J4U7.js → qwenContentGenerator-KLSURVNH.js} +28 -27
  151. package/chunks/{qwenOAuth2-LWLA6MQG.js → qwenOAuth2-ZV7YLMND.js} +5 -5
  152. package/chunks/{read-file-6JL4ULMY.js → read-file-HDAM3RHJ.js} +8 -8
  153. package/chunks/{read-mcp-resource-PIJF24RO.js → read-mcp-resource-CNQHBSJM.js} +2 -2
  154. package/chunks/record-artifact-PP3LKHTS.js +343 -0
  155. package/chunks/{ripGrep-DIUGLJE5.js → ripGrep-W36NLR7M.js} +26 -25
  156. package/chunks/{run-qwen-serve-CME3AKM4.js → run-qwen-serve-IMI2CQB5.js} +72 -28
  157. package/chunks/{scheduler-L7F6GDDT.js → scheduler-I5NKE5UJ.js} +26 -25
  158. package/chunks/{send-message-33PYFMCT.js → send-message-OA6MV4EU.js} +3 -3
  159. package/chunks/{serve-SRCBOJGP.js → serve-JMHMOYBO.js} +33 -32
  160. package/chunks/{server-QSQ45QFR.js → server-QIYIN34N.js} +1358 -492
  161. package/chunks/{session-5DG6NXHZ.js → session-BLL6HB35.js} +68 -65
  162. package/chunks/{settings-3EEQI4MN.js → settings-ABECXCTY.js} +30 -29
  163. package/chunks/{shell-B7FNYF5S.js → shell-A2TQI3U4.js} +26 -25
  164. package/chunks/{skill-AAOGHD5O.js → skill-4GTWZZWR.js} +10 -10
  165. package/chunks/{spawnChannel-3CBN3ZLK.js → spawnChannel-DFUVGP53.js} +30 -29
  166. package/chunks/{src-T5A7P7FS.js → src-ZORBMUM7.js} +77 -43
  167. package/chunks/{startInteractiveUI-2FGEQ6AS.js → startInteractiveUI-FH5MVXUE.js} +449 -175
  168. package/chunks/{syntheticOutput-YOSWIHDJ.js → syntheticOutput-F7LSDCAR.js} +3 -3
  169. package/chunks/{task-create-NHS2NGB3.js → task-create-RPFZNB4B.js} +7 -7
  170. package/chunks/{task-list-5UIB564R.js → task-list-TGGJX7EA.js} +5 -5
  171. package/chunks/{task-stop-HGXZ2VZ3.js → task-stop-W7NGTMYQ.js} +2 -2
  172. package/chunks/{task-update-IYYWUILG.js → task-update-5WUG5MVQ.js} +7 -7
  173. package/chunks/{team-create-KDF6W6PV.js → team-create-F76QOR6H.js} +26 -25
  174. package/chunks/{team-delete-FEKJCU4P.js → team-delete-UHQQ2QQX.js} +5 -5
  175. package/chunks/{team-plan-approval-PSFKQX7M.js → team-plan-approval-OKM7YZC2.js} +26 -25
  176. package/chunks/{theme-manager-5XOA2AO7.js → theme-manager-6R7EVWHW.js} +27 -26
  177. package/chunks/{todoWrite-TKMVZ2HT.js → todoWrite-C4ZAMSR3.js} +4 -4
  178. package/chunks/{tool-search-LFRILZXC.js → tool-search-UWXDKI7D.js} +9 -9
  179. package/chunks/{trustedFolders-7HQY6T6X.js → trustedFolders-DHASVVUP.js} +27 -26
  180. package/chunks/{validateNonInterActiveAuth-UC27B7BA.js → validateNonInterActiveAuth-56J7MOOP.js} +62 -59
  181. package/chunks/version-NE2I4PBN.js +10 -0
  182. package/chunks/{web-fetch-J4PIW36Q.js → web-fetch-UYGMOHW4.js} +5 -5
  183. package/chunks/{workflow-KUTESS6L.js → workflow-HGF3URV3.js} +35 -27
  184. package/chunks/{workspace-providers-status-LJT5AQDS.js → workspace-providers-status-KXB2OZFQ.js} +33 -32
  185. package/chunks/{workspace-service-YOBYVDXH.js → workspace-service-NSTI55BC.js} +39 -38
  186. package/chunks/workspace-skills-status-X5S6JGQZ.js +69 -0
  187. package/chunks/{write-file-FJYBZUHP.js → write-file-HVKT5VTX.js} +27 -26
  188. package/chunks/yargs-B64EYTCF.js +10 -0
  189. package/chunks/{zh-SLZNSSZB.js → zh-RS3HZHFH.js} +2 -0
  190. package/chunks/{zh-TW-P53C2SL5.js → zh-TW-L5GRQ4S4.js} +2 -0
  191. package/cli-entry.js +81 -8
  192. package/cli.js +348 -55
  193. package/locales/en.js +2 -0
  194. package/locales/zh-TW.js +2 -0
  195. package/locales/zh.js +2 -0
  196. package/node_modules/@qwen-code/audio-capture/package.json +1 -1
  197. package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-arm64/@qwen-code+audio-capture.node +0 -0
  198. package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-x64/@qwen-code+audio-capture.node +0 -0
  199. package/node_modules/@qwen-code/audio-capture/prebuilds/win32-x64/@qwen-code+audio-capture.node +0 -0
  200. package/package.json +3 -3
  201. package/web-shell/assets/{arc-D_rAuMDd.js → arc-Csx4m4N0.js} +1 -1
  202. package/web-shell/assets/{architectureDiagram-3BPJPVTR-DUCzbHVo.js → architectureDiagram-3BPJPVTR-BEa0vCM7.js} +1 -1
  203. package/web-shell/assets/{blockDiagram-GPEHLZMM-BEEBt1LL.js → blockDiagram-GPEHLZMM-BPzbKnn3.js} +1 -1
  204. package/web-shell/assets/{c4Diagram-AAUBKEIU-5ZJ_DeFL.js → c4Diagram-AAUBKEIU-B8YO71yP.js} +1 -1
  205. package/web-shell/assets/channel-QU1XUuVe.js +1 -0
  206. package/web-shell/assets/{chunk-2J33WTMH-Cfw7EiLn.js → chunk-2J33WTMH-32XhwSHt.js} +1 -1
  207. package/web-shell/assets/{chunk-4BX2VUAB-FGuZNCZY.js → chunk-4BX2VUAB-Bmoyo6PS.js} +1 -1
  208. package/web-shell/assets/{chunk-55IACEB6-CaBwYPBF.js → chunk-55IACEB6-TwH6PFhs.js} +1 -1
  209. package/web-shell/assets/{chunk-727SXJPM-BlR04OFz.js → chunk-727SXJPM-RI_IVUtZ.js} +1 -1
  210. package/web-shell/assets/{chunk-AQP2D5EJ-D3_mYJH5.js → chunk-AQP2D5EJ-DH6GIAZD.js} +1 -1
  211. package/web-shell/assets/{chunk-FMBD7UC4-7D4q4SbB.js → chunk-FMBD7UC4-DeD3k3D5.js} +1 -1
  212. package/web-shell/assets/{chunk-ND2GUHAM-BjX_Iwmr.js → chunk-ND2GUHAM-BLPb8nv3.js} +1 -1
  213. package/web-shell/assets/{chunk-QZHKN3VN-C3Hz_ZQh.js → chunk-QZHKN3VN-PZpyglW1.js} +1 -1
  214. package/web-shell/assets/classDiagram-4FO5ZUOK-fv1EWO-L.js +1 -0
  215. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-fv1EWO-L.js +1 -0
  216. package/web-shell/assets/{cose-bilkent-S5V4N54A-Ax4UycaT.js → cose-bilkent-S5V4N54A-Jsd0yKfz.js} +1 -1
  217. package/web-shell/assets/{dagre-BM42HDAG-C6hmPtGt.js → dagre-BM42HDAG-B-zADciZ.js} +1 -1
  218. package/web-shell/assets/{diagram-2AECGRRQ-tXZnxjmw.js → diagram-2AECGRRQ-Cn6GDxoo.js} +1 -1
  219. package/web-shell/assets/{diagram-5GNKFQAL-99dwIzQG.js → diagram-5GNKFQAL-udLgyJxO.js} +1 -1
  220. package/web-shell/assets/{diagram-KO2AKTUF-Bqhn0-2s.js → diagram-KO2AKTUF-EcLpfzy3.js} +1 -1
  221. package/web-shell/assets/{diagram-LMA3HP47-DzThVSvo.js → diagram-LMA3HP47-D-YFbYsJ.js} +1 -1
  222. package/web-shell/assets/{diagram-OG6HWLK6-DPE8vjln.js → diagram-OG6HWLK6-C8oRUATa.js} +1 -1
  223. package/web-shell/assets/{erDiagram-TEJ5UH35-KzUKgQCV.js → erDiagram-TEJ5UH35-DPH5nStc.js} +1 -1
  224. package/web-shell/assets/{flowDiagram-I6XJVG4X-DBN-DXw5.js → flowDiagram-I6XJVG4X-CSOTwkAt.js} +1 -1
  225. package/web-shell/assets/{ganttDiagram-6RSMTGT7-DuBsP9u8.js → ganttDiagram-6RSMTGT7-D0L988t0.js} +1 -1
  226. package/web-shell/assets/{gitGraphDiagram-PVQCEYII-BjWHMyKR.js → gitGraphDiagram-PVQCEYII-B9Oo7gxS.js} +1 -1
  227. package/web-shell/assets/index-BYZZL5N7.css +5 -0
  228. package/web-shell/assets/index-C_oTyz0s.js +753 -0
  229. package/web-shell/assets/{infoDiagram-5YYISTIA-BVux0vP6.js → infoDiagram-5YYISTIA-Ddj4YN2P.js} +1 -1
  230. package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-2qzw7f--.js → ishikawaDiagram-YF4QCWOH-CBjafcl7.js} +1 -1
  231. package/web-shell/assets/{journeyDiagram-JHISSGLW-Dnl65sxL.js → journeyDiagram-JHISSGLW-Bn3XD99u.js} +1 -1
  232. package/web-shell/assets/{kanban-definition-UN3LZRKU-BElSQYtY.js → kanban-definition-UN3LZRKU-D7PBPJmE.js} +1 -1
  233. package/web-shell/assets/{linear-BjoycSSY.js → linear-Bmfo22rZ.js} +1 -1
  234. package/web-shell/assets/{mermaid.core-xe6bvMRz.js → mermaid.core-xRkAI-EI.js} +5 -5
  235. package/web-shell/assets/{mindmap-definition-RKZ34NQL-D8DQgI4U.js → mindmap-definition-RKZ34NQL-B30zRinm.js} +1 -1
  236. package/web-shell/assets/{pieDiagram-4H26LBE5-Dwh7Gp7c.js → pieDiagram-4H26LBE5-DblnKR9Z.js} +1 -1
  237. package/web-shell/assets/{quadrantDiagram-W4KKPZXB-BXKNBln2.js → quadrantDiagram-W4KKPZXB-COkHFKjF.js} +1 -1
  238. package/web-shell/assets/{requirementDiagram-4Y6WPE33-BzaC3cIA.js → requirementDiagram-4Y6WPE33-DDU3MMyn.js} +1 -1
  239. package/web-shell/assets/{sankeyDiagram-5OEKKPKP-DP2t7eZk.js → sankeyDiagram-5OEKKPKP-Dt4lqEYv.js} +1 -1
  240. package/web-shell/assets/{sequenceDiagram-3UESZ5HK-XlFiWEYv.js → sequenceDiagram-3UESZ5HK-BRzby5gN.js} +1 -1
  241. package/web-shell/assets/{stateDiagram-AJRCARHV-CupgUMJO.js → stateDiagram-AJRCARHV-CylzByc3.js} +1 -1
  242. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-BKWZIKkM.js +1 -0
  243. package/web-shell/assets/{timeline-definition-PNZ67QCA-DstuGywa.js → timeline-definition-PNZ67QCA-BSzaNx6w.js} +1 -1
  244. package/web-shell/assets/{vennDiagram-CIIHVFJN-CnbNp6zu.js → vennDiagram-CIIHVFJN-BbzeKzCo.js} +1 -1
  245. package/web-shell/assets/{wardley-L42UT6IY-DnvoHq6R.js → wardley-L42UT6IY-BRJwCgE_.js} +1 -1
  246. package/web-shell/assets/{wardleyDiagram-YWT4CUSO-Cz0xHEWB.js → wardleyDiagram-YWT4CUSO-BmAs0mJ-.js} +1 -1
  247. package/web-shell/assets/{xychartDiagram-2RQKCTM6-p36gfo88.js → xychartDiagram-2RQKCTM6-DqywSqVW.js} +1 -1
  248. package/web-shell/index.html +2 -2
  249. package/bundled/loop/SKILL.test.ts +0 -119
  250. package/bundled/loop/autonomous-loop.test.ts +0 -59
  251. package/bundled/loop/loop-task-file.test.ts +0 -1064
  252. package/bundled/loop/loop-tick-resolver.test.ts +0 -906
  253. package/chunks/chunk-EJ7BN3EN.js +0 -11396
  254. package/chunks/chunk-L7QPM2YD.js +0 -481
  255. package/chunks/daemon-status-provider-P72STMLB.js +0 -72
  256. package/chunks/nonInteractiveCli-6BYQSJTB.js +0 -109
  257. package/chunks/workspace-skills-status-ONEPXJV6.js +0 -62
  258. package/web-shell/assets/channel-B5ynH0q8.js +0 -1
  259. package/web-shell/assets/classDiagram-4FO5ZUOK-CK3ibywp.js +0 -1
  260. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-CK3ibywp.js +0 -1
  261. package/web-shell/assets/index-9qfvU0_r.css +0 -5
  262. package/web-shell/assets/index-CWzDaQT5.js +0 -717
  263. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-By3E4QwN.js +0 -1
  264. package/chunks/{chunk-UFC57OYT.js → chunk-VRDMOSQS.js} +54 -54
@@ -7,8 +7,8 @@ import {
7
7
  sanitizeLogText,
8
8
  sanitizePromptText,
9
9
  sanitizeSenderName
10
- } from "./chunk-7DMQRPC7.js";
11
- import "./chunk-UFC57OYT.js";
10
+ } from "./chunk-6BEOH4KK.js";
11
+ import "./chunk-VRDMOSQS.js";
12
12
  import "./chunk-K5PGHDBN.js";
13
13
  import {
14
14
  wrapper_default
@@ -1114,7 +1114,7 @@ init_esbuild_shims();
1114
1114
 
1115
1115
  // packages/channels/qqbot/dist/QQChannel.js
1116
1116
  init_esbuild_shims();
1117
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "node:fs";
1117
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, renameSync, unlinkSync } from "node:fs";
1118
1118
  import { join as join2 } from "node:path";
1119
1119
 
1120
1120
  // packages/channels/qqbot/dist/types.js
@@ -1351,8 +1351,11 @@ async function fetchAccessToken(appId, appSecret) {
1351
1351
  signal: AbortSignal.timeout(FETCH_TIMEOUT)
1352
1352
  });
1353
1353
  if (!resp.ok) {
1354
- const body = await resp.text().catch(() => "");
1355
- throw new Error(`QQ Bot token request failed (HTTP ${resp.status}): ${body}`);
1354
+ await resp.body?.cancel().catch(() => {
1355
+ });
1356
+ process.stderr.write(`[QQ] Token request failed (HTTP ${resp.status})
1357
+ `);
1358
+ throw new Error(`QQ Bot token request failed (HTTP ${resp.status})`);
1356
1359
  }
1357
1360
  const data = await resp.json();
1358
1361
  if (!data.access_token) {
@@ -1364,6 +1367,27 @@ async function fetchAccessToken(appId, appSecret) {
1364
1367
  };
1365
1368
  }
1366
1369
  __name(fetchAccessToken, "fetchAccessToken");
1370
+ function validateGatewayUrl(url) {
1371
+ try {
1372
+ const parsed = new URL(url);
1373
+ if (parsed.protocol !== "wss:") {
1374
+ throw new Error(`QQ Bot gateway URL must use wss:// protocol, got: ${parsed.protocol}`);
1375
+ }
1376
+ if (!parsed.hostname.toLowerCase().endsWith(".qq.com")) {
1377
+ throw new Error(`QQ Bot gateway URL has unexpected hostname: ${parsed.hostname} (expected *.qq.com)`);
1378
+ }
1379
+ const clean = new URL(url);
1380
+ clean.username = "";
1381
+ clean.password = "";
1382
+ return clean.href;
1383
+ } catch (e) {
1384
+ if (e instanceof TypeError) {
1385
+ throw new Error("QQ Bot gateway URL is not a valid URL");
1386
+ }
1387
+ throw e;
1388
+ }
1389
+ }
1390
+ __name(validateGatewayUrl, "validateGatewayUrl");
1367
1391
  async function fetchGatewayUrl(accessToken, sandbox) {
1368
1392
  const gw = sandbox ? `${SANDBOX_HOST}/gateway` : `${API_HOST}/gateway`;
1369
1393
  const resp = await fetch(gw, {
@@ -1371,13 +1395,15 @@ async function fetchGatewayUrl(accessToken, sandbox) {
1371
1395
  signal: AbortSignal.timeout(FETCH_TIMEOUT)
1372
1396
  });
1373
1397
  if (!resp.ok) {
1398
+ await resp.body?.cancel().catch(() => {
1399
+ });
1374
1400
  throw new Error(`QQ Bot gateway request failed (HTTP ${resp.status})`);
1375
1401
  }
1376
1402
  const data = await resp.json();
1377
1403
  if (!data["url"]) {
1378
1404
  throw new Error("QQ Bot gateway response missing WebSocket URL");
1379
1405
  }
1380
- return data["url"];
1406
+ return validateGatewayUrl(data["url"]);
1381
1407
  }
1382
1408
  __name(fetchGatewayUrl, "fetchGatewayUrl");
1383
1409
  function getApiBase(sandbox) {
@@ -1402,32 +1428,7 @@ function isValidChatId(id) {
1402
1428
  return /^[A-Za-z0-9_-]+$/.test(id) && id.length <= 128;
1403
1429
  }
1404
1430
  __name(isValidChatId, "isValidChatId");
1405
- function hasLinkSyntax(text) {
1406
- const open = text.indexOf("[");
1407
- if (open === -1)
1408
- return false;
1409
- const mid = text.indexOf("](", open + 1);
1410
- if (mid === -1)
1411
- return false;
1412
- return text.indexOf(")", mid + 2) !== -1;
1413
- }
1414
- __name(hasLinkSyntax, "hasLinkSyntax");
1415
- function hasMarkdownSyntax(text) {
1416
- return /^#{1,6}\s/m.test(text) || text.includes("```") || /\*\*|__|~~/.test(text) || /`[^`]+`/.test(text) || hasLinkSyntax(text) || /^[-*+]\s/m.test(text) || /^\d+\.\s/m.test(text);
1417
- }
1418
- __name(hasMarkdownSyntax, "hasMarkdownSyntax");
1419
- function splitText(text) {
1420
- const MAX = 2e3;
1421
- if (text.length <= MAX)
1422
- return [text];
1423
- const chunks = [];
1424
- for (let i = 0; i < text.length; i += MAX) {
1425
- chunks.push(text.slice(i, i + MAX));
1426
- }
1427
- return chunks;
1428
- }
1429
- __name(splitText, "splitText");
1430
- var QQChannel = class extends ChannelBase {
1431
+ var QQChannel = class _QQChannel extends ChannelBase {
1431
1432
  static {
1432
1433
  __name(this, "QQChannel");
1433
1434
  }
@@ -1459,8 +1460,12 @@ var QQChannel = class extends ChannelBase {
1459
1460
  lastHeartbeatAck = 0;
1460
1461
  /** Debounce timer for saveQQState to avoid blocking event loop. */
1461
1462
  saveTimer = null;
1463
+ /** beforeExit hook to flush state when the event loop drains naturally. Does NOT fire for SIGKILL, OOM kills, or uncaughtException. */
1464
+ beforeExitHook = null;
1462
1465
  /** Timer for reconnectWithRetry fallback (unref'd so it doesn't block exit). */
1463
1466
  reconnectTimer = null;
1467
+ /** 30s READY timeout to prevent hanging on gateway without response. */
1468
+ readyTimeout = null;
1464
1469
  /** Guard against parallel reconnectWithRetry chains from stale close events. */
1465
1470
  isReconnecting = false;
1466
1471
  /** Track whether a chatId is a group or C2C for correct API routing. */
@@ -1469,7 +1474,44 @@ var QQChannel = class extends ChannelBase {
1469
1474
  replyMsgId = /* @__PURE__ */ new Map();
1470
1475
  /** msg_seq counter per user messageId, for multi-block streaming. */
1471
1476
  msgSeqMap = /* @__PURE__ */ new Map();
1477
+ /** Periodic cleanup timer for expired replyMsgId entries. */
1478
+ replyMsgIdCleanupTimer = null;
1479
+ /** 5-minute TTL for replyMsgId entries and seenMessages dedup. */
1480
+ static REPLY_MSG_ID_TTL_MS = 3e5;
1481
+ /** Idle-flush timeout: buffer is sent after this many ms of silence. */
1482
+ static IDLE_FLUSH_MS = 2e3;
1483
+ /** Max consecutive send failures before the stream is abandoned. */
1484
+ static MAX_FLUSH_RETRIES = 3;
1485
+ /** Retry delay for subsequent attempts (backoff beyond first retry). */
1486
+ static IDLE_FLUSH_BACKOFF_MS = 4e3;
1487
+ /** Max buffer length before forcing an immediate flush. */
1488
+ static MAX_BUFFER_LENGTH = 4096;
1472
1489
  /** Path to persisted QQ routing state: chatTypeMap, replyMsgId, msgSeqMap. */
1490
+ /**
1491
+ * Streaming state machine with per-session buffers.
1492
+ *
1493
+ * Three states for each session:
1494
+ * active — accumulating chunks in buffer (onResponseChunk extends timer)
1495
+ * flushing — sendMessage() is in-flight (prevents parallel sends)
1496
+ * idle — waiting for next chunk (timer counting down to idleFlush)
1497
+ *
1498
+ * Transitions:
1499
+ * active → flushing: idleFlush timer fires, or onToolCall cancels timer
1500
+ * flushing → idle: send settles, idle timer restarts on retry
1501
+ * any → done: onResponseComplete sends remaining content
1502
+ *
1503
+ * Guards:
1504
+ * - flushingSessions prevents concurrent sends per session
1505
+ * - pendingStreamDelete defers cleanup until in-flight send resolves
1506
+ * - flushedSessions tracks already-sent sessions to skip final fullText
1507
+ */
1508
+ // ── Streaming state ───────────────────────────────────────────
1509
+ streamState = /* @__PURE__ */ new Map();
1510
+ flushingSessions = /* @__PURE__ */ new Set();
1511
+ pendingStreamDelete = /* @__PURE__ */ new Set();
1512
+ reconnectId = 0;
1513
+ blockStreaming = false;
1514
+ flushedSessions = /* @__PURE__ */ new Set();
1473
1515
  qqStatePath;
1474
1516
  /**
1475
1517
  * Path to the SessionRouter persistence file we back up before shutdown.
@@ -1492,6 +1534,7 @@ var QQChannel = class extends ChannelBase {
1492
1534
  registerBridgeEvents: options?.registerBridgeEvents ?? !hasExternalRouter
1493
1535
  });
1494
1536
  this.qqConfig = config;
1537
+ this.blockStreaming = this.config.blockStreaming === "on";
1495
1538
  this.qqStatePath = join2(stateDir, `${safeName}-state.json`);
1496
1539
  this.globalSessionsPath = hasExternalRouter ? join2(stateDir, "sessions.json") : sessionsPath;
1497
1540
  this.sessionsBackupPath = join2(stateDir, `${safeName}-sessions-backup.json`);
@@ -1499,76 +1542,151 @@ var QQChannel = class extends ChannelBase {
1499
1542
  // ── ChannelBase interface ──────────────────────────────────────
1500
1543
  async connect() {
1501
1544
  this.disposed = false;
1545
+ this.reconnectId++;
1502
1546
  if (!this.config.instructions) {
1503
1547
  this.config.instructions = [
1504
1548
  "## QQ Bot Channel",
1505
1549
  "",
1506
1550
  "\u4F60\u662F\u901A\u8FC7 QQ Bot \u4E0E\u7528\u6237\u5BF9\u8BDD\u7684 AI \u52A9\u624B\u3002",
1507
- "\u56DE\u590D\u63A7\u5236\u5728 2000 \u5B57\u7B26\u4EE5\u5185\uFF08\u8D85\u957F\u4F1A\u81EA\u52A8\u5206\u5757\uFF09\uFF0C\u652F\u6301 Markdown \u683C\u5F0F\u3002"
1551
+ "\u56DE\u590D\u63A7\u5236\u5728 2000 \u5B57\u7B26\u4EE5\u5185\uFF0C\u652F\u6301 Markdown \u683C\u5F0F\u3002"
1508
1552
  ].join("\n");
1509
1553
  }
1510
1554
  for (let attempt = 0; attempt < 3; attempt++) {
1511
1555
  try {
1512
1556
  await this.fetchToken();
1513
1557
  await this.connectGateway();
1558
+ if (this.beforeExitHook) {
1559
+ process.off("beforeExit", this.beforeExitHook);
1560
+ }
1561
+ this.beforeExitHook = () => this.flushQQState();
1562
+ process.on("beforeExit", this.beforeExitHook);
1563
+ this.startReplyMsgIdCleanup();
1514
1564
  return;
1515
1565
  } catch (e) {
1516
1566
  if (attempt < 2) {
1517
1567
  const msg = e instanceof Error ? e.message : String(e);
1518
- process.stderr.write(`[QQ:${this.name}] Connect attempt ${attempt + 1} failed: ${msg}, retrying...
1568
+ process.stderr.write(`[QQ:${this.name}] Connect attempt ${attempt + 1} failed: ${sanitizeLogText(msg, 200)}, retrying...
1519
1569
  `);
1520
1570
  await this.sleep(2e3);
1521
1571
  } else {
1522
- throw e;
1572
+ throw new Error(sanitizeLogText(e instanceof Error ? e.message : String(e), 200), { cause: e });
1523
1573
  }
1524
1574
  }
1525
1575
  }
1526
1576
  }
1527
1577
  async sendMessage(chatId, text) {
1578
+ if (text.trim() === "<noreply>") {
1579
+ process.stderr.write(`[QQ:${this.name}] <noreply> skipped for ${sanitizeLogText(chatId, 64)}
1580
+ `);
1581
+ return;
1582
+ }
1528
1583
  const route = await this.resolveRoute(chatId);
1529
1584
  if (!route)
1530
1585
  return;
1531
- const msgId = this.replyMsgId.get(chatId);
1532
- const useMarkdown = hasMarkdownSyntax(text);
1533
- for (const chunk of splitText(text)) {
1534
- try {
1535
- const body = useMarkdown ? { msg_type: 2, markdown: { content: chunk } } : { content: chunk, msg_type: 0 };
1536
- const nextSeq = msgId ? (this.msgSeqMap.get(msgId) ?? 0) + 1 : 0;
1537
- if (msgId) {
1538
- body["msg_id"] = msgId;
1539
- body["msg_seq"] = nextSeq;
1540
- }
1541
- let resp = await sendQQMessage(route.base, route.path, this.accessToken, body);
1542
- if (!resp.ok && useMarkdown) {
1543
- const errBody = await resp.text().catch(() => "");
1544
- process.stderr.write(`[QQ:${this.name}] Markdown rejected (HTTP ${resp.status}: ${errBody.slice(0, 100)}), retrying as plain text
1586
+ const entry = this.replyMsgId.get(chatId);
1587
+ const msgId = entry && Date.now() - entry.timestamp < _QQChannel.REPLY_MSG_ID_TTL_MS ? entry.msgId : void 0;
1588
+ if (entry && !msgId) {
1589
+ process.stderr.write(`[QQ:${this.name}] replyMsgId entry expired for ${sanitizeLogText(chatId, 64)}, reply context expired, sending without msg_id
1590
+ `);
1591
+ this.msgSeqMap.delete(entry.msgId);
1592
+ this.replyMsgId.delete(chatId);
1593
+ this.saveQQState();
1594
+ }
1595
+ let nextSeq = 0;
1596
+ let rollbackApplied = false;
1597
+ try {
1598
+ const body = {
1599
+ msg_type: 2,
1600
+ markdown: { content: text }
1601
+ };
1602
+ nextSeq = msgId ? (this.msgSeqMap.get(msgId) ?? 0) + 1 : 0;
1603
+ if (msgId) {
1604
+ this.msgSeqMap.set(msgId, nextSeq);
1605
+ body["msg_id"] = msgId;
1606
+ body["msg_seq"] = nextSeq;
1607
+ }
1608
+ const resp = await sendQQMessage(route.base, route.path, this.accessToken, body);
1609
+ 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)})
1612
+ `);
1613
+ if (resp.status === 429) {
1614
+ process.stderr.write(`[QQ:${this.name}] MESSAGE DROPPED: rate-limited (429) on markdown attempt for ${sanitizeLogText(chatId, 64)}
1545
1615
  `);
1546
- const plainBody = {
1547
- content: chunk,
1548
- msg_type: 0
1549
- };
1550
1616
  if (msgId) {
1551
- plainBody["msg_id"] = msgId;
1552
- plainBody["msg_seq"] = nextSeq;
1617
+ this.msgSeqMap.set(msgId, nextSeq - 1);
1618
+ this.saveQQState();
1553
1619
  }
1554
- resp = await sendQQMessage(route.base, route.path, this.accessToken, plainBody);
1620
+ return;
1555
1621
  }
1556
- if (!resp.ok) {
1557
- const errBody = await resp.text().catch(() => "");
1558
- process.stderr.write(`[QQ:${this.name}] Send HTTP ${resp.status} (msg_seq=${body["msg_seq"] ?? "-"}): ${errBody.slice(0, 200)}
1622
+ if (msgId) {
1623
+ this.msgSeqMap.set(msgId, nextSeq - 1);
1624
+ rollbackApplied = true;
1625
+ const activeBody = {
1626
+ content: text,
1627
+ msg_type: 0,
1628
+ msg_id: msgId,
1629
+ msg_seq: nextSeq
1630
+ };
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)}
1559
1634
  `);
1560
- break;
1635
+ const current = this.replyMsgId.get(chatId);
1636
+ if (current?.msgId === msgId) {
1637
+ this.msgSeqMap.set(msgId, nextSeq);
1638
+ }
1639
+ this.saveQQState();
1640
+ await activeResp.text().catch(() => "");
1641
+ return;
1642
+ }
1643
+ process.stderr.write(`[QQ:${this.name}] Active retry also failed (HTTP ${activeResp.status}: ${sanitizeLogText(await activeResp.text().catch(() => ""), 200)})
1644
+ `);
1645
+ if (activeResp.status === 429) {
1646
+ process.stderr.write(`[QQ:${this.name}] MESSAGE DROPPED: active retry rate-limited (HTTP 429) for ${sanitizeLogText(chatId, 64)}
1647
+ `);
1648
+ this.saveQQState();
1649
+ return;
1650
+ }
1651
+ process.stderr.write(`[QQ:${this.name}] MESSAGE DROPPED: both passive and active send failed for ${sanitizeLogText(chatId, 64)}
1652
+ `);
1653
+ this.saveQQState();
1654
+ return;
1561
1655
  }
1562
- if (msgId)
1563
- this.msgSeqMap.set(msgId, nextSeq);
1564
- } catch (e) {
1565
- process.stderr.write(`[QQ:${this.name}] Send error: ${e}
1656
+ const plainBody = {
1657
+ content: text,
1658
+ msg_type: 0
1659
+ };
1660
+ const fallbackRes = await sendQQMessage(route.base, route.path, this.accessToken, plainBody);
1661
+ if (!fallbackRes.ok) {
1662
+ const fbErrBody = await fallbackRes.text().catch(() => "");
1663
+ if (fallbackRes.status === 429) {
1664
+ process.stderr.write(`[QQ:${this.name}] MESSAGE DROPPED: rate-limited (429) on plain-text fallback for ${sanitizeLogText(chatId, 64)}
1566
1665
  `);
1567
- break;
1666
+ return;
1667
+ }
1668
+ process.stderr.write(`[QQ:${this.name}] MESSAGE DROPPED: plain-text fallback failed (HTTP ${fallbackRes.status}: ${sanitizeLogText(fbErrBody, 200)}) for ${sanitizeLogText(chatId, 64)}
1669
+ `);
1670
+ return;
1671
+ }
1672
+ process.stderr.write(`[QQ:${this.name}] Plain-text fallback succeeded for ${sanitizeLogText(chatId, 64)}
1673
+ `);
1674
+ await fallbackRes.text().catch(() => "");
1675
+ return;
1568
1676
  }
1677
+ await resp.text().catch(() => "");
1678
+ if (msgId)
1679
+ this.saveQQState();
1680
+ } catch (e) {
1681
+ if (msgId && !rollbackApplied) {
1682
+ this.msgSeqMap.set(msgId, nextSeq - 1);
1683
+ }
1684
+ if (msgId)
1685
+ this.saveQQState();
1686
+ process.stderr.write(`[QQ:${this.name}] Send error: ${sanitizeLogText(e instanceof Error ? e.message : String(e), 200)}
1687
+ `);
1688
+ throw e;
1569
1689
  }
1570
- if (msgId)
1571
- this.saveQQState();
1572
1690
  }
1573
1691
  /**
1574
1692
  * Resolve API routing: handles disposed check, token refresh, chatId validation,
@@ -1594,6 +1712,7 @@ var QQChannel = class extends ChannelBase {
1594
1712
  this.disposed = true;
1595
1713
  this.stopHeartbeat();
1596
1714
  this.stopTokenRefresh();
1715
+ this.stopReplyMsgIdCleanup();
1597
1716
  if (this.seenCleanupTimer) {
1598
1717
  clearInterval(this.seenCleanupTimer);
1599
1718
  this.seenCleanupTimer = null;
@@ -1602,8 +1721,16 @@ var QQChannel = class extends ChannelBase {
1602
1721
  clearTimeout(this.reconnectTimer);
1603
1722
  this.reconnectTimer = null;
1604
1723
  }
1724
+ if (this.beforeExitHook) {
1725
+ process.off("beforeExit", this.beforeExitHook);
1726
+ this.beforeExitHook = null;
1727
+ }
1605
1728
  this.flushQQState();
1606
1729
  this.backupGlobalSessions();
1730
+ if (this.readyTimeout) {
1731
+ clearTimeout(this.readyTimeout);
1732
+ this.readyTimeout = null;
1733
+ }
1607
1734
  if (this.ws) {
1608
1735
  this.ws.close(1e3);
1609
1736
  this.ws = null;
@@ -1615,6 +1742,16 @@ var QQChannel = class extends ChannelBase {
1615
1742
  this.chatTypeMap.clear();
1616
1743
  this.replyMsgId.clear();
1617
1744
  this.msgSeqMap.clear();
1745
+ for (const [, state] of this.streamState) {
1746
+ if (state.timer)
1747
+ clearTimeout(state.timer);
1748
+ }
1749
+ this.streamState.clear();
1750
+ this.flushingSessions.clear();
1751
+ this.pendingStreamDelete.clear();
1752
+ this.flushedSessions.clear();
1753
+ this.seenMessages.clear();
1754
+ this.reconnectId++;
1618
1755
  }
1619
1756
  /**
1620
1757
  * QQ Bot API V2 does not provide a typing indicator endpoint.
@@ -1625,21 +1762,210 @@ var QQChannel = class extends ChannelBase {
1625
1762
  }
1626
1763
  onPromptEnd(_chatId, _sessionId, _messageId) {
1627
1764
  }
1765
+ // ── Streaming (idle-flush with per-session buffers) ────────────
1766
+ onResponseChunk(chatId, chunk, sessionId) {
1767
+ if (this.blockStreaming)
1768
+ return;
1769
+ let state = this.streamState.get(sessionId);
1770
+ if (!state) {
1771
+ state = { chatId, buffer: chunk, timer: null, retryCount: 0 };
1772
+ this.streamState.set(sessionId, state);
1773
+ } else {
1774
+ state.buffer += chunk;
1775
+ if (state.timer) {
1776
+ clearTimeout(state.timer);
1777
+ state.timer = null;
1778
+ }
1779
+ if (state.buffer.length >= _QQChannel.MAX_BUFFER_LENGTH) {
1780
+ const buf = state.buffer;
1781
+ state.buffer = "";
1782
+ this.flushAndTrack(sessionId, buf, state, "idleFlush");
1783
+ return;
1784
+ }
1785
+ }
1786
+ const reconnectId = this.reconnectId;
1787
+ state.timer = setTimeout(() => {
1788
+ this.idleFlush(sessionId, reconnectId);
1789
+ }, _QQChannel.IDLE_FLUSH_MS);
1790
+ state.timer.unref?.();
1791
+ }
1792
+ idleFlush(sessionId, reconnectId) {
1793
+ if (this.reconnectId !== reconnectId) {
1794
+ process.stderr.write(`[QQ:${this.name}] idleFlush discarded (reconnect) session=${sanitizeLogText(sessionId, 32)}
1795
+ `);
1796
+ return;
1797
+ }
1798
+ const state = this.streamState.get(sessionId);
1799
+ if (!state || !state.buffer)
1800
+ return;
1801
+ if (this.flushingSessions.has(sessionId)) {
1802
+ if (!state.timer) {
1803
+ const retryReconnectId = this.reconnectId;
1804
+ state.timer = setTimeout(() => {
1805
+ this.idleFlush(sessionId, retryReconnectId);
1806
+ }, _QQChannel.IDLE_FLUSH_MS);
1807
+ state.timer.unref?.();
1808
+ }
1809
+ return;
1810
+ }
1811
+ const buffer = state.buffer;
1812
+ state.buffer = "";
1813
+ state.timer = null;
1814
+ this.flushAndTrack(sessionId, buffer, state, "idleFlush");
1815
+ }
1816
+ /**
1817
+ * Shared send-and-track helper used by idleFlush and onToolCall.
1818
+ * Encapsulates .then() (cleanup on success) and .catch() (retry/re-buffer
1819
+ * on failure) logic to eliminate duplication.
1820
+ */
1821
+ flushAndTrack(sessionId, buffer, state, logLabel) {
1822
+ this.flushingSessions.add(sessionId);
1823
+ this.sendMessage(state.chatId, buffer).then(() => {
1824
+ const current = this.streamState.get(sessionId);
1825
+ if (current !== state)
1826
+ return;
1827
+ current.retryCount = 0;
1828
+ this.flushedSessions.add(sessionId);
1829
+ if (this.pendingStreamDelete.has(sessionId)) {
1830
+ this.pendingStreamDelete.delete(sessionId);
1831
+ const s2 = this.streamState.get(sessionId);
1832
+ if (s2 === state && s2.buffer) {
1833
+ this.idleFlush(sessionId, this.reconnectId);
1834
+ }
1835
+ }
1836
+ const s = this.streamState.get(sessionId);
1837
+ if (s === state && !s.buffer) {
1838
+ this.streamState.delete(sessionId);
1839
+ }
1840
+ }).catch((e) => {
1841
+ process.stderr.write(`[QQ:${this.name}] ${logLabel} send failed: ${sanitizeLogText(e instanceof Error ? e.message : String(e), 200)}
1842
+ `);
1843
+ if (this.pendingStreamDelete.has(sessionId)) {
1844
+ this.pendingStreamDelete.delete(sessionId);
1845
+ const current = this.streamState.get(sessionId);
1846
+ if (current === state) {
1847
+ current.buffer = buffer;
1848
+ current.retryCount++;
1849
+ if (current.retryCount < _QQChannel.MAX_FLUSH_RETRIES) {
1850
+ const reconnectId = this.reconnectId;
1851
+ const delay = current.retryCount > 1 ? _QQChannel.IDLE_FLUSH_BACKOFF_MS : _QQChannel.IDLE_FLUSH_MS;
1852
+ current.timer = setTimeout(() => {
1853
+ this.idleFlush(sessionId, reconnectId);
1854
+ }, delay);
1855
+ current.timer.unref?.();
1856
+ } else {
1857
+ this.streamState.delete(sessionId);
1858
+ this.flushedSessions.delete(sessionId);
1859
+ process.stderr.write(`[QQ:${this.name}] ${logLabel} retries exhausted for ${sanitizeLogText(sessionId, 64)}
1860
+ `);
1861
+ }
1862
+ }
1863
+ } else {
1864
+ const current = this.streamState.get(sessionId);
1865
+ if (current === state) {
1866
+ current.buffer = buffer + (current.buffer || "");
1867
+ if (current.buffer.length >= _QQChannel.MAX_BUFFER_LENGTH) {
1868
+ this.idleFlush(sessionId, this.reconnectId);
1869
+ } else {
1870
+ current.retryCount++;
1871
+ if (current.retryCount < _QQChannel.MAX_FLUSH_RETRIES) {
1872
+ if (!current.timer) {
1873
+ const reconnectId = this.reconnectId;
1874
+ const delay = current.retryCount > 1 ? _QQChannel.IDLE_FLUSH_BACKOFF_MS : _QQChannel.IDLE_FLUSH_MS;
1875
+ current.timer = setTimeout(() => {
1876
+ this.idleFlush(sessionId, reconnectId);
1877
+ }, delay);
1878
+ current.timer.unref?.();
1879
+ }
1880
+ } else {
1881
+ this.streamState.delete(sessionId);
1882
+ this.flushedSessions.delete(sessionId);
1883
+ process.stderr.write(`[QQ:${this.name}] ${logLabel} retries exhausted for ${sanitizeLogText(sessionId, 64)}
1884
+ `);
1885
+ }
1886
+ }
1887
+ }
1888
+ }
1889
+ }).finally(() => {
1890
+ const current = this.streamState.get(sessionId);
1891
+ if (!current || current === state) {
1892
+ this.flushingSessions.delete(sessionId);
1893
+ }
1894
+ });
1895
+ }
1896
+ onToolCall(_chatId, event) {
1897
+ const state = this.streamState.get(event.sessionId);
1898
+ if (!state || !state.buffer)
1899
+ return;
1900
+ if (this.flushingSessions.has(event.sessionId))
1901
+ return;
1902
+ if (state.timer) {
1903
+ clearTimeout(state.timer);
1904
+ state.timer = null;
1905
+ }
1906
+ const buffer = state.buffer;
1907
+ state.buffer = "";
1908
+ this.flushAndTrack(event.sessionId, buffer, state, "toolCallFlush");
1909
+ }
1910
+ async onResponseComplete(chatId, fullText, sessionId) {
1911
+ const state = this.streamState.get(sessionId);
1912
+ if (state?.timer) {
1913
+ clearTimeout(state.timer);
1914
+ state.timer = null;
1915
+ }
1916
+ if (state && this.flushingSessions.has(sessionId)) {
1917
+ this.pendingStreamDelete.add(sessionId);
1918
+ process.stderr.write(`[QQ:${this.name}] onResponseComplete deferred (flush in-flight) session=${sanitizeLogText(sessionId, 32)}
1919
+ `);
1920
+ return;
1921
+ }
1922
+ const wasFlushed = this.flushedSessions.has(sessionId);
1923
+ const remaining = state?.buffer ?? (wasFlushed ? "" : fullText);
1924
+ this.streamState.delete(sessionId);
1925
+ this.flushedSessions.delete(sessionId);
1926
+ if (remaining) {
1927
+ await super.onResponseComplete(chatId, remaining, sessionId);
1928
+ }
1929
+ }
1930
+ onSessionDied(sessionId) {
1931
+ const state = this.streamState.get(sessionId);
1932
+ if (state?.timer) {
1933
+ clearTimeout(state.timer);
1934
+ }
1935
+ this.streamState.delete(sessionId);
1936
+ this.flushingSessions.delete(sessionId);
1937
+ this.pendingStreamDelete.delete(sessionId);
1938
+ this.flushedSessions.delete(sessionId);
1939
+ super.onSessionDied(sessionId);
1940
+ }
1628
1941
  // ── State Persistence (cross-server context continuation) ──────
1629
1942
  /** Debounced state persistence to avoid blocking event loop. */
1630
1943
  saveQQState() {
1944
+ if (this.disposed)
1945
+ return;
1631
1946
  if (this.saveTimer)
1632
1947
  clearTimeout(this.saveTimer);
1948
+ const tmpPath = this.qqStatePath + ".tmp";
1633
1949
  this.saveTimer = setTimeout(() => {
1950
+ if (this.disposed)
1951
+ return;
1634
1952
  try {
1635
- writeFileSync2(this.qqStatePath, JSON.stringify({
1953
+ writeFileSync2(tmpPath, JSON.stringify({
1636
1954
  chatTypeMap: Array.from(this.chatTypeMap.entries()),
1637
1955
  replyMsgId: Array.from(this.replyMsgId.entries()),
1638
1956
  msgSeqMap: Array.from(this.msgSeqMap.entries())
1639
1957
  }), { mode: 384 });
1640
- } catch {
1958
+ renameSync(tmpPath, this.qqStatePath);
1959
+ } catch (e) {
1960
+ try {
1961
+ unlinkSync(tmpPath);
1962
+ } catch {
1963
+ }
1964
+ process.stderr.write(`[QQ:${this.name}] saveQQState write failed: ${sanitizeLogText(e instanceof Error ? e.message : String(e), 200)}
1965
+ `);
1641
1966
  }
1642
1967
  }, 500);
1968
+ this.saveTimer.unref();
1643
1969
  }
1644
1970
  /** Flush pending state writes immediately (called on disconnect). */
1645
1971
  flushQQState() {
@@ -1647,35 +1973,78 @@ var QQChannel = class extends ChannelBase {
1647
1973
  clearTimeout(this.saveTimer);
1648
1974
  this.saveTimer = null;
1649
1975
  }
1976
+ const tmpPath = this.qqStatePath + ".tmp";
1650
1977
  try {
1651
- writeFileSync2(this.qqStatePath, JSON.stringify({
1978
+ writeFileSync2(tmpPath, JSON.stringify({
1652
1979
  chatTypeMap: Array.from(this.chatTypeMap.entries()),
1653
1980
  replyMsgId: Array.from(this.replyMsgId.entries()),
1654
1981
  msgSeqMap: Array.from(this.msgSeqMap.entries())
1655
- }));
1656
- } catch {
1982
+ }), { mode: 384 });
1983
+ renameSync(tmpPath, this.qqStatePath);
1984
+ } catch (e) {
1985
+ try {
1986
+ unlinkSync(tmpPath);
1987
+ } catch {
1988
+ }
1989
+ process.stderr.write(`[QQ:${this.name}] flushQQState write failed: ${sanitizeLogText(e instanceof Error ? e.message : String(e), 200)}
1990
+ `);
1657
1991
  }
1658
1992
  }
1659
1993
  /**
1660
1994
  * Restore QQ routing state from disk.
1661
- * Trusts persisted JSON if the file is corrupt, new Map() may create
1662
- * entries with undefined values, causing get()===undefined to fall through
1663
- * to default routing (C2C). This is acceptable for a rare edge case.
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.
1664
1998
  */
1665
1999
  restoreQQState() {
1666
2000
  try {
1667
2001
  if (!existsSync2(this.qqStatePath))
1668
2002
  return false;
1669
2003
  const raw = JSON.parse(readFileSync2(this.qqStatePath, "utf-8"));
1670
- if (raw.chatTypeMap)
1671
- this.chatTypeMap = new Map(raw.chatTypeMap);
1672
- if (raw.replyMsgId)
1673
- this.replyMsgId = new Map(raw.replyMsgId);
1674
- if (raw.msgSeqMap)
1675
- this.msgSeqMap = new Map(raw.msgSeqMap);
2004
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
2005
+ process.stderr.write(`[QQ:${this.name}] Invalid QQ state file (not an object), ignoring
2006
+ `);
2007
+ return false;
2008
+ }
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)
2028
+ 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
2035
+ `);
2036
+ }
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
2043
+ `);
2044
+ }
1676
2045
  return true;
1677
2046
  } catch (e) {
1678
- process.stderr.write(`[QQ:${this.name}] Failed to restore QQ state: ${e instanceof Error ? e.message : String(e)}
2047
+ process.stderr.write(`[QQ:${this.name}] Failed to restore QQ state: ${sanitizeLogText(e instanceof Error ? e.message : String(e), 200)}
1679
2048
  `);
1680
2049
  return false;
1681
2050
  }
@@ -1744,6 +2113,47 @@ var QQChannel = class extends ChannelBase {
1744
2113
  } catch {
1745
2114
  }
1746
2115
  }
2116
+ // ── ReplyMsgId helpers ────────────────────────────────────────
2117
+ /**
2118
+ * Set replyMsgId for a chat, cleaning up the previous entry's msgSeqMap
2119
+ * to prevent orphaned entries accumulating over time.
2120
+ */
2121
+ setReplyMsgId(chatId, msgId) {
2122
+ const oldEntry = this.replyMsgId.get(chatId);
2123
+ if (oldEntry && oldEntry.msgId !== msgId) {
2124
+ this.msgSeqMap.delete(oldEntry.msgId);
2125
+ }
2126
+ this.replyMsgId.set(chatId, { msgId, timestamp: Date.now() });
2127
+ this.saveQQState();
2128
+ }
2129
+ /**
2130
+ * Start periodic cleanup of expired replyMsgId entries.
2131
+ * Evicts entries older than 5 minutes every 60 seconds, and cascades
2132
+ * to msgSeqMap.
2133
+ */
2134
+ startReplyMsgIdCleanup() {
2135
+ this.stopReplyMsgIdCleanup();
2136
+ this.replyMsgIdCleanupTimer = setInterval(() => {
2137
+ const cutoff = Date.now() - _QQChannel.REPLY_MSG_ID_TTL_MS;
2138
+ let dirty = false;
2139
+ for (const [chatId, entry] of this.replyMsgId) {
2140
+ if (entry.timestamp < cutoff) {
2141
+ this.msgSeqMap.delete(entry.msgId);
2142
+ this.replyMsgId.delete(chatId);
2143
+ dirty = true;
2144
+ }
2145
+ }
2146
+ if (dirty)
2147
+ this.saveQQState();
2148
+ }, 6e4);
2149
+ this.replyMsgIdCleanupTimer.unref();
2150
+ }
2151
+ stopReplyMsgIdCleanup() {
2152
+ if (this.replyMsgIdCleanupTimer) {
2153
+ clearInterval(this.replyMsgIdCleanupTimer);
2154
+ this.replyMsgIdCleanupTimer = null;
2155
+ }
2156
+ }
1747
2157
  // ── Token ──────────────────────────────────────────────────────
1748
2158
  async fetchToken() {
1749
2159
  const safeName = this.name.replace(/[^A-Za-z0-9_-]/g, "_");
@@ -1785,7 +2195,7 @@ var QQChannel = class extends ChannelBase {
1785
2195
  this.fetchToken().catch((e) => {
1786
2196
  if (this.disposed)
1787
2197
  return;
1788
- process.stderr.write(`[QQ:${this.name}] Token refresh failed: ${e}, retrying in 60s
2198
+ process.stderr.write(`[QQ:${this.name}] Token refresh failed: ${sanitizeLogText(e instanceof Error ? e.message : String(e), 200)}, retrying in 60s
1789
2199
  `);
1790
2200
  this.scheduleTokenRefreshRetry();
1791
2201
  });
@@ -1800,7 +2210,7 @@ var QQChannel = class extends ChannelBase {
1800
2210
  this.fetchToken().catch((e) => {
1801
2211
  if (this.disposed)
1802
2212
  return;
1803
- process.stderr.write(`[QQ:${this.name}] Token refresh failed: ${e}, retrying in 60s
2213
+ process.stderr.write(`[QQ:${this.name}] Token refresh failed: ${sanitizeLogText(e instanceof Error ? e.message : String(e), 200)}, retrying in 60s
1804
2214
  `);
1805
2215
  this.scheduleTokenRefreshRetry();
1806
2216
  });
@@ -1825,6 +2235,13 @@ var QQChannel = class extends ChannelBase {
1825
2235
  dialGateway(url, resolve, reject) {
1826
2236
  this.ws = new wrapper_default(url);
1827
2237
  const dialed = this.ws;
2238
+ this.readyTimeout = setTimeout(() => {
2239
+ if (this.ws !== dialed)
2240
+ return;
2241
+ this.ws.close(4002, "READY timeout");
2242
+ reject(new Error(`[QQ:${this.name}] READY timeout after 30s`));
2243
+ }, 3e4);
2244
+ this.readyTimeout.unref?.();
1828
2245
  this.ws.on("open", () => {
1829
2246
  process.stderr.write(`[QQ:${this.name}] WebSocket connected
1830
2247
  `);
@@ -1834,7 +2251,7 @@ var QQChannel = class extends ChannelBase {
1834
2251
  const msg = JSON.parse(data.toString());
1835
2252
  this.handleGatewayMessage(msg, resolve);
1836
2253
  } catch (e) {
1837
- process.stderr.write(`[QQ:${this.name}] Malformed gateway message: ${e instanceof Error ? e.message : String(e)}
2254
+ process.stderr.write(`[QQ:${this.name}] Malformed gateway message: ${sanitizeLogText(e instanceof Error ? e.message : String(e), 200)}
1838
2255
  `);
1839
2256
  }
1840
2257
  });
@@ -1843,6 +2260,10 @@ var QQChannel = class extends ChannelBase {
1843
2260
  return;
1844
2261
  process.stderr.write(`[QQ:${this.name}] WebSocket closed (code=${code})
1845
2262
  `);
2263
+ if (this.readyTimeout) {
2264
+ clearTimeout(this.readyTimeout);
2265
+ this.readyTimeout = null;
2266
+ }
1846
2267
  this.stopHeartbeat();
1847
2268
  this.ws = null;
1848
2269
  const shouldReconnect = this.serverRequestedReconnect || code !== 1e3 && this.reconnectAttempts < this.maxReconnectAttempts;
@@ -1874,7 +2295,7 @@ var QQChannel = class extends ChannelBase {
1874
2295
  }
1875
2296
  });
1876
2297
  this.ws.on("error", (e) => {
1877
- process.stderr.write(`[QQ:${this.name}] WebSocket error: ${e.message}
2298
+ process.stderr.write(`[QQ:${this.name}] WebSocket error: ${sanitizeLogText(e.message, 200)}
1878
2299
  `);
1879
2300
  if (!this.ws || this.ws.readyState !== wrapper_default.OPEN) {
1880
2301
  reject(e);
@@ -1897,6 +2318,10 @@ var QQChannel = class extends ChannelBase {
1897
2318
  if (t === "READY") {
1898
2319
  this.reconnectAttempts = 0;
1899
2320
  this.isReconnecting = false;
2321
+ if (this.readyTimeout) {
2322
+ clearTimeout(this.readyTimeout);
2323
+ this.readyTimeout = null;
2324
+ }
1900
2325
  this.sessionId = msg["d"]?.["session_id"] || "";
1901
2326
  this.tryResume = true;
1902
2327
  this.connectReject = null;
@@ -1918,6 +2343,10 @@ var QQChannel = class extends ChannelBase {
1918
2343
  } else if (t === "RESUMED") {
1919
2344
  this.reconnectAttempts = 0;
1920
2345
  this.isReconnecting = false;
2346
+ if (this.readyTimeout) {
2347
+ clearTimeout(this.readyTimeout);
2348
+ this.readyTimeout = null;
2349
+ }
1921
2350
  this.connectReject = null;
1922
2351
  this.startHeartbeat();
1923
2352
  onReady();
@@ -2002,7 +2431,7 @@ var QQChannel = class extends ChannelBase {
2002
2431
  } catch (e) {
2003
2432
  const msg = e instanceof Error ? e.message : String(e);
2004
2433
  const backoff = Math.min(1e3 * 2 ** (attempt + 1), 3e4);
2005
- process.stderr.write(`[QQ:${this.name}] RC: ${msg} (retry in ${backoff}ms, attempt ${attempt + 1}/${maxGwRetries})
2434
+ process.stderr.write(`[QQ:${this.name}] RC: ${sanitizeLogText(msg, 200)} (retry in ${backoff}ms, attempt ${attempt + 1}/${maxGwRetries})
2006
2435
  `);
2007
2436
  if (attempt < maxGwRetries - 1)
2008
2437
  await this.sleep(backoff);
@@ -2035,6 +2464,7 @@ var QQChannel = class extends ChannelBase {
2035
2464
  }
2036
2465
  this.ws.send(JSON.stringify({ op: OpCode.HEARTBEAT, d: this.seq }));
2037
2466
  }, this.heartbeatInterval);
2467
+ this.heartbeatTimer.unref();
2038
2468
  }
2039
2469
  stopHeartbeat() {
2040
2470
  if (this.heartbeatTimer) {
@@ -2051,7 +2481,7 @@ var QQChannel = class extends ChannelBase {
2051
2481
  this.seenMessages.set(eventId, now);
2052
2482
  if (!this.seenCleanupTimer) {
2053
2483
  this.seenCleanupTimer = setInterval(() => {
2054
- const cutoff = Date.now() - 3e5;
2484
+ const cutoff = Date.now() - _QQChannel.REPLY_MSG_ID_TTL_MS;
2055
2485
  for (const [id, ts] of this.seenMessages) {
2056
2486
  if (ts < cutoff)
2057
2487
  this.seenMessages.delete(id);
@@ -2060,7 +2490,7 @@ var QQChannel = class extends ChannelBase {
2060
2490
  clearInterval(this.seenCleanupTimer);
2061
2491
  this.seenCleanupTimer = null;
2062
2492
  }
2063
- }, 6e4);
2493
+ }, 6e4).unref();
2064
2494
  }
2065
2495
  return false;
2066
2496
  }
@@ -2069,10 +2499,11 @@ var QQChannel = class extends ChannelBase {
2069
2499
  return;
2070
2500
  if (!event.content?.trim())
2071
2501
  return;
2502
+ if (!event.author)
2503
+ return;
2072
2504
  const chatId = event.author.user_openid || event.author.id;
2073
2505
  this.chatTypeMap.set(chatId, "c2c");
2074
- this.replyMsgId.set(chatId, event.id);
2075
- this.saveQQState();
2506
+ this.setReplyMsgId(chatId, event.id);
2076
2507
  this.handleInbound({
2077
2508
  channelName: this.name,
2078
2509
  senderId: chatId,
@@ -2083,7 +2514,7 @@ var QQChannel = class extends ChannelBase {
2083
2514
  isGroup: false,
2084
2515
  isMentioned: true,
2085
2516
  isReplyToBot: false
2086
- }).catch((e) => process.stderr.write(`[QQ:${this.name}] C2C handler error: ${e}
2517
+ }).catch((e) => process.stderr.write(`[QQ:${this.name}] C2C handler error: ${sanitizeLogText(e instanceof Error ? e.message : String(e), 200)}
2087
2518
  `));
2088
2519
  }
2089
2520
  handleGroup(event) {
@@ -2096,8 +2527,7 @@ var QQChannel = class extends ChannelBase {
2096
2527
  }
2097
2528
  const chatId = event.group_openid;
2098
2529
  this.chatTypeMap.set(chatId, "group");
2099
- this.replyMsgId.set(chatId, event.id);
2100
- this.saveQQState();
2530
+ this.setReplyMsgId(chatId, event.id);
2101
2531
  const senderName = event.author.username || event.author.id || "QQ User";
2102
2532
  const cleanText = (event.content || "").replace(/<@[^>]{1,64}>/g, "").trim();
2103
2533
  if (!cleanText)
@@ -2123,7 +2553,7 @@ var QQChannel = class extends ChannelBase {
2123
2553
  // every group message is semantically a reply to the bot.
2124
2554
  isReplyToBot: true,
2125
2555
  ...isSlash ? {} : { alreadyPrefixed: true }
2126
- }).catch((e) => process.stderr.write(`[QQ:${this.name}] Group handler error: ${e}
2556
+ }).catch((e) => process.stderr.write(`[QQ:${this.name}] Group handler error: ${sanitizeLogText(e instanceof Error ? e.message : String(e), 200)}
2127
2557
  `));
2128
2558
  }
2129
2559
  };