@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.
- package/bundled/qc-helper/SKILL.md +1 -0
- package/bundled/qc-helper/docs/configuration/auth.md +40 -55
- package/bundled/qc-helper/docs/configuration/model-providers.md +224 -218
- package/bundled/qc-helper/docs/configuration/settings.md +68 -51
- package/bundled/qc-helper/docs/features/_meta.ts +1 -0
- package/bundled/qc-helper/docs/features/channels/dingtalk.md +12 -2
- package/bundled/qc-helper/docs/features/channels/overview.md +82 -7
- package/bundled/qc-helper/docs/features/channels/wecom.md +6 -0
- package/bundled/qc-helper/docs/features/code-review.md +109 -41
- package/bundled/qc-helper/docs/features/commands.md +45 -44
- package/bundled/qc-helper/docs/features/computer-use.md +77 -0
- package/bundled/qc-helper/docs/features/hooks.md +29 -0
- package/bundled/qc-helper/docs/features/sub-agents.md +21 -0
- package/bundled/qc-helper/docs/features/tool-use-summaries.md +19 -23
- package/bundled/qc-helper/docs/integration-vscode.md +2 -2
- package/bundled/qc-helper/docs/qwen-serve.md +66 -23
- package/bundled/qc-helper/docs/reference/keyboard-shortcuts.md +2 -2
- package/bundled/review/DESIGN.md +303 -46
- package/bundled/review/SKILL.md +670 -165
- package/chunks/MaxSizedBox-BEVSAWZ6.js +70 -0
- package/chunks/{StandaloneSessionPicker-EYRGH6PJ.js → StandaloneSessionPicker-2DA2MDDE.js} +81 -79
- package/chunks/{acpAgent-GRFTTRHW.js → acpAgent-G3WJU3PS.js} +1318 -465
- package/chunks/agent-DD3WSHXE.js +68 -0
- package/chunks/agent-headless-MHI7U2CU.js +62 -0
- package/chunks/{anthropicContentGenerator-CPF22MA7.js → anthropicContentGenerator-56U3YCP2.js} +187 -31
- package/chunks/{artifact-tool-D233YJFH.js → artifact-tool-MIZLXBLF.js} +5 -5
- package/chunks/{askUserQuestion-MKL5ZTMC.js → askUserQuestion-XMJVPNWM.js} +6 -6
- package/chunks/bridge-HKEWPNB5.js +78 -0
- package/chunks/{ca-MKD32FQL.js → ca-C2JTT2N5.js} +68 -6
- package/chunks/channel-worker-group-K7QKH6IG.js +13 -0
- package/chunks/channel-worker-manager-YSFB4HUU.js +418 -0
- package/chunks/channel-worker-supervisor-QCKIOYZD.js +20 -0
- package/chunks/chunk-2FMQQXMM.js +38 -0
- package/chunks/{chunk-CSOTA7KX.js → chunk-2FO5GFKZ.js} +2 -2
- package/chunks/{chunk-QQDPRDVW.js → chunk-2HRYPZT5.js} +1 -1
- package/chunks/{chunk-UXEW557D.js → chunk-2J4ZWME4.js} +3 -3
- package/chunks/{chunk-P3XEEI3X.js → chunk-2TQXXQMI.js} +36 -7
- package/chunks/{chunk-GSHQYYIV.js → chunk-2UW5RFRP.js} +6 -26
- package/chunks/{chunk-6IAWPIQ6.js → chunk-2W3OOD4W.js} +5 -3
- package/chunks/{chunk-2PSWA5ID.js → chunk-2X56YUSZ.js} +1 -1
- package/chunks/{chunk-NQKLOAVJ.js → chunk-2Z5UX4KI.js} +2 -2
- package/chunks/chunk-3MX7D6QN.js +516 -0
- package/chunks/{chunk-EEN2TKST.js → chunk-43RXPU5Q.js} +2 -2
- package/chunks/{chunk-55ZMG67I.js → chunk-4LD5APFL.js} +4 -4
- package/chunks/{chunk-WF4FAXDL.js → chunk-4QZ3XHWE.js} +62 -62
- package/chunks/chunk-4QZADKJS.js +34 -0
- package/chunks/{chunk-UIDXQNMV.js → chunk-4WGODMVR.js} +1 -1
- package/chunks/{chunk-E6US47KI.js → chunk-4WKAA4KS.js} +1 -1
- package/chunks/{chunk-TI3BWD4D.js → chunk-54UZQ57N.js} +39 -19
- package/chunks/{chunk-3GONHQOA.js → chunk-57OAHC2Y.js} +1 -1
- package/chunks/{chunk-JZSY4WP3.js → chunk-5DTTYNB7.js} +1 -1
- package/chunks/chunk-5HBA2Z7V.js +28 -0
- package/chunks/{chunk-SKAZWEV5.js → chunk-5M6IDOMF.js} +35 -98
- package/chunks/{chunk-A4BMJM77.js → chunk-5O2XNYP6.js} +1 -0
- package/chunks/{chunk-FQNC62B2.js → chunk-5VHASII5.js} +11 -11
- package/chunks/{chunk-GO6LNQXT.js → chunk-6AM5D4ME.js} +1 -1
- package/chunks/{chunk-BJBWRCSK.js → chunk-6H67XLET.js} +1 -1
- package/chunks/{chunk-O7FIRWJV.js → chunk-6JPDCEDB.js} +3 -3
- package/chunks/{chunk-3UP777ZS.js → chunk-6RLROE6G.js} +1 -1
- package/chunks/{chunk-GN3F6PIU.js → chunk-6WPJRYLZ.js} +5 -5
- package/chunks/{chunk-ZERZSAZL.js → chunk-75DOP5OR.js} +2 -2
- package/chunks/chunk-7DKX4A5Z.js +91 -0
- package/chunks/{chunk-M7S4GK5Z.js → chunk-7IDCQ35Q.js} +147 -30
- package/chunks/{chunk-22MNNNXE.js → chunk-7NFB7LBQ.js} +11 -3
- package/chunks/{chunk-ZL5HN75Q.js → chunk-7R4DPSRA.js} +37 -3
- package/chunks/{chunk-VZ7HY3YU.js → chunk-7STBROEV.js} +3 -3
- package/chunks/{chunk-SAIILPQ3.js → chunk-7X7TEQNX.js} +5 -3
- package/chunks/{chunk-RMLZAUWH.js → chunk-A3U2TQUL.js} +10 -12
- package/chunks/{chunk-IRJWHZWU.js → chunk-A4UCTEB5.js} +2 -2
- package/chunks/{chunk-BOBRZZKD.js → chunk-A7W4GQRW.js} +5 -5
- package/chunks/{chunk-O33TBKDC.js → chunk-AAUZBE5Y.js} +155 -19
- package/chunks/{chunk-SQ2UATK7.js → chunk-ADLYZJYZ.js} +11 -11
- package/chunks/{chunk-QYUE6W3T.js → chunk-ALGGS7UH.js} +2 -39
- package/chunks/{chunk-WMPVYQ4P.js → chunk-ARKANCNX.js} +3 -2
- package/chunks/{chunk-IBY3Q3HG.js → chunk-AU4R7ACM.js} +967 -181
- package/chunks/{channel-worker-supervisor-NZQUOEFJ.js → chunk-B7MFJBKE.js} +190 -34
- package/chunks/{chunk-HW4N45EE.js → chunk-B7NUUEXC.js} +38 -11
- package/chunks/{chunk-3B26UVCX.js → chunk-BLPMRN4B.js} +39 -10
- package/chunks/{chunk-ZN5T4BHI.js → chunk-BPZALHVR.js} +2 -2
- package/chunks/{chunk-BVYU3ZTI.js → chunk-BXT6PQ73.js} +1020 -942
- package/chunks/{chunk-OFEVLU4C.js → chunk-CARU2RR2.js} +1 -1
- package/chunks/{chunk-HDOH3QYE.js → chunk-CEGKYZDB.js} +2 -2
- package/chunks/{chunk-GLE2YUPU.js → chunk-COH43MRJ.js} +1 -1
- package/chunks/{chunk-E5A7LHNN.js → chunk-CPBF7KYF.js} +1 -1
- package/chunks/{chunk-UWCTAVOD.js → chunk-CR3C7WXL.js} +1 -1
- package/chunks/{chunk-IWKSG2AR.js → chunk-CXAOG665.js} +1 -1
- package/chunks/{chunk-64WXLC72.js → chunk-DJ2GSRLV.js} +1 -1
- package/chunks/{chunk-QN5NZ3UQ.js → chunk-DMTGGOSA.js} +2 -2
- package/chunks/{chunk-27BHARDE.js → chunk-DMVA55W5.js} +2124 -30
- package/chunks/{chunk-JPALE3PA.js → chunk-DQCCLVQK.js} +6 -6
- package/chunks/{chunk-X2XNQQJI.js → chunk-E2UVIFI2.js} +186 -399
- package/chunks/{chunk-KHDZHZMH.js → chunk-E5Z2AVNV.js} +1 -1
- package/chunks/{chunk-E7EMCSUG.js → chunk-EAHMOXLL.js} +64 -42
- package/chunks/{chunk-VGGOPZUL.js → chunk-EJ3KGYWL.js} +36 -8
- package/chunks/{chunk-DIWVZ3VM.js → chunk-ERTHKDKN.js} +5 -5
- package/chunks/{chunk-UX6OTBVD.js → chunk-EX5OULP3.js} +2 -2
- package/chunks/{chunk-5MBO76SL.js → chunk-F333XEAU.js} +3 -3
- package/chunks/{chunk-OACJLMLD.js → chunk-FBU7WRZI.js} +18 -2
- package/chunks/{chunk-5IFG2VC4.js → chunk-FCMNKVXT.js} +1 -1
- package/chunks/chunk-FEOBU3FA.js +253 -0
- package/chunks/{chunk-LAXNEOBG.js → chunk-FI2WSOWX.js} +19 -3
- package/chunks/{chunk-E7KNELIZ.js → chunk-FSA7ERJ2.js} +2 -2
- package/chunks/{chunk-44NROQYV.js → chunk-GDQQRV43.js} +4 -4
- package/chunks/{chunk-ZE6E424U.js → chunk-GN5HJJBO.js} +2 -2
- package/chunks/{chunk-O2YT7VB4.js → chunk-GOXKNSDZ.js} +2 -65
- package/chunks/{chunk-V35MD7KN.js → chunk-GRC5HGPI.js} +1 -1
- package/chunks/{chunk-ON3JA3RU.js → chunk-GTENQOEJ.js} +7 -9
- package/chunks/{chunk-S6RLAIUR.js → chunk-GYTKQYDB.js} +1 -1
- package/chunks/{chunk-A2X3KLC3.js → chunk-GZXCZHCT.js} +3 -3
- package/chunks/{chunk-K5PGHDBN.js → chunk-H6XPXXMH.js} +1 -1
- package/chunks/{chunk-PMFULMLX.js → chunk-HCJTURZD.js} +2262 -384
- package/chunks/chunk-HGG2RRXD.js +445 -0
- package/chunks/{chunk-II73RK2S.js → chunk-HGT6JR3U.js} +20 -1
- package/chunks/{chunk-DICGXFAI.js → chunk-HJCVNZLQ.js} +11 -9
- package/chunks/{chunk-3GNOQZDC.js → chunk-HJUDVNCO.js} +17663 -14877
- package/chunks/{chunk-AYNCLGTW.js → chunk-HRUGYOJN.js} +8 -51
- package/chunks/{chunk-KYMBIKIW.js → chunk-HTHUX2T5.js} +1 -1
- package/chunks/{chunk-LQ7TMOCE.js → chunk-HTO4JFDZ.js} +1 -1
- package/chunks/{chunk-BUZ6HGL4.js → chunk-HWTL6MPR.js} +357 -59
- package/chunks/chunk-IDS7MSUP.js +74 -0
- package/chunks/{chunk-4CGOWHKM.js → chunk-J4MOFEOK.js} +2 -2
- package/chunks/chunk-JCD4FM7K.js +296 -0
- package/chunks/{chunk-22IFUCVR.js → chunk-JESCGQM3.js} +1 -1
- package/chunks/{chunk-JZHIQJOE.js → chunk-JGO6RFI2.js} +6 -6
- package/chunks/{chunk-X3YGAX7V.js → chunk-JGUT3LWZ.js} +9 -2
- package/chunks/{chunk-JRQ7EVGE.js → chunk-JW7OOJC6.js} +2 -2
- package/chunks/{chunk-OQXDAMKY.js → chunk-KF5MMGG4.js} +8 -8
- package/chunks/{chunk-42XGFRJS.js → chunk-KKRTYONM.js} +8 -6
- package/chunks/{chunk-GGQQA4JW.js → chunk-KSO42X3Z.js} +15 -2
- package/chunks/{chunk-MRO43B25.js → chunk-KW7NOTN6.js} +1 -1
- package/chunks/{chunk-54YZOP7P.js → chunk-L573Z2QZ.js} +11894 -5190
- package/chunks/{chunk-TLWA7TPR.js → chunk-L5U3ELZX.js} +2 -2
- package/chunks/{chunk-YHKAK72D.js → chunk-LHEOLOXU.js} +251 -63
- package/chunks/{chunk-H6EMK6QK.js → chunk-LQIDP6K5.js} +11913 -5207
- package/chunks/{chunk-I6WGFJJ3.js → chunk-LSEJG6AM.js} +73 -19
- package/chunks/{chunk-MZ7BABX3.js → chunk-LXLTBIDP.js} +44 -7
- package/chunks/{chunk-HLKG6CZN.js → chunk-M5GB774H.js} +1 -1
- package/chunks/{chunk-JCH35JB3.js → chunk-MKK4XGLF.js} +2 -2
- package/chunks/{chunk-YKZIAK7C.js → chunk-MO7O5722.js} +424 -40
- package/chunks/chunk-MR3PXB6E.js +48 -0
- package/chunks/{chunk-A5JOJ77H.js → chunk-MRYWYBQ4.js} +38 -15
- package/chunks/{chunk-332PWN27.js → chunk-NHDKSOEY.js} +1 -1
- package/chunks/{chunk-TSPB77TG.js → chunk-NRAFASYX.js} +1016 -2967
- package/chunks/{chunk-BMFMJINR.js → chunk-OA4JVLSA.js} +28 -2
- package/chunks/{chunk-P7M4GA57.js → chunk-OCPBI7J5.js} +1 -1
- package/chunks/chunk-OJAMDVF5.js +66 -0
- package/chunks/{chunk-IJRL2S3D.js → chunk-OKAIGAYW.js} +6 -6
- package/chunks/{chunk-FBET5XZ2.js → chunk-PEJWA3QQ.js} +29 -10
- package/chunks/{chunk-L4AMBVQG.js → chunk-PMKW35AT.js} +54 -22
- package/chunks/{chunk-AGHU5VRK.js → chunk-PZL23GTM.js} +1 -1
- package/chunks/{chunk-PC4ZXHU2.js → chunk-Q6TUALBE.js} +3 -3
- package/chunks/{chunk-FXGLL2HL.js → chunk-Q7YDKOGY.js} +1 -1
- package/chunks/{chunk-XOHNA2S6.js → chunk-QAW7PIHT.js} +3 -3
- package/chunks/{chunk-XYR5RUA5.js → chunk-QE2WJ7FS.js} +10 -3
- package/chunks/{chunk-LGFAYIKX.js → chunk-QHTIBUWB.js} +1 -1
- package/chunks/{chunk-P3MYAZBY.js → chunk-QMGX2KO2.js} +1 -1
- package/chunks/{chunk-Z3I3QJK3.js → chunk-QRUIS3DN.js} +1 -1
- package/chunks/{chunk-ITOGNELQ.js → chunk-QSWDGAIC.js} +2 -2
- package/chunks/{chunk-PUVCVELY.js → chunk-QUJOI7SR.js} +70 -8
- package/chunks/{chunk-GHOTR7HL.js → chunk-QUPXZXLV.js} +1 -1
- package/chunks/{chunk-6VK6FIMQ.js → chunk-R5D5JOAM.js} +415 -379
- package/chunks/{chunk-CD6USWHZ.js → chunk-R5XLNDE2.js} +2 -2
- package/chunks/{chunk-W7EEOR7D.js → chunk-RCEEXIOO.js} +8 -6
- package/chunks/{chunk-TLRYABYP.js → chunk-RKUWKYED.js} +1 -1
- package/chunks/{chunk-RNBYOUGV.js → chunk-RXFQM6FQ.js} +1 -1
- package/chunks/{chunk-QXPI7FEC.js → chunk-S3TWXGKA.js} +4 -4
- package/chunks/{chunk-2EM7ECZD.js → chunk-SMSFWUFY.js} +309 -80
- package/chunks/{chunk-FV7425LN.js → chunk-SPJQO2CE.js} +18 -5
- package/chunks/{chunk-MLZQVCF3.js → chunk-SZRN75WQ.js} +1 -1
- package/chunks/{chunk-7RYW5LQV.js → chunk-T2NCM2ET.js} +2 -2
- package/chunks/{chunk-Z2Z3GUXZ.js → chunk-TB6UDU4T.js} +1 -1
- package/chunks/{chunk-EXPMGZZV.js → chunk-TEGEBB2I.js} +1 -1
- package/chunks/{chunk-MX2YXRER.js → chunk-TJTKSVIV.js} +1 -1
- package/chunks/{chunk-CCGOFQV3.js → chunk-TQBODCFD.js} +8 -7
- package/chunks/chunk-TV6K2FB4.js +224 -0
- package/chunks/{chunk-H6BD2ELD.js → chunk-TYAMGABM.js} +2 -2
- package/chunks/{chunk-QYNNN7BK.js → chunk-TYLOFD2U.js} +2 -2
- package/chunks/{chunk-VRDMOSQS.js → chunk-UDXSXLER.js} +2 -2
- package/chunks/{chunk-DHOLNQLC.js → chunk-UFDX27PL.js} +60 -40
- package/chunks/{chunk-WHJQ3JUS.js → chunk-UGN6CTNZ.js} +10 -10
- package/chunks/{chunk-CHB5SLZZ.js → chunk-UNLRKDMW.js} +4 -4
- package/chunks/{chunk-CJCKDMWO.js → chunk-URK542T4.js} +1 -1
- package/chunks/{chunk-AHNXYU4O.js → chunk-V6WHFGST.js} +1 -1
- package/chunks/{chunk-ZTQ26VBE.js → chunk-VDDGDZMA.js} +1 -1
- package/chunks/{chunk-BOBOCR5H.js → chunk-VEETSXYX.js} +4 -4
- package/chunks/{chunk-OMX7CUOE.js → chunk-VGC4I5JJ.js} +1 -1
- package/chunks/{chunk-43W4YSO6.js → chunk-VJNBRRZG.js} +2 -2
- package/chunks/{chunk-WQTHLB5T.js → chunk-VKS7EIOA.js} +7 -7
- package/chunks/{chunk-J7RCB6N5.js → chunk-VVDNGM6X.js} +2 -2
- package/chunks/{chunk-ER3BKOLB.js → chunk-W7FJ3N32.js} +1 -1
- package/chunks/{chunk-3HX5LZ6R.js → chunk-WBL3FJEU.js} +2 -2
- package/chunks/{chunk-7LTB54MK.js → chunk-WIEO4CWB.js} +2 -2
- package/chunks/chunk-WMC6HH7Y.js +493 -0
- package/chunks/{chunk-FEJ2FZ3U.js → chunk-WTNZ6DXB.js} +2 -2
- package/chunks/{chunk-L46XKEGM.js → chunk-X6ODDESK.js} +5 -8
- package/chunks/chunk-XDPYM5DE.js +71 -0
- package/chunks/{chunk-3RW4AZJV.js → chunk-XFSFZCM5.js} +1 -1
- package/chunks/{chunk-2MPVVENX.js → chunk-XU457J4V.js} +2 -2
- package/chunks/{chunk-SIUQ3YYX.js → chunk-XVNQMZ2I.js} +1 -1
- package/chunks/{chunk-SYCJMSIJ.js → chunk-XWRJCPHC.js} +1 -1
- package/chunks/{chunk-SUAKHHK7.js → chunk-Y66VOBRP.js} +3 -3
- package/chunks/chunk-YAF2MQZ6.js +19 -0
- package/chunks/chunk-YHN5SUIJ.js +219 -0
- package/chunks/{chunk-BR4QREVK.js → chunk-YQ3U5MUC.js} +1 -1
- package/chunks/{chunk-Y6Z2O3WR.js → chunk-YUZI3WAC.js} +1 -1
- package/chunks/{chunk-YDOXPLYL.js → chunk-ZA74ZTQU.js} +4564 -1326
- package/chunks/{chunk-DCZWC4PF.js → chunk-ZGE3222J.js} +5 -5
- package/chunks/chunk-ZP4ZZJJG.js +448 -0
- package/chunks/{chunk-AHNJGLFV.js → chunk-ZPAHLFZU.js} +3 -3
- package/chunks/cli-entry-path-4VJ3Y6T2.js +10 -0
- package/chunks/{computer-use-WMY3UOBX.js → computer-use-SHA6C3ES.js} +50 -50
- package/chunks/config-utils-XSHOMMK4.js +24 -0
- package/chunks/contextCommand-HOAF5LUC.js +68 -0
- package/chunks/create-sub-session-EAB2U5XW.js +207 -0
- package/chunks/create-sub-session-WJOWBFEP.js +375 -0
- package/chunks/{cron-create-JSXEIPVS.js → cron-create-GMVKSXZT.js} +9 -9
- package/chunks/{cron-delete-OIFVEPGU.js → cron-delete-VDZKUAVK.js} +7 -7
- package/chunks/{cron-list-D4ZX4IOE.js → cron-list-UG7C7RAR.js} +14 -10
- package/chunks/{daemon-WPAENEBW.js → daemon-MI5HF26N.js} +1074 -61
- package/chunks/daemon-status-provider-KFL3LRTP.js +76 -0
- package/chunks/{de-ZFPPQVSF.js → de-2VCDRDGU.js} +68 -6
- package/chunks/{devtools-FM6GJPYG.js → devtools-4QFYJT6U.js} +3 -3
- package/chunks/{dist-O2IRTRPP.js → dist-IEXZC5ZR.js} +10 -10
- package/chunks/{dist-SCFLLYMK.js → dist-PQK4GCKA.js} +12 -11
- package/chunks/{dist-63IS3ZMI.js → dist-PZV5RKM6.js} +4 -4
- package/chunks/{dist-E6UANPIM.js → dist-U75JK3PF.js} +1011 -197
- package/chunks/{dist-H2AEJCZ5.js → dist-VXO7QBON.js} +5 -5
- package/chunks/{dist-Z34QPAQW.js → dist-WH4TZSH3.js} +529 -79
- package/chunks/{dist-BEXOZXLQ.js → dist-X2ABIW5R.js} +36 -15
- package/chunks/earlyInputCapture-PKAQJMIP.js +69 -0
- package/chunks/{edit-OTYWAXGP.js → edit-6TXLYHJ2.js} +51 -51
- package/chunks/{en-N4EPZAQI.js → en-NJCA3TAA.js} +78 -6
- package/chunks/{enter-worktree-HNYBDVCX.js → enter-worktree-ZTILGPJX.js} +50 -50
- package/chunks/{enterPlanMode-CJCNGT63.js → enterPlanMode-62HRAMCH.js} +66 -51
- package/chunks/environment-OYA3ZVEL.js +89 -0
- package/chunks/errors-BFXOUUUR.js +75 -0
- package/chunks/{exit-worktree-DUUKOE3X.js → exit-worktree-HHJ5M6MV.js} +50 -50
- package/chunks/{exitPlanMode-AMMSXOAJ.js → exitPlanMode-M3SWHCL7.js} +51 -51
- package/chunks/{fast-path-OV6NIP3K.js → fast-path-3VMPLM74.js} +8 -8
- package/chunks/{fast-path-settings-7RIFI3PK.js → fast-path-settings-DAOVFADC.js} +8 -6
- package/chunks/{fileFromPath-IBEHA3CO.js → fileFromPath-UDALK7FM.js} +3 -3
- package/chunks/{fr-5WWMY53B.js → fr-5F3E7WKD.js} +68 -6
- package/chunks/{gemini-NQ3WWBKB.js → gemini-ZCGVYOJL.js} +181 -110
- package/chunks/{geminiContentGenerator-CDUNEJQE.js → geminiContentGenerator-XBYBAIDQ.js} +9 -9
- package/chunks/{getMachineId-bsd-4CASPIU4.js → getMachineId-bsd-EXV7SWPA.js} +3 -3
- package/chunks/{getMachineId-darwin-HPQPEMZR.js → getMachineId-darwin-IUOMFXU3.js} +3 -3
- package/chunks/{getMachineId-linux-AUARKYHL.js → getMachineId-linux-K7XJYFHL.js} +2 -2
- package/chunks/{getMachineId-unsupported-S32ZDA2T.js → getMachineId-unsupported-EA5FDRJ6.js} +2 -2
- package/chunks/{getMachineId-win-4EFLHYIJ.js → getMachineId-win-KBA6RAI5.js} +3 -3
- package/chunks/{glob-ROV3YS4R.js → glob-UOEYQNHN.js} +97 -75
- package/chunks/{grep-NRITKDCD.js → grep-OQM3CH7W.js} +50 -50
- package/chunks/handleAutoUpdate-ORBYVJHZ.js +69 -0
- package/chunks/i18n-NBP574CO.js +84 -0
- package/chunks/initializer-CWD7TCCD.js +73 -0
- package/chunks/installationInfo-CHPDUPWC.js +65 -0
- package/chunks/{ja-CUHZOLEJ.js → ja-66J6B53L.js} +69 -7
- package/chunks/{keychain-token-storage-UJV5XP4Z.js → keychain-token-storage-AU22CQI6.js} +4 -4
- package/chunks/{kittyProtocolDetector-OJRCJLIU.js → kittyProtocolDetector-HCNNKJTI.js} +2 -2
- package/chunks/list-ZTTO362W.js +76 -0
- package/chunks/loadedSettingsAdapter-V4ZTRKUO.js +70 -0
- package/chunks/{loop-wakeup-OHWEMFP5.js → loop-wakeup-C4EXLYVA.js} +10 -10
- package/chunks/{lowlight-FYAAUU5J.js → lowlight-J2OZNPCA.js} +1 -1
- package/chunks/{ls-7MFU2QYW.js → ls-2OUTG3IZ.js} +6 -6
- package/chunks/{lsp-XHDTPTGV.js → lsp-4VMNXZW6.js} +4 -4
- package/chunks/mcp-UH5PSGIK.js +70 -0
- package/chunks/{monitor-FL2ZO33W.js → monitor-F4NYEU5J.js} +50 -50
- package/chunks/{multipart-parser-AJ4WASWR.js → multipart-parser-AWZKXSQN.js} +3 -3
- package/chunks/nonInteractiveCli-7IHPXVNH.js +129 -0
- package/chunks/{notebook-edit-6PULTHL5.js → notebook-edit-HPDXNGQH.js} +51 -51
- package/chunks/openaiContentGenerator-OBDNNOE2.js +56 -0
- package/chunks/pidfile-AUMGYIOK.js +73 -0
- package/chunks/{pt-OLYN42DS.js → pt-TFZO5Y6T.js} +68 -6
- package/chunks/{qwenContentGenerator-NX2E7PB7.js → qwenContentGenerator-LH2U2YRG.js} +52 -52
- package/chunks/{qwenOAuth2-ZV7YLMND.js → qwenOAuth2-LEYOU7RU.js} +9 -9
- package/chunks/read-file-HUUFBLEO.js +30 -0
- package/chunks/{read-mcp-resource-CNQHBSJM.js → read-mcp-resource-JUFHCKSC.js} +5 -5
- package/chunks/{read-package-up-ER5OJUGP.js → read-package-up-TCM6I7S2.js} +2 -2
- package/chunks/{record-artifact-SQ2OKRJW.js → record-artifact-DWSMBXYY.js} +4 -4
- package/chunks/ripGrep-C7X33LU6.js +60 -0
- package/chunks/{ru-LDM3GWVQ.js → ru-4L4LFHIT.js} +68 -6
- package/chunks/{run-qwen-serve-JK2DL7PI.js → run-qwen-serve-DGW5AFDJ.js} +1465 -283
- package/chunks/runtime-NYUSLBEZ.js +99 -0
- package/chunks/{scheduler-O4IGVZKK.js → scheduler-5VKDXULT.js} +51 -51
- package/chunks/{send-message-76GT3JMC.js → send-message-JZ752NWD.js} +7 -7
- package/chunks/serve-UTAHSFXH.js +76 -0
- package/chunks/{server-SGNPKX2O.js → server-VUHSYSUV.js} +9088 -1817
- package/chunks/{session-5BNM25EZ.js → session-QSGSSNFB.js} +340 -135
- package/chunks/settings-X24RYEED.js +120 -0
- package/chunks/shell-4MSI2R4O.js +70 -0
- package/chunks/{skill-I22QK5XS.js → skill-JR3GTNVZ.js} +24 -24
- package/chunks/spawnChannel-RFAAO44J.js +70 -0
- package/chunks/{src-7XL4G4DC.js → src-VLI3TKEY.js} +4 -4
- package/chunks/{src-BBQSM46M.js → src-YDBNMIIM.js} +183 -53
- package/chunks/standalone-update-4CY4HKGC.js +79 -0
- package/chunks/{startInteractiveUI-JII36UGN.js → startInteractiveUI-FOD7MMVB.js} +2368 -1923
- package/chunks/stdioHelpers-UMY72QRP.js +14 -0
- package/chunks/{syntheticOutput-F7LSDCAR.js → syntheticOutput-LELY6HAI.js} +5 -5
- package/chunks/task-create-QY3BIMNM.js +23 -0
- package/chunks/{task-list-TGGJX7EA.js → task-list-D2U47WBJ.js} +11 -10
- package/chunks/{task-stop-W7NGTMYQ.js → task-stop-N6E5IYLH.js} +4 -4
- package/chunks/{task-update-XYWMHJGJ.js → task-update-HJCQILLB.js} +13 -12
- package/chunks/{team-create-A7NUU5AQ.js → team-create-ZLQSU2E3.js} +50 -50
- package/chunks/{team-delete-UHQQ2QQX.js → team-delete-RCWYDRF4.js} +11 -10
- package/chunks/{team-plan-approval-TOQRJ2KI.js → team-plan-approval-YUAYBYLS.js} +50 -50
- package/chunks/theme-manager-SKNEBMOG.js +64 -0
- package/chunks/{todoWrite-C4ZAMSR3.js → todoWrite-FKCGATPJ.js} +6 -6
- package/chunks/{tool-search-SZ4EFS2T.js → tool-search-V5J3TGWY.js} +24 -24
- package/chunks/total-session-admission-2WIH72I3.js +72 -0
- package/chunks/tree-sitter-YJVE2ZUK.js +2980 -0
- package/chunks/trustedFolders-JF265CRI.js +80 -0
- package/chunks/types-ML3TRJQ5.js +16 -0
- package/chunks/updateCheck-AMXNPBP6.js +69 -0
- package/chunks/updateEventEmitter-5MKC7BLQ.js +10 -0
- package/chunks/validateNonInterActiveAuth-6YHQO7PD.js +194 -0
- package/chunks/{version-NO2XSNNP.js → version-UEJJK7PE.js} +3 -3
- package/chunks/{web-fetch-UYGMOHW4.js → web-fetch-QGODBB6S.js} +8 -8
- package/chunks/{workflow-WO2H6NRI.js → workflow-5YFA5V4M.js} +51 -51
- package/chunks/workspace-providers-status-LMBLBNR7.js +73 -0
- package/chunks/workspace-registration-store-VV2S4MSV.js +27 -0
- package/chunks/workspace-registry-BVMPYRLN.js +76 -0
- package/chunks/workspace-service-PUYIZ3J4.js +86 -0
- package/chunks/workspace-skills-status-CJ7J2KPP.js +72 -0
- package/chunks/{write-file-PX2Z7IBE.js → write-file-OV77M6X7.js} +51 -51
- package/chunks/{yargs-B64EYTCF.js → yargs-6H2AUULL.js} +2 -2
- package/chunks/{zh-J4F6QWQV.js → zh-FSAH32JB.js} +79 -7
- package/chunks/{zh-TW-XVDWOLSW.js → zh-TW-D2YIE2LO.js} +79 -7
- package/cli.js +18 -17
- package/locales/ca.js +91 -7
- package/locales/de.js +90 -7
- package/locales/en.js +109 -7
- package/locales/fr.js +91 -7
- package/locales/ja.js +93 -7
- package/locales/pt.js +90 -7
- package/locales/ru.js +90 -7
- package/locales/zh-TW.js +106 -7
- package/locales/zh.js +106 -7
- package/package.json +3 -6
- package/web-shell/assets/{arc-BWyoJpY2.js → arc-CObt_TK9.js} +1 -1
- package/web-shell/assets/{architectureDiagram-3BPJPVTR-BT0mGrz2.js → architectureDiagram-3BPJPVTR-B7aiJ6CK.js} +1 -1
- package/web-shell/assets/{blockDiagram-GPEHLZMM-BLgUWvyf.js → blockDiagram-GPEHLZMM-BSK2CrID.js} +1 -1
- package/web-shell/assets/{c4Diagram-AAUBKEIU-DYx9M7oU.js → c4Diagram-AAUBKEIU-Bhx2p7T_.js} +1 -1
- package/web-shell/assets/channel-BBaLq8MV.js +1 -0
- package/web-shell/assets/{chunk-2J33WTMH-CfMHWbuZ.js → chunk-2J33WTMH-c3Hzv0Ni.js} +1 -1
- package/web-shell/assets/{chunk-4BX2VUAB-B1URoqEV.js → chunk-4BX2VUAB-DNXaK7ZF.js} +1 -1
- package/web-shell/assets/{chunk-55IACEB6-DAIaM4QF.js → chunk-55IACEB6-BXjvDj_i.js} +1 -1
- package/web-shell/assets/{chunk-727SXJPM-B2X34knw.js → chunk-727SXJPM-B6CaKFp-.js} +1 -1
- package/web-shell/assets/{chunk-AQP2D5EJ-Cif-zLTQ.js → chunk-AQP2D5EJ-tij6Mz9B.js} +1 -1
- package/web-shell/assets/{chunk-FMBD7UC4-H18WCXdY.js → chunk-FMBD7UC4-DdlsGk2h.js} +1 -1
- package/web-shell/assets/{chunk-ND2GUHAM-YFtetHi0.js → chunk-ND2GUHAM-CdgGZSHE.js} +1 -1
- package/web-shell/assets/{chunk-QZHKN3VN-By2hiH3r.js → chunk-QZHKN3VN-D8oJn1C0.js} +1 -1
- package/web-shell/assets/classDiagram-4FO5ZUOK-B2ZK6sWE.js +1 -0
- package/web-shell/assets/classDiagram-v2-Q7XG4LA2-B2ZK6sWE.js +1 -0
- package/web-shell/assets/{cose-bilkent-S5V4N54A-VrcnnLZu.js → cose-bilkent-S5V4N54A-BUVmNp1Y.js} +1 -1
- package/web-shell/assets/{dagre-BM42HDAG-CGo-IaVV.js → dagre-BM42HDAG-CO4q310O.js} +1 -1
- package/web-shell/assets/{diagram-2AECGRRQ-Dl4QjzIB.js → diagram-2AECGRRQ-C6gFvWkg.js} +1 -1
- package/web-shell/assets/{diagram-5GNKFQAL-qMLmsO0u.js → diagram-5GNKFQAL-NG2I_c0h.js} +1 -1
- package/web-shell/assets/{diagram-KO2AKTUF-CHlmuxPI.js → diagram-KO2AKTUF-DFKMS6I9.js} +1 -1
- package/web-shell/assets/{diagram-LMA3HP47-BUwU3GwT.js → diagram-LMA3HP47-BiElfsfP.js} +1 -1
- package/web-shell/assets/{diagram-OG6HWLK6-HEhwPDUM.js → diagram-OG6HWLK6-zLWDYUqu.js} +1 -1
- package/web-shell/assets/{erDiagram-TEJ5UH35-1n0llBmM.js → erDiagram-TEJ5UH35-DoVaazhK.js} +1 -1
- package/web-shell/assets/{flowDiagram-I6XJVG4X-BN74uMnt.js → flowDiagram-I6XJVG4X-0RLz-mXg.js} +1 -1
- package/web-shell/assets/{ganttDiagram-6RSMTGT7-BoOq78i3.js → ganttDiagram-6RSMTGT7-C-eX1A39.js} +3 -3
- package/web-shell/assets/{gitGraphDiagram-PVQCEYII-CN8b00c8.js → gitGraphDiagram-PVQCEYII-BauQu1Bu.js} +1 -1
- package/web-shell/assets/index-3uAT3ehO.css +5 -0
- package/web-shell/assets/index-CVHa2QH7.js +3 -0
- package/web-shell/assets/index-D1KigWat.js +1128 -0
- package/web-shell/assets/{infoDiagram-5YYISTIA-CCurnIna.js → infoDiagram-5YYISTIA-C800cFvZ.js} +1 -1
- package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-AwuRqdUP.js → ishikawaDiagram-YF4QCWOH-CFK1Jk6e.js} +1 -1
- package/web-shell/assets/{journeyDiagram-JHISSGLW-B7IICk-E.js → journeyDiagram-JHISSGLW-Cjx0WAC4.js} +1 -1
- package/web-shell/assets/{kanban-definition-UN3LZRKU-BYF0OKhx.js → kanban-definition-UN3LZRKU-DWMDHbiv.js} +1 -1
- package/web-shell/assets/{linear-GWWguGfD.js → linear-vpW4FrZs.js} +1 -1
- package/web-shell/assets/{mermaid.core-JqtkxvCl.js → mermaid.core-DsHRUnDd.js} +5 -5
- package/web-shell/assets/{mindmap-definition-RKZ34NQL-B0MmHsZ6.js → mindmap-definition-RKZ34NQL-D3Eb1cAK.js} +1 -1
- package/web-shell/assets/{pieDiagram-4H26LBE5-DhImUZLQ.js → pieDiagram-4H26LBE5-Cr2-6sWt.js} +1 -1
- package/web-shell/assets/{quadrantDiagram-W4KKPZXB-DGeSC3DT.js → quadrantDiagram-W4KKPZXB-Yd7yUgVp.js} +1 -1
- package/web-shell/assets/{requirementDiagram-4Y6WPE33-DkspY6Re.js → requirementDiagram-4Y6WPE33-BE6SprL8.js} +1 -1
- package/web-shell/assets/{sankeyDiagram-5OEKKPKP-CaOofS2x.js → sankeyDiagram-5OEKKPKP-DzkuMK8d.js} +1 -1
- package/web-shell/assets/{sequenceDiagram-3UESZ5HK-DFQ_kFnN.js → sequenceDiagram-3UESZ5HK-wD9A3Dhi.js} +1 -1
- package/web-shell/assets/{stateDiagram-AJRCARHV-D3snw0Tz.js → stateDiagram-AJRCARHV-BOp5q1Nm.js} +1 -1
- package/web-shell/assets/stateDiagram-v2-BHNVJYJU-BDsWUmPG.js +1 -0
- package/web-shell/assets/{timeline-definition-PNZ67QCA-l4zH8nLM.js → timeline-definition-PNZ67QCA-Btc08sUA.js} +1 -1
- package/web-shell/assets/{vennDiagram-CIIHVFJN-BnRlA98M.js → vennDiagram-CIIHVFJN-Bv6upsOA.js} +1 -1
- package/web-shell/assets/{wardley-L42UT6IY-BS0xUdmd.js → wardley-L42UT6IY-D-XamnCK.js} +1 -1
- package/web-shell/assets/{wardleyDiagram-YWT4CUSO-DKGBUWyQ.js → wardleyDiagram-YWT4CUSO-BbP_5Wfp.js} +1 -1
- package/web-shell/assets/{xychartDiagram-2RQKCTM6-DVb2HuOB.js → xychartDiagram-2RQKCTM6-CO5Hh28b.js} +1 -1
- package/web-shell/index.html +16 -4
- package/chunks/MaxSizedBox-5DT2SD7B.js +0 -71
- package/chunks/agent-H2DGKS77.js +0 -68
- package/chunks/agent-headless-KFMFIT6J.js +0 -62
- package/chunks/bridge-NA3ZQQKJ.js +0 -78
- package/chunks/chunk-5P5XGNYH.js +0 -93
- package/chunks/chunk-6SHO7WFF.js +0 -79
- package/chunks/chunk-IFEWJXFS.js +0 -37
- package/chunks/chunk-XNXVCNAT.js +0 -88
- package/chunks/contextCommand-LXFZRP4L.js +0 -68
- package/chunks/daemon-status-provider-GJRXY5EN.js +0 -76
- package/chunks/earlyInputCapture-KNOYZ4MJ.js +0 -69
- package/chunks/environment-ATFTYMLZ.js +0 -88
- package/chunks/errors-A54HAADA.js +0 -75
- package/chunks/handleAutoUpdate-CEZJAARW.js +0 -65
- package/chunks/initializer-BRFJKRRX.js +0 -71
- package/chunks/list-KVHF7O4X.js +0 -73
- package/chunks/loadedSettingsAdapter-LL5PSFKX.js +0 -68
- package/chunks/mcp-3GCXRTOQ.js +0 -68
- package/chunks/nonInteractiveCli-PTQI3DV6.js +0 -119
- package/chunks/openaiContentGenerator-YE42PKCY.js +0 -56
- package/chunks/pidfile-NZYSCFRK.js +0 -73
- package/chunks/read-file-33XGP5HF.js +0 -30
- package/chunks/ripGrep-QWZLGTAN.js +0 -60
- package/chunks/serve-JTMIGANH.js +0 -74
- package/chunks/settings-3MI4XXP6.js +0 -118
- package/chunks/shell-YJKBE67T.js +0 -68
- package/chunks/spawnChannel-UNV6W4DU.js +0 -70
- package/chunks/task-create-WCHTB435.js +0 -22
- package/chunks/theme-manager-WZ4RRMMB.js +0 -64
- package/chunks/total-session-admission-ULXJWXFV.js +0 -72
- package/chunks/trustedFolders-LAZZPZ3D.js +0 -79
- package/chunks/types-QX5C3CHJ.js +0 -12
- package/chunks/updateCheck-KONLM73X.js +0 -65
- package/chunks/validateNonInterActiveAuth-MPKSRCXD.js +0 -184
- package/chunks/workspace-providers-status-3W7E7DFW.js +0 -71
- package/chunks/workspace-registry-SXJZNN3O.js +0 -74
- package/chunks/workspace-service-CZRQZ4Q4.js +0 -80
- package/chunks/workspace-skills-status-K7WFREPD.js +0 -70
- package/node_modules/@qwen-code/audio-capture/dist/index.d.ts +0 -35
- package/node_modules/@qwen-code/audio-capture/dist/index.js +0 -48
- package/node_modules/@qwen-code/audio-capture/dist/index.js.map +0 -1
- package/node_modules/@qwen-code/audio-capture/dist/platform.d.ts +0 -7
- package/node_modules/@qwen-code/audio-capture/dist/platform.js +0 -18
- package/node_modules/@qwen-code/audio-capture/dist/platform.js.map +0 -1
- package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/LICENSE +0 -21
- package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/README.md +0 -58
- package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/SECURITY.md +0 -5
- package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/bin.js +0 -84
- package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/build-test.js +0 -19
- package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/index.js +0 -6
- package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/node-gyp-build.js +0 -207
- package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/optional.js +0 -7
- package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/package.json +0 -43
- package/node_modules/@qwen-code/audio-capture/package.json +0 -30
- package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-arm64/@qwen-code+audio-capture.node +0 -0
- package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-x64/@qwen-code+audio-capture.node +0 -0
- package/node_modules/@qwen-code/audio-capture/prebuilds/linux-arm64/@qwen-code+audio-capture.node +0 -0
- package/node_modules/@qwen-code/audio-capture/prebuilds/linux-x64/@qwen-code+audio-capture.node +0 -0
- package/node_modules/@qwen-code/audio-capture/prebuilds/win32-x64/@qwen-code+audio-capture.node +0 -0
- package/web-shell/assets/channel-wmPUGHtg.js +0 -1
- package/web-shell/assets/classDiagram-4FO5ZUOK-BA2ljEjW.js +0 -1
- package/web-shell/assets/classDiagram-v2-Q7XG4LA2-BA2ljEjW.js +0 -1
- package/web-shell/assets/index-CNwoO1Cn.js +0 -783
- package/web-shell/assets/index-CWQ0z6kZ.css +0 -5
- 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-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
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-
|
|
16
|
-
import "./chunk-
|
|
15
|
+
} from "./chunk-TYAMGABM.js";
|
|
16
|
+
import "./chunk-FCMNKVXT.js";
|
|
17
17
|
import {
|
|
18
18
|
init_esbuild_shims
|
|
19
|
-
} from "./chunk-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1549
|
+
_reconnectId = 0;
|
|
1513
1550
|
blockStreaming = false;
|
|
1514
1551
|
flushedSessions = /* @__PURE__ */ new Set();
|
|
1515
1552
|
qqStatePath;
|
|
1516
1553
|
/**
|
|
1517
|
-
* Path to the
|
|
1518
|
-
* start.ts
|
|
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
|
|
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 ?? !
|
|
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 =
|
|
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.
|
|
1762
|
+
this.reconnectAttempts = 0;
|
|
1763
|
+
this.serverRequestedReconnect = false;
|
|
1764
|
+
this.tryResume = false;
|
|
1546
1765
|
if (!this.config.instructions) {
|
|
1547
|
-
|
|
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
|
-
"\
|
|
1552
|
-
|
|
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
|
|
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
|
-
|
|
1606
|
-
|
|
1860
|
+
passiveBody["msg_id"] = msgId;
|
|
1861
|
+
passiveBody["msg_seq"] = nextSeq;
|
|
1607
1862
|
}
|
|
1608
|
-
const resp = await sendQQMessage(route.base, route.path, this.accessToken,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
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
|
|
1632
|
-
if (
|
|
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
|
|
1898
|
+
await activeMdResp.text().catch(() => "");
|
|
1641
1899
|
return;
|
|
1642
1900
|
}
|
|
1643
|
-
|
|
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
|
-
|
|
1646
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 <
|
|
1850
|
-
const reconnectId = this.
|
|
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
|
-
|
|
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 <
|
|
2222
|
+
if (this.maxFlushRetries <= 0 || current.retryCount < this.maxFlushRetries) {
|
|
1872
2223
|
if (!current.timer) {
|
|
1873
|
-
const reconnectId = this.
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
-
*
|
|
1996
|
-
*
|
|
1997
|
-
*
|
|
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
|
|
2010
|
-
const
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
if (
|
|
2014
|
-
process.stderr.write(`[QQ:${this.name}]
|
|
2015
|
-
`);
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
const
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
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
|
|
2030
|
-
return typeof
|
|
2031
|
-
}))
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
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.
|
|
2038
|
-
const
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
if (
|
|
2042
|
-
process.stderr.write(`[QQ:${this.name}]
|
|
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.
|
|
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)},
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}
|
|
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.
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
2411
|
-
|
|
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
|
-
|
|
2414
|
-
|
|
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
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2940
|
+
try {
|
|
2941
|
+
await this.fetchToken();
|
|
2942
|
+
} catch {
|
|
2943
|
+
process.stderr.write(`[QQ:${this.name}] RC: token refresh failed, retrying...
|
|
2424
2944
|
`);
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
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
|
-
|
|
2437
|
-
|
|
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
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
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) =>
|
|
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
|
|
3110
|
+
senderName,
|
|
2511
3111
|
chatId,
|
|
2512
|
-
text
|
|
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
|
-
|
|
2531
|
-
|
|
2532
|
-
const
|
|
2533
|
-
|
|
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
|
-
|
|
2539
|
-
|
|
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
|
-
|
|
3169
|
+
this.setReplyMsgId(chatId, event.id);
|
|
2543
3170
|
this.handleInbound({
|
|
2544
3171
|
channelName: this.name,
|
|
2545
|
-
senderId
|
|
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
|