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