@qwen-code/qwen-code 0.21.2 → 0.21.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled/qc-helper/docs/configuration/settings.md +41 -38
- package/bundled/qc-helper/docs/features/channels/github.md +7 -0
- package/bundled/qc-helper/docs/features/channels/gitlab.md +6 -0
- package/bundled/qc-helper/docs/features/code-review.md +78 -23
- package/bundled/qc-helper/docs/features/commands.md +2 -0
- package/bundled/qc-helper/docs/features/hooks.md +115 -18
- package/bundled/qc-helper/docs/features/memory.md +27 -0
- package/bundled/qc-helper/docs/features/skills.md +46 -1
- package/bundled/qc-helper/docs/features/sub-agents.md +42 -9
- package/bundled/qc-helper/docs/features/tool-use-summaries.md +7 -7
- package/bundled/qc-helper/docs/qwen-serve.md +21 -1
- package/bundled/qc-helper/docs/reference/keyboard-shortcuts.md +13 -13
- package/bundled/qc-helper/docs/support/troubleshooting.md +6 -1
- package/bundled/review/DESIGN.md +167 -32
- package/bundled/review/SKILL.md +156 -32
- package/chunks/{MaxSizedBox-TQBQ247P.js → MaxSizedBox-OJR636GP.js} +40 -38
- package/chunks/{StandaloneSessionPicker-2HPTKCLX.js → StandaloneSessionPicker-AMMBF5UY.js} +62 -59
- package/chunks/{acp-startup-profiler-2C4A5ZTJ.js → acp-startup-profiler-3RICG2YW.js} +2 -2
- package/chunks/{acpAgent-PS6EDGH3.js → acpAgent-M57LVIQA.js} +662 -560
- package/chunks/{agent-VDOUB35F.js → agent-PE37ARLM.js} +36 -34
- package/chunks/{agent-headless-EO53EFS2.js → agent-headless-45ASME4R.js} +36 -34
- package/chunks/{anthropicContentGenerator-OFHSZJBF.js → anthropicContentGenerator-WCBYB6JG.js} +337 -27
- package/chunks/{artifact-tool-RDN2W7ND.js → artifact-tool-TBOYEKTY.js} +2 -2
- package/chunks/{askUserQuestion-OZERWH5R.js → askUserQuestion-23Y5WZFJ.js} +2 -2
- package/chunks/{bridge-W7AZSRZQ.js → bridge-K3ZRPO4D.js} +42 -40
- package/chunks/{ca-6W3SK7OQ.js → ca-4OJN75WT.js} +46 -5
- package/chunks/{channel-management-service-5S7YCWX5.js → channel-management-service-IXJ2MZTS.js} +7 -7
- package/chunks/{channel-settings-store-JFVRSNER.js → channel-settings-store-PEAR3IME.js} +43 -41
- package/chunks/{channel-worker-group-OVWGYMZS.js → channel-worker-group-NDG4WOOZ.js} +8 -8
- package/chunks/{channel-worker-manager-H55GKOQA.js → channel-worker-manager-ZCW6UW34.js} +8 -8
- package/chunks/{channel-worker-supervisor-CABBXYHK.js → channel-worker-supervisor-ATGFTZRN.js} +4 -4
- package/chunks/{chunk-W3UKBMSI.js → chunk-22YFMJBW.js} +84 -8
- package/chunks/{chunk-6MPDY6ZG.js → chunk-24ER7UV4.js} +1 -1
- package/chunks/{chunk-QDK4F2N5.js → chunk-2DNKEBXY.js} +1 -1
- package/chunks/{chunk-D6MDRSZ6.js → chunk-2LZCLQIS.js} +22 -11
- package/chunks/{chunk-JYBU6YUE.js → chunk-2M5N3DHY.js} +1 -1
- package/chunks/{chunk-KR32NIAN.js → chunk-2MI7ZTRT.js} +10 -10
- package/chunks/{chunk-LHKCY7DY.js → chunk-2UISUFHQ.js} +3 -3
- package/chunks/{chunk-JXVC2PL3.js → chunk-33FNCQSY.js} +2 -2
- package/chunks/{chunk-NBD76HUW.js → chunk-3LGZEYOF.js} +2 -2
- package/chunks/{chunk-CO2U743O.js → chunk-3MWNROHB.js} +4 -4
- package/chunks/{chunk-E2BHWIDT.js → chunk-3RXUYQJI.js} +6 -2
- package/chunks/{chunk-E5Z2AVNV.js → chunk-3SH53ANB.js} +0 -11
- package/chunks/{chunk-LYI2PZDK.js → chunk-3XF4WT3B.js} +2 -2
- package/chunks/{chunk-N4ACHWNH.js → chunk-432XCUHF.js} +1 -1
- package/chunks/{chunk-FNDXMN7I.js → chunk-4AGGSHCH.js} +774 -205
- package/chunks/{chunk-DUNI4EBW.js → chunk-4K3RV3OA.js} +80 -50
- package/chunks/{chunk-L2E5GFAG.js → chunk-4LKVQFHM.js} +2 -2
- package/chunks/{chunk-YDLJGZHL.js → chunk-4NFY2S7N.js} +0 -33
- package/chunks/{chunk-JLZ3RBQG.js → chunk-5JPVOYWA.js} +144 -11
- package/chunks/{chunk-6TAASXWL.js → chunk-5VYFXHY2.js} +3 -3
- package/chunks/{chunk-EU5TBFJI.js → chunk-6NE5NTHJ.js} +2 -2
- package/chunks/{chunk-XQK4XXTN.js → chunk-6O5DVH27.js} +1 -1
- package/chunks/{chunk-2RFKHEXL.js → chunk-6PLDPT2C.js} +1 -1
- package/chunks/{chunk-3APUUXRF.js → chunk-A5YZ3FA2.js} +2 -1
- package/chunks/{chunk-SHN4KHDK.js → chunk-A6SX5TAH.js} +3 -3
- package/chunks/{chunk-SZ3KHTDR.js → chunk-AKA24BGO.js} +1 -1
- package/chunks/{chunk-Q3T5KA2V.js → chunk-BFHXDAR7.js} +1 -1
- package/chunks/{chunk-L55JWS76.js → chunk-BGXZBI5B.js} +1 -1
- package/chunks/{chunk-P5VC5USY.js → chunk-BKGA75MU.js} +1 -1
- package/chunks/{chunk-2KKCCLPT.js → chunk-BKXTHMOZ.js} +1 -1
- package/chunks/{chunk-V4XNUDPE.js → chunk-BSKUTHLO.js} +15 -7
- package/chunks/{chunk-MM7TH7ZM.js → chunk-CFDR3GNL.js} +18 -3
- package/chunks/{chunk-E2FN6SM7.js → chunk-CIUUYB23.js} +5 -5
- package/chunks/{chunk-A7Y5H4TX.js → chunk-D2GPKXCQ.js} +7 -7
- package/chunks/{chunk-JCJ7LKNA.js → chunk-DIEFUBV6.js} +1644 -1570
- package/chunks/{chunk-GL4JOZXU.js → chunk-DSGJLHM3.js} +4 -4
- package/chunks/{chunk-675H6PLP.js → chunk-DYA25Y7U.js} +1 -1
- package/chunks/{chunk-3I6DUGQD.js → chunk-E4WHKETF.js} +32 -4
- package/chunks/{chunk-CU47KXB5.js → chunk-EGYJRIDF.js} +6 -6
- package/chunks/{chunk-6CQWRWUC.js → chunk-EIMKVOSF.js} +3 -3
- package/chunks/{chunk-QTERJVPZ.js → chunk-EKTCKZV5.js} +1 -0
- package/chunks/{chunk-NHFEMWAZ.js → chunk-FF75UUB2.js} +1 -1
- package/chunks/{chunk-L32VV3ZJ.js → chunk-FG63DZQA.js} +12 -12
- package/chunks/{chunk-FCYYAB4S.js → chunk-FHPKHXHT.js} +3 -3
- package/chunks/{chunk-GJ2GBPBD.js → chunk-FXVHVCNA.js} +5 -5
- package/chunks/{chunk-56Z7QEAQ.js → chunk-GMSBXEH4.js} +16 -16
- package/chunks/chunk-GOFAQQZA.js +132 -0
- package/chunks/{chunk-YUFIEIWC.js → chunk-GTM6IHBB.js} +2 -2
- package/chunks/{chunk-HIVKI6O6.js → chunk-H35Q5CEG.js} +9 -7
- package/chunks/{chunk-FJ5WUMUQ.js → chunk-H36SETQS.js} +1 -1
- package/chunks/{chunk-AUOULH6Q.js → chunk-H6FIBBXK.js} +1610 -2196
- package/chunks/{chunk-VEMNQMHA.js → chunk-H6ZYP74A.js} +5 -13
- package/chunks/{chunk-TTJZAIFP.js → chunk-HEBW74ZW.js} +3 -3
- package/chunks/{chunk-6FMJLI5M.js → chunk-HFVW54NL.js} +41 -6
- package/chunks/{chunk-44F4LBWZ.js → chunk-HIXVWF7I.js} +56 -18
- package/chunks/{chunk-EQNUDTL6.js → chunk-HUIQ4FYC.js} +2 -2
- package/chunks/{chunk-5NNHICSA.js → chunk-I7JDGNG6.js} +3 -3
- package/chunks/{chunk-PVNQRENX.js → chunk-IF6K42YJ.js} +1 -1
- package/chunks/{chunk-PJOHNFIY.js → chunk-IWGTGH4G.js} +5 -5
- package/chunks/{chunk-B4AJNNOL.js → chunk-J26HHFI5.js} +1 -1
- package/chunks/{chunk-CNTR4GR6.js → chunk-JELIWHXD.js} +1 -1
- package/chunks/{chunk-V25K5ID7.js → chunk-K2J5JZ6B.js} +1 -1
- package/chunks/{chunk-IGTHU3T4.js → chunk-KKN2IE7Q.js} +1 -1
- package/chunks/{chunk-TZTAV5RH.js → chunk-KVUBYJDM.js} +2 -2
- package/chunks/{chunk-3F2WDAV6.js → chunk-KXWERR6P.js} +14 -14
- package/chunks/{chunk-JJPKFJBT.js → chunk-L7FTDKCD.js} +3 -3
- package/chunks/chunk-LBOVL47Y.js +29 -0
- package/chunks/{chunk-FS674JW4.js → chunk-LBWNSLJK.js} +3 -3
- package/chunks/{chunk-I5BT7ADR.js → chunk-LGT3YMLN.js} +4 -4
- package/chunks/{chunk-UV6KF7T3.js → chunk-LZV2BZPL.js} +3 -3
- package/chunks/{chunk-P755N4CS.js → chunk-M5LIY7Y6.js} +6 -6
- package/chunks/{chunk-YGE3IXJP.js → chunk-M744OFVE.js} +1 -1
- package/chunks/{chunk-NTDH4IML.js → chunk-MCXMWLFT.js} +7 -7
- package/chunks/chunk-MISUEWB4.js +8749 -0
- package/chunks/{chunk-TEGJSJDF.js → chunk-MOHAWIAW.js} +2 -2
- package/chunks/{chunk-KXDONCPU.js → chunk-MTR4JWQJ.js} +4 -4
- package/chunks/{chunk-RSVPYAGK.js → chunk-MUEJ4R3E.js} +5 -5
- package/chunks/{chunk-PCK4B6LR.js → chunk-N42C2FU3.js} +1 -1
- package/chunks/{chunk-WWCMSZII.js → chunk-NW57GHNL.js} +71 -7
- package/chunks/{chunk-PQNHRFJC.js → chunk-NYX53SV4.js} +0 -2
- package/chunks/{chunk-QARJNL2V.js → chunk-O2ZYHG33.js} +5 -4
- package/chunks/{chunk-V7RNNPGC.js → chunk-P5Y23G2L.js} +18 -1
- package/chunks/{chunk-GURGAJTX.js → chunk-PDDYAXL2.js} +4 -1
- package/chunks/chunk-PWCQRLM5.js +1170 -0
- package/chunks/{chunk-VMX5RZQH.js → chunk-Q3G5L5KF.js} +18 -9
- package/chunks/{chunk-DQ2O5QOJ.js → chunk-QDWLUXKP.js} +1 -1
- package/chunks/chunk-QL4TN4HS.js +59 -0
- package/chunks/{chunk-E6CJ2SMM.js → chunk-QMBEW7EW.js} +2 -2
- package/chunks/{chunk-U4VTUMMK.js → chunk-QONSRAEV.js} +1 -1
- package/chunks/{chunk-DEEABGH2.js → chunk-QPSYIV2O.js} +3320 -536
- package/chunks/{chunk-Q5WZTTWE.js → chunk-QWDVZH24.js} +6 -8
- package/chunks/{chunk-2LEOZTBT.js → chunk-QWW6I2UO.js} +111 -26
- package/chunks/{chunk-LOY7RWQG.js → chunk-QYPT3QUD.js} +3 -3
- package/chunks/{chunk-6PQPKTXP.js → chunk-R7XNRHYD.js} +1 -1
- package/chunks/{chunk-7SJN2IWT.js → chunk-RCXCGZG6.js} +5 -5
- package/chunks/{chunk-HX6RLYUO.js → chunk-RL6J3BPE.js} +4 -4
- package/chunks/{chunk-Z3S5EMOW.js → chunk-RPYYTL7E.js} +60 -12
- package/chunks/{chunk-MB32MS4U.js → chunk-RR224BUP.js} +215 -174
- package/chunks/{chunk-H6I4PGRV.js → chunk-SKSF3LGQ.js} +4 -4
- package/chunks/{chunk-77V3VIKL.js → chunk-SQ3YD5MI.js} +9 -9
- package/chunks/{chunk-CBBP4BMC.js → chunk-SRY26X4H.js} +72 -206
- package/chunks/{chunk-RDJOB6S3.js → chunk-TXABHVZD.js} +1 -1
- package/chunks/{chunk-R2XWGR75.js → chunk-TYERNYH4.js} +2 -2
- package/chunks/chunk-TZTBZ25J.js +222 -0
- package/chunks/{chunk-45IOTAUS.js → chunk-U25CMJYY.js} +1 -1
- package/chunks/{chunk-7ULQIS27.js → chunk-UEJESCS7.js} +1 -1
- package/chunks/{chunk-HC45LTEN.js → chunk-UH7Z7Y45.js} +1 -1
- package/chunks/{chunk-CPZAH673.js → chunk-UKZEXS6K.js} +2 -0
- package/chunks/{chunk-54YCNSK5.js → chunk-UQDFEU47.js} +4113 -10152
- package/chunks/{chunk-XP4L6KS3.js → chunk-URN76TKR.js} +2 -2
- package/chunks/{chunk-XUGZYLDH.js → chunk-UWSXFI6E.js} +1 -1
- package/chunks/{chunk-AOHY3QJ3.js → chunk-VA5NLVPT.js} +3 -3
- package/chunks/{chunk-PZEXDV3H.js → chunk-VUNRNRN6.js} +71 -43
- package/chunks/{chunk-FANRZEAJ.js → chunk-VWOVHOTT.js} +1 -1
- package/chunks/{chunk-QTHQ6Z5X.js → chunk-WK4TJUEA.js} +1 -1
- package/chunks/{chunk-ZPZ7ZJEE.js → chunk-WUC6ZFXO.js} +1 -1
- package/chunks/{chunk-7KAXXBER.js → chunk-WY2ENRDN.js} +3 -3
- package/chunks/{chunk-TIRRPCFS.js → chunk-X6QNZOTC.js} +1 -1
- package/chunks/{chunk-KWHKHSIS.js → chunk-X6WUYMOV.js} +8 -8
- package/chunks/{chunk-IIUGIWWY.js → chunk-XC2VAFVZ.js} +1 -1
- package/chunks/{chunk-D4HTUMGX.js → chunk-XC656O42.js} +3 -3
- package/chunks/{chunk-RG64CDT2.js → chunk-XMJO6C44.js} +131 -48
- package/chunks/{chunk-ZMOJ7ISP.js → chunk-XUMAD5IT.js} +1 -1
- package/chunks/{chunk-THVU3RNH.js → chunk-YAVY33G4.js} +1 -1
- package/chunks/{chunk-YANIWTRZ.js → chunk-YPUHMQXV.js} +1 -1
- package/chunks/{chunk-QCXGC7QB.js → chunk-YU6TBRTA.js} +3 -3
- package/chunks/{chunk-OFZKFPYG.js → chunk-YWMDTMAU.js} +3 -3
- package/chunks/{chunk-DSFHVTWD.js → chunk-YXEVDA66.js} +2 -2
- package/chunks/{chunk-JE2CEOBO.js → chunk-ZGSUFIL5.js} +4 -4
- package/chunks/{chunk-BBIQAX4V.js → chunk-ZJES4C3I.js} +30 -18
- package/chunks/{computer-use-5QWCFNKD.js → computer-use-UJMK6JQU.js} +36 -34
- package/chunks/{config-utils-WADIDWGG.js → config-utils-7COTJMWK.js} +4 -4
- package/chunks/{contextCommand-4Z7T7TUM.js → contextCommand-4YABGF4S.js} +40 -38
- package/chunks/{core-runtime-6N4EFC2Q.js → core-runtime-7M4VM3DA.js} +36 -34
- package/chunks/{create-sub-session-AEREHZBQ.js → create-sub-session-HOFDW4N7.js} +39 -37
- package/chunks/{create-sub-session-62YZWVAY.js → create-sub-session-J32WC64U.js} +2 -2
- package/chunks/{cron-create-6E7GIQTG.js → cron-create-HMFSHIAR.js} +4 -4
- package/chunks/{cron-delete-JD26XY3W.js → cron-delete-ZJFHOX57.js} +4 -4
- package/chunks/{cron-list-CUWFB44G.js → cron-list-ZEX2NBMN.js} +4 -4
- package/chunks/{daemon-PCNCI5YU.js → daemon-CCIX4BMG.js} +47 -6
- package/chunks/{daemon-status-provider-DDF7C5M7.js → daemon-status-provider-W2U5Q3J5.js} +47 -45
- package/chunks/{daemon-trust-policy-Q4KSOJTR.js → daemon-trust-policy-IFICWPXP.js} +42 -40
- package/chunks/{daemon-trust-policy-monitor-V6FTE3IY.js → daemon-trust-policy-monitor-GX7EO6FW.js} +42 -40
- package/chunks/{de-SY6O76BF.js → de-A6TI4LBB.js} +46 -5
- package/chunks/{deferred-core-runtime-GHJ5GQHK.js → deferred-core-runtime-U36OOOKE.js} +36 -34
- package/chunks/display-image-VS3TXYQH.js +184 -0
- package/chunks/{dist-ZHXOBXDN.js → dist-3BMEUGTG.js} +1 -1
- package/chunks/{dist-26BIMQT6.js → dist-4KAX7CCL.js} +289 -27
- package/chunks/{dist-OAOC5OV4.js → dist-7JGAAQSC.js} +2 -2
- package/chunks/{dist-SL2LUMML.js → dist-A7H2KKUC.js} +1 -1
- package/chunks/{dist-2PERFI23.js → dist-DPPHJAVL.js} +3 -3
- package/chunks/{dist-PVEAFLHY.js → dist-HVBMEYWK.js} +1 -1
- package/chunks/{dist-ULIG4M5H.js → dist-NEBRI7WO.js} +67 -15
- package/chunks/{dist-WGMNT3JQ.js → dist-SFQ34F4M.js} +24 -6
- package/chunks/{earlyInputCapture-TQXLLISD.js → earlyInputCapture-5ZWDH3SB.js} +37 -35
- package/chunks/{edit-FJEUV6KZ.js → edit-J7SW6UP6.js} +36 -34
- package/chunks/{en-NBK3JKCE.js → en-L4UQDLWW.js} +57 -6
- package/chunks/{enter-worktree-7GE3TZ4P.js → enter-worktree-NRLOHRB6.js} +36 -34
- package/chunks/{enterPlanMode-KR5TMPCJ.js → enterPlanMode-GDSTEFSM.js} +36 -34
- package/chunks/{environment-SWL3CMB3.js → environment-KHYEKG5W.js} +39 -37
- package/chunks/{errors-XQE7BGZV.js → errors-VNLAJV3O.js} +39 -37
- package/chunks/{exit-worktree-CHKTYNCV.js → exit-worktree-ARVID7DF.js} +36 -34
- package/chunks/{exitPlanMode-QYAG6LWX.js → exitPlanMode-4ZFY7KUH.js} +36 -34
- package/chunks/{fast-path-Z2K7W6DC.js → fast-path-KA65VREJ.js} +9 -5
- package/chunks/{fr-4KG5V7UT.js → fr-QF7RI5ZE.js} +46 -5
- package/chunks/{gemini-YU4XKEDG.js → gemini-5PMCNPF7.js} +149 -90
- package/chunks/{geminiContentGenerator-ZZX6NBO4.js → geminiContentGenerator-NFSYJ4HA.js} +6 -6
- package/chunks/{glob-VYF5CXSX.js → glob-F3EAUDAX.js} +36 -34
- package/chunks/{goal-tools-HGO6VXPB.js → goal-tools-SPQ5M6D3.js} +3 -3
- package/chunks/{grep-5DP5ZUTW.js → grep-UJMPVZER.js} +36 -34
- package/chunks/{handleAutoUpdate-UVBJ56DL.js → handleAutoUpdate-MG4FRZPN.js} +42 -40
- package/chunks/{i18n-UQNYHEUD.js → i18n-EVHV4ZF3.js} +38 -36
- package/chunks/{image-gen-WHPIZNWF.js → image-gen-UC7XVHI7.js} +9 -9
- package/chunks/{initializer-GMIICF4W.js → initializer-O5B6HUBI.js} +43 -41
- package/chunks/{installationInfo-4MGG4WVI.js → installationInfo-2LIKCNVX.js} +37 -35
- package/chunks/{ja-F3GSQXMF.js → ja-URQESVGW.js} +46 -5
- package/chunks/{keychain-token-storage-IKO4G53C.js → keychain-token-storage-7BT4TQ2A.js} +2 -2
- package/chunks/{kittyProtocolDetector-MBRTDRBK.js → kittyProtocolDetector-BFLAKYXH.js} +1 -1
- package/chunks/{list-CPN2W334.js → list-BPKIE3Z4.js} +45 -43
- package/chunks/{list-agents-PXJ7ZUFB.js → list-agents-TWEGRJWO.js} +2 -2
- package/chunks/{loadedSettingsAdapter-ZQAV5NDO.js → loadedSettingsAdapter-NGLFF4V4.js} +42 -40
- package/chunks/{loggingContentGenerator-5E7W4IEP.js → loggingContentGenerator-7TFESRG5.js} +52 -37
- package/chunks/{loop-wakeup-Y26NQZ5I.js → loop-wakeup-6LLEOQYV.js} +5 -5
- package/chunks/{ls-ONQTTMQR.js → ls-H5WX22VK.js} +4 -4
- package/chunks/{lsp-YGHHRGMD.js → lsp-G6FXYEIS.js} +2 -2
- package/chunks/{managed-npm-update-MKKAXYXA.js → managed-npm-update-CLZGGV7F.js} +39 -37
- package/chunks/{mcp-V5NTJF6X.js → mcp-X5OZOOXX.js} +42 -40
- package/chunks/{monitor-CO3ASY6E.js → monitor-TGSVSAZZ.js} +36 -34
- package/chunks/nonInteractiveCli-GUMJ5JLG.js +150 -0
- package/chunks/{notebook-edit-Z7AEE6FM.js → notebook-edit-EZISE7IC.js} +36 -34
- package/chunks/{openaiContentGenerator-CA3MQOP3.js → openaiContentGenerator-ZAKS5A6P.js} +18 -18
- package/chunks/{pidfile-NL7MKDRU.js → pidfile-PCMBX4LR.js} +37 -35
- package/chunks/{processUtils-PB5K2DYH.js → processUtils-2CSVE5BW.js} +2 -2
- package/chunks/{pt-EQ33KWEN.js → pt-EEEQXRUI.js} +46 -5
- package/chunks/{qwenContentGenerator-JYFNDCOS.js → qwenContentGenerator-S3H6PIC4.js} +42 -40
- package/chunks/{qwenOAuth2-W2ALKQDD.js → qwenOAuth2-MNU425VE.js} +8 -8
- package/chunks/{read-file-IEOR5H5W.js → read-file-HXJXHBI6.js} +10 -10
- package/chunks/{read-mcp-resource-U2IJN5HM.js → read-mcp-resource-DRZSI3HE.js} +2 -2
- package/chunks/{record-artifact-MGPJ7EG4.js → record-artifact-IPIRWMFY.js} +3 -3
- package/chunks/{resumeHistoryUtils-BNBTP5AS.js → resumeHistoryUtils-4QAMC2S6.js} +44 -42
- package/chunks/{ripGrep-UMQODKEB.js → ripGrep-MCETPF2E.js} +36 -34
- package/chunks/{ru-7OZAUKGG.js → ru-WGZDYQAB.js} +46 -5
- package/chunks/{run-qwen-serve-RN2R6QQW.js → run-qwen-serve-3NMODTXV.js} +219 -55
- package/chunks/{runtime-QFJ2MJF5.js → runtime-AQOV7DVP.js} +49 -45
- package/chunks/{scheduler-AMYUFOZI.js → scheduler-DN7SZ6ZE.js} +36 -34
- package/chunks/{sdk-exporters-http-4BQANSJ6.js → sdk-exporters-http-IKNWQJNV.js} +2 -2
- package/chunks/{sdk-impl-Z4VXWVFX.js → sdk-impl-7CUUMQDB.js} +2 -2
- package/chunks/{send-message-PMP4ATVM.js → send-message-HGCVVDTZ.js} +5 -4
- package/chunks/{serve-EIIYJVX3.js → serve-WZB45HVT.js} +42 -40
- package/chunks/{server-EP2LLAPD.js → server-GOTZFEEZ.js} +616 -387
- package/chunks/{session-GMPZBLOW.js → session-23LOFLWN.js} +149 -87
- package/chunks/{settings-LN7RNKBS.js → settings-LI2ORVXC.js} +41 -39
- package/chunks/{shell-EM2OWU5X.js → shell-GLC6OP3C.js} +36 -34
- package/chunks/{skill-T5VPSK73.js → skill-C7W6M4GO.js} +27 -14
- package/chunks/{skill-settings-GMR3GMNY.js → skill-settings-W25NCK5I.js} +41 -39
- package/chunks/{spawnChannel-YPCU62DI.js → spawnChannel-OHJLEBLD.js} +38 -36
- package/chunks/{standalone-update-JAKIM2NF.js → standalone-update-E4V5V7WT.js} +39 -37
- package/chunks/{startInteractiveUI-YETQWGYF.js → startInteractiveUI-FKILLW7Q.js} +1815 -1034
- package/chunks/{syntheticOutput-D7E6ZFKR.js → syntheticOutput-EYL7DWO7.js} +3 -3
- package/chunks/{task-create-NFCYD43L.js → task-create-CSCEG4SM.js} +10 -9
- package/chunks/{task-list-TAVMGEVD.js → task-list-6CVQPORX.js} +6 -6
- package/chunks/{task-stop-ZDF2UJRM.js → task-stop-Y5ZTFVRX.js} +2 -2
- package/chunks/{task-update-6L7LHGEX.js → task-update-C52LNFU5.js} +10 -9
- package/chunks/{team-create-LFJ6HGNY.js → team-create-HBIJ7NHT.js} +36 -34
- package/chunks/{team-delete-DPGGRQBU.js → team-delete-CE7DC53R.js} +6 -6
- package/chunks/{team-plan-approval-C2KLSDGO.js → team-plan-approval-IBJOKS4J.js} +36 -34
- package/chunks/terminal-image-renderer-BOQJPVLB.js +89 -0
- package/chunks/{theme-manager-JQ3ZGBP5.js → theme-manager-NSS7VLUD.js} +37 -35
- package/chunks/{todoWrite-2UO5G6G6.js → todoWrite-YKAA57W2.js} +107 -36
- package/chunks/{tool-search-FAZBFRJZ.js → tool-search-DEAS54VG.js} +13 -12
- package/chunks/{total-session-admission-5CALIVYO.js → total-session-admission-4LWTX3WD.js} +43 -41
- package/chunks/{trustedFolders-KOPSP44S.js → trustedFolders-NF3J2TA7.js} +38 -36
- package/chunks/{update-relaunch-6QL2TICS.js → update-relaunch-WUHRH5V3.js} +5 -5
- package/chunks/{updateCheck-4Q6RIKFW.js → updateCheck-7EG4BLBH.js} +41 -39
- package/chunks/{useAutoAcceptIndicator-Q5BMKZV3.js → useAutoAcceptIndicator-ZRV56GVP.js} +46 -44
- package/chunks/{validateNonInterActiveAuth-GKOC5F6Z.js → validateNonInterActiveAuth-WC2FPE3I.js} +86 -84
- package/chunks/{version-VSATY2LD.js → version-6SS7KCUP.js} +1 -1
- package/chunks/{web-fetch-JCLKL6NL.js → web-fetch-Y5QQMPKA.js} +13 -13
- package/chunks/{web-search-DSQTFUBX.js → web-search-QKKEU723.js} +8 -8
- package/chunks/{workflow-M6QAVKVJ.js → workflow-PWIU6Q2M.js} +418 -304
- package/chunks/{workspace-providers-status-YKLTLMJA.js → workspace-providers-status-B2DM6FNK.js} +45 -43
- package/chunks/{workspace-registration-store-5DMGKU64.js → workspace-registration-store-7Y33TTHJ.js} +2 -2
- package/chunks/{workspace-registry-PTYUSYXX.js → workspace-registry-TSNJKLPC.js} +43 -41
- package/chunks/{workspace-service-25ML3T47.js → workspace-service-4I2QHDX4.js} +50 -48
- package/chunks/{workspace-skills-status-YKZWOYRX.js → workspace-skills-status-HVXNWVGR.js} +44 -42
- package/chunks/{workspace-trust-reconciler-5FI2S3OS.js → workspace-trust-reconciler-RDBBGMWP.js} +49 -47
- package/chunks/{write-file-AY7VLP5X.js → write-file-RQ4DG5RY.js} +36 -34
- package/chunks/{zh-H3YDK5QW.js → zh-72XPUG5P.js} +54 -6
- package/chunks/{zh-TW-JYCP7XUA.js → zh-TW-LTB4SRH3.js} +54 -6
- package/chunks/{zoom-image-S4LJZMD5.js → zoom-image-O5YCB7WN.js} +10 -10
- package/cli.js +32 -48
- package/locales/ca.js +60 -4
- package/locales/de.js +62 -4
- package/locales/en.js +76 -6
- package/locales/fr.js +64 -4
- package/locales/ja.js +61 -4
- package/locales/pt.js +60 -4
- package/locales/ru.js +63 -4
- package/locales/zh-TW.js +67 -6
- package/locales/zh.js +67 -6
- package/package.json +3 -3
- package/web-shell/assets/{arc-xjArKHU2.js → arc-Bd7d781i.js} +1 -1
- package/web-shell/assets/{architectureDiagram-3BPJPVTR-BgrQDNYG.js → architectureDiagram-3BPJPVTR-CpnOJqNE.js} +1 -1
- package/web-shell/assets/{blockDiagram-GPEHLZMM-CXxWC-1_.js → blockDiagram-GPEHLZMM-NxVTiWnk.js} +1 -1
- package/web-shell/assets/{c4Diagram-AAUBKEIU-Dx1tLm3v.js → c4Diagram-AAUBKEIU-BWT75l82.js} +1 -1
- package/web-shell/assets/channel-A434sWK5.js +1 -0
- package/web-shell/assets/{chunk-2J33WTMH-CBxjogVR.js → chunk-2J33WTMH-Dzz03hk6.js} +1 -1
- package/web-shell/assets/{chunk-4BX2VUAB-izb-rY5W.js → chunk-4BX2VUAB-DRF-egfm.js} +1 -1
- package/web-shell/assets/{chunk-55IACEB6-NzLyvSb8.js → chunk-55IACEB6-Co15vq5x.js} +1 -1
- package/web-shell/assets/{chunk-727SXJPM-DVyKb0dl.js → chunk-727SXJPM-DHSUk1J7.js} +1 -1
- package/web-shell/assets/{chunk-AQP2D5EJ-BtqL7NzB.js → chunk-AQP2D5EJ-Br8u_E2R.js} +1 -1
- package/web-shell/assets/{chunk-FMBD7UC4-iNpdKIO0.js → chunk-FMBD7UC4-DXbPTnqF.js} +1 -1
- package/web-shell/assets/{chunk-ND2GUHAM-DI7cbFmX.js → chunk-ND2GUHAM-BleQdi4d.js} +1 -1
- package/web-shell/assets/{chunk-QZHKN3VN-3Y_-xFYs.js → chunk-QZHKN3VN-CqgBf-95.js} +1 -1
- package/web-shell/assets/classDiagram-4FO5ZUOK-CwPuhldB.js +1 -0
- package/web-shell/assets/classDiagram-v2-Q7XG4LA2-CwPuhldB.js +1 -0
- package/web-shell/assets/{cose-bilkent-S5V4N54A--raRatZn.js → cose-bilkent-S5V4N54A-WS7b13Yg.js} +1 -1
- package/web-shell/assets/{dagre-BM42HDAG-B8SU70mO.js → dagre-BM42HDAG-v_55ki8o.js} +1 -1
- package/web-shell/assets/{diagram-2AECGRRQ-ewgoakXo.js → diagram-2AECGRRQ-DaYAjbdK.js} +1 -1
- package/web-shell/assets/{diagram-5GNKFQAL-Bm_JLTnS.js → diagram-5GNKFQAL-D_Hy-L64.js} +1 -1
- package/web-shell/assets/{diagram-KO2AKTUF-DEIUjUy9.js → diagram-KO2AKTUF-dB06cZyD.js} +1 -1
- package/web-shell/assets/{diagram-LMA3HP47-C2Wmrpj-.js → diagram-LMA3HP47-DzSluD4F.js} +1 -1
- package/web-shell/assets/{diagram-OG6HWLK6-CtYe6bFx.js → diagram-OG6HWLK6-Cvb6CeXQ.js} +1 -1
- package/web-shell/assets/{erDiagram-TEJ5UH35-DfpHcWLV.js → erDiagram-TEJ5UH35-7eB2Hmov.js} +1 -1
- package/web-shell/assets/{flowDiagram-I6XJVG4X-CAiUhio5.js → flowDiagram-I6XJVG4X-B08iGCw_.js} +1 -1
- package/web-shell/assets/{ganttDiagram-6RSMTGT7-CYc7ezr4.js → ganttDiagram-6RSMTGT7-BgsrCHq-.js} +1 -1
- package/web-shell/assets/{gitGraphDiagram-PVQCEYII-70lbkcRS.js → gitGraphDiagram-PVQCEYII-DQLurQ2i.js} +1 -1
- package/web-shell/assets/{index-Ww86nT9f.js → index-B_1Z0Mgr.js} +1 -1
- package/web-shell/assets/index-Bi1dP2mU.css +5 -0
- package/web-shell/assets/index-DJ-z1Oba.js +1756 -0
- package/web-shell/assets/{infoDiagram-5YYISTIA-C7e8DWPa.js → infoDiagram-5YYISTIA-DXZGO8qB.js} +1 -1
- package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-BYLr2op-.js → ishikawaDiagram-YF4QCWOH-g7gbuH2w.js} +1 -1
- package/web-shell/assets/{journeyDiagram-JHISSGLW-hEibAHKX.js → journeyDiagram-JHISSGLW-euefICGn.js} +1 -1
- package/web-shell/assets/{kanban-definition-UN3LZRKU-F2rt8Uhp.js → kanban-definition-UN3LZRKU-Dws8-mjG.js} +1 -1
- package/web-shell/assets/{linear-DPhyNezd.js → linear-C63F0tyh.js} +1 -1
- package/web-shell/assets/{mermaid.core-ZUtnug0n.js → mermaid.core-fLrhp_4-.js} +5 -5
- package/web-shell/assets/{mindmap-definition-RKZ34NQL-DDWcMoi8.js → mindmap-definition-RKZ34NQL-qRkydfT-.js} +1 -1
- package/web-shell/assets/{pieDiagram-4H26LBE5-CTRKzw1_.js → pieDiagram-4H26LBE5-BZ0wQaKN.js} +1 -1
- package/web-shell/assets/{quadrantDiagram-W4KKPZXB-CAEwYG_z.js → quadrantDiagram-W4KKPZXB-Fjq6GbSZ.js} +1 -1
- package/web-shell/assets/{requirementDiagram-4Y6WPE33-CRjnH9D9.js → requirementDiagram-4Y6WPE33-CqWvjLHU.js} +1 -1
- package/web-shell/assets/{sankeyDiagram-5OEKKPKP-LPQT_sOd.js → sankeyDiagram-5OEKKPKP-VcXX6PVZ.js} +1 -1
- package/web-shell/assets/{sequenceDiagram-3UESZ5HK-Ht2Iqvb0.js → sequenceDiagram-3UESZ5HK-BJSRtxFr.js} +1 -1
- package/web-shell/assets/{stateDiagram-AJRCARHV-CIUdqOXu.js → stateDiagram-AJRCARHV-Dt9zUwmZ.js} +1 -1
- package/web-shell/assets/stateDiagram-v2-BHNVJYJU-CCxQt56w.js +1 -0
- package/web-shell/assets/{timeline-definition-PNZ67QCA-BcilneZF.js → timeline-definition-PNZ67QCA-DQwlNRws.js} +1 -1
- package/web-shell/assets/{vennDiagram-CIIHVFJN-BljwiiH1.js → vennDiagram-CIIHVFJN-DwxjUEtQ.js} +1 -1
- package/web-shell/assets/{wardley-L42UT6IY-DeKjkYjq.js → wardley-L42UT6IY-D3QdMSpJ.js} +1 -1
- package/web-shell/assets/{wardleyDiagram-YWT4CUSO-L3U1H7KG.js → wardleyDiagram-YWT4CUSO-BddN8oK8.js} +1 -1
- package/web-shell/assets/{xychartDiagram-2RQKCTM6-IJnWsgMT.js → xychartDiagram-2RQKCTM6-BUNuBrRJ.js} +1 -1
- package/web-shell/index.html +2 -2
- package/chunks/chunk-4KP2F2TE.js +0 -82
- package/chunks/chunk-HGT6JR3U.js +0 -177
- package/chunks/nonInteractiveCli-ZJYEOO7U.js +0 -148
- package/web-shell/assets/channel-B1cyk94G.js +0 -1
- package/web-shell/assets/classDiagram-4FO5ZUOK-BpBYZoRE.js +0 -1
- package/web-shell/assets/classDiagram-v2-Q7XG4LA2-BpBYZoRE.js +0 -1
- package/web-shell/assets/index-C1k3PDuU.css +0 -5
- package/web-shell/assets/index-DZLIXILO.js +0 -1750
- package/web-shell/assets/insert-DmRbh9Rv.svg +0 -1
- package/web-shell/assets/stateDiagram-v2-BHNVJYJU-BmqqQZ6w.js +0 -1
|
@@ -451,1167 +451,1630 @@ function readId(args) {
|
|
|
451
451
|
}
|
|
452
452
|
__name(readId, "readId");
|
|
453
453
|
|
|
454
|
-
// packages/channels/base/dist/
|
|
455
|
-
init_esbuild_shims();
|
|
456
|
-
|
|
457
|
-
// packages/channels/base/dist/PollingChannelBase.js
|
|
458
|
-
init_esbuild_shims();
|
|
459
|
-
import { createHash as createHash2 } from "node:crypto";
|
|
460
|
-
import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, renameSync as renameSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
461
|
-
import { join as join6 } from "node:path";
|
|
462
|
-
import process3 from "node:process";
|
|
463
|
-
|
|
464
|
-
// packages/channels/base/dist/ChannelBase.js
|
|
454
|
+
// packages/channels/base/dist/AcpBridge.js
|
|
465
455
|
init_esbuild_shims();
|
|
466
|
-
import {
|
|
456
|
+
import { spawn } from "node:child_process";
|
|
467
457
|
import { randomUUID } from "node:crypto";
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
var
|
|
458
|
+
import { Readable, Writable } from "node:stream";
|
|
459
|
+
import { EventEmitter } from "node:events";
|
|
460
|
+
var MID_TURN_QUEUE_DRAIN_METHOD = "craft/drainMidTurnQueue";
|
|
461
|
+
var TODO_STOP_GUARD_CONTINUATION_CLAIM_METHOD = "craft/claimTodoStopGuardContinuation";
|
|
462
|
+
var ACP_EVENT_LOOP_STALL_RESTART_MS = 5 * 60 * 1e3;
|
|
463
|
+
var ACP_START_TIMEOUT_MS = 30 * 1e3;
|
|
464
|
+
var ACP_PERMISSION_RESPONSE_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
465
|
+
var ACP_EVENT_LOOP_STALL_RE = /^\[perf\] acp agent event loop stall: max=(\d+(?:\.\d+)?)ms/m;
|
|
466
|
+
function readAvailableCommandAltNames(raw) {
|
|
467
|
+
if (typeof raw !== "object" || raw === null)
|
|
468
|
+
return void 0;
|
|
469
|
+
const record = raw;
|
|
470
|
+
const meta = record["_meta"];
|
|
471
|
+
const fromMeta = typeof meta === "object" && meta !== null ? meta["altNames"] : void 0;
|
|
472
|
+
const source = Array.isArray(record["altNames"]) ? record["altNames"] : Array.isArray(fromMeta) ? fromMeta : void 0;
|
|
473
|
+
if (!source)
|
|
474
|
+
return void 0;
|
|
475
|
+
const names = source.filter((n) => typeof n === "string");
|
|
476
|
+
return names.length > 0 ? names : void 0;
|
|
477
|
+
}
|
|
478
|
+
__name(readAvailableCommandAltNames, "readAvailableCommandAltNames");
|
|
479
|
+
var AcpBridge = class extends EventEmitter {
|
|
472
480
|
static {
|
|
473
|
-
__name(this, "
|
|
481
|
+
__name(this, "AcpBridge");
|
|
474
482
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
+
child = null;
|
|
484
|
+
connection = null;
|
|
485
|
+
options;
|
|
486
|
+
_availableCommands = [];
|
|
487
|
+
channelLoopMcpServer;
|
|
488
|
+
channelLoopToolHandlers = [];
|
|
489
|
+
knownSessionIds = /* @__PURE__ */ new Set();
|
|
490
|
+
sessionBindingTokens = /* @__PURE__ */ new Map();
|
|
491
|
+
channelLoopMcpRegistered = false;
|
|
492
|
+
channelLoopMcpRegistration = null;
|
|
493
|
+
pendingPermissions = /* @__PURE__ */ new Map();
|
|
494
|
+
constructor(options) {
|
|
495
|
+
super();
|
|
496
|
+
this.options = options;
|
|
483
497
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
this.
|
|
489
|
-
|
|
490
|
-
|
|
498
|
+
get availableCommands() {
|
|
499
|
+
return this._availableCommands;
|
|
500
|
+
}
|
|
501
|
+
async start() {
|
|
502
|
+
const { cliEntryPath, cwd } = this.options;
|
|
503
|
+
const args = [
|
|
504
|
+
...process.execArgv.filter((a) => !/^--inspect(-brk)?($|=)/.test(a)),
|
|
505
|
+
cliEntryPath,
|
|
506
|
+
"--acp"
|
|
507
|
+
];
|
|
508
|
+
if (this.options.model) {
|
|
509
|
+
args.push("--model", this.options.model);
|
|
510
|
+
}
|
|
511
|
+
this.child = spawn(process.execPath, args, {
|
|
512
|
+
cwd,
|
|
513
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
514
|
+
env: { ...process.env, QWEN_CODE_DISABLE_CRON: "1" },
|
|
515
|
+
shell: false
|
|
516
|
+
});
|
|
517
|
+
this.child.stderr?.on("data", (data) => {
|
|
518
|
+
const msg = data.toString().trim();
|
|
519
|
+
if (msg) {
|
|
520
|
+
process.stderr.write(`[AcpBridge] ${sanitizeLogText(msg, 4096)}
|
|
521
|
+
`);
|
|
522
|
+
this.maybeKillOnEventLoopStall(msg);
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
this.child.on("exit", (code, signal) => {
|
|
526
|
+
process.stderr.write(`[AcpBridge] Process exited (code=${code}, signal=${signal})
|
|
527
|
+
`);
|
|
528
|
+
this.resolvePendingPermissions();
|
|
529
|
+
this.knownSessionIds.clear();
|
|
530
|
+
this.sessionBindingTokens.clear();
|
|
531
|
+
this.connection = null;
|
|
532
|
+
this.child = null;
|
|
533
|
+
this.emit("disconnected", code, signal);
|
|
534
|
+
});
|
|
535
|
+
await new Promise((resolve3) => setTimeout(resolve3, 1e3));
|
|
536
|
+
if (!this.child || this.child.killed) {
|
|
537
|
+
throw new Error("ACP process failed to start");
|
|
538
|
+
}
|
|
539
|
+
const stdout = Readable.toWeb(this.child.stdout);
|
|
540
|
+
const stdin = Writable.toWeb(this.child.stdin);
|
|
541
|
+
const stream = ndJsonStream(stdin, stdout);
|
|
542
|
+
this.connection = new ClientSideConnection(() => ({
|
|
543
|
+
sessionUpdate: /* @__PURE__ */ __name((params) => {
|
|
544
|
+
this.handleSessionUpdate(params);
|
|
545
|
+
return Promise.resolve();
|
|
546
|
+
}, "sessionUpdate"),
|
|
547
|
+
requestPermission: /* @__PURE__ */ __name(async (params) => this.requestPermission(params), "requestPermission"),
|
|
548
|
+
extMethod: /* @__PURE__ */ __name(async (method, params) => this.handleExtMethod(method, params), "extMethod"),
|
|
549
|
+
extNotification: /* @__PURE__ */ __name(async () => {
|
|
550
|
+
}, "extNotification")
|
|
551
|
+
}), stream);
|
|
552
|
+
try {
|
|
553
|
+
await withTimeout(this.connection.initialize({
|
|
554
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
555
|
+
clientCapabilities: {}
|
|
556
|
+
}), ACP_START_TIMEOUT_MS, `ACP initialization timed out after ${ACP_START_TIMEOUT_MS}ms`);
|
|
557
|
+
await this.registerChannelLoopMcpServer();
|
|
558
|
+
} catch (error) {
|
|
559
|
+
this.stop();
|
|
560
|
+
throw error;
|
|
491
561
|
}
|
|
492
562
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
if (this.buffer.length > 0) {
|
|
497
|
-
this.emitBlock(this.buffer);
|
|
498
|
-
this.buffer = "";
|
|
563
|
+
registerChannelLoopToolHandler(handler) {
|
|
564
|
+
if (!this.channelLoopToolHandlers.includes(handler)) {
|
|
565
|
+
this.channelLoopToolHandlers.push(handler);
|
|
499
566
|
}
|
|
500
|
-
|
|
567
|
+
this.channelLoopMcpServer ??= new ChannelLoopMcpServer({
|
|
568
|
+
create: /* @__PURE__ */ __name((sessionId, input) => this.resolveChannelLoopToolHandler(sessionId).create(sessionId, input), "create"),
|
|
569
|
+
list: /* @__PURE__ */ __name((sessionId) => this.resolveChannelLoopToolHandler(sessionId).list(sessionId), "list"),
|
|
570
|
+
cancel: /* @__PURE__ */ __name((sessionId, id) => this.resolveChannelLoopToolHandler(sessionId).cancel(sessionId, id), "cancel")
|
|
571
|
+
});
|
|
572
|
+
void this.registerChannelLoopMcpServer();
|
|
501
573
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
this.
|
|
505
|
-
|
|
574
|
+
async newSession(cwd, _options, bindingToken) {
|
|
575
|
+
const conn = this.ensureConnection();
|
|
576
|
+
await this.registerChannelLoopMcpServer();
|
|
577
|
+
const response = await conn.newSession({ cwd, mcpServers: [] });
|
|
578
|
+
this.knownSessionIds.add(response.sessionId);
|
|
579
|
+
this.sessionBindingTokens.set(response.sessionId, bindingToken);
|
|
580
|
+
return response.sessionId;
|
|
506
581
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
582
|
+
async loadSession(sessionId, cwd, _options, bindingToken) {
|
|
583
|
+
const conn = this.ensureConnection();
|
|
584
|
+
await this.registerChannelLoopMcpServer();
|
|
585
|
+
await conn.loadSession({
|
|
586
|
+
sessionId,
|
|
587
|
+
cwd,
|
|
588
|
+
mcpServers: []
|
|
589
|
+
});
|
|
590
|
+
this.knownSessionIds.add(sessionId);
|
|
591
|
+
this.sessionBindingTokens.set(sessionId, bindingToken);
|
|
592
|
+
return sessionId;
|
|
593
|
+
}
|
|
594
|
+
async prompt(sessionId, text, options) {
|
|
595
|
+
const conn = this.ensureConnection();
|
|
596
|
+
const chunks = [];
|
|
597
|
+
let slashCommandOutput = "";
|
|
598
|
+
const onChunk = /* @__PURE__ */ __name((sid, chunk) => {
|
|
599
|
+
if (sid === sessionId)
|
|
600
|
+
chunks.push(chunk);
|
|
601
|
+
}, "onChunk");
|
|
602
|
+
const onSlashCommandOutput = /* @__PURE__ */ __name((sid, chunk) => {
|
|
603
|
+
if (sid === sessionId)
|
|
604
|
+
slashCommandOutput = chunk;
|
|
605
|
+
}, "onSlashCommandOutput");
|
|
606
|
+
const clearChunks = /* @__PURE__ */ __name((sid) => {
|
|
607
|
+
if (sid === sessionId) {
|
|
608
|
+
chunks.length = 0;
|
|
609
|
+
slashCommandOutput = "";
|
|
521
610
|
}
|
|
611
|
+
}, "clearChunks");
|
|
612
|
+
this.on("textChunk", onChunk);
|
|
613
|
+
this.on("slashCommandOutput", onSlashCommandOutput);
|
|
614
|
+
this.on("responseBoundary", clearChunks);
|
|
615
|
+
const prompt = [];
|
|
616
|
+
if (options?.imageBase64 && options.imageMimeType) {
|
|
617
|
+
prompt.push({
|
|
618
|
+
type: "image",
|
|
619
|
+
data: options.imageBase64,
|
|
620
|
+
mimeType: options.imageMimeType
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
prompt.push({ type: "text", text });
|
|
624
|
+
try {
|
|
625
|
+
await conn.prompt({
|
|
626
|
+
sessionId,
|
|
627
|
+
prompt
|
|
628
|
+
});
|
|
629
|
+
} finally {
|
|
630
|
+
this.off("textChunk", onChunk);
|
|
631
|
+
this.off("slashCommandOutput", onSlashCommandOutput);
|
|
632
|
+
this.off("responseBoundary", clearChunks);
|
|
522
633
|
}
|
|
634
|
+
return chunks.join("") || slashCommandOutput;
|
|
523
635
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
636
|
+
async cancelSession(sessionId) {
|
|
637
|
+
const conn = this.ensureConnection();
|
|
638
|
+
try {
|
|
639
|
+
await conn.cancel({ sessionId });
|
|
640
|
+
} finally {
|
|
641
|
+
this.resolvePendingPermissions(sessionId);
|
|
529
642
|
}
|
|
530
643
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
if (!trimmed)
|
|
644
|
+
async discardSession(sessionId, expectedBindingToken) {
|
|
645
|
+
if (expectedBindingToken !== void 0 && this.sessionBindingTokens.get(sessionId) !== expectedBindingToken) {
|
|
534
646
|
return;
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
647
|
+
}
|
|
648
|
+
if (!this.knownSessionIds.delete(sessionId))
|
|
649
|
+
return;
|
|
650
|
+
this.sessionBindingTokens.delete(sessionId);
|
|
651
|
+
this.resolvePendingPermissions(sessionId);
|
|
652
|
+
const conn = this.connection;
|
|
653
|
+
if (!conn || !this.isConnected)
|
|
654
|
+
return;
|
|
655
|
+
await conn.extMethod("qwen/control/session/close", { sessionId });
|
|
538
656
|
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
*/
|
|
544
|
-
findBlockBoundary(text) {
|
|
545
|
-
const last = text.lastIndexOf("\n\n");
|
|
546
|
-
if (last < 0 || last < this.opts.minChars)
|
|
547
|
-
return -1;
|
|
548
|
-
return last + 2;
|
|
549
|
-
}
|
|
550
|
-
/**
|
|
551
|
-
* Find the best break point at or before maxPos.
|
|
552
|
-
* Prefers paragraph break > newline > space > maxPos.
|
|
553
|
-
*/
|
|
554
|
-
findBreakPoint(text, maxPos) {
|
|
555
|
-
const sub = text.slice(0, maxPos);
|
|
556
|
-
const para = sub.lastIndexOf("\n\n");
|
|
557
|
-
if (para > 0)
|
|
558
|
-
return para + 2;
|
|
559
|
-
const nl = sub.lastIndexOf("\n");
|
|
560
|
-
if (nl > 0)
|
|
561
|
-
return nl + 1;
|
|
562
|
-
const sp = sub.lastIndexOf(" ");
|
|
563
|
-
if (sp > 0)
|
|
564
|
-
return sp + 1;
|
|
565
|
-
return maxPos;
|
|
566
|
-
}
|
|
567
|
-
clearIdleTimer() {
|
|
568
|
-
if (this.idleTimer !== null) {
|
|
569
|
-
clearTimeout(this.idleTimer);
|
|
570
|
-
this.idleTimer = null;
|
|
657
|
+
async respondToPermission(requestId, response) {
|
|
658
|
+
const pending = this.pendingPermissions.get(requestId);
|
|
659
|
+
if (!pending) {
|
|
660
|
+
return false;
|
|
571
661
|
}
|
|
662
|
+
clearTimeout(pending.timeout);
|
|
663
|
+
this.pendingPermissions.delete(requestId);
|
|
664
|
+
pending.resolve(response);
|
|
665
|
+
this.emit("permissionResolved", {
|
|
666
|
+
requestId,
|
|
667
|
+
outcome: response.outcome
|
|
668
|
+
});
|
|
669
|
+
return true;
|
|
572
670
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
disposition;
|
|
583
|
-
code = CHANNEL_PROACTIVE_DELIVERY_ERROR_CODE;
|
|
584
|
-
constructor(disposition, message, options) {
|
|
585
|
-
super(message, options);
|
|
586
|
-
this.disposition = disposition;
|
|
587
|
-
this.name = "ChannelProactiveDeliveryError";
|
|
588
|
-
}
|
|
589
|
-
};
|
|
590
|
-
function isChannelProactiveDeliveryError(error) {
|
|
591
|
-
if (typeof error !== "object" || error === null)
|
|
592
|
-
return false;
|
|
593
|
-
const candidate = error;
|
|
594
|
-
return candidate.code === CHANNEL_PROACTIVE_DELIVERY_ERROR_CODE && (candidate.disposition === "permanent" || candidate.disposition === "transient") && typeof candidate.message === "string";
|
|
595
|
-
}
|
|
596
|
-
__name(isChannelProactiveDeliveryError, "isChannelProactiveDeliveryError");
|
|
597
|
-
|
|
598
|
-
// packages/channels/base/dist/GroupGate.js
|
|
599
|
-
init_esbuild_shims();
|
|
600
|
-
var GroupGate = class {
|
|
601
|
-
static {
|
|
602
|
-
__name(this, "GroupGate");
|
|
671
|
+
stop() {
|
|
672
|
+
this.resolvePendingPermissions();
|
|
673
|
+
this.knownSessionIds.clear();
|
|
674
|
+
this.sessionBindingTokens.clear();
|
|
675
|
+
if (this.child) {
|
|
676
|
+
this.child.kill();
|
|
677
|
+
this.child = null;
|
|
678
|
+
}
|
|
679
|
+
this.connection = null;
|
|
603
680
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
constructor(policy = "disabled", groups = {}) {
|
|
607
|
-
this.policy = policy;
|
|
608
|
-
this.groups = groups;
|
|
681
|
+
get isConnected() {
|
|
682
|
+
return this.child !== null && !this.child.killed && this.child.exitCode === null;
|
|
609
683
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
684
|
+
handleSessionUpdate(params) {
|
|
685
|
+
const { sessionId } = params;
|
|
686
|
+
const update = params["update"];
|
|
687
|
+
if (!update)
|
|
688
|
+
return;
|
|
689
|
+
const type = update["sessionUpdate"];
|
|
690
|
+
switch (type) {
|
|
691
|
+
case "agent_message_chunk": {
|
|
692
|
+
const meta = update["_meta"];
|
|
693
|
+
if (typeof meta?.["parentToolCallId"] === "string") {
|
|
694
|
+
break;
|
|
695
|
+
}
|
|
696
|
+
const content = update["content"];
|
|
697
|
+
if (meta?.["qwenDiscreteMessage"] === true) {
|
|
698
|
+
if (meta["source"] === "background_notification_response" && meta["rewritten"] !== true && content?.type === "text" && content.text) {
|
|
699
|
+
this.emit("backgroundResponse", sessionId, content.text);
|
|
700
|
+
}
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
if (content?.type === "text" && content.text) {
|
|
704
|
+
this.emit(meta?.["source"] === "slash_command" ? "slashCommandOutput" : "textChunk", sessionId, content.text);
|
|
705
|
+
}
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
708
|
+
case "tool_call": {
|
|
709
|
+
const event = {
|
|
710
|
+
sessionId,
|
|
711
|
+
toolCallId: update["toolCallId"],
|
|
712
|
+
kind: update["kind"] || "",
|
|
713
|
+
title: update["title"] || "",
|
|
714
|
+
status: update["status"] || "pending",
|
|
715
|
+
rawInput: update["rawInput"]
|
|
716
|
+
};
|
|
717
|
+
if (event.status === "pending" || event.status === "in_progress") {
|
|
718
|
+
this.emitResponseBoundary(sessionId);
|
|
719
|
+
}
|
|
720
|
+
this.emit("toolCall", event);
|
|
721
|
+
break;
|
|
722
|
+
}
|
|
723
|
+
case "plan": {
|
|
724
|
+
this.emitResponseBoundary(sessionId);
|
|
725
|
+
break;
|
|
726
|
+
}
|
|
727
|
+
case "available_commands_update": {
|
|
728
|
+
if (Array.isArray(update["availableCommands"])) {
|
|
729
|
+
this._availableCommands = update["availableCommands"].map((cmd) => {
|
|
730
|
+
const altNames = readAvailableCommandAltNames(cmd);
|
|
731
|
+
return altNames ? { ...cmd, altNames } : cmd;
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
break;
|
|
630
735
|
}
|
|
736
|
+
default:
|
|
737
|
+
break;
|
|
631
738
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
739
|
+
this.emit("sessionUpdate", params);
|
|
740
|
+
}
|
|
741
|
+
ensureConnection() {
|
|
742
|
+
if (!this.connection || !this.isConnected) {
|
|
743
|
+
throw new Error("Not connected to ACP agent");
|
|
636
744
|
}
|
|
637
|
-
return
|
|
745
|
+
return this.connection;
|
|
638
746
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
747
|
+
requestPermission(request) {
|
|
748
|
+
const requestId = `acp-permission-${randomUUID()}`;
|
|
749
|
+
const sessionId = typeof request.sessionId === "string" && request.sessionId.length > 0 ? request.sessionId : request.toolCall.toolCallId;
|
|
750
|
+
return new Promise((resolve3) => {
|
|
751
|
+
const timeout = setTimeout(() => {
|
|
752
|
+
const pending = this.pendingPermissions.get(requestId);
|
|
753
|
+
if (!pending) {
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
process.stderr.write(`[AcpBridge] permission request ${sanitizeLogText(requestId, 128)} timed out after ${ACP_PERMISSION_RESPONSE_TIMEOUT_MS}ms (session=${sanitizeLogText(pending.sessionId, 128)})
|
|
757
|
+
`);
|
|
758
|
+
this.pendingPermissions.delete(requestId);
|
|
759
|
+
const response = {
|
|
760
|
+
outcome: { outcome: "cancelled" }
|
|
761
|
+
};
|
|
762
|
+
pending.resolve(response);
|
|
763
|
+
this.emit("permissionResolved", {
|
|
764
|
+
requestId,
|
|
765
|
+
outcome: response.outcome
|
|
766
|
+
});
|
|
767
|
+
}, ACP_PERMISSION_RESPONSE_TIMEOUT_MS);
|
|
768
|
+
timeout.unref?.();
|
|
769
|
+
this.pendingPermissions.set(requestId, { sessionId, resolve: resolve3, timeout });
|
|
770
|
+
this.emitResponseBoundary(sessionId);
|
|
771
|
+
this.emit("permissionRequest", {
|
|
772
|
+
requestId,
|
|
773
|
+
sessionId,
|
|
774
|
+
request
|
|
775
|
+
});
|
|
776
|
+
});
|
|
646
777
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
this.policy = policy;
|
|
778
|
+
emitResponseBoundary(sessionId) {
|
|
779
|
+
this.emit("responseBoundary", sessionId);
|
|
650
780
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
781
|
+
resolvePendingPermissions(sessionId) {
|
|
782
|
+
const response = {
|
|
783
|
+
outcome: { outcome: "cancelled" }
|
|
784
|
+
};
|
|
785
|
+
for (const [requestId, pending] of this.pendingPermissions) {
|
|
786
|
+
if (sessionId !== void 0 && pending.sessionId !== sessionId) {
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
clearTimeout(pending.timeout);
|
|
790
|
+
this.pendingPermissions.delete(requestId);
|
|
791
|
+
pending.resolve(response);
|
|
792
|
+
this.emit("permissionResolved", {
|
|
793
|
+
requestId,
|
|
794
|
+
outcome: response.outcome
|
|
795
|
+
});
|
|
663
796
|
}
|
|
664
|
-
|
|
665
|
-
|
|
797
|
+
}
|
|
798
|
+
maybeKillOnEventLoopStall(stderr) {
|
|
799
|
+
const match = ACP_EVENT_LOOP_STALL_RE.exec(stderr);
|
|
800
|
+
if (!match)
|
|
801
|
+
return;
|
|
802
|
+
const maxMs = Number(match[1]);
|
|
803
|
+
if (!Number.isFinite(maxMs) || maxMs < ACP_EVENT_LOOP_STALL_RESTART_MS) {
|
|
804
|
+
return;
|
|
666
805
|
}
|
|
667
|
-
|
|
806
|
+
const child = this.child;
|
|
807
|
+
if (!child || child.killed || child.exitCode !== null) {
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
process.stderr.write(`[AcpBridge] ACP agent event loop stalled for ${Math.round(maxMs)}ms; killing child process to trigger restart
|
|
811
|
+
`);
|
|
812
|
+
child.kill("SIGKILL");
|
|
668
813
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
// packages/channels/base/dist/group-history-store.js
|
|
672
|
-
init_esbuild_shims();
|
|
673
|
-
import { appendFileSync, chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
674
|
-
import { dirname as dirname2, join as join3 } from "node:path";
|
|
675
|
-
var DEFAULT_MAX_KEYS = 1e3;
|
|
676
|
-
var DEFAULT_COMPACT_AFTER_RECORDS = 1e3;
|
|
677
|
-
var GroupHistoryStore = class {
|
|
678
|
-
static {
|
|
679
|
-
__name(this, "GroupHistoryStore");
|
|
680
|
-
}
|
|
681
|
-
filePath;
|
|
682
|
-
maxKeys;
|
|
683
|
-
compactAfterRecords;
|
|
684
|
-
constructor(filePath, options = {}) {
|
|
685
|
-
this.filePath = filePath;
|
|
686
|
-
this.maxKeys = options.maxKeys ?? DEFAULT_MAX_KEYS;
|
|
687
|
-
this.compactAfterRecords = options.compactAfterRecords ?? DEFAULT_COMPACT_AFTER_RECORDS;
|
|
688
|
-
}
|
|
689
|
-
record(key, entry, limit) {
|
|
690
|
-
const normalizedLimit = normalizeLimit(limit);
|
|
691
|
-
if (normalizedLimit <= 0) {
|
|
814
|
+
async registerChannelLoopMcpServer() {
|
|
815
|
+
if (!this.connection || !this.channelLoopMcpServer || this.channelLoopMcpRegistered) {
|
|
692
816
|
return;
|
|
693
817
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
const limits = loaded.limits;
|
|
697
|
-
const current = state.get(key) ?? [];
|
|
698
|
-
current.push(entry);
|
|
699
|
-
if (current.length > normalizedLimit) {
|
|
700
|
-
current.splice(0, current.length - normalizedLimit);
|
|
701
|
-
}
|
|
702
|
-
state.delete(key);
|
|
703
|
-
state.set(key, current);
|
|
704
|
-
limits.set(key, normalizedLimit);
|
|
705
|
-
const evicted = evictOldKeys(state, this.maxKeys, limits);
|
|
706
|
-
this.append({
|
|
707
|
-
type: "message",
|
|
708
|
-
key,
|
|
709
|
-
limit: normalizedLimit,
|
|
710
|
-
entry,
|
|
711
|
-
recordedAt: Date.now()
|
|
712
|
-
});
|
|
713
|
-
if (evicted || loaded.hadInvalidRecords || loaded.recordCount + 1 >= this.compactAfterRecords) {
|
|
714
|
-
this.compact(state, limits);
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
drain(key, limit) {
|
|
718
|
-
const normalizedLimit = normalizeLimit(limit);
|
|
719
|
-
const loaded = this.loadState();
|
|
720
|
-
const state = loaded.entries;
|
|
721
|
-
const entries = normalizedLimit > 0 ? (state.get(key) ?? []).slice(-normalizedLimit) : [];
|
|
722
|
-
if (state.has(key)) {
|
|
723
|
-
state.delete(key);
|
|
724
|
-
loaded.limits.delete(key);
|
|
725
|
-
this.append({ type: "clear", key, recordedAt: Date.now() });
|
|
726
|
-
}
|
|
727
|
-
return entries;
|
|
728
|
-
}
|
|
729
|
-
clear(key) {
|
|
730
|
-
const loaded = this.loadState();
|
|
731
|
-
const state = loaded.entries;
|
|
732
|
-
if (!state.has(key)) {
|
|
818
|
+
if (this.channelLoopMcpRegistration) {
|
|
819
|
+
await this.channelLoopMcpRegistration;
|
|
733
820
|
return;
|
|
734
821
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
822
|
+
this.channelLoopMcpRegistration = this.connection.extMethod(WORKSPACE_MCP_RUNTIME_ADD_METHOD, {
|
|
823
|
+
name: CHANNEL_LOOP_MCP_SERVER_NAME,
|
|
824
|
+
originatorClientId: "channel",
|
|
825
|
+
config: {
|
|
826
|
+
type: "sdk",
|
|
827
|
+
[CLIENT_MCP_OVER_WS_CONFIG_FLAG]: true
|
|
828
|
+
}
|
|
829
|
+
}).then((result) => {
|
|
830
|
+
if (isSkippedMcpRegistration(result)) {
|
|
831
|
+
this.channelLoopMcpRegistered = false;
|
|
832
|
+
process.stderr.write(`[AcpBridge] Channel loop MCP server registration skipped${formatSkippedRegistrationReason(result)}
|
|
833
|
+
`);
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
this.channelLoopMcpRegistered = true;
|
|
837
|
+
}).catch((error) => {
|
|
838
|
+
this.channelLoopMcpRegistered = false;
|
|
839
|
+
process.stderr.write(`[AcpBridge] Failed to register channel loop MCP server: ${error instanceof Error ? error.message : String(error)}
|
|
840
|
+
`);
|
|
841
|
+
}).finally(() => {
|
|
842
|
+
this.channelLoopMcpRegistration = null;
|
|
843
|
+
});
|
|
844
|
+
await this.channelLoopMcpRegistration;
|
|
739
845
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
return;
|
|
846
|
+
async handleExtMethod(method, params) {
|
|
847
|
+
if (method === CLIENT_MCP_MESSAGE_METHOD) {
|
|
848
|
+
return this.handleClientMcpMessage(params);
|
|
744
849
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
this.append({ type: "clear", key, recordedAt });
|
|
850
|
+
if (method === MID_TURN_QUEUE_DRAIN_METHOD) {
|
|
851
|
+
return { messages: [], hasQueuedPrompt: false };
|
|
748
852
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
853
|
+
if (method === TODO_STOP_GUARD_CONTINUATION_CLAIM_METHOD) {
|
|
854
|
+
const sessionId = typeof params["sessionId"] === "string" ? params["sessionId"] : "";
|
|
855
|
+
return {
|
|
856
|
+
claimed: this.knownSessionIds.has(sessionId),
|
|
857
|
+
hasQueuedPrompt: false
|
|
858
|
+
};
|
|
755
859
|
}
|
|
756
|
-
|
|
860
|
+
throw new Error(`Method not found: ${method}`);
|
|
757
861
|
}
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
const read = this.readRecords();
|
|
762
|
-
for (const record of read.records) {
|
|
763
|
-
if (record.type === "clear") {
|
|
764
|
-
state.delete(record.key);
|
|
765
|
-
limits.delete(record.key);
|
|
766
|
-
continue;
|
|
767
|
-
}
|
|
768
|
-
const current = state.get(record.key) ?? [];
|
|
769
|
-
current.push(record.entry);
|
|
770
|
-
if (current.length > record.limit) {
|
|
771
|
-
current.splice(0, current.length - record.limit);
|
|
772
|
-
}
|
|
773
|
-
state.delete(record.key);
|
|
774
|
-
state.set(record.key, current);
|
|
775
|
-
limits.set(record.key, record.limit);
|
|
776
|
-
evictOldKeys(state, this.maxKeys, limits);
|
|
862
|
+
async handleClientMcpMessage(params) {
|
|
863
|
+
if (!this.channelLoopMcpServer) {
|
|
864
|
+
throw new Error("Channel loop MCP server is not registered.");
|
|
777
865
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
recordCount: read.records.length,
|
|
782
|
-
hadInvalidRecords: read.hadInvalidRecords
|
|
783
|
-
};
|
|
784
|
-
}
|
|
785
|
-
readRecords() {
|
|
786
|
-
if (!existsSync2(this.filePath)) {
|
|
787
|
-
return { records: [], hadInvalidRecords: false };
|
|
866
|
+
const server = params["server"];
|
|
867
|
+
if (server !== CHANNEL_LOOP_MCP_SERVER_NAME) {
|
|
868
|
+
throw new Error(`Unknown client MCP server: ${String(server)}`);
|
|
788
869
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
} catch (err) {
|
|
793
|
-
if (isErrnoCode(err, "ENOENT")) {
|
|
794
|
-
return { records: [], hadInvalidRecords: false };
|
|
795
|
-
}
|
|
796
|
-
throw err;
|
|
870
|
+
const payload = params["payload"];
|
|
871
|
+
if (typeof payload !== "object" || payload === null) {
|
|
872
|
+
throw new Error("Invalid client MCP payload.");
|
|
797
873
|
}
|
|
798
|
-
const
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
continue;
|
|
803
|
-
}
|
|
804
|
-
try {
|
|
805
|
-
const parsed = JSON.parse(line);
|
|
806
|
-
if (isGroupHistoryRecord(parsed)) {
|
|
807
|
-
records.push(parsed);
|
|
808
|
-
} else {
|
|
809
|
-
hadInvalidRecords = true;
|
|
810
|
-
}
|
|
811
|
-
} catch {
|
|
812
|
-
hadInvalidRecords = true;
|
|
813
|
-
}
|
|
874
|
+
const sessionId = typeof params["sessionId"] === "string" ? params["sessionId"] : void 0;
|
|
875
|
+
const response = await this.channelLoopMcpServer.handleMessage(payload, { sessionId });
|
|
876
|
+
if (!response) {
|
|
877
|
+
return { payload: { jsonrpc: "2.0", id: 0, result: {} } };
|
|
814
878
|
}
|
|
815
|
-
return {
|
|
816
|
-
}
|
|
817
|
-
append(record) {
|
|
818
|
-
const dir = dirname2(this.filePath);
|
|
819
|
-
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
820
|
-
chmodPrivate(dir, 448);
|
|
821
|
-
appendFileSync(this.filePath, `${JSON.stringify(record)}
|
|
822
|
-
`, {
|
|
823
|
-
encoding: "utf-8",
|
|
824
|
-
mode: 384
|
|
825
|
-
});
|
|
826
|
-
chmodPrivate(this.filePath, 384);
|
|
879
|
+
return { payload: response };
|
|
827
880
|
}
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
chmodPrivate(dir, 448);
|
|
832
|
-
const records = [];
|
|
833
|
-
const recordedAt = Date.now();
|
|
834
|
-
for (const [key, entries] of state) {
|
|
835
|
-
for (const entry of entries) {
|
|
836
|
-
records.push({
|
|
837
|
-
type: "message",
|
|
838
|
-
key,
|
|
839
|
-
limit: limits.get(key) ?? entries.length,
|
|
840
|
-
entry,
|
|
841
|
-
recordedAt
|
|
842
|
-
});
|
|
843
|
-
}
|
|
881
|
+
resolveChannelLoopToolHandler(sessionId) {
|
|
882
|
+
if (this.channelLoopToolHandlers.length === 1 && !this.channelLoopToolHandlers[0].canHandle) {
|
|
883
|
+
return this.channelLoopToolHandlers[0];
|
|
844
884
|
}
|
|
845
|
-
const
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
chmodPrivate(tempPath, 384);
|
|
850
|
-
renameSync2(tempPath, this.filePath);
|
|
851
|
-
chmodPrivate(this.filePath, 384);
|
|
885
|
+
const handler = this.channelLoopToolHandlers.find((candidate) => candidate.canHandle?.(sessionId) === true);
|
|
886
|
+
if (handler)
|
|
887
|
+
return handler;
|
|
888
|
+
throw new Error(this.channelLoopToolHandlers.length === 0 ? "No channel loop tool handler is registered." : `No channel loop handler matched session ${sessionId}.`);
|
|
852
889
|
}
|
|
853
890
|
};
|
|
854
|
-
function
|
|
855
|
-
|
|
856
|
-
|
|
891
|
+
async function withTimeout(operation, timeoutMs, message) {
|
|
892
|
+
let timer;
|
|
893
|
+
const timeout = new Promise((_, reject) => {
|
|
894
|
+
timer = setTimeout(() => reject(new Error(message)), timeoutMs);
|
|
895
|
+
timer.unref?.();
|
|
896
|
+
});
|
|
897
|
+
try {
|
|
898
|
+
return await Promise.race([operation, timeout]);
|
|
899
|
+
} finally {
|
|
900
|
+
if (timer)
|
|
901
|
+
clearTimeout(timer);
|
|
857
902
|
}
|
|
858
|
-
return Math.floor(limit);
|
|
859
903
|
}
|
|
860
|
-
__name(
|
|
861
|
-
function
|
|
862
|
-
|
|
863
|
-
while (state.size > maxKeys) {
|
|
864
|
-
const oldest = state.keys().next().value;
|
|
865
|
-
if (oldest === void 0) {
|
|
866
|
-
return evicted;
|
|
867
|
-
}
|
|
868
|
-
state.delete(oldest);
|
|
869
|
-
limits?.delete(oldest);
|
|
870
|
-
evicted = true;
|
|
871
|
-
}
|
|
872
|
-
return evicted;
|
|
904
|
+
__name(withTimeout, "withTimeout");
|
|
905
|
+
function isSkippedMcpRegistration(result) {
|
|
906
|
+
return typeof result === "object" && result !== null && result.skipped === true;
|
|
873
907
|
}
|
|
874
|
-
__name(
|
|
875
|
-
function
|
|
876
|
-
if (
|
|
877
|
-
return
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
return typeof value["key"] === "string";
|
|
881
|
-
}
|
|
882
|
-
if (value["type"] !== "message") {
|
|
883
|
-
return false;
|
|
884
|
-
}
|
|
885
|
-
return typeof value["key"] === "string" && typeof value["limit"] === "number" && isGroupHistoryEntry(value["entry"]);
|
|
886
|
-
}
|
|
887
|
-
__name(isGroupHistoryRecord, "isGroupHistoryRecord");
|
|
888
|
-
function isGroupHistoryEntry(value) {
|
|
889
|
-
return isRecord(value) && typeof value["senderId"] === "string" && typeof value["senderName"] === "string" && typeof value["text"] === "string" && typeof value["timestamp"] === "number";
|
|
890
|
-
}
|
|
891
|
-
__name(isGroupHistoryEntry, "isGroupHistoryEntry");
|
|
892
|
-
function isRecord(value) {
|
|
893
|
-
return typeof value === "object" && value !== null;
|
|
894
|
-
}
|
|
895
|
-
__name(isRecord, "isRecord");
|
|
896
|
-
function isErrnoCode(err, code) {
|
|
897
|
-
return err instanceof Error && "code" in err && err.code === code;
|
|
898
|
-
}
|
|
899
|
-
__name(isErrnoCode, "isErrnoCode");
|
|
900
|
-
function chmodPrivate(path4, mode) {
|
|
901
|
-
try {
|
|
902
|
-
chmodSync(path4, mode);
|
|
903
|
-
} catch {
|
|
904
|
-
}
|
|
908
|
+
__name(isSkippedMcpRegistration, "isSkippedMcpRegistration");
|
|
909
|
+
function formatSkippedRegistrationReason(result) {
|
|
910
|
+
if (typeof result !== "object" || result === null)
|
|
911
|
+
return ".";
|
|
912
|
+
const reason = result.reason;
|
|
913
|
+
return typeof reason === "string" && reason.length > 0 ? `: ${sanitizeLogText(reason, 256)}` : ".";
|
|
905
914
|
}
|
|
906
|
-
__name(
|
|
915
|
+
__name(formatSkippedRegistrationReason, "formatSkippedRegistrationReason");
|
|
907
916
|
|
|
908
|
-
// packages/channels/base/dist/
|
|
917
|
+
// packages/channels/base/dist/index.js
|
|
909
918
|
init_esbuild_shims();
|
|
910
|
-
|
|
919
|
+
|
|
920
|
+
// packages/channels/base/dist/PollingChannelBase.js
|
|
921
|
+
init_esbuild_shims();
|
|
922
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
923
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, renameSync as renameSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
924
|
+
import { join as join6 } from "node:path";
|
|
925
|
+
import process3 from "node:process";
|
|
926
|
+
|
|
927
|
+
// packages/channels/base/dist/ChannelBase.js
|
|
928
|
+
init_esbuild_shims();
|
|
929
|
+
import { basename as basename3, join as join5 } from "node:path";
|
|
930
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
931
|
+
|
|
932
|
+
// packages/channels/base/dist/BlockStreamer.js
|
|
933
|
+
init_esbuild_shims();
|
|
934
|
+
var BlockStreamer = class {
|
|
911
935
|
static {
|
|
912
|
-
__name(this, "
|
|
936
|
+
__name(this, "BlockStreamer");
|
|
913
937
|
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
938
|
+
buffer = "";
|
|
939
|
+
idleTimer = null;
|
|
940
|
+
sending = Promise.resolve();
|
|
941
|
+
opts;
|
|
942
|
+
/** Number of blocks emitted so far. */
|
|
943
|
+
blockCount = 0;
|
|
944
|
+
constructor(opts) {
|
|
945
|
+
this.opts = opts;
|
|
921
946
|
}
|
|
922
|
-
|
|
923
|
-
|
|
947
|
+
/** Feed a new text chunk from the agent stream. */
|
|
948
|
+
push(chunk) {
|
|
949
|
+
this.buffer += chunk;
|
|
950
|
+
this.clearIdleTimer();
|
|
951
|
+
this.checkEmit();
|
|
952
|
+
if (this.buffer.length > 0 && this.opts.idleMs > 0) {
|
|
953
|
+
this.idleTimer = setTimeout(() => this.onIdle(), this.opts.idleMs);
|
|
954
|
+
}
|
|
924
955
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
case "pairing":
|
|
932
|
-
return this.allowedUsers.has(senderId) || this.pairingStore?.isApproved(senderId) === true;
|
|
933
|
-
default:
|
|
934
|
-
throw new Error(`Unknown sender policy: ${this.policy}`);
|
|
956
|
+
/** Flush all remaining buffered text. Awaits all pending sends. */
|
|
957
|
+
async flush() {
|
|
958
|
+
this.clearIdleTimer();
|
|
959
|
+
if (this.buffer.length > 0) {
|
|
960
|
+
this.emitBlock(this.buffer);
|
|
961
|
+
this.buffer = "";
|
|
935
962
|
}
|
|
963
|
+
await this.sending;
|
|
936
964
|
}
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
965
|
+
/** Drop buffered text and cancel future idle emission. */
|
|
966
|
+
stop() {
|
|
967
|
+
this.clearIdleTimer();
|
|
968
|
+
this.buffer = "";
|
|
969
|
+
}
|
|
970
|
+
// ---------------------------------------------------------------------------
|
|
971
|
+
// Internal
|
|
972
|
+
// ---------------------------------------------------------------------------
|
|
973
|
+
checkEmit() {
|
|
974
|
+
while (this.buffer.length >= this.opts.maxChars) {
|
|
975
|
+
const bp = this.findBreakPoint(this.buffer, this.opts.maxChars);
|
|
976
|
+
this.emitBlock(this.buffer.slice(0, bp));
|
|
977
|
+
this.buffer = this.buffer.slice(bp);
|
|
978
|
+
}
|
|
979
|
+
if (this.buffer.length >= this.opts.minChars) {
|
|
980
|
+
const bp = this.findBlockBoundary(this.buffer);
|
|
981
|
+
if (bp > 0) {
|
|
982
|
+
this.emitBlock(this.buffer.slice(0, bp));
|
|
983
|
+
this.buffer = this.buffer.slice(bp);
|
|
952
984
|
}
|
|
953
|
-
default:
|
|
954
|
-
throw new Error(`Unknown sender policy: ${this.policy}`);
|
|
955
985
|
}
|
|
956
986
|
}
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
import process2 from "node:process";
|
|
964
|
-
var SessionRouter = class {
|
|
965
|
-
static {
|
|
966
|
-
__name(this, "SessionRouter");
|
|
987
|
+
onIdle() {
|
|
988
|
+
this.idleTimer = null;
|
|
989
|
+
if (this.buffer.length >= this.opts.minChars) {
|
|
990
|
+
this.emitBlock(this.buffer);
|
|
991
|
+
this.buffer = "";
|
|
992
|
+
}
|
|
967
993
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
sessionLoadWindows = /* @__PURE__ */ new Set();
|
|
976
|
-
liveSessionIds = /* @__PURE__ */ new Set();
|
|
977
|
-
routeTokens = /* @__PURE__ */ new Map();
|
|
978
|
-
lifecycleGeneration = 0;
|
|
979
|
-
bridge;
|
|
980
|
-
defaultCwd;
|
|
981
|
-
defaultScope;
|
|
982
|
-
channelScopes = /* @__PURE__ */ new Map();
|
|
983
|
-
channelApprovalModes = /* @__PURE__ */ new Map();
|
|
984
|
-
persistPath;
|
|
985
|
-
recoveryMode;
|
|
986
|
-
constructor(bridge, defaultCwd, scope = "user", persistPath, options = {}) {
|
|
987
|
-
this.bridge = bridge;
|
|
988
|
-
this.defaultCwd = defaultCwd;
|
|
989
|
-
this.defaultScope = scope;
|
|
990
|
-
this.persistPath = persistPath;
|
|
991
|
-
this.recoveryMode = options.recoveryMode ?? "eager";
|
|
994
|
+
emitBlock(text) {
|
|
995
|
+
const trimmed = text.trim();
|
|
996
|
+
if (!trimmed)
|
|
997
|
+
return;
|
|
998
|
+
this.blockCount++;
|
|
999
|
+
this.sending = this.sending.then(() => this.opts.send(trimmed)).catch(() => {
|
|
1000
|
+
});
|
|
992
1001
|
}
|
|
993
|
-
/**
|
|
994
|
-
|
|
995
|
-
|
|
1002
|
+
/**
|
|
1003
|
+
* Find the last paragraph boundary (\n\n) in the buffer.
|
|
1004
|
+
* Returns the position after the boundary, or -1 if no suitable boundary
|
|
1005
|
+
* exists at or after minChars.
|
|
1006
|
+
*/
|
|
1007
|
+
findBlockBoundary(text) {
|
|
1008
|
+
const last = text.lastIndexOf("\n\n");
|
|
1009
|
+
if (last < 0 || last < this.opts.minChars)
|
|
1010
|
+
return -1;
|
|
1011
|
+
return last + 2;
|
|
996
1012
|
}
|
|
997
|
-
/**
|
|
998
|
-
|
|
999
|
-
|
|
1013
|
+
/**
|
|
1014
|
+
* Find the best break point at or before maxPos.
|
|
1015
|
+
* Prefers paragraph break > newline > space > maxPos.
|
|
1016
|
+
*/
|
|
1017
|
+
findBreakPoint(text, maxPos) {
|
|
1018
|
+
const sub = text.slice(0, maxPos);
|
|
1019
|
+
const para = sub.lastIndexOf("\n\n");
|
|
1020
|
+
if (para > 0)
|
|
1021
|
+
return para + 2;
|
|
1022
|
+
const nl = sub.lastIndexOf("\n");
|
|
1023
|
+
if (nl > 0)
|
|
1024
|
+
return nl + 1;
|
|
1025
|
+
const sp = sub.lastIndexOf(" ");
|
|
1026
|
+
if (sp > 0)
|
|
1027
|
+
return sp + 1;
|
|
1028
|
+
return maxPos;
|
|
1000
1029
|
}
|
|
1001
|
-
|
|
1002
|
-
if (
|
|
1003
|
-
this.
|
|
1004
|
-
|
|
1005
|
-
this.channelApprovalModes.delete(channelName);
|
|
1030
|
+
clearIdleTimer() {
|
|
1031
|
+
if (this.idleTimer !== null) {
|
|
1032
|
+
clearTimeout(this.idleTimer);
|
|
1033
|
+
this.idleTimer = null;
|
|
1006
1034
|
}
|
|
1007
1035
|
}
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
return `${channelName}:__single__`;
|
|
1017
|
-
case "user":
|
|
1018
|
-
default:
|
|
1019
|
-
return `${channelName}:${senderId}:${chatId}`;
|
|
1020
|
-
}
|
|
1036
|
+
};
|
|
1037
|
+
|
|
1038
|
+
// packages/channels/base/dist/ChannelProactiveDeliveryError.js
|
|
1039
|
+
init_esbuild_shims();
|
|
1040
|
+
var CHANNEL_PROACTIVE_DELIVERY_ERROR_CODE = "channel_proactive_delivery_error";
|
|
1041
|
+
var ChannelProactiveDeliveryError = class extends Error {
|
|
1042
|
+
static {
|
|
1043
|
+
__name(this, "ChannelProactiveDeliveryError");
|
|
1021
1044
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1045
|
+
disposition;
|
|
1046
|
+
code = CHANNEL_PROACTIVE_DELIVERY_ERROR_CODE;
|
|
1047
|
+
constructor(disposition, message, options) {
|
|
1048
|
+
super(message, options);
|
|
1049
|
+
this.disposition = disposition;
|
|
1050
|
+
this.name = "ChannelProactiveDeliveryError";
|
|
1025
1051
|
}
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
this.promoteTargetToGroup(existing, isGroup);
|
|
1041
|
-
return existing;
|
|
1042
|
-
}
|
|
1043
|
-
const creating = this.creatingSessions.get(key);
|
|
1044
|
-
if (creating) {
|
|
1045
|
-
try {
|
|
1046
|
-
const sessionId = await creating.promise;
|
|
1047
|
-
try {
|
|
1048
|
-
this.assertOperationResultCurrent(key, sessionId, creating);
|
|
1049
|
-
} catch (error) {
|
|
1050
|
-
this.scheduleDiscardInvalidatedSession(sessionId, creating);
|
|
1051
|
-
throw error;
|
|
1052
|
-
}
|
|
1053
|
-
this.promoteTargetToGroup(sessionId, isGroup);
|
|
1054
|
-
return sessionId;
|
|
1055
|
-
} catch (error) {
|
|
1056
|
-
if (creating.invalidationError) {
|
|
1057
|
-
throw creating.invalidationError;
|
|
1058
|
-
}
|
|
1059
|
-
if (this.creatingSessions.get(key) === creating) {
|
|
1060
|
-
this.creatingSessions.delete(key);
|
|
1061
|
-
}
|
|
1062
|
-
this.releaseRouteToken(key, creating);
|
|
1063
|
-
failedWaits++;
|
|
1064
|
-
if (failedWaits > 3)
|
|
1065
|
-
throw error;
|
|
1066
|
-
continue;
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
const operation = this.createSessionOperation(key, {
|
|
1070
|
-
channelName: input.channelName,
|
|
1071
|
-
senderId: input.senderId,
|
|
1072
|
-
chatId: input.chatId,
|
|
1073
|
-
threadId: input.threadId,
|
|
1074
|
-
isGroup: input.isGroup
|
|
1075
|
-
}, (currentOperation) => existing ? this.loadOrReplaceSession(key, existing, input, currentOperation) : this.createAndStoreSession(key, input, currentOperation));
|
|
1076
|
-
this.creatingSessions.set(key, operation);
|
|
1077
|
-
try {
|
|
1078
|
-
const sessionId = await operation.promise;
|
|
1079
|
-
try {
|
|
1080
|
-
this.assertOperationResultCurrent(key, sessionId, operation);
|
|
1081
|
-
} catch (error) {
|
|
1082
|
-
this.scheduleDiscardInvalidatedSession(sessionId, operation);
|
|
1083
|
-
throw error;
|
|
1084
|
-
}
|
|
1085
|
-
this.promoteTargetToGroup(sessionId, isGroup);
|
|
1086
|
-
return sessionId;
|
|
1087
|
-
} finally {
|
|
1088
|
-
if (this.creatingSessions.get(key) === operation) {
|
|
1089
|
-
this.creatingSessions.delete(key);
|
|
1090
|
-
}
|
|
1091
|
-
this.releaseRouteToken(key, operation);
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1052
|
+
};
|
|
1053
|
+
function isChannelProactiveDeliveryError(error) {
|
|
1054
|
+
if (typeof error !== "object" || error === null)
|
|
1055
|
+
return false;
|
|
1056
|
+
const candidate = error;
|
|
1057
|
+
return candidate.code === CHANNEL_PROACTIVE_DELIVERY_ERROR_CODE && (candidate.disposition === "permanent" || candidate.disposition === "transient") && typeof candidate.message === "string";
|
|
1058
|
+
}
|
|
1059
|
+
__name(isChannelProactiveDeliveryError, "isChannelProactiveDeliveryError");
|
|
1060
|
+
|
|
1061
|
+
// packages/channels/base/dist/GroupGate.js
|
|
1062
|
+
init_esbuild_shims();
|
|
1063
|
+
var GroupGate = class {
|
|
1064
|
+
static {
|
|
1065
|
+
__name(this, "GroupGate");
|
|
1094
1066
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1067
|
+
policy;
|
|
1068
|
+
groups;
|
|
1069
|
+
constructor(policy = "disabled", groups = {}) {
|
|
1070
|
+
this.policy = policy;
|
|
1071
|
+
this.groups = groups;
|
|
1097
1072
|
}
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
senderId: input.senderId,
|
|
1112
|
-
chatId: input.chatId,
|
|
1113
|
-
threadId: input.threadId,
|
|
1114
|
-
isGroup: input.isGroup
|
|
1115
|
-
});
|
|
1116
|
-
this.toCwd.set(sessionId, input.cwd);
|
|
1117
|
-
this.liveSessionIds.add(sessionId);
|
|
1118
|
-
this.persist();
|
|
1119
|
-
return sessionId;
|
|
1120
|
-
} finally {
|
|
1121
|
-
this.endSessionLoad(loadWindow);
|
|
1073
|
+
/**
|
|
1074
|
+
* Full group check: policy + allowlist + mention gating.
|
|
1075
|
+
* Evaluation order:
|
|
1076
|
+
* 1. groupPolicy (disabled → drop)
|
|
1077
|
+
* 2. group allowlist (allowlist mode, no match → drop)
|
|
1078
|
+
* 3. mention gating (requireMention + not mentioned → drop silently)
|
|
1079
|
+
*
|
|
1080
|
+
* Mention gating runs before sender gate so that unmentioned messages
|
|
1081
|
+
* in groups don't trigger pairing flows.
|
|
1082
|
+
*/
|
|
1083
|
+
check(envelope) {
|
|
1084
|
+
if (!envelope.isGroup) {
|
|
1085
|
+
return { allowed: true };
|
|
1122
1086
|
}
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
const loadedSessionId = await this.bridge.loadSession(savedSessionId, savedCwd, this.sessionOptions(input.channelName), operation);
|
|
1130
|
-
try {
|
|
1131
|
-
this.assertOperationCurrent(operation);
|
|
1132
|
-
if (this.toSession.get(key) !== savedSessionId) {
|
|
1133
|
-
this.invalidateOperation(operation);
|
|
1134
|
-
this.assertOperationCurrent(operation);
|
|
1135
|
-
}
|
|
1136
|
-
} catch (error) {
|
|
1137
|
-
this.scheduleDiscardInvalidatedSession(loadedSessionId, operation);
|
|
1138
|
-
throw error;
|
|
1139
|
-
}
|
|
1140
|
-
if (typeof loadedSessionId !== "string" || loadedSessionId.length === 0 || loadWindow.delete(loadedSessionId)) {
|
|
1141
|
-
throw new Error("Invalid or dead restored session ID");
|
|
1142
|
-
}
|
|
1143
|
-
if (loadedSessionId !== savedSessionId) {
|
|
1144
|
-
const target = this.toTarget.get(savedSessionId);
|
|
1145
|
-
this.deleteByKey(key);
|
|
1146
|
-
this.toSession.set(key, loadedSessionId);
|
|
1147
|
-
if (target)
|
|
1148
|
-
this.toTarget.set(loadedSessionId, target);
|
|
1149
|
-
this.toCwd.set(loadedSessionId, savedCwd);
|
|
1150
|
-
this.persist();
|
|
1151
|
-
}
|
|
1152
|
-
this.liveSessionIds.add(loadedSessionId);
|
|
1153
|
-
return loadedSessionId;
|
|
1154
|
-
} catch (loadError) {
|
|
1155
|
-
this.assertOperationCurrent(operation);
|
|
1156
|
-
try {
|
|
1157
|
-
const replacement = await this.createLiveSession(input.cwd, loadWindow, key, this.sessionOptions(input.channelName), operation, input.channelName);
|
|
1158
|
-
try {
|
|
1159
|
-
this.assertOperationCurrent(operation);
|
|
1160
|
-
} catch (error) {
|
|
1161
|
-
this.scheduleDiscardInvalidatedSession(replacement, operation);
|
|
1162
|
-
throw error;
|
|
1163
|
-
}
|
|
1164
|
-
this.deleteByKey(key);
|
|
1165
|
-
this.toSession.set(key, replacement);
|
|
1166
|
-
this.toTarget.set(replacement, {
|
|
1167
|
-
channelName: input.channelName,
|
|
1168
|
-
senderId: input.senderId,
|
|
1169
|
-
chatId: input.chatId,
|
|
1170
|
-
threadId: input.threadId,
|
|
1171
|
-
isGroup: input.isGroup
|
|
1172
|
-
});
|
|
1173
|
-
this.toCwd.set(replacement, input.cwd);
|
|
1174
|
-
this.liveSessionIds.add(replacement);
|
|
1175
|
-
this.persist();
|
|
1176
|
-
process2.stderr.write(`[SessionRouter] Replaced unavailable session ${sanitizeLogText(savedSessionId, 128)} for key ${sanitizeLogText(key, 256)} after load failed: ${sanitizeLogText(loadError instanceof Error ? loadError.message : String(loadError), 512)}
|
|
1177
|
-
`);
|
|
1178
|
-
return replacement;
|
|
1179
|
-
} catch (createError) {
|
|
1180
|
-
this.assertOperationCurrent(operation);
|
|
1181
|
-
process2.stderr.write(`[SessionRouter] Failed to load session ${sanitizeLogText(savedSessionId, 128)} for key ${sanitizeLogText(key, 256)} (${sanitizeLogText(loadError instanceof Error ? loadError.message : String(loadError), 512)}) and failed to create a replacement (${sanitizeLogText(createError instanceof Error ? createError.message : String(createError), 512)})
|
|
1182
|
-
`);
|
|
1183
|
-
throw createError;
|
|
1184
|
-
}
|
|
1087
|
+
if (this.policy === "disabled") {
|
|
1088
|
+
return { allowed: false, reason: "disabled" };
|
|
1089
|
+
}
|
|
1090
|
+
if (this.policy === "allowlist") {
|
|
1091
|
+
if (!this.groups[envelope.chatId]) {
|
|
1092
|
+
return { allowed: false, reason: "not_allowlisted" };
|
|
1185
1093
|
}
|
|
1186
|
-
} finally {
|
|
1187
|
-
this.endSessionLoad(loadWindow);
|
|
1188
1094
|
}
|
|
1095
|
+
const groupConfig = this.groups[envelope.chatId] || this.groups["*"] || {};
|
|
1096
|
+
const requireMention = groupConfig.requireMention ?? true;
|
|
1097
|
+
if (requireMention && !envelope.isMentioned && !envelope.isReplyToBot) {
|
|
1098
|
+
return { allowed: false, reason: "mention_required" };
|
|
1099
|
+
}
|
|
1100
|
+
return { allowed: true };
|
|
1189
1101
|
}
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1102
|
+
};
|
|
1103
|
+
|
|
1104
|
+
// packages/channels/base/dist/DmGate.js
|
|
1105
|
+
init_esbuild_shims();
|
|
1106
|
+
var DmGate = class {
|
|
1107
|
+
static {
|
|
1108
|
+
__name(this, "DmGate");
|
|
1195
1109
|
}
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
return this.toSession.has(this.routingKey(channelName, senderId, chatId, threadId));
|
|
1200
|
-
}
|
|
1201
|
-
if (scope === "single") {
|
|
1202
|
-
return false;
|
|
1203
|
-
}
|
|
1204
|
-
for (const target of this.toTarget.values()) {
|
|
1205
|
-
if (target.channelName === channelName && target.senderId === senderId) {
|
|
1206
|
-
return true;
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
return false;
|
|
1110
|
+
policy;
|
|
1111
|
+
constructor(policy = "open") {
|
|
1112
|
+
this.policy = policy;
|
|
1210
1113
|
}
|
|
1211
1114
|
/**
|
|
1212
|
-
*
|
|
1115
|
+
* DM check: policy gating for private/non-group messages.
|
|
1116
|
+
* Evaluation order:
|
|
1117
|
+
* 1. Group messages bypass this gate (handled by GroupGate)
|
|
1118
|
+
* 2. dmPolicy (disabled → drop)
|
|
1119
|
+
*
|
|
1120
|
+
* Symmetric with GroupGate — GroupGate owns group messages,
|
|
1121
|
+
* DmGate owns DM messages.
|
|
1213
1122
|
*/
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
if (chatId) {
|
|
1218
|
-
const key = this.routingKey(channelName, senderId, chatId, threadId);
|
|
1219
|
-
this.invalidateRouteOperation(key);
|
|
1220
|
-
const sessionId = this.deleteByKey(key);
|
|
1221
|
-
if (sessionId)
|
|
1222
|
-
removedIds.push(sessionId);
|
|
1223
|
-
} else if (scope === "single") {
|
|
1224
|
-
return removedIds;
|
|
1225
|
-
} else {
|
|
1226
|
-
for (const [k, mappedSessionId] of [...this.toSession.entries()]) {
|
|
1227
|
-
const target = this.toTarget.get(mappedSessionId);
|
|
1228
|
-
if (target?.channelName === channelName && target.senderId === senderId) {
|
|
1229
|
-
this.invalidateRouteOperation(k);
|
|
1230
|
-
const sessionId = this.deleteByKey(k);
|
|
1231
|
-
if (sessionId)
|
|
1232
|
-
removedIds.push(sessionId);
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
for (const [key, operation] of [...this.creatingSessions]) {
|
|
1236
|
-
if (operation.target.channelName === channelName && operation.target.senderId === senderId) {
|
|
1237
|
-
this.invalidateRouteOperation(key);
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1123
|
+
check(envelope) {
|
|
1124
|
+
if (envelope.isGroup) {
|
|
1125
|
+
return { allowed: true };
|
|
1240
1126
|
}
|
|
1241
|
-
if (
|
|
1242
|
-
|
|
1243
|
-
|
|
1127
|
+
if (this.policy === "disabled") {
|
|
1128
|
+
return { allowed: false, reason: "disabled" };
|
|
1129
|
+
}
|
|
1130
|
+
return { allowed: true };
|
|
1244
1131
|
}
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1132
|
+
};
|
|
1133
|
+
|
|
1134
|
+
// packages/channels/base/dist/group-history-store.js
|
|
1135
|
+
init_esbuild_shims();
|
|
1136
|
+
import { appendFileSync, chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1137
|
+
import { dirname as dirname2, join as join3 } from "node:path";
|
|
1138
|
+
var DEFAULT_MAX_KEYS = 1e3;
|
|
1139
|
+
var DEFAULT_COMPACT_AFTER_RECORDS = 1e3;
|
|
1140
|
+
var GroupHistoryStore = class {
|
|
1141
|
+
static {
|
|
1142
|
+
__name(this, "GroupHistoryStore");
|
|
1143
|
+
}
|
|
1144
|
+
filePath;
|
|
1145
|
+
maxKeys;
|
|
1146
|
+
compactAfterRecords;
|
|
1147
|
+
constructor(filePath, options = {}) {
|
|
1148
|
+
this.filePath = filePath;
|
|
1149
|
+
this.maxKeys = options.maxKeys ?? DEFAULT_MAX_KEYS;
|
|
1150
|
+
this.compactAfterRecords = options.compactAfterRecords ?? DEFAULT_COMPACT_AFTER_RECORDS;
|
|
1151
|
+
}
|
|
1152
|
+
record(key, entry, limit) {
|
|
1153
|
+
const normalizedLimit = normalizeLimit(limit);
|
|
1154
|
+
if (normalizedLimit <= 0) {
|
|
1155
|
+
return;
|
|
1254
1156
|
}
|
|
1255
|
-
|
|
1256
|
-
|
|
1157
|
+
const loaded = this.loadState();
|
|
1158
|
+
const state = loaded.entries;
|
|
1159
|
+
const limits = loaded.limits;
|
|
1160
|
+
const current = state.get(key) ?? [];
|
|
1161
|
+
current.push(entry);
|
|
1162
|
+
if (current.length > normalizedLimit) {
|
|
1163
|
+
current.splice(0, current.length - normalizedLimit);
|
|
1257
1164
|
}
|
|
1258
|
-
|
|
1259
|
-
|
|
1165
|
+
state.delete(key);
|
|
1166
|
+
state.set(key, current);
|
|
1167
|
+
limits.set(key, normalizedLimit);
|
|
1168
|
+
const evicted = evictOldKeys(state, this.maxKeys, limits);
|
|
1169
|
+
this.append({
|
|
1170
|
+
type: "message",
|
|
1171
|
+
key,
|
|
1172
|
+
limit: normalizedLimit,
|
|
1173
|
+
entry,
|
|
1174
|
+
recordedAt: Date.now()
|
|
1175
|
+
});
|
|
1176
|
+
if (evicted || loaded.hadInvalidRecords || loaded.recordCount + 1 >= this.compactAfterRecords) {
|
|
1177
|
+
this.compact(state, limits);
|
|
1260
1178
|
}
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1179
|
+
}
|
|
1180
|
+
drain(key, limit) {
|
|
1181
|
+
const normalizedLimit = normalizeLimit(limit);
|
|
1182
|
+
const loaded = this.loadState();
|
|
1183
|
+
const state = loaded.entries;
|
|
1184
|
+
const entries = normalizedLimit > 0 ? (state.get(key) ?? []).slice(-normalizedLimit) : [];
|
|
1185
|
+
if (state.has(key)) {
|
|
1186
|
+
state.delete(key);
|
|
1187
|
+
loaded.limits.delete(key);
|
|
1188
|
+
this.append({ type: "clear", key, recordedAt: Date.now() });
|
|
1266
1189
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1190
|
+
return entries;
|
|
1191
|
+
}
|
|
1192
|
+
clear(key) {
|
|
1193
|
+
const loaded = this.loadState();
|
|
1194
|
+
const state = loaded.entries;
|
|
1195
|
+
if (!state.has(key)) {
|
|
1196
|
+
return;
|
|
1269
1197
|
}
|
|
1270
|
-
|
|
1198
|
+
state.delete(key);
|
|
1199
|
+
loaded.limits.delete(key);
|
|
1200
|
+
this.append({ type: "clear", key, recordedAt: Date.now() });
|
|
1201
|
+
this.compact(state, loaded.limits);
|
|
1271
1202
|
}
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1203
|
+
clearAll() {
|
|
1204
|
+
const loaded = this.loadState();
|
|
1205
|
+
if (loaded.entries.size === 0) {
|
|
1206
|
+
return;
|
|
1275
1207
|
}
|
|
1276
|
-
const
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
loadWindow.add(sessionId);
|
|
1208
|
+
const recordedAt = Date.now();
|
|
1209
|
+
for (const key of loaded.entries.keys()) {
|
|
1210
|
+
this.append({ type: "clear", key, recordedAt });
|
|
1280
1211
|
}
|
|
1281
|
-
|
|
1282
|
-
}
|
|
1283
|
-
deleteByKey(key) {
|
|
1284
|
-
const sessionId = this.toSession.get(key);
|
|
1285
|
-
if (!sessionId)
|
|
1286
|
-
return null;
|
|
1287
|
-
this.toSession.delete(key);
|
|
1288
|
-
this.toTarget.delete(sessionId);
|
|
1289
|
-
this.toCwd.delete(sessionId);
|
|
1290
|
-
this.liveSessionIds.delete(sessionId);
|
|
1291
|
-
return sessionId;
|
|
1212
|
+
this.compact(/* @__PURE__ */ new Map(), /* @__PURE__ */ new Map());
|
|
1292
1213
|
}
|
|
1293
|
-
|
|
1294
|
-
const
|
|
1295
|
-
if (
|
|
1296
|
-
return;
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
this.toTarget.set(sessionId, { ...current, isGroup: true });
|
|
1300
|
-
this.persist();
|
|
1214
|
+
size(key) {
|
|
1215
|
+
const state = this.loadState().entries;
|
|
1216
|
+
if (key !== void 0) {
|
|
1217
|
+
return state.get(key)?.length ?? 0;
|
|
1218
|
+
}
|
|
1219
|
+
return state.size;
|
|
1301
1220
|
}
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
const
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
if (
|
|
1308
|
-
|
|
1221
|
+
loadState() {
|
|
1222
|
+
const state = /* @__PURE__ */ new Map();
|
|
1223
|
+
const limits = /* @__PURE__ */ new Map();
|
|
1224
|
+
const read = this.readRecords();
|
|
1225
|
+
for (const record of read.records) {
|
|
1226
|
+
if (record.type === "clear") {
|
|
1227
|
+
state.delete(record.key);
|
|
1228
|
+
limits.delete(record.key);
|
|
1229
|
+
continue;
|
|
1230
|
+
}
|
|
1231
|
+
const current = state.get(record.key) ?? [];
|
|
1232
|
+
current.push(record.entry);
|
|
1233
|
+
if (current.length > record.limit) {
|
|
1234
|
+
current.splice(0, current.length - record.limit);
|
|
1309
1235
|
}
|
|
1236
|
+
state.delete(record.key);
|
|
1237
|
+
state.set(record.key, current);
|
|
1238
|
+
limits.set(record.key, record.limit);
|
|
1239
|
+
evictOldKeys(state, this.maxKeys, limits);
|
|
1310
1240
|
}
|
|
1311
|
-
return
|
|
1241
|
+
return {
|
|
1242
|
+
entries: state,
|
|
1243
|
+
limits,
|
|
1244
|
+
recordCount: read.records.length,
|
|
1245
|
+
hadInvalidRecords: read.hadInvalidRecords
|
|
1246
|
+
};
|
|
1312
1247
|
}
|
|
1313
|
-
|
|
1314
|
-
if (this.
|
|
1315
|
-
|
|
1248
|
+
readRecords() {
|
|
1249
|
+
if (!existsSync2(this.filePath)) {
|
|
1250
|
+
return { records: [], hadInvalidRecords: false };
|
|
1316
1251
|
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
this.toCwd.set(entry.sessionId, entry.cwd);
|
|
1326
|
-
restored++;
|
|
1252
|
+
let data;
|
|
1253
|
+
try {
|
|
1254
|
+
data = readFileSync2(this.filePath, "utf-8");
|
|
1255
|
+
} catch (err) {
|
|
1256
|
+
if (isErrnoCode(err, "ENOENT")) {
|
|
1257
|
+
return { records: [], hadInvalidRecords: false };
|
|
1258
|
+
}
|
|
1259
|
+
throw err;
|
|
1327
1260
|
}
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1261
|
+
const records = [];
|
|
1262
|
+
let hadInvalidRecords = false;
|
|
1263
|
+
for (const line of data.split("\n")) {
|
|
1264
|
+
if (line.trim().length === 0) {
|
|
1265
|
+
continue;
|
|
1266
|
+
}
|
|
1267
|
+
try {
|
|
1268
|
+
const parsed = JSON.parse(line);
|
|
1269
|
+
if (isGroupHistoryRecord(parsed)) {
|
|
1270
|
+
records.push(parsed);
|
|
1271
|
+
} else {
|
|
1272
|
+
hadInvalidRecords = true;
|
|
1273
|
+
}
|
|
1274
|
+
} catch {
|
|
1275
|
+
hadInvalidRecords = true;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
return { records, hadInvalidRecords };
|
|
1331
1279
|
}
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1280
|
+
append(record) {
|
|
1281
|
+
const dir = dirname2(this.filePath);
|
|
1282
|
+
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
1283
|
+
chmodPrivate(dir, 448);
|
|
1284
|
+
appendFileSync(this.filePath, `${JSON.stringify(record)}
|
|
1285
|
+
`, {
|
|
1286
|
+
encoding: "utf-8",
|
|
1287
|
+
mode: 384
|
|
1288
|
+
});
|
|
1289
|
+
chmodPrivate(this.filePath, 384);
|
|
1290
|
+
}
|
|
1291
|
+
compact(state, limits) {
|
|
1292
|
+
const dir = dirname2(this.filePath);
|
|
1293
|
+
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
1294
|
+
chmodPrivate(dir, 448);
|
|
1295
|
+
const records = [];
|
|
1296
|
+
const recordedAt = Date.now();
|
|
1297
|
+
for (const [key, entries] of state) {
|
|
1298
|
+
for (const entry of entries) {
|
|
1299
|
+
records.push({
|
|
1300
|
+
type: "message",
|
|
1301
|
+
key,
|
|
1302
|
+
limit: limits.get(key) ?? entries.length,
|
|
1303
|
+
entry,
|
|
1304
|
+
recordedAt
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1349
1307
|
}
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1308
|
+
const data = records.length > 0 ? `${records.map((record) => JSON.stringify(record)).join("\n")}
|
|
1309
|
+
` : "";
|
|
1310
|
+
const tempPath = join3(dir, `${Date.now()}-${process.pid}-${Math.random().toString(16).slice(2)}.tmp`);
|
|
1311
|
+
writeFileSync2(tempPath, data, { encoding: "utf-8", mode: 384 });
|
|
1312
|
+
chmodPrivate(tempPath, 384);
|
|
1313
|
+
renameSync2(tempPath, this.filePath);
|
|
1314
|
+
chmodPrivate(this.filePath, 384);
|
|
1315
|
+
}
|
|
1316
|
+
};
|
|
1317
|
+
function normalizeLimit(limit) {
|
|
1318
|
+
if (!Number.isFinite(limit) || limit <= 0) {
|
|
1319
|
+
return 0;
|
|
1320
|
+
}
|
|
1321
|
+
return Math.floor(limit);
|
|
1322
|
+
}
|
|
1323
|
+
__name(normalizeLimit, "normalizeLimit");
|
|
1324
|
+
function evictOldKeys(state, maxKeys, limits) {
|
|
1325
|
+
let evicted = false;
|
|
1326
|
+
while (state.size > maxKeys) {
|
|
1327
|
+
const oldest = state.keys().next().value;
|
|
1328
|
+
if (oldest === void 0) {
|
|
1329
|
+
return evicted;
|
|
1358
1330
|
}
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1331
|
+
state.delete(oldest);
|
|
1332
|
+
limits?.delete(oldest);
|
|
1333
|
+
evicted = true;
|
|
1334
|
+
}
|
|
1335
|
+
return evicted;
|
|
1336
|
+
}
|
|
1337
|
+
__name(evictOldKeys, "evictOldKeys");
|
|
1338
|
+
function isGroupHistoryRecord(value) {
|
|
1339
|
+
if (!isRecord(value)) {
|
|
1340
|
+
return false;
|
|
1341
|
+
}
|
|
1342
|
+
if (value["type"] === "clear") {
|
|
1343
|
+
return typeof value["key"] === "string";
|
|
1344
|
+
}
|
|
1345
|
+
if (value["type"] !== "message") {
|
|
1346
|
+
return false;
|
|
1347
|
+
}
|
|
1348
|
+
return typeof value["key"] === "string" && typeof value["limit"] === "number" && isGroupHistoryEntry(value["entry"]);
|
|
1349
|
+
}
|
|
1350
|
+
__name(isGroupHistoryRecord, "isGroupHistoryRecord");
|
|
1351
|
+
function isGroupHistoryEntry(value) {
|
|
1352
|
+
return isRecord(value) && typeof value["senderId"] === "string" && typeof value["senderName"] === "string" && typeof value["text"] === "string" && typeof value["timestamp"] === "number";
|
|
1353
|
+
}
|
|
1354
|
+
__name(isGroupHistoryEntry, "isGroupHistoryEntry");
|
|
1355
|
+
function isRecord(value) {
|
|
1356
|
+
return typeof value === "object" && value !== null;
|
|
1357
|
+
}
|
|
1358
|
+
__name(isRecord, "isRecord");
|
|
1359
|
+
function isErrnoCode(err, code) {
|
|
1360
|
+
return err instanceof Error && "code" in err && err.code === code;
|
|
1361
|
+
}
|
|
1362
|
+
__name(isErrnoCode, "isErrnoCode");
|
|
1363
|
+
function chmodPrivate(path4, mode) {
|
|
1364
|
+
try {
|
|
1365
|
+
chmodSync(path4, mode);
|
|
1366
|
+
} catch {
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
__name(chmodPrivate, "chmodPrivate");
|
|
1370
|
+
|
|
1371
|
+
// packages/channels/base/dist/SenderGate.js
|
|
1372
|
+
init_esbuild_shims();
|
|
1373
|
+
var SenderGate = class {
|
|
1374
|
+
static {
|
|
1375
|
+
__name(this, "SenderGate");
|
|
1376
|
+
}
|
|
1377
|
+
policy;
|
|
1378
|
+
allowedUsers;
|
|
1379
|
+
pairingStore;
|
|
1380
|
+
constructor(policy, allowedUsers = [], pairingStore) {
|
|
1381
|
+
this.policy = policy;
|
|
1382
|
+
this.allowedUsers = new Set(allowedUsers);
|
|
1383
|
+
this.pairingStore = pairingStore || null;
|
|
1384
|
+
}
|
|
1385
|
+
replaceAllowedUsers(users) {
|
|
1386
|
+
this.allowedUsers = new Set(users);
|
|
1387
|
+
}
|
|
1388
|
+
isAllowed(senderId) {
|
|
1389
|
+
switch (this.policy) {
|
|
1390
|
+
case "open":
|
|
1391
|
+
return true;
|
|
1392
|
+
case "allowlist":
|
|
1393
|
+
return this.allowedUsers.has(senderId);
|
|
1394
|
+
case "pairing":
|
|
1395
|
+
return this.allowedUsers.has(senderId) || this.pairingStore?.isApproved(senderId) === true;
|
|
1396
|
+
default:
|
|
1397
|
+
throw new Error(`Unknown sender policy: ${this.policy}`);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
check(senderId, senderName) {
|
|
1401
|
+
switch (this.policy) {
|
|
1402
|
+
case "open":
|
|
1403
|
+
return { allowed: true };
|
|
1404
|
+
case "allowlist":
|
|
1405
|
+
return { allowed: this.allowedUsers.has(senderId) };
|
|
1406
|
+
case "pairing": {
|
|
1407
|
+
if (this.allowedUsers.has(senderId)) {
|
|
1408
|
+
return { allowed: true };
|
|
1409
|
+
}
|
|
1410
|
+
if (this.pairingStore?.isApproved(senderId)) {
|
|
1411
|
+
return { allowed: true };
|
|
1412
|
+
}
|
|
1413
|
+
const code = this.pairingStore?.createRequest(senderId, senderName || senderId);
|
|
1414
|
+
return { allowed: false, pairingCode: code ?? null };
|
|
1415
|
+
}
|
|
1416
|
+
default:
|
|
1417
|
+
throw new Error(`Unknown sender policy: ${this.policy}`);
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
};
|
|
1421
|
+
|
|
1422
|
+
// packages/channels/base/dist/SessionRouter.js
|
|
1423
|
+
init_esbuild_shims();
|
|
1424
|
+
import { chmodSync as chmodSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync3, rmSync, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1425
|
+
import { dirname as dirname3, join as join4 } from "node:path";
|
|
1426
|
+
import process2 from "node:process";
|
|
1427
|
+
var SessionRouter = class {
|
|
1428
|
+
static {
|
|
1429
|
+
__name(this, "SessionRouter");
|
|
1430
|
+
}
|
|
1431
|
+
toSession = /* @__PURE__ */ new Map();
|
|
1432
|
+
// routing key → session ID
|
|
1433
|
+
toTarget = /* @__PURE__ */ new Map();
|
|
1434
|
+
// session ID → target
|
|
1435
|
+
toCwd = /* @__PURE__ */ new Map();
|
|
1436
|
+
// session ID → cwd
|
|
1437
|
+
creatingSessions = /* @__PURE__ */ new Map();
|
|
1438
|
+
sessionLoadWindows = /* @__PURE__ */ new Set();
|
|
1439
|
+
liveSessionIds = /* @__PURE__ */ new Set();
|
|
1440
|
+
routeTokens = /* @__PURE__ */ new Map();
|
|
1441
|
+
lifecycleGeneration = 0;
|
|
1442
|
+
bridge;
|
|
1443
|
+
defaultCwd;
|
|
1444
|
+
defaultScope;
|
|
1445
|
+
channelScopes = /* @__PURE__ */ new Map();
|
|
1446
|
+
channelApprovalModes = /* @__PURE__ */ new Map();
|
|
1447
|
+
persistPath;
|
|
1448
|
+
recoveryMode;
|
|
1449
|
+
constructor(bridge, defaultCwd, scope = "user", persistPath, options = {}) {
|
|
1450
|
+
this.bridge = bridge;
|
|
1451
|
+
this.defaultCwd = defaultCwd;
|
|
1452
|
+
this.defaultScope = scope;
|
|
1453
|
+
this.persistPath = persistPath;
|
|
1454
|
+
this.recoveryMode = options.recoveryMode ?? "eager";
|
|
1455
|
+
}
|
|
1456
|
+
/** Replace the bridge instance (used after crash recovery restart). */
|
|
1457
|
+
setBridge(bridge) {
|
|
1458
|
+
this.bridge = bridge;
|
|
1459
|
+
}
|
|
1460
|
+
/** Set scope override for a specific channel. */
|
|
1461
|
+
setChannelScope(channelName, scope) {
|
|
1462
|
+
this.channelScopes.set(channelName, scope);
|
|
1463
|
+
}
|
|
1464
|
+
setChannelApprovalMode(channelName, approvalMode) {
|
|
1465
|
+
if (approvalMode) {
|
|
1466
|
+
this.channelApprovalModes.set(channelName, approvalMode);
|
|
1467
|
+
} else {
|
|
1468
|
+
this.channelApprovalModes.delete(channelName);
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
routingKey(channelName, senderId, chatId, threadId) {
|
|
1472
|
+
const scope = this.channelScopes.get(channelName) || this.defaultScope;
|
|
1473
|
+
switch (scope) {
|
|
1474
|
+
case "thread":
|
|
1475
|
+
return `${channelName}:${threadId || chatId}`;
|
|
1476
|
+
case "chat_thread":
|
|
1477
|
+
return threadId ? `${channelName}:${chatId}:${threadId}` : `${channelName}:${chatId}`;
|
|
1478
|
+
case "single":
|
|
1479
|
+
return `${channelName}:__single__`;
|
|
1480
|
+
case "user":
|
|
1481
|
+
default:
|
|
1482
|
+
return `${channelName}:${senderId}:${chatId}`;
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
sessionOptions(channelName) {
|
|
1486
|
+
const approvalMode = this.channelApprovalModes.get(channelName);
|
|
1487
|
+
return approvalMode ? { approvalMode } : void 0;
|
|
1488
|
+
}
|
|
1489
|
+
async resolve(channelName, senderId, chatId, threadId, cwd, isGroup, options) {
|
|
1490
|
+
const key = this.routingKey(channelName, senderId, chatId, options?.routingThreadId ?? threadId);
|
|
1491
|
+
const input = {
|
|
1492
|
+
channelName,
|
|
1493
|
+
senderId,
|
|
1494
|
+
chatId,
|
|
1495
|
+
threadId,
|
|
1496
|
+
cwd: cwd || this.defaultCwd,
|
|
1497
|
+
isGroup
|
|
1498
|
+
};
|
|
1499
|
+
let failedWaits = 0;
|
|
1500
|
+
for (; ; ) {
|
|
1501
|
+
const existing = this.toSession.get(key);
|
|
1502
|
+
if (existing && this.isLive(existing)) {
|
|
1503
|
+
this.promoteTargetToGroup(existing, isGroup);
|
|
1504
|
+
return existing;
|
|
1505
|
+
}
|
|
1506
|
+
const creating = this.creatingSessions.get(key);
|
|
1507
|
+
if (creating) {
|
|
1366
1508
|
try {
|
|
1367
|
-
|
|
1368
|
-
const options = this.sessionOptions(entry.target.channelName);
|
|
1369
|
-
const sessionId = await this.bridge.loadSession(entry.sessionId, entry.cwd, options, operation);
|
|
1509
|
+
const sessionId = await creating.promise;
|
|
1370
1510
|
try {
|
|
1371
|
-
this.
|
|
1511
|
+
this.assertOperationResultCurrent(key, sessionId, creating);
|
|
1372
1512
|
} catch (error) {
|
|
1373
|
-
this.scheduleDiscardInvalidatedSession(sessionId,
|
|
1513
|
+
this.scheduleDiscardInvalidatedSession(sessionId, creating);
|
|
1374
1514
|
throw error;
|
|
1375
1515
|
}
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
if (
|
|
1380
|
-
throw
|
|
1381
|
-
}
|
|
1382
|
-
this.toSession.set(key, sessionId);
|
|
1383
|
-
this.toTarget.set(sessionId, entry.target);
|
|
1384
|
-
this.toCwd.set(sessionId, entry.cwd);
|
|
1385
|
-
reservation.resolve(sessionId);
|
|
1386
|
-
if (sessionId !== entry.sessionId) {
|
|
1387
|
-
changed = true;
|
|
1516
|
+
this.promoteTargetToGroup(sessionId, isGroup);
|
|
1517
|
+
return sessionId;
|
|
1518
|
+
} catch (error) {
|
|
1519
|
+
if (creating.invalidationError) {
|
|
1520
|
+
throw creating.invalidationError;
|
|
1388
1521
|
}
|
|
1389
|
-
|
|
1390
|
-
} catch (err) {
|
|
1391
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
1392
|
-
process2.stderr.write(`[SessionRouter] Failed to restore session ${sanitizeLogText(entry.sessionId, 128)} for key ${sanitizeLogText(key, 256)}: ${sanitizeLogText(reason, 512)}
|
|
1393
|
-
`);
|
|
1394
|
-
reservation.reject(new Error("Session restore failed", { cause: err }));
|
|
1395
|
-
failed++;
|
|
1396
|
-
changed = true;
|
|
1397
|
-
} finally {
|
|
1398
|
-
if (this.creatingSessions.get(key) === operation) {
|
|
1522
|
+
if (this.creatingSessions.get(key) === creating) {
|
|
1399
1523
|
this.creatingSessions.delete(key);
|
|
1400
1524
|
}
|
|
1401
|
-
this.releaseRouteToken(key,
|
|
1525
|
+
this.releaseRouteToken(key, creating);
|
|
1526
|
+
failedWaits++;
|
|
1527
|
+
if (failedWaits > 3)
|
|
1528
|
+
throw error;
|
|
1529
|
+
continue;
|
|
1402
1530
|
}
|
|
1403
1531
|
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1532
|
+
const operation = this.createSessionOperation(key, {
|
|
1533
|
+
channelName: input.channelName,
|
|
1534
|
+
senderId: input.senderId,
|
|
1535
|
+
chatId: input.chatId,
|
|
1536
|
+
threadId: input.threadId,
|
|
1537
|
+
isGroup: input.isGroup
|
|
1538
|
+
}, (currentOperation) => existing ? this.loadOrReplaceSession(key, existing, input, currentOperation) : this.createAndStoreSession(key, input, currentOperation));
|
|
1539
|
+
this.creatingSessions.set(key, operation);
|
|
1540
|
+
try {
|
|
1541
|
+
const sessionId = await operation.promise;
|
|
1542
|
+
try {
|
|
1543
|
+
this.assertOperationResultCurrent(key, sessionId, operation);
|
|
1544
|
+
} catch (error) {
|
|
1545
|
+
this.scheduleDiscardInvalidatedSession(sessionId, operation);
|
|
1546
|
+
throw error;
|
|
1547
|
+
}
|
|
1548
|
+
this.promoteTargetToGroup(sessionId, isGroup);
|
|
1549
|
+
return sessionId;
|
|
1550
|
+
} finally {
|
|
1551
|
+
if (this.creatingSessions.get(key) === operation) {
|
|
1552
|
+
this.creatingSessions.delete(key);
|
|
1553
|
+
}
|
|
1554
|
+
this.releaseRouteToken(key, operation);
|
|
1555
|
+
}
|
|
1409
1556
|
}
|
|
1410
|
-
return { restored, failed };
|
|
1411
1557
|
}
|
|
1412
|
-
|
|
1413
|
-
this.
|
|
1414
|
-
for (const operation of this.creatingSessions.values()) {
|
|
1415
|
-
this.invalidateOperation(operation);
|
|
1416
|
-
}
|
|
1417
|
-
this.toSession.clear();
|
|
1418
|
-
this.toTarget.clear();
|
|
1419
|
-
this.toCwd.clear();
|
|
1420
|
-
this.creatingSessions.clear();
|
|
1421
|
-
this.sessionLoadWindows.clear();
|
|
1422
|
-
this.liveSessionIds.clear();
|
|
1423
|
-
this.routeTokens.clear();
|
|
1558
|
+
isLive(sessionId) {
|
|
1559
|
+
return this.recoveryMode === "eager" || this.liveSessionIds.has(sessionId);
|
|
1424
1560
|
}
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1561
|
+
async createAndStoreSession(key, input, operation) {
|
|
1562
|
+
const loadWindow = this.beginSessionLoad();
|
|
1563
|
+
try {
|
|
1564
|
+
const sessionId = await this.createLiveSession(input.cwd, loadWindow, key, this.sessionOptions(input.channelName), operation, input.channelName);
|
|
1429
1565
|
try {
|
|
1430
|
-
|
|
1431
|
-
} catch {
|
|
1566
|
+
this.assertOperationCurrent(operation);
|
|
1567
|
+
} catch (error) {
|
|
1568
|
+
this.scheduleDiscardInvalidatedSession(sessionId, operation);
|
|
1569
|
+
throw error;
|
|
1432
1570
|
}
|
|
1571
|
+
this.toSession.set(key, sessionId);
|
|
1572
|
+
this.toTarget.set(sessionId, {
|
|
1573
|
+
channelName: input.channelName,
|
|
1574
|
+
senderId: input.senderId,
|
|
1575
|
+
chatId: input.chatId,
|
|
1576
|
+
threadId: input.threadId,
|
|
1577
|
+
isGroup: input.isGroup
|
|
1578
|
+
});
|
|
1579
|
+
this.toCwd.set(sessionId, input.cwd);
|
|
1580
|
+
this.liveSessionIds.add(sessionId);
|
|
1581
|
+
this.persist();
|
|
1582
|
+
return sessionId;
|
|
1583
|
+
} finally {
|
|
1584
|
+
this.endSessionLoad(loadWindow);
|
|
1433
1585
|
}
|
|
1434
1586
|
}
|
|
1435
|
-
|
|
1436
|
-
const
|
|
1437
|
-
|
|
1438
|
-
return void 0;
|
|
1439
|
-
let parsed;
|
|
1587
|
+
async loadOrReplaceSession(key, savedSessionId, input, operation) {
|
|
1588
|
+
const savedCwd = this.toCwd.get(savedSessionId) ?? input.cwd;
|
|
1589
|
+
const loadWindow = this.beginSessionLoad();
|
|
1440
1590
|
try {
|
|
1441
|
-
parsed = JSON.parse(readFileSync3(persistPath, "utf-8"));
|
|
1442
|
-
} catch (error) {
|
|
1443
|
-
const quarantinePath = `${persistPath}.corrupt-${Date.now()}`;
|
|
1444
1591
|
try {
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1592
|
+
const loadedSessionId = await this.bridge.loadSession(savedSessionId, savedCwd, this.sessionOptions(input.channelName), operation);
|
|
1593
|
+
try {
|
|
1594
|
+
this.assertOperationCurrent(operation);
|
|
1595
|
+
if (this.toSession.get(key) !== savedSessionId) {
|
|
1596
|
+
this.invalidateOperation(operation);
|
|
1597
|
+
this.assertOperationCurrent(operation);
|
|
1598
|
+
}
|
|
1599
|
+
} catch (error) {
|
|
1600
|
+
this.scheduleDiscardInvalidatedSession(loadedSessionId, operation);
|
|
1601
|
+
throw error;
|
|
1602
|
+
}
|
|
1603
|
+
if (typeof loadedSessionId !== "string" || loadedSessionId.length === 0 || loadWindow.delete(loadedSessionId)) {
|
|
1604
|
+
throw new Error("Invalid or dead restored session ID");
|
|
1605
|
+
}
|
|
1606
|
+
if (loadedSessionId !== savedSessionId) {
|
|
1607
|
+
const target = this.toTarget.get(savedSessionId);
|
|
1608
|
+
this.deleteByKey(key);
|
|
1609
|
+
this.toSession.set(key, loadedSessionId);
|
|
1610
|
+
if (target)
|
|
1611
|
+
this.toTarget.set(loadedSessionId, target);
|
|
1612
|
+
this.toCwd.set(loadedSessionId, savedCwd);
|
|
1613
|
+
this.persist();
|
|
1614
|
+
}
|
|
1615
|
+
this.liveSessionIds.add(loadedSessionId);
|
|
1616
|
+
return loadedSessionId;
|
|
1617
|
+
} catch (loadError) {
|
|
1618
|
+
this.assertOperationCurrent(operation);
|
|
1619
|
+
try {
|
|
1620
|
+
const replacement = await this.createLiveSession(input.cwd, loadWindow, key, this.sessionOptions(input.channelName), operation, input.channelName);
|
|
1621
|
+
try {
|
|
1622
|
+
this.assertOperationCurrent(operation);
|
|
1623
|
+
} catch (error) {
|
|
1624
|
+
this.scheduleDiscardInvalidatedSession(replacement, operation);
|
|
1625
|
+
throw error;
|
|
1626
|
+
}
|
|
1627
|
+
this.deleteByKey(key);
|
|
1628
|
+
this.toSession.set(key, replacement);
|
|
1629
|
+
this.toTarget.set(replacement, {
|
|
1630
|
+
channelName: input.channelName,
|
|
1631
|
+
senderId: input.senderId,
|
|
1632
|
+
chatId: input.chatId,
|
|
1633
|
+
threadId: input.threadId,
|
|
1634
|
+
isGroup: input.isGroup
|
|
1635
|
+
});
|
|
1636
|
+
this.toCwd.set(replacement, input.cwd);
|
|
1637
|
+
this.liveSessionIds.add(replacement);
|
|
1638
|
+
this.persist();
|
|
1639
|
+
process2.stderr.write(`[SessionRouter] Replaced unavailable session ${sanitizeLogText(savedSessionId, 128)} for key ${sanitizeLogText(key, 256)} after load failed: ${sanitizeLogText(loadError instanceof Error ? loadError.message : String(loadError), 512)}
|
|
1449
1640
|
`);
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
try {
|
|
1455
|
-
renameSync3(persistPath, quarantinePath);
|
|
1456
|
-
} catch {
|
|
1457
|
-
}
|
|
1458
|
-
process2.stderr.write(`[SessionRouter] Invalid route store at ${sanitizeLogText(persistPath, 1024)}: expected an object
|
|
1641
|
+
return replacement;
|
|
1642
|
+
} catch (createError) {
|
|
1643
|
+
this.assertOperationCurrent(operation);
|
|
1644
|
+
process2.stderr.write(`[SessionRouter] Failed to load session ${sanitizeLogText(savedSessionId, 128)} for key ${sanitizeLogText(key, 256)} (${sanitizeLogText(loadError instanceof Error ? loadError.message : String(loadError), 512)}) and failed to create a replacement (${sanitizeLogText(createError instanceof Error ? createError.message : String(createError), 512)})
|
|
1459
1645
|
`);
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
if (this.isPersistedEntry(value))
|
|
1466
|
-
entries[key] = value;
|
|
1467
|
-
else
|
|
1468
|
-
droppedKeys.push(key);
|
|
1646
|
+
throw createError;
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
} finally {
|
|
1650
|
+
this.endSessionLoad(loadWindow);
|
|
1469
1651
|
}
|
|
1470
|
-
return { entries, dropped: droppedKeys.length, droppedKeys };
|
|
1471
1652
|
}
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
return false;
|
|
1475
|
-
const entry = value;
|
|
1476
|
-
const target = entry["target"];
|
|
1477
|
-
if (typeof target !== "object" || target === null)
|
|
1478
|
-
return false;
|
|
1479
|
-
const typedTarget = target;
|
|
1480
|
-
return typeof entry["sessionId"] === "string" && entry["sessionId"].length > 0 && typeof entry["cwd"] === "string" && entry["cwd"].length > 0 && typeof typedTarget["channelName"] === "string" && typeof typedTarget["senderId"] === "string" && typeof typedTarget["chatId"] === "string" && (typedTarget["threadId"] === void 0 || typeof typedTarget["threadId"] === "string") && (typedTarget["isGroup"] === void 0 || typeof typedTarget["isGroup"] === "boolean");
|
|
1653
|
+
getTarget(sessionId) {
|
|
1654
|
+
return this.toTarget.get(sessionId);
|
|
1481
1655
|
}
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
continue;
|
|
1490
|
-
data[key] = {
|
|
1491
|
-
sessionId,
|
|
1492
|
-
target,
|
|
1493
|
-
cwd: this.toCwd.get(sessionId) ?? this.defaultCwd
|
|
1494
|
-
};
|
|
1656
|
+
getSession(channelName, senderId, chatId, threadId) {
|
|
1657
|
+
return this.toSession.get(this.routingKey(channelName, senderId, chatId, threadId));
|
|
1658
|
+
}
|
|
1659
|
+
hasSession(channelName, senderId, chatId, threadId) {
|
|
1660
|
+
const scope = this.channelScopes.get(channelName) || this.defaultScope;
|
|
1661
|
+
if (chatId) {
|
|
1662
|
+
return this.toSession.has(this.routingKey(channelName, senderId, chatId, threadId));
|
|
1495
1663
|
}
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
} catch {
|
|
1664
|
+
if (scope === "single") {
|
|
1665
|
+
return false;
|
|
1666
|
+
}
|
|
1667
|
+
for (const target of this.toTarget.values()) {
|
|
1668
|
+
if (target.channelName === channelName && target.senderId === senderId) {
|
|
1669
|
+
return true;
|
|
1503
1670
|
}
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1671
|
+
}
|
|
1672
|
+
return false;
|
|
1673
|
+
}
|
|
1674
|
+
/**
|
|
1675
|
+
* Remove session(s) for the given sender. Returns the removed session IDs.
|
|
1676
|
+
*/
|
|
1677
|
+
removeSession(channelName, senderId, chatId, threadId) {
|
|
1678
|
+
const removedIds = [];
|
|
1679
|
+
const scope = this.channelScopes.get(channelName) || this.defaultScope;
|
|
1680
|
+
if (chatId) {
|
|
1681
|
+
const key = this.routingKey(channelName, senderId, chatId, threadId);
|
|
1682
|
+
this.invalidateRouteOperation(key);
|
|
1683
|
+
const sessionId = this.deleteByKey(key);
|
|
1684
|
+
if (sessionId)
|
|
1685
|
+
removedIds.push(sessionId);
|
|
1686
|
+
} else if (scope === "single") {
|
|
1687
|
+
return removedIds;
|
|
1688
|
+
} else {
|
|
1689
|
+
for (const [k, mappedSessionId] of [...this.toSession.entries()]) {
|
|
1690
|
+
const target = this.toTarget.get(mappedSessionId);
|
|
1691
|
+
if (target?.channelName === channelName && target.senderId === senderId) {
|
|
1692
|
+
this.invalidateRouteOperation(k);
|
|
1693
|
+
const sessionId = this.deleteByKey(k);
|
|
1694
|
+
if (sessionId)
|
|
1695
|
+
removedIds.push(sessionId);
|
|
1696
|
+
}
|
|
1512
1697
|
}
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
try {
|
|
1518
|
-
rmSync(tempPath, { force: true });
|
|
1519
|
-
} catch {
|
|
1698
|
+
for (const [key, operation] of [...this.creatingSessions]) {
|
|
1699
|
+
if (operation.target.channelName === channelName && operation.target.senderId === senderId) {
|
|
1700
|
+
this.invalidateRouteOperation(key);
|
|
1701
|
+
}
|
|
1520
1702
|
}
|
|
1521
1703
|
}
|
|
1704
|
+
if (removedIds.length > 0)
|
|
1705
|
+
this.persist();
|
|
1706
|
+
return removedIds;
|
|
1522
1707
|
}
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
let
|
|
1526
|
-
for (
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
this.
|
|
1530
|
-
|
|
1531
|
-
this.scheduleDiscardInvalidatedSession(sessionId, operation);
|
|
1532
|
-
throw error;
|
|
1533
|
-
}
|
|
1534
|
-
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
1535
|
-
throw new Error("Invalid session ID from bridge");
|
|
1708
|
+
/** Remove a session mapping by daemon/ACP session ID. */
|
|
1709
|
+
removeSessionId(sessionId) {
|
|
1710
|
+
let removed = false;
|
|
1711
|
+
for (const [key, mappedSessionId] of [...this.toSession.entries()]) {
|
|
1712
|
+
if (mappedSessionId === sessionId) {
|
|
1713
|
+
this.invalidateRouteOperation(key);
|
|
1714
|
+
this.toSession.delete(key);
|
|
1715
|
+
removed = true;
|
|
1536
1716
|
}
|
|
1537
|
-
|
|
1538
|
-
|
|
1717
|
+
}
|
|
1718
|
+
if (this.toTarget.delete(sessionId)) {
|
|
1719
|
+
removed = true;
|
|
1720
|
+
}
|
|
1721
|
+
if (this.toCwd.delete(sessionId)) {
|
|
1722
|
+
removed = true;
|
|
1723
|
+
}
|
|
1724
|
+
this.liveSessionIds.delete(sessionId);
|
|
1725
|
+
if (!removed && this.sessionLoadWindows.size > 0) {
|
|
1726
|
+
for (const loadWindow of this.sessionLoadWindows) {
|
|
1727
|
+
loadWindow.add(sessionId);
|
|
1539
1728
|
}
|
|
1540
|
-
lastDeadSessionId = sessionId;
|
|
1541
1729
|
}
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
this.sessionLoadWindows.add(loadWindow);
|
|
1547
|
-
return loadWindow;
|
|
1730
|
+
if (removed) {
|
|
1731
|
+
this.persist();
|
|
1732
|
+
}
|
|
1733
|
+
return removed;
|
|
1548
1734
|
}
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
routeToken = {};
|
|
1553
|
-
this.routeTokens.set(key, routeToken);
|
|
1735
|
+
handleSessionDied(sessionId) {
|
|
1736
|
+
if (this.recoveryMode === "eager") {
|
|
1737
|
+
return this.removeSessionId(sessionId);
|
|
1554
1738
|
}
|
|
1555
|
-
const
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
operation.promise = Promise.resolve().then(() => run(operation)).catch((error) => {
|
|
1562
|
-
this.assertOperationCurrent(operation);
|
|
1563
|
-
throw error;
|
|
1564
|
-
});
|
|
1565
|
-
return operation;
|
|
1739
|
+
const known = this.toTarget.has(sessionId);
|
|
1740
|
+
this.liveSessionIds.delete(sessionId);
|
|
1741
|
+
for (const loadWindow of this.sessionLoadWindows) {
|
|
1742
|
+
loadWindow.add(sessionId);
|
|
1743
|
+
}
|
|
1744
|
+
return known;
|
|
1566
1745
|
}
|
|
1567
|
-
|
|
1568
|
-
this.
|
|
1569
|
-
|
|
1570
|
-
|
|
1746
|
+
deleteByKey(key) {
|
|
1747
|
+
const sessionId = this.toSession.get(key);
|
|
1748
|
+
if (!sessionId)
|
|
1749
|
+
return null;
|
|
1750
|
+
this.toSession.delete(key);
|
|
1751
|
+
this.toTarget.delete(sessionId);
|
|
1752
|
+
this.toCwd.delete(sessionId);
|
|
1753
|
+
this.liveSessionIds.delete(sessionId);
|
|
1754
|
+
return sessionId;
|
|
1755
|
+
}
|
|
1756
|
+
promoteTargetToGroup(sessionId, isGroup) {
|
|
1757
|
+
const current = this.toTarget.get(sessionId);
|
|
1758
|
+
if (!current)
|
|
1571
1759
|
return;
|
|
1572
|
-
|
|
1573
|
-
|
|
1760
|
+
if (current.isGroup === true || isGroup !== true)
|
|
1761
|
+
return;
|
|
1762
|
+
this.toTarget.set(sessionId, { ...current, isGroup: true });
|
|
1763
|
+
this.persist();
|
|
1574
1764
|
}
|
|
1575
|
-
|
|
1576
|
-
|
|
1765
|
+
/** Get all session entries for crash recovery. */
|
|
1766
|
+
getAll() {
|
|
1767
|
+
const entries = [];
|
|
1768
|
+
for (const [key, sessionId] of this.toSession) {
|
|
1769
|
+
const target = this.toTarget.get(sessionId);
|
|
1770
|
+
if (target) {
|
|
1771
|
+
entries.push({ key, sessionId, target });
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
return entries;
|
|
1577
1775
|
}
|
|
1578
|
-
|
|
1579
|
-
if (
|
|
1580
|
-
|
|
1776
|
+
restoreRoutes() {
|
|
1777
|
+
if (this.recoveryMode !== "lazy") {
|
|
1778
|
+
throw new Error("restoreRoutes requires lazy recovery mode");
|
|
1581
1779
|
}
|
|
1582
|
-
|
|
1583
|
-
|
|
1780
|
+
const persisted = this.readPersistedEntries();
|
|
1781
|
+
if (!persisted)
|
|
1782
|
+
return { restored: 0, dropped: 0 };
|
|
1783
|
+
this.dispose();
|
|
1784
|
+
let restored = 0;
|
|
1785
|
+
for (const [key, entry] of Object.entries(persisted.entries)) {
|
|
1786
|
+
this.toSession.set(key, entry.sessionId);
|
|
1787
|
+
this.toTarget.set(entry.sessionId, entry.target);
|
|
1788
|
+
this.toCwd.set(entry.sessionId, entry.cwd);
|
|
1789
|
+
restored++;
|
|
1584
1790
|
}
|
|
1791
|
+
if (persisted.dropped > 0)
|
|
1792
|
+
this.persist();
|
|
1793
|
+
return { restored, dropped: persisted.dropped };
|
|
1585
1794
|
}
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1795
|
+
/**
|
|
1796
|
+
* Restore session mappings from a previous bridge.
|
|
1797
|
+
* Called after bridge restart — attempts loadSession for each saved mapping.
|
|
1798
|
+
* Failed loads are dropped (new session on next message).
|
|
1799
|
+
*/
|
|
1800
|
+
async restoreSessions() {
|
|
1801
|
+
const persisted = this.readPersistedEntries();
|
|
1802
|
+
if (!persisted)
|
|
1803
|
+
return { restored: 0, failed: 0 };
|
|
1804
|
+
const entries = persisted.entries;
|
|
1805
|
+
const restoreGeneration = this.lifecycleGeneration;
|
|
1806
|
+
let restored = 0;
|
|
1807
|
+
let failed = 0;
|
|
1808
|
+
let changed = persisted.dropped > 0;
|
|
1809
|
+
const reservations = /* @__PURE__ */ new Map();
|
|
1810
|
+
for (const key of persisted.droppedKeys) {
|
|
1811
|
+
this.deleteByKey(key);
|
|
1589
1812
|
}
|
|
1590
|
-
|
|
1813
|
+
for (const key of Object.keys(entries)) {
|
|
1814
|
+
this.deleteByKey(key);
|
|
1815
|
+
const reservation = this.createSessionReservation();
|
|
1816
|
+
reservation.promise.catch(() => void 0);
|
|
1817
|
+
const operation = this.createSessionOperation(key, entries[key].target, () => reservation.promise);
|
|
1818
|
+
operation.promise.catch(() => void 0);
|
|
1819
|
+
this.creatingSessions.set(key, operation);
|
|
1820
|
+
reservations.set(key, { reservation, operation });
|
|
1821
|
+
}
|
|
1822
|
+
const loadWindow = this.beginSessionLoad();
|
|
1823
|
+
try {
|
|
1824
|
+
for (const [key, entry] of Object.entries(entries)) {
|
|
1825
|
+
const reserved = reservations.get(key);
|
|
1826
|
+
if (!reserved)
|
|
1827
|
+
continue;
|
|
1828
|
+
const { reservation, operation } = reserved;
|
|
1829
|
+
try {
|
|
1830
|
+
this.assertOperationCurrent(operation);
|
|
1831
|
+
const options = this.sessionOptions(entry.target.channelName);
|
|
1832
|
+
const sessionId = await this.bridge.loadSession(entry.sessionId, entry.cwd, options, operation);
|
|
1833
|
+
try {
|
|
1834
|
+
this.assertOperationCurrent(operation);
|
|
1835
|
+
} catch (error) {
|
|
1836
|
+
this.scheduleDiscardInvalidatedSession(sessionId, operation);
|
|
1837
|
+
throw error;
|
|
1838
|
+
}
|
|
1839
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
1840
|
+
throw new Error("Invalid restored session ID");
|
|
1841
|
+
}
|
|
1842
|
+
if (loadWindow.delete(sessionId)) {
|
|
1843
|
+
throw new Error("Restored session died before routing completed");
|
|
1844
|
+
}
|
|
1845
|
+
this.toSession.set(key, sessionId);
|
|
1846
|
+
this.toTarget.set(sessionId, entry.target);
|
|
1847
|
+
this.toCwd.set(sessionId, entry.cwd);
|
|
1848
|
+
reservation.resolve(sessionId);
|
|
1849
|
+
if (sessionId !== entry.sessionId) {
|
|
1850
|
+
changed = true;
|
|
1851
|
+
}
|
|
1852
|
+
restored++;
|
|
1853
|
+
} catch (err) {
|
|
1854
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
1855
|
+
process2.stderr.write(`[SessionRouter] Failed to restore session ${sanitizeLogText(entry.sessionId, 128)} for key ${sanitizeLogText(key, 256)}: ${sanitizeLogText(reason, 512)}
|
|
1856
|
+
`);
|
|
1857
|
+
reservation.reject(new Error("Session restore failed", { cause: err }));
|
|
1858
|
+
failed++;
|
|
1859
|
+
changed = true;
|
|
1860
|
+
} finally {
|
|
1861
|
+
if (this.creatingSessions.get(key) === operation) {
|
|
1862
|
+
this.creatingSessions.delete(key);
|
|
1863
|
+
}
|
|
1864
|
+
this.releaseRouteToken(key, operation);
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
} finally {
|
|
1868
|
+
this.endSessionLoad(loadWindow);
|
|
1869
|
+
}
|
|
1870
|
+
if (changed && restoreGeneration === this.lifecycleGeneration) {
|
|
1871
|
+
this.persist();
|
|
1872
|
+
}
|
|
1873
|
+
return { restored, failed };
|
|
1874
|
+
}
|
|
1875
|
+
dispose() {
|
|
1876
|
+
this.lifecycleGeneration++;
|
|
1877
|
+
for (const operation of this.creatingSessions.values()) {
|
|
1591
1878
|
this.invalidateOperation(operation);
|
|
1592
1879
|
}
|
|
1593
|
-
this.
|
|
1880
|
+
this.toSession.clear();
|
|
1881
|
+
this.toTarget.clear();
|
|
1882
|
+
this.toCwd.clear();
|
|
1883
|
+
this.creatingSessions.clear();
|
|
1884
|
+
this.sessionLoadWindows.clear();
|
|
1885
|
+
this.liveSessionIds.clear();
|
|
1886
|
+
this.routeTokens.clear();
|
|
1594
1887
|
}
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1888
|
+
/** Clear in-memory state and delete persist file. Used on clean shutdown. */
|
|
1889
|
+
clearAll() {
|
|
1890
|
+
this.dispose();
|
|
1891
|
+
if (this.persistPath && existsSync3(this.persistPath)) {
|
|
1892
|
+
try {
|
|
1893
|
+
unlinkSync(this.persistPath);
|
|
1894
|
+
} catch {
|
|
1895
|
+
}
|
|
1598
1896
|
}
|
|
1599
1897
|
}
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1898
|
+
readPersistedEntries() {
|
|
1899
|
+
const persistPath = this.persistPath;
|
|
1900
|
+
if (!persistPath || !existsSync3(persistPath))
|
|
1901
|
+
return void 0;
|
|
1902
|
+
let parsed;
|
|
1603
1903
|
try {
|
|
1604
|
-
|
|
1605
|
-
} catch {
|
|
1904
|
+
parsed = JSON.parse(readFileSync3(persistPath, "utf-8"));
|
|
1905
|
+
} catch (error) {
|
|
1906
|
+
const quarantinePath = `${persistPath}.corrupt-${Date.now()}`;
|
|
1907
|
+
try {
|
|
1908
|
+
renameSync3(persistPath, quarantinePath);
|
|
1909
|
+
} catch {
|
|
1910
|
+
}
|
|
1911
|
+
process2.stderr.write(`[SessionRouter] Corrupted persist file at ${sanitizeLogText(persistPath, 1024)}: ${sanitizeLogText(error instanceof Error ? error.message : String(error), 512)}
|
|
1912
|
+
`);
|
|
1913
|
+
return void 0;
|
|
1606
1914
|
}
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1915
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1916
|
+
const quarantinePath = `${persistPath}.corrupt-${Date.now()}`;
|
|
1917
|
+
try {
|
|
1918
|
+
renameSync3(persistPath, quarantinePath);
|
|
1919
|
+
} catch {
|
|
1920
|
+
}
|
|
1921
|
+
process2.stderr.write(`[SessionRouter] Invalid route store at ${sanitizeLogText(persistPath, 1024)}: expected an object
|
|
1922
|
+
`);
|
|
1923
|
+
return void 0;
|
|
1924
|
+
}
|
|
1925
|
+
const entries = {};
|
|
1926
|
+
const droppedKeys = [];
|
|
1927
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
1928
|
+
if (this.isPersistedEntry(value))
|
|
1929
|
+
entries[key] = value;
|
|
1930
|
+
else
|
|
1931
|
+
droppedKeys.push(key);
|
|
1932
|
+
}
|
|
1933
|
+
return { entries, dropped: droppedKeys.length, droppedKeys };
|
|
1934
|
+
}
|
|
1935
|
+
isPersistedEntry(value) {
|
|
1936
|
+
if (typeof value !== "object" || value === null)
|
|
1937
|
+
return false;
|
|
1938
|
+
const entry = value;
|
|
1939
|
+
const target = entry["target"];
|
|
1940
|
+
if (typeof target !== "object" || target === null)
|
|
1941
|
+
return false;
|
|
1942
|
+
const typedTarget = target;
|
|
1943
|
+
return typeof entry["sessionId"] === "string" && entry["sessionId"].length > 0 && typeof entry["cwd"] === "string" && entry["cwd"].length > 0 && typeof typedTarget["channelName"] === "string" && typeof typedTarget["senderId"] === "string" && typeof typedTarget["chatId"] === "string" && (typedTarget["threadId"] === void 0 || typeof typedTarget["threadId"] === "string") && (typedTarget["isGroup"] === void 0 || typeof typedTarget["isGroup"] === "boolean");
|
|
1944
|
+
}
|
|
1945
|
+
persist() {
|
|
1946
|
+
if (!this.persistPath)
|
|
1947
|
+
return;
|
|
1948
|
+
const data = {};
|
|
1949
|
+
for (const [key, sessionId] of this.toSession) {
|
|
1950
|
+
const target = this.toTarget.get(sessionId);
|
|
1951
|
+
if (!target)
|
|
1952
|
+
continue;
|
|
1953
|
+
data[key] = {
|
|
1954
|
+
sessionId,
|
|
1955
|
+
target,
|
|
1956
|
+
cwd: this.toCwd.get(sessionId) ?? this.defaultCwd
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
const dir = dirname3(this.persistPath);
|
|
1960
|
+
const tempPath = join4(dir, `${Date.now()}-${process2.pid}-${Math.random().toString(16).slice(2)}.tmp`);
|
|
1961
|
+
try {
|
|
1962
|
+
mkdirSync3(dir, { recursive: true, mode: 448 });
|
|
1963
|
+
try {
|
|
1964
|
+
chmodSync2(dir, 448);
|
|
1965
|
+
} catch {
|
|
1966
|
+
}
|
|
1967
|
+
writeFileSync3(tempPath, JSON.stringify(data, null, 2), {
|
|
1968
|
+
encoding: "utf-8",
|
|
1969
|
+
mode: 384
|
|
1970
|
+
});
|
|
1971
|
+
renameSync3(tempPath, this.persistPath);
|
|
1972
|
+
try {
|
|
1973
|
+
chmodSync2(this.persistPath, 384);
|
|
1974
|
+
} catch {
|
|
1975
|
+
}
|
|
1976
|
+
} catch (error) {
|
|
1977
|
+
process2.stderr.write(`[SessionRouter] Failed to persist routes at ${sanitizeLogText(this.persistPath, 1024)}: ${sanitizeLogText(error instanceof Error ? error.message : String(error), 512)}
|
|
1978
|
+
`);
|
|
1979
|
+
} finally {
|
|
1980
|
+
try {
|
|
1981
|
+
rmSync(tempPath, { force: true });
|
|
1982
|
+
} catch {
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
async createLiveSession(cwd, loadWindow, routingKey, options, operation, sourceId) {
|
|
1987
|
+
const maxAttempts = 2;
|
|
1988
|
+
let lastDeadSessionId;
|
|
1989
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
1990
|
+
const sessionId = await this.bridge.newSession(cwd, { ...options, sourceId }, operation);
|
|
1991
|
+
try {
|
|
1992
|
+
this.assertOperationCurrent(operation);
|
|
1993
|
+
} catch (error) {
|
|
1994
|
+
this.scheduleDiscardInvalidatedSession(sessionId, operation);
|
|
1995
|
+
throw error;
|
|
1996
|
+
}
|
|
1997
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
1998
|
+
throw new Error("Invalid session ID from bridge");
|
|
1999
|
+
}
|
|
2000
|
+
if (!loadWindow.delete(sessionId)) {
|
|
2001
|
+
return sessionId;
|
|
2002
|
+
}
|
|
2003
|
+
lastDeadSessionId = sessionId;
|
|
2004
|
+
}
|
|
2005
|
+
throw new Error(`Session ${lastDeadSessionId ?? "unknown"} died before routing completed (${maxAttempts}/${maxAttempts} attempts, key ${routingKey})`);
|
|
2006
|
+
}
|
|
2007
|
+
beginSessionLoad() {
|
|
2008
|
+
const loadWindow = /* @__PURE__ */ new Set();
|
|
2009
|
+
this.sessionLoadWindows.add(loadWindow);
|
|
2010
|
+
return loadWindow;
|
|
2011
|
+
}
|
|
2012
|
+
createSessionOperation(key, target, run) {
|
|
2013
|
+
let routeToken = this.routeTokens.get(key);
|
|
2014
|
+
if (!routeToken) {
|
|
2015
|
+
routeToken = {};
|
|
2016
|
+
this.routeTokens.set(key, routeToken);
|
|
2017
|
+
}
|
|
2018
|
+
const operation = {
|
|
2019
|
+
promise: Promise.resolve(""),
|
|
2020
|
+
target,
|
|
2021
|
+
lifecycleGeneration: this.lifecycleGeneration,
|
|
2022
|
+
routeToken
|
|
2023
|
+
};
|
|
2024
|
+
operation.promise = Promise.resolve().then(() => run(operation)).catch((error) => {
|
|
2025
|
+
this.assertOperationCurrent(operation);
|
|
2026
|
+
throw error;
|
|
2027
|
+
});
|
|
2028
|
+
return operation;
|
|
2029
|
+
}
|
|
2030
|
+
invalidateRouteOperation(key) {
|
|
2031
|
+
this.routeTokens.delete(key);
|
|
2032
|
+
const operation = this.creatingSessions.get(key);
|
|
2033
|
+
if (!operation)
|
|
2034
|
+
return;
|
|
2035
|
+
this.invalidateOperation(operation);
|
|
2036
|
+
this.creatingSessions.delete(key);
|
|
2037
|
+
}
|
|
2038
|
+
invalidateOperation(operation) {
|
|
2039
|
+
operation.invalidationError ??= new Error("Session route operation was invalidated");
|
|
2040
|
+
}
|
|
2041
|
+
assertOperationCurrent(operation) {
|
|
2042
|
+
if (operation.lifecycleGeneration !== this.lifecycleGeneration) {
|
|
2043
|
+
this.invalidateOperation(operation);
|
|
2044
|
+
}
|
|
2045
|
+
if (operation.invalidationError) {
|
|
2046
|
+
throw operation.invalidationError;
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
assertOperationResultCurrent(key, sessionId, operation) {
|
|
2050
|
+
if (operation.routeToken !== this.routeTokens.get(key)) {
|
|
2051
|
+
this.invalidateOperation(operation);
|
|
2052
|
+
}
|
|
2053
|
+
if (this.toSession.get(key) !== sessionId) {
|
|
2054
|
+
this.invalidateOperation(operation);
|
|
2055
|
+
}
|
|
2056
|
+
this.assertOperationCurrent(operation);
|
|
2057
|
+
}
|
|
2058
|
+
releaseRouteToken(key, operation) {
|
|
2059
|
+
if (this.routeTokens.get(key) === operation.routeToken && !this.toSession.has(key) && !this.creatingSessions.has(key)) {
|
|
2060
|
+
this.routeTokens.delete(key);
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
scheduleDiscardInvalidatedSession(sessionId, operation) {
|
|
2064
|
+
if ([...this.toSession.values()].includes(sessionId))
|
|
2065
|
+
return;
|
|
2066
|
+
try {
|
|
2067
|
+
void this.bridge.discardSession?.(sessionId, operation).catch(() => void 0);
|
|
2068
|
+
} catch {
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
createSessionReservation() {
|
|
2072
|
+
let resolveReservation;
|
|
2073
|
+
let rejectReservation;
|
|
2074
|
+
const promise = new Promise((resolve3, reject) => {
|
|
2075
|
+
resolveReservation = resolve3;
|
|
2076
|
+
rejectReservation = reject;
|
|
2077
|
+
});
|
|
1615
2078
|
return {
|
|
1616
2079
|
promise,
|
|
1617
2080
|
resolve: resolveReservation,
|
|
@@ -1652,6 +2115,7 @@ var ChannelLoopScheduler = class {
|
|
|
1652
2115
|
runningTick;
|
|
1653
2116
|
inFlightJobs = /* @__PURE__ */ new Map();
|
|
1654
2117
|
generation = 0;
|
|
2118
|
+
recoveryEpoch = 0;
|
|
1655
2119
|
constructor(options) {
|
|
1656
2120
|
this.store = options.store;
|
|
1657
2121
|
this.channels = options.channels;
|
|
@@ -1691,6 +2155,14 @@ var ChannelLoopScheduler = class {
|
|
|
1691
2155
|
this.generation++;
|
|
1692
2156
|
this.inFlightJobs.clear();
|
|
1693
2157
|
}
|
|
2158
|
+
/**
|
|
2159
|
+
* Mark that a bridge recovery started. Loop prompts already in flight that
|
|
2160
|
+
* the bridge replacement aborts are cleared instead of counted as agent
|
|
2161
|
+
* failures, so a crash-restart cannot auto-disable a loop.
|
|
2162
|
+
*/
|
|
2163
|
+
markBridgeRecovery() {
|
|
2164
|
+
this.recoveryEpoch++;
|
|
2165
|
+
}
|
|
1694
2166
|
async reconcileStartupState() {
|
|
1695
2167
|
const jobs = await this.store.list();
|
|
1696
2168
|
const staleRunning = jobs.filter((job) => job.runningSince);
|
|
@@ -1761,6 +2233,7 @@ var ChannelLoopScheduler = class {
|
|
|
1761
2233
|
return;
|
|
1762
2234
|
if (this.generation !== generation)
|
|
1763
2235
|
return;
|
|
2236
|
+
let recoveryEpoch = this.recoveryEpoch;
|
|
1764
2237
|
const runningSince = now.toISOString();
|
|
1765
2238
|
let resultPreview;
|
|
1766
2239
|
try {
|
|
@@ -1772,6 +2245,7 @@ var ChannelLoopScheduler = class {
|
|
|
1772
2245
|
await this.clearRunningSince(latestJob.id, runningSince);
|
|
1773
2246
|
return;
|
|
1774
2247
|
}
|
|
2248
|
+
recoveryEpoch = this.recoveryEpoch;
|
|
1775
2249
|
resultPreview = await channel.runLoopPrompt(latestJob, {
|
|
1776
2250
|
timeoutMs: this.loopTimeoutMs,
|
|
1777
2251
|
shouldContinue: /* @__PURE__ */ __name(async () => {
|
|
@@ -1800,6 +2274,19 @@ var ChannelLoopScheduler = class {
|
|
|
1800
2274
|
await this.clearRunningSince(latestJob.id, runningSince);
|
|
1801
2275
|
return;
|
|
1802
2276
|
}
|
|
2277
|
+
if (recoveryEpoch !== this.recoveryEpoch) {
|
|
2278
|
+
try {
|
|
2279
|
+
await this.store.update(latestJob.id, {
|
|
2280
|
+
lastFinishedAt: this.now().toISOString(),
|
|
2281
|
+
lastStatus: "error",
|
|
2282
|
+
lastError: truncateError(err instanceof Error ? err.message : String(err)),
|
|
2283
|
+
runningSince: void 0
|
|
2284
|
+
});
|
|
2285
|
+
} catch {
|
|
2286
|
+
await this.clearRunningSince(latestJob.id, runningSince);
|
|
2287
|
+
}
|
|
2288
|
+
return;
|
|
2289
|
+
}
|
|
1803
2290
|
await this.recordFailure(currentJob2, now, err instanceof Error ? err.message : String(err));
|
|
1804
2291
|
return;
|
|
1805
2292
|
}
|
|
@@ -2258,6 +2745,10 @@ var ChannelBase = class {
|
|
|
2258
2745
|
__name(this, "ChannelBase");
|
|
2259
2746
|
}
|
|
2260
2747
|
config;
|
|
2748
|
+
/**
|
|
2749
|
+
* Recovery invariant: session-resolution and prompt-capture paths must await
|
|
2750
|
+
* waitForBridgeRecovery() immediately before that operation.
|
|
2751
|
+
*/
|
|
2261
2752
|
bridge;
|
|
2262
2753
|
groupGate;
|
|
2263
2754
|
dmGate;
|
|
@@ -2269,6 +2760,8 @@ var ChannelBase = class {
|
|
|
2269
2760
|
memoryScope;
|
|
2270
2761
|
/** Resolved proxy URL, available to subclasses for adapter-specific clients. */
|
|
2271
2762
|
proxy;
|
|
2763
|
+
/** Adapter-owned persistent state directory, when supplied by the runtime. */
|
|
2764
|
+
stateDir;
|
|
2272
2765
|
channelMemory;
|
|
2273
2766
|
memoryIntentClassifier;
|
|
2274
2767
|
channelMemoryRecallObserver;
|
|
@@ -2284,6 +2777,7 @@ var ChannelBase = class {
|
|
|
2284
2777
|
/** Per-session promise chain to serialize prompt + send (followup mode). */
|
|
2285
2778
|
sessionQueues = /* @__PURE__ */ new Map();
|
|
2286
2779
|
registerBridgeEvents;
|
|
2780
|
+
bridgeRecovery;
|
|
2287
2781
|
/**
|
|
2288
2782
|
* Per-session generation, bumped by /clear. A queued followup turn captures the
|
|
2289
2783
|
* generation when it enqueues and bails if /clear bumped it before the turn ran,
|
|
@@ -2564,6 +3058,7 @@ var ChannelBase = class {
|
|
|
2564
3058
|
this.config = config;
|
|
2565
3059
|
this.bridge = bridge;
|
|
2566
3060
|
this.proxy = options?.proxy;
|
|
3061
|
+
this.stateDir = options?.stateDir;
|
|
2567
3062
|
this.identity = Object.freeze(this.resolveIdentity(name, config));
|
|
2568
3063
|
this.memoryScope = Object.freeze(this.resolveMemoryScope(name, config));
|
|
2569
3064
|
this.channelMemory = options?.channelMemory;
|
|
@@ -2572,6 +3067,7 @@ var ChannelBase = class {
|
|
|
2572
3067
|
this.groupHistory = new GroupHistoryStore(options?.groupHistoryPath ?? join5(getGlobalQwenDir(), "channels", `${encodeURIComponent(name)}-group-history.jsonl`));
|
|
2573
3068
|
this.loopController = options?.loopController;
|
|
2574
3069
|
this.observedContacts = options?.observedContacts;
|
|
3070
|
+
this.bridgeRecovery = options?.bridgeRecovery;
|
|
2575
3071
|
this.groupGate = new GroupGate(config.groupPolicy, config.groups);
|
|
2576
3072
|
this.dmGate = new DmGate(config.dmPolicy);
|
|
2577
3073
|
const pairingStore = config.senderPolicy === "pairing" ? new PairingStore(name, config.cwd) : void 0;
|
|
@@ -2740,7 +3236,7 @@ var ChannelBase = class {
|
|
|
2740
3236
|
ensureOutputSegment(sessionId, active) {
|
|
2741
3237
|
if (!active.owner)
|
|
2742
3238
|
return void 0;
|
|
2743
|
-
const segmentId = active.activeSegmentId ??
|
|
3239
|
+
const segmentId = active.activeSegmentId ?? randomUUID2();
|
|
2744
3240
|
const context = this.outputSegmentContext(sessionId, active, segmentId);
|
|
2745
3241
|
if (context)
|
|
2746
3242
|
active.activeSegmentId = segmentId;
|
|
@@ -3005,6 +3501,7 @@ var ChannelBase = class {
|
|
|
3005
3501
|
await this.loopController?.disable(job.id);
|
|
3006
3502
|
throw new Error(`Loop ${job.id} target is no longer authorized.`);
|
|
3007
3503
|
}
|
|
3504
|
+
await this.waitForBridgeRecovery();
|
|
3008
3505
|
const sessionId = await this.router.resolve(this.name, job.target.senderId, job.target.chatId, job.target.threadId, job.cwd, job.target.isGroup);
|
|
3009
3506
|
const label = sanitizeQuotedText(job.label || job.id, 80);
|
|
3010
3507
|
const createdBy = sanitizeSenderName(job.createdBy || "unknown");
|
|
@@ -3062,7 +3559,7 @@ ${promptText}` : promptText;
|
|
|
3062
3559
|
doneResolve = resolve3;
|
|
3063
3560
|
});
|
|
3064
3561
|
const promptState = {
|
|
3065
|
-
runId:
|
|
3562
|
+
runId: randomUUID2(),
|
|
3066
3563
|
cancelled: false,
|
|
3067
3564
|
done,
|
|
3068
3565
|
resolve: doneResolve,
|
|
@@ -3112,6 +3609,7 @@ ${promptText}` : promptText;
|
|
|
3112
3609
|
heldChunks.length = 0;
|
|
3113
3610
|
this.onResponseBoundary(job.target.chatId, sessionId);
|
|
3114
3611
|
}, "onResponseBoundary");
|
|
3612
|
+
await this.waitForBridgeRecovery();
|
|
3115
3613
|
const promptBridge = this.bridge;
|
|
3116
3614
|
promptBridge.on("textChunk", onChunk);
|
|
3117
3615
|
promptBridge.on("responseBoundary", onResponseBoundary);
|
|
@@ -3219,6 +3717,7 @@ ${promptText}` : promptText;
|
|
|
3219
3717
|
}
|
|
3220
3718
|
async runWebhookTask(task, options = {}) {
|
|
3221
3719
|
const target = this.resolveWebhookTaskTarget(task);
|
|
3720
|
+
await this.waitForBridgeRecovery();
|
|
3222
3721
|
const sessionId = await this.router.resolve(this.name, target.senderId, target.chatId, target.threadId, this.config.cwd, target.isGroup, {
|
|
3223
3722
|
routingThreadId: this.webhookRoutingThreadId(task, target)
|
|
3224
3723
|
});
|
|
@@ -3275,7 +3774,7 @@ ${promptText}` : promptText;
|
|
|
3275
3774
|
doneResolve = resolve3;
|
|
3276
3775
|
});
|
|
3277
3776
|
const promptState = {
|
|
3278
|
-
runId:
|
|
3777
|
+
runId: randomUUID2(),
|
|
3279
3778
|
cancelled: false,
|
|
3280
3779
|
done,
|
|
3281
3780
|
resolve: doneResolve,
|
|
@@ -3318,6 +3817,7 @@ ${promptText}` : promptText;
|
|
|
3318
3817
|
releaseHeldChunks();
|
|
3319
3818
|
}
|
|
3320
3819
|
}, "onChunk");
|
|
3820
|
+
await this.waitForBridgeRecovery();
|
|
3321
3821
|
const promptBridge = this.bridge;
|
|
3322
3822
|
promptBridge.on("textChunk", onChunk);
|
|
3323
3823
|
try {
|
|
@@ -5157,6 +5657,17 @@ ${promptText}`;
|
|
|
5157
5657
|
markPreflighted(envelope) {
|
|
5158
5658
|
this.preflightedEnvelopes.add(envelope);
|
|
5159
5659
|
}
|
|
5660
|
+
/** Wait until the currently active bridge recovery, if any, has completed. */
|
|
5661
|
+
async waitForBridgeRecovery() {
|
|
5662
|
+
let completedRecovery;
|
|
5663
|
+
while (true) {
|
|
5664
|
+
const bridgeRecovery = this.bridgeRecovery?.();
|
|
5665
|
+
if (!bridgeRecovery || bridgeRecovery === completedRecovery)
|
|
5666
|
+
return;
|
|
5667
|
+
await bridgeRecovery;
|
|
5668
|
+
completedRecovery = bridgeRecovery;
|
|
5669
|
+
}
|
|
5670
|
+
}
|
|
5160
5671
|
/**
|
|
5161
5672
|
* Process an inbound message after preflight gates have passed.
|
|
5162
5673
|
*
|
|
@@ -5165,6 +5676,7 @@ ${promptText}`;
|
|
|
5165
5676
|
* already preflighted, such as during collect-buffer drain.
|
|
5166
5677
|
*/
|
|
5167
5678
|
async processInbound(envelope) {
|
|
5679
|
+
await this.waitForBridgeRecovery();
|
|
5168
5680
|
if (!this.preflightedEnvelopes.delete(envelope)) {
|
|
5169
5681
|
throw new Error("processInbound called without a successful preflightInbound check.");
|
|
5170
5682
|
}
|
|
@@ -5215,6 +5727,7 @@ ${promptText}`;
|
|
|
5215
5727
|
return;
|
|
5216
5728
|
}
|
|
5217
5729
|
}
|
|
5730
|
+
await this.waitForBridgeRecovery();
|
|
5218
5731
|
const sessionId = await this.router.resolve(this.name, envelope.senderId, envelope.chatId, envelope.threadId, this.config.cwd, envelope.isGroup);
|
|
5219
5732
|
if (bangText.startsWith("!")) {
|
|
5220
5733
|
const cmd = bangText.slice(1).trim();
|
|
@@ -5405,7 +5918,7 @@ ${promptToSend}`;
|
|
|
5405
5918
|
doneResolve = r;
|
|
5406
5919
|
});
|
|
5407
5920
|
const promptState = {
|
|
5408
|
-
runId:
|
|
5921
|
+
runId: randomUUID2(),
|
|
5409
5922
|
owner: {
|
|
5410
5923
|
kind: "channel_user",
|
|
5411
5924
|
id: envelope.senderId
|
|
@@ -5473,6 +5986,7 @@ ${promptToSend}`;
|
|
|
5473
5986
|
void this.notifyOutputSegmentEnd(envelope.chatId, sessionId, segment, "response_boundary");
|
|
5474
5987
|
streamer?.stop();
|
|
5475
5988
|
}, "onResponseBoundary");
|
|
5989
|
+
await this.waitForBridgeRecovery();
|
|
5476
5990
|
const promptBridge = this.bridge;
|
|
5477
5991
|
promptBridge.on("textChunk", onChunk);
|
|
5478
5992
|
promptBridge.on("responseBoundary", onResponseBoundary);
|
|
@@ -5594,578 +6108,135 @@ Ask the bot operator to approve you with:
|
|
|
5594
6108
|
await this.sendThreadMessage(chatId, threadId, "Too many pending pairing requests. Please try again later.");
|
|
5595
6109
|
}
|
|
5596
6110
|
}
|
|
5597
|
-
};
|
|
5598
|
-
function truncateGroupHistoryField(value) {
|
|
5599
|
-
return value.slice(0, GROUP_HISTORY_ENTRY_METADATA_LIMIT);
|
|
5600
|
-
}
|
|
5601
|
-
__name(truncateGroupHistoryField, "truncateGroupHistoryField");
|
|
5602
|
-
function isPromiseLike(value) {
|
|
5603
|
-
return value !== null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
|
|
5604
|
-
}
|
|
5605
|
-
__name(isPromiseLike, "isPromiseLike");
|
|
5606
|
-
function isDebugPayloadEnabled(channelName) {
|
|
5607
|
-
const raw = process.env[DEBUG_PAYLOAD_ENV]?.trim();
|
|
5608
|
-
if (!raw)
|
|
5609
|
-
return false;
|
|
5610
|
-
if (["1", "true", "yes", "all", "*"].includes(raw.toLowerCase())) {
|
|
5611
|
-
return true;
|
|
5612
|
-
}
|
|
5613
|
-
return raw.split(",").map((value) => value.trim()).filter(Boolean).includes(channelName);
|
|
5614
|
-
}
|
|
5615
|
-
__name(isDebugPayloadEnabled, "isDebugPayloadEnabled");
|
|
5616
|
-
function redactPayloadValue(key, value) {
|
|
5617
|
-
if (!key)
|
|
5618
|
-
return value;
|
|
5619
|
-
return SENSITIVE_PAYLOAD_KEY_PATTERN.test(key) ? "[redacted]" : value;
|
|
5620
|
-
}
|
|
5621
|
-
__name(redactPayloadValue, "redactPayloadValue");
|
|
5622
|
-
function truncateLoopLabel(prompt) {
|
|
5623
|
-
const chars = Array.from(prompt);
|
|
5624
|
-
return chars.length > 60 ? `${chars.slice(0, 57).join("")}...` : prompt;
|
|
5625
|
-
}
|
|
5626
|
-
__name(truncateLoopLabel, "truncateLoopLabel");
|
|
5627
|
-
|
|
5628
|
-
// packages/channels/base/dist/PollingChannelBase.js
|
|
5629
|
-
var INITIAL_BACKOFF = 2e3;
|
|
5630
|
-
var MAX_BACKOFF = 3e4;
|
|
5631
|
-
var PollingChannelBase = class extends ChannelBase {
|
|
5632
|
-
static {
|
|
5633
|
-
__name(this, "PollingChannelBase");
|
|
5634
|
-
}
|
|
5635
|
-
cursor;
|
|
5636
|
-
abortController = new AbortController();
|
|
5637
|
-
running = false;
|
|
5638
|
-
consecutiveErrors = 0;
|
|
5639
|
-
abortableSleep(ms) {
|
|
5640
|
-
const signal = this.abortController.signal;
|
|
5641
|
-
return new Promise((resolve3) => {
|
|
5642
|
-
if (signal.aborted) {
|
|
5643
|
-
resolve3();
|
|
5644
|
-
return;
|
|
5645
|
-
}
|
|
5646
|
-
const timer = setTimeout(() => {
|
|
5647
|
-
signal.removeEventListener("abort", onAbort);
|
|
5648
|
-
resolve3();
|
|
5649
|
-
}, ms);
|
|
5650
|
-
const onAbort = /* @__PURE__ */ __name(() => {
|
|
5651
|
-
clearTimeout(timer);
|
|
5652
|
-
resolve3();
|
|
5653
|
-
}, "onAbort");
|
|
5654
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
5655
|
-
});
|
|
5656
|
-
}
|
|
5657
|
-
constructor(name, config, bridge, options) {
|
|
5658
|
-
super(name, config, bridge, options);
|
|
5659
|
-
this.cursor = this.loadCursorFromDisk() ?? this.createInitialCursor();
|
|
5660
|
-
}
|
|
5661
|
-
validateCursor(parsed) {
|
|
5662
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
5663
|
-
return null;
|
|
5664
|
-
return parsed;
|
|
5665
|
-
}
|
|
5666
|
-
get pollInterval() {
|
|
5667
|
-
const configured = this.config.pollInterval;
|
|
5668
|
-
if (typeof configured === "number" && Number.isFinite(configured) && configured > 0) {
|
|
5669
|
-
return configured;
|
|
5670
|
-
}
|
|
5671
|
-
return 6e4;
|
|
5672
|
-
}
|
|
5673
|
-
saveCursor() {
|
|
5674
|
-
const path4 = this.cursorPath();
|
|
5675
|
-
mkdirSync4(join6(getGlobalQwenDir(), "channels"), { recursive: true });
|
|
5676
|
-
const tmp = `${path4}.tmp`;
|
|
5677
|
-
writeFileSync4(tmp, JSON.stringify(this.cursor) + "\n", "utf-8");
|
|
5678
|
-
renameSync4(tmp, path4);
|
|
5679
|
-
}
|
|
5680
|
-
startPollLoop() {
|
|
5681
|
-
if (this.running)
|
|
5682
|
-
return;
|
|
5683
|
-
this.running = true;
|
|
5684
|
-
this.consecutiveErrors = 0;
|
|
5685
|
-
this.abortController = new AbortController();
|
|
5686
|
-
this.runLoop();
|
|
5687
|
-
}
|
|
5688
|
-
stopPollLoop() {
|
|
5689
|
-
this.running = false;
|
|
5690
|
-
this.abortController.abort();
|
|
5691
|
-
}
|
|
5692
|
-
async runLoop() {
|
|
5693
|
-
const signal = this.abortController.signal;
|
|
5694
|
-
while (this.running && !signal.aborted) {
|
|
5695
|
-
try {
|
|
5696
|
-
await this.pollOnce();
|
|
5697
|
-
this.saveCursor();
|
|
5698
|
-
this.consecutiveErrors = 0;
|
|
5699
|
-
} catch (err) {
|
|
5700
|
-
this.consecutiveErrors++;
|
|
5701
|
-
const backoff = Math.min(INITIAL_BACKOFF * 2 ** (this.consecutiveErrors - 1), MAX_BACKOFF);
|
|
5702
|
-
process3.stderr.write(`[Channel:${this.name}] poll error (attempt ${this.consecutiveErrors}), backing off ${backoff}ms: ${err}
|
|
5703
|
-
`);
|
|
5704
|
-
await this.abortableSleep(backoff);
|
|
5705
|
-
continue;
|
|
5706
|
-
}
|
|
5707
|
-
await this.abortableSleep(this.pollInterval);
|
|
5708
|
-
}
|
|
5709
|
-
}
|
|
5710
|
-
loadCursorFromDisk() {
|
|
5711
|
-
try {
|
|
5712
|
-
const raw = readFileSync4(this.cursorPath(), "utf-8").trim();
|
|
5713
|
-
if (!raw)
|
|
5714
|
-
return null;
|
|
5715
|
-
return this.validateCursor(JSON.parse(raw));
|
|
5716
|
-
} catch {
|
|
5717
|
-
return null;
|
|
5718
|
-
}
|
|
5719
|
-
}
|
|
5720
|
-
cursorPath() {
|
|
5721
|
-
const encoded = this.name.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 200);
|
|
5722
|
-
const hash = createHash2("sha256").update(this.name).digest("hex").slice(0, 16);
|
|
5723
|
-
return join6(getGlobalQwenDir(), "channels", `${encoded}-${hash}-poll-cursor.json`);
|
|
5724
|
-
}
|
|
5725
|
-
};
|
|
5726
|
-
|
|
5727
|
-
// packages/channels/base/dist/AcpBridge.js
|
|
5728
|
-
init_esbuild_shims();
|
|
5729
|
-
import { spawn } from "node:child_process";
|
|
5730
|
-
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
5731
|
-
import { Readable, Writable } from "node:stream";
|
|
5732
|
-
import { EventEmitter } from "node:events";
|
|
5733
|
-
var MID_TURN_QUEUE_DRAIN_METHOD = "craft/drainMidTurnQueue";
|
|
5734
|
-
var TODO_STOP_GUARD_CONTINUATION_CLAIM_METHOD = "craft/claimTodoStopGuardContinuation";
|
|
5735
|
-
var ACP_EVENT_LOOP_STALL_RESTART_MS = 5 * 60 * 1e3;
|
|
5736
|
-
var ACP_PERMISSION_RESPONSE_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
5737
|
-
var ACP_EVENT_LOOP_STALL_RE = /^\[perf\] acp agent event loop stall: max=(\d+(?:\.\d+)?)ms/m;
|
|
5738
|
-
function readAvailableCommandAltNames(raw) {
|
|
5739
|
-
if (typeof raw !== "object" || raw === null)
|
|
5740
|
-
return void 0;
|
|
5741
|
-
const record = raw;
|
|
5742
|
-
const meta = record["_meta"];
|
|
5743
|
-
const fromMeta = typeof meta === "object" && meta !== null ? meta["altNames"] : void 0;
|
|
5744
|
-
const source = Array.isArray(record["altNames"]) ? record["altNames"] : Array.isArray(fromMeta) ? fromMeta : void 0;
|
|
5745
|
-
if (!source)
|
|
5746
|
-
return void 0;
|
|
5747
|
-
const names = source.filter((n) => typeof n === "string");
|
|
5748
|
-
return names.length > 0 ? names : void 0;
|
|
5749
|
-
}
|
|
5750
|
-
__name(readAvailableCommandAltNames, "readAvailableCommandAltNames");
|
|
5751
|
-
var AcpBridge = class extends EventEmitter {
|
|
5752
|
-
static {
|
|
5753
|
-
__name(this, "AcpBridge");
|
|
5754
|
-
}
|
|
5755
|
-
child = null;
|
|
5756
|
-
connection = null;
|
|
5757
|
-
options;
|
|
5758
|
-
_availableCommands = [];
|
|
5759
|
-
channelLoopMcpServer;
|
|
5760
|
-
channelLoopToolHandlers = [];
|
|
5761
|
-
knownSessionIds = /* @__PURE__ */ new Set();
|
|
5762
|
-
sessionBindingTokens = /* @__PURE__ */ new Map();
|
|
5763
|
-
channelLoopMcpRegistered = false;
|
|
5764
|
-
channelLoopMcpRegistration = null;
|
|
5765
|
-
pendingPermissions = /* @__PURE__ */ new Map();
|
|
5766
|
-
constructor(options) {
|
|
5767
|
-
super();
|
|
5768
|
-
this.options = options;
|
|
5769
|
-
}
|
|
5770
|
-
get availableCommands() {
|
|
5771
|
-
return this._availableCommands;
|
|
5772
|
-
}
|
|
5773
|
-
async start() {
|
|
5774
|
-
const { cliEntryPath, cwd } = this.options;
|
|
5775
|
-
const args = [
|
|
5776
|
-
...process.execArgv.filter((a) => !/^--inspect(-brk)?($|=)/.test(a)),
|
|
5777
|
-
cliEntryPath,
|
|
5778
|
-
"--acp"
|
|
5779
|
-
];
|
|
5780
|
-
if (this.options.model) {
|
|
5781
|
-
args.push("--model", this.options.model);
|
|
5782
|
-
}
|
|
5783
|
-
this.child = spawn(process.execPath, args, {
|
|
5784
|
-
cwd,
|
|
5785
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
5786
|
-
env: { ...process.env, QWEN_CODE_DISABLE_CRON: "1" },
|
|
5787
|
-
shell: false
|
|
5788
|
-
});
|
|
5789
|
-
this.child.stderr?.on("data", (data) => {
|
|
5790
|
-
const msg = data.toString().trim();
|
|
5791
|
-
if (msg) {
|
|
5792
|
-
process.stderr.write(`[AcpBridge] ${sanitizeLogText(msg, 4096)}
|
|
5793
|
-
`);
|
|
5794
|
-
this.maybeKillOnEventLoopStall(msg);
|
|
5795
|
-
}
|
|
5796
|
-
});
|
|
5797
|
-
this.child.on("exit", (code, signal) => {
|
|
5798
|
-
process.stderr.write(`[AcpBridge] Process exited (code=${code}, signal=${signal})
|
|
5799
|
-
`);
|
|
5800
|
-
this.resolvePendingPermissions();
|
|
5801
|
-
this.knownSessionIds.clear();
|
|
5802
|
-
this.sessionBindingTokens.clear();
|
|
5803
|
-
this.connection = null;
|
|
5804
|
-
this.child = null;
|
|
5805
|
-
this.emit("disconnected", code, signal);
|
|
5806
|
-
});
|
|
5807
|
-
await new Promise((resolve3) => setTimeout(resolve3, 1e3));
|
|
5808
|
-
if (!this.child || this.child.killed) {
|
|
5809
|
-
throw new Error("ACP process failed to start");
|
|
5810
|
-
}
|
|
5811
|
-
const stdout = Readable.toWeb(this.child.stdout);
|
|
5812
|
-
const stdin = Writable.toWeb(this.child.stdin);
|
|
5813
|
-
const stream = ndJsonStream(stdin, stdout);
|
|
5814
|
-
this.connection = new ClientSideConnection(() => ({
|
|
5815
|
-
sessionUpdate: /* @__PURE__ */ __name((params) => {
|
|
5816
|
-
this.handleSessionUpdate(params);
|
|
5817
|
-
return Promise.resolve();
|
|
5818
|
-
}, "sessionUpdate"),
|
|
5819
|
-
requestPermission: /* @__PURE__ */ __name(async (params) => this.requestPermission(params), "requestPermission"),
|
|
5820
|
-
extMethod: /* @__PURE__ */ __name(async (method, params) => this.handleExtMethod(method, params), "extMethod"),
|
|
5821
|
-
extNotification: /* @__PURE__ */ __name(async () => {
|
|
5822
|
-
}, "extNotification")
|
|
5823
|
-
}), stream);
|
|
5824
|
-
await this.connection.initialize({
|
|
5825
|
-
protocolVersion: PROTOCOL_VERSION,
|
|
5826
|
-
clientCapabilities: {}
|
|
5827
|
-
});
|
|
5828
|
-
await this.registerChannelLoopMcpServer();
|
|
5829
|
-
}
|
|
5830
|
-
registerChannelLoopToolHandler(handler) {
|
|
5831
|
-
if (!this.channelLoopToolHandlers.includes(handler)) {
|
|
5832
|
-
this.channelLoopToolHandlers.push(handler);
|
|
5833
|
-
}
|
|
5834
|
-
this.channelLoopMcpServer ??= new ChannelLoopMcpServer({
|
|
5835
|
-
create: /* @__PURE__ */ __name((sessionId, input) => this.resolveChannelLoopToolHandler(sessionId).create(sessionId, input), "create"),
|
|
5836
|
-
list: /* @__PURE__ */ __name((sessionId) => this.resolveChannelLoopToolHandler(sessionId).list(sessionId), "list"),
|
|
5837
|
-
cancel: /* @__PURE__ */ __name((sessionId, id) => this.resolveChannelLoopToolHandler(sessionId).cancel(sessionId, id), "cancel")
|
|
5838
|
-
});
|
|
5839
|
-
void this.registerChannelLoopMcpServer();
|
|
5840
|
-
}
|
|
5841
|
-
async newSession(cwd, _options, bindingToken) {
|
|
5842
|
-
const conn = this.ensureConnection();
|
|
5843
|
-
await this.registerChannelLoopMcpServer();
|
|
5844
|
-
const response = await conn.newSession({ cwd, mcpServers: [] });
|
|
5845
|
-
this.knownSessionIds.add(response.sessionId);
|
|
5846
|
-
this.sessionBindingTokens.set(response.sessionId, bindingToken);
|
|
5847
|
-
return response.sessionId;
|
|
5848
|
-
}
|
|
5849
|
-
async loadSession(sessionId, cwd, _options, bindingToken) {
|
|
5850
|
-
const conn = this.ensureConnection();
|
|
5851
|
-
await this.registerChannelLoopMcpServer();
|
|
5852
|
-
await conn.loadSession({
|
|
5853
|
-
sessionId,
|
|
5854
|
-
cwd,
|
|
5855
|
-
mcpServers: []
|
|
5856
|
-
});
|
|
5857
|
-
this.knownSessionIds.add(sessionId);
|
|
5858
|
-
this.sessionBindingTokens.set(sessionId, bindingToken);
|
|
5859
|
-
return sessionId;
|
|
5860
|
-
}
|
|
5861
|
-
async prompt(sessionId, text, options) {
|
|
5862
|
-
const conn = this.ensureConnection();
|
|
5863
|
-
const chunks = [];
|
|
5864
|
-
let slashCommandOutput = "";
|
|
5865
|
-
const onChunk = /* @__PURE__ */ __name((sid, chunk) => {
|
|
5866
|
-
if (sid === sessionId)
|
|
5867
|
-
chunks.push(chunk);
|
|
5868
|
-
}, "onChunk");
|
|
5869
|
-
const onSlashCommandOutput = /* @__PURE__ */ __name((sid, chunk) => {
|
|
5870
|
-
if (sid === sessionId)
|
|
5871
|
-
slashCommandOutput = chunk;
|
|
5872
|
-
}, "onSlashCommandOutput");
|
|
5873
|
-
const clearChunks = /* @__PURE__ */ __name((sid) => {
|
|
5874
|
-
if (sid === sessionId) {
|
|
5875
|
-
chunks.length = 0;
|
|
5876
|
-
slashCommandOutput = "";
|
|
5877
|
-
}
|
|
5878
|
-
}, "clearChunks");
|
|
5879
|
-
this.on("textChunk", onChunk);
|
|
5880
|
-
this.on("slashCommandOutput", onSlashCommandOutput);
|
|
5881
|
-
this.on("responseBoundary", clearChunks);
|
|
5882
|
-
const prompt = [];
|
|
5883
|
-
if (options?.imageBase64 && options.imageMimeType) {
|
|
5884
|
-
prompt.push({
|
|
5885
|
-
type: "image",
|
|
5886
|
-
data: options.imageBase64,
|
|
5887
|
-
mimeType: options.imageMimeType
|
|
5888
|
-
});
|
|
5889
|
-
}
|
|
5890
|
-
prompt.push({ type: "text", text });
|
|
5891
|
-
try {
|
|
5892
|
-
await conn.prompt({
|
|
5893
|
-
sessionId,
|
|
5894
|
-
prompt
|
|
5895
|
-
});
|
|
5896
|
-
} finally {
|
|
5897
|
-
this.off("textChunk", onChunk);
|
|
5898
|
-
this.off("slashCommandOutput", onSlashCommandOutput);
|
|
5899
|
-
this.off("responseBoundary", clearChunks);
|
|
5900
|
-
}
|
|
5901
|
-
return chunks.join("") || slashCommandOutput;
|
|
5902
|
-
}
|
|
5903
|
-
async cancelSession(sessionId) {
|
|
5904
|
-
const conn = this.ensureConnection();
|
|
5905
|
-
try {
|
|
5906
|
-
await conn.cancel({ sessionId });
|
|
5907
|
-
} finally {
|
|
5908
|
-
this.resolvePendingPermissions(sessionId);
|
|
5909
|
-
}
|
|
5910
|
-
}
|
|
5911
|
-
async discardSession(sessionId, expectedBindingToken) {
|
|
5912
|
-
if (expectedBindingToken !== void 0 && this.sessionBindingTokens.get(sessionId) !== expectedBindingToken) {
|
|
5913
|
-
return;
|
|
5914
|
-
}
|
|
5915
|
-
if (!this.knownSessionIds.delete(sessionId))
|
|
5916
|
-
return;
|
|
5917
|
-
this.sessionBindingTokens.delete(sessionId);
|
|
5918
|
-
this.resolvePendingPermissions(sessionId);
|
|
5919
|
-
const conn = this.connection;
|
|
5920
|
-
if (!conn || !this.isConnected)
|
|
5921
|
-
return;
|
|
5922
|
-
await conn.extMethod("qwen/control/session/close", { sessionId });
|
|
5923
|
-
}
|
|
5924
|
-
async respondToPermission(requestId, response) {
|
|
5925
|
-
const pending = this.pendingPermissions.get(requestId);
|
|
5926
|
-
if (!pending) {
|
|
5927
|
-
return false;
|
|
5928
|
-
}
|
|
5929
|
-
clearTimeout(pending.timeout);
|
|
5930
|
-
this.pendingPermissions.delete(requestId);
|
|
5931
|
-
pending.resolve(response);
|
|
5932
|
-
this.emit("permissionResolved", {
|
|
5933
|
-
requestId,
|
|
5934
|
-
outcome: response.outcome
|
|
5935
|
-
});
|
|
5936
|
-
return true;
|
|
5937
|
-
}
|
|
5938
|
-
stop() {
|
|
5939
|
-
this.resolvePendingPermissions();
|
|
5940
|
-
this.knownSessionIds.clear();
|
|
5941
|
-
this.sessionBindingTokens.clear();
|
|
5942
|
-
if (this.child) {
|
|
5943
|
-
this.child.kill();
|
|
5944
|
-
this.child = null;
|
|
5945
|
-
}
|
|
5946
|
-
this.connection = null;
|
|
5947
|
-
}
|
|
5948
|
-
get isConnected() {
|
|
5949
|
-
return this.child !== null && !this.child.killed && this.child.exitCode === null;
|
|
5950
|
-
}
|
|
5951
|
-
handleSessionUpdate(params) {
|
|
5952
|
-
const { sessionId } = params;
|
|
5953
|
-
const update = params["update"];
|
|
5954
|
-
if (!update)
|
|
5955
|
-
return;
|
|
5956
|
-
const type = update["sessionUpdate"];
|
|
5957
|
-
switch (type) {
|
|
5958
|
-
case "agent_message_chunk": {
|
|
5959
|
-
const meta = update["_meta"];
|
|
5960
|
-
if (typeof meta?.["parentToolCallId"] === "string") {
|
|
5961
|
-
break;
|
|
5962
|
-
}
|
|
5963
|
-
const content = update["content"];
|
|
5964
|
-
if (meta?.["qwenDiscreteMessage"] === true) {
|
|
5965
|
-
if (meta["source"] === "background_notification_response" && meta["rewritten"] !== true && content?.type === "text" && content.text) {
|
|
5966
|
-
this.emit("backgroundResponse", sessionId, content.text);
|
|
5967
|
-
}
|
|
5968
|
-
break;
|
|
5969
|
-
}
|
|
5970
|
-
if (content?.type === "text" && content.text) {
|
|
5971
|
-
this.emit(meta?.["source"] === "slash_command" ? "slashCommandOutput" : "textChunk", sessionId, content.text);
|
|
5972
|
-
}
|
|
5973
|
-
break;
|
|
5974
|
-
}
|
|
5975
|
-
case "tool_call": {
|
|
5976
|
-
const event = {
|
|
5977
|
-
sessionId,
|
|
5978
|
-
toolCallId: update["toolCallId"],
|
|
5979
|
-
kind: update["kind"] || "",
|
|
5980
|
-
title: update["title"] || "",
|
|
5981
|
-
status: update["status"] || "pending",
|
|
5982
|
-
rawInput: update["rawInput"]
|
|
5983
|
-
};
|
|
5984
|
-
if (event.status === "pending" || event.status === "in_progress") {
|
|
5985
|
-
this.emitResponseBoundary(sessionId);
|
|
5986
|
-
}
|
|
5987
|
-
this.emit("toolCall", event);
|
|
5988
|
-
break;
|
|
5989
|
-
}
|
|
5990
|
-
case "plan": {
|
|
5991
|
-
this.emitResponseBoundary(sessionId);
|
|
5992
|
-
break;
|
|
5993
|
-
}
|
|
5994
|
-
case "available_commands_update": {
|
|
5995
|
-
if (Array.isArray(update["availableCommands"])) {
|
|
5996
|
-
this._availableCommands = update["availableCommands"].map((cmd) => {
|
|
5997
|
-
const altNames = readAvailableCommandAltNames(cmd);
|
|
5998
|
-
return altNames ? { ...cmd, altNames } : cmd;
|
|
5999
|
-
});
|
|
6000
|
-
}
|
|
6001
|
-
break;
|
|
6002
|
-
}
|
|
6003
|
-
default:
|
|
6004
|
-
break;
|
|
6005
|
-
}
|
|
6006
|
-
this.emit("sessionUpdate", params);
|
|
6007
|
-
}
|
|
6008
|
-
ensureConnection() {
|
|
6009
|
-
if (!this.connection || !this.isConnected) {
|
|
6010
|
-
throw new Error("Not connected to ACP agent");
|
|
6011
|
-
}
|
|
6012
|
-
return this.connection;
|
|
6111
|
+
};
|
|
6112
|
+
function truncateGroupHistoryField(value) {
|
|
6113
|
+
return value.slice(0, GROUP_HISTORY_ENTRY_METADATA_LIMIT);
|
|
6114
|
+
}
|
|
6115
|
+
__name(truncateGroupHistoryField, "truncateGroupHistoryField");
|
|
6116
|
+
function isPromiseLike(value) {
|
|
6117
|
+
return value !== null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
|
|
6118
|
+
}
|
|
6119
|
+
__name(isPromiseLike, "isPromiseLike");
|
|
6120
|
+
function isDebugPayloadEnabled(channelName) {
|
|
6121
|
+
const raw = process.env[DEBUG_PAYLOAD_ENV]?.trim();
|
|
6122
|
+
if (!raw)
|
|
6123
|
+
return false;
|
|
6124
|
+
if (["1", "true", "yes", "all", "*"].includes(raw.toLowerCase())) {
|
|
6125
|
+
return true;
|
|
6013
6126
|
}
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6127
|
+
return raw.split(",").map((value) => value.trim()).filter(Boolean).includes(channelName);
|
|
6128
|
+
}
|
|
6129
|
+
__name(isDebugPayloadEnabled, "isDebugPayloadEnabled");
|
|
6130
|
+
function redactPayloadValue(key, value) {
|
|
6131
|
+
if (!key)
|
|
6132
|
+
return value;
|
|
6133
|
+
return SENSITIVE_PAYLOAD_KEY_PATTERN.test(key) ? "[redacted]" : value;
|
|
6134
|
+
}
|
|
6135
|
+
__name(redactPayloadValue, "redactPayloadValue");
|
|
6136
|
+
function truncateLoopLabel(prompt) {
|
|
6137
|
+
const chars = Array.from(prompt);
|
|
6138
|
+
return chars.length > 60 ? `${chars.slice(0, 57).join("")}...` : prompt;
|
|
6139
|
+
}
|
|
6140
|
+
__name(truncateLoopLabel, "truncateLoopLabel");
|
|
6141
|
+
|
|
6142
|
+
// packages/channels/base/dist/PollingChannelBase.js
|
|
6143
|
+
var INITIAL_BACKOFF = 2e3;
|
|
6144
|
+
var MAX_BACKOFF = 3e4;
|
|
6145
|
+
var PollingChannelBase = class extends ChannelBase {
|
|
6146
|
+
static {
|
|
6147
|
+
__name(this, "PollingChannelBase");
|
|
6148
|
+
}
|
|
6149
|
+
cursor;
|
|
6150
|
+
abortController = new AbortController();
|
|
6151
|
+
running = false;
|
|
6152
|
+
consecutiveErrors = 0;
|
|
6153
|
+
abortableSleep(ms) {
|
|
6154
|
+
const signal = this.abortController.signal;
|
|
6017
6155
|
return new Promise((resolve3) => {
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
requestId,
|
|
6032
|
-
outcome: response.outcome
|
|
6033
|
-
});
|
|
6034
|
-
}, ACP_PERMISSION_RESPONSE_TIMEOUT_MS);
|
|
6035
|
-
timeout.unref?.();
|
|
6036
|
-
this.pendingPermissions.set(requestId, { sessionId, resolve: resolve3, timeout });
|
|
6037
|
-
this.emitResponseBoundary(sessionId);
|
|
6038
|
-
this.emit("permissionRequest", {
|
|
6039
|
-
requestId,
|
|
6040
|
-
sessionId,
|
|
6041
|
-
request
|
|
6042
|
-
});
|
|
6156
|
+
if (signal.aborted) {
|
|
6157
|
+
resolve3();
|
|
6158
|
+
return;
|
|
6159
|
+
}
|
|
6160
|
+
const timer = setTimeout(() => {
|
|
6161
|
+
signal.removeEventListener("abort", onAbort);
|
|
6162
|
+
resolve3();
|
|
6163
|
+
}, ms);
|
|
6164
|
+
const onAbort = /* @__PURE__ */ __name(() => {
|
|
6165
|
+
clearTimeout(timer);
|
|
6166
|
+
resolve3();
|
|
6167
|
+
}, "onAbort");
|
|
6168
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
6043
6169
|
});
|
|
6044
6170
|
}
|
|
6045
|
-
|
|
6046
|
-
|
|
6171
|
+
constructor(name, config, bridge, options) {
|
|
6172
|
+
super(name, config, bridge, options);
|
|
6173
|
+
this.cursor = this.loadCursorFromDisk() ?? this.createInitialCursor();
|
|
6047
6174
|
}
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
for (const [requestId, pending] of this.pendingPermissions) {
|
|
6053
|
-
if (sessionId !== void 0 && pending.sessionId !== sessionId) {
|
|
6054
|
-
continue;
|
|
6055
|
-
}
|
|
6056
|
-
clearTimeout(pending.timeout);
|
|
6057
|
-
this.pendingPermissions.delete(requestId);
|
|
6058
|
-
pending.resolve(response);
|
|
6059
|
-
this.emit("permissionResolved", {
|
|
6060
|
-
requestId,
|
|
6061
|
-
outcome: response.outcome
|
|
6062
|
-
});
|
|
6063
|
-
}
|
|
6175
|
+
validateCursor(parsed) {
|
|
6176
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
6177
|
+
return null;
|
|
6178
|
+
return parsed;
|
|
6064
6179
|
}
|
|
6065
|
-
|
|
6066
|
-
const
|
|
6067
|
-
if (
|
|
6068
|
-
return;
|
|
6069
|
-
const maxMs = Number(match[1]);
|
|
6070
|
-
if (!Number.isFinite(maxMs) || maxMs < ACP_EVENT_LOOP_STALL_RESTART_MS) {
|
|
6071
|
-
return;
|
|
6072
|
-
}
|
|
6073
|
-
const child = this.child;
|
|
6074
|
-
if (!child || child.killed || child.exitCode !== null) {
|
|
6075
|
-
return;
|
|
6180
|
+
get pollInterval() {
|
|
6181
|
+
const configured = this.config.pollInterval;
|
|
6182
|
+
if (typeof configured === "number" && Number.isFinite(configured) && configured > 0) {
|
|
6183
|
+
return configured;
|
|
6076
6184
|
}
|
|
6077
|
-
|
|
6078
|
-
`);
|
|
6079
|
-
child.kill("SIGKILL");
|
|
6185
|
+
return 6e4;
|
|
6080
6186
|
}
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
}
|
|
6085
|
-
|
|
6086
|
-
|
|
6187
|
+
saveCursor() {
|
|
6188
|
+
const path4 = this.cursorPath();
|
|
6189
|
+
mkdirSync4(join6(getGlobalQwenDir(), "channels"), { recursive: true });
|
|
6190
|
+
const tmp = `${path4}.tmp`;
|
|
6191
|
+
writeFileSync4(tmp, JSON.stringify(this.cursor) + "\n", "utf-8");
|
|
6192
|
+
renameSync4(tmp, path4);
|
|
6193
|
+
}
|
|
6194
|
+
startPollLoop() {
|
|
6195
|
+
if (this.running)
|
|
6087
6196
|
return;
|
|
6088
|
-
|
|
6089
|
-
this.
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6197
|
+
this.running = true;
|
|
6198
|
+
this.consecutiveErrors = 0;
|
|
6199
|
+
this.abortController = new AbortController();
|
|
6200
|
+
this.runLoop();
|
|
6201
|
+
}
|
|
6202
|
+
stopPollLoop() {
|
|
6203
|
+
this.running = false;
|
|
6204
|
+
this.abortController.abort();
|
|
6205
|
+
}
|
|
6206
|
+
async runLoop() {
|
|
6207
|
+
const signal = this.abortController.signal;
|
|
6208
|
+
while (this.running && !signal.aborted) {
|
|
6209
|
+
try {
|
|
6210
|
+
await this.pollOnce();
|
|
6211
|
+
this.saveCursor();
|
|
6212
|
+
this.consecutiveErrors = 0;
|
|
6213
|
+
} catch (err) {
|
|
6214
|
+
this.consecutiveErrors++;
|
|
6215
|
+
const backoff = Math.min(INITIAL_BACKOFF * 2 ** (this.consecutiveErrors - 1), MAX_BACKOFF);
|
|
6216
|
+
process3.stderr.write(`[Channel:${this.name}] poll error (attempt ${this.consecutiveErrors}), backing off ${backoff}ms: ${err}
|
|
6100
6217
|
`);
|
|
6101
|
-
|
|
6218
|
+
await this.abortableSleep(backoff);
|
|
6219
|
+
continue;
|
|
6102
6220
|
}
|
|
6103
|
-
this.
|
|
6104
|
-
}).catch((error) => {
|
|
6105
|
-
this.channelLoopMcpRegistered = false;
|
|
6106
|
-
process.stderr.write(`[AcpBridge] Failed to register channel loop MCP server: ${error instanceof Error ? error.message : String(error)}
|
|
6107
|
-
`);
|
|
6108
|
-
}).finally(() => {
|
|
6109
|
-
this.channelLoopMcpRegistration = null;
|
|
6110
|
-
});
|
|
6111
|
-
await this.channelLoopMcpRegistration;
|
|
6112
|
-
}
|
|
6113
|
-
async handleExtMethod(method, params) {
|
|
6114
|
-
if (method === CLIENT_MCP_MESSAGE_METHOD) {
|
|
6115
|
-
return this.handleClientMcpMessage(params);
|
|
6116
|
-
}
|
|
6117
|
-
if (method === MID_TURN_QUEUE_DRAIN_METHOD) {
|
|
6118
|
-
return { messages: [], hasQueuedPrompt: false };
|
|
6119
|
-
}
|
|
6120
|
-
if (method === TODO_STOP_GUARD_CONTINUATION_CLAIM_METHOD) {
|
|
6121
|
-
const sessionId = typeof params["sessionId"] === "string" ? params["sessionId"] : "";
|
|
6122
|
-
return {
|
|
6123
|
-
claimed: this.knownSessionIds.has(sessionId),
|
|
6124
|
-
hasQueuedPrompt: false
|
|
6125
|
-
};
|
|
6221
|
+
await this.abortableSleep(this.pollInterval);
|
|
6126
6222
|
}
|
|
6127
|
-
throw new Error(`Method not found: ${method}`);
|
|
6128
6223
|
}
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
const payload = params["payload"];
|
|
6138
|
-
if (typeof payload !== "object" || payload === null) {
|
|
6139
|
-
throw new Error("Invalid client MCP payload.");
|
|
6140
|
-
}
|
|
6141
|
-
const sessionId = typeof params["sessionId"] === "string" ? params["sessionId"] : void 0;
|
|
6142
|
-
const response = await this.channelLoopMcpServer.handleMessage(payload, { sessionId });
|
|
6143
|
-
if (!response) {
|
|
6144
|
-
return { payload: { jsonrpc: "2.0", id: 0, result: {} } };
|
|
6224
|
+
loadCursorFromDisk() {
|
|
6225
|
+
try {
|
|
6226
|
+
const raw = readFileSync4(this.cursorPath(), "utf-8").trim();
|
|
6227
|
+
if (!raw)
|
|
6228
|
+
return null;
|
|
6229
|
+
return this.validateCursor(JSON.parse(raw));
|
|
6230
|
+
} catch {
|
|
6231
|
+
return null;
|
|
6145
6232
|
}
|
|
6146
|
-
return { payload: response };
|
|
6147
6233
|
}
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
}
|
|
6152
|
-
const handler = this.channelLoopToolHandlers.find((candidate) => candidate.canHandle?.(sessionId) === true);
|
|
6153
|
-
if (handler)
|
|
6154
|
-
return handler;
|
|
6155
|
-
throw new Error(this.channelLoopToolHandlers.length === 0 ? "No channel loop tool handler is registered." : `No channel loop handler matched session ${sessionId}.`);
|
|
6234
|
+
cursorPath() {
|
|
6235
|
+
const encoded = this.name.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 200);
|
|
6236
|
+
const hash = createHash2("sha256").update(this.name).digest("hex").slice(0, 16);
|
|
6237
|
+
return join6(getGlobalQwenDir(), "channels", `${encoded}-${hash}-poll-cursor.json`);
|
|
6156
6238
|
}
|
|
6157
6239
|
};
|
|
6158
|
-
function isSkippedMcpRegistration(result) {
|
|
6159
|
-
return typeof result === "object" && result !== null && result.skipped === true;
|
|
6160
|
-
}
|
|
6161
|
-
__name(isSkippedMcpRegistration, "isSkippedMcpRegistration");
|
|
6162
|
-
function formatSkippedRegistrationReason(result) {
|
|
6163
|
-
if (typeof result !== "object" || result === null)
|
|
6164
|
-
return ".";
|
|
6165
|
-
const reason = result.reason;
|
|
6166
|
-
return typeof reason === "string" && reason.length > 0 ? `: ${sanitizeLogText(reason, 256)}` : ".";
|
|
6167
|
-
}
|
|
6168
|
-
__name(formatSkippedRegistrationReason, "formatSkippedRegistrationReason");
|
|
6169
6240
|
|
|
6170
6241
|
// packages/channels/base/dist/DaemonChannelBridge.js
|
|
6171
6242
|
init_esbuild_shims();
|
|
@@ -7058,9 +7129,11 @@ __name(isTerminalTaskLifecycleType, "isTerminalTaskLifecycleType");
|
|
|
7058
7129
|
export {
|
|
7059
7130
|
resolvePath,
|
|
7060
7131
|
getGlobalQwenDir,
|
|
7132
|
+
getWorkspaceScopeDirName,
|
|
7061
7133
|
ChannelProactiveDeliveryError,
|
|
7062
7134
|
isChannelProactiveDeliveryError,
|
|
7063
7135
|
PairingStore,
|
|
7136
|
+
truncateCodePoints,
|
|
7064
7137
|
sanitizeSenderName,
|
|
7065
7138
|
sanitizePromptText,
|
|
7066
7139
|
sanitizeLogText,
|
|
@@ -7069,6 +7142,7 @@ export {
|
|
|
7069
7142
|
ChannelBase,
|
|
7070
7143
|
PollingChannelBase,
|
|
7071
7144
|
CHANNEL_LOOP_MCP_SERVER_NAME,
|
|
7145
|
+
ACP_EVENT_LOOP_STALL_RESTART_MS,
|
|
7072
7146
|
AcpBridge,
|
|
7073
7147
|
DaemonChannelBridge,
|
|
7074
7148
|
ChannelLoopStore,
|