@qwen-code/qwen-code 0.19.3-nightly.20260701.a974594d7 → 0.19.4
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/loop/loop-tick-resolver.test.ts +42 -0
- package/bundled/loop/loop-tick-resolver.ts +1 -1
- package/bundled/qc-helper/docs/configuration/auth.md +1 -1
- package/bundled/qc-helper/docs/configuration/settings.md +23 -11
- package/bundled/qc-helper/docs/features/approval-mode.md +3 -0
- package/bundled/qc-helper/docs/features/auto-mode.md +40 -0
- package/bundled/qc-helper/docs/features/channels/overview.md +81 -18
- package/bundled/qc-helper/docs/features/code-review.md +17 -61
- package/bundled/qc-helper/docs/features/commands.md +6 -6
- package/bundled/qc-helper/docs/features/mcp.md +3 -1
- package/bundled/qc-helper/docs/features/scheduled-tasks.md +10 -0
- package/bundled/qc-helper/docs/qwen-serve.md +19 -0
- package/bundled/review/DESIGN.md +21 -26
- package/bundled/review/SKILL.md +45 -129
- package/chunks/{MaxSizedBox-UZHJ73JB.js → MaxSizedBox-ERTNFI22.js} +20 -18
- package/chunks/{StandaloneSessionPicker-GGUIZWCE.js → StandaloneSessionPicker-Y5NGRLRY.js} +32 -29
- package/chunks/{acpAgent-QYZFICEZ.js → acpAgent-UMN6OYTR.js} +473 -187
- package/chunks/{agent-2EN7YNSW.js → agent-56DZU4ZU.js} +17 -15
- package/chunks/{agent-headless-SLTEZ5LV.js → agent-headless-SR7N2IJG.js} +17 -15
- package/chunks/{anthropicContentGenerator-CA3JTAMO.js → anthropicContentGenerator-5OZFO6WQ.js} +1 -1
- package/chunks/{artifact-tool-RM7WINJO.js → artifact-tool-JVZMHA4B.js} +1 -1
- package/chunks/{askUserQuestion-J3P6QAJ7.js → askUserQuestion-CMJZ2F3D.js} +1 -1
- package/chunks/{bridge-HZVAUTMX.js → bridge-AUHNFOUS.js} +23 -21
- package/chunks/channel-worker-supervisor-A74FV522.js +308 -0
- package/chunks/{chunk-N4V4VBP6.js → chunk-2X2JUE47.js} +2 -2
- package/chunks/{chunk-JV7A66DR.js → chunk-4VZH35PN.js} +528 -358
- package/chunks/{chunk-M2U5NM24.js → chunk-54YZOP7P.js} +309 -72
- package/chunks/{chunk-WGY3GIPP.js → chunk-6AQR7732.js} +1 -1
- package/chunks/{chunk-FR3II765.js → chunk-6CIIP6OE.js} +4 -4
- package/chunks/{chunk-IKO44NAT.js → chunk-6TLY5W3B.js} +2 -2
- package/chunks/{chunk-VN4CKYUW.js → chunk-73H73ASU.js} +1 -1
- package/chunks/{chunk-IBEM64M4.js → chunk-75X2VLSH.js} +36 -25
- package/chunks/{chunk-PLQTLKMR.js → chunk-7KPI4LAI.js} +3 -3
- package/chunks/{chunk-NVTRORSK.js → chunk-7VPC7D7O.js} +2 -2
- package/chunks/chunk-AHNXYU4O.js +24 -0
- package/chunks/{chunk-MTJVHQDP.js → chunk-B7KGXIPZ.js} +302 -257
- package/chunks/{chunk-35FH347N.js → chunk-BFG6OZN7.js} +16097 -16236
- package/chunks/{chunk-T44AF4I6.js → chunk-BNJLLAQ7.js} +1 -1
- package/chunks/{chunk-OEY3YAL2.js → chunk-BQNJBEUA.js} +2 -2
- package/chunks/{chunk-Q7TDHJ2G.js → chunk-C2IHHGBZ.js} +10 -10
- package/chunks/chunk-CG7SVARK.js +367 -0
- package/chunks/{chunk-7SP275SH.js → chunk-CS5XYDBO.js} +5 -5
- package/chunks/{chunk-V5YJAUHW.js → chunk-CUGEBAKG.js} +1 -1
- package/chunks/{chunk-FQDMX5IL.js → chunk-D5KISTLW.js} +309 -72
- package/chunks/{chunk-I4MXURND.js → chunk-DQ74W65K.js} +356 -468
- package/chunks/chunk-E5A7LHNN.js +51 -0
- package/chunks/{chunk-3PJXIDKI.js → chunk-E6US47KI.js} +3 -27
- package/chunks/chunk-EDAGQADF.js +21 -0
- package/chunks/{chunk-HUGAEYZR.js → chunk-FNLXLI4I.js} +2 -2
- package/chunks/{chunk-WJAY4WXR.js → chunk-FYKX6XOT.js} +1 -1
- package/chunks/{chunk-CA4ML3VH.js → chunk-G7OQ3RFJ.js} +1 -1
- package/chunks/{chunk-VHRREANT.js → chunk-GICCHL2Q.js} +3 -1
- package/chunks/{chunk-QHHNFG22.js → chunk-GLPV75ES.js} +1 -1
- package/chunks/{chunk-LD2XBG6Z.js → chunk-GO6LNQXT.js} +1 -39
- package/chunks/chunk-HI55Z6PR.js +3523 -0
- package/chunks/{chunk-B5MPZI4K.js → chunk-HJDF3ZVU.js} +2670 -1233
- package/chunks/{chunk-GDIG7TMG.js → chunk-HRZB5CSC.js} +1 -1
- package/chunks/{chunk-LDWTYE33.js → chunk-J3F7X3LW.js} +1 -1
- package/chunks/{chunk-FV4SQ54H.js → chunk-KVPFPC4J.js} +227 -85
- package/chunks/{chunk-Q3JOGB4U.js → chunk-L7QPM2YD.js} +1 -1
- package/chunks/{chunk-4722VFFU.js → chunk-L7VH7HQA.js} +6 -6
- package/chunks/{chunk-HWG4XLD7.js → chunk-LKWAVAB5.js} +1 -1
- package/chunks/{chunk-YCRTJYSQ.js → chunk-MEQWZGK5.js} +1 -1
- package/chunks/{chunk-6FIWYVN4.js → chunk-MKHC4BYZ.js} +30 -5
- package/chunks/{chunk-YCEHOJZJ.js → chunk-MNDTD5E5.js} +65 -38
- package/chunks/{chunk-ZGSV5ORL.js → chunk-MVZXZWGT.js} +809 -759
- package/chunks/chunk-MX2YXRER.js +44 -0
- package/chunks/{chunk-TMILTLP3.js → chunk-ND25GXEU.js} +21 -4
- package/chunks/{chunk-AIBPG5MB.js → chunk-OAFUMTWW.js} +2 -2
- package/chunks/chunk-OJCECIJA.js +238 -0
- package/chunks/{chunk-EUURTJY7.js → chunk-PIYV6COW.js} +1 -1
- package/chunks/{chunk-LXEW6BHF.js → chunk-Q7VHIRDD.js} +1 -1
- package/chunks/{chunk-DBR7PF4B.js → chunk-QAFQLTXG.js} +1 -1
- package/chunks/{chunk-3TK7FSCP.js → chunk-QMIND2AS.js} +3 -3
- package/chunks/{chunk-ESVSWG6Z.js → chunk-QOMAHFYI.js} +1 -1
- package/chunks/{chunk-J37FGIOA.js → chunk-QXPI7FEC.js} +38 -4
- package/chunks/chunk-RM3PGSGL.js +66 -0
- package/chunks/chunk-RNBYOUGV.js +42 -0
- package/chunks/chunk-S5EQPDTM.js +91 -0
- package/chunks/{chunk-GZKQ36JS.js → chunk-SFN4TEHY.js} +4 -4
- package/chunks/{chunk-DHXWNSJA.js → chunk-T22H4V4R.js} +42 -0
- package/chunks/{chunk-LXGWLOCM.js → chunk-TENOUWY2.js} +1 -1
- package/chunks/{chunk-BLAE2TCO.js → chunk-TTFNVEO5.js} +3 -3
- package/chunks/{chunk-FZY7NH37.js → chunk-UANCYOOQ.js} +1 -1
- package/chunks/{chunk-E5I6YSNO.js → chunk-UQFP2FEF.js} +1 -1
- package/chunks/{chunk-3O2BJMFW.js → chunk-UQYOMM6J.js} +4 -4
- package/chunks/{chunk-JERW36RG.js → chunk-UT5JU5DJ.js} +15 -15
- package/chunks/{chunk-DJ25MIU2.js → chunk-VKGG724L.js} +8 -8
- package/chunks/{chunk-G525INVW.js → chunk-W2ZOAX7F.js} +3 -3
- package/chunks/{chunk-HIYUAXBG.js → chunk-W6X4FGWX.js} +1 -1
- package/chunks/{chunk-OE5J6UA7.js → chunk-XCUFHOAD.js} +1 -1
- package/chunks/{chunk-KXVVXAAC.js → chunk-YBDZ63KI.js} +3 -3
- package/chunks/{chunk-Z34RYLRO.js → chunk-ZJP5T7IA.js} +1 -1
- package/chunks/{chunk-NOYV2NXI.js → chunk-ZXCVPMIK.js} +411 -43
- package/chunks/{computer-use-FCNWHVPW.js → computer-use-VWN5HH6A.js} +20 -18
- package/chunks/{contextCommand-FVFMZZEU.js → contextCommand-DFFG5KDJ.js} +19 -17
- package/chunks/{cron-create-IY5NMIHQ.js → cron-create-CSSWJ67I.js} +1 -1
- package/chunks/{cron-delete-OLNEQWKO.js → cron-delete-E56CFQ7A.js} +1 -1
- package/chunks/{cron-list-YAUAUV24.js → cron-list-BKSINY6Y.js} +1 -1
- package/chunks/daemon-5T6IHWJV.js +8430 -0
- package/chunks/{daemon-status-provider-42JEXENZ.js → daemon-status-provider-V5UY7O7J.js} +26 -24
- package/chunks/{dist-4FCNM6KJ.js → dist-2HDL5SHQ.js} +2 -2
- package/chunks/{dist-SU6L7ZNJ.js → dist-B7SSDVWT.js} +3 -3
- package/chunks/{dist-VD7HXTML.js → dist-BL3SL665.js} +1 -1
- package/chunks/{dist-UKPWQJCL.js → dist-TAVAGZU6.js} +37 -13
- package/chunks/{dist-K4XZILIO.js → dist-XBOVZ3R5.js} +26 -5
- package/chunks/{earlyInputCapture-FFFEC6P4.js → earlyInputCapture-VVYU4FDO.js} +17 -15
- package/chunks/{edit-32ILSGZY.js → edit-F35PTMBA.js} +17 -15
- package/chunks/{en-U3LWGSFX.js → en-FZREZFPO.js} +18 -0
- package/chunks/{enter-worktree-XWZKIN6A.js → enter-worktree-3DIW7EGJ.js} +17 -15
- package/chunks/{enterPlanMode-LPFFY7BJ.js → enterPlanMode-QNXT77KP.js} +27 -15
- package/chunks/{errors-UXBPYPI2.js → errors-M53YG76R.js} +19 -17
- package/chunks/{exit-worktree-IBS4UIU7.js → exit-worktree-BSDWNWHZ.js} +17 -15
- package/chunks/{exitPlanMode-JA72YHII.js → exitPlanMode-EL7AY327.js} +41 -23
- package/chunks/{fast-path-7UU67Y2W.js → fast-path-DUCSMUFW.js} +2 -2
- package/chunks/{gemini-GMVKJNMX.js → gemini-F3IERB37.js} +59 -51
- package/chunks/{geminiContentGenerator-7NIFF2L4.js → geminiContentGenerator-6OULAKFQ.js} +1 -1
- package/chunks/{glob-4NJCW5HI.js → glob-ZMLFTD4T.js} +17 -15
- package/chunks/{grep-PAIMLQEL.js → grep-H5X66EI7.js} +17 -15
- package/chunks/{initializer-R6KU4VYM.js → initializer-LEOT2OLH.js} +21 -19
- package/chunks/{list-H7LXAO64.js → list-M3DKXML6.js} +23 -21
- package/chunks/{loadedSettingsAdapter-H27JMTLU.js → loadedSettingsAdapter-OKMQFGGG.js} +21 -19
- package/chunks/{loop-wakeup-Y3OIRQFM.js → loop-wakeup-FPBAVVPR.js} +2 -2
- package/chunks/{ls-TVJEYS7O.js → ls-UGWQXPTY.js} +1 -1
- package/chunks/{lsp-JZSDVR3F.js → lsp-24WV7NXB.js} +1 -1
- package/chunks/{monitor-JSIHDPXP.js → monitor-ARL5P45R.js} +19 -16
- package/chunks/{nonInteractiveCli-Y4CHOETG.js → nonInteractiveCli-NUVLF3YR.js} +53 -45
- package/chunks/{notebook-edit-SHRNL2UM.js → notebook-edit-ANTZ2DDY.js} +17 -15
- package/chunks/{openaiContentGenerator-ORZZ7VFO.js → openaiContentGenerator-TFGMKGHN.js} +6 -6
- package/chunks/pidfile-AWTL4C63.js +72 -0
- package/chunks/{qwenContentGenerator-H643PL2G.js → qwenContentGenerator-6QWAYTWS.js} +18 -16
- package/chunks/{qwenOAuth2-OI2B7PES.js → qwenOAuth2-LWLA6MQG.js} +3 -3
- package/chunks/{read-file-5PRNPTTY.js → read-file-EJY73XQ3.js} +4 -4
- package/chunks/{read-mcp-resource-QTXGKTEV.js → read-mcp-resource-NCBTPP7X.js} +1 -1
- package/chunks/{ripGrep-JIKZMABJ.js → ripGrep-HC4SA6CQ.js} +17 -15
- package/chunks/{run-qwen-serve-PQV7QOHZ.js → run-qwen-serve-AGLZ642J.js} +291 -55
- package/chunks/{scheduler-OS3R5MGU.js → scheduler-TURO7NGY.js} +17 -15
- package/chunks/{send-message-DKGHWSTF.js → send-message-3S2NYS3O.js} +5 -3
- package/chunks/{serve-LVYZDKCZ.js → serve-5DAMOIDW.js} +22 -19
- package/chunks/{server-P4YIH35I.js → server-4AGEK2HC.js} +2268 -1222
- package/chunks/{session-DE4TZBKH.js → session-7ZOQUC4Y.js} +54 -46
- package/chunks/{settings-DFTYMJYF.js → settings-DR4V6L5Y.js} +20 -18
- package/chunks/{shell-3GNQLNV7.js → shell-H5WFKUXH.js} +17 -15
- package/chunks/{skill-NCNG4KKZ.js → skill-ANHU5ZCR.js} +4 -4
- package/chunks/{spawnChannel-DHRIMDVL.js → spawnChannel-VN5LJV7Z.js} +19 -17
- package/chunks/{src-PNZYGM3R.js → src-OVZLTVVM.js} +49 -17
- package/chunks/{startInteractiveUI-P6GZBFQW.js → startInteractiveUI-OA22V32P.js} +11110 -10670
- package/chunks/{syntheticOutput-X7L5HWLN.js → syntheticOutput-SV7GBHZ5.js} +2 -2
- package/chunks/{task-create-AWEUI4GI.js → task-create-W2H2UJCH.js} +6 -5
- package/chunks/{task-list-KZ3VJLDU.js → task-list-THBOPV4G.js} +5 -4
- package/chunks/{task-stop-GUNOZKJ5.js → task-stop-33FVCNCJ.js} +1 -1
- package/chunks/{task-update-Y56GP6DU.js → task-update-LVFQLXEI.js} +6 -5
- package/chunks/{team-create-6JMW3KQK.js → team-create-OYL5CAQO.js} +17 -15
- package/chunks/{team-delete-6AFRGDJY.js → team-delete-NTUQ25UY.js} +5 -4
- package/chunks/{theme-manager-FEHQO22G.js → theme-manager-XCMPF2SI.js} +17 -15
- package/chunks/{todoWrite-JRS6NOEU.js → todoWrite-SEPO2RI3.js} +1 -1
- package/chunks/{tool-search-IXAEKS7J.js → tool-search-YXW55X6Z.js} +29 -5
- package/chunks/{trustedFolders-SJRXNHZE.js → trustedFolders-TEPGM5YL.js} +18 -16
- package/chunks/{validateNonInterActiveAuth-HCH7OBDH.js → validateNonInterActiveAuth-OQIS7WUE.js} +50 -42
- package/chunks/{web-fetch-RELNL37A.js → web-fetch-OE7LHURA.js} +4 -4
- package/chunks/{workflow-M2COBI7R.js → workflow-RTUWORAP.js} +48 -27
- package/chunks/{workspace-providers-status-GOCWYDMK.js → workspace-providers-status-DM2XANVB.js} +23 -21
- package/chunks/{workspace-service-34C7SUBU.js → workspace-service-VKBI2KZD.js} +27 -25
- package/chunks/{write-file-E52WRXZL.js → write-file-WGALQ53L.js} +17 -15
- package/chunks/{zh-TW-LKLBTZHE.js → zh-TW-6GIKXEAN.js} +18 -0
- package/chunks/{zh-F5T4WWZO.js → zh-V3B4TFWL.js} +18 -0
- package/cli.js +6 -6
- package/locales/en.js +30 -0
- package/locales/fr.js +1 -2
- package/locales/ja.js +1 -2
- package/locales/zh-TW.js +29 -0
- package/locales/zh.js +29 -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-C2DILvUl.js → arc-Det5Ccum.js} +1 -1
- package/web-shell/assets/{architectureDiagram-3BPJPVTR-b8gZ2cNw.js → architectureDiagram-3BPJPVTR-DqNWVFpl.js} +1 -1
- package/web-shell/assets/{blockDiagram-GPEHLZMM-BuRpghMj.js → blockDiagram-GPEHLZMM-CYl7tYBi.js} +1 -1
- package/web-shell/assets/{c4Diagram-AAUBKEIU-2ItdhFmE.js → c4Diagram-AAUBKEIU-DaAEi0NQ.js} +1 -1
- package/web-shell/assets/channel-Bm0bFauD.js +1 -0
- package/web-shell/assets/{chunk-2J33WTMH-B1QXa0q3.js → chunk-2J33WTMH-Df2CDEE2.js} +1 -1
- package/web-shell/assets/{chunk-4BX2VUAB-DXfhKlNq.js → chunk-4BX2VUAB-DNBFcyL2.js} +1 -1
- package/web-shell/assets/{chunk-55IACEB6-ClSNjxLo.js → chunk-55IACEB6-BKjF-0HI.js} +1 -1
- package/web-shell/assets/{chunk-727SXJPM-Dsh2wisB.js → chunk-727SXJPM-DYwAwfIM.js} +1 -1
- package/web-shell/assets/{chunk-AQP2D5EJ-C1TkY5AR.js → chunk-AQP2D5EJ-C6FswLqy.js} +1 -1
- package/web-shell/assets/{chunk-FMBD7UC4-CGtN9XR6.js → chunk-FMBD7UC4-DRWAcZSY.js} +1 -1
- package/web-shell/assets/{chunk-ND2GUHAM-BHiPi1SS.js → chunk-ND2GUHAM-AoL16mNR.js} +1 -1
- package/web-shell/assets/{chunk-QZHKN3VN-CzG3I5o1.js → chunk-QZHKN3VN-a8PtLMck.js} +1 -1
- package/web-shell/assets/classDiagram-4FO5ZUOK--NgUfb55.js +1 -0
- package/web-shell/assets/classDiagram-v2-Q7XG4LA2--NgUfb55.js +1 -0
- package/web-shell/assets/{cose-bilkent-S5V4N54A-EgrIoH_K.js → cose-bilkent-S5V4N54A-C4Aeu5qf.js} +1 -1
- package/web-shell/assets/{dagre-BM42HDAG-BUnpdX8S.js → dagre-BM42HDAG-BOulamjo.js} +1 -1
- package/web-shell/assets/{diagram-2AECGRRQ-BlLUCVYu.js → diagram-2AECGRRQ-D1wt2ykO.js} +1 -1
- package/web-shell/assets/{diagram-5GNKFQAL-FG-enmjQ.js → diagram-5GNKFQAL-BRseus-O.js} +1 -1
- package/web-shell/assets/{diagram-KO2AKTUF-BlzmTONk.js → diagram-KO2AKTUF-XSMkNzpZ.js} +1 -1
- package/web-shell/assets/{diagram-LMA3HP47-CoBQ8mIx.js → diagram-LMA3HP47-21KNLqoi.js} +1 -1
- package/web-shell/assets/{diagram-OG6HWLK6-DvxV6sX8.js → diagram-OG6HWLK6-DnUI8F5G.js} +1 -1
- package/web-shell/assets/{erDiagram-TEJ5UH35-Jq1tpAoj.js → erDiagram-TEJ5UH35-BVSOMuM2.js} +1 -1
- package/web-shell/assets/{flowDiagram-I6XJVG4X-CbtYOry5.js → flowDiagram-I6XJVG4X-fYtDPJsW.js} +1 -1
- package/web-shell/assets/{ganttDiagram-6RSMTGT7-ZEn2VEKR.js → ganttDiagram-6RSMTGT7-CmRXosW_.js} +1 -1
- package/web-shell/assets/{gitGraphDiagram-PVQCEYII-BZh-LkhJ.js → gitGraphDiagram-PVQCEYII-DPrmySxO.js} +1 -1
- package/web-shell/assets/index-BpKnmyFV.js +717 -0
- package/web-shell/assets/index-fLrtxpOG.css +5 -0
- package/web-shell/assets/{infoDiagram-5YYISTIA-B9HuZygK.js → infoDiagram-5YYISTIA-x3VbEuCV.js} +1 -1
- package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-D76cNpYG.js → ishikawaDiagram-YF4QCWOH-SmitHRMC.js} +1 -1
- package/web-shell/assets/{journeyDiagram-JHISSGLW-6bGo7P1t.js → journeyDiagram-JHISSGLW-BxvA551S.js} +1 -1
- package/web-shell/assets/{kanban-definition-UN3LZRKU-DuYyRBvB.js → kanban-definition-UN3LZRKU-DNHC16jY.js} +1 -1
- package/web-shell/assets/{linear-CpdSW1BC.js → linear-CQ6irBL9.js} +1 -1
- package/web-shell/assets/{mermaid.core-s5xbZMMj.js → mermaid.core-DtB6NO1e.js} +72 -72
- package/web-shell/assets/{mindmap-definition-RKZ34NQL-go01fZ2y.js → mindmap-definition-RKZ34NQL-QKMAgV2v.js} +1 -1
- package/web-shell/assets/{pieDiagram-4H26LBE5-BfuZiEHO.js → pieDiagram-4H26LBE5-bvb6dGn6.js} +1 -1
- package/web-shell/assets/{quadrantDiagram-W4KKPZXB-DsoINqV6.js → quadrantDiagram-W4KKPZXB-DLgdlG4D.js} +1 -1
- package/web-shell/assets/{requirementDiagram-4Y6WPE33-B7FU4LiU.js → requirementDiagram-4Y6WPE33-2M9YH4GF.js} +1 -1
- package/web-shell/assets/{sankeyDiagram-5OEKKPKP-BEDjFnGB.js → sankeyDiagram-5OEKKPKP-DzwfclTH.js} +1 -1
- package/web-shell/assets/{sequenceDiagram-3UESZ5HK-gzmmL5tK.js → sequenceDiagram-3UESZ5HK-BvPmTuy2.js} +1 -1
- package/web-shell/assets/{stateDiagram-AJRCARHV-BtHD4IN_.js → stateDiagram-AJRCARHV-BjwUmrJF.js} +1 -1
- package/web-shell/assets/stateDiagram-v2-BHNVJYJU-C4L8zXeL.js +1 -0
- package/web-shell/assets/{timeline-definition-PNZ67QCA-T_8gmMCf.js → timeline-definition-PNZ67QCA-CXzS6qqz.js} +1 -1
- package/web-shell/assets/{vennDiagram-CIIHVFJN-DdjuqnjO.js → vennDiagram-CIIHVFJN-yRSolF5B.js} +1 -1
- package/web-shell/assets/{wardley-L42UT6IY-Cgtrvb37.js → wardley-L42UT6IY-ClLJjHPG.js} +1 -1
- package/web-shell/assets/{wardleyDiagram-YWT4CUSO-BW3f3-yW.js → wardleyDiagram-YWT4CUSO-VIa6t2p2.js} +1 -1
- package/web-shell/assets/{xychartDiagram-2RQKCTM6-BUS9IKkc.js → xychartDiagram-2RQKCTM6-CtmrkezW.js} +1 -1
- package/web-shell/index.html +18 -2
- package/chunks/chunk-2JKEQWDZ.js +0 -1596
- package/chunks/chunk-32MCHZQC.js +0 -73
- package/chunks/chunk-UAKXW3DN.js +0 -30
- package/web-shell/assets/channel-eRvn2UA3.js +0 -1
- package/web-shell/assets/classDiagram-4FO5ZUOK-YZNwy1rx.js +0 -1
- package/web-shell/assets/classDiagram-v2-Q7XG4LA2-YZNwy1rx.js +0 -1
- package/web-shell/assets/index-5FhE0ORO.css +0 -5
- package/web-shell/assets/index-CDTGmqVm.js +0 -720
- package/web-shell/assets/stateDiagram-v2-BHNVJYJU-DFy6gLDd.js +0 -1
package/chunks/chunk-2JKEQWDZ.js
DELETED
|
@@ -1,1596 +0,0 @@
|
|
|
1
|
-
// Force strict mode and setup for ESM
|
|
2
|
-
"use strict";
|
|
3
|
-
import {
|
|
4
|
-
ClientSideConnection,
|
|
5
|
-
PROTOCOL_VERSION,
|
|
6
|
-
ndJsonStream
|
|
7
|
-
} from "./chunk-UFC57OYT.js";
|
|
8
|
-
import {
|
|
9
|
-
init_esbuild_shims
|
|
10
|
-
} from "./chunk-A4BMJM77.js";
|
|
11
|
-
import {
|
|
12
|
-
__name
|
|
13
|
-
} from "./chunk-J2S4EL5Y.js";
|
|
14
|
-
|
|
15
|
-
// packages/channels/base/dist/paths.js
|
|
16
|
-
init_esbuild_shims();
|
|
17
|
-
import * as path from "node:path";
|
|
18
|
-
import * as os from "node:os";
|
|
19
|
-
function resolvePath(dir) {
|
|
20
|
-
let resolved = dir;
|
|
21
|
-
if (resolved === "~" || resolved.startsWith("~/") || resolved.startsWith("~\\")) {
|
|
22
|
-
const relativeSegments = resolved === "~" ? [] : resolved.slice(2).split(/[/\\]+/).filter(Boolean);
|
|
23
|
-
resolved = path.join(os.homedir(), ...relativeSegments);
|
|
24
|
-
}
|
|
25
|
-
if (!path.isAbsolute(resolved)) {
|
|
26
|
-
resolved = path.resolve(resolved);
|
|
27
|
-
}
|
|
28
|
-
return resolved;
|
|
29
|
-
}
|
|
30
|
-
__name(resolvePath, "resolvePath");
|
|
31
|
-
function getGlobalQwenDir() {
|
|
32
|
-
const envDir = process.env["QWEN_HOME"];
|
|
33
|
-
if (envDir) {
|
|
34
|
-
return resolvePath(envDir);
|
|
35
|
-
}
|
|
36
|
-
const homeDir = os.homedir();
|
|
37
|
-
return homeDir ? path.join(homeDir, ".qwen") : path.join(os.tmpdir(), ".qwen");
|
|
38
|
-
}
|
|
39
|
-
__name(getGlobalQwenDir, "getGlobalQwenDir");
|
|
40
|
-
|
|
41
|
-
// packages/channels/base/dist/SessionRouter.js
|
|
42
|
-
init_esbuild_shims();
|
|
43
|
-
import { existsSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
44
|
-
import process2 from "node:process";
|
|
45
|
-
var SessionRouter = class {
|
|
46
|
-
static {
|
|
47
|
-
__name(this, "SessionRouter");
|
|
48
|
-
}
|
|
49
|
-
toSession = /* @__PURE__ */ new Map();
|
|
50
|
-
// routing key → session ID
|
|
51
|
-
toTarget = /* @__PURE__ */ new Map();
|
|
52
|
-
// session ID → target
|
|
53
|
-
toCwd = /* @__PURE__ */ new Map();
|
|
54
|
-
// session ID → cwd
|
|
55
|
-
creatingSessions = /* @__PURE__ */ new Map();
|
|
56
|
-
sessionLoadWindows = /* @__PURE__ */ new Set();
|
|
57
|
-
bridge;
|
|
58
|
-
defaultCwd;
|
|
59
|
-
defaultScope;
|
|
60
|
-
channelScopes = /* @__PURE__ */ new Map();
|
|
61
|
-
persistPath;
|
|
62
|
-
constructor(bridge, defaultCwd, scope = "user", persistPath) {
|
|
63
|
-
this.bridge = bridge;
|
|
64
|
-
this.defaultCwd = defaultCwd;
|
|
65
|
-
this.defaultScope = scope;
|
|
66
|
-
this.persistPath = persistPath;
|
|
67
|
-
}
|
|
68
|
-
/** Replace the bridge instance (used after crash recovery restart). */
|
|
69
|
-
setBridge(bridge) {
|
|
70
|
-
this.bridge = bridge;
|
|
71
|
-
}
|
|
72
|
-
/** Set scope override for a specific channel. */
|
|
73
|
-
setChannelScope(channelName, scope) {
|
|
74
|
-
this.channelScopes.set(channelName, scope);
|
|
75
|
-
}
|
|
76
|
-
routingKey(channelName, senderId, chatId, threadId) {
|
|
77
|
-
const scope = this.channelScopes.get(channelName) || this.defaultScope;
|
|
78
|
-
switch (scope) {
|
|
79
|
-
case "thread":
|
|
80
|
-
return `${channelName}:${threadId || chatId}`;
|
|
81
|
-
case "single":
|
|
82
|
-
return `${channelName}:__single__`;
|
|
83
|
-
case "user":
|
|
84
|
-
default:
|
|
85
|
-
return `${channelName}:${senderId}:${chatId}`;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
async resolve(channelName, senderId, chatId, threadId, cwd) {
|
|
89
|
-
const key = this.routingKey(channelName, senderId, chatId, threadId);
|
|
90
|
-
let failedCreateWaits = 0;
|
|
91
|
-
for (; ; ) {
|
|
92
|
-
const existing = this.toSession.get(key);
|
|
93
|
-
if (existing) {
|
|
94
|
-
return existing;
|
|
95
|
-
}
|
|
96
|
-
const creating = this.creatingSessions.get(key);
|
|
97
|
-
if (creating) {
|
|
98
|
-
try {
|
|
99
|
-
return await creating;
|
|
100
|
-
} catch (err) {
|
|
101
|
-
if (this.creatingSessions.get(key) === creating) {
|
|
102
|
-
this.creatingSessions.delete(key);
|
|
103
|
-
}
|
|
104
|
-
failedCreateWaits++;
|
|
105
|
-
if (failedCreateWaits > 3) {
|
|
106
|
-
throw err;
|
|
107
|
-
}
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
const created = Promise.resolve().then(async () => {
|
|
112
|
-
const sessionCwd = cwd || this.defaultCwd;
|
|
113
|
-
const loadWindow = this.beginSessionLoad();
|
|
114
|
-
try {
|
|
115
|
-
const sessionId = await this.createLiveSession(sessionCwd, loadWindow, key);
|
|
116
|
-
this.toSession.set(key, sessionId);
|
|
117
|
-
this.toTarget.set(sessionId, {
|
|
118
|
-
channelName,
|
|
119
|
-
senderId,
|
|
120
|
-
chatId,
|
|
121
|
-
threadId
|
|
122
|
-
});
|
|
123
|
-
this.toCwd.set(sessionId, sessionCwd);
|
|
124
|
-
this.persist();
|
|
125
|
-
return sessionId;
|
|
126
|
-
} finally {
|
|
127
|
-
this.endSessionLoad(loadWindow);
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
this.creatingSessions.set(key, created);
|
|
131
|
-
try {
|
|
132
|
-
return await created;
|
|
133
|
-
} finally {
|
|
134
|
-
if (this.creatingSessions.get(key) === created) {
|
|
135
|
-
this.creatingSessions.delete(key);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
getTarget(sessionId) {
|
|
141
|
-
return this.toTarget.get(sessionId);
|
|
142
|
-
}
|
|
143
|
-
getSession(channelName, senderId, chatId, threadId) {
|
|
144
|
-
return this.toSession.get(this.routingKey(channelName, senderId, chatId, threadId));
|
|
145
|
-
}
|
|
146
|
-
hasSession(channelName, senderId, chatId, threadId) {
|
|
147
|
-
if (chatId) {
|
|
148
|
-
return this.toSession.has(this.routingKey(channelName, senderId, chatId, threadId));
|
|
149
|
-
}
|
|
150
|
-
for (const target of this.toTarget.values()) {
|
|
151
|
-
if (target.channelName === channelName && target.senderId === senderId) {
|
|
152
|
-
return true;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return false;
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Remove session(s) for the given sender. Returns the removed session IDs.
|
|
159
|
-
*/
|
|
160
|
-
removeSession(channelName, senderId, chatId, threadId) {
|
|
161
|
-
const removedIds = [];
|
|
162
|
-
if (chatId) {
|
|
163
|
-
const key = this.routingKey(channelName, senderId, chatId, threadId);
|
|
164
|
-
const sessionId = this.deleteByKey(key);
|
|
165
|
-
if (sessionId)
|
|
166
|
-
removedIds.push(sessionId);
|
|
167
|
-
} else {
|
|
168
|
-
for (const [k, mappedSessionId] of [...this.toSession.entries()]) {
|
|
169
|
-
const target = this.toTarget.get(mappedSessionId);
|
|
170
|
-
if (target?.channelName === channelName && target.senderId === senderId) {
|
|
171
|
-
const sessionId = this.deleteByKey(k);
|
|
172
|
-
if (sessionId)
|
|
173
|
-
removedIds.push(sessionId);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
if (removedIds.length > 0)
|
|
178
|
-
this.persist();
|
|
179
|
-
return removedIds;
|
|
180
|
-
}
|
|
181
|
-
/** Remove a session mapping by daemon/ACP session ID. */
|
|
182
|
-
removeSessionId(sessionId) {
|
|
183
|
-
let removed = false;
|
|
184
|
-
for (const [key, mappedSessionId] of [...this.toSession.entries()]) {
|
|
185
|
-
if (mappedSessionId === sessionId) {
|
|
186
|
-
this.toSession.delete(key);
|
|
187
|
-
removed = true;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
if (this.toTarget.delete(sessionId)) {
|
|
191
|
-
removed = true;
|
|
192
|
-
}
|
|
193
|
-
if (this.toCwd.delete(sessionId)) {
|
|
194
|
-
removed = true;
|
|
195
|
-
}
|
|
196
|
-
if (!removed && this.sessionLoadWindows.size > 0) {
|
|
197
|
-
for (const loadWindow of this.sessionLoadWindows) {
|
|
198
|
-
loadWindow.add(sessionId);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
if (removed) {
|
|
202
|
-
this.persist();
|
|
203
|
-
}
|
|
204
|
-
return removed;
|
|
205
|
-
}
|
|
206
|
-
deleteByKey(key) {
|
|
207
|
-
const sessionId = this.toSession.get(key);
|
|
208
|
-
if (!sessionId)
|
|
209
|
-
return null;
|
|
210
|
-
this.toSession.delete(key);
|
|
211
|
-
this.toTarget.delete(sessionId);
|
|
212
|
-
this.toCwd.delete(sessionId);
|
|
213
|
-
return sessionId;
|
|
214
|
-
}
|
|
215
|
-
/** Get all session entries for crash recovery. */
|
|
216
|
-
getAll() {
|
|
217
|
-
const entries = [];
|
|
218
|
-
for (const [key, sessionId] of this.toSession) {
|
|
219
|
-
const target = this.toTarget.get(sessionId);
|
|
220
|
-
if (target) {
|
|
221
|
-
entries.push({ key, sessionId, target });
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
return entries;
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* Restore session mappings from a previous bridge.
|
|
228
|
-
* Called after bridge restart — attempts loadSession for each saved mapping.
|
|
229
|
-
* Failed loads are dropped (new session on next message).
|
|
230
|
-
*/
|
|
231
|
-
async restoreSessions() {
|
|
232
|
-
if (!this.persistPath || !existsSync(this.persistPath)) {
|
|
233
|
-
return { restored: 0, failed: 0 };
|
|
234
|
-
}
|
|
235
|
-
let entries;
|
|
236
|
-
try {
|
|
237
|
-
entries = JSON.parse(readFileSync(this.persistPath, "utf-8"));
|
|
238
|
-
} catch {
|
|
239
|
-
return { restored: 0, failed: 0 };
|
|
240
|
-
}
|
|
241
|
-
let restored = 0;
|
|
242
|
-
let failed = 0;
|
|
243
|
-
let changed = false;
|
|
244
|
-
const reservations = /* @__PURE__ */ new Map();
|
|
245
|
-
for (const key of Object.keys(entries)) {
|
|
246
|
-
this.deleteByKey(key);
|
|
247
|
-
const reservation = this.createSessionReservation();
|
|
248
|
-
reservation.promise.catch(() => void 0);
|
|
249
|
-
this.creatingSessions.set(key, reservation.promise);
|
|
250
|
-
reservations.set(key, reservation);
|
|
251
|
-
}
|
|
252
|
-
const loadWindow = this.beginSessionLoad();
|
|
253
|
-
try {
|
|
254
|
-
for (const [key, entry] of Object.entries(entries)) {
|
|
255
|
-
const reservation = reservations.get(key);
|
|
256
|
-
if (!reservation)
|
|
257
|
-
continue;
|
|
258
|
-
try {
|
|
259
|
-
const sessionId = await this.bridge.loadSession(entry.sessionId, entry.cwd);
|
|
260
|
-
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
261
|
-
throw new Error("Invalid restored session ID");
|
|
262
|
-
}
|
|
263
|
-
if (loadWindow.delete(sessionId)) {
|
|
264
|
-
throw new Error("Restored session died before routing completed");
|
|
265
|
-
}
|
|
266
|
-
this.toSession.set(key, sessionId);
|
|
267
|
-
this.toTarget.set(sessionId, entry.target);
|
|
268
|
-
this.toCwd.set(sessionId, entry.cwd);
|
|
269
|
-
reservation.resolve(sessionId);
|
|
270
|
-
if (sessionId !== entry.sessionId) {
|
|
271
|
-
changed = true;
|
|
272
|
-
}
|
|
273
|
-
restored++;
|
|
274
|
-
} catch (err) {
|
|
275
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
276
|
-
process2.stderr.write(`[SessionRouter] Failed to restore session ${entry.sessionId} for key ${key}: ${reason}
|
|
277
|
-
`);
|
|
278
|
-
reservation.reject(new Error("Session restore failed", { cause: err }));
|
|
279
|
-
failed++;
|
|
280
|
-
changed = true;
|
|
281
|
-
} finally {
|
|
282
|
-
if (this.creatingSessions.get(key) === reservation.promise) {
|
|
283
|
-
this.creatingSessions.delete(key);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
} finally {
|
|
288
|
-
this.endSessionLoad(loadWindow);
|
|
289
|
-
}
|
|
290
|
-
if (changed) {
|
|
291
|
-
this.persist();
|
|
292
|
-
}
|
|
293
|
-
return { restored, failed };
|
|
294
|
-
}
|
|
295
|
-
/** Clear in-memory state and delete persist file. Used on clean shutdown. */
|
|
296
|
-
clearAll() {
|
|
297
|
-
this.toSession.clear();
|
|
298
|
-
this.toTarget.clear();
|
|
299
|
-
this.toCwd.clear();
|
|
300
|
-
this.creatingSessions.clear();
|
|
301
|
-
this.sessionLoadWindows.clear();
|
|
302
|
-
if (this.persistPath && existsSync(this.persistPath)) {
|
|
303
|
-
try {
|
|
304
|
-
unlinkSync(this.persistPath);
|
|
305
|
-
} catch {
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
persist() {
|
|
310
|
-
if (!this.persistPath)
|
|
311
|
-
return;
|
|
312
|
-
const data = {};
|
|
313
|
-
for (const [key, sessionId] of this.toSession) {
|
|
314
|
-
const target = this.toTarget.get(sessionId);
|
|
315
|
-
if (target) {
|
|
316
|
-
data[key] = {
|
|
317
|
-
sessionId,
|
|
318
|
-
target,
|
|
319
|
-
cwd: this.toCwd.get(sessionId) || this.defaultCwd
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
try {
|
|
324
|
-
writeFileSync(this.persistPath, JSON.stringify(data, null, 2), "utf-8");
|
|
325
|
-
} catch {
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
async createLiveSession(cwd, loadWindow, routingKey) {
|
|
329
|
-
const maxAttempts = 2;
|
|
330
|
-
let lastDeadSessionId;
|
|
331
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
332
|
-
const sessionId = await this.bridge.newSession(cwd);
|
|
333
|
-
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
334
|
-
throw new Error("Invalid session ID from bridge");
|
|
335
|
-
}
|
|
336
|
-
if (!loadWindow.delete(sessionId)) {
|
|
337
|
-
return sessionId;
|
|
338
|
-
}
|
|
339
|
-
lastDeadSessionId = sessionId;
|
|
340
|
-
}
|
|
341
|
-
throw new Error(`Session ${lastDeadSessionId ?? "unknown"} died before routing completed (${maxAttempts}/${maxAttempts} attempts, key ${routingKey})`);
|
|
342
|
-
}
|
|
343
|
-
beginSessionLoad() {
|
|
344
|
-
const loadWindow = /* @__PURE__ */ new Set();
|
|
345
|
-
this.sessionLoadWindows.add(loadWindow);
|
|
346
|
-
return loadWindow;
|
|
347
|
-
}
|
|
348
|
-
createSessionReservation() {
|
|
349
|
-
let resolveReservation;
|
|
350
|
-
let rejectReservation;
|
|
351
|
-
const promise = new Promise((resolve2, reject) => {
|
|
352
|
-
resolveReservation = resolve2;
|
|
353
|
-
rejectReservation = reject;
|
|
354
|
-
});
|
|
355
|
-
return {
|
|
356
|
-
promise,
|
|
357
|
-
resolve: resolveReservation,
|
|
358
|
-
reject: rejectReservation
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
endSessionLoad(loadWindow) {
|
|
362
|
-
this.sessionLoadWindows.delete(loadWindow);
|
|
363
|
-
}
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
// packages/channels/base/dist/sanitize.js
|
|
367
|
-
init_esbuild_shims();
|
|
368
|
-
var PROMPT_UNSAFE_INVISIBLES = /[\u0080-\u009f\u200b-\u200d\u2028\u2029\u202a-\u202e\u2060\u2066-\u2069\ufeff]/g;
|
|
369
|
-
function truncateCodePoints(str, max) {
|
|
370
|
-
const cp = Array.from(str);
|
|
371
|
-
return cp.length > max ? cp.slice(0, max).join("") : str;
|
|
372
|
-
}
|
|
373
|
-
__name(truncateCodePoints, "truncateCodePoints");
|
|
374
|
-
function sanitizeSenderName(name) {
|
|
375
|
-
const cleaned = name.replace(PROMPT_UNSAFE_INVISIBLES, " ").replace(/[\u0000-\u001f\u007f]/g, " ").replace(/[[\]\r\n]/g, " ");
|
|
376
|
-
return truncateCodePoints(cleaned, 64).trim() || "unknown";
|
|
377
|
-
}
|
|
378
|
-
__name(sanitizeSenderName, "sanitizeSenderName");
|
|
379
|
-
function sanitizeQuotedText(text, maxLen) {
|
|
380
|
-
const cleaned = text.replace(PROMPT_UNSAFE_INVISIBLES, " ").replace(/[\u0000-\u001f\u007f]/g, " ").replace(/["[\]]/g, " ");
|
|
381
|
-
const cp = Array.from(cleaned);
|
|
382
|
-
return cp.length > maxLen ? cp.slice(0, maxLen - 1).join("") + "\u2026" : cleaned;
|
|
383
|
-
}
|
|
384
|
-
__name(sanitizeQuotedText, "sanitizeQuotedText");
|
|
385
|
-
function sanitizePromptText(text) {
|
|
386
|
-
return text.replace(PROMPT_UNSAFE_INVISIBLES, " ").replace(/^([ \t]*)\[([^\]\r\n]{1,64})\](:?)/gm, "$1$2$3").replace(/[\u0000-\u001f\u007f]/g, " ");
|
|
387
|
-
}
|
|
388
|
-
__name(sanitizePromptText, "sanitizePromptText");
|
|
389
|
-
function sanitizePromptPath(path3) {
|
|
390
|
-
const cleaned = path3.replace(PROMPT_UNSAFE_INVISIBLES, " ").replace(/[\u0000-\u001f\u007f]/g, " ");
|
|
391
|
-
return truncateCodePoints(cleaned, 1024);
|
|
392
|
-
}
|
|
393
|
-
__name(sanitizePromptPath, "sanitizePromptPath");
|
|
394
|
-
function sanitizeLogText(text, maxLen) {
|
|
395
|
-
return truncateCodePoints(text, maxLen).replace(/\n/g, "\\n").replace(PROMPT_UNSAFE_INVISIBLES, " ").replace(/[\u0000-\u001f\u007f]/g, " ");
|
|
396
|
-
}
|
|
397
|
-
__name(sanitizeLogText, "sanitizeLogText");
|
|
398
|
-
|
|
399
|
-
// packages/channels/base/dist/ChannelBase.js
|
|
400
|
-
init_esbuild_shims();
|
|
401
|
-
import { basename } from "node:path";
|
|
402
|
-
|
|
403
|
-
// packages/channels/base/dist/BlockStreamer.js
|
|
404
|
-
init_esbuild_shims();
|
|
405
|
-
var BlockStreamer = class {
|
|
406
|
-
static {
|
|
407
|
-
__name(this, "BlockStreamer");
|
|
408
|
-
}
|
|
409
|
-
buffer = "";
|
|
410
|
-
idleTimer = null;
|
|
411
|
-
sending = Promise.resolve();
|
|
412
|
-
opts;
|
|
413
|
-
/** Number of blocks emitted so far. */
|
|
414
|
-
blockCount = 0;
|
|
415
|
-
constructor(opts) {
|
|
416
|
-
this.opts = opts;
|
|
417
|
-
}
|
|
418
|
-
/** Feed a new text chunk from the agent stream. */
|
|
419
|
-
push(chunk) {
|
|
420
|
-
this.buffer += chunk;
|
|
421
|
-
this.clearIdleTimer();
|
|
422
|
-
this.checkEmit();
|
|
423
|
-
if (this.buffer.length > 0 && this.opts.idleMs > 0) {
|
|
424
|
-
this.idleTimer = setTimeout(() => this.onIdle(), this.opts.idleMs);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
/** Flush all remaining buffered text. Awaits all pending sends. */
|
|
428
|
-
async flush() {
|
|
429
|
-
this.clearIdleTimer();
|
|
430
|
-
if (this.buffer.length > 0) {
|
|
431
|
-
this.emitBlock(this.buffer);
|
|
432
|
-
this.buffer = "";
|
|
433
|
-
}
|
|
434
|
-
await this.sending;
|
|
435
|
-
}
|
|
436
|
-
/** Drop buffered text and cancel future idle emission. */
|
|
437
|
-
stop() {
|
|
438
|
-
this.clearIdleTimer();
|
|
439
|
-
this.buffer = "";
|
|
440
|
-
}
|
|
441
|
-
// ---------------------------------------------------------------------------
|
|
442
|
-
// Internal
|
|
443
|
-
// ---------------------------------------------------------------------------
|
|
444
|
-
checkEmit() {
|
|
445
|
-
while (this.buffer.length >= this.opts.maxChars) {
|
|
446
|
-
const bp = this.findBreakPoint(this.buffer, this.opts.maxChars);
|
|
447
|
-
this.emitBlock(this.buffer.slice(0, bp));
|
|
448
|
-
this.buffer = this.buffer.slice(bp);
|
|
449
|
-
}
|
|
450
|
-
if (this.buffer.length >= this.opts.minChars) {
|
|
451
|
-
const bp = this.findBlockBoundary(this.buffer);
|
|
452
|
-
if (bp > 0) {
|
|
453
|
-
this.emitBlock(this.buffer.slice(0, bp));
|
|
454
|
-
this.buffer = this.buffer.slice(bp);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
onIdle() {
|
|
459
|
-
this.idleTimer = null;
|
|
460
|
-
if (this.buffer.length >= this.opts.minChars) {
|
|
461
|
-
this.emitBlock(this.buffer);
|
|
462
|
-
this.buffer = "";
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
emitBlock(text) {
|
|
466
|
-
const trimmed = text.trim();
|
|
467
|
-
if (!trimmed)
|
|
468
|
-
return;
|
|
469
|
-
this.blockCount++;
|
|
470
|
-
this.sending = this.sending.then(() => this.opts.send(trimmed)).catch(() => {
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
/**
|
|
474
|
-
* Find the last paragraph boundary (\n\n) in the buffer.
|
|
475
|
-
* Returns the position after the boundary, or -1 if no suitable boundary
|
|
476
|
-
* exists at or after minChars.
|
|
477
|
-
*/
|
|
478
|
-
findBlockBoundary(text) {
|
|
479
|
-
const last = text.lastIndexOf("\n\n");
|
|
480
|
-
if (last < 0 || last < this.opts.minChars)
|
|
481
|
-
return -1;
|
|
482
|
-
return last + 2;
|
|
483
|
-
}
|
|
484
|
-
/**
|
|
485
|
-
* Find the best break point at or before maxPos.
|
|
486
|
-
* Prefers paragraph break > newline > space > maxPos.
|
|
487
|
-
*/
|
|
488
|
-
findBreakPoint(text, maxPos) {
|
|
489
|
-
const sub = text.slice(0, maxPos);
|
|
490
|
-
const para = sub.lastIndexOf("\n\n");
|
|
491
|
-
if (para > 0)
|
|
492
|
-
return para + 2;
|
|
493
|
-
const nl = sub.lastIndexOf("\n");
|
|
494
|
-
if (nl > 0)
|
|
495
|
-
return nl + 1;
|
|
496
|
-
const sp = sub.lastIndexOf(" ");
|
|
497
|
-
if (sp > 0)
|
|
498
|
-
return sp + 1;
|
|
499
|
-
return maxPos;
|
|
500
|
-
}
|
|
501
|
-
clearIdleTimer() {
|
|
502
|
-
if (this.idleTimer !== null) {
|
|
503
|
-
clearTimeout(this.idleTimer);
|
|
504
|
-
this.idleTimer = null;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
};
|
|
508
|
-
|
|
509
|
-
// packages/channels/base/dist/GroupGate.js
|
|
510
|
-
init_esbuild_shims();
|
|
511
|
-
var GroupGate = class {
|
|
512
|
-
static {
|
|
513
|
-
__name(this, "GroupGate");
|
|
514
|
-
}
|
|
515
|
-
policy;
|
|
516
|
-
groups;
|
|
517
|
-
constructor(policy = "disabled", groups = {}) {
|
|
518
|
-
this.policy = policy;
|
|
519
|
-
this.groups = groups;
|
|
520
|
-
}
|
|
521
|
-
/**
|
|
522
|
-
* Full group check: policy + allowlist + mention gating.
|
|
523
|
-
* Evaluation order:
|
|
524
|
-
* 1. groupPolicy (disabled → drop)
|
|
525
|
-
* 2. group allowlist (allowlist mode, no match → drop)
|
|
526
|
-
* 3. mention gating (requireMention + not mentioned → drop silently)
|
|
527
|
-
*
|
|
528
|
-
* Mention gating runs before sender gate so that unmentioned messages
|
|
529
|
-
* in groups don't trigger pairing flows.
|
|
530
|
-
*/
|
|
531
|
-
check(envelope) {
|
|
532
|
-
if (!envelope.isGroup) {
|
|
533
|
-
return { allowed: true };
|
|
534
|
-
}
|
|
535
|
-
if (this.policy === "disabled") {
|
|
536
|
-
return { allowed: false, reason: "disabled" };
|
|
537
|
-
}
|
|
538
|
-
if (this.policy === "allowlist") {
|
|
539
|
-
if (!this.groups[envelope.chatId]) {
|
|
540
|
-
return { allowed: false, reason: "not_allowlisted" };
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
const groupConfig = this.groups[envelope.chatId] || this.groups["*"] || {};
|
|
544
|
-
const requireMention = groupConfig.requireMention ?? true;
|
|
545
|
-
if (requireMention && !envelope.isMentioned && !envelope.isReplyToBot) {
|
|
546
|
-
return { allowed: false, reason: "mention_required" };
|
|
547
|
-
}
|
|
548
|
-
return { allowed: true };
|
|
549
|
-
}
|
|
550
|
-
};
|
|
551
|
-
|
|
552
|
-
// packages/channels/base/dist/SenderGate.js
|
|
553
|
-
init_esbuild_shims();
|
|
554
|
-
var SenderGate = class {
|
|
555
|
-
static {
|
|
556
|
-
__name(this, "SenderGate");
|
|
557
|
-
}
|
|
558
|
-
policy;
|
|
559
|
-
allowedUsers;
|
|
560
|
-
pairingStore;
|
|
561
|
-
constructor(policy, allowedUsers = [], pairingStore) {
|
|
562
|
-
this.policy = policy;
|
|
563
|
-
this.allowedUsers = new Set(allowedUsers);
|
|
564
|
-
this.pairingStore = pairingStore || null;
|
|
565
|
-
}
|
|
566
|
-
check(senderId, senderName) {
|
|
567
|
-
switch (this.policy) {
|
|
568
|
-
case "open":
|
|
569
|
-
return { allowed: true };
|
|
570
|
-
case "allowlist":
|
|
571
|
-
return { allowed: this.allowedUsers.has(senderId) };
|
|
572
|
-
case "pairing": {
|
|
573
|
-
if (this.allowedUsers.has(senderId)) {
|
|
574
|
-
return { allowed: true };
|
|
575
|
-
}
|
|
576
|
-
if (this.pairingStore?.isApproved(senderId)) {
|
|
577
|
-
return { allowed: true };
|
|
578
|
-
}
|
|
579
|
-
const code = this.pairingStore?.createRequest(senderId, senderName || senderId);
|
|
580
|
-
return { allowed: false, pairingCode: code ?? null };
|
|
581
|
-
}
|
|
582
|
-
default:
|
|
583
|
-
throw new Error(`Unknown sender policy: ${this.policy}`);
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
};
|
|
587
|
-
|
|
588
|
-
// packages/channels/base/dist/PairingStore.js
|
|
589
|
-
init_esbuild_shims();
|
|
590
|
-
import * as crypto from "node:crypto";
|
|
591
|
-
import * as fs from "node:fs";
|
|
592
|
-
import * as path2 from "node:path";
|
|
593
|
-
var SAFE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
594
|
-
var CODE_LENGTH = 8;
|
|
595
|
-
var EXPIRY_MS = 60 * 60 * 1e3;
|
|
596
|
-
var MAX_PENDING = 3;
|
|
597
|
-
var PairingStore = class {
|
|
598
|
-
static {
|
|
599
|
-
__name(this, "PairingStore");
|
|
600
|
-
}
|
|
601
|
-
dir;
|
|
602
|
-
pendingPath;
|
|
603
|
-
allowlistPath;
|
|
604
|
-
constructor(channelName) {
|
|
605
|
-
this.dir = path2.join(getGlobalQwenDir(), "channels");
|
|
606
|
-
this.pendingPath = path2.join(this.dir, `${channelName}-pairing.json`);
|
|
607
|
-
this.allowlistPath = path2.join(this.dir, `${channelName}-allowlist.json`);
|
|
608
|
-
}
|
|
609
|
-
isApproved(senderId) {
|
|
610
|
-
const list = this.readAllowlist();
|
|
611
|
-
return list.includes(senderId);
|
|
612
|
-
}
|
|
613
|
-
/**
|
|
614
|
-
* Create a pairing request for an unknown sender.
|
|
615
|
-
* Returns the code if created, or null if the pending cap is reached.
|
|
616
|
-
* If the sender already has a non-expired pending request, returns that code.
|
|
617
|
-
*/
|
|
618
|
-
createRequest(senderId, senderName) {
|
|
619
|
-
const pending = this.readPending();
|
|
620
|
-
const now = Date.now();
|
|
621
|
-
const active = pending.filter((r) => now - r.createdAt < EXPIRY_MS);
|
|
622
|
-
const existing = active.find((r) => r.senderId === senderId);
|
|
623
|
-
if (existing) {
|
|
624
|
-
return existing.code;
|
|
625
|
-
}
|
|
626
|
-
if (active.length >= MAX_PENDING) {
|
|
627
|
-
return null;
|
|
628
|
-
}
|
|
629
|
-
const code = generateCode();
|
|
630
|
-
active.push({ senderId, senderName, code, createdAt: now });
|
|
631
|
-
this.writePending(active);
|
|
632
|
-
return code;
|
|
633
|
-
}
|
|
634
|
-
/**
|
|
635
|
-
* Approve a pairing request by code.
|
|
636
|
-
* Returns the sender ID if found, or null if not found / expired.
|
|
637
|
-
*/
|
|
638
|
-
approve(code) {
|
|
639
|
-
const pending = this.readPending();
|
|
640
|
-
const now = Date.now();
|
|
641
|
-
const idx = pending.findIndex((r) => r.code === code.toUpperCase() && now - r.createdAt < EXPIRY_MS);
|
|
642
|
-
if (idx === -1)
|
|
643
|
-
return null;
|
|
644
|
-
const request = pending[idx];
|
|
645
|
-
pending.splice(idx, 1);
|
|
646
|
-
this.writePending(pending);
|
|
647
|
-
const list = this.readAllowlist();
|
|
648
|
-
if (!list.includes(request.senderId)) {
|
|
649
|
-
list.push(request.senderId);
|
|
650
|
-
this.writeAllowlist(list);
|
|
651
|
-
}
|
|
652
|
-
return request;
|
|
653
|
-
}
|
|
654
|
-
listPending() {
|
|
655
|
-
const pending = this.readPending();
|
|
656
|
-
const now = Date.now();
|
|
657
|
-
return pending.filter((r) => now - r.createdAt < EXPIRY_MS);
|
|
658
|
-
}
|
|
659
|
-
getAllowlist() {
|
|
660
|
-
return this.readAllowlist();
|
|
661
|
-
}
|
|
662
|
-
ensureDir() {
|
|
663
|
-
if (!fs.existsSync(this.dir)) {
|
|
664
|
-
fs.mkdirSync(this.dir, { recursive: true });
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
readPending() {
|
|
668
|
-
try {
|
|
669
|
-
const data = fs.readFileSync(this.pendingPath, "utf-8");
|
|
670
|
-
return JSON.parse(data);
|
|
671
|
-
} catch {
|
|
672
|
-
return [];
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
writePending(requests) {
|
|
676
|
-
this.ensureDir();
|
|
677
|
-
fs.writeFileSync(this.pendingPath, JSON.stringify(requests, null, 2));
|
|
678
|
-
}
|
|
679
|
-
readAllowlist() {
|
|
680
|
-
try {
|
|
681
|
-
const data = fs.readFileSync(this.allowlistPath, "utf-8");
|
|
682
|
-
return JSON.parse(data);
|
|
683
|
-
} catch {
|
|
684
|
-
return [];
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
writeAllowlist(list) {
|
|
688
|
-
this.ensureDir();
|
|
689
|
-
fs.writeFileSync(this.allowlistPath, JSON.stringify(list, null, 2));
|
|
690
|
-
}
|
|
691
|
-
};
|
|
692
|
-
function generateCode() {
|
|
693
|
-
let code = "";
|
|
694
|
-
for (let i = 0; i < CODE_LENGTH; i++) {
|
|
695
|
-
code += SAFE_ALPHABET[crypto.randomInt(SAFE_ALPHABET.length)];
|
|
696
|
-
}
|
|
697
|
-
return code;
|
|
698
|
-
}
|
|
699
|
-
__name(generateCode, "generateCode");
|
|
700
|
-
|
|
701
|
-
// packages/channels/base/dist/ChannelBase.js
|
|
702
|
-
var CLEAR_CANCEL_TIMEOUT_MS = 3e3;
|
|
703
|
-
var COMMAND_TOKEN_CHARS = "a-zA-Z0-9_:-";
|
|
704
|
-
var PARSE_COMMAND_RE = new RegExp(`^\\/([${COMMAND_TOKEN_CHARS}]+)(?:@\\S+)?\\s*(.*)`, "s");
|
|
705
|
-
var COMMAND_TOKEN_RE = new RegExp(`^[${COMMAND_TOKEN_CHARS}]+(?:@\\S+)?$`);
|
|
706
|
-
var ChannelBase = class {
|
|
707
|
-
static {
|
|
708
|
-
__name(this, "ChannelBase");
|
|
709
|
-
}
|
|
710
|
-
config;
|
|
711
|
-
bridge;
|
|
712
|
-
groupGate;
|
|
713
|
-
gate;
|
|
714
|
-
router;
|
|
715
|
-
name;
|
|
716
|
-
/** Resolved proxy URL, available to subclasses for adapter-specific clients. */
|
|
717
|
-
proxy;
|
|
718
|
-
instructedSessions = /* @__PURE__ */ new Set();
|
|
719
|
-
commands = /* @__PURE__ */ new Map();
|
|
720
|
-
/** Per-session promise chain to serialize prompt + send (followup mode). */
|
|
721
|
-
sessionQueues = /* @__PURE__ */ new Map();
|
|
722
|
-
registerBridgeEvents;
|
|
723
|
-
/**
|
|
724
|
-
* Per-session generation, bumped by /clear. A queued followup turn captures the
|
|
725
|
-
* generation when it enqueues and bails if /clear bumped it before the turn ran,
|
|
726
|
-
* so a cleared session can't be resurrected by an already-queued prompt.
|
|
727
|
-
*/
|
|
728
|
-
sessionGenerations = /* @__PURE__ */ new Map();
|
|
729
|
-
/** Per-session active prompt tracking for dispatch modes. */
|
|
730
|
-
activePrompts = /* @__PURE__ */ new Map();
|
|
731
|
-
/** Per-session message buffer for collect mode. */
|
|
732
|
-
collectBuffers = /* @__PURE__ */ new Map();
|
|
733
|
-
bridgeToolCallListener = /* @__PURE__ */ __name((event) => {
|
|
734
|
-
const target = this.router.getTarget(event.sessionId);
|
|
735
|
-
if (target) {
|
|
736
|
-
this.onToolCall(target.chatId, event);
|
|
737
|
-
}
|
|
738
|
-
}, "bridgeToolCallListener");
|
|
739
|
-
bridgeSessionDiedListener = /* @__PURE__ */ __name((event) => {
|
|
740
|
-
this.onSessionDied(event.sessionId);
|
|
741
|
-
}, "bridgeSessionDiedListener");
|
|
742
|
-
constructor(name, config, bridge, options) {
|
|
743
|
-
this.name = name;
|
|
744
|
-
this.config = config;
|
|
745
|
-
this.bridge = bridge;
|
|
746
|
-
this.proxy = options?.proxy;
|
|
747
|
-
this.groupGate = new GroupGate(config.groupPolicy, config.groups);
|
|
748
|
-
const pairingStore = config.senderPolicy === "pairing" ? new PairingStore(name) : void 0;
|
|
749
|
-
this.gate = new SenderGate(config.senderPolicy, config.allowedUsers, pairingStore);
|
|
750
|
-
this.router = options?.router || new SessionRouter(bridge, config.cwd, config.sessionScope);
|
|
751
|
-
this.registerSharedCommands();
|
|
752
|
-
this.registerBridgeEvents = options?.registerBridgeEvents ?? !options?.router;
|
|
753
|
-
if (this.registerBridgeEvents) {
|
|
754
|
-
this.attachBridgeEvents(bridge);
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
/** Replace the bridge instance (used after crash recovery restart). */
|
|
758
|
-
setBridge(bridge) {
|
|
759
|
-
if (this.registerBridgeEvents) {
|
|
760
|
-
this.detachBridgeEvents(this.bridge);
|
|
761
|
-
}
|
|
762
|
-
this.router.setBridge(bridge);
|
|
763
|
-
this.bridge = bridge;
|
|
764
|
-
if (this.registerBridgeEvents) {
|
|
765
|
-
this.attachBridgeEvents(bridge);
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
onToolCall(_chatId, _event) {
|
|
769
|
-
}
|
|
770
|
-
onSessionDied(sessionId) {
|
|
771
|
-
this.router.removeSessionId(sessionId);
|
|
772
|
-
this.instructedSessions.delete(sessionId);
|
|
773
|
-
}
|
|
774
|
-
attachBridgeEvents(bridge) {
|
|
775
|
-
bridge.on("toolCall", this.bridgeToolCallListener);
|
|
776
|
-
bridge.on("sessionDied", this.bridgeSessionDiedListener);
|
|
777
|
-
}
|
|
778
|
-
detachBridgeEvents(bridge) {
|
|
779
|
-
bridge.off("toolCall", this.bridgeToolCallListener);
|
|
780
|
-
bridge.off("sessionDied", this.bridgeSessionDiedListener);
|
|
781
|
-
}
|
|
782
|
-
/**
|
|
783
|
-
* Called when a prompt actually begins processing (inside the session queue).
|
|
784
|
-
* Override to show a platform-specific working indicator (e.g., typing, reaction).
|
|
785
|
-
* Not called for buffered messages (collect mode) or gated/blocked messages.
|
|
786
|
-
*/
|
|
787
|
-
onPromptStart(_chatId, _sessionId, _messageId) {
|
|
788
|
-
}
|
|
789
|
-
/**
|
|
790
|
-
* Called when a prompt finishes (response sent or cancelled).
|
|
791
|
-
* Override to hide the working indicator.
|
|
792
|
-
*/
|
|
793
|
-
onPromptEnd(_chatId, _sessionId, _messageId) {
|
|
794
|
-
}
|
|
795
|
-
/**
|
|
796
|
-
* Called for each text chunk as the agent streams its response.
|
|
797
|
-
* Override to implement progressive display (e.g., updating an AI card in-place).
|
|
798
|
-
* Default: no-op (chunks are collected internally and delivered via onResponseComplete).
|
|
799
|
-
*/
|
|
800
|
-
onResponseChunk(_chatId, _chunk, _sessionId) {
|
|
801
|
-
}
|
|
802
|
-
/**
|
|
803
|
-
* Called when the agent's full response is ready.
|
|
804
|
-
* Override to customize delivery (e.g., finalize an AI card).
|
|
805
|
-
* Default: calls sendMessage() with the full response text.
|
|
806
|
-
*/
|
|
807
|
-
async onResponseComplete(chatId, fullText, _sessionId) {
|
|
808
|
-
await this.sendMessage(chatId, fullText);
|
|
809
|
-
}
|
|
810
|
-
/**
|
|
811
|
-
* Register a slash command handler. Subclasses can call this to add
|
|
812
|
-
* platform-specific commands (e.g., /start for Telegram).
|
|
813
|
-
* Overrides shared commands if the same name is registered.
|
|
814
|
-
*/
|
|
815
|
-
registerCommand(name, handler) {
|
|
816
|
-
this.commands.set(name.toLowerCase(), handler);
|
|
817
|
-
}
|
|
818
|
-
registerCancelCommand(name = "cancel") {
|
|
819
|
-
this.registerCommand(name, async (envelope) => {
|
|
820
|
-
if (!this.isAuthorizedForSharedSession(envelope)) {
|
|
821
|
-
await this.sendMessage(envelope.chatId, "Only authorized members can cancel requests in this shared session.");
|
|
822
|
-
return true;
|
|
823
|
-
}
|
|
824
|
-
const activeSessionId = this.findActiveSessionId(envelope);
|
|
825
|
-
if (!activeSessionId) {
|
|
826
|
-
await this.sendMessage(envelope.chatId, "No request is currently running.");
|
|
827
|
-
return true;
|
|
828
|
-
}
|
|
829
|
-
const active = this.activePrompts.get(activeSessionId);
|
|
830
|
-
if (!active) {
|
|
831
|
-
await this.sendMessage(envelope.chatId, "No request is currently running.");
|
|
832
|
-
return true;
|
|
833
|
-
}
|
|
834
|
-
const cancelRequested = active.cancelRequested ?? this.bridge.cancelSession(activeSessionId).then(() => true, (err) => {
|
|
835
|
-
process.stderr.write(`[${this.name}] cancelSession failed for session=${activeSessionId}: ${err instanceof Error ? err.message : err}
|
|
836
|
-
`);
|
|
837
|
-
active.cancelRequested = void 0;
|
|
838
|
-
return false;
|
|
839
|
-
});
|
|
840
|
-
active.cancelRequested = cancelRequested;
|
|
841
|
-
const cancelSucceeded = await cancelRequested;
|
|
842
|
-
if (!cancelSucceeded) {
|
|
843
|
-
await this.sendMessage(envelope.chatId, "Failed to cancel current request.");
|
|
844
|
-
return true;
|
|
845
|
-
}
|
|
846
|
-
active.cancelled = true;
|
|
847
|
-
active.stopStreaming?.();
|
|
848
|
-
this.collectBuffers.delete(activeSessionId);
|
|
849
|
-
await this.sendMessage(envelope.chatId, "Cancelled current request.");
|
|
850
|
-
return true;
|
|
851
|
-
});
|
|
852
|
-
}
|
|
853
|
-
/** Register shared slash commands. Called from constructor. */
|
|
854
|
-
registerSharedCommands() {
|
|
855
|
-
const doClear = /* @__PURE__ */ __name(async (envelope) => {
|
|
856
|
-
const removedIds = this.router.removeSession(this.name, envelope.senderId, envelope.chatId, envelope.threadId);
|
|
857
|
-
if (removedIds.length > 0) {
|
|
858
|
-
for (const id of removedIds) {
|
|
859
|
-
if (this.isSharedSession(envelope)) {
|
|
860
|
-
const who = sanitizeSenderName(envelope.senderName || envelope.senderId || "unknown");
|
|
861
|
-
process.stderr.write(`[${this.name}] shared session ${id} cleared by ${who} (sender ${envelope.senderId})
|
|
862
|
-
`);
|
|
863
|
-
}
|
|
864
|
-
this.sessionGenerations.set(id, (this.sessionGenerations.get(id) ?? 0) + 1);
|
|
865
|
-
const active = this.activePrompts.get(id);
|
|
866
|
-
this.collectBuffers.delete(id);
|
|
867
|
-
if (active) {
|
|
868
|
-
const settled = await this.cancelAndAwaitActive(active, id);
|
|
869
|
-
if (!settled) {
|
|
870
|
-
const wedgedChat = active.chatId ? sanitizeLogText(active.chatId, 64) : "unknown";
|
|
871
|
-
const wedgedMessage = active.messageId ? `, message ${sanitizeLogText(active.messageId, 64)}` : "";
|
|
872
|
-
process.stderr.write(`[${this.name}] /clear abandoned a wedged turn for session ${id} (chat ${wedgedChat}${wedgedMessage}): it did not wind down within ${CLEAR_CANCEL_TIMEOUT_MS}ms
|
|
873
|
-
`);
|
|
874
|
-
active.clearEvicted = true;
|
|
875
|
-
try {
|
|
876
|
-
this.onPromptEnd(active.chatId, id, active.messageId);
|
|
877
|
-
} catch (err) {
|
|
878
|
-
process.stderr.write(`[${this.name}] onPromptEnd threw during /clear eviction for session ${id}: ${err instanceof Error ? err.message : err}
|
|
879
|
-
`);
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
this.instructedSessions.delete(id);
|
|
884
|
-
const drained = this.sessionQueues.get(id);
|
|
885
|
-
const bumpedGeneration = this.sessionGenerations.get(id);
|
|
886
|
-
this.sessionQueues.delete(id);
|
|
887
|
-
this.activePrompts.delete(id);
|
|
888
|
-
if (drained) {
|
|
889
|
-
void drained.then(() => {
|
|
890
|
-
if (!this.sessionQueues.has(id) && this.sessionGenerations.get(id) === bumpedGeneration) {
|
|
891
|
-
this.sessionGenerations.delete(id);
|
|
892
|
-
}
|
|
893
|
-
});
|
|
894
|
-
} else {
|
|
895
|
-
this.sessionGenerations.delete(id);
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
await this.sendMessage(envelope.chatId, "Session cleared. The next message starts a fresh conversation.");
|
|
899
|
-
} else {
|
|
900
|
-
await this.sendMessage(envelope.chatId, "No active session to clear.");
|
|
901
|
-
}
|
|
902
|
-
}, "doClear");
|
|
903
|
-
const clearHandler = /* @__PURE__ */ __name(async (envelope, args) => {
|
|
904
|
-
if (!this.isAuthorizedForSharedSession(envelope)) {
|
|
905
|
-
await this.sendMessage(envelope.chatId, "Only authorized members can clear this shared session.");
|
|
906
|
-
return true;
|
|
907
|
-
}
|
|
908
|
-
if (this.isSharedSession(envelope) && args.toLowerCase() !== "confirm") {
|
|
909
|
-
await this.sendMessage(envelope.chatId, 'This clears the shared session for everyone who shares it. Re-send with "confirm" (e.g. /clear confirm) to proceed.');
|
|
910
|
-
return true;
|
|
911
|
-
}
|
|
912
|
-
await doClear(envelope);
|
|
913
|
-
return true;
|
|
914
|
-
}, "clearHandler");
|
|
915
|
-
this.registerCommand("clear", clearHandler);
|
|
916
|
-
this.registerCommand("reset", clearHandler);
|
|
917
|
-
this.registerCommand("new", clearHandler);
|
|
918
|
-
this.registerCommand("who", async (envelope) => {
|
|
919
|
-
if (!this.isAuthorizedForSharedSession(envelope)) {
|
|
920
|
-
await this.sendMessage(envelope.chatId, "Only authorized members can view this shared session.");
|
|
921
|
-
return true;
|
|
922
|
-
}
|
|
923
|
-
const active = this.router.hasSession(this.name, envelope.senderId, envelope.chatId, envelope.threadId);
|
|
924
|
-
const scopeNote = this.config.sessionScope === "single" ? " (shared channel-wide)" : this.isSharedSession(envelope) ? envelope.isGroup ? " (shared by this group)" : "" : envelope.isGroup ? " (private to you)" : "";
|
|
925
|
-
await this.sendMessage(envelope.chatId, [
|
|
926
|
-
`Channel: ${this.name}`,
|
|
927
|
-
// Only the basename — don't leak the absolute cwd to group members.
|
|
928
|
-
`Workspace: ${basename(this.config.cwd)}`,
|
|
929
|
-
`Session: ${active ? "active" : "none"}${scopeNote}`
|
|
930
|
-
].join("\n"));
|
|
931
|
-
return true;
|
|
932
|
-
});
|
|
933
|
-
this.registerCommand("help", async (envelope) => {
|
|
934
|
-
const lines = [
|
|
935
|
-
"Commands:",
|
|
936
|
-
"/help \u2014 Show this help",
|
|
937
|
-
this.isSharedSession(envelope) ? "/clear confirm \u2014 Clear the shared session (aliases: /reset, /new)" : "/clear \u2014 Clear your session (aliases: /reset, /new)",
|
|
938
|
-
"/who \u2014 Show current session & workspace",
|
|
939
|
-
"/status \u2014 Show session info"
|
|
940
|
-
];
|
|
941
|
-
const sharedCmds = /* @__PURE__ */ new Set([
|
|
942
|
-
"help",
|
|
943
|
-
"clear",
|
|
944
|
-
"reset",
|
|
945
|
-
"new",
|
|
946
|
-
"who",
|
|
947
|
-
"status"
|
|
948
|
-
]);
|
|
949
|
-
const platformCmds = [...this.commands.keys()].filter((c) => !sharedCmds.has(c));
|
|
950
|
-
if (platformCmds.length > 0) {
|
|
951
|
-
for (const cmd of platformCmds) {
|
|
952
|
-
lines.push(`/${cmd}`);
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
const sessionId = this.router.getSession(this.name, envelope.senderId, envelope.chatId, envelope.threadId);
|
|
956
|
-
const agentCommands = sessionId ? this.getAgentCommandsForSession(sessionId) : this.bridge.availableCommands;
|
|
957
|
-
if (agentCommands.length > 0) {
|
|
958
|
-
lines.push("", "Agent commands (forwarded to Qwen Code):");
|
|
959
|
-
for (const cmd of agentCommands) {
|
|
960
|
-
lines.push(`/${cmd.name} \u2014 ${cmd.description}`);
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
lines.push("", "Send any text to chat with the agent.");
|
|
964
|
-
await this.sendMessage(envelope.chatId, lines.join("\n"));
|
|
965
|
-
return true;
|
|
966
|
-
});
|
|
967
|
-
this.registerCommand("status", async (envelope) => {
|
|
968
|
-
if (!this.isAuthorizedForSharedSession(envelope)) {
|
|
969
|
-
await this.sendMessage(envelope.chatId, "Only authorized members can view this shared session.");
|
|
970
|
-
return true;
|
|
971
|
-
}
|
|
972
|
-
const hasSession = this.router.hasSession(this.name, envelope.senderId, envelope.chatId, envelope.threadId);
|
|
973
|
-
const policy = this.config.senderPolicy;
|
|
974
|
-
const lines = [
|
|
975
|
-
`Session: ${hasSession ? "active" : "none"}`,
|
|
976
|
-
`Access: ${policy}`,
|
|
977
|
-
`Channel: ${this.name}`
|
|
978
|
-
];
|
|
979
|
-
await this.sendMessage(envelope.chatId, lines.join("\n"));
|
|
980
|
-
return true;
|
|
981
|
-
});
|
|
982
|
-
}
|
|
983
|
-
/** Check if a message text matches a registered local command. */
|
|
984
|
-
isLocalCommand(text) {
|
|
985
|
-
const parsed = this.parseCommand(text);
|
|
986
|
-
return parsed !== null && this.commands.has(parsed.command);
|
|
987
|
-
}
|
|
988
|
-
findActiveSessionId(envelope) {
|
|
989
|
-
const sessionId = this.router.getSession(this.name, envelope.senderId, envelope.chatId, envelope.threadId);
|
|
990
|
-
return sessionId && this.activePrompts.has(sessionId) ? sessionId : void 0;
|
|
991
|
-
}
|
|
992
|
-
/**
|
|
993
|
-
* Whether the resolved session is SHARED across senders. `single` collapses
|
|
994
|
-
* the whole channel to one `__single__` session for EVERY sender — group OR
|
|
995
|
-
* DM — so it is ALWAYS shared (even a DM maps to `__single__`). `thread` is
|
|
996
|
-
* shared only in a group (a DM maps to the lone caller's own chat). `user` is
|
|
997
|
-
* per-sender, never shared. Drives both the destructive-/clear confirm gate
|
|
998
|
-
* and the host-shell (`!`) gate.
|
|
999
|
-
*/
|
|
1000
|
-
isSharedSession(envelope) {
|
|
1001
|
-
return this.config.sessionScope === "single" || envelope.isGroup && this.config.sessionScope === "thread";
|
|
1002
|
-
}
|
|
1003
|
-
/**
|
|
1004
|
-
* Whether `envelope.senderId` may act on the resolved session's destructive or
|
|
1005
|
-
* workspace-leaking commands (/clear, /who). A SHARED session with a non-empty
|
|
1006
|
-
* allowedUsers list is restricted to those members; a per-user session, or one
|
|
1007
|
-
* with no allowlist, is unrestricted. Shared verbatim by /clear and /who so the
|
|
1008
|
-
* gate can't drift; each caller sends its own rejection wording.
|
|
1009
|
-
*/
|
|
1010
|
-
isAuthorizedForSharedSession(envelope) {
|
|
1011
|
-
if (!this.isSharedSession(envelope))
|
|
1012
|
-
return true;
|
|
1013
|
-
const authorized = this.config.allowedUsers;
|
|
1014
|
-
return authorized.length === 0 || authorized.includes(envelope.senderId);
|
|
1015
|
-
}
|
|
1016
|
-
stopActiveStreaming(active, sessionId, reason) {
|
|
1017
|
-
try {
|
|
1018
|
-
active.stopStreaming?.();
|
|
1019
|
-
} catch (err) {
|
|
1020
|
-
process.stderr.write(`[${this.name}] stopStreaming threw during ${reason} for session ${sessionId}: ${err instanceof Error ? err.message : err}
|
|
1021
|
-
`);
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
/**
|
|
1025
|
-
* Cancel the active turn and wait (bounded) for it to wind down. Stops the
|
|
1026
|
-
* BlockStreamer so buffered text can't leak via the idle timer, then fires a
|
|
1027
|
-
* best-effort cancelSession (NOT awaited — a wedged child/daemon can leave the
|
|
1028
|
-
* request pending forever). Returns true if active.done settled first, false
|
|
1029
|
-
* if the CLEAR_CANCEL_TIMEOUT_MS bound won (the turn never wound down). Used by
|
|
1030
|
-
* /clear, which genuinely EVICTS the session and so must proceed even when the
|
|
1031
|
-
* turn is wedged. Steer no longer uses this: it best-effort cancels then chains
|
|
1032
|
-
* the new turn behind the old one (see handleInbound), so it never needs to
|
|
1033
|
-
* proceed past a still-active turn.
|
|
1034
|
-
*/
|
|
1035
|
-
async cancelAndAwaitActive(active, sessionId) {
|
|
1036
|
-
active.cancelled = true;
|
|
1037
|
-
this.stopActiveStreaming(active, sessionId, "cancel");
|
|
1038
|
-
void this.bridge.cancelSession(sessionId).catch((err) => {
|
|
1039
|
-
process.stderr.write(`[${this.name}] cancelSession failed for session=${sessionId} (clear/await): ${err instanceof Error ? err.message : err}
|
|
1040
|
-
`);
|
|
1041
|
-
});
|
|
1042
|
-
let timer;
|
|
1043
|
-
const settled = await Promise.race([
|
|
1044
|
-
active.done.then(() => true),
|
|
1045
|
-
new Promise((resolve2) => {
|
|
1046
|
-
timer = setTimeout(() => resolve2(false), CLEAR_CANCEL_TIMEOUT_MS);
|
|
1047
|
-
})
|
|
1048
|
-
]);
|
|
1049
|
-
clearTimeout(timer);
|
|
1050
|
-
return settled;
|
|
1051
|
-
}
|
|
1052
|
-
/**
|
|
1053
|
-
* Parse a slash command from message text.
|
|
1054
|
-
* Returns { command, raw, args } or null if not a slash command. `command` is
|
|
1055
|
-
* lowercased for case-insensitive LOCAL dispatch (registerCommand lowercases the
|
|
1056
|
-
* names it stores); `raw` keeps the typed case so agent-command matching can be
|
|
1057
|
-
* CASE-SENSITIVE, mirroring the CLI's parseSlashCommand (`cmd.name === part`).
|
|
1058
|
-
*/
|
|
1059
|
-
parseCommand(text) {
|
|
1060
|
-
const trimmed = text.trim();
|
|
1061
|
-
if (!trimmed.startsWith("/"))
|
|
1062
|
-
return null;
|
|
1063
|
-
const match = trimmed.match(PARSE_COMMAND_RE);
|
|
1064
|
-
if (!match)
|
|
1065
|
-
return null;
|
|
1066
|
-
return {
|
|
1067
|
-
command: match[1].toLowerCase(),
|
|
1068
|
-
raw: match[1],
|
|
1069
|
-
args: match[2].trim()
|
|
1070
|
-
};
|
|
1071
|
-
}
|
|
1072
|
-
/**
|
|
1073
|
-
* Whether `text` is a real slash command rather than prose that merely starts
|
|
1074
|
-
* with `/`. A command's first whitespace-delimited token must match
|
|
1075
|
-
* parseCommand()'s charset — `[a-zA-Z0-9_:-]+`, plus an optional `@botname`
|
|
1076
|
-
* suffix — and not be a `//` line comment or `/*` block comment. Slash-prefixed
|
|
1077
|
-
* paths (`/tmp/foo`), comments, and a bare `/` are prose and keep their
|
|
1078
|
-
* `[sender]` tag.
|
|
1079
|
-
*
|
|
1080
|
-
* Intentionally stricter than the CLI's looser classifier (cli
|
|
1081
|
-
* `ui/utils/commandUtils.ts`), which forwards any non-comment, non-path
|
|
1082
|
-
* `/<token>` (e.g. `/café`, a zero-width-laden token). Such inputs aren't
|
|
1083
|
-
* runnable commands, and in a SHARED group session forwarding them unattributed
|
|
1084
|
-
* is worse than a redundant tag — so anything off the command charset is
|
|
1085
|
-
* treated as prose and keeps its `[sender]` tag. Purely lexical — never
|
|
1086
|
-
* consults the async command list, so it can't race a fresh session.
|
|
1087
|
-
*/
|
|
1088
|
-
isSlashCommand(text) {
|
|
1089
|
-
const trimmed = text.trim();
|
|
1090
|
-
if (!trimmed.startsWith("/") || trimmed.startsWith("//") || trimmed.startsWith("/*")) {
|
|
1091
|
-
return false;
|
|
1092
|
-
}
|
|
1093
|
-
const firstToken = trimmed.slice(1).split(/\s+/u)[0] ?? "";
|
|
1094
|
-
return COMMAND_TOKEN_RE.test(firstToken);
|
|
1095
|
-
}
|
|
1096
|
-
/**
|
|
1097
|
-
* Whether `text` names a command this channel can actually run: a locally
|
|
1098
|
-
* registered command (`this.commands`, e.g. /clear, /who) OR an agent command
|
|
1099
|
-
* THIS session exposes — by canonical name OR alias (e.g. `/summarize` for
|
|
1100
|
-
* `/compress`). Paired with isSlashCommand so the `[sender]` attribution tag is
|
|
1101
|
-
* suppressed ONLY for RECOGNIZED commands; command-SHAPED-but-unrecognized text
|
|
1102
|
-
* (e.g. `/x\n[SYSTEM]: …`) keeps its tag rather than reaching a shared group
|
|
1103
|
-
* unattributed, where an injected second line is more likely read as a system
|
|
1104
|
-
* directive. Purely synchronous, like isSlashCommand: it reads the session's
|
|
1105
|
-
* availableCommands snapshot WITHOUT awaiting, so it never races a fresh session
|
|
1106
|
-
* (a genuine agent command sent before the snapshot loads is treated as
|
|
1107
|
-
* unrecognized and KEEPS its tag — the safe default).
|
|
1108
|
-
*/
|
|
1109
|
-
isRecognizedCommand(text, sessionId) {
|
|
1110
|
-
const parsed = this.parseCommand(text);
|
|
1111
|
-
if (!parsed)
|
|
1112
|
-
return false;
|
|
1113
|
-
if (this.commands.has(parsed.command))
|
|
1114
|
-
return true;
|
|
1115
|
-
const token = text.trim().slice(1).split(/\s+/u)[0] ?? "";
|
|
1116
|
-
return this.getAgentCommandsForSession(sessionId).some((cmd) => cmd.name === token || Array.isArray(cmd.altNames) && cmd.altNames.includes(token));
|
|
1117
|
-
}
|
|
1118
|
-
/**
|
|
1119
|
-
* The agent-command snapshot for THIS session. DaemonChannelBridge keys
|
|
1120
|
-
* commands per session, so its global `availableCommands` getter can return
|
|
1121
|
-
* ANOTHER session's list — prefer its getAvailableCommands(sessionId) when
|
|
1122
|
-
* present. AcpBridge runs a single agent and exposes only the global getter
|
|
1123
|
-
* (inherently session-correct), so fall back to it. Synchronous, matching
|
|
1124
|
-
* isRecognizedCommand's no-await contract.
|
|
1125
|
-
*/
|
|
1126
|
-
getAgentCommandsForSession(sessionId) {
|
|
1127
|
-
const bridge = this.bridge;
|
|
1128
|
-
if (typeof bridge.getAvailableCommands === "function") {
|
|
1129
|
-
return bridge.getAvailableCommands(sessionId) ?? [];
|
|
1130
|
-
}
|
|
1131
|
-
return bridge.availableCommands ?? [];
|
|
1132
|
-
}
|
|
1133
|
-
async handleInbound(envelope) {
|
|
1134
|
-
const groupResult = this.groupGate.check(envelope);
|
|
1135
|
-
if (!groupResult.allowed) {
|
|
1136
|
-
return;
|
|
1137
|
-
}
|
|
1138
|
-
const result = this.gate.check(envelope.senderId, envelope.senderName);
|
|
1139
|
-
if (!result.allowed) {
|
|
1140
|
-
if (result.pairingCode !== void 0) {
|
|
1141
|
-
await this.onPairingRequired(envelope.chatId, result.pairingCode);
|
|
1142
|
-
}
|
|
1143
|
-
return;
|
|
1144
|
-
}
|
|
1145
|
-
const parsed = this.parseCommand(envelope.text);
|
|
1146
|
-
if (parsed) {
|
|
1147
|
-
const handler = this.commands.get(parsed.command);
|
|
1148
|
-
if (handler) {
|
|
1149
|
-
const handled = await handler(envelope, parsed.args);
|
|
1150
|
-
if (handled)
|
|
1151
|
-
return;
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
const bangText = envelope.text.trimStart();
|
|
1155
|
-
if (bangText.startsWith("!")) {
|
|
1156
|
-
if (envelope.isGroup || this.isSharedSession(envelope)) {
|
|
1157
|
-
const who = sanitizeSenderName(envelope.senderName || envelope.senderId || "unknown");
|
|
1158
|
-
process.stderr.write(`[${this.name}] blocked ! shell command from ${who} (sender ${envelope.senderId}) in chat ${sanitizeLogText(envelope.chatId, 64)}
|
|
1159
|
-
`);
|
|
1160
|
-
}
|
|
1161
|
-
if (envelope.isGroup) {
|
|
1162
|
-
await this.sendMessage(envelope.chatId, "Shell commands (`!`) are disabled in group chats.");
|
|
1163
|
-
return;
|
|
1164
|
-
}
|
|
1165
|
-
if (this.isSharedSession(envelope)) {
|
|
1166
|
-
await this.sendMessage(envelope.chatId, "Shell commands (`!`) are disabled in shared sessions.");
|
|
1167
|
-
return;
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
const sessionId = await this.router.resolve(this.name, envelope.senderId, envelope.chatId, envelope.threadId, this.config.cwd);
|
|
1171
|
-
if (bangText.startsWith("!")) {
|
|
1172
|
-
const cmd = bangText.slice(1).trim();
|
|
1173
|
-
const bridgeShellCommand = this.bridge.shellCommand;
|
|
1174
|
-
if (cmd && bridgeShellCommand) {
|
|
1175
|
-
try {
|
|
1176
|
-
const result2 = await this.bridge.shellCommand(sessionId, cmd);
|
|
1177
|
-
const longestRun = Math.max(0, ...Array.from((result2.output || "").matchAll(/`+/g), (m) => m[0].length));
|
|
1178
|
-
const fence = "`".repeat(Math.max(3, longestRun + 1));
|
|
1179
|
-
const output = result2.output ? `${fence}
|
|
1180
|
-
${result2.output}
|
|
1181
|
-
${fence}` : "(no output)";
|
|
1182
|
-
const exitLine = result2.exitCode !== null && result2.exitCode !== 0 ? `
|
|
1183
|
-
Exit code: ${result2.exitCode}` : "";
|
|
1184
|
-
await this.sendMessage(envelope.chatId, `$ ${cmd}
|
|
1185
|
-
${output}${exitLine}`);
|
|
1186
|
-
} catch (error) {
|
|
1187
|
-
await this.sendMessage(envelope.chatId, `Shell command failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1188
|
-
}
|
|
1189
|
-
return;
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
let promptText = envelope.text;
|
|
1193
|
-
if ((envelope.isGroup || this.config.sessionScope === "single") && !envelope.alreadyPrefixed && !(this.isSlashCommand(envelope.text) && this.isRecognizedCommand(envelope.text, sessionId))) {
|
|
1194
|
-
const who = sanitizeSenderName(envelope.senderName || envelope.senderId || "unknown");
|
|
1195
|
-
promptText = `[${who}] ${sanitizePromptText(promptText)}`;
|
|
1196
|
-
}
|
|
1197
|
-
if (envelope.referencedText) {
|
|
1198
|
-
const quoted = sanitizeQuotedText(envelope.referencedText, 500);
|
|
1199
|
-
promptText = `[Replying to: "${quoted}"]
|
|
1200
|
-
|
|
1201
|
-
${promptText}`;
|
|
1202
|
-
}
|
|
1203
|
-
let imageBase64 = envelope.imageBase64;
|
|
1204
|
-
let imageMimeType = envelope.imageMimeType;
|
|
1205
|
-
if (envelope.attachments?.length) {
|
|
1206
|
-
const filePaths = [];
|
|
1207
|
-
for (const att of envelope.attachments) {
|
|
1208
|
-
if (att.type === "image" && att.data && !imageBase64) {
|
|
1209
|
-
imageBase64 = att.data;
|
|
1210
|
-
imageMimeType = att.mimeType;
|
|
1211
|
-
} else if (att.filePath) {
|
|
1212
|
-
const label = att.type === "file" ? "file" : att.type;
|
|
1213
|
-
const name = att.fileName ? ` "${sanitizeQuotedText(att.fileName, 128)}"` : "";
|
|
1214
|
-
const renderedPath = sanitizePromptPath(att.filePath);
|
|
1215
|
-
filePaths.push(`User sent a ${label}${name}. It has been saved to: ${renderedPath}`);
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
if (filePaths.length > 0) {
|
|
1219
|
-
promptText = promptText + "\n\n" + filePaths.join("\n");
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
if (this.config.instructions && !this.instructedSessions.has(sessionId)) {
|
|
1223
|
-
promptText = `${this.config.instructions}
|
|
1224
|
-
|
|
1225
|
-
${promptText}`;
|
|
1226
|
-
this.instructedSessions.add(sessionId);
|
|
1227
|
-
}
|
|
1228
|
-
const groupCfg = envelope.isGroup ? this.config.groups[envelope.chatId] || this.config.groups["*"] : void 0;
|
|
1229
|
-
const mode = groupCfg?.dispatchMode || this.config.dispatchMode || "steer";
|
|
1230
|
-
const active = this.activePrompts.get(sessionId);
|
|
1231
|
-
let steerWatchdog;
|
|
1232
|
-
if (active) {
|
|
1233
|
-
switch (mode) {
|
|
1234
|
-
case "collect": {
|
|
1235
|
-
let buffer = this.collectBuffers.get(sessionId);
|
|
1236
|
-
if (!buffer) {
|
|
1237
|
-
buffer = [];
|
|
1238
|
-
this.collectBuffers.set(sessionId, buffer);
|
|
1239
|
-
}
|
|
1240
|
-
buffer.push({ text: promptText, envelope });
|
|
1241
|
-
return;
|
|
1242
|
-
}
|
|
1243
|
-
case "steer": {
|
|
1244
|
-
if (!this.isAuthorizedForSharedSession(envelope)) {
|
|
1245
|
-
process.stderr.write(`[${this.name}] steer denied for ${envelope.senderId} in shared session (chat=${sanitizeLogText(envelope.chatId, 64)}); queuing instead
|
|
1246
|
-
`);
|
|
1247
|
-
break;
|
|
1248
|
-
}
|
|
1249
|
-
active.cancelled = true;
|
|
1250
|
-
process.stderr.write(`[${this.name}] steer: cancelled active turn for ${envelope.senderId} in session ${sessionId}
|
|
1251
|
-
`);
|
|
1252
|
-
this.stopActiveStreaming(active, sessionId, "steer");
|
|
1253
|
-
void this.bridge.cancelSession(sessionId).catch((err) => {
|
|
1254
|
-
process.stderr.write(`[${this.name}] cancelSession failed for session=${sessionId} (steer): ${err instanceof Error ? err.message : err}
|
|
1255
|
-
`);
|
|
1256
|
-
});
|
|
1257
|
-
steerWatchdog = setTimeout(() => {
|
|
1258
|
-
if (this.activePrompts.get(sessionId) === active) {
|
|
1259
|
-
process.stderr.write(`[${this.name}] steer queued behind active turn for session ${sessionId}: still waiting after ${CLEAR_CANCEL_TIMEOUT_MS}ms (use /clear to recover)
|
|
1260
|
-
`);
|
|
1261
|
-
}
|
|
1262
|
-
}, CLEAR_CANCEL_TIMEOUT_MS);
|
|
1263
|
-
steerWatchdog.unref?.();
|
|
1264
|
-
promptText = `[The user sent a new message while you were working. Their previous request has been cancelled.]
|
|
1265
|
-
|
|
1266
|
-
${promptText}`;
|
|
1267
|
-
break;
|
|
1268
|
-
}
|
|
1269
|
-
case "followup": {
|
|
1270
|
-
break;
|
|
1271
|
-
}
|
|
1272
|
-
default: {
|
|
1273
|
-
const _exhaustive = mode;
|
|
1274
|
-
throw new Error(`Unknown dispatch mode: ${_exhaustive}`);
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
const prev = this.sessionQueues.get(sessionId) ?? Promise.resolve();
|
|
1279
|
-
const generation = this.sessionGenerations.get(sessionId) ?? 0;
|
|
1280
|
-
const useBlockStreaming = this.config.blockStreaming === "on";
|
|
1281
|
-
const current = prev.then(async () => {
|
|
1282
|
-
clearTimeout(steerWatchdog);
|
|
1283
|
-
if ((this.sessionGenerations.get(sessionId) ?? 0) !== generation) {
|
|
1284
|
-
const loggedText = sanitizeLogText(envelope.text, 80);
|
|
1285
|
-
process.stderr.write(`[${this.name}] dropped queued turn from ${envelope.senderId} for session ${sessionId}: session was cleared before it ran (text: ${loggedText})
|
|
1286
|
-
`);
|
|
1287
|
-
return;
|
|
1288
|
-
}
|
|
1289
|
-
let doneResolve = /* @__PURE__ */ __name(() => {
|
|
1290
|
-
}, "doneResolve");
|
|
1291
|
-
const done = new Promise((r) => {
|
|
1292
|
-
doneResolve = r;
|
|
1293
|
-
});
|
|
1294
|
-
const promptState = {
|
|
1295
|
-
cancelled: false,
|
|
1296
|
-
done,
|
|
1297
|
-
resolve: doneResolve,
|
|
1298
|
-
chatId: envelope.chatId,
|
|
1299
|
-
messageId: envelope.messageId
|
|
1300
|
-
};
|
|
1301
|
-
this.activePrompts.set(sessionId, promptState);
|
|
1302
|
-
this.onPromptStart(envelope.chatId, sessionId, envelope.messageId);
|
|
1303
|
-
const streamer = useBlockStreaming ? new BlockStreamer({
|
|
1304
|
-
minChars: this.config.blockStreamingChunk?.minChars ?? 400,
|
|
1305
|
-
maxChars: this.config.blockStreamingChunk?.maxChars ?? 1e3,
|
|
1306
|
-
idleMs: this.config.blockStreamingCoalesce?.idleMs ?? 1500,
|
|
1307
|
-
send: /* @__PURE__ */ __name((text) => this.sendMessage(envelope.chatId, text), "send")
|
|
1308
|
-
}) : null;
|
|
1309
|
-
promptState.stopStreaming = () => streamer?.stop();
|
|
1310
|
-
const onChunk = /* @__PURE__ */ __name((sid, chunk) => {
|
|
1311
|
-
if (sid === sessionId && !promptState.cancelled) {
|
|
1312
|
-
this.onResponseChunk(envelope.chatId, chunk, sessionId);
|
|
1313
|
-
streamer?.push(chunk);
|
|
1314
|
-
}
|
|
1315
|
-
}, "onChunk");
|
|
1316
|
-
const promptBridge = this.bridge;
|
|
1317
|
-
promptBridge.on("textChunk", onChunk);
|
|
1318
|
-
try {
|
|
1319
|
-
const response = await promptBridge.prompt(sessionId, promptText, {
|
|
1320
|
-
imageBase64,
|
|
1321
|
-
imageMimeType
|
|
1322
|
-
});
|
|
1323
|
-
if (promptState.cancelRequested && !promptState.cancelled) {
|
|
1324
|
-
promptState.cancelled = await promptState.cancelRequested;
|
|
1325
|
-
}
|
|
1326
|
-
if (!promptState.cancelled && response) {
|
|
1327
|
-
if (streamer) {
|
|
1328
|
-
await streamer.flush();
|
|
1329
|
-
} else {
|
|
1330
|
-
await this.onResponseComplete(envelope.chatId, response, sessionId);
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
} finally {
|
|
1334
|
-
promptBridge.off("textChunk", onChunk);
|
|
1335
|
-
streamer?.stop();
|
|
1336
|
-
const stillCurrent = this.activePrompts.get(sessionId) === promptState;
|
|
1337
|
-
if (!promptState.clearEvicted) {
|
|
1338
|
-
try {
|
|
1339
|
-
this.onPromptEnd(envelope.chatId, sessionId, envelope.messageId);
|
|
1340
|
-
} catch (err) {
|
|
1341
|
-
process.stderr.write(`[${this.name}] onPromptEnd threw in finally for session ${sessionId}: ${err instanceof Error ? err.message : err}
|
|
1342
|
-
`);
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
if (stillCurrent) {
|
|
1346
|
-
this.activePrompts.delete(sessionId);
|
|
1347
|
-
}
|
|
1348
|
-
promptState.resolve();
|
|
1349
|
-
const buffer = this.collectBuffers.get(sessionId);
|
|
1350
|
-
if (stillCurrent && buffer && buffer.length > 0) {
|
|
1351
|
-
this.collectBuffers.delete(sessionId);
|
|
1352
|
-
const lost = buffer.length;
|
|
1353
|
-
const coalesced = buffer.map((b) => b.text).join("\n\n");
|
|
1354
|
-
const lastEnvelope = buffer[buffer.length - 1].envelope;
|
|
1355
|
-
const syntheticEnvelope = {
|
|
1356
|
-
...lastEnvelope,
|
|
1357
|
-
text: coalesced,
|
|
1358
|
-
// Coalesced text already carries each message's [sender] prefix.
|
|
1359
|
-
alreadyPrefixed: true,
|
|
1360
|
-
// Clear attachments/references — already resolved in original text
|
|
1361
|
-
referencedText: void 0,
|
|
1362
|
-
attachments: void 0,
|
|
1363
|
-
imageBase64: void 0,
|
|
1364
|
-
imageMimeType: void 0
|
|
1365
|
-
};
|
|
1366
|
-
this.handleInbound(syntheticEnvelope).catch((err) => {
|
|
1367
|
-
process.stderr.write(`[${this.name}] dropped ${lost} buffered message(s) on collect re-entry for session ${sessionId} (last sender ${lastEnvelope.senderId}): ${err instanceof Error ? err.message : String(err)}
|
|
1368
|
-
`);
|
|
1369
|
-
});
|
|
1370
|
-
}
|
|
1371
|
-
}
|
|
1372
|
-
});
|
|
1373
|
-
this.sessionQueues.set(sessionId, current.catch(() => {
|
|
1374
|
-
}));
|
|
1375
|
-
await current;
|
|
1376
|
-
}
|
|
1377
|
-
async onPairingRequired(chatId, code) {
|
|
1378
|
-
if (code) {
|
|
1379
|
-
await this.sendMessage(chatId, `Your pairing code is: ${code}
|
|
1380
|
-
|
|
1381
|
-
Ask the bot operator to approve you with:
|
|
1382
|
-
qwen channel pairing approve ${this.name} ${code}`);
|
|
1383
|
-
} else {
|
|
1384
|
-
await this.sendMessage(chatId, "Too many pending pairing requests. Please try again later.");
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
};
|
|
1388
|
-
|
|
1389
|
-
// packages/channels/base/dist/index.js
|
|
1390
|
-
init_esbuild_shims();
|
|
1391
|
-
|
|
1392
|
-
// packages/channels/base/dist/AcpBridge.js
|
|
1393
|
-
init_esbuild_shims();
|
|
1394
|
-
import { spawn } from "node:child_process";
|
|
1395
|
-
import { Readable, Writable } from "node:stream";
|
|
1396
|
-
import { EventEmitter } from "node:events";
|
|
1397
|
-
function readAvailableCommandAltNames(raw) {
|
|
1398
|
-
if (typeof raw !== "object" || raw === null)
|
|
1399
|
-
return void 0;
|
|
1400
|
-
const record = raw;
|
|
1401
|
-
const meta = record["_meta"];
|
|
1402
|
-
const fromMeta = typeof meta === "object" && meta !== null ? meta["altNames"] : void 0;
|
|
1403
|
-
const source = Array.isArray(record["altNames"]) ? record["altNames"] : Array.isArray(fromMeta) ? fromMeta : void 0;
|
|
1404
|
-
if (!source)
|
|
1405
|
-
return void 0;
|
|
1406
|
-
const names = source.filter((n) => typeof n === "string");
|
|
1407
|
-
return names.length > 0 ? names : void 0;
|
|
1408
|
-
}
|
|
1409
|
-
__name(readAvailableCommandAltNames, "readAvailableCommandAltNames");
|
|
1410
|
-
var AcpBridge = class extends EventEmitter {
|
|
1411
|
-
static {
|
|
1412
|
-
__name(this, "AcpBridge");
|
|
1413
|
-
}
|
|
1414
|
-
child = null;
|
|
1415
|
-
connection = null;
|
|
1416
|
-
options;
|
|
1417
|
-
_availableCommands = [];
|
|
1418
|
-
constructor(options) {
|
|
1419
|
-
super();
|
|
1420
|
-
this.options = options;
|
|
1421
|
-
}
|
|
1422
|
-
get availableCommands() {
|
|
1423
|
-
return this._availableCommands;
|
|
1424
|
-
}
|
|
1425
|
-
async start() {
|
|
1426
|
-
const { cliEntryPath, cwd } = this.options;
|
|
1427
|
-
const args = [
|
|
1428
|
-
...process.execArgv.filter((a) => !/^--inspect(-brk)?($|=)/.test(a)),
|
|
1429
|
-
cliEntryPath,
|
|
1430
|
-
"--acp"
|
|
1431
|
-
];
|
|
1432
|
-
if (this.options.model) {
|
|
1433
|
-
args.push("--model", this.options.model);
|
|
1434
|
-
}
|
|
1435
|
-
this.child = spawn(process.execPath, args, {
|
|
1436
|
-
cwd,
|
|
1437
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1438
|
-
env: { ...process.env },
|
|
1439
|
-
shell: false
|
|
1440
|
-
});
|
|
1441
|
-
this.child.stderr?.on("data", (data) => {
|
|
1442
|
-
const msg = data.toString().trim();
|
|
1443
|
-
if (msg) {
|
|
1444
|
-
process.stderr.write(`[AcpBridge] ${msg}
|
|
1445
|
-
`);
|
|
1446
|
-
}
|
|
1447
|
-
});
|
|
1448
|
-
this.child.on("exit", (code, signal) => {
|
|
1449
|
-
process.stderr.write(`[AcpBridge] Process exited (code=${code}, signal=${signal})
|
|
1450
|
-
`);
|
|
1451
|
-
this.connection = null;
|
|
1452
|
-
this.child = null;
|
|
1453
|
-
this.emit("disconnected", code, signal);
|
|
1454
|
-
});
|
|
1455
|
-
await new Promise((resolve2) => setTimeout(resolve2, 1e3));
|
|
1456
|
-
if (!this.child || this.child.killed) {
|
|
1457
|
-
throw new Error("ACP process failed to start");
|
|
1458
|
-
}
|
|
1459
|
-
const stdout = Readable.toWeb(this.child.stdout);
|
|
1460
|
-
const stdin = Writable.toWeb(this.child.stdin);
|
|
1461
|
-
const stream = ndJsonStream(stdin, stdout);
|
|
1462
|
-
this.connection = new ClientSideConnection(() => ({
|
|
1463
|
-
sessionUpdate: /* @__PURE__ */ __name((params) => {
|
|
1464
|
-
this.handleSessionUpdate(params);
|
|
1465
|
-
return Promise.resolve();
|
|
1466
|
-
}, "sessionUpdate"),
|
|
1467
|
-
requestPermission: /* @__PURE__ */ __name(async (params) => {
|
|
1468
|
-
const options = Array.isArray(params.options) ? params.options : [];
|
|
1469
|
-
const optionId = options.find((o) => o.optionId === "proceed_once")?.optionId || options[0]?.optionId || "proceed_once";
|
|
1470
|
-
return { outcome: { outcome: "selected", optionId } };
|
|
1471
|
-
}, "requestPermission"),
|
|
1472
|
-
extNotification: /* @__PURE__ */ __name(async () => {
|
|
1473
|
-
}, "extNotification")
|
|
1474
|
-
}), stream);
|
|
1475
|
-
await this.connection.initialize({
|
|
1476
|
-
protocolVersion: PROTOCOL_VERSION,
|
|
1477
|
-
clientCapabilities: {}
|
|
1478
|
-
});
|
|
1479
|
-
}
|
|
1480
|
-
async newSession(cwd) {
|
|
1481
|
-
const conn = this.ensureConnection();
|
|
1482
|
-
const response = await conn.newSession({ cwd, mcpServers: [] });
|
|
1483
|
-
return response.sessionId;
|
|
1484
|
-
}
|
|
1485
|
-
async loadSession(sessionId, cwd) {
|
|
1486
|
-
const conn = this.ensureConnection();
|
|
1487
|
-
const response = await conn.loadSession({
|
|
1488
|
-
sessionId,
|
|
1489
|
-
cwd,
|
|
1490
|
-
mcpServers: []
|
|
1491
|
-
});
|
|
1492
|
-
return response.sessionId;
|
|
1493
|
-
}
|
|
1494
|
-
async prompt(sessionId, text, options) {
|
|
1495
|
-
const conn = this.ensureConnection();
|
|
1496
|
-
const chunks = [];
|
|
1497
|
-
const onChunk = /* @__PURE__ */ __name((sid, chunk) => {
|
|
1498
|
-
if (sid === sessionId)
|
|
1499
|
-
chunks.push(chunk);
|
|
1500
|
-
}, "onChunk");
|
|
1501
|
-
this.on("textChunk", onChunk);
|
|
1502
|
-
const prompt = [];
|
|
1503
|
-
if (options?.imageBase64 && options.imageMimeType) {
|
|
1504
|
-
prompt.push({
|
|
1505
|
-
type: "image",
|
|
1506
|
-
data: options.imageBase64,
|
|
1507
|
-
mimeType: options.imageMimeType
|
|
1508
|
-
});
|
|
1509
|
-
}
|
|
1510
|
-
prompt.push({ type: "text", text });
|
|
1511
|
-
try {
|
|
1512
|
-
await conn.prompt({
|
|
1513
|
-
sessionId,
|
|
1514
|
-
prompt
|
|
1515
|
-
});
|
|
1516
|
-
} finally {
|
|
1517
|
-
this.off("textChunk", onChunk);
|
|
1518
|
-
}
|
|
1519
|
-
return chunks.join("");
|
|
1520
|
-
}
|
|
1521
|
-
async cancelSession(sessionId) {
|
|
1522
|
-
const conn = this.ensureConnection();
|
|
1523
|
-
await conn.cancel({ sessionId });
|
|
1524
|
-
}
|
|
1525
|
-
stop() {
|
|
1526
|
-
if (this.child) {
|
|
1527
|
-
this.child.kill();
|
|
1528
|
-
this.child = null;
|
|
1529
|
-
}
|
|
1530
|
-
this.connection = null;
|
|
1531
|
-
}
|
|
1532
|
-
get isConnected() {
|
|
1533
|
-
return this.child !== null && !this.child.killed && this.child.exitCode === null;
|
|
1534
|
-
}
|
|
1535
|
-
handleSessionUpdate(params) {
|
|
1536
|
-
const { sessionId } = params;
|
|
1537
|
-
const update = params["update"];
|
|
1538
|
-
if (!update)
|
|
1539
|
-
return;
|
|
1540
|
-
const type = update["sessionUpdate"];
|
|
1541
|
-
switch (type) {
|
|
1542
|
-
case "agent_message_chunk": {
|
|
1543
|
-
const content = update["content"];
|
|
1544
|
-
if (content?.type === "text" && content.text) {
|
|
1545
|
-
this.emit("textChunk", sessionId, content.text);
|
|
1546
|
-
}
|
|
1547
|
-
break;
|
|
1548
|
-
}
|
|
1549
|
-
case "tool_call": {
|
|
1550
|
-
const event = {
|
|
1551
|
-
sessionId,
|
|
1552
|
-
toolCallId: update["toolCallId"],
|
|
1553
|
-
kind: update["kind"] || "",
|
|
1554
|
-
title: update["title"] || "",
|
|
1555
|
-
status: update["status"] || "pending",
|
|
1556
|
-
rawInput: update["rawInput"]
|
|
1557
|
-
};
|
|
1558
|
-
this.emit("toolCall", event);
|
|
1559
|
-
break;
|
|
1560
|
-
}
|
|
1561
|
-
case "available_commands_update": {
|
|
1562
|
-
if (Array.isArray(update["availableCommands"])) {
|
|
1563
|
-
this._availableCommands = update["availableCommands"].map((cmd) => {
|
|
1564
|
-
const altNames = readAvailableCommandAltNames(cmd);
|
|
1565
|
-
return altNames ? { ...cmd, altNames } : cmd;
|
|
1566
|
-
});
|
|
1567
|
-
}
|
|
1568
|
-
break;
|
|
1569
|
-
}
|
|
1570
|
-
default:
|
|
1571
|
-
break;
|
|
1572
|
-
}
|
|
1573
|
-
this.emit("sessionUpdate", params);
|
|
1574
|
-
}
|
|
1575
|
-
ensureConnection() {
|
|
1576
|
-
if (!this.connection || !this.isConnected) {
|
|
1577
|
-
throw new Error("Not connected to ACP agent");
|
|
1578
|
-
}
|
|
1579
|
-
return this.connection;
|
|
1580
|
-
}
|
|
1581
|
-
};
|
|
1582
|
-
|
|
1583
|
-
// packages/channels/base/dist/DaemonChannelBridge.js
|
|
1584
|
-
init_esbuild_shims();
|
|
1585
|
-
|
|
1586
|
-
export {
|
|
1587
|
-
resolvePath,
|
|
1588
|
-
getGlobalQwenDir,
|
|
1589
|
-
AcpBridge,
|
|
1590
|
-
PairingStore,
|
|
1591
|
-
SessionRouter,
|
|
1592
|
-
sanitizeSenderName,
|
|
1593
|
-
sanitizePromptText,
|
|
1594
|
-
sanitizeLogText,
|
|
1595
|
-
ChannelBase
|
|
1596
|
-
};
|