@qwen-code/qwen-code 0.19.6 → 0.19.7-nightly.20260708.394c1a289
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/model-providers.md +5 -1
- package/bundled/qc-helper/docs/configuration/settings.md +33 -1
- package/bundled/qc-helper/docs/features/channels/_meta.ts +1 -0
- package/bundled/qc-helper/docs/features/channels/overview.md +41 -31
- package/bundled/qc-helper/docs/features/channels/wecom.md +111 -0
- package/bundled/qc-helper/docs/features/code-review.md +16 -6
- package/bundled/qc-helper/docs/features/commands.md +1 -1
- package/bundled/qc-helper/docs/features/hooks.md +6 -0
- package/bundled/qc-helper/docs/features/skills.md +5 -3
- package/bundled/qc-helper/docs/integration-github-action.md +1 -1
- package/bundled/review/DESIGN.md +50 -18
- package/bundled/review/SKILL.md +102 -12
- package/chunks/{MaxSizedBox-SK7CUWZQ.js → MaxSizedBox-XS37LWTD.js} +31 -29
- package/chunks/{StandaloneSessionPicker-G44HGSAP.js → StandaloneSessionPicker-LPALJTKN.js} +44 -41
- package/chunks/{acpAgent-PKCTYWJV.js → acpAgent-4VE3FGJX.js} +769 -366
- package/chunks/{agent-IYMHUXWB.js → agent-CCHERW6L.js} +24 -24
- package/chunks/{agent-headless-L2DAW6I5.js → agent-headless-55N6UBUU.js} +24 -24
- package/chunks/{anthropicContentGenerator-H5ZOEENQ.js → anthropicContentGenerator-AKYPSZIV.js} +7 -7
- package/chunks/{artifact-tool-SLYPWG3C.js → artifact-tool-D233YJFH.js} +27 -8
- package/chunks/{askUserQuestion-OKPTECON.js → askUserQuestion-MKL5ZTMC.js} +2 -2
- package/chunks/{bridge-DF3IJQ5V.js → bridge-U6HXOHE7.js} +34 -32
- package/chunks/{channel-worker-supervisor-JUYQ5Z4R.js → channel-worker-supervisor-CRCVYQIE.js} +1 -1
- package/chunks/{chunk-AVQDCKNF.js → chunk-22MNNNXE.js} +4 -0
- package/chunks/chunk-2DYY5XUZ.js +8223 -0
- package/chunks/chunk-2FHWCKWA.js +262 -0
- package/chunks/{chunk-6BEOH4KK.js → chunk-2NPS2UDH.js} +767 -122
- package/chunks/{chunk-N2IJLN2L.js → chunk-2SWMCXVR.js} +13 -5
- package/chunks/{chunk-OBVBTAX5.js → chunk-2YELHTUE.js} +2 -2
- package/chunks/chunk-332PWN27.js +19 -0
- package/chunks/{chunk-CLQPBI4H.js → chunk-3B26UVCX.js} +132 -26
- package/chunks/{chunk-HCFSKO6K.js → chunk-3QDZDAJG.js} +1 -1
- package/chunks/{chunk-G7OQ3RFJ.js → chunk-42XGFRJS.js} +3 -3
- package/chunks/{chunk-SL2P3JUA.js → chunk-43W4YSO6.js} +1 -12
- package/chunks/{chunk-JA5KWZTJ.js → chunk-44NROQYV.js} +5 -3
- package/chunks/{chunk-BOAWGUY6.js → chunk-4AFOWB47.js} +121 -49
- package/chunks/{chunk-MOUXUCXQ.js → chunk-4KJ2BPIT.js} +1 -1
- package/chunks/{chunk-YTWX5TT2.js → chunk-5FL2K3ZB.js} +1 -1
- package/chunks/{chunk-UNXGEDX2.js → chunk-5OSSOBZC.js} +133 -25
- package/chunks/{chunk-JN2VJ33O.js → chunk-7I4MGFBN.js} +966 -121
- package/chunks/{chunk-T42ACHFI.js → chunk-7LTB54MK.js} +1 -1
- package/chunks/chunk-A2X3KLC3.js +3807 -0
- package/chunks/{chunk-EWALXUAD.js → chunk-AGHU5VRK.js} +8 -0
- package/chunks/{chunk-GLPV75ES.js → chunk-AHNJGLFV.js} +2 -2
- package/chunks/{chunk-IUSKVKH5.js → chunk-AXTPFMRS.js} +1 -1
- package/chunks/{chunk-T22H4V4R.js → chunk-B34OV7K2.js} +1 -1
- package/chunks/{chunk-FZ5NVRVK.js → chunk-B5FOCTEZ.js} +3 -3
- package/chunks/{chunk-3UC7ZC43.js → chunk-B6WUEODT.js} +7 -7
- package/chunks/{chunk-MJKEQSZI.js → chunk-BGL43SJ3.js} +1 -1
- package/chunks/{chunk-OAYOHSGP.js → chunk-BHRU4SDF.js} +4 -4
- package/chunks/{chunk-YPCPZSZB.js → chunk-CCGOFQV3.js} +56 -4
- package/chunks/{chunk-FDY3XOTM.js → chunk-CR5IU2RX.js} +4 -4
- package/chunks/{chunk-LWGLBVWA.js → chunk-CSOTA7KX.js} +1 -1
- package/chunks/{chunk-AGZF43UV.js → chunk-E7HM76PR.js} +7153 -4406
- package/chunks/{chunk-NIOPRZNN.js → chunk-E7KNELIZ.js} +1 -1
- package/chunks/{chunk-YSU33ACT.js → chunk-ELMPRVCB.js} +5 -13
- package/chunks/{chunk-SEZC2725.js → chunk-ER3BKOLB.js} +13 -0
- package/chunks/{chunk-3VJ7MT3N.js → chunk-EXPMGZZV.js} +14 -3
- package/chunks/{chunk-DBF3HTLT.js → chunk-FB2ULV4M.js} +4 -4
- package/chunks/{chunk-RZORUN6X.js → chunk-FEJ2FZ3U.js} +1 -1
- package/chunks/chunk-FK77ILF5.js +2477 -0
- package/chunks/{chunk-DMSNJDWG.js → chunk-FV7425LN.js} +38 -3
- package/chunks/{chunk-ALX6BUYW.js → chunk-G7ZG7CA6.js} +186 -27
- package/chunks/{chunk-2INJBYLJ.js → chunk-GIY6JTTC.js} +6 -6
- package/chunks/{chunk-D5KISTLW.js → chunk-H6EMK6QK.js} +1 -1
- package/chunks/{chunk-KYTQOCDE.js → chunk-HW4N45EE.js} +17 -46
- package/chunks/{chunk-VXGPYF7U.js → chunk-IFEWJXFS.js} +1 -1
- package/chunks/chunk-II73RK2S.js +158 -0
- package/chunks/{chunk-TENOUWY2.js → chunk-IJRL2S3D.js} +3 -3
- package/chunks/{chunk-GQBRGBLS.js → chunk-J43WPR6U.js} +9 -9
- package/chunks/{chunk-SRNPMG7J.js → chunk-JYOWO2F7.js} +16 -10
- package/chunks/{chunk-57NRQM3P.js → chunk-K34DD7FH.js} +2 -2
- package/chunks/{chunk-O2RJARYU.js → chunk-KKWO4IX2.js} +1409 -4088
- package/chunks/{chunk-SOT7OMGL.js → chunk-KT3SI3CQ.js} +2 -2
- package/chunks/{chunk-NUK2PFSU.js → chunk-L7IWK7JY.js} +1 -1
- package/chunks/{chunk-MHQKYQDS.js → chunk-LC4SMGYY.js} +158 -3
- package/chunks/chunk-LGFAYIKX.js +115 -0
- package/chunks/{chunk-MBWLP5OT.js → chunk-LKXDC3AW.js} +162 -40
- package/chunks/{chunk-7Z6UEJA4.js → chunk-ME344634.js} +5 -2
- package/chunks/{chunk-V6NH3CUC.js → chunk-MOQOAH7N.js} +3 -3
- package/chunks/{chunk-FPHA7WIZ.js → chunk-MYZWWRVW.js} +3 -3
- package/chunks/{chunk-YHTNWGRR.js → chunk-MZ7BABX3.js} +1 -1
- package/chunks/chunk-N74BA4VP.js +949 -0
- package/chunks/{chunk-B236XPGF.js → chunk-NQKLOAVJ.js} +29 -2
- package/chunks/{chunk-LXFCHX4X.js → chunk-OU6FPTJZ.js} +312 -25
- package/chunks/{chunk-HTWHAN44.js → chunk-PC4ZXHU2.js} +1 -1
- package/chunks/{chunk-3X76L4UF.js → chunk-PLGONYEL.js} +5 -5
- package/chunks/{chunk-WXUDZHOJ.js → chunk-PVXPDMGP.js} +10 -2
- package/chunks/{chunk-ZO5EA3XJ.js → chunk-QCSLZO6J.js} +1 -1
- package/chunks/{chunk-CEYA7K7C.js → chunk-QHT5USLK.js} +62 -12
- package/chunks/{chunk-NPQWQYU6.js → chunk-QYNNN7BK.js} +1 -1
- package/chunks/{chunk-N4YJQTRL.js → chunk-RFIL6REB.js} +71 -2
- package/chunks/{chunk-KSPVDFE4.js → chunk-SAIILPQ3.js} +0 -3
- package/chunks/{chunk-OIFSBXSP.js → chunk-SJHAUELE.js} +3 -3
- package/chunks/{chunk-BZHLWC6T.js → chunk-SUAKHHK7.js} +2 -2
- package/chunks/{chunk-QOKV6AO4.js → chunk-T2EJHSE3.js} +184 -147
- package/chunks/{chunk-ZI4MGBB5.js → chunk-TBAZMRW3.js} +1 -1
- package/chunks/{chunk-6QVRLKTZ.js → chunk-TBLJNMI7.js} +7 -7
- package/chunks/{chunk-J6ABRKAB.js → chunk-TDGF35EN.js} +2 -2
- package/chunks/chunk-TEYGKLIZ.js +167 -0
- package/chunks/{chunk-TOAUDIS7.js → chunk-TLWA7TPR.js} +6 -2
- package/chunks/chunk-UIDXQNMV.js +13 -0
- package/chunks/{chunk-MX7CTJHR.js → chunk-UZCT5OLN.js} +9 -9
- package/chunks/{chunk-LM3YCCZJ.js → chunk-VEB3NYSO.js} +1 -1
- package/chunks/{chunk-UTL7KABL.js → chunk-VQJ64BIQ.js} +7 -7
- package/chunks/{chunk-C2QKQZEQ.js → chunk-VTCPREC7.js} +3 -3
- package/chunks/{chunk-5ES5YPED.js → chunk-VZ7HY3YU.js} +1 -1
- package/chunks/{chunk-QOMAHFYI.js → chunk-W7EEOR7D.js} +3 -3
- package/chunks/{chunk-NVFJSIAX.js → chunk-WMPVYQ4P.js} +1 -1
- package/chunks/{chunk-V5VTULL5.js → chunk-X265R5C6.js} +3 -3
- package/chunks/{chunk-LKWAVAB5.js → chunk-X2XNQQJI.js} +2 -2
- package/chunks/{chunk-MJKMIQMK.js → chunk-X3YGAX7V.js} +1 -1
- package/chunks/{chunk-5WQF27GW.js → chunk-XQ2GVLNJ.js} +1 -1
- package/chunks/chunk-XUSONNJ4.js +28 -0
- package/chunks/{chunk-DXXNMPJH.js → chunk-YMWIKAEE.js} +460 -93
- package/chunks/{chunk-A34EPIUV.js → chunk-YYU6H66M.js} +18 -15
- package/chunks/{chunk-XH7H6YG4.js → chunk-ZK5EI6NR.js} +4 -107
- package/chunks/{computer-use-5S3NVXRA.js → computer-use-4CTF6WS3.js} +24 -24
- package/chunks/{contextCommand-DPBALZFK.js → contextCommand-Z3XWQCY6.js} +28 -27
- package/chunks/{cron-create-7JYUDTUY.js → cron-create-JSXEIPVS.js} +4 -4
- package/chunks/{cron-delete-YPULQQAK.js → cron-delete-OIFVEPGU.js} +4 -4
- package/chunks/{cron-list-HJIW5MHP.js → cron-list-D4ZX4IOE.js} +11 -7
- package/chunks/{daemon-7XYW4BC3.js → daemon-F3I2RXP6.js} +208 -63
- package/chunks/daemon-status-provider-6A4QCG6Q.js +74 -0
- package/chunks/dist-573HNSVO.js +3217 -0
- package/chunks/{dist-ABQ7VWRR.js → dist-FQ5WRJPL.js} +2 -2
- package/chunks/{dist-JPSVN367.js → dist-IMFFBMAT.js} +6 -3792
- package/chunks/{dist-BV56HW3W.js → dist-OT7G2WNM.js} +250 -1
- package/chunks/{dist-AW5ZYMUI.js → dist-PKFTNUHO.js} +10 -11
- package/chunks/{dist-AAO65DPC.js → dist-YTRKAVUB.js} +1 -1
- package/chunks/{earlyInputCapture-GGPV4IFT.js → earlyInputCapture-7O47HW62.js} +25 -25
- package/chunks/{edit-HJ7ISN4C.js → edit-E32KWLF2.js} +25 -25
- package/chunks/{en-XR3S5VWY.js → en-GA2RMXY5.js} +6 -1
- package/chunks/{enter-worktree-2BQT675D.js → enter-worktree-ZKFFMHUD.js} +24 -24
- package/chunks/{enterPlanMode-SJXHJ3WD.js → enterPlanMode-HLDC6NGX.js} +24 -24
- package/chunks/{errors-EVGER4AZ.js → errors-MTIDDXX7.js} +26 -26
- package/chunks/{exit-worktree-RPDDOSPM.js → exit-worktree-YHY72XA2.js} +24 -24
- package/chunks/{exitPlanMode-BQEROAJ7.js → exitPlanMode-JNXKBZ2C.js} +24 -24
- package/chunks/{fast-path-FX2ICJ2O.js → fast-path-DVAGBROV.js} +5 -2
- package/chunks/{fr-MBMNQSQ6.js → fr-GWNQG4XH.js} +4 -0
- package/chunks/{gemini-EHUXCOUI.js → gemini-2TMHJVRC.js} +444 -256
- package/chunks/{geminiContentGenerator-YDUUKAM2.js → geminiContentGenerator-WCH4GWD3.js} +4 -4
- package/chunks/{glob-3VIGV2DL.js → glob-I6Y4AGKO.js} +52 -28
- package/chunks/{grep-4D3S4NUC.js → grep-3P2J3LU5.js} +24 -24
- package/chunks/handleAutoUpdate-4IVRMRG4.js +65 -0
- package/chunks/{initializer-QZTZSSLW.js → initializer-DVNCPVTC.js} +31 -29
- package/chunks/{ja-HFV5QCHR.js → ja-2HOTA47X.js} +4 -0
- package/chunks/{keychain-token-storage-GPAKRDKQ.js → keychain-token-storage-UJV5XP4Z.js} +2 -2
- package/chunks/{list-WQ24EAMK.js → list-4D6QK2KZ.js} +30 -30
- package/chunks/{loadedSettingsAdapter-XV74KOSR.js → loadedSettingsAdapter-GNGELH6Q.js} +28 -28
- package/chunks/{loop-wakeup-34YK7T7O.js → loop-wakeup-OHWEMFP5.js} +5 -5
- package/chunks/{ls-MPD5OJUX.js → ls-L654WOAT.js} +4 -4
- package/chunks/{lsp-72PWTDW7.js → lsp-M35KE54Y.js} +2 -2
- package/chunks/{mcp-CO3MKM7N.js → mcp-2IL72YJP.js} +28 -28
- package/chunks/{monitor-GB56T65A.js → monitor-BLVCZAVX.js} +32 -27
- package/chunks/nonInteractiveCli-UD3V7UOO.js +116 -0
- package/chunks/{notebook-edit-VMXAAE3R.js → notebook-edit-5TVMWIWY.js} +25 -25
- package/chunks/{openaiContentGenerator-GM77FLII.js → openaiContentGenerator-5LJ5A7YY.js} +12 -12
- package/chunks/{pidfile-HFXSHR64.js → pidfile-AN5JQJ2T.js} +25 -25
- package/chunks/{qwenContentGenerator-QSCXQIKJ.js → qwenContentGenerator-E2JSB4FJ.js} +26 -26
- package/chunks/{qwenOAuth2-LWLA6MQG.js → qwenOAuth2-ZV7YLMND.js} +5 -5
- package/chunks/{read-file-SVH6CGQY.js → read-file-N5DEFUDU.js} +8 -8
- package/chunks/{read-mcp-resource-OWOBJY72.js → read-mcp-resource-CNQHBSJM.js} +2 -2
- package/chunks/{record-artifact-URK3YUDJ.js → record-artifact-PP3LKHTS.js} +2 -2
- package/chunks/{ripGrep-JD5O2FLX.js → ripGrep-BLJHTHKE.js} +24 -24
- package/chunks/{run-qwen-serve-FADLRJUN.js → run-qwen-serve-4JDO64RN.js} +785 -35
- package/chunks/{scheduler-Q65SB7DJ.js → scheduler-FQR4XA7J.js} +24 -24
- package/chunks/{send-message-L7OLQEKH.js → send-message-OA6MV4EU.js} +3 -3
- package/chunks/{serve-6ZXAMQ4M.js → serve-ZOCFC7YM.js} +29 -29
- package/chunks/{server-WKDQA6XR.js → server-GEOYEPYT.js} +2504 -439
- package/chunks/{session-STWOX7RA.js → session-NJCONHGX.js} +66 -61
- package/chunks/{settings-LC4CCWQQ.js → settings-QBGGXANI.js} +27 -27
- package/chunks/{shell-FEG3RLPW.js → shell-2DVPSEKT.js} +24 -24
- package/chunks/{skill-XTXAMDAP.js → skill-HIPXUHV7.js} +24 -12
- package/chunks/{spawnChannel-B6JJPBPV.js → spawnChannel-3JYXY52G.js} +27 -27
- package/chunks/{src-HITDGVRM.js → src-NLEX4CUC.js} +91 -36
- package/chunks/{startInteractiveUI-IT423PW4.js → startInteractiveUI-TYGZIU27.js} +6213 -14273
- package/chunks/{syntheticOutput-J3MLCQAR.js → syntheticOutput-F7LSDCAR.js} +3 -3
- package/chunks/{task-create-ELEBZ7H2.js → task-create-RPFZNB4B.js} +7 -7
- package/chunks/{task-list-IEUZLF3Y.js → task-list-TGGJX7EA.js} +5 -5
- package/chunks/{task-stop-QEZW3UDY.js → task-stop-W7NGTMYQ.js} +2 -2
- package/chunks/{task-update-DW36J5P5.js → task-update-5WUG5MVQ.js} +7 -7
- package/chunks/{team-create-DAG6SNHZ.js → team-create-MGX57SZJ.js} +24 -24
- package/chunks/{team-delete-SVITIMBO.js → team-delete-UHQQ2QQX.js} +5 -5
- package/chunks/{team-plan-approval-DRDY5OPC.js → team-plan-approval-BO2N7J3E.js} +24 -24
- package/chunks/{theme-manager-N67EZC6B.js → theme-manager-WU7LOA64.js} +25 -25
- package/chunks/{todoWrite-SFFQHZYY.js → todoWrite-C4ZAMSR3.js} +4 -4
- package/chunks/{tool-search-CMDJ5HGM.js → tool-search-CLBKVEVK.js} +11 -28
- package/chunks/{trustedFolders-JD4QX6UJ.js → trustedFolders-GAQM2GHB.js} +25 -25
- package/chunks/updateCheck-RIK2YIQE.js +65 -0
- package/chunks/{validateNonInterActiveAuth-ADDCGZ6A.js → validateNonInterActiveAuth-NS3GBIJZ.js} +61 -56
- package/chunks/{version-VAONYVE5.js → version-B3RSLP54.js} +2 -1
- package/chunks/{web-fetch-SXAXJHIO.js → web-fetch-UYGMOHW4.js} +5 -5
- package/chunks/{workflow-DG2BR42M.js → workflow-7APPIGND.js} +25 -25
- package/chunks/{workspace-providers-status-XGW5OZTD.js → workspace-providers-status-SC67GLMY.js} +30 -30
- package/chunks/{workspace-service-GX3GGZLU.js → workspace-service-ZJHF22WA.js} +35 -35
- package/chunks/{workspace-skills-status-LFK5WV2V.js → workspace-skills-status-NJEK6P6Y.js} +29 -29
- package/chunks/{write-file-UR7FXWOU.js → write-file-R6Z3VLRY.js} +25 -25
- package/chunks/{zh-FSVJTXA4.js → zh-7XVVRTDH.js} +6 -1
- package/chunks/{zh-TW-66MG4LWL.js → zh-TW-DPH554OG.js} +6 -1
- package/cli.js +19 -11
- package/locales/en.js +9 -2
- package/locales/fr.js +6 -0
- package/locales/ja.js +6 -0
- package/locales/zh-TW.js +9 -2
- package/locales/zh.js +9 -2
- package/node_modules/@qwen-code/audio-capture/package.json +1 -1
- package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-arm64/@qwen-code+audio-capture.node +0 -0
- package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-x64/@qwen-code+audio-capture.node +0 -0
- package/node_modules/@qwen-code/audio-capture/prebuilds/win32-x64/@qwen-code+audio-capture.node +0 -0
- package/package.json +3 -3
- package/web-shell/assets/{arc-CUpe0ibu.js → arc-C7hjaa7g.js} +1 -1
- package/web-shell/assets/{architectureDiagram-3BPJPVTR-JvyrY_mK.js → architectureDiagram-3BPJPVTR-pm9KtMZY.js} +1 -1
- package/web-shell/assets/{blockDiagram-GPEHLZMM-CK3Y_vom.js → blockDiagram-GPEHLZMM-CApElKsU.js} +1 -1
- package/web-shell/assets/{c4Diagram-AAUBKEIU-DprSnyrf.js → c4Diagram-AAUBKEIU-BeDe0-9N.js} +1 -1
- package/web-shell/assets/channel-BNf1dW-j.js +1 -0
- package/web-shell/assets/{chunk-2J33WTMH-DJtRJPmu.js → chunk-2J33WTMH-DJEkmLdl.js} +1 -1
- package/web-shell/assets/{chunk-4BX2VUAB-ON6Q2-BG.js → chunk-4BX2VUAB-DXX8gdpe.js} +1 -1
- package/web-shell/assets/{chunk-55IACEB6-c60ou5HZ.js → chunk-55IACEB6-DORmk1kQ.js} +1 -1
- package/web-shell/assets/{chunk-727SXJPM-BamtPKxx.js → chunk-727SXJPM-DWmO4mCZ.js} +1 -1
- package/web-shell/assets/{chunk-AQP2D5EJ-CsVgpu-T.js → chunk-AQP2D5EJ-C7LRaYw2.js} +1 -1
- package/web-shell/assets/{chunk-FMBD7UC4-vZrpjAx4.js → chunk-FMBD7UC4-CCdXgyC5.js} +1 -1
- package/web-shell/assets/{chunk-ND2GUHAM-qxyNOd5t.js → chunk-ND2GUHAM-SyL_xvWc.js} +1 -1
- package/web-shell/assets/{chunk-QZHKN3VN-BvTJSM_S.js → chunk-QZHKN3VN-BzOuCKxa.js} +1 -1
- package/web-shell/assets/classDiagram-4FO5ZUOK-uOIGlzgi.js +1 -0
- package/web-shell/assets/classDiagram-v2-Q7XG4LA2-uOIGlzgi.js +1 -0
- package/web-shell/assets/{cose-bilkent-S5V4N54A-1tXB4pju.js → cose-bilkent-S5V4N54A-BMPWyDKc.js} +1 -1
- package/web-shell/assets/{dagre-BM42HDAG-DglBzme8.js → dagre-BM42HDAG-B_GnPWik.js} +1 -1
- package/web-shell/assets/{diagram-2AECGRRQ-DEJpqsWz.js → diagram-2AECGRRQ-fJqMc3S_.js} +1 -1
- package/web-shell/assets/{diagram-5GNKFQAL-CsQ1NrMG.js → diagram-5GNKFQAL-ximV3O9D.js} +1 -1
- package/web-shell/assets/{diagram-KO2AKTUF-Z-3codAi.js → diagram-KO2AKTUF-2vVyjzqw.js} +1 -1
- package/web-shell/assets/{diagram-LMA3HP47-mBuPTA2i.js → diagram-LMA3HP47-Dckcx1sT.js} +1 -1
- package/web-shell/assets/{diagram-OG6HWLK6-tcA3K3s9.js → diagram-OG6HWLK6-BAVv47ga.js} +1 -1
- package/web-shell/assets/{erDiagram-TEJ5UH35-D9bXFRyZ.js → erDiagram-TEJ5UH35-Bn3nf5J0.js} +1 -1
- package/web-shell/assets/{flowDiagram-I6XJVG4X-Ck0Py04s.js → flowDiagram-I6XJVG4X-BIbGF9JT.js} +1 -1
- package/web-shell/assets/{ganttDiagram-6RSMTGT7-rOevCekE.js → ganttDiagram-6RSMTGT7-CDbhb3AH.js} +1 -1
- package/web-shell/assets/{gitGraphDiagram-PVQCEYII-CoPQaLj0.js → gitGraphDiagram-PVQCEYII-CbKG1a_w.js} +1 -1
- package/web-shell/assets/index-CnE2ur2L.js +779 -0
- package/web-shell/assets/index-hSKXrlSE.css +5 -0
- package/web-shell/assets/{infoDiagram-5YYISTIA-a77CUKoI.js → infoDiagram-5YYISTIA-DUFxTy8m.js} +1 -1
- package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-DlLgUAO-.js → ishikawaDiagram-YF4QCWOH-D69hrBG8.js} +1 -1
- package/web-shell/assets/{journeyDiagram-JHISSGLW-BXR5kVAZ.js → journeyDiagram-JHISSGLW-DsvEw51Y.js} +1 -1
- package/web-shell/assets/{kanban-definition-UN3LZRKU-h5CoHZg7.js → kanban-definition-UN3LZRKU-lFFjZEYd.js} +1 -1
- package/web-shell/assets/{linear-BkaREbyD.js → linear-Bo5bWzvo.js} +1 -1
- package/web-shell/assets/{mermaid.core-Bkagz0ng.js → mermaid.core-C_HKMJx5.js} +5 -5
- package/web-shell/assets/{mindmap-definition-RKZ34NQL-D7EMKR_5.js → mindmap-definition-RKZ34NQL-DlV-NUTs.js} +1 -1
- package/web-shell/assets/{pieDiagram-4H26LBE5-DAmPOjsL.js → pieDiagram-4H26LBE5-B2e6FH2a.js} +1 -1
- package/web-shell/assets/{quadrantDiagram-W4KKPZXB-e4WFQkQu.js → quadrantDiagram-W4KKPZXB-D75kSk-b.js} +1 -1
- package/web-shell/assets/{requirementDiagram-4Y6WPE33-v1zo7OiV.js → requirementDiagram-4Y6WPE33-CQ6xakhM.js} +1 -1
- package/web-shell/assets/{sankeyDiagram-5OEKKPKP-Bstm4aW_.js → sankeyDiagram-5OEKKPKP-J_XTKrcY.js} +1 -1
- package/web-shell/assets/{sequenceDiagram-3UESZ5HK-DzcZy6xN.js → sequenceDiagram-3UESZ5HK-D7jTcBtf.js} +1 -1
- package/web-shell/assets/{stateDiagram-AJRCARHV-BEJH6YbH.js → stateDiagram-AJRCARHV-CrlM_OT3.js} +1 -1
- package/web-shell/assets/stateDiagram-v2-BHNVJYJU-Bs4TK_84.js +1 -0
- package/web-shell/assets/{timeline-definition-PNZ67QCA-TI1wH-Pm.js → timeline-definition-PNZ67QCA-5oZi76Ef.js} +1 -1
- package/web-shell/assets/{vennDiagram-CIIHVFJN-YaH8VDMS.js → vennDiagram-CIIHVFJN-DO5YptPK.js} +1 -1
- package/web-shell/assets/{wardley-L42UT6IY-B80co7OP.js → wardley-L42UT6IY-4t1eJDvM.js} +1 -1
- package/web-shell/assets/{wardleyDiagram-YWT4CUSO-4fHNDEMl.js → wardleyDiagram-YWT4CUSO-DKZc661S.js} +1 -1
- package/web-shell/assets/{xychartDiagram-2RQKCTM6-DT3q4Z3J.js → xychartDiagram-2RQKCTM6-su9hp08G.js} +1 -1
- package/web-shell/index.html +2 -2
- package/chunks/chunk-L7QPM2YD.js +0 -481
- package/chunks/daemon-status-provider-2EWTHT7Y.js +0 -73
- package/chunks/nonInteractiveCli-HNZRIM5I.js +0 -111
- package/web-shell/assets/channel-BAxQ2-aZ.js +0 -1
- package/web-shell/assets/classDiagram-4FO5ZUOK-Cvd_xhZI.js +0 -1
- package/web-shell/assets/classDiagram-v2-Q7XG4LA2-Cvd_xhZI.js +0 -1
- package/web-shell/assets/index-Cr7MYW6T.css +0 -5
- package/web-shell/assets/index-DCHPAUp3.js +0 -730
- package/web-shell/assets/stateDiagram-v2-BHNVJYJU-BopifCBI.js +0 -1
|
@@ -11,28 +11,36 @@ import {
|
|
|
11
11
|
openQwenAsrRealtimeStream,
|
|
12
12
|
openVoiceStream,
|
|
13
13
|
openVoiceStreamWithRetry
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-2YELHTUE.js";
|
|
15
15
|
import {
|
|
16
16
|
SetupGithubError,
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
collectSessionData,
|
|
18
|
+
generateExportFilename,
|
|
19
|
+
normalizeSessionData,
|
|
20
|
+
setupGithub,
|
|
21
|
+
toHtml,
|
|
22
|
+
toJson,
|
|
23
|
+
toJsonl,
|
|
24
|
+
toMarkdown
|
|
25
|
+
} from "./chunk-FK77ILF5.js";
|
|
26
|
+
import "./chunk-ZK5EI6NR.js";
|
|
27
|
+
import "./chunk-LGFAYIKX.js";
|
|
19
28
|
import "./chunk-54YZOP7P.js";
|
|
20
|
-
import "./chunk-XH7H6YG4.js";
|
|
21
29
|
import {
|
|
22
30
|
createDaemonStatusProvider
|
|
23
|
-
} from "./chunk-
|
|
24
|
-
import "./chunk-
|
|
31
|
+
} from "./chunk-FB2ULV4M.js";
|
|
32
|
+
import "./chunk-BHRU4SDF.js";
|
|
25
33
|
import "./chunk-KHDZHZMH.js";
|
|
26
34
|
import {
|
|
27
35
|
createWorkspaceProvidersStatusProvider
|
|
28
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-B6WUEODT.js";
|
|
29
37
|
import {
|
|
30
38
|
getAuthTypeFromEnv,
|
|
31
39
|
resolveCliGenerationConfig
|
|
32
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-KT3SI3CQ.js";
|
|
33
41
|
import {
|
|
34
42
|
createWorkspaceSkillsStatusProvider
|
|
35
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-VTCPREC7.js";
|
|
36
44
|
import "./chunk-6IAWPIQ6.js";
|
|
37
45
|
import {
|
|
38
46
|
EMPTY_WORKSPACE_VOICE_UPDATE_ERROR,
|
|
@@ -44,14 +52,14 @@ import {
|
|
|
44
52
|
transcribeWorkspaceVoiceAudio,
|
|
45
53
|
validateWorkspaceVoiceState,
|
|
46
54
|
voiceSettingsScopeToWire
|
|
47
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-UZCT5OLN.js";
|
|
48
56
|
import {
|
|
49
57
|
PermissionRulesValidationError,
|
|
50
58
|
buildPermissionSettings,
|
|
51
59
|
isPermissionRuleType,
|
|
52
60
|
normalizePermissionRules,
|
|
53
61
|
readPermissionRuleSet
|
|
54
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-5FL2K3ZB.js";
|
|
55
63
|
import {
|
|
56
64
|
assertVoiceBaseUrlNetworkAllowed,
|
|
57
65
|
isStreamingVoiceModel,
|
|
@@ -59,13 +67,13 @@ import {
|
|
|
59
67
|
resolveVoiceTranscriptionConfig,
|
|
60
68
|
sanitizeVoiceErrorMessage,
|
|
61
69
|
transcribeVoiceAudio
|
|
62
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-B5FOCTEZ.js";
|
|
63
71
|
import {
|
|
64
72
|
isVoiceEnabled,
|
|
65
73
|
isVoiceMode,
|
|
66
74
|
readVoiceModel
|
|
67
|
-
} from "./chunk-
|
|
68
|
-
import "./chunk-
|
|
75
|
+
} from "./chunk-3QDZDAJG.js";
|
|
76
|
+
import "./chunk-E7KNELIZ.js";
|
|
69
77
|
import {
|
|
70
78
|
WorkspacePermissionRulesSessionRequiredError,
|
|
71
79
|
WorkspaceSettingsPartialPersistError
|
|
@@ -79,6 +87,7 @@ import {
|
|
|
79
87
|
MAX_CLIENT_ID_LENGTH,
|
|
80
88
|
MAX_SERVER_NAME_LENGTH,
|
|
81
89
|
MAX_TOOL_NAME_LENGTH,
|
|
90
|
+
PathMutexRegistry,
|
|
82
91
|
TooManyActiveDeviceFlowsError,
|
|
83
92
|
UnsupportedDeviceFlowProviderError,
|
|
84
93
|
UpstreamDeviceFlowError,
|
|
@@ -114,7 +123,7 @@ import {
|
|
|
114
123
|
setRateLimiter,
|
|
115
124
|
unsafeRevealSecret,
|
|
116
125
|
validateMcpRuntimeServerName
|
|
117
|
-
} from "./chunk-
|
|
126
|
+
} from "./chunk-RFIL6REB.js";
|
|
118
127
|
import {
|
|
119
128
|
isLoopbackBind
|
|
120
129
|
} from "./chunk-RNBYOUGV.js";
|
|
@@ -126,16 +135,16 @@ import {
|
|
|
126
135
|
getSettingDefinition,
|
|
127
136
|
loadSettings,
|
|
128
137
|
validateSettingValue
|
|
129
|
-
} from "./chunk-
|
|
138
|
+
} from "./chunk-QHT5USLK.js";
|
|
130
139
|
import {
|
|
131
140
|
SUPPORTED_LANGUAGES
|
|
132
|
-
} from "./chunk-
|
|
141
|
+
} from "./chunk-TBLJNMI7.js";
|
|
133
142
|
import "./chunk-TLRYABYP.js";
|
|
134
143
|
import "./chunk-4RQIB5UZ.js";
|
|
135
144
|
import {
|
|
136
145
|
SessionArtifactValidationError,
|
|
137
146
|
createAcpSessionBridge
|
|
138
|
-
} from "./chunk-
|
|
147
|
+
} from "./chunk-OU6FPTJZ.js";
|
|
139
148
|
import "./chunk-P7M4GA57.js";
|
|
140
149
|
import {
|
|
141
150
|
BranchWhilePromptActiveError,
|
|
@@ -165,33 +174,37 @@ import {
|
|
|
165
174
|
WorkspaceInitRaceError,
|
|
166
175
|
WorkspaceInitSymlinkError,
|
|
167
176
|
WorkspaceMismatchError
|
|
168
|
-
} from "./chunk-
|
|
169
|
-
import "./chunk-
|
|
177
|
+
} from "./chunk-B34OV7K2.js";
|
|
178
|
+
import "./chunk-AGHU5VRK.js";
|
|
170
179
|
import {
|
|
171
180
|
MAX_WORKSPACE_PATH_LENGTH,
|
|
172
|
-
canonicalizeWorkspace
|
|
173
|
-
|
|
181
|
+
canonicalizeWorkspace,
|
|
182
|
+
canonicalizeWorkspaces
|
|
183
|
+
} from "./chunk-ER3BKOLB.js";
|
|
174
184
|
import {
|
|
175
185
|
EVENT_SCHEMA_VERSION,
|
|
176
186
|
SubscriberLimitExceededError
|
|
177
|
-
} from "./chunk-
|
|
187
|
+
} from "./chunk-G7ZG7CA6.js";
|
|
178
188
|
import "./chunk-VRDMOSQS.js";
|
|
179
|
-
import "./chunk-
|
|
180
|
-
import "./chunk-
|
|
189
|
+
import "./chunk-K34DD7FH.js";
|
|
190
|
+
import "./chunk-EXPMGZZV.js";
|
|
181
191
|
import {
|
|
182
192
|
STATUS_SCHEMA_VERSION,
|
|
183
193
|
createIdleWorkspaceMemoryStatus,
|
|
184
194
|
mapDomainErrorToErrorKind
|
|
185
|
-
} from "./chunk-
|
|
195
|
+
} from "./chunk-ME344634.js";
|
|
186
196
|
import "./chunk-HLKG6CZN.js";
|
|
187
|
-
import "./chunk-
|
|
197
|
+
import "./chunk-XUSONNJ4.js";
|
|
198
|
+
import "./chunk-43W4YSO6.js";
|
|
188
199
|
import {
|
|
189
200
|
getWorkspaceTrustStatus
|
|
190
|
-
} from "./chunk-
|
|
201
|
+
} from "./chunk-MOQOAH7N.js";
|
|
191
202
|
import {
|
|
192
203
|
writeStderrLine
|
|
193
204
|
} from "./chunk-Y6Z2O3WR.js";
|
|
194
|
-
import
|
|
205
|
+
import {
|
|
206
|
+
isWithinRoot as isWithinRoot2
|
|
207
|
+
} from "./chunk-QYUE6W3T.js";
|
|
195
208
|
import {
|
|
196
209
|
ALL_PROVIDERS,
|
|
197
210
|
BTW_MAX_INPUT_LENGTH,
|
|
@@ -199,8 +212,12 @@ import {
|
|
|
199
212
|
Client,
|
|
200
213
|
ClientMcpRegistrar,
|
|
201
214
|
ExtensionManager,
|
|
215
|
+
GROUP_COLOR_OPTIONS,
|
|
216
|
+
IMAGE_CAPABILITY,
|
|
202
217
|
ModelsConfig,
|
|
203
218
|
RUNTIME_MCP_IF_ABSENT_CONFIG_FLAG,
|
|
219
|
+
SessionOrganizationError,
|
|
220
|
+
SessionOrganizationService,
|
|
204
221
|
SessionService,
|
|
205
222
|
StandardFileSystemService,
|
|
206
223
|
StdioClientTransport,
|
|
@@ -209,27 +226,42 @@ import {
|
|
|
209
226
|
TrustGateError,
|
|
210
227
|
WorkspaceMemoryFileTooLargeError,
|
|
211
228
|
WorkspaceMemoryWriteTimeoutError,
|
|
229
|
+
buildUsageDashboard,
|
|
212
230
|
checkForExtensionUpdate,
|
|
213
231
|
detectLineEnding,
|
|
214
232
|
encodeTextFileContent,
|
|
215
233
|
glob,
|
|
216
234
|
loadIgnoreRules,
|
|
235
|
+
loadUsageHistory,
|
|
217
236
|
parseInstallSource,
|
|
218
237
|
redactUrlCredentials,
|
|
219
238
|
shouldShowStep,
|
|
239
|
+
stripTerminalControlSequences,
|
|
220
240
|
writeWorkspaceContextFile
|
|
221
|
-
} from "./chunk-
|
|
241
|
+
} from "./chunk-E7HM76PR.js";
|
|
222
242
|
import "./chunk-5P5XGNYH.js";
|
|
223
243
|
import "./chunk-SKAZWEV5.js";
|
|
224
244
|
import "./chunk-K5PGHDBN.js";
|
|
225
|
-
import
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
import "./chunk-
|
|
230
|
-
import "./chunk-
|
|
245
|
+
import {
|
|
246
|
+
MAX_JOBS,
|
|
247
|
+
nextDurableFireMs
|
|
248
|
+
} from "./chunk-3B26UVCX.js";
|
|
249
|
+
import "./chunk-W7EEOR7D.js";
|
|
250
|
+
import "./chunk-42XGFRJS.js";
|
|
251
|
+
import "./chunk-X2XNQQJI.js";
|
|
252
|
+
import "./chunk-VZ7HY3YU.js";
|
|
253
|
+
import {
|
|
254
|
+
nextFireTime,
|
|
255
|
+
parseCron
|
|
256
|
+
} from "./chunk-2MPVVENX.js";
|
|
231
257
|
import "./chunk-ZTQ26VBE.js";
|
|
232
|
-
import
|
|
258
|
+
import {
|
|
259
|
+
appendCronRun,
|
|
260
|
+
generateCronTaskId,
|
|
261
|
+
getCronFilePath,
|
|
262
|
+
readCronTasks,
|
|
263
|
+
updateCronTasks
|
|
264
|
+
} from "./chunk-FV7425LN.js";
|
|
233
265
|
import "./chunk-MLZQVCF3.js";
|
|
234
266
|
import "./chunk-LQ7TMOCE.js";
|
|
235
267
|
import "./chunk-E5A7LHNN.js";
|
|
@@ -237,12 +269,12 @@ import {
|
|
|
237
269
|
APPROVAL_MODES,
|
|
238
270
|
SubagentError,
|
|
239
271
|
SubagentErrorCode
|
|
240
|
-
} from "./chunk-
|
|
272
|
+
} from "./chunk-VEB3NYSO.js";
|
|
241
273
|
import "./chunk-GO6LNQXT.js";
|
|
242
|
-
import "./chunk-
|
|
243
|
-
import "./chunk-
|
|
244
|
-
import "./chunk-
|
|
245
|
-
import "./chunk-
|
|
274
|
+
import "./chunk-NQKLOAVJ.js";
|
|
275
|
+
import "./chunk-WMPVYQ4P.js";
|
|
276
|
+
import "./chunk-TLWA7TPR.js";
|
|
277
|
+
import "./chunk-QYNNN7BK.js";
|
|
246
278
|
import {
|
|
247
279
|
addDaemonRequestAttribute,
|
|
248
280
|
decodeBufferWithEncodingInfo,
|
|
@@ -256,15 +288,15 @@ import {
|
|
|
256
288
|
recordDaemonHttpRequest,
|
|
257
289
|
recordDaemonHttpResponse,
|
|
258
290
|
withDaemonRequestSpan
|
|
259
|
-
} from "./chunk-
|
|
291
|
+
} from "./chunk-7I4MGFBN.js";
|
|
260
292
|
import "./chunk-E6US47KI.js";
|
|
261
293
|
import "./chunk-UWCTAVOD.js";
|
|
262
294
|
import "./chunk-OFEVLU4C.js";
|
|
263
295
|
import "./chunk-OMX7CUOE.js";
|
|
264
|
-
import "./chunk-
|
|
265
|
-
import "./chunk-
|
|
266
|
-
import "./chunk-
|
|
267
|
-
import "./chunk-
|
|
296
|
+
import "./chunk-4KJ2BPIT.js";
|
|
297
|
+
import "./chunk-SUAKHHK7.js";
|
|
298
|
+
import "./chunk-FEJ2FZ3U.js";
|
|
299
|
+
import "./chunk-X3YGAX7V.js";
|
|
268
300
|
import {
|
|
269
301
|
QwenOAuth2Client,
|
|
270
302
|
QwenOAuthPollError,
|
|
@@ -273,13 +305,13 @@ import {
|
|
|
273
305
|
isDeviceAuthorizationSuccess,
|
|
274
306
|
isDeviceTokenPending,
|
|
275
307
|
isDeviceTokenSuccess
|
|
276
|
-
} from "./chunk-
|
|
277
|
-
import "./chunk-
|
|
308
|
+
} from "./chunk-IJRL2S3D.js";
|
|
309
|
+
import "./chunk-AHNJGLFV.js";
|
|
278
310
|
import "./chunk-22IFUCVR.js";
|
|
279
|
-
import "./chunk-
|
|
311
|
+
import "./chunk-7LTB54MK.js";
|
|
280
312
|
import "./chunk-SIUQ3YYX.js";
|
|
281
|
-
import "./chunk-
|
|
282
|
-
import "./chunk-
|
|
313
|
+
import "./chunk-HW4N45EE.js";
|
|
314
|
+
import "./chunk-CCGOFQV3.js";
|
|
283
315
|
import "./chunk-IRJWHZWU.js";
|
|
284
316
|
import "./chunk-SYCJMSIJ.js";
|
|
285
317
|
import "./chunk-55ZMG67I.js";
|
|
@@ -288,12 +320,12 @@ import {
|
|
|
288
320
|
} from "./chunk-H6BD2ELD.js";
|
|
289
321
|
import "./chunk-5IFG2VC4.js";
|
|
290
322
|
import "./chunk-64WXLC72.js";
|
|
291
|
-
import "./chunk-
|
|
323
|
+
import "./chunk-H6EMK6QK.js";
|
|
292
324
|
import {
|
|
293
325
|
FatalConfigError,
|
|
294
326
|
Storage,
|
|
295
327
|
createDebugLogger
|
|
296
|
-
} from "./chunk-
|
|
328
|
+
} from "./chunk-22MNNNXE.js";
|
|
297
329
|
import "./chunk-ZERZSAZL.js";
|
|
298
330
|
import "./chunk-QN5NZ3UQ.js";
|
|
299
331
|
import "./chunk-BR4QREVK.js";
|
|
@@ -460,6 +492,35 @@ function canonicalizeBoundWorkspaceCached(boundWorkspace) {
|
|
|
460
492
|
return canonical;
|
|
461
493
|
}
|
|
462
494
|
__name(canonicalizeBoundWorkspaceCached, "canonicalizeBoundWorkspaceCached");
|
|
495
|
+
function canonicalizeBoundWorkspacesCached(boundWorkspaces) {
|
|
496
|
+
const inputs = typeof boundWorkspaces === "string" ? [boundWorkspaces] : boundWorkspaces;
|
|
497
|
+
if (inputs.length === 0) {
|
|
498
|
+
throw new FsError(
|
|
499
|
+
"parse_error",
|
|
500
|
+
"boundWorkspaces must include at least one workspace root"
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
const out = [];
|
|
504
|
+
const seen = /* @__PURE__ */ new Set();
|
|
505
|
+
for (const workspace of inputs) {
|
|
506
|
+
const canonical = canonicalizeBoundWorkspaceCached(workspace);
|
|
507
|
+
if (seen.has(canonical)) continue;
|
|
508
|
+
seen.add(canonical);
|
|
509
|
+
out.push(canonical);
|
|
510
|
+
}
|
|
511
|
+
return out;
|
|
512
|
+
}
|
|
513
|
+
__name(canonicalizeBoundWorkspacesCached, "canonicalizeBoundWorkspacesCached");
|
|
514
|
+
function findContainingWorkspace(absolute, boundWorkspaces) {
|
|
515
|
+
let match;
|
|
516
|
+
for (const workspace of boundWorkspaces) {
|
|
517
|
+
if (isWithinRoot(absolute, workspace) && (match === void 0 || workspace.length > match.length)) {
|
|
518
|
+
match = workspace;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return match;
|
|
522
|
+
}
|
|
523
|
+
__name(findContainingWorkspace, "findContainingWorkspace");
|
|
463
524
|
async function findExistingAncestor(absolute) {
|
|
464
525
|
let current = absolute;
|
|
465
526
|
const tailParts = [];
|
|
@@ -505,7 +566,7 @@ async function findExistingAncestor(absolute) {
|
|
|
505
566
|
);
|
|
506
567
|
}
|
|
507
568
|
__name(findExistingAncestor, "findExistingAncestor");
|
|
508
|
-
async function resolveWithinWorkspace(input,
|
|
569
|
+
async function resolveWithinWorkspace(input, boundWorkspaces, intent) {
|
|
509
570
|
if (typeof input !== "string" || input.length === 0) {
|
|
510
571
|
throw new FsError("parse_error", "path must be a non-empty string");
|
|
511
572
|
}
|
|
@@ -518,9 +579,58 @@ async function resolveWithinWorkspace(input, boundWorkspace, intent) {
|
|
|
518
579
|
}
|
|
519
580
|
);
|
|
520
581
|
}
|
|
521
|
-
const
|
|
522
|
-
const
|
|
523
|
-
if (
|
|
582
|
+
const boundCanonicals = canonicalizeBoundWorkspacesCached(boundWorkspaces);
|
|
583
|
+
const primary = boundCanonicals[0];
|
|
584
|
+
if (primary === void 0) {
|
|
585
|
+
throw new FsError(
|
|
586
|
+
"parse_error",
|
|
587
|
+
"boundWorkspaces must include at least one workspace root"
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
const absolute = path.resolve(primary, input);
|
|
591
|
+
let boundCanonical = findContainingWorkspace(absolute, boundCanonicals);
|
|
592
|
+
let preResolvedCanonical;
|
|
593
|
+
if (boundCanonical === void 0 && path.isAbsolute(input)) {
|
|
594
|
+
try {
|
|
595
|
+
preResolvedCanonical = await fsp.realpath(absolute);
|
|
596
|
+
boundCanonical = findContainingWorkspace(
|
|
597
|
+
preResolvedCanonical,
|
|
598
|
+
boundCanonicals
|
|
599
|
+
);
|
|
600
|
+
} catch (err) {
|
|
601
|
+
const code = err?.code;
|
|
602
|
+
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
603
|
+
try {
|
|
604
|
+
const { ancestor, tail } = await findExistingAncestor(absolute);
|
|
605
|
+
const ancestorReal = await fsp.realpath(ancestor);
|
|
606
|
+
const canonicalCandidate = tail ? path.join(ancestorReal, tail) : ancestorReal;
|
|
607
|
+
boundCanonical = findContainingWorkspace(
|
|
608
|
+
canonicalCandidate,
|
|
609
|
+
boundCanonicals
|
|
610
|
+
);
|
|
611
|
+
if (boundCanonical !== void 0) {
|
|
612
|
+
const finalPathIsSymlink = await fsp.lstat(absolute).then((stat) => stat.isSymbolicLink()).catch((lstatErr) => {
|
|
613
|
+
if (lstatErr.code === "ENOENT" || lstatErr.code === "ENOTDIR") {
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
616
|
+
throw lstatErr;
|
|
617
|
+
});
|
|
618
|
+
if (!finalPathIsSymlink && ENOENT_TOLERATING_INTENTS.has(intent)) {
|
|
619
|
+
preResolvedCanonical = canonicalCandidate;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
} catch (innerErr) {
|
|
623
|
+
const innerCode = innerErr?.code;
|
|
624
|
+
if (innerCode !== "ENOENT" && innerCode !== "ENOTDIR") {
|
|
625
|
+
throw innerErr;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
} else {
|
|
629
|
+
throw err;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
if (boundCanonical === void 0) {
|
|
524
634
|
throw new FsError(
|
|
525
635
|
"path_outside_workspace",
|
|
526
636
|
`path escapes workspace: ${input}`
|
|
@@ -528,7 +638,7 @@ async function resolveWithinWorkspace(input, boundWorkspace, intent) {
|
|
|
528
638
|
}
|
|
529
639
|
let canonical;
|
|
530
640
|
try {
|
|
531
|
-
canonical = await fsp.realpath(absolute);
|
|
641
|
+
canonical = preResolvedCanonical ?? await fsp.realpath(absolute);
|
|
532
642
|
} catch (err) {
|
|
533
643
|
const code = err?.code;
|
|
534
644
|
if (code === "ENOENT" && ENOENT_TOLERATING_INTENTS.has(intent)) {
|
|
@@ -579,7 +689,7 @@ async function resolveWithinWorkspace(input, boundWorkspace, intent) {
|
|
|
579
689
|
const { ancestor: targetAncestor, tail: targetTail } = await findExistingAncestor(cursor);
|
|
580
690
|
const targetAncestorReal = await fsp.realpath(targetAncestor);
|
|
581
691
|
const canonicalTarget = targetTail ? path.join(targetAncestorReal, targetTail) : targetAncestorReal;
|
|
582
|
-
if (!isWithinRoot(canonicalTarget,
|
|
692
|
+
if (!boundCanonicals.some((root) => isWithinRoot(canonicalTarget, root))) {
|
|
583
693
|
throw new FsError(
|
|
584
694
|
"symlink_escape",
|
|
585
695
|
`dangling symlink target escapes workspace: ${input}`,
|
|
@@ -632,7 +742,7 @@ async function resolveWithinWorkspace(input, boundWorkspace, intent) {
|
|
|
632
742
|
throw err;
|
|
633
743
|
}
|
|
634
744
|
}
|
|
635
|
-
if (!isWithinRoot(canonical,
|
|
745
|
+
if (!boundCanonicals.some((root) => isWithinRoot(canonical, root))) {
|
|
636
746
|
const kind = canonical !== absolute ? "symlink_escape" : "path_outside_workspace";
|
|
637
747
|
throw new FsError(
|
|
638
748
|
kind,
|
|
@@ -805,34 +915,42 @@ __name(createAuditPublisher, "createAuditPublisher");
|
|
|
805
915
|
// packages/cli/src/serve/fs/workspace-file-system.ts
|
|
806
916
|
init_esbuild_shims();
|
|
807
917
|
import { createHash as createHash2, randomBytes } from "node:crypto";
|
|
808
|
-
import { promises as fsp2 } from "node:fs";
|
|
918
|
+
import { constants as fsConstants, promises as fsp2 } from "node:fs";
|
|
809
919
|
import * as path4 from "node:path";
|
|
810
920
|
function createWorkspaceFileSystemFactory(deps) {
|
|
811
|
-
const
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
921
|
+
const boundWorkspaces = canonicalizeWorkspaces(deps.boundWorkspaces);
|
|
922
|
+
if (boundWorkspaces.length === 0) {
|
|
923
|
+
throw new Error("WorkspaceFileSystem requires at least one workspace root");
|
|
924
|
+
}
|
|
925
|
+
assertNoNestedWorkspaces(boundWorkspaces);
|
|
926
|
+
const primaryWorkspace = boundWorkspaces[0];
|
|
927
|
+
const workspaces = boundWorkspaces.map((workspace) => {
|
|
928
|
+
const ignore = deps.ignore ?? loadIgnoreRules({
|
|
929
|
+
projectRoot: workspace,
|
|
930
|
+
useGitignore: true,
|
|
931
|
+
useQwenignore: true,
|
|
932
|
+
...deps.customIgnoreFiles !== void 0 ? { customIgnoreFiles: deps.customIgnoreFiles } : {},
|
|
933
|
+
ignoreDirs: []
|
|
934
|
+
});
|
|
935
|
+
Object.freeze(ignore);
|
|
936
|
+
return { path: workspace, ignore };
|
|
818
937
|
});
|
|
819
|
-
Object.freeze(ignore);
|
|
820
938
|
const audit = createAuditPublisher({
|
|
821
939
|
emit: deps.emit,
|
|
822
|
-
boundWorkspace,
|
|
940
|
+
boundWorkspace: primaryWorkspace,
|
|
823
941
|
includeRawPaths: deps.includeRawPaths
|
|
824
942
|
});
|
|
825
943
|
const lowFs = new StandardFileSystemService();
|
|
826
|
-
const pathLocks = new PathMutexRegistry();
|
|
944
|
+
const pathLocks = deps.pathLocks ?? new PathMutexRegistry();
|
|
827
945
|
return {
|
|
828
946
|
assertCanWrite() {
|
|
829
947
|
assertTrustedForIntent(deps.trusted, "write");
|
|
830
948
|
},
|
|
831
949
|
forRequest(ctx) {
|
|
832
950
|
return new WorkspaceFileSystemImpl({
|
|
833
|
-
|
|
951
|
+
primaryWorkspace,
|
|
952
|
+
workspaces,
|
|
834
953
|
trusted: deps.trusted,
|
|
835
|
-
ignore,
|
|
836
954
|
audit,
|
|
837
955
|
ctx,
|
|
838
956
|
lowFs,
|
|
@@ -842,6 +960,24 @@ function createWorkspaceFileSystemFactory(deps) {
|
|
|
842
960
|
};
|
|
843
961
|
}
|
|
844
962
|
__name(createWorkspaceFileSystemFactory, "createWorkspaceFileSystemFactory");
|
|
963
|
+
function assertNoNestedWorkspaces(workspaces) {
|
|
964
|
+
for (let i = 0; i < workspaces.length; i++) {
|
|
965
|
+
const a = workspaces[i];
|
|
966
|
+
if (a === void 0) continue;
|
|
967
|
+
for (let j = i + 1; j < workspaces.length; j++) {
|
|
968
|
+
const b = workspaces[j];
|
|
969
|
+
if (b === void 0) continue;
|
|
970
|
+
if (isWithinRoot(a, b) || isWithinRoot(b, a)) {
|
|
971
|
+
throw new Error(
|
|
972
|
+
`Nested workspace roots are not supported: ${JSON.stringify(
|
|
973
|
+
a
|
|
974
|
+
)} and ${JSON.stringify(b)}`
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
__name(assertNoNestedWorkspaces, "assertNoNestedWorkspaces");
|
|
845
981
|
var WorkspaceFileSystemImpl = class {
|
|
846
982
|
constructor(deps) {
|
|
847
983
|
this.deps = deps;
|
|
@@ -849,11 +985,25 @@ var WorkspaceFileSystemImpl = class {
|
|
|
849
985
|
static {
|
|
850
986
|
__name(this, "WorkspaceFileSystemImpl");
|
|
851
987
|
}
|
|
988
|
+
workspaceForPath(p) {
|
|
989
|
+
let match;
|
|
990
|
+
for (const workspace of this.deps.workspaces) {
|
|
991
|
+
if (isWithinRoot(p, workspace.path) && (match === void 0 || workspace.path.length > match.path.length)) {
|
|
992
|
+
match = workspace;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
return match;
|
|
996
|
+
}
|
|
997
|
+
ignoreVerdict(p, kind = "file") {
|
|
998
|
+
const workspace = this.workspaceForPath(p);
|
|
999
|
+
if (!workspace) return { ignored: false };
|
|
1000
|
+
return shouldIgnore(p, workspace.path, workspace.ignore, kind);
|
|
1001
|
+
}
|
|
852
1002
|
async resolve(input, intent) {
|
|
853
1003
|
try {
|
|
854
1004
|
return await resolveWithinWorkspace(
|
|
855
1005
|
input,
|
|
856
|
-
this.deps.
|
|
1006
|
+
this.deps.workspaces.map((workspace) => workspace.path),
|
|
857
1007
|
intent
|
|
858
1008
|
);
|
|
859
1009
|
} catch (err) {
|
|
@@ -898,12 +1048,7 @@ var WorkspaceFileSystemImpl = class {
|
|
|
898
1048
|
);
|
|
899
1049
|
}
|
|
900
1050
|
const snapshot = await readTextSnapshotFromResolvedFile(p, opts);
|
|
901
|
-
const ignoreVerdict =
|
|
902
|
-
p,
|
|
903
|
-
this.deps.boundWorkspace,
|
|
904
|
-
this.deps.ignore,
|
|
905
|
-
"file"
|
|
906
|
-
);
|
|
1051
|
+
const ignoreVerdict = this.ignoreVerdict(p, "file");
|
|
907
1052
|
const meta = snapshot.meta;
|
|
908
1053
|
if (ignoreVerdict.ignored) meta.matchedIgnore = ignoreVerdict.category;
|
|
909
1054
|
this.deps.audit.recordAccess(this.deps.ctx, {
|
|
@@ -1005,10 +1150,8 @@ var WorkspaceFileSystemImpl = class {
|
|
|
1005
1150
|
for await (const d of dir) {
|
|
1006
1151
|
const childAbs = path4.join(p, d.name);
|
|
1007
1152
|
const kind = kindFromStatLike(d);
|
|
1008
|
-
const verdict =
|
|
1153
|
+
const verdict = this.ignoreVerdict(
|
|
1009
1154
|
childAbs,
|
|
1010
|
-
this.deps.boundWorkspace,
|
|
1011
|
-
this.deps.ignore,
|
|
1012
1155
|
kind === "directory" ? "directory" : "file"
|
|
1013
1156
|
);
|
|
1014
1157
|
if (verdict.ignored && !opts.includeIgnored) continue;
|
|
@@ -1046,81 +1189,126 @@ var WorkspaceFileSystemImpl = class {
|
|
|
1046
1189
|
{ hint: 'pass a relative pattern such as "src/**/*.ts"' }
|
|
1047
1190
|
);
|
|
1048
1191
|
}
|
|
1049
|
-
const
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1192
|
+
const searchRoots = [];
|
|
1193
|
+
if (opts.cwd === void 0) {
|
|
1194
|
+
for (const workspace of this.deps.workspaces) {
|
|
1195
|
+
searchRoots.push({ cwd: workspace.path, workspace });
|
|
1196
|
+
}
|
|
1053
1197
|
} else {
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1198
|
+
const cwd = opts.cwd;
|
|
1199
|
+
let cwdReal;
|
|
1200
|
+
const directWorkspace = this.workspaceForPath(cwd);
|
|
1201
|
+
if (directWorkspace && cwd === directWorkspace.path) {
|
|
1202
|
+
cwdReal = cwd;
|
|
1203
|
+
} else {
|
|
1204
|
+
try {
|
|
1205
|
+
cwdReal = await fsp2.realpath(path4.resolve(cwd));
|
|
1206
|
+
} catch (err) {
|
|
1207
|
+
const code = err?.code;
|
|
1208
|
+
if (code === "ENOENT") {
|
|
1209
|
+
throw new FsError(
|
|
1210
|
+
"path_not_found",
|
|
1211
|
+
`glob cwd does not exist: ${cwd}`,
|
|
1212
|
+
{ cause: err }
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
throw err;
|
|
1064
1216
|
}
|
|
1065
|
-
throw err;
|
|
1066
1217
|
}
|
|
1218
|
+
const workspace = this.workspaceForPath(cwdReal);
|
|
1219
|
+
if (!workspace) {
|
|
1220
|
+
throw new FsError(
|
|
1221
|
+
"path_outside_workspace",
|
|
1222
|
+
`glob cwd is outside workspace: ${cwd}`,
|
|
1223
|
+
{ hint: "opts.cwd must be a path obtained from fs.resolve()" }
|
|
1224
|
+
);
|
|
1225
|
+
}
|
|
1226
|
+
searchRoots.push({ cwd: cwdReal, workspace });
|
|
1067
1227
|
}
|
|
1068
|
-
if (!isWithinRoot(cwdReal, this.deps.boundWorkspace)) {
|
|
1069
|
-
throw new FsError(
|
|
1070
|
-
"path_outside_workspace",
|
|
1071
|
-
`glob cwd is outside workspace: ${cwd}`,
|
|
1072
|
-
{ hint: "opts.cwd must be a path obtained from fs.resolve()" }
|
|
1073
|
-
);
|
|
1074
|
-
}
|
|
1075
|
-
const matches = await glob(pattern, {
|
|
1076
|
-
cwd: cwdReal,
|
|
1077
|
-
nodir: false,
|
|
1078
|
-
absolute: true,
|
|
1079
|
-
dot: true,
|
|
1080
|
-
ignore: ["**/node_modules/**", "**/.git/**"]
|
|
1081
|
-
});
|
|
1082
1228
|
const out = [];
|
|
1229
|
+
const seenCanonicals = /* @__PURE__ */ new Set();
|
|
1083
1230
|
const max = opts.maxResults ?? Number.POSITIVE_INFINITY;
|
|
1084
1231
|
let escapedCount = 0;
|
|
1085
1232
|
let permissionErrorCount = 0;
|
|
1086
1233
|
let transientErrorCount = 0;
|
|
1087
|
-
|
|
1234
|
+
const globErrors = [];
|
|
1235
|
+
let successfulGlobRoots = 0;
|
|
1236
|
+
for (const searchRoot of searchRoots) {
|
|
1088
1237
|
if (out.length >= max) break;
|
|
1089
|
-
|
|
1090
|
-
let canonical;
|
|
1238
|
+
let matches;
|
|
1091
1239
|
try {
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
transientErrorCount += 1;
|
|
1240
|
+
await fsp2.access(searchRoot.cwd, fsConstants.R_OK | fsConstants.X_OK);
|
|
1241
|
+
const rootStat = await fsp2.stat(searchRoot.cwd);
|
|
1242
|
+
if (!rootStat.isDirectory()) {
|
|
1243
|
+
const err = new Error(
|
|
1244
|
+
`glob workspace root is not a directory: ${searchRoot.cwd}`
|
|
1245
|
+
);
|
|
1246
|
+
err.code = "ENOTDIR";
|
|
1247
|
+
throw err;
|
|
1101
1248
|
}
|
|
1249
|
+
matches = await glob(pattern, {
|
|
1250
|
+
cwd: searchRoot.cwd,
|
|
1251
|
+
nodir: false,
|
|
1252
|
+
absolute: true,
|
|
1253
|
+
dot: true,
|
|
1254
|
+
ignore: ["**/node_modules/**", "**/.git/**"]
|
|
1255
|
+
});
|
|
1256
|
+
} catch (err) {
|
|
1257
|
+
globErrors.push(err);
|
|
1258
|
+
this.deps.audit.recordDenied(this.deps.ctx, {
|
|
1259
|
+
intent: "glob",
|
|
1260
|
+
input: pattern,
|
|
1261
|
+
errorKind: errorKindForRealpathFailure(err),
|
|
1262
|
+
hint: `glob failed for workspace root: ${errorCode(err) ?? "unknown error"}`,
|
|
1263
|
+
pattern
|
|
1264
|
+
});
|
|
1102
1265
|
continue;
|
|
1103
1266
|
}
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1267
|
+
successfulGlobRoots += 1;
|
|
1268
|
+
for (const hit of matches) {
|
|
1269
|
+
if (out.length >= max) break;
|
|
1270
|
+
const absolute = path4.resolve(hit);
|
|
1271
|
+
let canonical;
|
|
1272
|
+
try {
|
|
1273
|
+
canonical = await fsp2.realpath(absolute);
|
|
1274
|
+
} catch (err) {
|
|
1275
|
+
const code = errorCode(err);
|
|
1276
|
+
if (code === "ENOENT" || code === "ELOOP") {
|
|
1277
|
+
escapedCount += 1;
|
|
1278
|
+
} else if (code === "EACCES" || code === "EPERM") {
|
|
1279
|
+
permissionErrorCount += 1;
|
|
1280
|
+
} else {
|
|
1281
|
+
transientErrorCount += 1;
|
|
1282
|
+
}
|
|
1283
|
+
continue;
|
|
1284
|
+
}
|
|
1285
|
+
const inAnyWorkspace = this.deps.workspaces.some(
|
|
1286
|
+
(workspace) => isWithinRoot(canonical, workspace.path)
|
|
1287
|
+
);
|
|
1288
|
+
if (!inAnyWorkspace) {
|
|
1289
|
+
escapedCount += 1;
|
|
1290
|
+
continue;
|
|
1291
|
+
}
|
|
1292
|
+
if (seenCanonicals.has(canonical)) continue;
|
|
1293
|
+
let dirent = null;
|
|
1294
|
+
try {
|
|
1295
|
+
dirent = await fsp2.lstat(canonical);
|
|
1296
|
+
} catch {
|
|
1297
|
+
dirent = null;
|
|
1298
|
+
}
|
|
1299
|
+
const kind = dirent?.isDirectory() ? "directory" : "file";
|
|
1300
|
+
const verdict = this.ignoreVerdict(canonical, kind);
|
|
1301
|
+
if (verdict.ignored && !opts.includeIgnored) continue;
|
|
1302
|
+
seenCanonicals.add(canonical);
|
|
1303
|
+
out.push(canonical);
|
|
1108
1304
|
}
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
const kind = dirent?.isDirectory() ? "directory" : "file";
|
|
1116
|
-
const verdict = shouldIgnore(
|
|
1117
|
-
canonical,
|
|
1118
|
-
this.deps.boundWorkspace,
|
|
1119
|
-
this.deps.ignore,
|
|
1120
|
-
kind
|
|
1305
|
+
}
|
|
1306
|
+
if (globErrors.length > 0 && successfulGlobRoots === 0) {
|
|
1307
|
+
if (globErrors.length === 1) throw globErrors[0];
|
|
1308
|
+
throw new AggregateError(
|
|
1309
|
+
globErrors,
|
|
1310
|
+
"glob failed for all workspace roots"
|
|
1121
1311
|
);
|
|
1122
|
-
if (verdict.ignored && !opts.includeIgnored) continue;
|
|
1123
|
-
out.push(canonical);
|
|
1124
1312
|
}
|
|
1125
1313
|
if (escapedCount > 0) {
|
|
1126
1314
|
this.deps.audit.recordDenied(this.deps.ctx, {
|
|
@@ -1144,21 +1332,14 @@ var WorkspaceFileSystemImpl = class {
|
|
|
1144
1332
|
this.deps.audit.recordDenied(this.deps.ctx, {
|
|
1145
1333
|
intent: "glob",
|
|
1146
1334
|
input: pattern,
|
|
1147
|
-
// `io_error` (not `permission_denied`) so monitoring
|
|
1148
|
-
// pipelines that page security oncall on
|
|
1149
|
-
// `permission_denied` aren't woken up by a failing disk
|
|
1150
|
-
// or busy file. The kind was added to `FsErrorKind` for
|
|
1151
|
-
// exactly this case (and for `wrapAsFsError`'s ENOSPC /
|
|
1152
|
-
// EIO / EBUSY / ETXTBSY / ENAMETOOLONG / EMFILE / ENFILE
|
|
1153
|
-
// mappings).
|
|
1154
1335
|
errorKind: "io_error",
|
|
1155
|
-
hint: `glob skipped ${transientErrorCount} hit(s) due to transient I/O errors
|
|
1336
|
+
hint: `glob skipped ${transientErrorCount} hit(s) due to transient I/O errors`,
|
|
1156
1337
|
pattern
|
|
1157
1338
|
});
|
|
1158
1339
|
}
|
|
1159
1340
|
this.deps.audit.recordAccess(this.deps.ctx, {
|
|
1160
1341
|
intent: "glob",
|
|
1161
|
-
absolute: this.deps.
|
|
1342
|
+
absolute: this.deps.primaryWorkspace,
|
|
1162
1343
|
durationMs: performance.now() - start,
|
|
1163
1344
|
sizeBytes: out.length,
|
|
1164
1345
|
pattern
|
|
@@ -1190,12 +1371,7 @@ var WorkspaceFileSystemImpl = class {
|
|
|
1190
1371
|
expectedHash: opts.expectedHash,
|
|
1191
1372
|
meta
|
|
1192
1373
|
});
|
|
1193
|
-
const verdict =
|
|
1194
|
-
p,
|
|
1195
|
-
this.deps.boundWorkspace,
|
|
1196
|
-
this.deps.ignore,
|
|
1197
|
-
"file"
|
|
1198
|
-
);
|
|
1374
|
+
const verdict = this.ignoreVerdict(p, "file");
|
|
1199
1375
|
if (verdict.ignored) meta.matchedIgnore = verdict.category;
|
|
1200
1376
|
meta.sizeBytes = result.sizeBytes;
|
|
1201
1377
|
meta.hash = result.hash;
|
|
@@ -1254,12 +1430,7 @@ var WorkspaceFileSystemImpl = class {
|
|
|
1254
1430
|
mode: "overwrite",
|
|
1255
1431
|
meta
|
|
1256
1432
|
});
|
|
1257
|
-
const verdict =
|
|
1258
|
-
p,
|
|
1259
|
-
this.deps.boundWorkspace,
|
|
1260
|
-
this.deps.ignore,
|
|
1261
|
-
"file"
|
|
1262
|
-
);
|
|
1433
|
+
const verdict = this.ignoreVerdict(p, "file");
|
|
1263
1434
|
if (verdict.ignored) meta.matchedIgnore = verdict.category;
|
|
1264
1435
|
meta.sizeBytes = result.sizeBytes;
|
|
1265
1436
|
meta.hash = result.hash;
|
|
@@ -1295,12 +1466,7 @@ var WorkspaceFileSystemImpl = class {
|
|
|
1295
1466
|
content,
|
|
1296
1467
|
_meta: opts ? buildWriteMeta(opts) : void 0
|
|
1297
1468
|
});
|
|
1298
|
-
const verdict =
|
|
1299
|
-
p,
|
|
1300
|
-
this.deps.boundWorkspace,
|
|
1301
|
-
this.deps.ignore,
|
|
1302
|
-
"file"
|
|
1303
|
-
);
|
|
1469
|
+
const verdict = this.ignoreVerdict(p, "file");
|
|
1304
1470
|
this.deps.audit.recordAccess(this.deps.ctx, {
|
|
1305
1471
|
intent: "write",
|
|
1306
1472
|
absolute: p,
|
|
@@ -1370,12 +1536,7 @@ var WorkspaceFileSystemImpl = class {
|
|
|
1370
1536
|
expectedHash: opts.expectedHash,
|
|
1371
1537
|
meta
|
|
1372
1538
|
});
|
|
1373
|
-
const verdict =
|
|
1374
|
-
p,
|
|
1375
|
-
this.deps.boundWorkspace,
|
|
1376
|
-
this.deps.ignore,
|
|
1377
|
-
"file"
|
|
1378
|
-
);
|
|
1539
|
+
const verdict = this.ignoreVerdict(p, "file");
|
|
1379
1540
|
if (verdict.ignored) meta.matchedIgnore = verdict.category;
|
|
1380
1541
|
meta.sizeBytes = result.sizeBytes;
|
|
1381
1542
|
meta.hash = result.hash;
|
|
@@ -1454,12 +1615,7 @@ var WorkspaceFileSystemImpl = class {
|
|
|
1454
1615
|
content: next,
|
|
1455
1616
|
_meta: readResult._meta
|
|
1456
1617
|
});
|
|
1457
|
-
const editVerdict =
|
|
1458
|
-
p,
|
|
1459
|
-
this.deps.boundWorkspace,
|
|
1460
|
-
this.deps.ignore,
|
|
1461
|
-
"file"
|
|
1462
|
-
);
|
|
1618
|
+
const editVerdict = this.ignoreVerdict(p, "file");
|
|
1463
1619
|
this.deps.audit.recordAccess(this.deps.ctx, {
|
|
1464
1620
|
intent: "edit",
|
|
1465
1621
|
absolute: p,
|
|
@@ -1513,28 +1669,6 @@ function isContentHash(value) {
|
|
|
1513
1669
|
return typeof value === "string" && CONTENT_HASH_RE.test(value);
|
|
1514
1670
|
}
|
|
1515
1671
|
__name(isContentHash, "isContentHash");
|
|
1516
|
-
var PathMutexRegistry = class {
|
|
1517
|
-
static {
|
|
1518
|
-
__name(this, "PathMutexRegistry");
|
|
1519
|
-
}
|
|
1520
|
-
tails = /* @__PURE__ */ new Map();
|
|
1521
|
-
async runExclusive(key, fn) {
|
|
1522
|
-
const previous = this.tails.get(key) ?? Promise.resolve();
|
|
1523
|
-
let release;
|
|
1524
|
-
const current = new Promise((resolve4) => {
|
|
1525
|
-
release = resolve4;
|
|
1526
|
-
});
|
|
1527
|
-
const tail = previous.catch(() => void 0).then(() => current);
|
|
1528
|
-
this.tails.set(key, tail);
|
|
1529
|
-
await previous.catch(() => void 0);
|
|
1530
|
-
try {
|
|
1531
|
-
return await fn();
|
|
1532
|
-
} finally {
|
|
1533
|
-
release();
|
|
1534
|
-
if (this.tails.get(key) === tail) this.tails.delete(key);
|
|
1535
|
-
}
|
|
1536
|
-
}
|
|
1537
|
-
};
|
|
1538
1672
|
function validateWriteTextAtomicOptions(opts) {
|
|
1539
1673
|
if (opts.mode !== "create" && opts.mode !== "replace") {
|
|
1540
1674
|
throw new FsError(
|
|
@@ -2080,6 +2214,17 @@ function kindFromStatLike(s) {
|
|
|
2080
2214
|
return "other";
|
|
2081
2215
|
}
|
|
2082
2216
|
__name(kindFromStatLike, "kindFromStatLike");
|
|
2217
|
+
function errorCode(err) {
|
|
2218
|
+
return err?.code;
|
|
2219
|
+
}
|
|
2220
|
+
__name(errorCode, "errorCode");
|
|
2221
|
+
function errorKindForRealpathFailure(err) {
|
|
2222
|
+
const code = errorCode(err);
|
|
2223
|
+
if (code === "EACCES" || code === "EPERM") return "permission_denied";
|
|
2224
|
+
if (code === "ENOENT" || code === "ELOOP") return "symlink_escape";
|
|
2225
|
+
return "io_error";
|
|
2226
|
+
}
|
|
2227
|
+
__name(errorKindForRealpathFailure, "errorKindForRealpathFailure");
|
|
2083
2228
|
function buildWriteMeta(opts) {
|
|
2084
2229
|
const meta = {};
|
|
2085
2230
|
if (opts.bom !== void 0) meta["bom"] = opts.bom;
|
|
@@ -4742,8 +4887,78 @@ function createDaemonSubagentManager(boundWorkspace, safeMode = false) {
|
|
|
4742
4887
|
}
|
|
4743
4888
|
__name(createDaemonSubagentManager, "createDaemonSubagentManager");
|
|
4744
4889
|
|
|
4890
|
+
// packages/cli/src/serve/session-organization-helpers.ts
|
|
4891
|
+
init_esbuild_shims();
|
|
4892
|
+
function createSessionOrganizationService(workspaceCwd) {
|
|
4893
|
+
return new SessionOrganizationService(workspaceCwd, (message) => {
|
|
4894
|
+
writeStderrLine(`qwen serve: session-org: ${message}`);
|
|
4895
|
+
});
|
|
4896
|
+
}
|
|
4897
|
+
__name(createSessionOrganizationService, "createSessionOrganizationService");
|
|
4898
|
+
|
|
4745
4899
|
// packages/cli/src/serve/server/session-archive.ts
|
|
4746
4900
|
init_esbuild_shims();
|
|
4901
|
+
|
|
4902
|
+
// packages/cli/src/serve/scheduled-task-session-lifecycle.ts
|
|
4903
|
+
init_esbuild_shims();
|
|
4904
|
+
function isBoundTask(task) {
|
|
4905
|
+
return typeof task.sessionId === "string" && task.sessionId.length > 0;
|
|
4906
|
+
}
|
|
4907
|
+
__name(isBoundTask, "isBoundTask");
|
|
4908
|
+
async function disableTasksForSessions(projectRoot, sessionIds) {
|
|
4909
|
+
if (sessionIds.length === 0) return;
|
|
4910
|
+
const targets = new Set(sessionIds);
|
|
4911
|
+
await updateCronTasks(projectRoot, (tasks) => {
|
|
4912
|
+
let changed = false;
|
|
4913
|
+
const next = tasks.map((task) => {
|
|
4914
|
+
if (isBoundTask(task) && targets.has(task.sessionId) && task.enabled !== false) {
|
|
4915
|
+
changed = true;
|
|
4916
|
+
return { ...task, enabled: false, disabledByArchive: true };
|
|
4917
|
+
}
|
|
4918
|
+
return task;
|
|
4919
|
+
});
|
|
4920
|
+
return changed ? next : tasks;
|
|
4921
|
+
});
|
|
4922
|
+
}
|
|
4923
|
+
__name(disableTasksForSessions, "disableTasksForSessions");
|
|
4924
|
+
async function enableTasksForSessions(projectRoot, sessionIds, now = Date.now()) {
|
|
4925
|
+
if (sessionIds.length === 0) return;
|
|
4926
|
+
const targets = new Set(sessionIds);
|
|
4927
|
+
await updateCronTasks(projectRoot, (tasks) => {
|
|
4928
|
+
let changed = false;
|
|
4929
|
+
const next = tasks.map((task) => {
|
|
4930
|
+
if (isBoundTask(task) && targets.has(task.sessionId) && task.enabled === false && task.disabledByArchive === true) {
|
|
4931
|
+
changed = true;
|
|
4932
|
+
const resumed = { ...task, enabled: true };
|
|
4933
|
+
delete resumed.disabledByArchive;
|
|
4934
|
+
const minute = now - now % 6e4;
|
|
4935
|
+
if (resumed.recurring) {
|
|
4936
|
+
resumed.lastFiredAt = minute;
|
|
4937
|
+
} else {
|
|
4938
|
+
resumed.createdAt = now;
|
|
4939
|
+
resumed.lastFiredAt = minute;
|
|
4940
|
+
}
|
|
4941
|
+
return resumed;
|
|
4942
|
+
}
|
|
4943
|
+
return task;
|
|
4944
|
+
});
|
|
4945
|
+
return changed ? next : tasks;
|
|
4946
|
+
});
|
|
4947
|
+
}
|
|
4948
|
+
__name(enableTasksForSessions, "enableTasksForSessions");
|
|
4949
|
+
async function removeTasksForSessions(projectRoot, sessionIds) {
|
|
4950
|
+
if (sessionIds.length === 0) return;
|
|
4951
|
+
const targets = new Set(sessionIds);
|
|
4952
|
+
await updateCronTasks(projectRoot, (tasks) => {
|
|
4953
|
+
const next = tasks.filter(
|
|
4954
|
+
(task) => !isBoundTask(task) || !targets.has(task.sessionId)
|
|
4955
|
+
);
|
|
4956
|
+
return next.length === tasks.length ? tasks : next;
|
|
4957
|
+
});
|
|
4958
|
+
}
|
|
4959
|
+
__name(removeTasksForSessions, "removeTasksForSessions");
|
|
4960
|
+
|
|
4961
|
+
// packages/cli/src/serve/server/session-archive.ts
|
|
4747
4962
|
var SessionArchiveCoordinator = class {
|
|
4748
4963
|
static {
|
|
4749
4964
|
__name(this, "SessionArchiveCoordinator");
|
|
@@ -4846,6 +5061,13 @@ async function deleteDaemonSessions(params) {
|
|
|
4846
5061
|
}
|
|
4847
5062
|
})
|
|
4848
5063
|
);
|
|
5064
|
+
await removeTasksForSessions(service.getProjectRoot(), removed).catch(
|
|
5065
|
+
(err) => {
|
|
5066
|
+
logSessionArchiveWarning(
|
|
5067
|
+
`removeTasksForSessions failed for [${removed.join(", ")}]: ${err instanceof Error ? err.message : String(err)}`
|
|
5068
|
+
);
|
|
5069
|
+
}
|
|
5070
|
+
);
|
|
4849
5071
|
return { removed, notFound, errors: [...closeErrors, ...removeErrors] };
|
|
4850
5072
|
}
|
|
4851
5073
|
__name(deleteDaemonSessions, "deleteDaemonSessions");
|
|
@@ -5002,6 +5224,13 @@ async function archiveDaemonSessions(params) {
|
|
|
5002
5224
|
notFound,
|
|
5003
5225
|
errors
|
|
5004
5226
|
});
|
|
5227
|
+
await disableTasksForSessions(service.getProjectRoot(), archived).catch(
|
|
5228
|
+
(err) => {
|
|
5229
|
+
logSessionArchiveWarning(
|
|
5230
|
+
`disableTasksForSessions failed for [${archived.join(", ")}]: ${err instanceof Error ? err.message : String(err)} \u2014 bound tasks may keep firing until reconciled`
|
|
5231
|
+
);
|
|
5232
|
+
}
|
|
5233
|
+
);
|
|
5005
5234
|
return { archived, alreadyArchived, notFound, errors };
|
|
5006
5235
|
}
|
|
5007
5236
|
__name(archiveDaemonSessions, "archiveDaemonSessions");
|
|
@@ -5048,6 +5277,17 @@ async function unarchiveDaemonSessions(params) {
|
|
|
5048
5277
|
notFound,
|
|
5049
5278
|
errors
|
|
5050
5279
|
});
|
|
5280
|
+
const resumeSessionIds = [.../* @__PURE__ */ new Set([...unarchived, ...alreadyActive])];
|
|
5281
|
+
try {
|
|
5282
|
+
await enableTasksForSessions(service.getProjectRoot(), resumeSessionIds);
|
|
5283
|
+
} catch (err) {
|
|
5284
|
+
logSessionArchiveWarning(
|
|
5285
|
+
`enableTasksForSessions failed for [${resumeSessionIds.join(", ")}]: ${err instanceof Error ? err.message : String(err)}`
|
|
5286
|
+
);
|
|
5287
|
+
for (const sessionId of resumeSessionIds) {
|
|
5288
|
+
errors.push({ sessionId, error: err });
|
|
5289
|
+
}
|
|
5290
|
+
}
|
|
5051
5291
|
return { unarchived, alreadyActive, notFound, errors };
|
|
5052
5292
|
}
|
|
5053
5293
|
__name(unarchiveDaemonSessions, "unarchiveDaemonSessions");
|
|
@@ -5145,6 +5385,11 @@ var ALL_QWEN_VENDOR_METHODS = [
|
|
|
5145
5385
|
`${QWEN_METHOD_NS}session/context`,
|
|
5146
5386
|
`${QWEN_METHOD_NS}session/supported_commands`,
|
|
5147
5387
|
`${QWEN_METHOD_NS}session/update_metadata`,
|
|
5388
|
+
`${QWEN_METHOD_NS}session/update_organization`,
|
|
5389
|
+
`${QWEN_METHOD_NS}workspace/session_groups/list`,
|
|
5390
|
+
`${QWEN_METHOD_NS}workspace/session_groups/create`,
|
|
5391
|
+
`${QWEN_METHOD_NS}workspace/session_groups/update`,
|
|
5392
|
+
`${QWEN_METHOD_NS}workspace/session_groups/delete`,
|
|
5148
5393
|
`${QWEN_METHOD_NS}workspace/mcp`,
|
|
5149
5394
|
`${QWEN_METHOD_NS}workspace/skills`,
|
|
5150
5395
|
`${QWEN_METHOD_NS}workspace/providers`,
|
|
@@ -5343,6 +5588,17 @@ function toRpcError(err) {
|
|
|
5343
5588
|
if (err instanceof AcpParamError || err instanceof InvalidCursorError) {
|
|
5344
5589
|
return { code: RPC.INVALID_PARAMS, message: err.message };
|
|
5345
5590
|
}
|
|
5591
|
+
if (err instanceof SessionOrganizationError) {
|
|
5592
|
+
const isServerSide = err.code === "session_organization_store_unreadable";
|
|
5593
|
+
return {
|
|
5594
|
+
code: isServerSide ? RPC.INTERNAL_ERROR : RPC.INVALID_PARAMS,
|
|
5595
|
+
message: err.message,
|
|
5596
|
+
data: {
|
|
5597
|
+
errorKind: err.code,
|
|
5598
|
+
...err.field ? { field: err.field } : {}
|
|
5599
|
+
}
|
|
5600
|
+
};
|
|
5601
|
+
}
|
|
5346
5602
|
if (err instanceof SubagentError) {
|
|
5347
5603
|
return { code: RPC.INVALID_PARAMS, message: err.message };
|
|
5348
5604
|
}
|
|
@@ -5529,6 +5785,19 @@ var AcpDispatcher = class {
|
|
|
5529
5785
|
workspaceCwd: this.boundWorkspace
|
|
5530
5786
|
};
|
|
5531
5787
|
}
|
|
5788
|
+
parseBoundWorkspaceParam(params) {
|
|
5789
|
+
const rawWorkspace = typeof params["workspaceCwd"] === "string" ? params["workspaceCwd"] : void 0;
|
|
5790
|
+
if (rawWorkspace === void 0) {
|
|
5791
|
+
return this.boundWorkspace;
|
|
5792
|
+
}
|
|
5793
|
+
const requestedWorkspace = canonicalizeWorkspace(
|
|
5794
|
+
parseOptionalWorkspaceCwd2({ cwd: rawWorkspace }, this.boundWorkspace)
|
|
5795
|
+
);
|
|
5796
|
+
if (requestedWorkspace !== this.boundWorkspace) {
|
|
5797
|
+
throw new WorkspaceMismatchError(this.boundWorkspace, requestedWorkspace);
|
|
5798
|
+
}
|
|
5799
|
+
return requestedWorkspace;
|
|
5800
|
+
}
|
|
5532
5801
|
parseSessionIds(params) {
|
|
5533
5802
|
const sessionIds = params["sessionIds"];
|
|
5534
5803
|
if (!Array.isArray(sessionIds) || sessionIds.length === 0 || sessionIds.length > 100 || !sessionIds.every((s) => typeof s === "string")) {
|
|
@@ -5682,7 +5951,8 @@ var AcpDispatcher = class {
|
|
|
5682
5951
|
methods: advertisedQwenVendorMethods(
|
|
5683
5952
|
this.sessionShellCommandEnabled
|
|
5684
5953
|
)
|
|
5685
|
-
}
|
|
5954
|
+
},
|
|
5955
|
+
imageCapability: IMAGE_CAPABILITY
|
|
5686
5956
|
}
|
|
5687
5957
|
}
|
|
5688
5958
|
};
|
|
@@ -5838,7 +6108,8 @@ var AcpDispatcher = class {
|
|
|
5838
6108
|
return method === "session/load" ? await this.bridge.loadSession({
|
|
5839
6109
|
sessionId,
|
|
5840
6110
|
workspaceCwd: cwd,
|
|
5841
|
-
clientId: conn.clientId
|
|
6111
|
+
clientId: conn.clientId,
|
|
6112
|
+
historyReplay: "response"
|
|
5842
6113
|
}) : await this.bridge.resumeSession({
|
|
5843
6114
|
sessionId,
|
|
5844
6115
|
workspaceCwd: cwd,
|
|
@@ -5868,12 +6139,31 @@ var AcpDispatcher = class {
|
|
|
5868
6139
|
return;
|
|
5869
6140
|
}
|
|
5870
6141
|
conn.getOrCreateSession(sessionId).clientId = restored.clientId;
|
|
6142
|
+
if (method === "session/load") {
|
|
6143
|
+
conn.markInitialReplayPending(sessionId);
|
|
6144
|
+
}
|
|
5871
6145
|
conn.ownSession(sessionId);
|
|
5872
6146
|
const loadConfigOptions = await this.configOptionsFor(sessionId);
|
|
5873
6147
|
const loadModels = this.extractModelState(loadConfigOptions);
|
|
5874
6148
|
const loadModes = this.extractModeState(loadConfigOptions);
|
|
6149
|
+
const loadState = restored.state ?? {};
|
|
6150
|
+
const loadMeta = isObject(loadState._meta) ? loadState._meta : void 0;
|
|
6151
|
+
const loadQwenMeta = isObject(loadMeta?.[QWEN_META_KEY]) ? loadMeta[QWEN_META_KEY] : void 0;
|
|
6152
|
+
const replayStatus = method === "session/load" && restored.partial === true ? {
|
|
6153
|
+
partial: true,
|
|
6154
|
+
...typeof restored.replayError === "string" ? { replayError: restored.replayError } : {}
|
|
6155
|
+
} : void 0;
|
|
5875
6156
|
this.replyConn(conn, id, {
|
|
5876
|
-
...
|
|
6157
|
+
...loadState,
|
|
6158
|
+
...replayStatus ? {
|
|
6159
|
+
_meta: {
|
|
6160
|
+
...loadMeta ?? {},
|
|
6161
|
+
[QWEN_META_KEY]: {
|
|
6162
|
+
...loadQwenMeta ?? {},
|
|
6163
|
+
sessionLoadReplay: replayStatus
|
|
6164
|
+
}
|
|
6165
|
+
}
|
|
6166
|
+
} : {},
|
|
5877
6167
|
...loadConfigOptions ? { configOptions: loadConfigOptions } : {},
|
|
5878
6168
|
...loadModels ? { models: loadModels } : {},
|
|
5879
6169
|
...loadModes ? { modes: loadModes } : {}
|
|
@@ -5881,22 +6171,22 @@ var AcpDispatcher = class {
|
|
|
5881
6171
|
return;
|
|
5882
6172
|
}
|
|
5883
6173
|
case "session/list": {
|
|
5884
|
-
const
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
)
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
if (requestedWorkspace !== this.boundWorkspace) {
|
|
5892
|
-
throw new WorkspaceMismatchError(
|
|
5893
|
-
this.boundWorkspace,
|
|
5894
|
-
requestedWorkspace
|
|
5895
|
-
);
|
|
6174
|
+
const workspaceCwd = this.parseBoundWorkspaceParam(params);
|
|
6175
|
+
const cursor = typeof params["cursor"] === "string" ? params["cursor"] : void 0;
|
|
6176
|
+
const rawView = typeof params["view"] === "string" ? params["view"] : void 0;
|
|
6177
|
+
let view;
|
|
6178
|
+
if (rawView !== void 0) {
|
|
6179
|
+
if (rawView !== "organized") {
|
|
6180
|
+
throw new AcpParamError('`view` must be "organized"');
|
|
5896
6181
|
}
|
|
5897
|
-
|
|
6182
|
+
view = rawView;
|
|
6183
|
+
}
|
|
6184
|
+
const group = typeof params["group"] === "string" ? params["group"] : void 0;
|
|
6185
|
+
if (group !== void 0 && view !== "organized") {
|
|
6186
|
+
throw new AcpParamError(
|
|
6187
|
+
'`group` requires `view` to be "organized"'
|
|
6188
|
+
);
|
|
5898
6189
|
}
|
|
5899
|
-
const cursor = typeof params["cursor"] === "string" ? params["cursor"] : void 0;
|
|
5900
6190
|
const meta = isObject(params["_meta"]) ? params["_meta"] : void 0;
|
|
5901
6191
|
const metaSize = typeof meta?.["size"] === "number" ? meta["size"] : void 0;
|
|
5902
6192
|
const rawArchiveState = typeof params["archiveState"] === "string" ? params["archiveState"] : typeof meta?.["archiveState"] === "string" ? meta["archiveState"] : void 0;
|
|
@@ -5912,7 +6202,7 @@ var AcpDispatcher = class {
|
|
|
5912
6202
|
const result = await listWorkspaceSessionsForResponse(
|
|
5913
6203
|
this.bridge,
|
|
5914
6204
|
workspaceCwd,
|
|
5915
|
-
{ cursor, size: metaSize, archiveState }
|
|
6205
|
+
{ cursor, size: metaSize, archiveState, view, group }
|
|
5916
6206
|
);
|
|
5917
6207
|
this.replyConn(conn, id, {
|
|
5918
6208
|
sessions: result.sessions.map((s) => ({
|
|
@@ -5925,9 +6215,15 @@ var AcpDispatcher = class {
|
|
|
5925
6215
|
title: s.displayName,
|
|
5926
6216
|
clientCount: s.clientCount,
|
|
5927
6217
|
hasActivePrompt: s.hasActivePrompt,
|
|
5928
|
-
isArchived: s.isArchived === true
|
|
6218
|
+
isArchived: s.isArchived === true,
|
|
6219
|
+
...s.isPinned !== void 0 ? { isPinned: s.isPinned } : {},
|
|
6220
|
+
...s.pinnedAt !== void 0 ? { pinnedAt: s.pinnedAt } : {},
|
|
6221
|
+
...s.groupId !== void 0 ? { groupId: s.groupId } : {},
|
|
6222
|
+
...s.color !== void 0 ? { color: s.color } : {}
|
|
5929
6223
|
})),
|
|
5930
|
-
...result.nextCursor != null ? { nextCursor: result.nextCursor } : {}
|
|
6224
|
+
...result.nextCursor != null ? { nextCursor: result.nextCursor } : {},
|
|
6225
|
+
...result.liveMergeFailed ? { liveMergeFailed: true } : {},
|
|
6226
|
+
...result.truncated ? { truncated: true } : {}
|
|
5931
6227
|
});
|
|
5932
6228
|
return;
|
|
5933
6229
|
}
|
|
@@ -6449,6 +6745,94 @@ var AcpDispatcher = class {
|
|
|
6449
6745
|
});
|
|
6450
6746
|
return;
|
|
6451
6747
|
}
|
|
6748
|
+
case `${QWEN_METHOD_NS}session/update_organization`: {
|
|
6749
|
+
const sessionId = String(params["sessionId"] ?? "");
|
|
6750
|
+
if (!sessionId) {
|
|
6751
|
+
throw new AcpParamError("`sessionId` is required");
|
|
6752
|
+
}
|
|
6753
|
+
if ("isPinned" in params && typeof params["isPinned"] !== "boolean") {
|
|
6754
|
+
throw new AcpParamError("`isPinned` must be a boolean");
|
|
6755
|
+
}
|
|
6756
|
+
if ("groupId" in params && params["groupId"] !== null && typeof params["groupId"] !== "string") {
|
|
6757
|
+
throw new AcpParamError("`groupId` must be a string or null");
|
|
6758
|
+
}
|
|
6759
|
+
if ("color" in params && params["color"] !== null && (typeof params["color"] !== "string" || !GROUP_COLOR_OPTIONS.includes(
|
|
6760
|
+
params["color"]
|
|
6761
|
+
))) {
|
|
6762
|
+
throw new AcpParamError(
|
|
6763
|
+
"`color` must be a supported color or null"
|
|
6764
|
+
);
|
|
6765
|
+
}
|
|
6766
|
+
await this.archiveCoordinator.runSharedMany([sessionId], async () => {
|
|
6767
|
+
const sessionService = new SessionService(this.boundWorkspace);
|
|
6768
|
+
let exists = await sessionService.sessionExistsInAnyState(sessionId);
|
|
6769
|
+
if (!exists) {
|
|
6770
|
+
try {
|
|
6771
|
+
const liveSummary = this.bridge.getSessionSummary(sessionId);
|
|
6772
|
+
exists = liveSummary.workspaceCwd === this.boundWorkspace;
|
|
6773
|
+
} catch {
|
|
6774
|
+
exists = false;
|
|
6775
|
+
}
|
|
6776
|
+
}
|
|
6777
|
+
if (!exists) {
|
|
6778
|
+
throw new AcpParamError(`Session not found: ${sessionId}`);
|
|
6779
|
+
}
|
|
6780
|
+
const organization = await createSessionOrganizationService(
|
|
6781
|
+
this.boundWorkspace
|
|
6782
|
+
).updateSessionOrganization(sessionId, {
|
|
6783
|
+
...typeof params["isPinned"] === "boolean" ? { isPinned: params["isPinned"] } : {},
|
|
6784
|
+
..."groupId" in params ? { groupId: params["groupId"] } : {},
|
|
6785
|
+
..."color" in params ? { color: params["color"] } : {}
|
|
6786
|
+
});
|
|
6787
|
+
this.replyConn(conn, id, { sessionId, ...organization });
|
|
6788
|
+
});
|
|
6789
|
+
return;
|
|
6790
|
+
}
|
|
6791
|
+
case `${QWEN_METHOD_NS}workspace/session_groups/list`: {
|
|
6792
|
+
const workspaceCwd = this.parseBoundWorkspaceParam(params);
|
|
6793
|
+
const groups = await createSessionOrganizationService(workspaceCwd).listGroups();
|
|
6794
|
+
this.replyConn(conn, id, groups);
|
|
6795
|
+
return;
|
|
6796
|
+
}
|
|
6797
|
+
case `${QWEN_METHOD_NS}workspace/session_groups/create`: {
|
|
6798
|
+
const workspaceCwd = this.parseBoundWorkspaceParam(params);
|
|
6799
|
+
const group = await createSessionOrganizationService(
|
|
6800
|
+
workspaceCwd
|
|
6801
|
+
).createGroup({
|
|
6802
|
+
name: params["name"],
|
|
6803
|
+
color: params["color"]
|
|
6804
|
+
});
|
|
6805
|
+
this.replyConn(conn, id, { group });
|
|
6806
|
+
return;
|
|
6807
|
+
}
|
|
6808
|
+
case `${QWEN_METHOD_NS}workspace/session_groups/update`: {
|
|
6809
|
+
const workspaceCwd = this.parseBoundWorkspaceParam(params);
|
|
6810
|
+
const groupId = String(params["groupId"] ?? "");
|
|
6811
|
+
if (!groupId) {
|
|
6812
|
+
throw new AcpParamError("`groupId` is required");
|
|
6813
|
+
}
|
|
6814
|
+
const group = await createSessionOrganizationService(
|
|
6815
|
+
workspaceCwd
|
|
6816
|
+
).updateGroup(groupId, {
|
|
6817
|
+
..."name" in params ? { name: params["name"] } : {},
|
|
6818
|
+
..."color" in params ? { color: params["color"] } : {},
|
|
6819
|
+
..."order" in params ? { order: params["order"] } : {}
|
|
6820
|
+
});
|
|
6821
|
+
this.replyConn(conn, id, { group });
|
|
6822
|
+
return;
|
|
6823
|
+
}
|
|
6824
|
+
case `${QWEN_METHOD_NS}workspace/session_groups/delete`: {
|
|
6825
|
+
const workspaceCwd = this.parseBoundWorkspaceParam(params);
|
|
6826
|
+
const groupId = String(params["groupId"] ?? "");
|
|
6827
|
+
if (!groupId) {
|
|
6828
|
+
throw new AcpParamError("`groupId` is required");
|
|
6829
|
+
}
|
|
6830
|
+
const deleted = await createSessionOrganizationService(workspaceCwd).deleteGroup(
|
|
6831
|
+
groupId
|
|
6832
|
+
);
|
|
6833
|
+
this.replyConn(conn, id, { deleted });
|
|
6834
|
+
return;
|
|
6835
|
+
}
|
|
6452
6836
|
case `${QWEN_METHOD_NS}workspace/mcp`:
|
|
6453
6837
|
this.replyConn(
|
|
6454
6838
|
conn,
|
|
@@ -8144,12 +8528,46 @@ var AcpDispatcher = class {
|
|
|
8144
8528
|
*/
|
|
8145
8529
|
async pumpSessionEvents(conn, sessionId, signal, lastEventId) {
|
|
8146
8530
|
try {
|
|
8531
|
+
let lastDeliveredId = lastEventId ?? 0;
|
|
8532
|
+
let sawEviction = false;
|
|
8533
|
+
let subscribeFromEventId = lastEventId;
|
|
8534
|
+
if (conn.hasInitialReplayPending(sessionId)) {
|
|
8535
|
+
const snapshot = this.bridge.getSessionReplaySnapshot(sessionId);
|
|
8536
|
+
if (snapshot) {
|
|
8537
|
+
const snapshotEvents = [
|
|
8538
|
+
...snapshot.compactedTurns,
|
|
8539
|
+
...snapshot.liveJournal
|
|
8540
|
+
];
|
|
8541
|
+
for (const event of snapshotEvents) {
|
|
8542
|
+
if (signal.aborted) return;
|
|
8543
|
+
if (typeof event.id === "number" && event.id <= lastDeliveredId) {
|
|
8544
|
+
continue;
|
|
8545
|
+
}
|
|
8546
|
+
conn.touch();
|
|
8547
|
+
this.translateEvent(conn, sessionId, event);
|
|
8548
|
+
if (typeof event.id === "number") {
|
|
8549
|
+
lastDeliveredId = event.id;
|
|
8550
|
+
conn.releaseDeferredSessionReplies(sessionId, event.id);
|
|
8551
|
+
}
|
|
8552
|
+
}
|
|
8553
|
+
if (signal.aborted) return;
|
|
8554
|
+
lastDeliveredId = Math.max(lastDeliveredId, snapshot.lastEventId);
|
|
8555
|
+
subscribeFromEventId = Math.max(
|
|
8556
|
+
subscribeFromEventId ?? 0,
|
|
8557
|
+
snapshot.lastEventId
|
|
8558
|
+
);
|
|
8559
|
+
} else {
|
|
8560
|
+
writeStderrLine(
|
|
8561
|
+
`qwen serve: /acp initial replay skipped (no snapshot) session=${logSafe(sessionId)}`
|
|
8562
|
+
);
|
|
8563
|
+
conn.markInitialReplayComplete(sessionId);
|
|
8564
|
+
conn.endReplayDeferral(sessionId, lastDeliveredId, false);
|
|
8565
|
+
}
|
|
8566
|
+
}
|
|
8147
8567
|
const iterable = this.bridge.subscribeEvents(sessionId, {
|
|
8148
8568
|
signal,
|
|
8149
|
-
...
|
|
8569
|
+
...subscribeFromEventId !== void 0 ? { lastEventId: subscribeFromEventId } : {}
|
|
8150
8570
|
});
|
|
8151
|
-
let lastDeliveredId = lastEventId ?? 0;
|
|
8152
|
-
let sawEviction = false;
|
|
8153
8571
|
for await (const event of iterable) {
|
|
8154
8572
|
if (signal.aborted) break;
|
|
8155
8573
|
conn.touch();
|
|
@@ -8161,6 +8579,7 @@ var AcpDispatcher = class {
|
|
|
8161
8579
|
}
|
|
8162
8580
|
if (event.type === "replay_complete") {
|
|
8163
8581
|
conn.endReplayDeferral(sessionId, lastDeliveredId, sawEviction);
|
|
8582
|
+
conn.markInitialReplayComplete(sessionId);
|
|
8164
8583
|
const replayedCount = event.data?.replayedCount;
|
|
8165
8584
|
writeStderrLine(
|
|
8166
8585
|
`qwen serve: /acp replay complete (${logSafe(sessionId)}) from=${lastEventId ?? "none"} delivered_through=${lastDeliveredId} count=${replayedCount ?? "n/a"} evicted=${sawEviction}`
|
|
@@ -8489,6 +8908,16 @@ var AcpConnection = class {
|
|
|
8489
8908
|
}
|
|
8490
8909
|
return binding;
|
|
8491
8910
|
}
|
|
8911
|
+
markInitialReplayPending(sessionId) {
|
|
8912
|
+
this.getOrCreateSession(sessionId).initialReplayPending = true;
|
|
8913
|
+
}
|
|
8914
|
+
hasInitialReplayPending(sessionId) {
|
|
8915
|
+
return this.sessions.get(sessionId)?.initialReplayPending === true;
|
|
8916
|
+
}
|
|
8917
|
+
markInitialReplayComplete(sessionId) {
|
|
8918
|
+
const binding = this.sessions.get(sessionId);
|
|
8919
|
+
if (binding) binding.initialReplayPending = false;
|
|
8920
|
+
}
|
|
8492
8921
|
getDiagnostic() {
|
|
8493
8922
|
const liveStreams = /* @__PURE__ */ new Set();
|
|
8494
8923
|
if (this.connStream && !this.connStream.isClosed) {
|
|
@@ -8676,15 +9105,16 @@ var AcpConnection = class {
|
|
|
8676
9105
|
if (prevStream && prevStream !== stream && prevStream !== this.connStream) {
|
|
8677
9106
|
prevStream.close();
|
|
8678
9107
|
}
|
|
8679
|
-
|
|
9108
|
+
const replayingOnAttach = resumeFromEventId !== void 0 || binding.initialReplayPending === true;
|
|
9109
|
+
binding.replayPending = replayingOnAttach;
|
|
8680
9110
|
if (binding.replayPending) {
|
|
8681
9111
|
writeStderrLine(
|
|
8682
|
-
`qwen serve: /acp replay deferral armed (${logSafe(sessionId)}, from id ${resumeFromEventId})`
|
|
9112
|
+
`qwen serve: /acp replay deferral armed (${logSafe(sessionId)}, from id ${resumeFromEventId ?? "initial"})`
|
|
8683
9113
|
);
|
|
8684
9114
|
}
|
|
8685
9115
|
const gap = binding.buffer.splice(0);
|
|
8686
9116
|
for (const entry of gap) {
|
|
8687
|
-
if (
|
|
9117
|
+
if (!replayingOnAttach) {
|
|
8688
9118
|
void stream.send(entry.frame, entry.id);
|
|
8689
9119
|
} else if (entry.id !== void 0) {
|
|
8690
9120
|
continue;
|
|
@@ -9459,10 +9889,10 @@ function isCdpInboundFrame(frame) {
|
|
|
9459
9889
|
}
|
|
9460
9890
|
__name(isCdpInboundFrame, "isCdpInboundFrame");
|
|
9461
9891
|
var CdpReverseLink = class {
|
|
9462
|
-
constructor(sendToExtension, commandTimeoutMs = DEFAULT_COMMAND_TIMEOUT_MS,
|
|
9892
|
+
constructor(sendToExtension, commandTimeoutMs = DEFAULT_COMMAND_TIMEOUT_MS, log2) {
|
|
9463
9893
|
this.sendToExtension = sendToExtension;
|
|
9464
9894
|
this.commandTimeoutMs = commandTimeoutMs;
|
|
9465
|
-
this.log =
|
|
9895
|
+
this.log = log2;
|
|
9466
9896
|
}
|
|
9467
9897
|
static {
|
|
9468
9898
|
__name(this, "CdpReverseLink");
|
|
@@ -9904,10 +10334,10 @@ var CdpBrowserEmulator = class {
|
|
|
9904
10334
|
var CLOSE_NO_BRIDGE = 1011;
|
|
9905
10335
|
var CLOSE_NORMAL = 1e3;
|
|
9906
10336
|
var CDP_WS_HEARTBEAT_MS = 15e3;
|
|
9907
|
-
function attachCdpClient(ws, registry,
|
|
10337
|
+
function attachCdpClient(ws, registry, log2) {
|
|
9908
10338
|
const bridge = registry.getActive();
|
|
9909
10339
|
if (!bridge) {
|
|
9910
|
-
|
|
10340
|
+
log2("qwen serve: /cdp rejected \u2014 no extension bridge connected");
|
|
9911
10341
|
try {
|
|
9912
10342
|
ws.close(
|
|
9913
10343
|
CLOSE_NO_BRIDGE,
|
|
@@ -9918,7 +10348,7 @@ function attachCdpClient(ws, registry, log) {
|
|
|
9918
10348
|
return;
|
|
9919
10349
|
}
|
|
9920
10350
|
if (bridge.cdpBound) {
|
|
9921
|
-
|
|
10351
|
+
log2("qwen serve: /cdp rejected \u2014 a puppeteer client is already bound");
|
|
9922
10352
|
try {
|
|
9923
10353
|
ws.close(CLOSE_NO_BRIDGE, "A CDP client is already connected");
|
|
9924
10354
|
} catch {
|
|
@@ -9929,19 +10359,19 @@ function attachCdpClient(ws, registry, log) {
|
|
|
9929
10359
|
const link = new CdpReverseLink(
|
|
9930
10360
|
(frame) => bridge.send(frame),
|
|
9931
10361
|
void 0,
|
|
9932
|
-
|
|
10362
|
+
log2
|
|
9933
10363
|
);
|
|
9934
10364
|
const emulator = new CdpBrowserEmulator({
|
|
9935
10365
|
reply: /* @__PURE__ */ __name((frame) => {
|
|
9936
10366
|
safeWsSend(ws, JSON.stringify(frame), "CDP");
|
|
9937
10367
|
}, "reply"),
|
|
9938
10368
|
forwardToTab: link.forwardToTab,
|
|
9939
|
-
log
|
|
10369
|
+
log: log2
|
|
9940
10370
|
});
|
|
9941
10371
|
link.bindEmulator(emulator);
|
|
9942
10372
|
bridge.routeInbound = (frame) => link.handleInbound(frame);
|
|
9943
10373
|
link.onDetach = (reason) => {
|
|
9944
|
-
|
|
10374
|
+
log2(`qwen serve: /cdp tab detached (${reason}); closing puppeteer socket`);
|
|
9945
10375
|
try {
|
|
9946
10376
|
ws.close(CLOSE_NORMAL, `tab detached: ${reason}`);
|
|
9947
10377
|
} catch {
|
|
@@ -9952,7 +10382,7 @@ function attachCdpClient(ws, registry, log) {
|
|
|
9952
10382
|
const heartbeat = setInterval(() => {
|
|
9953
10383
|
if (disposed) return;
|
|
9954
10384
|
if (!heartbeatAlive) {
|
|
9955
|
-
|
|
10385
|
+
log2("qwen serve: /cdp heartbeat missed; closing puppeteer socket");
|
|
9956
10386
|
dispose("puppeteer /cdp heartbeat missed");
|
|
9957
10387
|
try {
|
|
9958
10388
|
ws.close(CLOSE_NORMAL, "cdp heartbeat missed");
|
|
@@ -9964,7 +10394,7 @@ function attachCdpClient(ws, registry, log) {
|
|
|
9964
10394
|
try {
|
|
9965
10395
|
ws.ping();
|
|
9966
10396
|
} catch (err) {
|
|
9967
|
-
|
|
10397
|
+
log2(
|
|
9968
10398
|
`qwen serve: /cdp heartbeat ping failed: ${err instanceof Error ? err.message : String(err)}`
|
|
9969
10399
|
);
|
|
9970
10400
|
dispose("puppeteer /cdp heartbeat ping failed");
|
|
@@ -9982,11 +10412,11 @@ function attachCdpClient(ws, registry, log) {
|
|
|
9982
10412
|
if (notifyExtension && registry.getActive() === bridge) {
|
|
9983
10413
|
try {
|
|
9984
10414
|
bridge.send({ type: CDP_FRAME_TYPES.release });
|
|
9985
|
-
|
|
10415
|
+
log2(
|
|
9986
10416
|
`qwen serve: /cdp sent release to extension (puppeteer disconnected: ${reason})`
|
|
9987
10417
|
);
|
|
9988
10418
|
} catch (err) {
|
|
9989
|
-
|
|
10419
|
+
log2(
|
|
9990
10420
|
`qwen serve: /cdp release send failed: ${err instanceof Error ? err.message : String(err)}`
|
|
9991
10421
|
);
|
|
9992
10422
|
}
|
|
@@ -9999,7 +10429,7 @@ function attachCdpClient(ws, registry, log) {
|
|
|
9999
10429
|
}
|
|
10000
10430
|
}, "dispose");
|
|
10001
10431
|
link.onAttachFailure = (reason) => {
|
|
10002
|
-
|
|
10432
|
+
log2(`qwen serve: /cdp attach failed (${reason}); closing puppeteer socket`);
|
|
10003
10433
|
dispose(`cdp_attach failed: ${reason}`, false);
|
|
10004
10434
|
try {
|
|
10005
10435
|
ws.close(CLOSE_NO_BRIDGE, "cdp attach failed");
|
|
@@ -10010,7 +10440,7 @@ function attachCdpClient(ws, registry, log) {
|
|
|
10010
10440
|
heartbeatAlive = true;
|
|
10011
10441
|
});
|
|
10012
10442
|
bridge.onExtensionGone = () => {
|
|
10013
|
-
|
|
10443
|
+
log2("qwen serve: extension /acp dropped; closing puppeteer /cdp socket");
|
|
10014
10444
|
dispose("extension /acp disconnected", false);
|
|
10015
10445
|
try {
|
|
10016
10446
|
ws.close(CLOSE_NORMAL, "extension disconnected");
|
|
@@ -10026,19 +10456,19 @@ function attachCdpClient(ws, registry, log) {
|
|
|
10026
10456
|
return;
|
|
10027
10457
|
}
|
|
10028
10458
|
void emulator.handleFromClient(frame).catch((err) => {
|
|
10029
|
-
|
|
10459
|
+
log2(
|
|
10030
10460
|
`qwen serve: /cdp emulator error: ${err instanceof Error ? err.message : String(err)}`
|
|
10031
10461
|
);
|
|
10032
10462
|
});
|
|
10033
10463
|
});
|
|
10034
10464
|
ws.on("close", () => dispose("puppeteer /cdp socket closed"));
|
|
10035
10465
|
ws.on("error", (err) => {
|
|
10036
|
-
|
|
10466
|
+
log2(
|
|
10037
10467
|
`qwen serve: /cdp WS error: ${err instanceof Error ? err.message : String(err)}`
|
|
10038
10468
|
);
|
|
10039
10469
|
dispose("puppeteer /cdp socket error");
|
|
10040
10470
|
});
|
|
10041
|
-
|
|
10471
|
+
log2("qwen serve: /cdp puppeteer client bound to extension bridge");
|
|
10042
10472
|
}
|
|
10043
10473
|
__name(attachCdpClient, "attachCdpClient");
|
|
10044
10474
|
|
|
@@ -10132,6 +10562,7 @@ var WS_READ_METHODS = /* @__PURE__ */ new Set([
|
|
|
10132
10562
|
"_qwen/workspace/providers",
|
|
10133
10563
|
"_qwen/workspace/env",
|
|
10134
10564
|
"_qwen/workspace/preflight",
|
|
10565
|
+
"_qwen/workspace/session_groups/list",
|
|
10135
10566
|
"_qwen/workspace/trust",
|
|
10136
10567
|
"_qwen/workspace/permissions",
|
|
10137
10568
|
"_qwen/workspace/voice",
|
|
@@ -10176,7 +10607,7 @@ var MAX_INFLIGHT_MCP_DISPATCH = 8;
|
|
|
10176
10607
|
function mountAcpHttp(app, bridge, opts) {
|
|
10177
10608
|
const enabled = opts.enabled ?? process.env["QWEN_SERVE_ACP_HTTP"] !== "0";
|
|
10178
10609
|
if (!enabled) return void 0;
|
|
10179
|
-
const
|
|
10610
|
+
const path14 = opts.path ?? "/acp";
|
|
10180
10611
|
const dispatcherRef = {};
|
|
10181
10612
|
const registry = new ConnectionRegistry(
|
|
10182
10613
|
(req, clientId) => {
|
|
@@ -10313,7 +10744,7 @@ function mountAcpHttp(app, bridge, opts) {
|
|
|
10313
10744
|
opts.archiveCoordinator ?? new SessionArchiveCoordinator()
|
|
10314
10745
|
);
|
|
10315
10746
|
dispatcherRef.current = dispatcher;
|
|
10316
|
-
app.post(
|
|
10747
|
+
app.post(path14, async (req, res) => {
|
|
10317
10748
|
const ct = req.headers["content-type"];
|
|
10318
10749
|
if (!ct || !ct.startsWith("application/json")) {
|
|
10319
10750
|
res.status(415).json({ error: "Content-Type must be application/json" });
|
|
@@ -10417,7 +10848,7 @@ function mountAcpHttp(app, bridge, opts) {
|
|
|
10417
10848
|
);
|
|
10418
10849
|
});
|
|
10419
10850
|
});
|
|
10420
|
-
app.get(
|
|
10851
|
+
app.get(path14, (req, res) => {
|
|
10421
10852
|
const accept = req.headers["accept"] ?? "";
|
|
10422
10853
|
if (!accept.includes("text/event-stream")) {
|
|
10423
10854
|
res.status(406).json({ error: "Accept header must include text/event-stream" });
|
|
@@ -10503,7 +10934,7 @@ function mountAcpHttp(app, bridge, opts) {
|
|
|
10503
10934
|
onPumpSettled();
|
|
10504
10935
|
});
|
|
10505
10936
|
});
|
|
10506
|
-
app.delete(
|
|
10937
|
+
app.delete(path14, (req, res) => {
|
|
10507
10938
|
const connectionId = headerOf(req, ACP_CONNECTION_HEADER);
|
|
10508
10939
|
if (!connectionId) {
|
|
10509
10940
|
res.status(400).json({ error: "Missing Acp-Connection-Id" });
|
|
@@ -10566,7 +10997,7 @@ function mountAcpHttp(app, bridge, opts) {
|
|
|
10566
10997
|
const extraRoute = opts.extraWsRoutes?.find(
|
|
10567
10998
|
(route) => route.path === url.pathname
|
|
10568
10999
|
);
|
|
10569
|
-
if (url.pathname !==
|
|
11000
|
+
if (url.pathname !== path14 && !isCdpPath && !extraRoute) {
|
|
10570
11001
|
logReject(`unknown-path ${url.pathname}`);
|
|
10571
11002
|
socket.destroy();
|
|
10572
11003
|
return;
|
|
@@ -10963,7 +11394,7 @@ function mountAcpHttp(app, bridge, opts) {
|
|
|
10963
11394
|
});
|
|
10964
11395
|
}, "upgradeListener");
|
|
10965
11396
|
httpServer.on("upgrade", upgradeListener);
|
|
10966
|
-
writeStderrLine(`qwen serve: /acp WebSocket transport enabled on ${
|
|
11397
|
+
writeStderrLine(`qwen serve: /acp WebSocket transport enabled on ${path14}`);
|
|
10967
11398
|
}
|
|
10968
11399
|
__name(setupWebSocket, "setupWebSocket");
|
|
10969
11400
|
return {
|
|
@@ -11558,7 +11989,8 @@ function registerDaemonStatusRoutes(app, deps) {
|
|
|
11558
11989
|
deviceFlowRegistry: deps.deviceFlowRegistry,
|
|
11559
11990
|
sessionShellCommandEnabled: deps.sessionShellCommandEnabled,
|
|
11560
11991
|
getChannelWorkerSnapshot: deps.getChannelWorkerSnapshot,
|
|
11561
|
-
getPerfSnapshot: deps.getPerfSnapshot
|
|
11992
|
+
getPerfSnapshot: deps.getPerfSnapshot,
|
|
11993
|
+
getMetricsSeries: deps.getMetricsSeries
|
|
11562
11994
|
})
|
|
11563
11995
|
);
|
|
11564
11996
|
} catch (err) {
|
|
@@ -13951,12 +14383,13 @@ __name(resolvePromptDeadlineMs, "resolvePromptDeadlineMs");
|
|
|
13951
14383
|
init_esbuild_shims();
|
|
13952
14384
|
var DEFAULT_SESSION_PAGE_SIZE = 20;
|
|
13953
14385
|
var MAX_SESSION_PAGE_SIZE = 100;
|
|
14386
|
+
var MAX_ORGANIZED_SESSIONS = 5e4;
|
|
13954
14387
|
var InvalidCursorError = class extends Error {
|
|
13955
14388
|
static {
|
|
13956
14389
|
__name(this, "InvalidCursorError");
|
|
13957
14390
|
}
|
|
13958
|
-
constructor(cursor) {
|
|
13959
|
-
super(`Invalid cursor: "${cursor}" is not a valid
|
|
14391
|
+
constructor(cursor, kind = "numeric") {
|
|
14392
|
+
super(`Invalid cursor: "${cursor}" is not a valid ${kind} cursor`);
|
|
13960
14393
|
this.name = "InvalidCursorError";
|
|
13961
14394
|
}
|
|
13962
14395
|
};
|
|
@@ -13970,34 +14403,241 @@ function parseSessionCursor(cursor) {
|
|
|
13970
14403
|
return parsed;
|
|
13971
14404
|
}
|
|
13972
14405
|
__name(parseSessionCursor, "parseSessionCursor");
|
|
13973
|
-
|
|
13974
|
-
|
|
13975
|
-
|
|
13976
|
-
|
|
13977
|
-
|
|
13978
|
-
|
|
13979
|
-
|
|
14406
|
+
function parseOrganizedCursor(cursor, expected) {
|
|
14407
|
+
if (cursor === "") return void 0;
|
|
14408
|
+
try {
|
|
14409
|
+
const decoded = Buffer.from(cursor, "base64url").toString("utf8");
|
|
14410
|
+
const parsed = JSON.parse(decoded);
|
|
14411
|
+
const last = parsed.last;
|
|
14412
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed) || typeof last !== "object" || last === null || Array.isArray(last) || typeof last.isPinned !== "boolean" || typeof last.activityTime !== "number" || !Number.isFinite(last.activityTime) || typeof last.sessionId !== "string" || last.sessionId.length === 0 || parsed.group !== expected.group || parsed.archiveState !== expected.archiveState) {
|
|
14413
|
+
throw new Error("invalid organized cursor");
|
|
14414
|
+
}
|
|
14415
|
+
return last;
|
|
14416
|
+
} catch {
|
|
14417
|
+
throw new InvalidCursorError(cursor, "organized");
|
|
13980
14418
|
}
|
|
13981
|
-
|
|
13982
|
-
|
|
13983
|
-
|
|
13984
|
-
|
|
13985
|
-
|
|
13986
|
-
|
|
13987
|
-
|
|
13988
|
-
|
|
13989
|
-
|
|
13990
|
-
|
|
13991
|
-
|
|
13992
|
-
|
|
13993
|
-
|
|
13994
|
-
|
|
13995
|
-
|
|
13996
|
-
|
|
13997
|
-
|
|
13998
|
-
|
|
13999
|
-
|
|
14419
|
+
}
|
|
14420
|
+
__name(parseOrganizedCursor, "parseOrganizedCursor");
|
|
14421
|
+
function encodeOrganizedCursor(last, group, archiveState) {
|
|
14422
|
+
return Buffer.from(
|
|
14423
|
+
JSON.stringify({ group, archiveState, last }),
|
|
14424
|
+
"utf8"
|
|
14425
|
+
).toString("base64url");
|
|
14426
|
+
}
|
|
14427
|
+
__name(encodeOrganizedCursor, "encodeOrganizedCursor");
|
|
14428
|
+
function toSummary2(item) {
|
|
14429
|
+
return {
|
|
14430
|
+
sessionId: item.sessionId,
|
|
14431
|
+
workspaceCwd: item.cwd,
|
|
14432
|
+
createdAt: item.startTime,
|
|
14433
|
+
updatedAt: new Date(item.mtime).toISOString(),
|
|
14434
|
+
displayName: item.customTitle || item.prompt,
|
|
14435
|
+
clientCount: 0,
|
|
14436
|
+
hasActivePrompt: false,
|
|
14437
|
+
isArchived: item.isArchived === true
|
|
14438
|
+
};
|
|
14439
|
+
}
|
|
14440
|
+
__name(toSummary2, "toSummary");
|
|
14441
|
+
async function listAllPersistedSummaries(sessionService, archiveState) {
|
|
14442
|
+
const sessions = [];
|
|
14443
|
+
let truncated = false;
|
|
14444
|
+
let cursor;
|
|
14445
|
+
do {
|
|
14446
|
+
const page = await sessionService.listSessions({
|
|
14447
|
+
cursor,
|
|
14448
|
+
size: 1e4,
|
|
14449
|
+
archiveState
|
|
14000
14450
|
});
|
|
14451
|
+
const remaining = MAX_ORGANIZED_SESSIONS - sessions.length;
|
|
14452
|
+
sessions.push(...page.items.slice(0, remaining).map(toSummary2));
|
|
14453
|
+
cursor = page.nextCursor;
|
|
14454
|
+
if (page.items.length === 0) {
|
|
14455
|
+
break;
|
|
14456
|
+
}
|
|
14457
|
+
if (page.items.length > remaining || sessions.length >= MAX_ORGANIZED_SESSIONS && cursor !== void 0) {
|
|
14458
|
+
writeStderrLine(
|
|
14459
|
+
`qwen serve: organized session list truncated at ${MAX_ORGANIZED_SESSIONS} sessions`
|
|
14460
|
+
);
|
|
14461
|
+
truncated = true;
|
|
14462
|
+
break;
|
|
14463
|
+
}
|
|
14464
|
+
} while (cursor !== void 0);
|
|
14465
|
+
return { sessions, truncated };
|
|
14466
|
+
}
|
|
14467
|
+
__name(listAllPersistedSummaries, "listAllPersistedSummaries");
|
|
14468
|
+
function getSummaryActivityTime(session) {
|
|
14469
|
+
const time = Date.parse(session.updatedAt ?? session.createdAt);
|
|
14470
|
+
return Number.isFinite(time) ? time : 0;
|
|
14471
|
+
}
|
|
14472
|
+
__name(getSummaryActivityTime, "getSummaryActivityTime");
|
|
14473
|
+
function compareOrganizedSessions(activityTimeById, a, b) {
|
|
14474
|
+
return compareOrganizedCursorKeys(
|
|
14475
|
+
getOrganizedCursorKey(activityTimeById, a),
|
|
14476
|
+
getOrganizedCursorKey(activityTimeById, b)
|
|
14477
|
+
);
|
|
14478
|
+
}
|
|
14479
|
+
__name(compareOrganizedSessions, "compareOrganizedSessions");
|
|
14480
|
+
function getOrganizedCursorKey(activityTimeById, session) {
|
|
14481
|
+
return {
|
|
14482
|
+
isPinned: session.isPinned === true,
|
|
14483
|
+
activityTime: activityTimeById.get(session.sessionId) ?? 0,
|
|
14484
|
+
sessionId: session.sessionId
|
|
14485
|
+
};
|
|
14486
|
+
}
|
|
14487
|
+
__name(getOrganizedCursorKey, "getOrganizedCursorKey");
|
|
14488
|
+
function compareOrganizedCursorKeys(a, b) {
|
|
14489
|
+
const byPinned = Number(b.isPinned) - Number(a.isPinned);
|
|
14490
|
+
if (byPinned !== 0) return byPinned;
|
|
14491
|
+
const byTime = b.activityTime - a.activityTime;
|
|
14492
|
+
if (byTime !== 0) return byTime;
|
|
14493
|
+
return a.sessionId.localeCompare(b.sessionId);
|
|
14494
|
+
}
|
|
14495
|
+
__name(compareOrganizedCursorKeys, "compareOrganizedCursorKeys");
|
|
14496
|
+
function applyOrganization(session, organization) {
|
|
14497
|
+
return {
|
|
14498
|
+
...session,
|
|
14499
|
+
groupId: organization?.groupId ?? null,
|
|
14500
|
+
color: organization?.color ?? null,
|
|
14501
|
+
isPinned: organization?.isPinned === true,
|
|
14502
|
+
...organization?.pinnedAt !== void 0 ? { pinnedAt: organization.pinnedAt } : {}
|
|
14503
|
+
};
|
|
14504
|
+
}
|
|
14505
|
+
__name(applyOrganization, "applyOrganization");
|
|
14506
|
+
async function listOrganizedWorkspaceSessionsForResponse(bridge, workspaceCwd, options, pageSize) {
|
|
14507
|
+
const archiveState = options.archiveState ?? "active";
|
|
14508
|
+
const sessionService = new SessionService(workspaceCwd);
|
|
14509
|
+
const organizationService = createSessionOrganizationService(workspaceCwd);
|
|
14510
|
+
const snapshot = await organizationService.readSnapshot();
|
|
14511
|
+
const knownGroupIds = new Set(snapshot.groups.map((group2) => group2.id));
|
|
14512
|
+
const group = options.group ?? "all";
|
|
14513
|
+
if (group !== "all" && group !== "pinned" && group !== "ungrouped" && !knownGroupIds.has(group)) {
|
|
14514
|
+
throw new SessionOrganizationError(
|
|
14515
|
+
`Group not found: ${group}`,
|
|
14516
|
+
"group_not_found",
|
|
14517
|
+
"group"
|
|
14518
|
+
);
|
|
14519
|
+
}
|
|
14520
|
+
const cursorKey = options.cursor !== void 0 ? parseOrganizedCursor(options.cursor, { group, archiveState }) : void 0;
|
|
14521
|
+
const isFirstPage = cursorKey === void 0;
|
|
14522
|
+
let liveMergeFailed = false;
|
|
14523
|
+
const bySessionId = /* @__PURE__ */ new Map();
|
|
14524
|
+
const persisted = await listAllPersistedSummaries(
|
|
14525
|
+
sessionService,
|
|
14526
|
+
archiveState
|
|
14527
|
+
);
|
|
14528
|
+
for (const session of persisted.sessions) {
|
|
14529
|
+
bySessionId.set(
|
|
14530
|
+
session.sessionId,
|
|
14531
|
+
applyOrganization(session, snapshot.sessions.get(session.sessionId))
|
|
14532
|
+
);
|
|
14533
|
+
}
|
|
14534
|
+
if (archiveState !== "archived" && isFirstPage) {
|
|
14535
|
+
try {
|
|
14536
|
+
const liveSessions = bridge.listWorkspaceSessions(workspaceCwd);
|
|
14537
|
+
for (const live of liveSessions) {
|
|
14538
|
+
const existing = bySessionId.get(live.sessionId);
|
|
14539
|
+
const organization = snapshot.sessions.get(live.sessionId);
|
|
14540
|
+
if (existing) {
|
|
14541
|
+
bySessionId.set(
|
|
14542
|
+
live.sessionId,
|
|
14543
|
+
applyOrganization(
|
|
14544
|
+
{
|
|
14545
|
+
...existing,
|
|
14546
|
+
...live,
|
|
14547
|
+
createdAt: existing.createdAt,
|
|
14548
|
+
displayName: live.displayName ?? existing.displayName,
|
|
14549
|
+
updatedAt: live.updatedAt ?? existing.updatedAt,
|
|
14550
|
+
clientCount: live.clientCount,
|
|
14551
|
+
hasActivePrompt: live.hasActivePrompt,
|
|
14552
|
+
isArchived: false
|
|
14553
|
+
},
|
|
14554
|
+
organization
|
|
14555
|
+
)
|
|
14556
|
+
);
|
|
14557
|
+
} else if (!await sessionService.sessionExists(live.sessionId)) {
|
|
14558
|
+
bySessionId.set(
|
|
14559
|
+
live.sessionId,
|
|
14560
|
+
applyOrganization(
|
|
14561
|
+
{
|
|
14562
|
+
...live,
|
|
14563
|
+
createdAt: live.createdAt,
|
|
14564
|
+
clientCount: live.clientCount,
|
|
14565
|
+
hasActivePrompt: live.hasActivePrompt,
|
|
14566
|
+
isArchived: false
|
|
14567
|
+
},
|
|
14568
|
+
organization
|
|
14569
|
+
)
|
|
14570
|
+
);
|
|
14571
|
+
}
|
|
14572
|
+
}
|
|
14573
|
+
} catch (error2) {
|
|
14574
|
+
liveMergeFailed = true;
|
|
14575
|
+
writeStderrLine(
|
|
14576
|
+
`qwen serve: organized session list live merge failed; using persisted sessions only: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
14577
|
+
);
|
|
14578
|
+
}
|
|
14579
|
+
}
|
|
14580
|
+
const filtered = [...bySessionId.values()].filter((session) => {
|
|
14581
|
+
if (group === "all") return true;
|
|
14582
|
+
if (group === "pinned") return session.isPinned === true;
|
|
14583
|
+
if (group === "ungrouped")
|
|
14584
|
+
return session.groupId == null && session.color == null;
|
|
14585
|
+
return session.color == null && session.groupId === group;
|
|
14586
|
+
});
|
|
14587
|
+
const activityTimeById = new Map(
|
|
14588
|
+
filtered.map((session) => [
|
|
14589
|
+
session.sessionId,
|
|
14590
|
+
getSummaryActivityTime(session)
|
|
14591
|
+
])
|
|
14592
|
+
);
|
|
14593
|
+
filtered.sort((a, b) => compareOrganizedSessions(activityTimeById, a, b));
|
|
14594
|
+
const afterCursor = cursorKey === void 0 ? filtered : filtered.filter(
|
|
14595
|
+
(session) => compareOrganizedCursorKeys(
|
|
14596
|
+
cursorKey,
|
|
14597
|
+
getOrganizedCursorKey(activityTimeById, session)
|
|
14598
|
+
) < 0
|
|
14599
|
+
);
|
|
14600
|
+
const page = afterCursor.slice(0, pageSize);
|
|
14601
|
+
const nextCursor = page.length < afterCursor.length ? encodeOrganizedCursor(
|
|
14602
|
+
getOrganizedCursorKey(activityTimeById, page[page.length - 1]),
|
|
14603
|
+
group,
|
|
14604
|
+
archiveState
|
|
14605
|
+
) : void 0;
|
|
14606
|
+
return {
|
|
14607
|
+
sessions: page,
|
|
14608
|
+
...nextCursor !== void 0 ? { nextCursor } : {},
|
|
14609
|
+
...liveMergeFailed ? { liveMergeFailed: true } : {},
|
|
14610
|
+
...persisted.truncated ? { truncated: true } : {}
|
|
14611
|
+
};
|
|
14612
|
+
}
|
|
14613
|
+
__name(listOrganizedWorkspaceSessionsForResponse, "listOrganizedWorkspaceSessionsForResponse");
|
|
14614
|
+
async function listWorkspaceSessionsForResponse(bridge, workspaceCwd, options) {
|
|
14615
|
+
const rawSize = options?.size;
|
|
14616
|
+
const requestedSize = typeof rawSize === "number" && Number.isSafeInteger(rawSize) ? rawSize : DEFAULT_SESSION_PAGE_SIZE;
|
|
14617
|
+
const pageSize = Math.min(Math.max(requestedSize, 1), MAX_SESSION_PAGE_SIZE);
|
|
14618
|
+
if (options?.view === "organized") {
|
|
14619
|
+
return listOrganizedWorkspaceSessionsForResponse(
|
|
14620
|
+
bridge,
|
|
14621
|
+
workspaceCwd,
|
|
14622
|
+
options,
|
|
14623
|
+
pageSize
|
|
14624
|
+
);
|
|
14625
|
+
}
|
|
14626
|
+
let numericCursor;
|
|
14627
|
+
if (options?.cursor != null) {
|
|
14628
|
+
numericCursor = parseSessionCursor(options.cursor);
|
|
14629
|
+
}
|
|
14630
|
+
const isFirstPage = numericCursor === void 0;
|
|
14631
|
+
const sessionService = new SessionService(workspaceCwd);
|
|
14632
|
+
const archiveState = options?.archiveState ?? "active";
|
|
14633
|
+
const persisted = await sessionService.listSessions({
|
|
14634
|
+
cursor: numericCursor,
|
|
14635
|
+
size: pageSize,
|
|
14636
|
+
archiveState
|
|
14637
|
+
});
|
|
14638
|
+
const bySessionId = /* @__PURE__ */ new Map();
|
|
14639
|
+
for (const item of persisted.items) {
|
|
14640
|
+
bySessionId.set(item.sessionId, toSummary2(item));
|
|
14001
14641
|
}
|
|
14002
14642
|
if (archiveState === "archived") {
|
|
14003
14643
|
const sessions2 = [...bySessionId.values()];
|
|
@@ -14048,6 +14688,65 @@ function parseSessionPageSizeQuery(raw) {
|
|
|
14048
14688
|
}
|
|
14049
14689
|
__name(parseSessionPageSizeQuery, "parseSessionPageSizeQuery");
|
|
14050
14690
|
|
|
14691
|
+
// packages/cli/src/serve/server/session-export.ts
|
|
14692
|
+
init_esbuild_shims();
|
|
14693
|
+
var SESSION_EXPORT_FORMATS = ["html", "md", "json", "jsonl"];
|
|
14694
|
+
var EXPORT_FORMATS = {
|
|
14695
|
+
html: {
|
|
14696
|
+
mimeType: "text/html; charset=utf-8",
|
|
14697
|
+
render: toHtml
|
|
14698
|
+
},
|
|
14699
|
+
md: {
|
|
14700
|
+
mimeType: "text/markdown; charset=utf-8",
|
|
14701
|
+
render: toMarkdown
|
|
14702
|
+
},
|
|
14703
|
+
json: {
|
|
14704
|
+
mimeType: "application/json; charset=utf-8",
|
|
14705
|
+
render: toJson
|
|
14706
|
+
},
|
|
14707
|
+
jsonl: {
|
|
14708
|
+
mimeType: "application/jsonl; charset=utf-8",
|
|
14709
|
+
render: toJsonl
|
|
14710
|
+
}
|
|
14711
|
+
};
|
|
14712
|
+
function parseSessionExportFormat(rawFormat) {
|
|
14713
|
+
if (rawFormat === void 0) return "html";
|
|
14714
|
+
if (typeof rawFormat !== "string") return void 0;
|
|
14715
|
+
return SESSION_EXPORT_FORMATS.includes(rawFormat) ? rawFormat : void 0;
|
|
14716
|
+
}
|
|
14717
|
+
__name(parseSessionExportFormat, "parseSessionExportFormat");
|
|
14718
|
+
function sessionExportFormatValues() {
|
|
14719
|
+
return [...SESSION_EXPORT_FORMATS];
|
|
14720
|
+
}
|
|
14721
|
+
__name(sessionExportFormatValues, "sessionExportFormatValues");
|
|
14722
|
+
async function exportSessionTranscript(params) {
|
|
14723
|
+
const { workspaceCwd, sessionId, format } = params;
|
|
14724
|
+
const sessionData = await new SessionService(workspaceCwd).loadSession(
|
|
14725
|
+
sessionId
|
|
14726
|
+
);
|
|
14727
|
+
if (!sessionData) {
|
|
14728
|
+
throw new SessionNotFoundError(sessionId);
|
|
14729
|
+
}
|
|
14730
|
+
const exportConfig = params.config ?? {};
|
|
14731
|
+
const collected = await collectSessionData(
|
|
14732
|
+
sessionData.conversation,
|
|
14733
|
+
exportConfig
|
|
14734
|
+
);
|
|
14735
|
+
const normalized = normalizeSessionData(
|
|
14736
|
+
collected,
|
|
14737
|
+
sessionData.conversation.messages,
|
|
14738
|
+
exportConfig
|
|
14739
|
+
);
|
|
14740
|
+
const formatDefinition = EXPORT_FORMATS[format];
|
|
14741
|
+
return {
|
|
14742
|
+
format,
|
|
14743
|
+
filename: generateExportFilename(format),
|
|
14744
|
+
mimeType: formatDefinition.mimeType,
|
|
14745
|
+
content: formatDefinition.render(normalized)
|
|
14746
|
+
};
|
|
14747
|
+
}
|
|
14748
|
+
__name(exportSessionTranscript, "exportSessionTranscript");
|
|
14749
|
+
|
|
14051
14750
|
// packages/cli/src/serve/routes/session.ts
|
|
14052
14751
|
function sendArtifactValidationError(res, err) {
|
|
14053
14752
|
if (!(err instanceof SessionArtifactValidationError)) {
|
|
@@ -14064,6 +14763,19 @@ function sendArtifactValidationError(res, err) {
|
|
|
14064
14763
|
return true;
|
|
14065
14764
|
}
|
|
14066
14765
|
__name(sendArtifactValidationError, "sendArtifactValidationError");
|
|
14766
|
+
function sendSessionOrganizationError(res, err) {
|
|
14767
|
+
if (!(err instanceof SessionOrganizationError)) {
|
|
14768
|
+
return false;
|
|
14769
|
+
}
|
|
14770
|
+
const status = err.code === "group_name_conflict" ? 409 : err.code === "group_not_found" ? 404 : err.code === "session_organization_store_unreadable" ? 500 : 400;
|
|
14771
|
+
res.status(status).json({
|
|
14772
|
+
error: err.message,
|
|
14773
|
+
code: err.code,
|
|
14774
|
+
...err.field !== void 0 ? { field: err.field } : {}
|
|
14775
|
+
});
|
|
14776
|
+
return true;
|
|
14777
|
+
}
|
|
14778
|
+
__name(sendSessionOrganizationError, "sendSessionOrganizationError");
|
|
14067
14779
|
function registerSessionRoutes(app, deps) {
|
|
14068
14780
|
const {
|
|
14069
14781
|
boundWorkspace,
|
|
@@ -14092,6 +14804,24 @@ function registerSessionRoutes(app, deps) {
|
|
|
14092
14804
|
sessionId: e.sessionId,
|
|
14093
14805
|
error: e.error instanceof Error ? e.error.message : String(e.error)
|
|
14094
14806
|
})), "serializeSessionErrors");
|
|
14807
|
+
const resolveWorkspaceParam = /* @__PURE__ */ __name((req, res) => {
|
|
14808
|
+
const workspaceCwd = req.params["id"] ?? "";
|
|
14809
|
+
if (!path10.isAbsolute(workspaceCwd)) {
|
|
14810
|
+
res.status(400).json({ error: "`:id` must decode to an absolute workspace path" });
|
|
14811
|
+
return null;
|
|
14812
|
+
}
|
|
14813
|
+
const key = canonicalizeWorkspace(workspaceCwd);
|
|
14814
|
+
if (key !== boundWorkspace) {
|
|
14815
|
+
res.status(400).json({
|
|
14816
|
+
error: `Workspace mismatch: daemon is bound to "${boundWorkspace}"`,
|
|
14817
|
+
code: "workspace_mismatch",
|
|
14818
|
+
boundWorkspace,
|
|
14819
|
+
requestedWorkspace: key
|
|
14820
|
+
});
|
|
14821
|
+
return null;
|
|
14822
|
+
}
|
|
14823
|
+
return key;
|
|
14824
|
+
}, "resolveWorkspaceParam");
|
|
14095
14825
|
const withMutableSession = /* @__PURE__ */ __name((route, handler) => async (req, res) => {
|
|
14096
14826
|
const sessionId = requireSessionId(req, res);
|
|
14097
14827
|
if (sessionId === null) return;
|
|
@@ -14175,6 +14905,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
14175
14905
|
return action === "load" ? await bridge.loadSession({
|
|
14176
14906
|
sessionId,
|
|
14177
14907
|
workspaceCwd: cwd,
|
|
14908
|
+
historyReplay: "response",
|
|
14178
14909
|
...clientId !== void 0 ? { clientId } : {}
|
|
14179
14910
|
}) : await bridge.resumeSession({
|
|
14180
14911
|
sessionId,
|
|
@@ -14305,6 +15036,42 @@ function registerSessionRoutes(app, deps) {
|
|
|
14305
15036
|
});
|
|
14306
15037
|
}
|
|
14307
15038
|
});
|
|
15039
|
+
app.get("/session/:id/export", async (req, res) => {
|
|
15040
|
+
const sessionId = requireSessionId(req, res);
|
|
15041
|
+
if (sessionId === null) return;
|
|
15042
|
+
const rawFormat = req.query["format"];
|
|
15043
|
+
const format = parseSessionExportFormat(rawFormat);
|
|
15044
|
+
if (!format) {
|
|
15045
|
+
res.status(400).json({
|
|
15046
|
+
error: "Invalid export format",
|
|
15047
|
+
code: "invalid_export_format",
|
|
15048
|
+
format: typeof rawFormat === "string" ? rawFormat : String(rawFormat),
|
|
15049
|
+
allowedFormats: sessionExportFormatValues()
|
|
15050
|
+
});
|
|
15051
|
+
return;
|
|
15052
|
+
}
|
|
15053
|
+
try {
|
|
15054
|
+
const result = await archiveCoordinator.runSharedMany(
|
|
15055
|
+
[sessionId],
|
|
15056
|
+
async () => {
|
|
15057
|
+
await assertSessionLoadable(boundWorkspace, sessionId);
|
|
15058
|
+
return exportSessionTranscript({
|
|
15059
|
+
workspaceCwd: boundWorkspace,
|
|
15060
|
+
sessionId,
|
|
15061
|
+
format,
|
|
15062
|
+
config: { getChannel: /* @__PURE__ */ __name(() => "daemon", "getChannel") }
|
|
15063
|
+
});
|
|
15064
|
+
}
|
|
15065
|
+
);
|
|
15066
|
+
const filename = result.filename.replace(/["\\\r\n]/g, "_");
|
|
15067
|
+
res.status(200).set("Cache-Control", "no-store").set("X-Content-Type-Options", "nosniff").set("Content-Type", result.mimeType).set("Content-Disposition", `attachment; filename="${filename}"`).send(result.content);
|
|
15068
|
+
} catch (err) {
|
|
15069
|
+
sendBridgeError2(res, err, {
|
|
15070
|
+
route: "GET /session/:id/export",
|
|
15071
|
+
sessionId
|
|
15072
|
+
});
|
|
15073
|
+
}
|
|
15074
|
+
});
|
|
14308
15075
|
app.get("/session/:id/context", async (req, res) => {
|
|
14309
15076
|
const sessionId = requireSessionId(req, res);
|
|
14310
15077
|
if (sessionId === null) return;
|
|
@@ -14827,6 +15594,145 @@ function registerSessionRoutes(app, deps) {
|
|
|
14827
15594
|
res.status(200).json({ sessionId, ...effective });
|
|
14828
15595
|
})
|
|
14829
15596
|
);
|
|
15597
|
+
app.patch("/session/:id/organization", mutate(), async (req, res) => {
|
|
15598
|
+
const sessionId = requireSessionId(req, res);
|
|
15599
|
+
if (sessionId === null) return;
|
|
15600
|
+
try {
|
|
15601
|
+
await archiveCoordinator.runSharedMany([sessionId], async () => {
|
|
15602
|
+
const sessionService = new SessionService(boundWorkspace);
|
|
15603
|
+
let exists = await sessionService.sessionExistsInAnyState(sessionId);
|
|
15604
|
+
if (!exists) {
|
|
15605
|
+
try {
|
|
15606
|
+
const summary = bridge.getSessionSummary(sessionId);
|
|
15607
|
+
exists = summary.workspaceCwd === boundWorkspace;
|
|
15608
|
+
} catch {
|
|
15609
|
+
exists = false;
|
|
15610
|
+
}
|
|
15611
|
+
}
|
|
15612
|
+
if (!exists) {
|
|
15613
|
+
res.status(404).json({
|
|
15614
|
+
error: `No session with id "${sessionId}"`,
|
|
15615
|
+
sessionId
|
|
15616
|
+
});
|
|
15617
|
+
return;
|
|
15618
|
+
}
|
|
15619
|
+
const body = safeBody(req);
|
|
15620
|
+
const rawIsPinned = body["isPinned"];
|
|
15621
|
+
if (rawIsPinned !== void 0 && typeof rawIsPinned !== "boolean") {
|
|
15622
|
+
res.status(400).json({
|
|
15623
|
+
error: "`isPinned` must be a boolean",
|
|
15624
|
+
code: "invalid_session_organization",
|
|
15625
|
+
field: "isPinned"
|
|
15626
|
+
});
|
|
15627
|
+
return;
|
|
15628
|
+
}
|
|
15629
|
+
const rawGroupId = body["groupId"];
|
|
15630
|
+
if (rawGroupId !== void 0 && rawGroupId !== null && typeof rawGroupId !== "string") {
|
|
15631
|
+
res.status(400).json({
|
|
15632
|
+
error: "`groupId` must be a string or null",
|
|
15633
|
+
code: "invalid_session_organization",
|
|
15634
|
+
field: "groupId"
|
|
15635
|
+
});
|
|
15636
|
+
return;
|
|
15637
|
+
}
|
|
15638
|
+
const rawColor = body["color"];
|
|
15639
|
+
if (rawColor !== void 0 && rawColor !== null && (typeof rawColor !== "string" || !GROUP_COLOR_OPTIONS.includes(rawColor))) {
|
|
15640
|
+
res.status(400).json({
|
|
15641
|
+
error: "`color` must be a supported color or null",
|
|
15642
|
+
code: "invalid_session_organization",
|
|
15643
|
+
field: "color"
|
|
15644
|
+
});
|
|
15645
|
+
return;
|
|
15646
|
+
}
|
|
15647
|
+
const organization = await createSessionOrganizationService(
|
|
15648
|
+
boundWorkspace
|
|
15649
|
+
).updateSessionOrganization(sessionId, {
|
|
15650
|
+
...rawIsPinned !== void 0 ? { isPinned: rawIsPinned } : {},
|
|
15651
|
+
...rawGroupId !== void 0 ? { groupId: rawGroupId } : {},
|
|
15652
|
+
...rawColor !== void 0 ? { color: rawColor } : {}
|
|
15653
|
+
});
|
|
15654
|
+
res.status(200).json({ sessionId, ...organization });
|
|
15655
|
+
});
|
|
15656
|
+
} catch (err) {
|
|
15657
|
+
if (sendSessionOrganizationError(res, err)) return;
|
|
15658
|
+
sendBridgeError2(res, err, {
|
|
15659
|
+
route: "PATCH /session/:id/organization",
|
|
15660
|
+
sessionId
|
|
15661
|
+
});
|
|
15662
|
+
}
|
|
15663
|
+
});
|
|
15664
|
+
app.get("/workspace/:id/session-groups", async (req, res) => {
|
|
15665
|
+
const key = resolveWorkspaceParam(req, res);
|
|
15666
|
+
if (key === null) return;
|
|
15667
|
+
try {
|
|
15668
|
+
res.status(200).json(await createSessionOrganizationService(key).listGroups());
|
|
15669
|
+
} catch (err) {
|
|
15670
|
+
sendBridgeError2(res, err, {
|
|
15671
|
+
route: "GET /workspace/:id/session-groups"
|
|
15672
|
+
});
|
|
15673
|
+
}
|
|
15674
|
+
});
|
|
15675
|
+
app.post("/workspace/:id/session-groups", mutate(), async (req, res) => {
|
|
15676
|
+
const key = resolveWorkspaceParam(req, res);
|
|
15677
|
+
if (key === null) return;
|
|
15678
|
+
const body = safeBody(req);
|
|
15679
|
+
try {
|
|
15680
|
+
const group = await createSessionOrganizationService(key).createGroup({
|
|
15681
|
+
name: body["name"],
|
|
15682
|
+
color: body["color"]
|
|
15683
|
+
});
|
|
15684
|
+
res.status(201).json({ group });
|
|
15685
|
+
} catch (err) {
|
|
15686
|
+
if (sendSessionOrganizationError(res, err)) return;
|
|
15687
|
+
sendBridgeError2(res, err, {
|
|
15688
|
+
route: "POST /workspace/:id/session-groups"
|
|
15689
|
+
});
|
|
15690
|
+
}
|
|
15691
|
+
});
|
|
15692
|
+
app.patch(
|
|
15693
|
+
"/workspace/:id/session-groups/:groupId",
|
|
15694
|
+
mutate(),
|
|
15695
|
+
async (req, res) => {
|
|
15696
|
+
const key = resolveWorkspaceParam(req, res);
|
|
15697
|
+
if (key === null) return;
|
|
15698
|
+
const body = safeBody(req);
|
|
15699
|
+
try {
|
|
15700
|
+
const group = await createSessionOrganizationService(key).updateGroup(
|
|
15701
|
+
req.params["groupId"] ?? "",
|
|
15702
|
+
{
|
|
15703
|
+
...Object.prototype.hasOwnProperty.call(body, "name") ? { name: body["name"] } : {},
|
|
15704
|
+
...Object.prototype.hasOwnProperty.call(body, "color") ? { color: body["color"] } : {},
|
|
15705
|
+
...Object.prototype.hasOwnProperty.call(body, "order") ? { order: body["order"] } : {}
|
|
15706
|
+
}
|
|
15707
|
+
);
|
|
15708
|
+
res.status(200).json({ group });
|
|
15709
|
+
} catch (err) {
|
|
15710
|
+
if (sendSessionOrganizationError(res, err)) return;
|
|
15711
|
+
sendBridgeError2(res, err, {
|
|
15712
|
+
route: "PATCH /workspace/:id/session-groups/:groupId"
|
|
15713
|
+
});
|
|
15714
|
+
}
|
|
15715
|
+
}
|
|
15716
|
+
);
|
|
15717
|
+
app.delete(
|
|
15718
|
+
"/workspace/:id/session-groups/:groupId",
|
|
15719
|
+
mutate(),
|
|
15720
|
+
async (req, res) => {
|
|
15721
|
+
const key = resolveWorkspaceParam(req, res);
|
|
15722
|
+
if (key === null) return;
|
|
15723
|
+
try {
|
|
15724
|
+
const deleted = await createSessionOrganizationService(key).deleteGroup(
|
|
15725
|
+
req.params["groupId"] ?? ""
|
|
15726
|
+
);
|
|
15727
|
+
res.status(200).json({ deleted });
|
|
15728
|
+
} catch (err) {
|
|
15729
|
+
if (sendSessionOrganizationError(res, err)) return;
|
|
15730
|
+
sendBridgeError2(res, err, {
|
|
15731
|
+
route: "DELETE /workspace/:id/session-groups/:groupId"
|
|
15732
|
+
});
|
|
15733
|
+
}
|
|
15734
|
+
}
|
|
15735
|
+
);
|
|
14830
15736
|
app.get("/workspace/:id/sessions", async (req, res) => {
|
|
14831
15737
|
const workspaceCwd = req.params["id"] ?? "";
|
|
14832
15738
|
if (!path10.isAbsolute(workspaceCwd)) {
|
|
@@ -14846,6 +15752,26 @@ function registerSessionRoutes(app, deps) {
|
|
|
14846
15752
|
try {
|
|
14847
15753
|
const cursor = typeof req.query["cursor"] === "string" ? req.query["cursor"] : void 0;
|
|
14848
15754
|
const size = parseSessionPageSizeQuery(req.query["size"]);
|
|
15755
|
+
const rawView = req.query["view"];
|
|
15756
|
+
let view;
|
|
15757
|
+
if (rawView !== void 0) {
|
|
15758
|
+
if (rawView !== "organized") {
|
|
15759
|
+
res.status(400).json({
|
|
15760
|
+
error: '`view` must be "organized"',
|
|
15761
|
+
code: "invalid_session_view"
|
|
15762
|
+
});
|
|
15763
|
+
return;
|
|
15764
|
+
}
|
|
15765
|
+
view = "organized";
|
|
15766
|
+
}
|
|
15767
|
+
const group = typeof req.query["group"] === "string" ? req.query["group"] : void 0;
|
|
15768
|
+
if (group !== void 0 && view !== "organized") {
|
|
15769
|
+
res.status(400).json({
|
|
15770
|
+
error: "`group` requires `view=organized`",
|
|
15771
|
+
code: "invalid_session_group_filter"
|
|
15772
|
+
});
|
|
15773
|
+
return;
|
|
15774
|
+
}
|
|
14849
15775
|
const rawArchiveState = req.query["archiveState"];
|
|
14850
15776
|
let archiveState;
|
|
14851
15777
|
if (rawArchiveState !== void 0) {
|
|
@@ -14859,13 +15785,17 @@ function registerSessionRoutes(app, deps) {
|
|
|
14859
15785
|
archiveState = rawArchiveState;
|
|
14860
15786
|
}
|
|
14861
15787
|
const result = await listWorkspaceSessionsForResponse(bridge, key, {
|
|
14862
|
-
cursor,
|
|
14863
|
-
size,
|
|
14864
|
-
archiveState
|
|
15788
|
+
...cursor !== void 0 ? { cursor } : {},
|
|
15789
|
+
...size !== void 0 ? { size } : {},
|
|
15790
|
+
...archiveState !== void 0 ? { archiveState } : {},
|
|
15791
|
+
...view !== void 0 ? { view } : {},
|
|
15792
|
+
...group !== void 0 ? { group } : {}
|
|
14865
15793
|
});
|
|
14866
15794
|
res.status(200).json({
|
|
14867
15795
|
sessions: result.sessions,
|
|
14868
|
-
...result.nextCursor != null ? { nextCursor: result.nextCursor } : {}
|
|
15796
|
+
...result.nextCursor != null ? { nextCursor: result.nextCursor } : {},
|
|
15797
|
+
...result.liveMergeFailed ? { liveMergeFailed: true } : {},
|
|
15798
|
+
...result.truncated ? { truncated: true } : {}
|
|
14869
15799
|
});
|
|
14870
15800
|
} catch (err) {
|
|
14871
15801
|
if (err instanceof InvalidCursorError) {
|
|
@@ -14875,6 +15805,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
14875
15805
|
});
|
|
14876
15806
|
return;
|
|
14877
15807
|
}
|
|
15808
|
+
if (sendSessionOrganizationError(res, err)) return;
|
|
14878
15809
|
writeStderrLine(
|
|
14879
15810
|
`qwen serve: failed to list sessions for workspace ${safeLogValue(
|
|
14880
15811
|
key
|
|
@@ -15185,58 +16116,918 @@ function registerSessionRoutes(app, deps) {
|
|
|
15185
16116
|
});
|
|
15186
16117
|
return;
|
|
15187
16118
|
}
|
|
15188
|
-
const clientId = parseClientIdHeader(req, res);
|
|
15189
|
-
if (clientId === null) return;
|
|
15190
|
-
const response = await bridge.setSessionApprovalMode(
|
|
15191
|
-
sessionId,
|
|
15192
|
-
mode,
|
|
15193
|
-
{ persist: persist === true },
|
|
15194
|
-
clientId !== void 0 ? { clientId } : void 0
|
|
16119
|
+
const clientId = parseClientIdHeader(req, res);
|
|
16120
|
+
if (clientId === null) return;
|
|
16121
|
+
const response = await bridge.setSessionApprovalMode(
|
|
16122
|
+
sessionId,
|
|
16123
|
+
mode,
|
|
16124
|
+
{ persist: persist === true },
|
|
16125
|
+
clientId !== void 0 ? { clientId } : void 0
|
|
16126
|
+
);
|
|
16127
|
+
res.status(200).json(response);
|
|
16128
|
+
}
|
|
16129
|
+
)
|
|
16130
|
+
);
|
|
16131
|
+
app.post(
|
|
16132
|
+
"/session/:id/language",
|
|
16133
|
+
mutate(),
|
|
16134
|
+
withMutableSession(
|
|
16135
|
+
"POST /session/:id/language",
|
|
16136
|
+
async (req, res, sessionId) => {
|
|
16137
|
+
const body = safeBody(req);
|
|
16138
|
+
const language = body["language"];
|
|
16139
|
+
const syncOutputLanguage = body["syncOutputLanguage"];
|
|
16140
|
+
if (typeof language !== "string" || !LANGUAGE_CODES.includes(language)) {
|
|
16141
|
+
res.status(400).json({
|
|
16142
|
+
error: "`language` is required and must be one of: " + LANGUAGE_CODES.join(", "),
|
|
16143
|
+
code: "invalid_language",
|
|
16144
|
+
allowed: LANGUAGE_CODES
|
|
16145
|
+
});
|
|
16146
|
+
return;
|
|
16147
|
+
}
|
|
16148
|
+
if (syncOutputLanguage !== void 0 && typeof syncOutputLanguage !== "boolean") {
|
|
16149
|
+
res.status(400).json({
|
|
16150
|
+
error: "`syncOutputLanguage` must be a boolean when provided",
|
|
16151
|
+
code: "invalid_sync_flag"
|
|
16152
|
+
});
|
|
16153
|
+
return;
|
|
16154
|
+
}
|
|
16155
|
+
const clientId = parseClientIdHeader(req, res);
|
|
16156
|
+
if (clientId === null) return;
|
|
16157
|
+
const response = await bridge.setSessionLanguage(
|
|
16158
|
+
sessionId,
|
|
16159
|
+
{
|
|
16160
|
+
language,
|
|
16161
|
+
syncOutputLanguage: syncOutputLanguage === true
|
|
16162
|
+
},
|
|
16163
|
+
clientId !== void 0 ? { clientId } : void 0
|
|
16164
|
+
);
|
|
16165
|
+
res.status(200).json(response);
|
|
16166
|
+
}
|
|
16167
|
+
)
|
|
16168
|
+
);
|
|
16169
|
+
}
|
|
16170
|
+
__name(registerSessionRoutes, "registerSessionRoutes");
|
|
16171
|
+
|
|
16172
|
+
// packages/cli/src/serve/routes/scheduled-tasks.ts
|
|
16173
|
+
init_esbuild_shims();
|
|
16174
|
+
var MAX_SCHEDULED_TASKS = MAX_JOBS;
|
|
16175
|
+
var MAX_PROMPT_LENGTH = 1e5;
|
|
16176
|
+
var MAX_NAME_LENGTH2 = 200;
|
|
16177
|
+
var MAX_CRON_LENGTH = 200;
|
|
16178
|
+
var MAX_SESSION_NAME_LENGTH = 60;
|
|
16179
|
+
function scheduledTaskSessionName(label) {
|
|
16180
|
+
const cleaned = stripTerminalControlSequences(label).replace(/[\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/g, "").trim().replace(/\s+/g, " ");
|
|
16181
|
+
let short = cleaned;
|
|
16182
|
+
if (cleaned.length > MAX_SESSION_NAME_LENGTH) {
|
|
16183
|
+
let cut = MAX_SESSION_NAME_LENGTH - 1;
|
|
16184
|
+
const boundary = cleaned.charCodeAt(cut - 1);
|
|
16185
|
+
if (boundary >= 55296 && boundary <= 56319) cut -= 1;
|
|
16186
|
+
short = `${cleaned.slice(0, cut)}\u2026`;
|
|
16187
|
+
}
|
|
16188
|
+
return `\u23F0 ${short}`;
|
|
16189
|
+
}
|
|
16190
|
+
__name(scheduledTaskSessionName, "scheduledTaskSessionName");
|
|
16191
|
+
function computeNextRunAt(task) {
|
|
16192
|
+
if (task.enabled === false) return null;
|
|
16193
|
+
return nextDurableFireMs(task);
|
|
16194
|
+
}
|
|
16195
|
+
__name(computeNextRunAt, "computeNextRunAt");
|
|
16196
|
+
function toView(task) {
|
|
16197
|
+
return {
|
|
16198
|
+
id: task.id,
|
|
16199
|
+
name: typeof task.name === "string" && task.name.length > 0 ? task.name : null,
|
|
16200
|
+
cron: task.cron,
|
|
16201
|
+
prompt: task.prompt,
|
|
16202
|
+
recurring: task.recurring,
|
|
16203
|
+
// Absent enabled defaults to enabled — tool-created tasks never write it.
|
|
16204
|
+
enabled: task.enabled !== false,
|
|
16205
|
+
createdAt: task.createdAt,
|
|
16206
|
+
lastFiredAt: task.lastFiredAt,
|
|
16207
|
+
nextRunAt: computeNextRunAt(task),
|
|
16208
|
+
// The task's bound session (its run-history transcript), or null for an
|
|
16209
|
+
// unbound tool-created/legacy task.
|
|
16210
|
+
sessionId: typeof task.sessionId === "string" && task.sessionId.length > 0 ? task.sessionId : null,
|
|
16211
|
+
// Absent runs (tool-created / never-fired) normalizes to [] so the client
|
|
16212
|
+
// never special-cases undefined.
|
|
16213
|
+
runs: Array.isArray(task.runs) ? task.runs : []
|
|
16214
|
+
};
|
|
16215
|
+
}
|
|
16216
|
+
__name(toView, "toView");
|
|
16217
|
+
function validateCron(cron) {
|
|
16218
|
+
try {
|
|
16219
|
+
parseCron(cron);
|
|
16220
|
+
nextFireTime(cron, /* @__PURE__ */ new Date());
|
|
16221
|
+
return null;
|
|
16222
|
+
} catch (err) {
|
|
16223
|
+
return err instanceof Error ? err.message : String(err);
|
|
16224
|
+
}
|
|
16225
|
+
}
|
|
16226
|
+
__name(validateCron, "validateCron");
|
|
16227
|
+
function canonicalCron(cron) {
|
|
16228
|
+
try {
|
|
16229
|
+
const f = parseCron(cron);
|
|
16230
|
+
const s = /* @__PURE__ */ __name((set) => [...set].sort((a, b) => a - b).join(","), "s");
|
|
16231
|
+
return [
|
|
16232
|
+
s(f.minute),
|
|
16233
|
+
s(f.hour),
|
|
16234
|
+
s(f.dayOfMonth),
|
|
16235
|
+
s(f.month),
|
|
16236
|
+
s(f.dayOfWeek),
|
|
16237
|
+
f.domIsWild ? "W" : "",
|
|
16238
|
+
f.dowIsWild ? "W" : ""
|
|
16239
|
+
].join("|");
|
|
16240
|
+
} catch {
|
|
16241
|
+
return null;
|
|
16242
|
+
}
|
|
16243
|
+
}
|
|
16244
|
+
__name(canonicalCron, "canonicalCron");
|
|
16245
|
+
function registerScheduledTasksRoutes(app, deps) {
|
|
16246
|
+
const { boundWorkspace, mutate, safeBody: safeBody2, bridge } = deps;
|
|
16247
|
+
app.get("/scheduled-tasks", async (_req, res) => {
|
|
16248
|
+
try {
|
|
16249
|
+
const tasks = await readCronTasks(boundWorkspace);
|
|
16250
|
+
res.status(200).json({ v: 1, tasks: tasks.map(toView) });
|
|
16251
|
+
} catch (err) {
|
|
16252
|
+
writeStderrLine(
|
|
16253
|
+
`qwen serve: GET /scheduled-tasks failed: ${err instanceof Error ? err.message : String(err)}`
|
|
16254
|
+
);
|
|
16255
|
+
res.status(500).json({
|
|
16256
|
+
error: "Failed to read scheduled tasks (the tasks file may be corrupt)",
|
|
16257
|
+
code: "scheduled_tasks_read_failed"
|
|
16258
|
+
});
|
|
16259
|
+
}
|
|
16260
|
+
});
|
|
16261
|
+
app.post("/scheduled-tasks", mutate(), async (req, res) => {
|
|
16262
|
+
const body = safeBody2(req);
|
|
16263
|
+
const cron = typeof body["cron"] === "string" ? body["cron"].trim() : "";
|
|
16264
|
+
if (cron.length === 0) {
|
|
16265
|
+
res.status(400).json({
|
|
16266
|
+
error: "`cron` is required and must be a non-empty string",
|
|
16267
|
+
code: "invalid_cron"
|
|
16268
|
+
});
|
|
16269
|
+
return;
|
|
16270
|
+
}
|
|
16271
|
+
if (cron.length > MAX_CRON_LENGTH) {
|
|
16272
|
+
res.status(400).json({
|
|
16273
|
+
error: `\`cron\` exceeds ${MAX_CRON_LENGTH}-character limit`,
|
|
16274
|
+
code: "invalid_cron"
|
|
16275
|
+
});
|
|
16276
|
+
return;
|
|
16277
|
+
}
|
|
16278
|
+
const cronError = validateCron(cron);
|
|
16279
|
+
if (cronError) {
|
|
16280
|
+
res.status(400).json({ error: cronError, code: "invalid_cron" });
|
|
16281
|
+
return;
|
|
16282
|
+
}
|
|
16283
|
+
const prompt = typeof body["prompt"] === "string" ? body["prompt"].trim() : "";
|
|
16284
|
+
if (prompt.length === 0) {
|
|
16285
|
+
res.status(400).json({
|
|
16286
|
+
error: "`prompt` is required and must be a non-empty string",
|
|
16287
|
+
code: "invalid_prompt"
|
|
16288
|
+
});
|
|
16289
|
+
return;
|
|
16290
|
+
}
|
|
16291
|
+
if (prompt.length > MAX_PROMPT_LENGTH) {
|
|
16292
|
+
res.status(400).json({
|
|
16293
|
+
error: `\`prompt\` exceeds ${MAX_PROMPT_LENGTH}-character limit`,
|
|
16294
|
+
code: "invalid_prompt"
|
|
16295
|
+
});
|
|
16296
|
+
return;
|
|
16297
|
+
}
|
|
16298
|
+
const nameResult = parseNameField(body["name"]);
|
|
16299
|
+
if (nameResult.error) {
|
|
16300
|
+
res.status(400).json({ error: nameResult.error, code: "invalid_name" });
|
|
16301
|
+
return;
|
|
16302
|
+
}
|
|
16303
|
+
if (body["recurring"] !== void 0 && typeof body["recurring"] !== "boolean") {
|
|
16304
|
+
res.status(400).json({
|
|
16305
|
+
error: "`recurring` must be a boolean",
|
|
16306
|
+
code: "invalid_recurring"
|
|
16307
|
+
});
|
|
16308
|
+
return;
|
|
16309
|
+
}
|
|
16310
|
+
if (body["enabled"] !== void 0 && typeof body["enabled"] !== "boolean") {
|
|
16311
|
+
res.status(400).json({
|
|
16312
|
+
error: "`enabled` must be a boolean",
|
|
16313
|
+
code: "invalid_enabled"
|
|
16314
|
+
});
|
|
16315
|
+
return;
|
|
16316
|
+
}
|
|
16317
|
+
const recurring = body["recurring"] !== false;
|
|
16318
|
+
const enabled = body["enabled"] !== false;
|
|
16319
|
+
let boundSessionId;
|
|
16320
|
+
if (bridge) {
|
|
16321
|
+
try {
|
|
16322
|
+
if ((await readCronTasks(boundWorkspace)).length >= MAX_SCHEDULED_TASKS) {
|
|
16323
|
+
res.status(409).json({
|
|
16324
|
+
error: `Maximum number of scheduled tasks (${MAX_SCHEDULED_TASKS}) reached`,
|
|
16325
|
+
code: "max_tasks_reached"
|
|
16326
|
+
});
|
|
16327
|
+
return;
|
|
16328
|
+
}
|
|
16329
|
+
} catch {
|
|
16330
|
+
}
|
|
16331
|
+
try {
|
|
16332
|
+
const session = await bridge.spawnOrAttach({
|
|
16333
|
+
workspaceCwd: boundWorkspace,
|
|
16334
|
+
sessionScope: "thread"
|
|
16335
|
+
});
|
|
16336
|
+
boundSessionId = session.sessionId;
|
|
16337
|
+
try {
|
|
16338
|
+
bridge.updateSessionMetadata(boundSessionId, {
|
|
16339
|
+
displayName: scheduledTaskSessionName(nameResult.value ?? prompt)
|
|
16340
|
+
});
|
|
16341
|
+
} catch {
|
|
16342
|
+
}
|
|
16343
|
+
} catch (err) {
|
|
16344
|
+
writeStderrLine(
|
|
16345
|
+
`qwen serve: POST /scheduled-tasks failed to create the task's session: ${err instanceof Error ? err.message : String(err)}`
|
|
16346
|
+
);
|
|
16347
|
+
res.status(500).json({
|
|
16348
|
+
error: "Failed to create the task's session",
|
|
16349
|
+
code: "scheduled_tasks_session_failed"
|
|
16350
|
+
});
|
|
16351
|
+
return;
|
|
16352
|
+
}
|
|
16353
|
+
}
|
|
16354
|
+
const now = Date.now();
|
|
16355
|
+
const task = {
|
|
16356
|
+
id: generateCronTaskId(),
|
|
16357
|
+
cron,
|
|
16358
|
+
prompt,
|
|
16359
|
+
recurring,
|
|
16360
|
+
createdAt: now,
|
|
16361
|
+
// Pin to the creation minute so the scheduler can't fire during the
|
|
16362
|
+
// minute the task was created — same guard cronScheduler.create uses.
|
|
16363
|
+
lastFiredAt: now - now % 6e4,
|
|
16364
|
+
enabled,
|
|
16365
|
+
...boundSessionId !== void 0 ? { sessionId: boundSessionId } : {},
|
|
16366
|
+
...nameResult.value !== void 0 ? { name: nameResult.value } : {}
|
|
16367
|
+
};
|
|
16368
|
+
const rollbackSession = /* @__PURE__ */ __name(async () => {
|
|
16369
|
+
if (boundSessionId !== void 0 && bridge) {
|
|
16370
|
+
await bridge.closeSession(boundSessionId).catch(() => {
|
|
16371
|
+
});
|
|
16372
|
+
await new SessionService(boundWorkspace).removeSession(boundSessionId).catch(() => {
|
|
16373
|
+
});
|
|
16374
|
+
}
|
|
16375
|
+
}, "rollbackSession");
|
|
16376
|
+
let overCap = false;
|
|
16377
|
+
try {
|
|
16378
|
+
await updateCronTasks(boundWorkspace, (tasks) => {
|
|
16379
|
+
if (tasks.length >= MAX_SCHEDULED_TASKS) {
|
|
16380
|
+
overCap = true;
|
|
16381
|
+
return tasks;
|
|
16382
|
+
}
|
|
16383
|
+
return [...tasks, task];
|
|
16384
|
+
});
|
|
16385
|
+
} catch (err) {
|
|
16386
|
+
await rollbackSession();
|
|
16387
|
+
writeStderrLine(
|
|
16388
|
+
`qwen serve: POST /scheduled-tasks failed: ${err instanceof Error ? err.message : String(err)}`
|
|
16389
|
+
);
|
|
16390
|
+
res.status(500).json({
|
|
16391
|
+
error: "Failed to create scheduled task",
|
|
16392
|
+
code: "scheduled_tasks_write_failed"
|
|
16393
|
+
});
|
|
16394
|
+
return;
|
|
16395
|
+
}
|
|
16396
|
+
if (overCap) {
|
|
16397
|
+
await rollbackSession();
|
|
16398
|
+
res.status(409).json({
|
|
16399
|
+
error: `Maximum number of scheduled tasks (${MAX_SCHEDULED_TASKS}) reached`,
|
|
16400
|
+
code: "max_tasks_reached"
|
|
16401
|
+
});
|
|
16402
|
+
return;
|
|
16403
|
+
}
|
|
16404
|
+
res.status(201).json(toView(task));
|
|
16405
|
+
});
|
|
16406
|
+
app.patch("/scheduled-tasks/:id", mutate(), async (req, res) => {
|
|
16407
|
+
const id = typeof req.params["id"] === "string" ? req.params["id"] : "";
|
|
16408
|
+
if (id.length === 0) {
|
|
16409
|
+
res.status(400).json({ error: "Task id is required", code: "invalid_id" });
|
|
16410
|
+
return;
|
|
16411
|
+
}
|
|
16412
|
+
const body = safeBody2(req);
|
|
16413
|
+
const patch = {};
|
|
16414
|
+
let clearName = false;
|
|
16415
|
+
if ("cron" in body) {
|
|
16416
|
+
const cron = typeof body["cron"] === "string" ? body["cron"].trim() : "";
|
|
16417
|
+
if (cron.length === 0 || cron.length > MAX_CRON_LENGTH) {
|
|
16418
|
+
res.status(400).json({
|
|
16419
|
+
error: "`cron` must be a non-empty string within the length limit",
|
|
16420
|
+
code: "invalid_cron"
|
|
16421
|
+
});
|
|
16422
|
+
return;
|
|
16423
|
+
}
|
|
16424
|
+
const cronError = validateCron(cron);
|
|
16425
|
+
if (cronError) {
|
|
16426
|
+
res.status(400).json({ error: cronError, code: "invalid_cron" });
|
|
16427
|
+
return;
|
|
16428
|
+
}
|
|
16429
|
+
patch.cron = cron;
|
|
16430
|
+
}
|
|
16431
|
+
if ("prompt" in body) {
|
|
16432
|
+
const prompt = typeof body["prompt"] === "string" ? body["prompt"].trim() : "";
|
|
16433
|
+
if (prompt.length === 0 || prompt.length > MAX_PROMPT_LENGTH) {
|
|
16434
|
+
res.status(400).json({
|
|
16435
|
+
error: "`prompt` must be a non-empty string within the length limit",
|
|
16436
|
+
code: "invalid_prompt"
|
|
16437
|
+
});
|
|
16438
|
+
return;
|
|
16439
|
+
}
|
|
16440
|
+
patch.prompt = prompt;
|
|
16441
|
+
}
|
|
16442
|
+
if ("name" in body) {
|
|
16443
|
+
const nameResult = parseNameField(body["name"]);
|
|
16444
|
+
if (nameResult.error) {
|
|
16445
|
+
res.status(400).json({ error: nameResult.error, code: "invalid_name" });
|
|
16446
|
+
return;
|
|
16447
|
+
}
|
|
16448
|
+
if (nameResult.value === void 0) {
|
|
16449
|
+
clearName = true;
|
|
16450
|
+
} else {
|
|
16451
|
+
patch.name = nameResult.value;
|
|
16452
|
+
}
|
|
16453
|
+
}
|
|
16454
|
+
if ("recurring" in body) {
|
|
16455
|
+
if (typeof body["recurring"] !== "boolean") {
|
|
16456
|
+
res.status(400).json({
|
|
16457
|
+
error: "`recurring` must be a boolean",
|
|
16458
|
+
code: "invalid_recurring"
|
|
16459
|
+
});
|
|
16460
|
+
return;
|
|
16461
|
+
}
|
|
16462
|
+
patch.recurring = body["recurring"];
|
|
16463
|
+
}
|
|
16464
|
+
if ("enabled" in body) {
|
|
16465
|
+
if (typeof body["enabled"] !== "boolean") {
|
|
16466
|
+
res.status(400).json({
|
|
16467
|
+
error: "`enabled` must be a boolean",
|
|
16468
|
+
code: "invalid_enabled"
|
|
16469
|
+
});
|
|
16470
|
+
return;
|
|
16471
|
+
}
|
|
16472
|
+
patch.enabled = body["enabled"];
|
|
16473
|
+
}
|
|
16474
|
+
if (Object.keys(patch).length === 0 && !clearName) {
|
|
16475
|
+
res.status(400).json({
|
|
16476
|
+
error: "No updatable fields provided",
|
|
16477
|
+
code: "empty_patch"
|
|
16478
|
+
});
|
|
16479
|
+
return;
|
|
16480
|
+
}
|
|
16481
|
+
let found = false;
|
|
16482
|
+
let updated;
|
|
16483
|
+
let blockedByArchive = false;
|
|
16484
|
+
try {
|
|
16485
|
+
await updateCronTasks(boundWorkspace, (tasks) => {
|
|
16486
|
+
const idx = tasks.findIndex((t) => t.id === id);
|
|
16487
|
+
if (idx === -1) return tasks;
|
|
16488
|
+
found = true;
|
|
16489
|
+
const current = tasks[idx];
|
|
16490
|
+
if (patch.enabled === true && current.disabledByArchive === true) {
|
|
16491
|
+
blockedByArchive = true;
|
|
16492
|
+
return tasks;
|
|
16493
|
+
}
|
|
16494
|
+
const next = { ...current, ...patch };
|
|
16495
|
+
if (clearName) delete next.name;
|
|
16496
|
+
const justReEnabled = current.enabled === false && patch.enabled === true;
|
|
16497
|
+
const cronChanged = patch.cron !== void 0 && canonicalCron(patch.cron) !== canonicalCron(current.cron);
|
|
16498
|
+
const becameRecurring = patch.recurring === true && current.recurring !== true;
|
|
16499
|
+
const becameOneShot = patch.recurring === false && current.recurring !== false;
|
|
16500
|
+
{
|
|
16501
|
+
const now = Date.now();
|
|
16502
|
+
const minute = now - now % 6e4;
|
|
16503
|
+
if (next.recurring && (justReEnabled || cronChanged || becameRecurring)) {
|
|
16504
|
+
next.lastFiredAt = minute;
|
|
16505
|
+
} else if (!next.recurring && (justReEnabled || cronChanged || becameOneShot)) {
|
|
16506
|
+
next.createdAt = now;
|
|
16507
|
+
next.lastFiredAt = minute;
|
|
16508
|
+
}
|
|
16509
|
+
}
|
|
16510
|
+
updated = next;
|
|
16511
|
+
return tasks.map((t, i) => i === idx ? next : t);
|
|
16512
|
+
});
|
|
16513
|
+
} catch (err) {
|
|
16514
|
+
writeStderrLine(
|
|
16515
|
+
`qwen serve: PATCH /scheduled-tasks/${id} failed: ${err instanceof Error ? err.message : String(err)}`
|
|
16516
|
+
);
|
|
16517
|
+
res.status(500).json({
|
|
16518
|
+
error: "Failed to update scheduled task",
|
|
16519
|
+
code: "scheduled_tasks_write_failed"
|
|
16520
|
+
});
|
|
16521
|
+
return;
|
|
16522
|
+
}
|
|
16523
|
+
if (blockedByArchive) {
|
|
16524
|
+
res.status(409).json({
|
|
16525
|
+
error: "This task was disabled by archiving its session; unarchive the session to re-enable it.",
|
|
16526
|
+
code: "task_session_archived"
|
|
16527
|
+
});
|
|
16528
|
+
return;
|
|
16529
|
+
}
|
|
16530
|
+
if (!found || !updated) {
|
|
16531
|
+
res.status(404).json({ error: "Task not found", code: "task_not_found" });
|
|
16532
|
+
return;
|
|
16533
|
+
}
|
|
16534
|
+
const effectiveLabelChanged = patch.name !== void 0 || clearName || patch.prompt !== void 0 && updated.name === void 0;
|
|
16535
|
+
if (bridge && updated.sessionId && effectiveLabelChanged) {
|
|
16536
|
+
try {
|
|
16537
|
+
bridge.updateSessionMetadata(updated.sessionId, {
|
|
16538
|
+
displayName: scheduledTaskSessionName(updated.name ?? updated.prompt)
|
|
16539
|
+
});
|
|
16540
|
+
} catch {
|
|
16541
|
+
}
|
|
16542
|
+
}
|
|
16543
|
+
res.status(200).json(toView(updated));
|
|
16544
|
+
});
|
|
16545
|
+
app.delete("/scheduled-tasks/:id", mutate(), async (req, res) => {
|
|
16546
|
+
const id = typeof req.params["id"] === "string" ? req.params["id"] : "";
|
|
16547
|
+
if (id.length === 0) {
|
|
16548
|
+
res.status(400).json({ error: "Task id is required", code: "invalid_id" });
|
|
16549
|
+
return;
|
|
16550
|
+
}
|
|
16551
|
+
let boundSessionId;
|
|
16552
|
+
let removed = false;
|
|
16553
|
+
try {
|
|
16554
|
+
await updateCronTasks(boundWorkspace, (tasks) => {
|
|
16555
|
+
const idx = tasks.findIndex((t) => t.id === id);
|
|
16556
|
+
if (idx === -1) return tasks;
|
|
16557
|
+
const match = tasks[idx].sessionId;
|
|
16558
|
+
if (typeof match === "string" && match.length > 0) {
|
|
16559
|
+
boundSessionId = match;
|
|
16560
|
+
}
|
|
16561
|
+
removed = true;
|
|
16562
|
+
return tasks.filter((_, i) => i !== idx);
|
|
16563
|
+
});
|
|
16564
|
+
} catch (err) {
|
|
16565
|
+
writeStderrLine(
|
|
16566
|
+
`qwen serve: DELETE /scheduled-tasks/${id} failed: ${err instanceof Error ? err.message : String(err)}`
|
|
16567
|
+
);
|
|
16568
|
+
res.status(500).json({
|
|
16569
|
+
error: "Failed to delete scheduled task",
|
|
16570
|
+
code: "scheduled_tasks_write_failed"
|
|
16571
|
+
});
|
|
16572
|
+
return;
|
|
16573
|
+
}
|
|
16574
|
+
if (!removed) {
|
|
16575
|
+
res.status(404).json({ error: "Task not found", code: "task_not_found" });
|
|
16576
|
+
return;
|
|
16577
|
+
}
|
|
16578
|
+
if (boundSessionId && bridge) {
|
|
16579
|
+
await bridge.closeSession(boundSessionId).catch(() => {
|
|
16580
|
+
});
|
|
16581
|
+
}
|
|
16582
|
+
res.status(200).json({ deleted: true, id });
|
|
16583
|
+
});
|
|
16584
|
+
app.post("/scheduled-tasks/:id/run", mutate(), async (req, res) => {
|
|
16585
|
+
const id = typeof req.params["id"] === "string" ? req.params["id"] : "";
|
|
16586
|
+
if (id.length === 0) {
|
|
16587
|
+
res.status(400).json({ error: "Task id is required", code: "invalid_id" });
|
|
16588
|
+
return;
|
|
16589
|
+
}
|
|
16590
|
+
const now = Date.now();
|
|
16591
|
+
let found = false;
|
|
16592
|
+
let blockedDisabled = false;
|
|
16593
|
+
let updated;
|
|
16594
|
+
try {
|
|
16595
|
+
await updateCronTasks(boundWorkspace, (tasks) => {
|
|
16596
|
+
const idx = tasks.findIndex((t) => t.id === id);
|
|
16597
|
+
if (idx === -1) return tasks;
|
|
16598
|
+
found = true;
|
|
16599
|
+
const current = tasks[idx];
|
|
16600
|
+
if (current.enabled === false) {
|
|
16601
|
+
blockedDisabled = true;
|
|
16602
|
+
return tasks;
|
|
16603
|
+
}
|
|
16604
|
+
const next = {
|
|
16605
|
+
...current,
|
|
16606
|
+
lastFiredAt: now,
|
|
16607
|
+
runs: appendCronRun(current.runs, {
|
|
16608
|
+
at: now,
|
|
16609
|
+
kind: "manual",
|
|
16610
|
+
...current.sessionId ? { sessionId: current.sessionId } : {}
|
|
16611
|
+
})
|
|
16612
|
+
};
|
|
16613
|
+
updated = next;
|
|
16614
|
+
if (!current.recurring) {
|
|
16615
|
+
return tasks.filter((_, i) => i !== idx);
|
|
16616
|
+
}
|
|
16617
|
+
return tasks.map((t, i) => i === idx ? next : t);
|
|
16618
|
+
});
|
|
16619
|
+
} catch (err) {
|
|
16620
|
+
writeStderrLine(
|
|
16621
|
+
`qwen serve: POST /scheduled-tasks/${id}/run failed: ${err instanceof Error ? err.message : String(err)}`
|
|
16622
|
+
);
|
|
16623
|
+
res.status(500).json({
|
|
16624
|
+
error: "Failed to record scheduled task run",
|
|
16625
|
+
code: "scheduled_tasks_write_failed"
|
|
16626
|
+
});
|
|
16627
|
+
return;
|
|
16628
|
+
}
|
|
16629
|
+
if (blockedDisabled) {
|
|
16630
|
+
res.status(409).json({
|
|
16631
|
+
error: "Cannot run a disabled task; enable it first (unarchive its session if it was archived).",
|
|
16632
|
+
code: "task_disabled"
|
|
16633
|
+
});
|
|
16634
|
+
return;
|
|
16635
|
+
}
|
|
16636
|
+
if (!found || !updated) {
|
|
16637
|
+
res.status(404).json({ error: "Task not found", code: "task_not_found" });
|
|
16638
|
+
return;
|
|
16639
|
+
}
|
|
16640
|
+
const view = toView(updated);
|
|
16641
|
+
if (!updated.recurring) view.nextRunAt = null;
|
|
16642
|
+
res.status(200).json(view);
|
|
16643
|
+
});
|
|
16644
|
+
}
|
|
16645
|
+
__name(registerScheduledTasksRoutes, "registerScheduledTasksRoutes");
|
|
16646
|
+
function parseNameField(raw) {
|
|
16647
|
+
if (raw === void 0 || raw === null) return { value: void 0 };
|
|
16648
|
+
if (typeof raw !== "string") {
|
|
16649
|
+
return { error: "`name` must be a string" };
|
|
16650
|
+
}
|
|
16651
|
+
const trimmed = raw.trim();
|
|
16652
|
+
if (trimmed.length === 0) return { value: void 0 };
|
|
16653
|
+
if (trimmed.length > MAX_NAME_LENGTH2) {
|
|
16654
|
+
return { error: `\`name\` exceeds ${MAX_NAME_LENGTH2}-character limit` };
|
|
16655
|
+
}
|
|
16656
|
+
return { value: trimmed };
|
|
16657
|
+
}
|
|
16658
|
+
__name(parseNameField, "parseNameField");
|
|
16659
|
+
|
|
16660
|
+
// packages/cli/src/serve/routes/usage-stats.ts
|
|
16661
|
+
init_esbuild_shims();
|
|
16662
|
+
var DEFAULT_HEATMAP_DAYS = 183;
|
|
16663
|
+
var MIN_HEATMAP_DAYS = 1;
|
|
16664
|
+
var MAX_HEATMAP_DAYS = 366;
|
|
16665
|
+
var DEFAULT_CACHE_TTL_MS = 6e4;
|
|
16666
|
+
var USAGE_RANGES = ["today", "week", "month"];
|
|
16667
|
+
function parseRange(raw) {
|
|
16668
|
+
return typeof raw === "string" && USAGE_RANGES.includes(raw) ? raw : "today";
|
|
16669
|
+
}
|
|
16670
|
+
__name(parseRange, "parseRange");
|
|
16671
|
+
function parseHeatmapDays(raw) {
|
|
16672
|
+
const value = typeof raw === "string" ? Number.parseInt(raw, 10) : Number.NaN;
|
|
16673
|
+
if (!Number.isFinite(value)) return DEFAULT_HEATMAP_DAYS;
|
|
16674
|
+
return Math.min(MAX_HEATMAP_DAYS, Math.max(MIN_HEATMAP_DAYS, value));
|
|
16675
|
+
}
|
|
16676
|
+
__name(parseHeatmapDays, "parseHeatmapDays");
|
|
16677
|
+
function registerUsageStatsRoutes(app, deps = {}) {
|
|
16678
|
+
const loadHistory = deps.loadHistory ?? (() => loadUsageHistory(void 0, { persistRebuild: false }));
|
|
16679
|
+
const ttlMs = deps.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
16680
|
+
let cache = null;
|
|
16681
|
+
const getRecords = /* @__PURE__ */ __name(() => {
|
|
16682
|
+
const now = Date.now();
|
|
16683
|
+
const fresh = cache !== null && (cache.settledAt === null || now - cache.settledAt < ttlMs);
|
|
16684
|
+
if (!fresh) {
|
|
16685
|
+
const entry = { promise: loadHistory(), settledAt: null };
|
|
16686
|
+
cache = entry;
|
|
16687
|
+
entry.promise.then(
|
|
16688
|
+
() => {
|
|
16689
|
+
if (cache === entry) entry.settledAt = Date.now();
|
|
16690
|
+
},
|
|
16691
|
+
() => {
|
|
16692
|
+
if (cache === entry) cache = null;
|
|
16693
|
+
}
|
|
16694
|
+
);
|
|
16695
|
+
}
|
|
16696
|
+
return cache.promise;
|
|
16697
|
+
}, "getRecords");
|
|
16698
|
+
app.get("/usage/dashboard", async (req, res) => {
|
|
16699
|
+
const range = parseRange(req.query["range"]);
|
|
16700
|
+
const heatmapDays = parseHeatmapDays(req.query["heatmapDays"]);
|
|
16701
|
+
try {
|
|
16702
|
+
const records = await getRecords();
|
|
16703
|
+
const dashboard = buildUsageDashboard(records, { range, heatmapDays });
|
|
16704
|
+
res.status(200).json(dashboard);
|
|
16705
|
+
} catch (err) {
|
|
16706
|
+
writeStderrLine(
|
|
16707
|
+
`qwen serve: GET /usage/dashboard failed: ${err instanceof Error ? err.message : String(err)}`
|
|
16708
|
+
);
|
|
16709
|
+
res.status(500).json({
|
|
16710
|
+
error: "Failed to load usage dashboard",
|
|
16711
|
+
code: "usage_dashboard_failed"
|
|
16712
|
+
});
|
|
16713
|
+
}
|
|
16714
|
+
});
|
|
16715
|
+
}
|
|
16716
|
+
__name(registerUsageStatsRoutes, "registerUsageStatsRoutes");
|
|
16717
|
+
|
|
16718
|
+
// packages/cli/src/serve/scheduled-task-keepalive.ts
|
|
16719
|
+
init_esbuild_shims();
|
|
16720
|
+
import * as fsSync from "node:fs";
|
|
16721
|
+
import * as path11 from "node:path";
|
|
16722
|
+
var log = createDebugLogger("SCHED_KEEPALIVE");
|
|
16723
|
+
function collectBoundSessionIds(tasks) {
|
|
16724
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16725
|
+
const ids = [];
|
|
16726
|
+
for (const task of tasks) {
|
|
16727
|
+
const sessionId = task.sessionId;
|
|
16728
|
+
if (task.enabled === false || // disabled (e.g. archived) — let it be reaped
|
|
16729
|
+
typeof sessionId !== "string" || sessionId.length === 0 || seen.has(sessionId)) {
|
|
16730
|
+
continue;
|
|
16731
|
+
}
|
|
16732
|
+
seen.add(sessionId);
|
|
16733
|
+
ids.push(sessionId);
|
|
16734
|
+
}
|
|
16735
|
+
return ids;
|
|
16736
|
+
}
|
|
16737
|
+
__name(collectBoundSessionIds, "collectBoundSessionIds");
|
|
16738
|
+
var KEEPALIVE_REVIVE_TIMEOUT_MS = 3e4;
|
|
16739
|
+
var KEEPALIVE_SPAWN_TIMEOUT_MS = 3e4;
|
|
16740
|
+
var MAX_REVIVE_BACKOFF_MS = 30 * 6e4;
|
|
16741
|
+
async function bindAndNameSessions(bridge, boundWorkspace, tasks, renamed, spawnTimeoutMs, binding) {
|
|
16742
|
+
const unbound = tasks.filter(
|
|
16743
|
+
(t) => !t.sessionId && t.enabled !== false && !binding.has(t.id)
|
|
16744
|
+
);
|
|
16745
|
+
const needsName = tasks.filter(
|
|
16746
|
+
(t) => t.sessionId && t.enabled !== false && !renamed.has(t.sessionId)
|
|
16747
|
+
);
|
|
16748
|
+
for (const task of unbound) {
|
|
16749
|
+
let spawnedSessionId;
|
|
16750
|
+
try {
|
|
16751
|
+
binding.add(task.id);
|
|
16752
|
+
const rawSpawn = bridge.spawnOrAttach({
|
|
16753
|
+
workspaceCwd: boundWorkspace,
|
|
16754
|
+
sessionScope: "thread"
|
|
16755
|
+
});
|
|
16756
|
+
let timedOut = false;
|
|
16757
|
+
rawSpawn.then(({ sessionId: sessionId2 }) => {
|
|
16758
|
+
if (timedOut) {
|
|
16759
|
+
log.debug(
|
|
16760
|
+
"keepalive: late spawn resolved, cleaning up",
|
|
16761
|
+
task.id,
|
|
16762
|
+
sessionId2
|
|
16763
|
+
);
|
|
16764
|
+
bridge.closeSession(sessionId2).catch(() => {
|
|
16765
|
+
});
|
|
16766
|
+
new SessionService(boundWorkspace).removeSession(sessionId2).catch(() => {
|
|
16767
|
+
});
|
|
16768
|
+
}
|
|
16769
|
+
}).catch(() => {
|
|
16770
|
+
}).finally(() => {
|
|
16771
|
+
binding.delete(task.id);
|
|
16772
|
+
});
|
|
16773
|
+
const { sessionId } = await withTimeout(
|
|
16774
|
+
rawSpawn,
|
|
16775
|
+
spawnTimeoutMs,
|
|
16776
|
+
`spawnOrAttach(${task.id})`
|
|
16777
|
+
).catch((err) => {
|
|
16778
|
+
timedOut = true;
|
|
16779
|
+
throw err;
|
|
16780
|
+
});
|
|
16781
|
+
spawnedSessionId = sessionId;
|
|
16782
|
+
try {
|
|
16783
|
+
bridge.updateSessionMetadata(sessionId, {
|
|
16784
|
+
displayName: scheduledTaskSessionName(task.prompt)
|
|
16785
|
+
});
|
|
16786
|
+
renamed.add(sessionId);
|
|
16787
|
+
} catch {
|
|
16788
|
+
}
|
|
16789
|
+
let matched = false;
|
|
16790
|
+
await updateCronTasks(boundWorkspace, (list) => {
|
|
16791
|
+
if (!list.some(
|
|
16792
|
+
(t) => t.id === task.id && !t.sessionId && t.enabled !== false
|
|
16793
|
+
)) {
|
|
16794
|
+
return list;
|
|
16795
|
+
}
|
|
16796
|
+
const result = list.map(
|
|
16797
|
+
(t) => t.id === task.id && !t.sessionId && t.enabled !== false ? { ...t, sessionId } : t
|
|
15195
16798
|
);
|
|
15196
|
-
|
|
16799
|
+
matched = true;
|
|
16800
|
+
return result;
|
|
16801
|
+
});
|
|
16802
|
+
if (!matched) {
|
|
16803
|
+
throw new Error(`task ${task.id} no longer on disk`);
|
|
15197
16804
|
}
|
|
15198
|
-
|
|
15199
|
-
|
|
15200
|
-
|
|
15201
|
-
|
|
15202
|
-
|
|
15203
|
-
|
|
15204
|
-
|
|
15205
|
-
|
|
15206
|
-
|
|
15207
|
-
|
|
15208
|
-
|
|
15209
|
-
|
|
15210
|
-
|
|
15211
|
-
|
|
15212
|
-
|
|
15213
|
-
|
|
15214
|
-
|
|
15215
|
-
|
|
16805
|
+
log.debug(
|
|
16806
|
+
"keepalive: bound task",
|
|
16807
|
+
task.id,
|
|
16808
|
+
"to dedicated session",
|
|
16809
|
+
sessionId
|
|
16810
|
+
);
|
|
16811
|
+
} catch (err) {
|
|
16812
|
+
log.debug("keepalive: failed to bind task", task.id, err);
|
|
16813
|
+
if (spawnedSessionId !== void 0) {
|
|
16814
|
+
await bridge.closeSession(spawnedSessionId).catch(() => {
|
|
16815
|
+
});
|
|
16816
|
+
await new SessionService(boundWorkspace).removeSession(spawnedSessionId).catch(() => {
|
|
16817
|
+
});
|
|
16818
|
+
}
|
|
16819
|
+
}
|
|
16820
|
+
}
|
|
16821
|
+
for (const task of needsName) {
|
|
16822
|
+
const sessionId = task.sessionId;
|
|
16823
|
+
try {
|
|
16824
|
+
bridge.updateSessionMetadata(sessionId, {
|
|
16825
|
+
displayName: scheduledTaskSessionName(task.prompt)
|
|
16826
|
+
});
|
|
16827
|
+
renamed.add(sessionId);
|
|
16828
|
+
} catch (err) {
|
|
16829
|
+
log.debug("keepalive: failed to name session", sessionId, err);
|
|
16830
|
+
}
|
|
16831
|
+
}
|
|
16832
|
+
}
|
|
16833
|
+
__name(bindAndNameSessions, "bindAndNameSessions");
|
|
16834
|
+
function startScheduledTaskKeepalive(opts) {
|
|
16835
|
+
const { bridge, boundWorkspace, intervalMs } = opts;
|
|
16836
|
+
const reviveTimeoutMs = opts.reviveTimeoutMs ?? KEEPALIVE_REVIVE_TIMEOUT_MS;
|
|
16837
|
+
const spawnTimeoutMs = opts.spawnTimeoutMs ?? KEEPALIVE_SPAWN_TIMEOUT_MS;
|
|
16838
|
+
const reviveState = /* @__PURE__ */ new Map();
|
|
16839
|
+
const reviving = /* @__PURE__ */ new Set();
|
|
16840
|
+
const binding = /* @__PURE__ */ new Set();
|
|
16841
|
+
const renamed = /* @__PURE__ */ new Set();
|
|
16842
|
+
const tick = /* @__PURE__ */ __name(async () => {
|
|
16843
|
+
let tasks;
|
|
16844
|
+
try {
|
|
16845
|
+
tasks = await readCronTasks(boundWorkspace);
|
|
16846
|
+
} catch (err) {
|
|
16847
|
+
log.debug("keepalive: readCronTasks failed, skipping this pass", err);
|
|
16848
|
+
return;
|
|
16849
|
+
}
|
|
16850
|
+
for (const sessionId of collectBoundSessionIds(tasks)) {
|
|
16851
|
+
try {
|
|
16852
|
+
bridge.recordHeartbeat(sessionId);
|
|
16853
|
+
reviveState.delete(sessionId);
|
|
16854
|
+
} catch (err) {
|
|
16855
|
+
const state = reviveState.get(sessionId);
|
|
16856
|
+
if (reviving.has(sessionId)) {
|
|
16857
|
+
continue;
|
|
15216
16858
|
}
|
|
15217
|
-
if (
|
|
15218
|
-
|
|
15219
|
-
error: "`syncOutputLanguage` must be a boolean when provided",
|
|
15220
|
-
code: "invalid_sync_flag"
|
|
15221
|
-
});
|
|
15222
|
-
return;
|
|
16859
|
+
if (state && Date.now() < state.nextAttemptAt) {
|
|
16860
|
+
continue;
|
|
15223
16861
|
}
|
|
15224
|
-
|
|
15225
|
-
|
|
15226
|
-
const response = await bridge.setSessionLanguage(
|
|
16862
|
+
log.debug("keepalive: recordHeartbeat failed for", sessionId, err);
|
|
16863
|
+
const load = bridge.loadSession({
|
|
15227
16864
|
sessionId,
|
|
15228
|
-
|
|
15229
|
-
|
|
15230
|
-
|
|
15231
|
-
|
|
15232
|
-
|
|
15233
|
-
)
|
|
15234
|
-
|
|
16865
|
+
workspaceCwd: boundWorkspace,
|
|
16866
|
+
historyReplay: "response"
|
|
16867
|
+
});
|
|
16868
|
+
reviving.add(sessionId);
|
|
16869
|
+
void load.catch(() => {
|
|
16870
|
+
}).finally(() => {
|
|
16871
|
+
reviving.delete(sessionId);
|
|
16872
|
+
});
|
|
16873
|
+
try {
|
|
16874
|
+
await withTimeout(load, reviveTimeoutMs, `loadSession(${sessionId})`);
|
|
16875
|
+
log.debug("keepalive: revived non-resident session", sessionId);
|
|
16876
|
+
reviveState.delete(sessionId);
|
|
16877
|
+
} catch (loadErr) {
|
|
16878
|
+
const failures = (state?.failures ?? 0) + 1;
|
|
16879
|
+
const backoff = Math.min(
|
|
16880
|
+
intervalMs * 2 ** Math.min(failures - 1, 6),
|
|
16881
|
+
MAX_REVIVE_BACKOFF_MS
|
|
16882
|
+
);
|
|
16883
|
+
reviveState.set(sessionId, {
|
|
16884
|
+
failures,
|
|
16885
|
+
nextAttemptAt: Date.now() + backoff
|
|
16886
|
+
});
|
|
16887
|
+
log.debug(
|
|
16888
|
+
"keepalive: failed to revive session",
|
|
16889
|
+
sessionId,
|
|
16890
|
+
`(failure ${failures}, next retry in ${backoff}ms)`,
|
|
16891
|
+
loadErr
|
|
16892
|
+
);
|
|
16893
|
+
}
|
|
16894
|
+
}
|
|
16895
|
+
}
|
|
16896
|
+
if (reviveState.size > 0 || renamed.size > 0) {
|
|
16897
|
+
const live = new Set(tasks.map((t) => t.sessionId));
|
|
16898
|
+
for (const id of reviveState.keys()) {
|
|
16899
|
+
if (!live.has(id)) reviveState.delete(id);
|
|
16900
|
+
}
|
|
16901
|
+
for (const id of renamed) {
|
|
16902
|
+
if (!live.has(id)) renamed.delete(id);
|
|
16903
|
+
}
|
|
16904
|
+
}
|
|
16905
|
+
await bindAndNameSessions(
|
|
16906
|
+
bridge,
|
|
16907
|
+
boundWorkspace,
|
|
16908
|
+
tasks,
|
|
16909
|
+
renamed,
|
|
16910
|
+
spawnTimeoutMs,
|
|
16911
|
+
binding
|
|
16912
|
+
);
|
|
16913
|
+
}, "tick");
|
|
16914
|
+
let running = false;
|
|
16915
|
+
const timer = setInterval(() => {
|
|
16916
|
+
if (running) return;
|
|
16917
|
+
running = true;
|
|
16918
|
+
void tick().finally(() => {
|
|
16919
|
+
running = false;
|
|
16920
|
+
});
|
|
16921
|
+
}, intervalMs);
|
|
16922
|
+
timer.unref?.();
|
|
16923
|
+
let bindDebounce;
|
|
16924
|
+
const cronFilePath = getCronFilePath(boundWorkspace);
|
|
16925
|
+
const cronDir = path11.dirname(cronFilePath);
|
|
16926
|
+
const cronFileName = path11.basename(cronFilePath);
|
|
16927
|
+
let fileWatcher;
|
|
16928
|
+
try {
|
|
16929
|
+
fsSync.mkdirSync(cronDir, { recursive: true });
|
|
16930
|
+
fileWatcher = fsSync.watch(
|
|
16931
|
+
cronDir,
|
|
16932
|
+
{ persistent: false },
|
|
16933
|
+
(_event, filename) => {
|
|
16934
|
+
if (filename !== null && filename !== cronFileName) return;
|
|
16935
|
+
if (bindDebounce) clearTimeout(bindDebounce);
|
|
16936
|
+
bindDebounce = setTimeout(() => {
|
|
16937
|
+
if (running) return;
|
|
16938
|
+
running = true;
|
|
16939
|
+
void tick().finally(() => {
|
|
16940
|
+
running = false;
|
|
16941
|
+
});
|
|
16942
|
+
}, 500);
|
|
16943
|
+
bindDebounce.unref?.();
|
|
16944
|
+
}
|
|
16945
|
+
);
|
|
16946
|
+
fileWatcher.on("error", () => {
|
|
16947
|
+
});
|
|
16948
|
+
} catch {
|
|
16949
|
+
}
|
|
16950
|
+
let stopped = false;
|
|
16951
|
+
return {
|
|
16952
|
+
stop: /* @__PURE__ */ __name(() => {
|
|
16953
|
+
if (stopped) return;
|
|
16954
|
+
stopped = true;
|
|
16955
|
+
clearInterval(timer);
|
|
16956
|
+
if (bindDebounce) clearTimeout(bindDebounce);
|
|
16957
|
+
fileWatcher?.close();
|
|
16958
|
+
}, "stop"),
|
|
16959
|
+
tick
|
|
16960
|
+
};
|
|
16961
|
+
}
|
|
16962
|
+
__name(startScheduledTaskKeepalive, "startScheduledTaskKeepalive");
|
|
16963
|
+
var REHYDRATE_LOAD_TIMEOUT_MS = 3e4;
|
|
16964
|
+
var REHYDRATE_MAX_CONCURRENCY = 4;
|
|
16965
|
+
async function rehydrateScheduledTaskSessions(deps) {
|
|
16966
|
+
const { bridge, boundWorkspace } = deps;
|
|
16967
|
+
const timeoutMs = deps.loadTimeoutMs ?? REHYDRATE_LOAD_TIMEOUT_MS;
|
|
16968
|
+
let tasks;
|
|
16969
|
+
try {
|
|
16970
|
+
tasks = await readCronTasks(boundWorkspace);
|
|
16971
|
+
} catch (err) {
|
|
16972
|
+
log.debug("rehydrate: readCronTasks failed", err);
|
|
16973
|
+
return { loaded: [], failed: [] };
|
|
16974
|
+
}
|
|
16975
|
+
const sessionIds = collectBoundSessionIds(tasks);
|
|
16976
|
+
const loaded = [];
|
|
16977
|
+
const failed = [];
|
|
16978
|
+
const loadOne = /* @__PURE__ */ __name(async (sessionId) => {
|
|
16979
|
+
const load = bridge.loadSession({
|
|
16980
|
+
sessionId,
|
|
16981
|
+
workspaceCwd: boundWorkspace,
|
|
16982
|
+
historyReplay: "response"
|
|
16983
|
+
});
|
|
16984
|
+
void load.catch(() => {
|
|
16985
|
+
});
|
|
16986
|
+
try {
|
|
16987
|
+
await withTimeout(load, timeoutMs, `loadSession(${sessionId})`);
|
|
16988
|
+
loaded.push(sessionId);
|
|
16989
|
+
} catch (err) {
|
|
16990
|
+
failed.push(sessionId);
|
|
16991
|
+
try {
|
|
16992
|
+
deps.onError?.(sessionId, err);
|
|
16993
|
+
} catch {
|
|
15235
16994
|
}
|
|
16995
|
+
}
|
|
16996
|
+
}, "loadOne");
|
|
16997
|
+
const queue = sessionIds.slice();
|
|
16998
|
+
const worker = /* @__PURE__ */ __name(async () => {
|
|
16999
|
+
for (let sessionId = queue.shift(); sessionId !== void 0; sessionId = queue.shift()) {
|
|
17000
|
+
await loadOne(sessionId);
|
|
17001
|
+
}
|
|
17002
|
+
}, "worker");
|
|
17003
|
+
await Promise.all(
|
|
17004
|
+
Array.from(
|
|
17005
|
+
{ length: Math.min(REHYDRATE_MAX_CONCURRENCY, queue.length) },
|
|
17006
|
+
() => worker()
|
|
15236
17007
|
)
|
|
15237
17008
|
);
|
|
17009
|
+
return { loaded, failed };
|
|
15238
17010
|
}
|
|
15239
|
-
__name(
|
|
17011
|
+
__name(rehydrateScheduledTaskSessions, "rehydrateScheduledTaskSessions");
|
|
17012
|
+
function withTimeout(p, ms, label) {
|
|
17013
|
+
return new Promise((resolve4, reject) => {
|
|
17014
|
+
const timer = setTimeout(() => {
|
|
17015
|
+
reject(new Error(`${label} timed out after ${ms}ms`));
|
|
17016
|
+
}, ms);
|
|
17017
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
17018
|
+
p.then(
|
|
17019
|
+
(v) => {
|
|
17020
|
+
clearTimeout(timer);
|
|
17021
|
+
resolve4(v);
|
|
17022
|
+
},
|
|
17023
|
+
(e) => {
|
|
17024
|
+
clearTimeout(timer);
|
|
17025
|
+
reject(e);
|
|
17026
|
+
}
|
|
17027
|
+
);
|
|
17028
|
+
});
|
|
17029
|
+
}
|
|
17030
|
+
__name(withTimeout, "withTimeout");
|
|
15240
17031
|
|
|
15241
17032
|
// packages/cli/src/serve/routes/workspace-status.ts
|
|
15242
17033
|
init_esbuild_shims();
|
|
@@ -16387,7 +18178,7 @@ __name(registerSseEventsRoutes, "registerSseEventsRoutes");
|
|
|
16387
18178
|
// packages/cli/src/serve/routes/a2ui-action.ts
|
|
16388
18179
|
init_esbuild_shims();
|
|
16389
18180
|
import * as fsp4 from "node:fs/promises";
|
|
16390
|
-
import * as
|
|
18181
|
+
import * as path12 from "node:path";
|
|
16391
18182
|
var A2UI_MIME = "application/a2ui+json";
|
|
16392
18183
|
var ACTION_TOOL = "action";
|
|
16393
18184
|
var CALL_TIMEOUT_MS = 15e3;
|
|
@@ -16407,7 +18198,7 @@ __name(usableServerConfig, "usableServerConfig");
|
|
|
16407
18198
|
async function findFromSettingsFile(workspaceCwd) {
|
|
16408
18199
|
try {
|
|
16409
18200
|
const raw = await fsp4.readFile(
|
|
16410
|
-
|
|
18201
|
+
path12.join(workspaceCwd, ".qwen", "settings.json"),
|
|
16411
18202
|
"utf8"
|
|
16412
18203
|
);
|
|
16413
18204
|
const settings = JSON.parse(raw);
|
|
@@ -16532,6 +18323,8 @@ __name(registerA2uiActionRoutes, "registerA2uiActionRoutes");
|
|
|
16532
18323
|
|
|
16533
18324
|
// packages/cli/src/serve/server/fs-factory.ts
|
|
16534
18325
|
init_esbuild_shims();
|
|
18326
|
+
import * as path13 from "node:path";
|
|
18327
|
+
var IDE_WORKSPACE_PATH_ENV_VAR = "QWEN_CODE_IDE_WORKSPACE_PATH";
|
|
16535
18328
|
function createDefaultFsAuditEmit() {
|
|
16536
18329
|
const WARN_EVERY = 100;
|
|
16537
18330
|
let droppedCount = 0;
|
|
@@ -16554,28 +18347,100 @@ __name(createDefaultFsAuditEmit, "createDefaultFsAuditEmit");
|
|
|
16554
18347
|
function resolveBridgeFsFactory(input) {
|
|
16555
18348
|
if (input.injected) return input.injected;
|
|
16556
18349
|
return createWorkspaceFileSystemFactory({
|
|
16557
|
-
|
|
18350
|
+
boundWorkspaces: input.boundWorkspaces,
|
|
16558
18351
|
trusted: input.trusted,
|
|
16559
18352
|
emit: input.emit ?? createDefaultFsAuditEmit(),
|
|
18353
|
+
pathLocks: input.pathLocks,
|
|
16560
18354
|
...input.customIgnoreFiles !== void 0 ? { customIgnoreFiles: input.customIgnoreFiles } : {}
|
|
16561
18355
|
});
|
|
16562
18356
|
}
|
|
16563
18357
|
__name(resolveBridgeFsFactory, "resolveBridgeFsFactory");
|
|
18358
|
+
function resolveBoundWorkspacesFromIdeEnv(primaryWorkspace, ideWorkspacePath = process.env[IDE_WORKSPACE_PATH_ENV_VAR], includeWorkspace) {
|
|
18359
|
+
let primary = primaryWorkspace;
|
|
18360
|
+
const envCanonicals = [];
|
|
18361
|
+
try {
|
|
18362
|
+
primary = canonicalizeWorkspace(primaryWorkspace);
|
|
18363
|
+
} catch (err) {
|
|
18364
|
+
writeStderrLine(
|
|
18365
|
+
`qwen serve: failed to canonicalize IDE workspace paths, using primary only: ${err}`
|
|
18366
|
+
);
|
|
18367
|
+
return [primary];
|
|
18368
|
+
}
|
|
18369
|
+
for (const workspace of parseIdeWorkspacePathEnv(ideWorkspacePath)) {
|
|
18370
|
+
try {
|
|
18371
|
+
const canonical = canonicalizeWorkspace(workspace);
|
|
18372
|
+
if (envCanonicals.includes(canonical)) continue;
|
|
18373
|
+
envCanonicals.push(canonical);
|
|
18374
|
+
} catch (err) {
|
|
18375
|
+
writeStderrLine(
|
|
18376
|
+
`qwen serve: skipping IDE workspace root that failed to canonicalize: ${workspace} (${err})`
|
|
18377
|
+
);
|
|
18378
|
+
}
|
|
18379
|
+
}
|
|
18380
|
+
if (envCanonicals.length > 0 && !envCanonicals.some(
|
|
18381
|
+
(workspace) => isWithinRoot2(primary, workspace) || isWithinRoot2(workspace, primary)
|
|
18382
|
+
)) {
|
|
18383
|
+
writeStderrLine(
|
|
18384
|
+
"qwen serve: ignoring stale IDE workspace paths that do not overlap the selected workspace"
|
|
18385
|
+
);
|
|
18386
|
+
return [primary];
|
|
18387
|
+
}
|
|
18388
|
+
const workspaces = [primary, ...envCanonicals.filter((w) => w !== primary)];
|
|
18389
|
+
const filteredWorkspaces = includeWorkspace === void 0 ? workspaces : workspaces.filter(includeWorkspace);
|
|
18390
|
+
return dropNestedWorkspacesPreservingPrimary(filteredWorkspaces);
|
|
18391
|
+
}
|
|
18392
|
+
__name(resolveBoundWorkspacesFromIdeEnv, "resolveBoundWorkspacesFromIdeEnv");
|
|
18393
|
+
function parseIdeWorkspacePathEnv(value) {
|
|
18394
|
+
if (value === void 0 || value.length === 0) return [];
|
|
18395
|
+
if (value.trimStart().startsWith("[")) {
|
|
18396
|
+
try {
|
|
18397
|
+
const parsed = JSON.parse(value);
|
|
18398
|
+
if (Array.isArray(parsed) && parsed.every((item) => typeof item === "string")) {
|
|
18399
|
+
return parsed.filter(
|
|
18400
|
+
(workspace) => workspace.length > 0 && path13.isAbsolute(workspace)
|
|
18401
|
+
);
|
|
18402
|
+
}
|
|
18403
|
+
throw new Error("IDE workspace path JSON must be a string array");
|
|
18404
|
+
} catch {
|
|
18405
|
+
}
|
|
18406
|
+
}
|
|
18407
|
+
return value.split(path13.delimiter).filter((workspace) => workspace.length > 0 && path13.isAbsolute(workspace));
|
|
18408
|
+
}
|
|
18409
|
+
__name(parseIdeWorkspacePathEnv, "parseIdeWorkspacePathEnv");
|
|
18410
|
+
function dropNestedWorkspacesPreservingPrimary(workspaces) {
|
|
18411
|
+
const primary = workspaces[0];
|
|
18412
|
+
if (primary === void 0) return [];
|
|
18413
|
+
const withoutPrimaryOverlaps = workspaces.filter(
|
|
18414
|
+
(workspace, i) => i === 0 || workspace !== primary && !isWithinRoot2(workspace, primary) && !isWithinRoot2(primary, workspace)
|
|
18415
|
+
);
|
|
18416
|
+
const filtered = withoutPrimaryOverlaps.filter(
|
|
18417
|
+
(workspace, i) => i === 0 || !withoutPrimaryOverlaps.some(
|
|
18418
|
+
(other, j) => i !== j && j !== 0 && workspace !== other && isWithinRoot2(workspace, other)
|
|
18419
|
+
)
|
|
18420
|
+
);
|
|
18421
|
+
if (filtered.length < workspaces.length) {
|
|
18422
|
+
writeStderrLine(
|
|
18423
|
+
"qwen serve: dropping nested IDE workspace roots (parent folders already cover children)"
|
|
18424
|
+
);
|
|
18425
|
+
}
|
|
18426
|
+
return filtered;
|
|
18427
|
+
}
|
|
18428
|
+
__name(dropNestedWorkspacesPreservingPrimary, "dropNestedWorkspacesPreservingPrimary");
|
|
16564
18429
|
|
|
16565
18430
|
// packages/cli/src/serve/server/telemetry.ts
|
|
16566
18431
|
init_esbuild_shims();
|
|
16567
18432
|
function resolveDaemonTelemetryRoute(req) {
|
|
16568
|
-
const
|
|
16569
|
-
if (req.method === "POST" &&
|
|
18433
|
+
const path14 = req.path.replace(/\/$/, "") || "/";
|
|
18434
|
+
if (req.method === "POST" && path14 === "/session") {
|
|
16570
18435
|
return { route: "POST /session" };
|
|
16571
18436
|
}
|
|
16572
|
-
if (req.method === "POST" &&
|
|
18437
|
+
if (req.method === "POST" && path14 === "/sessions/delete") {
|
|
16573
18438
|
return { route: "POST /sessions/delete" };
|
|
16574
18439
|
}
|
|
16575
|
-
if (req.method === "GET" &&
|
|
18440
|
+
if (req.method === "GET" && path14 === "/daemon/status") {
|
|
16576
18441
|
return { route: "GET /daemon/status" };
|
|
16577
18442
|
}
|
|
16578
|
-
const sessionAction =
|
|
18443
|
+
const sessionAction = path14.match(
|
|
16579
18444
|
/^\/session\/([^/]+)\/(load|resume|prompt|cancel|recap|btw|mid-turn-message|model|shell|detach|rewind|approval-mode|language|a2ui-action)$/
|
|
16580
18445
|
);
|
|
16581
18446
|
const sessionActionId = sessionAction?.[1];
|
|
@@ -16586,14 +18451,14 @@ function resolveDaemonTelemetryRoute(req) {
|
|
|
16586
18451
|
sessionId: sessionActionId
|
|
16587
18452
|
};
|
|
16588
18453
|
}
|
|
16589
|
-
const sessionMetadata =
|
|
18454
|
+
const sessionMetadata = path14.match(/^\/session\/([^/]+)\/metadata$/);
|
|
16590
18455
|
if (sessionMetadata?.[1] && req.method === "PATCH") {
|
|
16591
18456
|
return {
|
|
16592
18457
|
route: "PATCH /session/:id/metadata",
|
|
16593
18458
|
sessionId: sessionMetadata[1]
|
|
16594
18459
|
};
|
|
16595
18460
|
}
|
|
16596
|
-
const sessionArtifacts =
|
|
18461
|
+
const sessionArtifacts = path14.match(/^\/session\/([^/]+)\/artifacts$/);
|
|
16597
18462
|
if (sessionArtifacts?.[1]) {
|
|
16598
18463
|
if (req.method === "GET") {
|
|
16599
18464
|
return {
|
|
@@ -16608,7 +18473,7 @@ function resolveDaemonTelemetryRoute(req) {
|
|
|
16608
18473
|
};
|
|
16609
18474
|
}
|
|
16610
18475
|
}
|
|
16611
|
-
const sessionArtifact =
|
|
18476
|
+
const sessionArtifact = path14.match(
|
|
16612
18477
|
/^\/session\/([^/]+)\/artifacts\/([^/]+)$/
|
|
16613
18478
|
);
|
|
16614
18479
|
if (sessionArtifact?.[1] && req.method === "DELETE") {
|
|
@@ -16617,7 +18482,7 @@ function resolveDaemonTelemetryRoute(req) {
|
|
|
16617
18482
|
sessionId: sessionArtifact[1]
|
|
16618
18483
|
};
|
|
16619
18484
|
}
|
|
16620
|
-
const sessionPermission =
|
|
18485
|
+
const sessionPermission = path14.match(
|
|
16621
18486
|
/^\/session\/([^/]+)\/permission\/([^/]+)$/
|
|
16622
18487
|
);
|
|
16623
18488
|
if (sessionPermission?.[1] && sessionPermission?.[2] && req.method === "POST") {
|
|
@@ -16628,7 +18493,7 @@ function resolveDaemonTelemetryRoute(req) {
|
|
|
16628
18493
|
...rawRequestId.length <= MAX_CLIENT_ID_LENGTH && CLIENT_ID_RE.test(rawRequestId) ? { permissionRequestId: rawRequestId } : {}
|
|
16629
18494
|
};
|
|
16630
18495
|
}
|
|
16631
|
-
const globalPermission =
|
|
18496
|
+
const globalPermission = path14.match(/^\/permission\/([^/]+)$/);
|
|
16632
18497
|
if (globalPermission?.[1] && req.method === "POST") {
|
|
16633
18498
|
const rawRequestId = globalPermission[1];
|
|
16634
18499
|
return {
|
|
@@ -16636,79 +18501,87 @@ function resolveDaemonTelemetryRoute(req) {
|
|
|
16636
18501
|
...rawRequestId.length <= MAX_CLIENT_ID_LENGTH && CLIENT_ID_RE.test(rawRequestId) ? { permissionRequestId: rawRequestId } : {}
|
|
16637
18502
|
};
|
|
16638
18503
|
}
|
|
16639
|
-
const deleteSession =
|
|
18504
|
+
const deleteSession = path14.match(/^\/session\/([^/]+)$/);
|
|
16640
18505
|
const deleteSessionId = deleteSession?.[1];
|
|
16641
18506
|
if (deleteSessionId && req.method === "DELETE") {
|
|
16642
18507
|
return { route: "DELETE /session/:id", sessionId: deleteSessionId };
|
|
16643
18508
|
}
|
|
16644
|
-
if (req.method === "GET" && /^\/workspace\/[^/]+\/sessions$/.test(
|
|
18509
|
+
if (req.method === "GET" && /^\/workspace\/[^/]+\/sessions$/.test(path14)) {
|
|
16645
18510
|
return { route: "GET /workspace/:id/sessions" };
|
|
16646
18511
|
}
|
|
16647
|
-
if (req.method === "POST" &&
|
|
18512
|
+
if (req.method === "POST" && path14 === "/workspace/init") {
|
|
16648
18513
|
return { route: "POST /workspace/init" };
|
|
16649
18514
|
}
|
|
16650
|
-
if (req.method === "POST" &&
|
|
18515
|
+
if (req.method === "POST" && path14 === "/workspace/setup-github") {
|
|
16651
18516
|
return { route: "POST /workspace/setup-github" };
|
|
16652
18517
|
}
|
|
16653
|
-
if (req.method === "POST" &&
|
|
18518
|
+
if (req.method === "POST" && path14 === "/workspace/reload") {
|
|
16654
18519
|
return { route: "POST /workspace/reload" };
|
|
16655
18520
|
}
|
|
16656
|
-
const mcpRestart =
|
|
18521
|
+
const mcpRestart = path14.match(/^\/workspace\/mcp\/([^/]+)\/restart$/);
|
|
16657
18522
|
if (mcpRestart?.[1] && req.method === "POST") {
|
|
16658
18523
|
return { route: "POST /workspace/mcp/:server/restart" };
|
|
16659
18524
|
}
|
|
16660
|
-
if (req.method === "POST" &&
|
|
18525
|
+
if (req.method === "POST" && path14 === "/workspace/mcp/servers") {
|
|
16661
18526
|
return { route: "POST /workspace/mcp/servers" };
|
|
16662
18527
|
}
|
|
16663
|
-
const mcpDelete =
|
|
18528
|
+
const mcpDelete = path14.match(/^\/workspace\/mcp\/servers\/([^/]+)$/);
|
|
16664
18529
|
if (mcpDelete?.[1] && req.method === "DELETE") {
|
|
16665
18530
|
return { route: "DELETE /workspace/mcp/servers/:name" };
|
|
16666
18531
|
}
|
|
16667
|
-
if (req.method === "POST" &&
|
|
18532
|
+
if (req.method === "POST" && path14 === "/workspace/auth/device-flow") {
|
|
16668
18533
|
return { route: "POST /workspace/auth/device-flow" };
|
|
16669
18534
|
}
|
|
16670
|
-
const deviceFlowDelete =
|
|
18535
|
+
const deviceFlowDelete = path14.match(
|
|
16671
18536
|
/^\/workspace\/auth\/device-flow\/([^/]+)$/
|
|
16672
18537
|
);
|
|
16673
18538
|
if (deviceFlowDelete?.[1] && req.method === "DELETE") {
|
|
16674
18539
|
return { route: "DELETE /workspace/auth/device-flow/:id" };
|
|
16675
18540
|
}
|
|
16676
|
-
const toolEnable =
|
|
18541
|
+
const toolEnable = path14.match(/^\/workspace\/tools\/([^/]+)\/enable$/);
|
|
16677
18542
|
if (toolEnable?.[1] && req.method === "POST") {
|
|
16678
18543
|
return { route: "POST /workspace/tools/:name/enable" };
|
|
16679
18544
|
}
|
|
16680
|
-
if (
|
|
18545
|
+
if (path14 === "/workspace/settings") {
|
|
16681
18546
|
if (req.method === "GET") return { route: "GET /workspace/settings" };
|
|
16682
18547
|
if (req.method === "POST") return { route: "POST /workspace/settings" };
|
|
16683
18548
|
}
|
|
16684
|
-
if (
|
|
18549
|
+
if (path14 === "/workspace/permissions") {
|
|
16685
18550
|
if (req.method === "GET") return { route: "GET /workspace/permissions" };
|
|
16686
18551
|
if (req.method === "POST") return { route: "POST /workspace/permissions" };
|
|
16687
18552
|
}
|
|
16688
|
-
if (
|
|
18553
|
+
if (path14 === "/workspace/trust") {
|
|
16689
18554
|
if (req.method === "GET") return { route: "GET /workspace/trust" };
|
|
16690
18555
|
}
|
|
16691
|
-
if (req.method === "POST" &&
|
|
18556
|
+
if (req.method === "POST" && path14 === "/workspace/trust/request") {
|
|
16692
18557
|
return { route: "POST /workspace/trust/request" };
|
|
16693
18558
|
}
|
|
16694
|
-
if (
|
|
18559
|
+
if (path14 === "/workspace/voice") {
|
|
16695
18560
|
if (req.method === "GET") return { route: "GET /workspace/voice" };
|
|
16696
18561
|
if (req.method === "POST") return { route: "POST /workspace/voice" };
|
|
16697
18562
|
}
|
|
16698
|
-
if (req.method === "POST" &&
|
|
18563
|
+
if (req.method === "POST" && path14 === "/workspace/voice/transcribe") {
|
|
16699
18564
|
return { route: "POST /workspace/voice/transcribe" };
|
|
16700
18565
|
}
|
|
16701
18566
|
return void 0;
|
|
16702
18567
|
}
|
|
16703
18568
|
__name(resolveDaemonTelemetryRoute, "resolveDaemonTelemetryRoute");
|
|
16704
|
-
function daemonTelemetryMiddleware(
|
|
16705
|
-
const
|
|
18569
|
+
function daemonTelemetryMiddleware(resolveWorkspaceCwd, recordRequest) {
|
|
18570
|
+
const workspaceHashByCwd = /* @__PURE__ */ new Map();
|
|
18571
|
+
const resolveWorkspaceHash = /* @__PURE__ */ __name((workspaceCwd) => {
|
|
18572
|
+
const existing = workspaceHashByCwd.get(workspaceCwd);
|
|
18573
|
+
if (existing !== void 0) return existing;
|
|
18574
|
+
const workspaceHash = hashDaemonWorkspace(workspaceCwd);
|
|
18575
|
+
workspaceHashByCwd.set(workspaceCwd, workspaceHash);
|
|
18576
|
+
return workspaceHash;
|
|
18577
|
+
}, "resolveWorkspaceHash");
|
|
16706
18578
|
return (req, res, next) => {
|
|
16707
18579
|
const route = resolveDaemonTelemetryRoute(req);
|
|
16708
18580
|
if (!route) {
|
|
16709
18581
|
next();
|
|
16710
18582
|
return;
|
|
16711
18583
|
}
|
|
18584
|
+
const workspaceHash = resolveWorkspaceHash(resolveWorkspaceCwd(req));
|
|
16712
18585
|
const rawClientId = req.get(CLIENT_ID_HEADER);
|
|
16713
18586
|
const clientId = rawClientId !== void 0 && rawClientId !== "" && rawClientId.length <= MAX_CLIENT_ID_LENGTH && CLIENT_ID_RE.test(rawClientId) ? rawClientId : void 0;
|
|
16714
18587
|
const startMs = Date.now();
|
|
@@ -16727,11 +18600,11 @@ function daemonTelemetryMiddleware(boundWorkspace) {
|
|
|
16727
18600
|
if (done) return;
|
|
16728
18601
|
done = true;
|
|
16729
18602
|
recordDaemonHttpResponse(span, res.statusCode);
|
|
16730
|
-
|
|
16731
|
-
|
|
16732
|
-
|
|
16733
|
-
res.statusCode
|
|
16734
|
-
|
|
18603
|
+
const durationMs = Date.now() - startMs;
|
|
18604
|
+
recordDaemonHttpRequest(durationMs, route.route, res.statusCode);
|
|
18605
|
+
if (route.route !== "GET /daemon/status") {
|
|
18606
|
+
recordRequest?.(durationMs, res.statusCode);
|
|
18607
|
+
}
|
|
16735
18608
|
resolve4();
|
|
16736
18609
|
}, "finish");
|
|
16737
18610
|
res.once("finish", finish);
|
|
@@ -17055,10 +18928,10 @@ function installRateLimiter(app, opts, daemonLog) {
|
|
|
17055
18928
|
{ tier, key: key.slice(0, 64) }
|
|
17056
18929
|
);
|
|
17057
18930
|
} : void 0,
|
|
17058
|
-
onError: daemonLog ? (err,
|
|
18931
|
+
onError: daemonLog ? (err, path14) => {
|
|
17059
18932
|
daemonLog.warn(
|
|
17060
18933
|
`rate limiter error (fail-open): ${err instanceof Error ? err.message : String(err)}`,
|
|
17061
|
-
{ path:
|
|
18934
|
+
{ path: path14 }
|
|
17062
18935
|
);
|
|
17063
18936
|
} : void 0
|
|
17064
18937
|
});
|
|
@@ -17109,6 +18982,73 @@ function installSelfOriginStripMiddleware(app, getPort) {
|
|
|
17109
18982
|
}
|
|
17110
18983
|
__name(installSelfOriginStripMiddleware, "installSelfOriginStripMiddleware");
|
|
17111
18984
|
|
|
18985
|
+
// packages/cli/src/serve/workspace-registry.ts
|
|
18986
|
+
init_esbuild_shims();
|
|
18987
|
+
function createWorkspaceRegistry(inputRuntimes) {
|
|
18988
|
+
if (inputRuntimes.length === 0) {
|
|
18989
|
+
throw new Error(
|
|
18990
|
+
"WorkspaceRegistry requires at least one workspace runtime."
|
|
18991
|
+
);
|
|
18992
|
+
}
|
|
18993
|
+
const primaryRuntimes = inputRuntimes.filter((runtime) => runtime.primary);
|
|
18994
|
+
if (primaryRuntimes.length !== 1) {
|
|
18995
|
+
throw new Error(
|
|
18996
|
+
"WorkspaceRegistry requires exactly one primary workspace runtime."
|
|
18997
|
+
);
|
|
18998
|
+
}
|
|
18999
|
+
const byCwd = /* @__PURE__ */ new Map();
|
|
19000
|
+
const byId = /* @__PURE__ */ new Map();
|
|
19001
|
+
for (const runtime of inputRuntimes) {
|
|
19002
|
+
if (byCwd.has(runtime.workspaceCwd)) {
|
|
19003
|
+
throw new Error(
|
|
19004
|
+
`Duplicate workspace runtime cwd ${JSON.stringify(
|
|
19005
|
+
runtime.workspaceCwd
|
|
19006
|
+
)}.`
|
|
19007
|
+
);
|
|
19008
|
+
}
|
|
19009
|
+
byCwd.set(runtime.workspaceCwd, runtime);
|
|
19010
|
+
if (byId.has(runtime.workspaceId)) {
|
|
19011
|
+
throw new Error(
|
|
19012
|
+
`Duplicate workspace runtime id ${JSON.stringify(
|
|
19013
|
+
runtime.workspaceId
|
|
19014
|
+
)}.`
|
|
19015
|
+
);
|
|
19016
|
+
}
|
|
19017
|
+
byId.set(runtime.workspaceId, runtime);
|
|
19018
|
+
}
|
|
19019
|
+
const runtimes = Object.freeze([...inputRuntimes]);
|
|
19020
|
+
const primary = primaryRuntimes[0];
|
|
19021
|
+
return {
|
|
19022
|
+
primary,
|
|
19023
|
+
list: /* @__PURE__ */ __name(() => runtimes, "list"),
|
|
19024
|
+
getByWorkspaceCwd: /* @__PURE__ */ __name((workspaceCwd) => byCwd.get(workspaceCwd), "getByWorkspaceCwd"),
|
|
19025
|
+
getByWorkspaceId: /* @__PURE__ */ __name((workspaceId) => byId.get(workspaceId), "getByWorkspaceId"),
|
|
19026
|
+
resolveWorkspaceCwd: /* @__PURE__ */ __name((workspaceCwd) => workspaceCwd === void 0 ? primary : byCwd.get(workspaceCwd), "resolveWorkspaceCwd"),
|
|
19027
|
+
resolveLiveSessionOwner: /* @__PURE__ */ __name((sessionId) => {
|
|
19028
|
+
const matches = [];
|
|
19029
|
+
for (const runtime of runtimes) {
|
|
19030
|
+
try {
|
|
19031
|
+
runtime.bridge.getSessionSummary(sessionId);
|
|
19032
|
+
matches.push(runtime);
|
|
19033
|
+
} catch (err) {
|
|
19034
|
+
if (err instanceof SessionNotFoundError) continue;
|
|
19035
|
+
throw err;
|
|
19036
|
+
}
|
|
19037
|
+
}
|
|
19038
|
+
if (matches.length === 0) return { kind: "not_found" };
|
|
19039
|
+
if (matches.length === 1) {
|
|
19040
|
+
return { kind: "found", runtime: matches[0] };
|
|
19041
|
+
}
|
|
19042
|
+
return { kind: "ambiguous", runtimes: matches };
|
|
19043
|
+
}, "resolveLiveSessionOwner")
|
|
19044
|
+
};
|
|
19045
|
+
}
|
|
19046
|
+
__name(createWorkspaceRegistry, "createWorkspaceRegistry");
|
|
19047
|
+
function createSingleWorkspaceRegistry(runtime) {
|
|
19048
|
+
return createWorkspaceRegistry([runtime]);
|
|
19049
|
+
}
|
|
19050
|
+
__name(createSingleWorkspaceRegistry, "createSingleWorkspaceRegistry");
|
|
19051
|
+
|
|
17112
19052
|
// packages/cli/src/serve/routes/workspace-lifecycle.ts
|
|
17113
19053
|
init_esbuild_shims();
|
|
17114
19054
|
function registerWorkspaceLifecycleRoutes(app, deps) {
|
|
@@ -17407,37 +19347,101 @@ __name(registerWorkspaceToolsRoutes, "registerWorkspaceToolsRoutes");
|
|
|
17407
19347
|
|
|
17408
19348
|
// packages/cli/src/serve/server.ts
|
|
17409
19349
|
var warnedDefaultTrust = false;
|
|
19350
|
+
function describeRegistryPrimaryForConflict(registry) {
|
|
19351
|
+
return `registry primary cwd=${JSON.stringify(registry.primary.workspaceCwd)}, workspaceId=${JSON.stringify(registry.primary.workspaceId)}`;
|
|
19352
|
+
}
|
|
19353
|
+
__name(describeRegistryPrimaryForConflict, "describeRegistryPrimaryForConflict");
|
|
19354
|
+
var DEFAULT_SESSION_IDLE_TIMEOUT_MS = 30 * 6e4;
|
|
19355
|
+
var KEEPALIVE_MIN_INTERVAL_MS = 3e4;
|
|
19356
|
+
var KEEPALIVE_MAX_INTERVAL_MS = 10 * 6e4;
|
|
19357
|
+
function computeKeepaliveIntervalMs(idleTimeoutMs) {
|
|
19358
|
+
if (idleTimeoutMs <= 0) return KEEPALIVE_MAX_INTERVAL_MS;
|
|
19359
|
+
const target = Math.min(
|
|
19360
|
+
Math.max(KEEPALIVE_MIN_INTERVAL_MS, Math.floor(idleTimeoutMs / 3)),
|
|
19361
|
+
KEEPALIVE_MAX_INTERVAL_MS
|
|
19362
|
+
);
|
|
19363
|
+
return Math.max(1, Math.min(target, Math.floor(idleTimeoutMs / 2)));
|
|
19364
|
+
}
|
|
19365
|
+
__name(computeKeepaliveIntervalMs, "computeKeepaliveIntervalMs");
|
|
17410
19366
|
function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
17411
19367
|
const app = (0, import_express4.default)();
|
|
17412
|
-
const
|
|
17413
|
-
|
|
19368
|
+
const injectedWorkspaceRegistry = deps.workspaceRegistry;
|
|
19369
|
+
const boundWorkspace = injectedWorkspaceRegistry?.primary.workspaceCwd ?? deps.boundWorkspace ?? canonicalizeWorkspace(opts.workspace ?? process.cwd());
|
|
19370
|
+
if (injectedWorkspaceRegistry) {
|
|
19371
|
+
const primary = injectedWorkspaceRegistry.primary;
|
|
19372
|
+
const registryConflictCandidates = [
|
|
19373
|
+
{
|
|
19374
|
+
depName: "deps.boundWorkspace",
|
|
19375
|
+
depValue: deps.boundWorkspace,
|
|
19376
|
+
registryValue: primary.workspaceCwd,
|
|
19377
|
+
detail: `deps.boundWorkspace=${JSON.stringify(deps.boundWorkspace)}`
|
|
19378
|
+
},
|
|
19379
|
+
{
|
|
19380
|
+
depName: "deps.bridge",
|
|
19381
|
+
depValue: deps.bridge,
|
|
19382
|
+
registryValue: primary.bridge,
|
|
19383
|
+
detail: "deps.bridge is a different object"
|
|
19384
|
+
},
|
|
19385
|
+
{
|
|
19386
|
+
depName: "deps.workspace",
|
|
19387
|
+
depValue: deps.workspace,
|
|
19388
|
+
registryValue: primary.workspaceService,
|
|
19389
|
+
detail: "deps.workspace is a different object"
|
|
19390
|
+
},
|
|
19391
|
+
{
|
|
19392
|
+
depName: "deps.fsFactory",
|
|
19393
|
+
depValue: deps.fsFactory,
|
|
19394
|
+
registryValue: primary.routeFileSystemFactory,
|
|
19395
|
+
detail: "deps.fsFactory is a different object"
|
|
19396
|
+
},
|
|
19397
|
+
{
|
|
19398
|
+
depName: "deps.clientMcpSenderRegistry",
|
|
19399
|
+
depValue: deps.clientMcpSenderRegistry,
|
|
19400
|
+
registryValue: primary.clientMcpSenderRegistry,
|
|
19401
|
+
detail: "deps.clientMcpSenderRegistry is a different object"
|
|
19402
|
+
}
|
|
19403
|
+
];
|
|
19404
|
+
for (const candidate of registryConflictCandidates) {
|
|
19405
|
+
if (candidate.depValue === void 0 || candidate.depValue === candidate.registryValue) {
|
|
19406
|
+
continue;
|
|
19407
|
+
}
|
|
19408
|
+
throw new Error(
|
|
19409
|
+
`createServeApp: workspaceRegistry conflicts with ${candidate.depName}: ${describeRegistryPrimaryForConflict(
|
|
19410
|
+
injectedWorkspaceRegistry
|
|
19411
|
+
)}; ${candidate.detail}.`
|
|
19412
|
+
);
|
|
19413
|
+
}
|
|
19414
|
+
}
|
|
19415
|
+
if (!injectedWorkspaceRegistry && !deps.fsFactory && !deps.bridge && !warnedDefaultTrust) {
|
|
17414
19416
|
warnedDefaultTrust = true;
|
|
17415
19417
|
process.stderr.write(
|
|
17416
19418
|
"qwen serve: createServeApp default fsFactory uses trusted=false \u2014 agent ACP writeTextFile calls will reject with untrusted_workspace. Inject deps.fsFactory (with explicit trust) or deps.bridge to override.\n"
|
|
17417
19419
|
);
|
|
17418
19420
|
}
|
|
17419
|
-
const fsFactory = resolveBridgeFsFactory({
|
|
17420
|
-
boundWorkspace,
|
|
19421
|
+
const fsFactory = injectedWorkspaceRegistry?.primary.routeFileSystemFactory ?? resolveBridgeFsFactory({
|
|
19422
|
+
boundWorkspaces: [boundWorkspace],
|
|
17421
19423
|
injected: deps.fsFactory,
|
|
17422
19424
|
trusted: false
|
|
17423
19425
|
});
|
|
17424
19426
|
const tokenConfigured = typeof opts.token === "string" && opts.token.length > 0;
|
|
17425
19427
|
const sessionShellCommandEnabled = opts.enableSessionShell === true && tokenConfigured;
|
|
17426
|
-
if (opts.clientMcpOverWs === true && deps.bridge && !deps.clientMcpSenderRegistry) {
|
|
19428
|
+
if (opts.clientMcpOverWs === true && deps.bridge && !injectedWorkspaceRegistry && !deps.clientMcpSenderRegistry) {
|
|
17427
19429
|
throw new Error(
|
|
17428
19430
|
"createServeApp: deps.bridge requires deps.clientMcpSenderRegistry when clientMcpOverWs is enabled (the bridge is already wired to its own sender; a fresh registry here would be an orphan)."
|
|
17429
19431
|
);
|
|
17430
19432
|
}
|
|
17431
|
-
const clientMcpSenderRegistry = deps.clientMcpSenderRegistry ?? new ClientMcpSenderRegistry();
|
|
19433
|
+
const clientMcpSenderRegistry = injectedWorkspaceRegistry?.primary.clientMcpSenderRegistry ?? deps.clientMcpSenderRegistry ?? new ClientMcpSenderRegistry();
|
|
17432
19434
|
const { languageCodes, currentServeFeatures, invalidateServeFeaturesCache } = createServeFeatures({
|
|
17433
19435
|
opts,
|
|
17434
19436
|
boundWorkspace,
|
|
17435
19437
|
persistSettingAvailable: deps.persistSetting !== void 0,
|
|
17436
|
-
|
|
19438
|
+
// Registry injection supplies the primary workspace service through the
|
|
19439
|
+
// runtime, so it has the same reload surface as legacy deps.workspace.
|
|
19440
|
+
reloadAvailable: deps.workspace !== void 0 || injectedWorkspaceRegistry !== void 0,
|
|
17437
19441
|
sessionShellCommandEnabled
|
|
17438
19442
|
});
|
|
17439
19443
|
const statusProvider = deps.statusProvider ?? createDaemonStatusProvider();
|
|
17440
|
-
const bridge = deps.bridge ?? createAcpSessionBridge({
|
|
19444
|
+
const bridge = injectedWorkspaceRegistry?.primary.bridge ?? deps.bridge ?? createAcpSessionBridge({
|
|
17441
19445
|
maxSessions: opts.maxSessions,
|
|
17442
19446
|
maxPendingPromptsPerSession: opts.maxPendingPromptsPerSession,
|
|
17443
19447
|
eventRingSize: opts.eventRingSize,
|
|
@@ -17467,7 +19471,7 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
17467
19471
|
const { daemonLog } = deps;
|
|
17468
19472
|
const sendBridgeError2 = /* @__PURE__ */ __name((res, err, ctx) => sendBridgeError(res, err, ctx, daemonLog), "sendBridgeError");
|
|
17469
19473
|
const sendPermissionVoteError2 = /* @__PURE__ */ __name((res, err, ctx) => sendPermissionVoteError(res, err, ctx, daemonLog), "sendPermissionVoteError");
|
|
17470
|
-
const workspace = deps.workspace ?? createDaemonWorkspaceService({
|
|
19474
|
+
const workspace = injectedWorkspaceRegistry?.primary.workspaceService ?? deps.workspace ?? createDaemonWorkspaceService({
|
|
17471
19475
|
boundWorkspace,
|
|
17472
19476
|
contextFilename: deps.contextFilename ?? "QWEN.md",
|
|
17473
19477
|
statusProvider,
|
|
@@ -17491,6 +19495,23 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
17491
19495
|
bridge.publishWorkspaceEvent(event);
|
|
17492
19496
|
}, "publishWorkspaceEvent")
|
|
17493
19497
|
});
|
|
19498
|
+
const workspaceRegistry = injectedWorkspaceRegistry ?? createSingleWorkspaceRegistry({
|
|
19499
|
+
workspaceId: hashDaemonWorkspace(boundWorkspace),
|
|
19500
|
+
workspaceCwd: boundWorkspace,
|
|
19501
|
+
primary: true,
|
|
19502
|
+
trusted: deps.primaryWorkspaceTrusted ?? false,
|
|
19503
|
+
env: { mode: "parent-process", overlayKeys: [] },
|
|
19504
|
+
bridge,
|
|
19505
|
+
workspaceService: workspace,
|
|
19506
|
+
routeFileSystemFactory: fsFactory,
|
|
19507
|
+
clientMcpSenderRegistry
|
|
19508
|
+
});
|
|
19509
|
+
app.locals.workspaceRegistry = workspaceRegistry;
|
|
19510
|
+
const primaryRuntime = workspaceRegistry.primary;
|
|
19511
|
+
const primaryBoundWorkspace = primaryRuntime.workspaceCwd;
|
|
19512
|
+
const primaryBridge = primaryRuntime.bridge;
|
|
19513
|
+
const primaryWorkspace = primaryRuntime.workspaceService;
|
|
19514
|
+
const primaryRouteFileSystemFactory = primaryRuntime.routeFileSystemFactory;
|
|
17494
19515
|
if (opts.allowOrigins && opts.allowOrigins.length > 0) {
|
|
17495
19516
|
const parsedAllowOrigins = parseAllowOriginPatterns(opts.allowOrigins);
|
|
17496
19517
|
if (parsedAllowOrigins.allowAny && !opts.token) {
|
|
@@ -17506,7 +19527,7 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
17506
19527
|
const healthDemoRoutes = createHealthDemoRoutes({
|
|
17507
19528
|
opts,
|
|
17508
19529
|
getPort,
|
|
17509
|
-
bridge,
|
|
19530
|
+
bridge: primaryBridge,
|
|
17510
19531
|
getActiveSseCount,
|
|
17511
19532
|
getRateLimiter: /* @__PURE__ */ __name(() => rateLimiter, "getRateLimiter")
|
|
17512
19533
|
});
|
|
@@ -17531,16 +19552,21 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
17531
19552
|
tokenConfigured,
|
|
17532
19553
|
requireAuth: opts.requireAuth === true
|
|
17533
19554
|
});
|
|
17534
|
-
app.use(
|
|
17535
|
-
|
|
19555
|
+
app.use(
|
|
19556
|
+
daemonTelemetryMiddleware(
|
|
19557
|
+
() => primaryBoundWorkspace,
|
|
19558
|
+
deps.recordDaemonRequest
|
|
19559
|
+
)
|
|
19560
|
+
);
|
|
19561
|
+
const buildWorkspaceCtx = createBuildWorkspaceCtx(primaryBoundWorkspace);
|
|
17536
19562
|
const acpHandleRef = {};
|
|
17537
|
-
const workspaceRememberLane = new WorkspaceRememberTaskLane(
|
|
19563
|
+
const workspaceRememberLane = new WorkspaceRememberTaskLane(primaryBridge);
|
|
17538
19564
|
const cdpTunnelRegistry = opts.cdpTunnelOverWs === true ? new CdpTunnelRegistry() : void 0;
|
|
17539
19565
|
registerDaemonStatusRoutes(app, {
|
|
17540
19566
|
opts,
|
|
17541
|
-
boundWorkspace,
|
|
17542
|
-
bridge,
|
|
17543
|
-
workspace,
|
|
19567
|
+
boundWorkspace: primaryBoundWorkspace,
|
|
19568
|
+
bridge: primaryBridge,
|
|
19569
|
+
workspace: primaryWorkspace,
|
|
17544
19570
|
daemonLog,
|
|
17545
19571
|
startup: deps.startup,
|
|
17546
19572
|
qwenCodeVersion: deps.qwenCodeVersion,
|
|
@@ -17552,54 +19578,55 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
17552
19578
|
deviceFlowRegistry,
|
|
17553
19579
|
sessionShellCommandEnabled,
|
|
17554
19580
|
getChannelWorkerSnapshot: deps.getChannelWorkerSnapshot,
|
|
17555
|
-
getPerfSnapshot: deps.getPerfSnapshot
|
|
19581
|
+
getPerfSnapshot: deps.getPerfSnapshot,
|
|
19582
|
+
getMetricsSeries: deps.getMetricsSeries
|
|
17556
19583
|
});
|
|
17557
19584
|
registerCapabilitiesRoutes(app, {
|
|
17558
19585
|
qwenCodeVersion: deps.qwenCodeVersion,
|
|
17559
19586
|
mode: opts.mode,
|
|
17560
19587
|
currentServeFeatures,
|
|
17561
|
-
boundWorkspace,
|
|
17562
|
-
permissionPolicy:
|
|
19588
|
+
boundWorkspace: primaryBoundWorkspace,
|
|
19589
|
+
permissionPolicy: primaryBridge.permissionPolicy,
|
|
17563
19590
|
maxPendingPromptsPerSession: opts.maxPendingPromptsPerSession,
|
|
17564
19591
|
languageCodes
|
|
17565
19592
|
});
|
|
17566
19593
|
registerWorkspaceStatusRoutes(app, {
|
|
17567
|
-
boundWorkspace,
|
|
17568
|
-
bridge,
|
|
17569
|
-
workspace,
|
|
19594
|
+
boundWorkspace: primaryBoundWorkspace,
|
|
19595
|
+
bridge: primaryBridge,
|
|
19596
|
+
workspace: primaryWorkspace,
|
|
17570
19597
|
sendBridgeError: sendBridgeError2
|
|
17571
19598
|
});
|
|
17572
19599
|
mountWorkspaceMemoryRoutes(app, {
|
|
17573
|
-
bridge,
|
|
17574
|
-
boundWorkspace,
|
|
19600
|
+
bridge: primaryBridge,
|
|
19601
|
+
boundWorkspace: primaryBoundWorkspace,
|
|
17575
19602
|
mutate,
|
|
17576
19603
|
parseClientId: parseClientIdHeader,
|
|
17577
19604
|
safeBody
|
|
17578
19605
|
});
|
|
17579
19606
|
mountWorkspaceMemoryRememberRoutes(app, {
|
|
17580
|
-
bridge,
|
|
19607
|
+
bridge: primaryBridge,
|
|
17581
19608
|
lane: workspaceRememberLane,
|
|
17582
19609
|
mutate,
|
|
17583
19610
|
parseClientId: parseClientIdHeader,
|
|
17584
19611
|
safeBody
|
|
17585
19612
|
});
|
|
17586
19613
|
mountWorkspaceAgentsRoutes(app, {
|
|
17587
|
-
bridge,
|
|
17588
|
-
boundWorkspace,
|
|
19614
|
+
bridge: primaryBridge,
|
|
19615
|
+
boundWorkspace: primaryBoundWorkspace,
|
|
17589
19616
|
mutate,
|
|
17590
19617
|
parseClientId: parseClientIdHeader,
|
|
17591
19618
|
safeBody
|
|
17592
19619
|
});
|
|
17593
19620
|
registerWorkspaceDiagnosticStatusRoutes(app, {
|
|
17594
|
-
boundWorkspace,
|
|
17595
|
-
bridge,
|
|
17596
|
-
workspace,
|
|
19621
|
+
boundWorkspace: primaryBoundWorkspace,
|
|
19622
|
+
bridge: primaryBridge,
|
|
19623
|
+
workspace: primaryWorkspace,
|
|
17597
19624
|
sendBridgeError: sendBridgeError2
|
|
17598
19625
|
});
|
|
17599
19626
|
registerWorkspaceExtensionRoutes(app, {
|
|
17600
|
-
boundWorkspace,
|
|
17601
|
-
bridge,
|
|
17602
|
-
workspace,
|
|
19627
|
+
boundWorkspace: primaryBoundWorkspace,
|
|
19628
|
+
bridge: primaryBridge,
|
|
19629
|
+
workspace: primaryWorkspace,
|
|
17603
19630
|
mutate,
|
|
17604
19631
|
safeBody,
|
|
17605
19632
|
sendBridgeError: sendBridgeError2,
|
|
@@ -17609,28 +19636,28 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
17609
19636
|
parseClientId: parseClientIdHeader
|
|
17610
19637
|
});
|
|
17611
19638
|
registerWorkspaceFileWriteRoutes(app, {
|
|
17612
|
-
bridge,
|
|
19639
|
+
bridge: primaryBridge,
|
|
17613
19640
|
mutate,
|
|
17614
19641
|
parseClientId: parseClientIdHeader,
|
|
17615
19642
|
safeBody
|
|
17616
19643
|
});
|
|
17617
19644
|
registerWorkspaceSetupGithubRoutes(app, {
|
|
17618
|
-
boundWorkspace,
|
|
17619
|
-
bridge,
|
|
19645
|
+
boundWorkspace: primaryBoundWorkspace,
|
|
19646
|
+
bridge: primaryBridge,
|
|
17620
19647
|
mutate,
|
|
17621
19648
|
parseClientId: parseClientIdHeader,
|
|
17622
19649
|
safeBody
|
|
17623
19650
|
});
|
|
17624
19651
|
registerWorkspaceTrustRoutes(app, {
|
|
17625
|
-
boundWorkspace,
|
|
17626
|
-
workspace,
|
|
19652
|
+
boundWorkspace: primaryBoundWorkspace,
|
|
19653
|
+
workspace: primaryWorkspace,
|
|
17627
19654
|
mutate,
|
|
17628
19655
|
safeBody,
|
|
17629
|
-
parseAndValidateClientId: /* @__PURE__ */ __name((req, res) => parseAndValidateWorkspaceClientId(req, res,
|
|
19656
|
+
parseAndValidateClientId: /* @__PURE__ */ __name((req, res) => parseAndValidateWorkspaceClientId(req, res, primaryBridge), "parseAndValidateClientId")
|
|
17630
19657
|
});
|
|
17631
19658
|
const broadcastSettingsChanged = /* @__PURE__ */ __name((key, value, scope, clientId) => {
|
|
17632
19659
|
invalidateServeFeaturesCache();
|
|
17633
|
-
|
|
19660
|
+
primaryBridge.publishWorkspaceEvent({
|
|
17634
19661
|
type: "settings_changed",
|
|
17635
19662
|
data: { key, value, scope },
|
|
17636
19663
|
...clientId ? { originatorClientId: clientId } : {}
|
|
@@ -17639,42 +19666,42 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
17639
19666
|
if (deps.persistSetting) {
|
|
17640
19667
|
const persistSetting = deps.persistSetting;
|
|
17641
19668
|
registerWorkspaceSettingsRoutes(app, {
|
|
17642
|
-
boundWorkspace,
|
|
19669
|
+
boundWorkspace: primaryBoundWorkspace,
|
|
17643
19670
|
mutate,
|
|
17644
19671
|
safeBody,
|
|
17645
19672
|
persistSetting: /* @__PURE__ */ __name(async (...args) => {
|
|
17646
19673
|
await persistSetting(...args);
|
|
17647
19674
|
}, "persistSetting"),
|
|
17648
19675
|
broadcastSettingsChanged,
|
|
17649
|
-
parseAndValidateClientId: /* @__PURE__ */ __name((req, res) => parseAndValidateWorkspaceClientId(req, res,
|
|
19676
|
+
parseAndValidateClientId: /* @__PURE__ */ __name((req, res) => parseAndValidateWorkspaceClientId(req, res, primaryBridge), "parseAndValidateClientId")
|
|
17650
19677
|
});
|
|
17651
19678
|
}
|
|
17652
19679
|
registerWorkspacePermissionsRoutes(app, {
|
|
17653
|
-
boundWorkspace,
|
|
19680
|
+
boundWorkspace: primaryBoundWorkspace,
|
|
17654
19681
|
mutate,
|
|
17655
19682
|
safeBody,
|
|
17656
|
-
workspace,
|
|
17657
|
-
parseAndValidateClientId: /* @__PURE__ */ __name((req, res) => parseAndValidateWorkspaceClientId(req, res,
|
|
19683
|
+
workspace: primaryWorkspace,
|
|
19684
|
+
parseAndValidateClientId: /* @__PURE__ */ __name((req, res) => parseAndValidateWorkspaceClientId(req, res, primaryBridge), "parseAndValidateClientId")
|
|
17658
19685
|
});
|
|
17659
19686
|
registerWorkspaceVoiceRoutes(app, {
|
|
17660
|
-
boundWorkspace,
|
|
19687
|
+
boundWorkspace: primaryBoundWorkspace,
|
|
17661
19688
|
mutate,
|
|
17662
19689
|
safeBody,
|
|
17663
19690
|
persistSetting: deps.persistSetting,
|
|
17664
19691
|
persistSettings: deps.persistSettings,
|
|
17665
19692
|
transcribe: deps.voiceTranscriber,
|
|
17666
19693
|
broadcastSettingsChanged,
|
|
17667
|
-
parseAndValidateClientId: /* @__PURE__ */ __name((req, res) => parseAndValidateWorkspaceClientId(req, res,
|
|
19694
|
+
parseAndValidateClientId: /* @__PURE__ */ __name((req, res) => parseAndValidateWorkspaceClientId(req, res, primaryBridge), "parseAndValidateClientId")
|
|
17668
19695
|
});
|
|
17669
19696
|
registerA2uiActionRoutes(app, {
|
|
17670
|
-
boundWorkspace,
|
|
19697
|
+
boundWorkspace: primaryBoundWorkspace,
|
|
17671
19698
|
mutate,
|
|
17672
19699
|
safeBody,
|
|
17673
19700
|
// UI-server discovery uses the daemon's workspace MCP status, which
|
|
17674
19701
|
// includes servers registered at runtime.
|
|
17675
19702
|
getMcpServers: /* @__PURE__ */ __name(async () => {
|
|
17676
19703
|
const ctx = buildWorkspaceCtx("POST /session/:id/a2ui-action");
|
|
17677
|
-
const status = await
|
|
19704
|
+
const status = await primaryWorkspace.getWorkspaceMcpStatus(ctx);
|
|
17678
19705
|
return status.servers ?? [];
|
|
17679
19706
|
}, "getMcpServers")
|
|
17680
19707
|
});
|
|
@@ -17683,13 +19710,13 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
17683
19710
|
deviceFlowRegistry,
|
|
17684
19711
|
getSupportedDeviceFlowProviders,
|
|
17685
19712
|
sendBridgeError: sendBridgeError2,
|
|
17686
|
-
boundWorkspace,
|
|
19713
|
+
boundWorkspace: primaryBoundWorkspace,
|
|
17687
19714
|
allowPrivateAuthBaseUrl: opts.allowPrivateAuthBaseUrl === true,
|
|
17688
19715
|
installAuthProvider: deps.installAuthProvider
|
|
17689
19716
|
});
|
|
17690
19717
|
registerSessionRoutes(app, {
|
|
17691
|
-
boundWorkspace,
|
|
17692
|
-
bridge,
|
|
19718
|
+
boundWorkspace: primaryBoundWorkspace,
|
|
19719
|
+
bridge: primaryBridge,
|
|
17693
19720
|
archiveCoordinator,
|
|
17694
19721
|
mutate,
|
|
17695
19722
|
sendBridgeError: sendBridgeError2,
|
|
@@ -17699,47 +19726,83 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
17699
19726
|
languageCodes
|
|
17700
19727
|
});
|
|
17701
19728
|
registerWorkspaceMcpControlRoutes(app, {
|
|
17702
|
-
boundWorkspace,
|
|
17703
|
-
bridge,
|
|
17704
|
-
workspace,
|
|
19729
|
+
boundWorkspace: primaryBoundWorkspace,
|
|
19730
|
+
bridge: primaryBridge,
|
|
19731
|
+
workspace: primaryWorkspace,
|
|
17705
19732
|
mutate,
|
|
17706
19733
|
safeBody,
|
|
17707
19734
|
sendBridgeError: sendBridgeError2,
|
|
17708
|
-
parseAndValidateClientId: /* @__PURE__ */ __name((req, res) => parseAndValidateWorkspaceClientId(req, res,
|
|
19735
|
+
parseAndValidateClientId: /* @__PURE__ */ __name((req, res) => parseAndValidateWorkspaceClientId(req, res, primaryBridge), "parseAndValidateClientId")
|
|
17709
19736
|
});
|
|
17710
19737
|
registerWorkspaceLifecycleRoutes(app, {
|
|
17711
|
-
boundWorkspace,
|
|
17712
|
-
workspace,
|
|
19738
|
+
boundWorkspace: primaryBoundWorkspace,
|
|
19739
|
+
workspace: primaryWorkspace,
|
|
17713
19740
|
mutate,
|
|
17714
19741
|
safeBody,
|
|
17715
19742
|
sendBridgeError: sendBridgeError2,
|
|
17716
19743
|
invalidateServeFeaturesCache,
|
|
17717
|
-
parseAndValidateClientId: /* @__PURE__ */ __name((req, res) => parseAndValidateWorkspaceClientId(req, res,
|
|
19744
|
+
parseAndValidateClientId: /* @__PURE__ */ __name((req, res) => parseAndValidateWorkspaceClientId(req, res, primaryBridge), "parseAndValidateClientId")
|
|
17718
19745
|
});
|
|
17719
19746
|
registerWorkspaceToolsRoutes(app, {
|
|
17720
|
-
boundWorkspace,
|
|
17721
|
-
workspace,
|
|
19747
|
+
boundWorkspace: primaryBoundWorkspace,
|
|
19748
|
+
workspace: primaryWorkspace,
|
|
17722
19749
|
mutate,
|
|
17723
19750
|
safeBody,
|
|
17724
19751
|
sendBridgeError: sendBridgeError2,
|
|
17725
|
-
parseAndValidateClientId: /* @__PURE__ */ __name((req, res) => parseAndValidateWorkspaceClientId(req, res,
|
|
19752
|
+
parseAndValidateClientId: /* @__PURE__ */ __name((req, res) => parseAndValidateWorkspaceClientId(req, res, primaryBridge), "parseAndValidateClientId")
|
|
17726
19753
|
});
|
|
19754
|
+
registerScheduledTasksRoutes(app, {
|
|
19755
|
+
boundWorkspace: primaryBoundWorkspace,
|
|
19756
|
+
mutate,
|
|
19757
|
+
safeBody,
|
|
19758
|
+
bridge: deps.manageScheduledTaskSessions ? bridge : void 0
|
|
19759
|
+
});
|
|
19760
|
+
registerUsageStatsRoutes(app);
|
|
19761
|
+
if (deps.manageScheduledTaskSessions) {
|
|
19762
|
+
const idleTimeoutMs = opts.sessionIdleTimeoutMs ?? DEFAULT_SESSION_IDLE_TIMEOUT_MS;
|
|
19763
|
+
const keepalive = startScheduledTaskKeepalive({
|
|
19764
|
+
bridge,
|
|
19765
|
+
boundWorkspace,
|
|
19766
|
+
intervalMs: computeKeepaliveIntervalMs(idleTimeoutMs)
|
|
19767
|
+
});
|
|
19768
|
+
app.locals.stopScheduledTaskKeepalive = keepalive.stop;
|
|
19769
|
+
void rehydrateScheduledTaskSessions({
|
|
19770
|
+
bridge,
|
|
19771
|
+
boundWorkspace,
|
|
19772
|
+
onError: /* @__PURE__ */ __name((sessionId, err) => {
|
|
19773
|
+
process.stderr.write(
|
|
19774
|
+
`qwen serve: failed to rehydrate scheduled-task session ${sessionId}: ${err instanceof Error ? err.message : String(err)}
|
|
19775
|
+
`
|
|
19776
|
+
);
|
|
19777
|
+
}, "onError")
|
|
19778
|
+
// Outer catch is defense-in-depth: rehydrateScheduledTaskSessions already
|
|
19779
|
+
// catches readCronTasks failures and per-session load errors internally
|
|
19780
|
+
// (returning { loaded, failed }), so this only guards an unexpected throw
|
|
19781
|
+
// from the function entry itself. Log rather than swallow it — a silent
|
|
19782
|
+
// failure here leaves every bound task dormant with no diagnostic.
|
|
19783
|
+
}).catch((err) => {
|
|
19784
|
+
process.stderr.write(
|
|
19785
|
+
`qwen serve: unexpected scheduled-task rehydration failure: ${err instanceof Error ? err.message : String(err)}
|
|
19786
|
+
`
|
|
19787
|
+
);
|
|
19788
|
+
});
|
|
19789
|
+
}
|
|
17727
19790
|
registerPermissionRoutes(app, {
|
|
17728
|
-
bridge,
|
|
19791
|
+
bridge: primaryBridge,
|
|
17729
19792
|
mutate,
|
|
17730
19793
|
sendPermissionVoteError: sendPermissionVoteError2
|
|
17731
19794
|
});
|
|
17732
19795
|
registerSseEventsRoutes(app, {
|
|
17733
|
-
bridge,
|
|
19796
|
+
bridge: primaryBridge,
|
|
17734
19797
|
daemonLog,
|
|
17735
19798
|
writerIdleTimeoutMs: opts.writerIdleTimeoutMs,
|
|
17736
19799
|
sendBridgeError: sendBridgeError2
|
|
17737
19800
|
});
|
|
17738
|
-
acpHandleRef.current = mountAcpHttp(app,
|
|
17739
|
-
boundWorkspace,
|
|
19801
|
+
acpHandleRef.current = mountAcpHttp(app, primaryBridge, {
|
|
19802
|
+
boundWorkspace: primaryBoundWorkspace,
|
|
17740
19803
|
archiveCoordinator,
|
|
17741
|
-
workspace,
|
|
17742
|
-
fsFactory,
|
|
19804
|
+
workspace: primaryWorkspace,
|
|
19805
|
+
fsFactory: primaryRouteFileSystemFactory,
|
|
17743
19806
|
deviceFlowRegistry,
|
|
17744
19807
|
token: opts.token,
|
|
17745
19808
|
// Mirror the REST CORS allowlist onto the WS CSRF wall so an
|
|
@@ -17758,8 +19821,8 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
17758
19821
|
// `clientMcpOverWs` is on; the WS layer never builds a provider otherwise.
|
|
17759
19822
|
...opts.clientMcpOverWs === true ? {
|
|
17760
19823
|
clientMcpProviderFactory: /* @__PURE__ */ __name((connectionId) => createClientMcpServerProvider(
|
|
17761
|
-
clientMcpSenderRegistry,
|
|
17762
|
-
|
|
19824
|
+
primaryRuntime.clientMcpSenderRegistry,
|
|
19825
|
+
primaryBridge,
|
|
17763
19826
|
connectionId
|
|
17764
19827
|
), "clientMcpProviderFactory")
|
|
17765
19828
|
} : {},
|
|
@@ -17773,7 +19836,7 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
17773
19836
|
extraWsRoutes: [
|
|
17774
19837
|
{
|
|
17775
19838
|
path: "/voice/stream",
|
|
17776
|
-
onConnection: createVoiceWsConnectionHandler(
|
|
19839
|
+
onConnection: createVoiceWsConnectionHandler(primaryBoundWorkspace)
|
|
17777
19840
|
}
|
|
17778
19841
|
]
|
|
17779
19842
|
});
|
|
@@ -17793,11 +19856,13 @@ __name(createServeApp, "createServeApp");
|
|
|
17793
19856
|
export {
|
|
17794
19857
|
InvalidCursorError,
|
|
17795
19858
|
PromptDeadlineExceededError,
|
|
19859
|
+
computeKeepaliveIntervalMs,
|
|
17796
19860
|
createDefaultFsAuditEmit,
|
|
17797
19861
|
createServeApp,
|
|
17798
19862
|
detectFromLoopback,
|
|
17799
19863
|
getActiveSseCount,
|
|
17800
19864
|
listWorkspaceSessionsForResponse,
|
|
19865
|
+
resolveBoundWorkspacesFromIdeEnv,
|
|
17801
19866
|
resolveBridgeFsFactory,
|
|
17802
19867
|
resolvePromptDeadlineMs
|
|
17803
19868
|
};
|