@qwen-code/qwen-code 0.19.9-nightly.20260714.9dd8389eb → 0.19.9-preview.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled/qc-helper/docs/configuration/settings.md +7 -0
- package/bundled/qc-helper/docs/features/sub-agents.md +21 -0
- package/bundled/qc-helper/docs/qwen-serve.md +7 -2
- package/bundled/review/SKILL.md +23 -11
- package/chunks/{MaxSizedBox-QAYHOC4F.js → MaxSizedBox-BEVSAWZ6.js} +10 -11
- package/chunks/{StandaloneSessionPicker-D3R6YYX3.js → StandaloneSessionPicker-2DA2MDDE.js} +22 -23
- package/chunks/{acpAgent-ENHBW72A.js → acpAgent-G3WJU3PS.js} +189 -93
- package/chunks/{agent-57LX4KMG.js → agent-DD3WSHXE.js} +7 -8
- package/chunks/{agent-headless-AC6EVNAP.js → agent-headless-MHI7U2CU.js} +7 -8
- package/chunks/{artifact-tool-BWZZJ42H.js → artifact-tool-MIZLXBLF.js} +1 -1
- package/chunks/{askUserQuestion-WU3S5A5T.js → askUserQuestion-XMJVPNWM.js} +1 -1
- package/chunks/{bridge-4OZH44MK.js → bridge-HKEWPNB5.js} +10 -11
- package/chunks/{channel-worker-manager-5RWKSGEU.js → channel-worker-manager-YSFB4HUU.js} +2 -2
- package/chunks/{channel-worker-supervisor-PCDAZN4D.js → channel-worker-supervisor-QCKIOYZD.js} +2 -2
- package/chunks/{chunk-DKXW66XA.js → chunk-2FMQQXMM.js} +1 -1
- package/chunks/{chunk-UI3W56WF.js → chunk-2J4ZWME4.js} +1 -1
- package/chunks/{chunk-QABSSDQH.js → chunk-2TQXXQMI.js} +1 -1
- package/chunks/{chunk-DNKX35GS.js → chunk-2UW5RFRP.js} +4 -4
- package/chunks/{chunk-DPSWJWZI.js → chunk-2W3OOD4W.js} +1 -0
- package/chunks/{chunk-MBJ3D6SF.js → chunk-43RXPU5Q.js} +1 -1
- package/chunks/{chunk-AAS2Q2TV.js → chunk-4QZ3XHWE.js} +2 -2
- package/chunks/{chunk-ZRKBI3DP.js → chunk-4QZADKJS.js} +1 -1
- package/chunks/{chunk-G6QTKQN4.js → chunk-54UZQ57N.js} +3 -3
- package/chunks/{chunk-YH4J5XI3.js → chunk-5VHASII5.js} +4 -4
- package/chunks/{chunk-AAWKZ5R7.js → chunk-6JPDCEDB.js} +1 -1
- package/chunks/{chunk-7NAWTFVY.js → chunk-6WPJRYLZ.js} +2 -2
- package/chunks/{chunk-JR63TOV4.js → chunk-7DKX4A5Z.js} +1 -1
- package/chunks/{chunk-T2L6SGQY.js → chunk-7IDCQ35Q.js} +37 -6
- package/chunks/{chunk-6OATMEYN.js → chunk-7STBROEV.js} +1 -1
- package/chunks/{chunk-7BIZARI2.js → chunk-A3U2TQUL.js} +4 -4
- package/chunks/{chunk-WN4D7YCI.js → chunk-A7W4GQRW.js} +1 -1
- package/chunks/{chunk-SE74CIZD.js → chunk-AAUZBE5Y.js} +1 -1
- package/chunks/{chunk-CLTWL6HD.js → chunk-ADLYZJYZ.js} +6 -6
- package/chunks/{chunk-SXU3KKNI.js → chunk-AU4R7ACM.js} +4 -0
- package/chunks/{chunk-URU5UEK6.js → chunk-B7MFJBKE.js} +1 -1
- package/chunks/{chunk-E67NFMF4.js → chunk-BLPMRN4B.js} +1 -1
- package/chunks/{chunk-7U7UIO5W.js → chunk-BXT6PQ73.js} +20 -5
- package/chunks/{chunk-ASVORVFT.js → chunk-CEGKYZDB.js} +1 -1
- package/chunks/{chunk-Z56JO4JZ.js → chunk-DMVA55W5.js} +7 -7
- package/chunks/{chunk-T5C2OUCI.js → chunk-DQCCLVQK.js} +3 -3
- package/chunks/{chunk-HE54PGVI.js → chunk-EAHMOXLL.js} +2 -2
- package/chunks/{chunk-C4CXFRKY.js → chunk-EJ3KGYWL.js} +25 -4
- package/chunks/{chunk-CSACSGAW.js → chunk-ERTHKDKN.js} +2 -2
- package/chunks/{chunk-OCC3UTWA.js → chunk-EX5OULP3.js} +1 -1
- package/chunks/{chunk-TXY2C3D2.js → chunk-F333XEAU.js} +1 -1
- package/chunks/{chunk-GYGGUR4Q.js → chunk-FEOBU3FA.js} +5 -5
- package/chunks/{chunk-PMRH4YYW.js → chunk-FI2WSOWX.js} +2 -1
- package/chunks/{chunk-N7FCSERB.js → chunk-GN5HJJBO.js} +1 -1
- package/chunks/{chunk-EU3U7USF.js → chunk-GTENQOEJ.js} +2 -2
- package/chunks/{chunk-MBP3K324.js → chunk-HCJTURZD.js} +32 -11
- package/chunks/{chunk-K3PPUB46.js → chunk-HGG2RRXD.js} +1 -1
- package/chunks/{chunk-YVEDCAUE.js → chunk-HGT6JR3U.js} +19 -0
- package/chunks/{chunk-X7EBAABW.js → chunk-HJCVNZLQ.js} +1 -1
- package/chunks/{chunk-SSFDPGFN.js → chunk-HJUDVNCO.js} +7959 -5497
- package/chunks/{chunk-6HHAADP2.js → chunk-HRUGYOJN.js} +4 -4
- package/chunks/{chunk-T7FSRWQ5.js → chunk-HWTL6MPR.js} +114 -9
- package/chunks/{chunk-HZYUNZDQ.js → chunk-JCD4FM7K.js} +1 -1
- package/chunks/{chunk-DMTQY5GY.js → chunk-JGO6RFI2.js} +3 -3
- package/chunks/{chunk-2UUAIFQO.js → chunk-JGUT3LWZ.js} +5 -0
- package/chunks/{chunk-S2QXYLUB.js → chunk-JW7OOJC6.js} +1 -1
- package/chunks/{chunk-4FPUAYTT.js → chunk-KF5MMGG4.js} +4 -4
- package/chunks/{chunk-XHW5VUIQ.js → chunk-LHEOLOXU.js} +157 -55
- package/chunks/{chunk-2MF6ICOL.js → chunk-LSEJG6AM.js} +36 -8
- package/chunks/{chunk-AGBFWLA5.js → chunk-MKK4XGLF.js} +1 -1
- package/chunks/{chunk-4LJNI7MU.js → chunk-MO7O5722.js} +262 -7
- package/chunks/{chunk-FDL4PB5H.js → chunk-MRYWYBQ4.js} +8 -8
- package/chunks/{chunk-ZLM3KXBT.js → chunk-NRAFASYX.js} +15 -14
- package/chunks/{chunk-WL4L7EZH.js → chunk-OA4JVLSA.js} +27 -1
- package/chunks/{chunk-B6KHOCU7.js → chunk-PMKW35AT.js} +14 -10
- package/chunks/{chunk-YWUK7OS7.js → chunk-QE2WJ7FS.js} +1 -1
- package/chunks/{chunk-B6ABUBYY.js → chunk-QUJOI7SR.js} +67 -5
- package/chunks/{chunk-WM5HY72H.js → chunk-R5D5JOAM.js} +2 -2
- package/chunks/{chunk-BBNVFRGD.js → chunk-R5XLNDE2.js} +1 -1
- package/chunks/{chunk-RJL5T4WJ.js → chunk-SMSFWUFY.js} +53 -40
- package/chunks/{chunk-WWQ3QIJS.js → chunk-UFDX27PL.js} +11 -11
- package/chunks/{chunk-LAM6CGP5.js → chunk-UGN6CTNZ.js} +3 -3
- package/chunks/{chunk-AUGSSJX3.js → chunk-UNLRKDMW.js} +3 -3
- package/chunks/{chunk-4QOPEEL2.js → chunk-VEETSXYX.js} +1 -1
- package/chunks/{chunk-P3S67A5R.js → chunk-VKS7EIOA.js} +4 -4
- package/chunks/{chunk-PHYCWSDG.js → chunk-VVDNGM6X.js} +1 -1
- package/chunks/{chunk-J2TDDOSM.js → chunk-WMC6HH7Y.js} +1 -1
- package/chunks/{chunk-WX6SD4IQ.js → chunk-WTNZ6DXB.js} +1 -1
- package/chunks/{chunk-EUNRLYJG.js → chunk-X6ODDESK.js} +4 -4
- package/chunks/{chunk-V75IOTWF.js → chunk-XDPYM5DE.js} +4 -4
- package/chunks/{chunk-4DYYRLIE.js → chunk-ZA74ZTQU.js} +644 -321
- package/chunks/{chunk-SUWGUGAD.js → chunk-ZGE3222J.js} +2 -2
- package/chunks/{chunk-OATTJGPJ.js → chunk-ZP4ZZJJG.js} +7 -7
- package/chunks/{computer-use-DIP5BWXP.js → computer-use-SHA6C3ES.js} +7 -8
- package/chunks/{config-utils-CFT33LTP.js → config-utils-XSHOMMK4.js} +3 -3
- package/chunks/{contextCommand-QU4GJWWD.js → contextCommand-HOAF5LUC.js} +9 -10
- package/chunks/{create-sub-session-WNX2OQVX.js → create-sub-session-EAB2U5XW.js} +1 -1
- package/chunks/{create-sub-session-TCLIYVGR.js → create-sub-session-WJOWBFEP.js} +7 -8
- package/chunks/{cron-create-36TIFKRT.js → cron-create-GMVKSXZT.js} +1 -1
- package/chunks/{cron-delete-OQD3WULU.js → cron-delete-VDZKUAVK.js} +1 -1
- package/chunks/{cron-list-4533T2FH.js → cron-list-UG7C7RAR.js} +1 -1
- package/chunks/{daemon-CEZ27OH4.js → daemon-MI5HF26N.js} +343 -40
- package/chunks/{daemon-status-provider-SIBFMLBQ.js → daemon-status-provider-KFL3LRTP.js} +14 -15
- package/chunks/{dist-JZE7F6YR.js → dist-IEXZC5ZR.js} +1 -1
- package/chunks/{dist-ERCPDVA6.js → dist-PQK4GCKA.js} +1 -1
- package/chunks/{dist-3V3ZV6ML.js → dist-U75JK3PF.js} +1 -1
- package/chunks/{dist-PHEPJLXD.js → dist-VXO7QBON.js} +2 -2
- package/chunks/{dist-HNN7DR2L.js → dist-WH4TZSH3.js} +1 -1
- package/chunks/{dist-MOEC47TR.js → dist-X2ABIW5R.js} +1 -1
- package/chunks/{earlyInputCapture-L66BEG2C.js → earlyInputCapture-PKAQJMIP.js} +7 -8
- package/chunks/{edit-Y5LAJSST.js → edit-6TXLYHJ2.js} +7 -8
- package/chunks/{enter-worktree-X5LML7T4.js → enter-worktree-ZTILGPJX.js} +7 -8
- package/chunks/{enterPlanMode-XWF32QOW.js → enterPlanMode-62HRAMCH.js} +7 -8
- package/chunks/{environment-YVMA5F4I.js → environment-OYA3ZVEL.js} +9 -10
- package/chunks/{errors-4Y5SUAOC.js → errors-BFXOUUUR.js} +9 -10
- package/chunks/{exit-worktree-SFHU7XYC.js → exit-worktree-HHJ5M6MV.js} +7 -8
- package/chunks/{exitPlanMode-FP3NAN6E.js → exitPlanMode-M3SWHCL7.js} +7 -8
- package/chunks/{fast-path-ZJNFFCBM.js → fast-path-3VMPLM74.js} +2 -2
- package/chunks/{gemini-5CAB22BI.js → gemini-ZCGVYOJL.js} +100 -42
- package/chunks/{geminiContentGenerator-GE3HPIFB.js → geminiContentGenerator-XBYBAIDQ.js} +1 -1
- package/chunks/{glob-YPACZAEL.js → glob-UOEYQNHN.js} +7 -8
- package/chunks/{grep-IPR4XW37.js → grep-OQM3CH7W.js} +7 -8
- package/chunks/{handleAutoUpdate-P2KEP56V.js → handleAutoUpdate-ORBYVJHZ.js} +11 -12
- package/chunks/{i18n-BRCT66AA.js → i18n-NBP574CO.js} +8 -9
- package/chunks/{initializer-34HXSK3A.js → initializer-CWD7TCCD.js} +12 -13
- package/chunks/{installationInfo-WOTDQMSC.js → installationInfo-CHPDUPWC.js} +8 -9
- package/chunks/{list-XOGP7UPP.js → list-ZTTO362W.js} +15 -16
- package/chunks/{loadedSettingsAdapter-L4YKGU6F.js → loadedSettingsAdapter-V4ZTRKUO.js} +12 -13
- package/chunks/{loop-wakeup-MVUKDVCP.js → loop-wakeup-C4EXLYVA.js} +2 -2
- package/chunks/{ls-RXNIAQDE.js → ls-2OUTG3IZ.js} +1 -1
- package/chunks/{lsp-HB5LM5IL.js → lsp-4VMNXZW6.js} +1 -1
- package/chunks/{mcp-HH7DIP7Y.js → mcp-UH5PSGIK.js} +12 -13
- package/chunks/{monitor-25DEBT6U.js → monitor-F4NYEU5J.js} +7 -8
- package/chunks/{nonInteractiveCli-CT3VSSLK.js → nonInteractiveCli-7IHPXVNH.js} +43 -44
- package/chunks/{notebook-edit-TAVXSWQ6.js → notebook-edit-HPDXNGQH.js} +7 -8
- package/chunks/{openaiContentGenerator-6I7DKUAN.js → openaiContentGenerator-OBDNNOE2.js} +4 -4
- package/chunks/{pidfile-SIQGCFLZ.js → pidfile-AUMGYIOK.js} +7 -8
- package/chunks/{qwenContentGenerator-5DDIXI2N.js → qwenContentGenerator-LH2U2YRG.js} +8 -9
- package/chunks/{read-file-WDOAYRFU.js → read-file-HUUFBLEO.js} +3 -3
- package/chunks/{read-mcp-resource-6LRTZKXI.js → read-mcp-resource-JUFHCKSC.js} +1 -1
- package/chunks/{record-artifact-PMDTY4OS.js → record-artifact-DWSMBXYY.js} +1 -1
- package/chunks/{ripGrep-MI7PHN3Q.js → ripGrep-C7X33LU6.js} +7 -8
- package/chunks/{run-qwen-serve-PNTYF3RE.js → run-qwen-serve-DGW5AFDJ.js} +229 -80
- package/chunks/{runtime-V3FBMUTG.js → runtime-NYUSLBEZ.js} +16 -17
- package/chunks/{scheduler-4TAUIJBM.js → scheduler-5VKDXULT.js} +7 -8
- package/chunks/{send-message-SR4WSXP2.js → send-message-JZ752NWD.js} +2 -2
- package/chunks/{serve-WB2TR7XM.js → serve-UTAHSFXH.js} +12 -13
- package/chunks/{server-AF6LNFPK.js → server-VUHSYSUV.js} +2751 -774
- package/chunks/{session-BIGK4NRW.js → session-QSGSSNFB.js} +44 -45
- package/chunks/{settings-IXF5FIQO.js → settings-X24RYEED.js} +11 -12
- package/chunks/{shell-5VWJ2PPG.js → shell-4MSI2R4O.js} +9 -8
- package/chunks/{skill-SI667Y3B.js → skill-JR3GTNVZ.js} +3 -3
- package/chunks/{spawnChannel-IKX6DD7J.js → spawnChannel-RFAAO44J.js} +9 -10
- package/chunks/{src-55DZWDAK.js → src-YDBNMIIM.js} +40 -9
- package/chunks/{standalone-update-MHLSFHE6.js → standalone-update-4CY4HKGC.js} +9 -10
- package/chunks/{startInteractiveUI-V2EDWLAX.js → startInteractiveUI-FOD7MMVB.js} +183 -131
- package/chunks/{syntheticOutput-YKFNUQZD.js → syntheticOutput-LELY6HAI.js} +2 -2
- package/chunks/{task-create-NUFOHU53.js → task-create-QY3BIMNM.js} +3 -3
- package/chunks/{task-list-Z2IKQY3S.js → task-list-D2U47WBJ.js} +1 -1
- package/chunks/{task-stop-KQFIVQTJ.js → task-stop-N6E5IYLH.js} +1 -1
- package/chunks/{task-update-HKADXSTD.js → task-update-HJCQILLB.js} +3 -3
- package/chunks/{team-create-IZAMLBJS.js → team-create-ZLQSU2E3.js} +7 -8
- package/chunks/{team-delete-SDNTGZLG.js → team-delete-RCWYDRF4.js} +1 -1
- package/chunks/{team-plan-approval-3R4RCKCP.js → team-plan-approval-YUAYBYLS.js} +7 -8
- package/chunks/{theme-manager-LCOXZ5DO.js → theme-manager-SKNEBMOG.js} +7 -8
- package/chunks/{todoWrite-YNXB27GB.js → todoWrite-FKCGATPJ.js} +1 -1
- package/chunks/{tool-search-NYNCD5LF.js → tool-search-V5J3TGWY.js} +4 -4
- package/chunks/{total-session-admission-PJWGOMQE.js → total-session-admission-2WIH72I3.js} +10 -11
- package/chunks/{trustedFolders-DOJDHNYP.js → trustedFolders-JF265CRI.js} +8 -9
- package/chunks/types-ML3TRJQ5.js +16 -0
- package/chunks/{updateCheck-ZXL66KIG.js → updateCheck-AMXNPBP6.js} +9 -10
- package/chunks/{validateNonInterActiveAuth-JSNJZCYY.js → validateNonInterActiveAuth-6YHQO7PD.js} +39 -40
- package/chunks/{version-3NLBENG5.js → version-UEJJK7PE.js} +1 -1
- package/chunks/{web-fetch-ZSPR3PRR.js → web-fetch-QGODBB6S.js} +2 -2
- package/chunks/{workflow-BQYLF7XP.js → workflow-5YFA5V4M.js} +8 -9
- package/chunks/{workspace-providers-status-FA2DGEZW.js → workspace-providers-status-LMBLBNR7.js} +15 -16
- package/chunks/{workspace-registration-store-TARTAGMI.js → workspace-registration-store-VV2S4MSV.js} +1 -1
- package/chunks/{workspace-registry-AGE7WAXX.js → workspace-registry-BVMPYRLN.js} +10 -11
- package/chunks/{workspace-service-ERC5VR2B.js → workspace-service-PUYIZ3J4.js} +22 -19
- package/chunks/{workspace-skills-status-FKYWGNCN.js → workspace-skills-status-CJ7J2KPP.js} +14 -15
- package/chunks/{write-file-S47KSK7Z.js → write-file-OV77M6X7.js} +7 -8
- package/cli.js +11 -11
- package/package.json +3 -3
- package/web-shell/assets/{arc-Zj4EkBUY.js → arc-CObt_TK9.js} +1 -1
- package/web-shell/assets/{architectureDiagram-3BPJPVTR-DAk1Ua3Y.js → architectureDiagram-3BPJPVTR-B7aiJ6CK.js} +1 -1
- package/web-shell/assets/{blockDiagram-GPEHLZMM-DBNqUthM.js → blockDiagram-GPEHLZMM-BSK2CrID.js} +1 -1
- package/web-shell/assets/{c4Diagram-AAUBKEIU-Dq5QGJ53.js → c4Diagram-AAUBKEIU-Bhx2p7T_.js} +1 -1
- package/web-shell/assets/channel-BBaLq8MV.js +1 -0
- package/web-shell/assets/{chunk-2J33WTMH-DY0AeGru.js → chunk-2J33WTMH-c3Hzv0Ni.js} +1 -1
- package/web-shell/assets/{chunk-4BX2VUAB-COX3OyIi.js → chunk-4BX2VUAB-DNXaK7ZF.js} +1 -1
- package/web-shell/assets/{chunk-55IACEB6-Csoyddbh.js → chunk-55IACEB6-BXjvDj_i.js} +1 -1
- package/web-shell/assets/{chunk-727SXJPM-BJPONdxX.js → chunk-727SXJPM-B6CaKFp-.js} +1 -1
- package/web-shell/assets/{chunk-AQP2D5EJ-syLVH6fg.js → chunk-AQP2D5EJ-tij6Mz9B.js} +1 -1
- package/web-shell/assets/{chunk-FMBD7UC4-Cfm59Nhw.js → chunk-FMBD7UC4-DdlsGk2h.js} +1 -1
- package/web-shell/assets/{chunk-ND2GUHAM-DZf6tAqS.js → chunk-ND2GUHAM-CdgGZSHE.js} +1 -1
- package/web-shell/assets/{chunk-QZHKN3VN-DKBp_v5q.js → chunk-QZHKN3VN-D8oJn1C0.js} +1 -1
- package/web-shell/assets/classDiagram-4FO5ZUOK-B2ZK6sWE.js +1 -0
- package/web-shell/assets/classDiagram-v2-Q7XG4LA2-B2ZK6sWE.js +1 -0
- package/web-shell/assets/{cose-bilkent-S5V4N54A-DUU3mMQ5.js → cose-bilkent-S5V4N54A-BUVmNp1Y.js} +1 -1
- package/web-shell/assets/{dagre-BM42HDAG-BUgNfbh-.js → dagre-BM42HDAG-CO4q310O.js} +1 -1
- package/web-shell/assets/{diagram-2AECGRRQ-DT7MVfAg.js → diagram-2AECGRRQ-C6gFvWkg.js} +1 -1
- package/web-shell/assets/{diagram-5GNKFQAL-Cv4YG-ec.js → diagram-5GNKFQAL-NG2I_c0h.js} +1 -1
- package/web-shell/assets/{diagram-KO2AKTUF-BAe9LLkn.js → diagram-KO2AKTUF-DFKMS6I9.js} +1 -1
- package/web-shell/assets/{diagram-LMA3HP47-DW8wqTIP.js → diagram-LMA3HP47-BiElfsfP.js} +1 -1
- package/web-shell/assets/{diagram-OG6HWLK6-CmkuWbaK.js → diagram-OG6HWLK6-zLWDYUqu.js} +1 -1
- package/web-shell/assets/{erDiagram-TEJ5UH35-DwvdHjZ9.js → erDiagram-TEJ5UH35-DoVaazhK.js} +1 -1
- package/web-shell/assets/{flowDiagram-I6XJVG4X-CSsXrhW_.js → flowDiagram-I6XJVG4X-0RLz-mXg.js} +1 -1
- package/web-shell/assets/{ganttDiagram-6RSMTGT7-BtuznCdi.js → ganttDiagram-6RSMTGT7-C-eX1A39.js} +1 -1
- package/web-shell/assets/{gitGraphDiagram-PVQCEYII-DGmAm06g.js → gitGraphDiagram-PVQCEYII-BauQu1Bu.js} +1 -1
- package/web-shell/assets/index-3uAT3ehO.css +5 -0
- package/web-shell/assets/{index-BEtz1cvN.js → index-CVHa2QH7.js} +1 -1
- package/web-shell/assets/index-D1KigWat.js +1128 -0
- package/web-shell/assets/{infoDiagram-5YYISTIA-OAiZ1q0K.js → infoDiagram-5YYISTIA-C800cFvZ.js} +1 -1
- package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-DyK0KTIT.js → ishikawaDiagram-YF4QCWOH-CFK1Jk6e.js} +1 -1
- package/web-shell/assets/{journeyDiagram-JHISSGLW-DzYJlwfO.js → journeyDiagram-JHISSGLW-Cjx0WAC4.js} +1 -1
- package/web-shell/assets/{kanban-definition-UN3LZRKU-CU_tcDwi.js → kanban-definition-UN3LZRKU-DWMDHbiv.js} +1 -1
- package/web-shell/assets/{linear-DvZXjF17.js → linear-vpW4FrZs.js} +1 -1
- package/web-shell/assets/{mermaid.core-_ZFEQlOl.js → mermaid.core-DsHRUnDd.js} +5 -5
- package/web-shell/assets/{mindmap-definition-RKZ34NQL-Or4p64HB.js → mindmap-definition-RKZ34NQL-D3Eb1cAK.js} +1 -1
- package/web-shell/assets/{pieDiagram-4H26LBE5-lXE-zZ6b.js → pieDiagram-4H26LBE5-Cr2-6sWt.js} +1 -1
- package/web-shell/assets/{quadrantDiagram-W4KKPZXB-DUXb9mUV.js → quadrantDiagram-W4KKPZXB-Yd7yUgVp.js} +1 -1
- package/web-shell/assets/{requirementDiagram-4Y6WPE33-D_10uHfo.js → requirementDiagram-4Y6WPE33-BE6SprL8.js} +1 -1
- package/web-shell/assets/{sankeyDiagram-5OEKKPKP-E8c0fPqe.js → sankeyDiagram-5OEKKPKP-DzkuMK8d.js} +1 -1
- package/web-shell/assets/{sequenceDiagram-3UESZ5HK-CJMtFttK.js → sequenceDiagram-3UESZ5HK-wD9A3Dhi.js} +1 -1
- package/web-shell/assets/{stateDiagram-AJRCARHV-DhbviRbB.js → stateDiagram-AJRCARHV-BOp5q1Nm.js} +1 -1
- package/web-shell/assets/stateDiagram-v2-BHNVJYJU-BDsWUmPG.js +1 -0
- package/web-shell/assets/{timeline-definition-PNZ67QCA-CM5yMkFK.js → timeline-definition-PNZ67QCA-Btc08sUA.js} +1 -1
- package/web-shell/assets/{vennDiagram-CIIHVFJN-BCi-zWNN.js → vennDiagram-CIIHVFJN-Bv6upsOA.js} +1 -1
- package/web-shell/assets/{wardley-L42UT6IY-BJ_yJNjJ.js → wardley-L42UT6IY-D-XamnCK.js} +1 -1
- package/web-shell/assets/{wardleyDiagram-YWT4CUSO-TjtLk-qN.js → wardleyDiagram-YWT4CUSO-BbP_5Wfp.js} +1 -1
- package/web-shell/assets/{xychartDiagram-2RQKCTM6-D1TjyoQ3.js → xychartDiagram-2RQKCTM6-CO5Hh28b.js} +1 -1
- package/web-shell/index.html +2 -2
- package/chunks/chunk-CU3L64TP.js +0 -93
- package/chunks/types-JNKGKUJT.js +0 -12
- package/web-shell/assets/channel-DWfYijFS.js +0 -1
- package/web-shell/assets/classDiagram-4FO5ZUOK-DDht-78J.js +0 -1
- package/web-shell/assets/classDiagram-v2-Q7XG4LA2-DDht-78J.js +0 -1
- package/web-shell/assets/index-B9Xwf4OS.css +0 -5
- package/web-shell/assets/index-BUkM4ZXX.js +0 -1088
- package/web-shell/assets/stateDiagram-v2-BHNVJYJU-CntljfwW.js +0 -1
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
openQwenAsrRealtimeStream,
|
|
5
5
|
openVoiceStream,
|
|
6
6
|
openVoiceStreamWithRetry
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-QUJOI7SR.js";
|
|
8
8
|
import {
|
|
9
9
|
MAX_REMEMBER_CONTENT_BYTES,
|
|
10
10
|
createWorkspaceMemoryExtractionErrorLogger,
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
shouldSuppressRememberErrorDetails,
|
|
17
17
|
workspaceMemoryFailureCode,
|
|
18
18
|
workspaceMemoryFailureDiagnostics
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-HGG2RRXD.js";
|
|
20
20
|
import {
|
|
21
21
|
SetupGithubError,
|
|
22
22
|
collectSessionData,
|
|
@@ -27,9 +27,9 @@ import {
|
|
|
27
27
|
toJson,
|
|
28
28
|
toJsonl,
|
|
29
29
|
toMarkdown
|
|
30
|
-
} from "./chunk-
|
|
31
|
-
import "./chunk-
|
|
32
|
-
import "./chunk-
|
|
30
|
+
} from "./chunk-BXT6PQ73.js";
|
|
31
|
+
import "./chunk-2FMQQXMM.js";
|
|
32
|
+
import "./chunk-QE2WJ7FS.js";
|
|
33
33
|
import "./chunk-QHTIBUWB.js";
|
|
34
34
|
import "./chunk-L573Z2QZ.js";
|
|
35
35
|
import {
|
|
@@ -39,13 +39,17 @@ import {
|
|
|
39
39
|
ClientMcpSenderRegistry,
|
|
40
40
|
DeviceFlowRegistry,
|
|
41
41
|
MAX_CLIENT_ID_LENGTH,
|
|
42
|
+
MAX_CONCURRENT_VOICE_SESSIONS,
|
|
42
43
|
MAX_SERVER_NAME_LENGTH,
|
|
44
|
+
MAX_SKILL_NAME_LENGTH,
|
|
43
45
|
MAX_TOOL_NAME_LENGTH,
|
|
44
46
|
PathMutexRegistry,
|
|
45
47
|
QWEN_CDP_MCP_COMMAND_ENV,
|
|
46
48
|
TooManyActiveDeviceFlowsError,
|
|
47
49
|
UnsupportedDeviceFlowProviderError,
|
|
48
50
|
UpstreamDeviceFlowError,
|
|
51
|
+
VoiceLeaseAbortError,
|
|
52
|
+
WorkspaceVoiceCoordinator,
|
|
49
53
|
allowOriginCors,
|
|
50
54
|
bearerAuth,
|
|
51
55
|
brandSecret,
|
|
@@ -81,29 +85,29 @@ import {
|
|
|
81
85
|
setRateLimiter,
|
|
82
86
|
unsafeRevealSecret,
|
|
83
87
|
validateMcpRuntimeServerName
|
|
84
|
-
} from "./chunk-
|
|
88
|
+
} from "./chunk-MO7O5722.js";
|
|
85
89
|
import {
|
|
86
90
|
isLoopbackBind
|
|
87
91
|
} from "./chunk-RXFQM6FQ.js";
|
|
88
92
|
import {
|
|
89
93
|
createDaemonStatusProvider
|
|
90
|
-
} from "./chunk-
|
|
91
|
-
import "./chunk-
|
|
94
|
+
} from "./chunk-X6ODDESK.js";
|
|
95
|
+
import "./chunk-JGO6RFI2.js";
|
|
92
96
|
import "./chunk-E5Z2AVNV.js";
|
|
93
97
|
import {
|
|
94
98
|
createWorkspaceProvidersStatusProvider
|
|
95
|
-
} from "./chunk-
|
|
99
|
+
} from "./chunk-ADLYZJYZ.js";
|
|
96
100
|
import {
|
|
97
101
|
getAuthTypeFromEnv,
|
|
98
102
|
resolveCliGenerationConfig
|
|
99
|
-
} from "./chunk-
|
|
103
|
+
} from "./chunk-ERTHKDKN.js";
|
|
100
104
|
import {
|
|
101
105
|
snapshotProcessEnv
|
|
102
|
-
} from "./chunk-
|
|
106
|
+
} from "./chunk-6JPDCEDB.js";
|
|
103
107
|
import {
|
|
104
108
|
createWorkspaceSkillsStatusProvider
|
|
105
|
-
} from "./chunk-
|
|
106
|
-
import "./chunk-
|
|
109
|
+
} from "./chunk-KF5MMGG4.js";
|
|
110
|
+
import "./chunk-2W3OOD4W.js";
|
|
107
111
|
import {
|
|
108
112
|
EMPTY_WORKSPACE_VOICE_UPDATE_ERROR,
|
|
109
113
|
WorkspaceVoiceError,
|
|
@@ -114,14 +118,14 @@ import {
|
|
|
114
118
|
transcribeWorkspaceVoiceAudio,
|
|
115
119
|
validateWorkspaceVoiceState,
|
|
116
120
|
voiceSettingsScopeToWire
|
|
117
|
-
} from "./chunk-
|
|
121
|
+
} from "./chunk-LHEOLOXU.js";
|
|
118
122
|
import {
|
|
119
123
|
PermissionRulesValidationError,
|
|
120
124
|
buildPermissionSettings,
|
|
121
125
|
isPermissionRuleType,
|
|
122
126
|
normalizePermissionRules,
|
|
123
127
|
readPermissionRuleSet
|
|
124
|
-
} from "./chunk-
|
|
128
|
+
} from "./chunk-JW7OOJC6.js";
|
|
125
129
|
import {
|
|
126
130
|
assertVoiceBaseUrlNetworkAllowed,
|
|
127
131
|
isStreamingVoiceModel,
|
|
@@ -129,17 +133,19 @@ import {
|
|
|
129
133
|
resolveVoiceTranscriptionConfig,
|
|
130
134
|
sanitizeVoiceErrorMessage,
|
|
131
135
|
transcribeVoiceAudio
|
|
132
|
-
} from "./chunk-
|
|
136
|
+
} from "./chunk-EJ3KGYWL.js";
|
|
133
137
|
import {
|
|
134
138
|
isVoiceEnabled,
|
|
135
139
|
isVoiceMode,
|
|
136
140
|
readVoiceModel
|
|
137
|
-
} from "./chunk-
|
|
141
|
+
} from "./chunk-GN5HJJBO.js";
|
|
138
142
|
import "./chunk-FSA7ERJ2.js";
|
|
139
143
|
import {
|
|
140
144
|
WorkspacePermissionRulesSessionRequiredError,
|
|
141
|
-
WorkspaceSettingsPartialPersistError
|
|
142
|
-
|
|
145
|
+
WorkspaceSettingsPartialPersistError,
|
|
146
|
+
WorkspaceSkillNotFoundError,
|
|
147
|
+
WorkspaceSkillNotToggleableError
|
|
148
|
+
} from "./chunk-OA4JVLSA.js";
|
|
143
149
|
import {
|
|
144
150
|
createTotalSessionAdmissionController
|
|
145
151
|
} from "./chunk-7R4DPSRA.js";
|
|
@@ -152,7 +158,7 @@ import {
|
|
|
152
158
|
SessionArtifactAuthorizationError,
|
|
153
159
|
SessionArtifactValidationError,
|
|
154
160
|
createAcpSessionBridge
|
|
155
|
-
} from "./chunk-
|
|
161
|
+
} from "./chunk-HCJTURZD.js";
|
|
156
162
|
import "./chunk-PZL23GTM.js";
|
|
157
163
|
import {
|
|
158
164
|
EVENT_SCHEMA_VERSION,
|
|
@@ -194,27 +200,27 @@ import {
|
|
|
194
200
|
import "./chunk-WBL3FJEU.js";
|
|
195
201
|
import {
|
|
196
202
|
loadChannelsConfig
|
|
197
|
-
} from "./chunk-
|
|
198
|
-
import "./chunk-
|
|
203
|
+
} from "./chunk-FEOBU3FA.js";
|
|
204
|
+
import "./chunk-HRUGYOJN.js";
|
|
199
205
|
import "./chunk-57OAHC2Y.js";
|
|
200
206
|
import {
|
|
201
207
|
parseChannelWebhookConfigLenient
|
|
202
|
-
} from "./chunk-
|
|
203
|
-
import "./chunk-
|
|
208
|
+
} from "./chunk-ZP4ZZJJG.js";
|
|
209
|
+
import "./chunk-4QZADKJS.js";
|
|
204
210
|
import "./chunk-V6WHFGST.js";
|
|
205
|
-
import "./chunk-
|
|
211
|
+
import "./chunk-VEETSXYX.js";
|
|
206
212
|
import "./chunk-TEGEBB2I.js";
|
|
207
213
|
import {
|
|
208
214
|
STATUS_SCHEMA_VERSION,
|
|
209
215
|
createIdleWorkspaceMemoryStatus,
|
|
210
216
|
mapDomainErrorToErrorKind
|
|
211
|
-
} from "./chunk-
|
|
217
|
+
} from "./chunk-FI2WSOWX.js";
|
|
212
218
|
import {
|
|
213
219
|
MAX_REGISTERED_WORKSPACES,
|
|
214
220
|
WorkspaceRegistrationStoreCommittedError,
|
|
215
221
|
WorkspaceRegistrationStoreLimitError,
|
|
216
222
|
workspaceRegistrationId
|
|
217
|
-
} from "./chunk-
|
|
223
|
+
} from "./chunk-WMC6HH7Y.js";
|
|
218
224
|
import {
|
|
219
225
|
MAX_WORKSPACE_PATH_LENGTH,
|
|
220
226
|
canonicalizeWorkspace,
|
|
@@ -234,7 +240,7 @@ import {
|
|
|
234
240
|
isChannelWebhookEnqueueError
|
|
235
241
|
} from "./chunk-OJAMDVF5.js";
|
|
236
242
|
import "./chunk-T2NCM2ET.js";
|
|
237
|
-
import "./chunk-
|
|
243
|
+
import "./chunk-AU4R7ACM.js";
|
|
238
244
|
import "./chunk-UDXSXLER.js";
|
|
239
245
|
import {
|
|
240
246
|
getDialogSettingKeys,
|
|
@@ -245,18 +251,18 @@ import {
|
|
|
245
251
|
getWritableScopes,
|
|
246
252
|
loadSettings,
|
|
247
253
|
validateSettingValue
|
|
248
|
-
} from "./chunk-
|
|
254
|
+
} from "./chunk-7IDCQ35Q.js";
|
|
249
255
|
import "./chunk-RKUWKYED.js";
|
|
250
256
|
import {
|
|
251
257
|
SUPPORTED_LANGUAGES
|
|
252
|
-
} from "./chunk-
|
|
253
|
-
import "./chunk-
|
|
258
|
+
} from "./chunk-AAUZBE5Y.js";
|
|
259
|
+
import "./chunk-A7W4GQRW.js";
|
|
254
260
|
import "./chunk-GOXKNSDZ.js";
|
|
255
261
|
import "./chunk-IDS7MSUP.js";
|
|
256
262
|
import "./chunk-QRUIS3DN.js";
|
|
257
263
|
import {
|
|
258
264
|
getWorkspaceTrustStatus
|
|
259
|
-
} from "./chunk-
|
|
265
|
+
} from "./chunk-HJCVNZLQ.js";
|
|
260
266
|
import "./chunk-ALGGS7UH.js";
|
|
261
267
|
import {
|
|
262
268
|
isWithinRoot as isWithinRoot2
|
|
@@ -264,7 +270,7 @@ import {
|
|
|
264
270
|
import {
|
|
265
271
|
writeStderrLine
|
|
266
272
|
} from "./chunk-YUZI3WAC.js";
|
|
267
|
-
import "./chunk-
|
|
273
|
+
import "./chunk-2J4ZWME4.js";
|
|
268
274
|
import "./chunk-VJNBRRZG.js";
|
|
269
275
|
import {
|
|
270
276
|
ALL_PROVIDERS,
|
|
@@ -295,7 +301,6 @@ import {
|
|
|
295
301
|
WorkspaceMemoryFileTooLargeError,
|
|
296
302
|
WorkspaceMemoryWriteTimeoutError,
|
|
297
303
|
buildUsageDashboard,
|
|
298
|
-
checkForExtensionUpdate,
|
|
299
304
|
detectLineEnding,
|
|
300
305
|
encodeTextFileContent,
|
|
301
306
|
glob,
|
|
@@ -306,22 +311,22 @@ import {
|
|
|
306
311
|
resolveBranchName,
|
|
307
312
|
resolveProviderProtocol,
|
|
308
313
|
shouldShowStep,
|
|
314
|
+
stripAnsiAndControl,
|
|
309
315
|
stripTerminalControlSequences,
|
|
310
316
|
watchRepoBranch,
|
|
311
317
|
writeWorkspaceContextFile
|
|
312
|
-
} from "./chunk-
|
|
313
|
-
import "./chunk-CU3L64TP.js";
|
|
318
|
+
} from "./chunk-HJUDVNCO.js";
|
|
314
319
|
import "./chunk-H6XPXXMH.js";
|
|
315
320
|
import "./chunk-5M6IDOMF.js";
|
|
316
321
|
import {
|
|
317
322
|
MAX_JOBS,
|
|
318
323
|
nextDurableFireMs
|
|
319
|
-
} from "./chunk-
|
|
324
|
+
} from "./chunk-BLPMRN4B.js";
|
|
320
325
|
import "./chunk-RCEEXIOO.js";
|
|
321
326
|
import "./chunk-KKRTYONM.js";
|
|
322
327
|
import "./chunk-TV6K2FB4.js";
|
|
323
328
|
import "./chunk-E2UVIFI2.js";
|
|
324
|
-
import "./chunk-
|
|
329
|
+
import "./chunk-7STBROEV.js";
|
|
325
330
|
import {
|
|
326
331
|
nextFireTime,
|
|
327
332
|
parseCron
|
|
@@ -342,7 +347,7 @@ import {
|
|
|
342
347
|
APPROVAL_MODES,
|
|
343
348
|
SubagentError,
|
|
344
349
|
SubagentErrorCode
|
|
345
|
-
} from "./chunk-
|
|
350
|
+
} from "./chunk-F333XEAU.js";
|
|
346
351
|
import "./chunk-6AM5D4ME.js";
|
|
347
352
|
import "./chunk-2Z5UX4KI.js";
|
|
348
353
|
import "./chunk-ARKANCNX.js";
|
|
@@ -361,15 +366,15 @@ import {
|
|
|
361
366
|
recordDaemonHttpRequest,
|
|
362
367
|
recordDaemonHttpResponse,
|
|
363
368
|
withDaemonRequestSpan
|
|
364
|
-
} from "./chunk-
|
|
369
|
+
} from "./chunk-HWTL6MPR.js";
|
|
365
370
|
import "./chunk-4WKAA4KS.js";
|
|
366
371
|
import "./chunk-CR3C7WXL.js";
|
|
367
372
|
import "./chunk-CARU2RR2.js";
|
|
368
373
|
import "./chunk-VGC4I5JJ.js";
|
|
369
374
|
import "./chunk-FBU7WRZI.js";
|
|
370
375
|
import "./chunk-Y66VOBRP.js";
|
|
371
|
-
import "./chunk-
|
|
372
|
-
import "./chunk-
|
|
376
|
+
import "./chunk-WTNZ6DXB.js";
|
|
377
|
+
import "./chunk-JGUT3LWZ.js";
|
|
373
378
|
import {
|
|
374
379
|
QwenOAuth2Client,
|
|
375
380
|
QwenOAuthPollError,
|
|
@@ -3251,7 +3256,8 @@ async function persistVoiceUpdate(deps, settings, update, clientId, workspaceTru
|
|
|
3251
3256
|
throw new Error("workspace voice settings persistence is not available");
|
|
3252
3257
|
}
|
|
3253
3258
|
const writes = buildWorkspaceVoiceSettingsWrites(settings, update, {
|
|
3254
|
-
workspaceTrusted
|
|
3259
|
+
workspaceTrusted,
|
|
3260
|
+
...deps.scopeOverride ? { scopeOverride: deps.scopeOverride } : {}
|
|
3255
3261
|
});
|
|
3256
3262
|
if (deps.persistSettings) {
|
|
3257
3263
|
try {
|
|
@@ -3281,6 +3287,9 @@ async function persistVoiceUpdate(deps, settings, update, clientId, workspaceTru
|
|
|
3281
3287
|
writeStderrLine(
|
|
3282
3288
|
`qwen serve: POST /workspace/voice partial persist error (workspace=${deps.boundWorkspace}, committed=${committed.length}/${writes.length}, failedKey=${write.key}, failedScope=${voiceSettingsScopeToWire(write.scope)}): ${err instanceof Error ? err.message : String(err)}`
|
|
3283
3289
|
);
|
|
3290
|
+
for (const committedWrite of committed) {
|
|
3291
|
+
broadcastVoiceWrite(deps, committedWrite, clientId);
|
|
3292
|
+
}
|
|
3284
3293
|
throw new WorkspaceSettingsPartialPersistError(
|
|
3285
3294
|
`Voice settings partial persist failed: committed=${committed.length}/${writes.length}`,
|
|
3286
3295
|
committed,
|
|
@@ -3309,6 +3318,75 @@ function requestAbortSignal2(req, res) {
|
|
|
3309
3318
|
return controller.signal;
|
|
3310
3319
|
}
|
|
3311
3320
|
__name(requestAbortSignal2, "requestAbortSignal");
|
|
3321
|
+
function loadVoiceSettings(deps) {
|
|
3322
|
+
return loadSettings(
|
|
3323
|
+
deps.boundWorkspace,
|
|
3324
|
+
deps.env ? { skipLoadEnvironment: true } : true
|
|
3325
|
+
);
|
|
3326
|
+
}
|
|
3327
|
+
__name(loadVoiceSettings, "loadVoiceSettings");
|
|
3328
|
+
function admissionState(req) {
|
|
3329
|
+
return req.voiceAdmissionState;
|
|
3330
|
+
}
|
|
3331
|
+
__name(admissionState, "admissionState");
|
|
3332
|
+
function admissionLease(req) {
|
|
3333
|
+
return admissionState(req)?.lease;
|
|
3334
|
+
}
|
|
3335
|
+
__name(admissionLease, "admissionLease");
|
|
3336
|
+
function installAdmissionLease(req, res, deps) {
|
|
3337
|
+
const result = deps.acquireVoiceLease?.();
|
|
3338
|
+
if (!result) return true;
|
|
3339
|
+
if (result.kind === "rejected") {
|
|
3340
|
+
if (result.reason === "draining") {
|
|
3341
|
+
sendWorkspaceDraining(res);
|
|
3342
|
+
} else {
|
|
3343
|
+
res.set("Retry-After", "5").status(503).json({
|
|
3344
|
+
error: "Too many voice sessions in progress; try again shortly.",
|
|
3345
|
+
code: "voice_capacity_exceeded"
|
|
3346
|
+
});
|
|
3347
|
+
}
|
|
3348
|
+
return false;
|
|
3349
|
+
}
|
|
3350
|
+
let released = false;
|
|
3351
|
+
const release = /* @__PURE__ */ __name(() => {
|
|
3352
|
+
if (released) return;
|
|
3353
|
+
released = true;
|
|
3354
|
+
result.lease.release();
|
|
3355
|
+
}, "release");
|
|
3356
|
+
const state = {
|
|
3357
|
+
lease: result.lease,
|
|
3358
|
+
operationStarted: false,
|
|
3359
|
+
release
|
|
3360
|
+
};
|
|
3361
|
+
req.voiceAdmissionState = state;
|
|
3362
|
+
const releaseBeforeOperation = /* @__PURE__ */ __name(() => {
|
|
3363
|
+
if (!state.operationStarted) release();
|
|
3364
|
+
}, "releaseBeforeOperation");
|
|
3365
|
+
res.once("finish", releaseBeforeOperation);
|
|
3366
|
+
res.once("close", releaseBeforeOperation);
|
|
3367
|
+
return true;
|
|
3368
|
+
}
|
|
3369
|
+
__name(installAdmissionLease, "installAdmissionLease");
|
|
3370
|
+
function beginVoiceOperation(req) {
|
|
3371
|
+
const state = admissionState(req);
|
|
3372
|
+
if (!state) return;
|
|
3373
|
+
state.operationStarted = true;
|
|
3374
|
+
return state.release;
|
|
3375
|
+
}
|
|
3376
|
+
__name(beginVoiceOperation, "beginVoiceOperation");
|
|
3377
|
+
function combinedAbortSignal(req, res) {
|
|
3378
|
+
const requestSignal = requestAbortSignal2(req, res);
|
|
3379
|
+
const leaseSignal = admissionLease(req)?.signal;
|
|
3380
|
+
return leaseSignal ? AbortSignal.any([requestSignal, leaseSignal]) : requestSignal;
|
|
3381
|
+
}
|
|
3382
|
+
__name(combinedAbortSignal, "combinedAbortSignal");
|
|
3383
|
+
function sendWorkspaceDraining(res) {
|
|
3384
|
+
res.set("Retry-After", "5").status(503).json({
|
|
3385
|
+
error: "Workspace runtime is being removed",
|
|
3386
|
+
code: "workspace_draining"
|
|
3387
|
+
});
|
|
3388
|
+
}
|
|
3389
|
+
__name(sendWorkspaceDraining, "sendWorkspaceDraining");
|
|
3312
3390
|
function isSupportedAudioContentType(contentType) {
|
|
3313
3391
|
return typeof contentType === "string" && (contentType.startsWith("audio/") || contentType === "application/octet-stream");
|
|
3314
3392
|
}
|
|
@@ -3324,164 +3402,326 @@ function readBinaryBody(req) {
|
|
|
3324
3402
|
return void 0;
|
|
3325
3403
|
}
|
|
3326
3404
|
__name(readBinaryBody, "readBinaryBody");
|
|
3327
|
-
function
|
|
3328
|
-
|
|
3329
|
-
|
|
3405
|
+
function sendUnsupportedVoiceContentType(res) {
|
|
3406
|
+
res.status(415).json({
|
|
3407
|
+
error: "Content-Type must be audio/* or application/octet-stream for voice transcription",
|
|
3408
|
+
code: "unsupported_voice_content_type"
|
|
3409
|
+
});
|
|
3410
|
+
}
|
|
3411
|
+
__name(sendUnsupportedVoiceContentType, "sendUnsupportedVoiceContentType");
|
|
3412
|
+
function handleVoiceStatus(res, deps, route) {
|
|
3413
|
+
try {
|
|
3414
|
+
res.status(200).json(
|
|
3415
|
+
buildWorkspaceVoiceStatus(deps.boundWorkspace, loadVoiceSettings(deps))
|
|
3416
|
+
);
|
|
3417
|
+
} catch (err) {
|
|
3418
|
+
writeStderrLine(
|
|
3419
|
+
`qwen serve: ${route} error: ${err instanceof Error ? err.message : String(err)}`
|
|
3420
|
+
);
|
|
3421
|
+
res.status(500).json({
|
|
3422
|
+
error: "Failed to load voice settings",
|
|
3423
|
+
code: "internal_error"
|
|
3424
|
+
});
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
__name(handleVoiceStatus, "handleVoiceStatus");
|
|
3428
|
+
async function handleVoiceUpdate(req, res, deps, route) {
|
|
3429
|
+
if (!deps.persistSettings && !deps.persistSetting) {
|
|
3430
|
+
res.status(501).json({
|
|
3431
|
+
error: "Workspace voice settings persistence is not available",
|
|
3432
|
+
code: "not_implemented"
|
|
3433
|
+
});
|
|
3434
|
+
return;
|
|
3435
|
+
}
|
|
3436
|
+
const parsed = parseWorkspaceVoiceUpdateParams(deps.safeBody(req));
|
|
3437
|
+
if ("error" in parsed) {
|
|
3438
|
+
res.status(400).json(parsed);
|
|
3439
|
+
return;
|
|
3440
|
+
}
|
|
3441
|
+
const settings = loadVoiceSettings(deps);
|
|
3442
|
+
try {
|
|
3443
|
+
validateWorkspaceVoiceState(settings, parsed, { env: deps.env });
|
|
3444
|
+
} catch (err) {
|
|
3445
|
+
if (sendVoiceError(res, err)) return;
|
|
3446
|
+
throw err;
|
|
3447
|
+
}
|
|
3448
|
+
const clientId = deps.parseAndValidateClientId(req, res);
|
|
3449
|
+
if (clientId === null) return;
|
|
3450
|
+
try {
|
|
3451
|
+
const workspaceTrusted = getWorkspaceTrustStatus(settings.merged, deps.boundWorkspace).effective.state === "trusted";
|
|
3452
|
+
await persistVoiceUpdate(
|
|
3453
|
+
deps,
|
|
3454
|
+
settings,
|
|
3455
|
+
parsed,
|
|
3456
|
+
clientId,
|
|
3457
|
+
workspaceTrusted
|
|
3458
|
+
);
|
|
3459
|
+
} catch (err) {
|
|
3460
|
+
writeStderrLine(
|
|
3461
|
+
`qwen serve: ${route} persist error (workspace=${deps.boundWorkspace}): ${err instanceof Error ? err.message : String(err)}`
|
|
3462
|
+
);
|
|
3463
|
+
res.status(500).json({
|
|
3464
|
+
error: "Failed to persist voice settings",
|
|
3465
|
+
code: "persist_error"
|
|
3466
|
+
});
|
|
3467
|
+
return;
|
|
3468
|
+
}
|
|
3469
|
+
try {
|
|
3470
|
+
res.status(200).json(
|
|
3471
|
+
buildWorkspaceVoiceStatus(deps.boundWorkspace, loadVoiceSettings(deps))
|
|
3472
|
+
);
|
|
3473
|
+
} catch (err) {
|
|
3474
|
+
writeStderrLine(
|
|
3475
|
+
`qwen serve: ${route} reload error after persist (workspace=${deps.boundWorkspace}): ${err instanceof Error ? err.message : String(err)}`
|
|
3476
|
+
);
|
|
3477
|
+
res.status(500).json({
|
|
3478
|
+
error: "Voice settings persisted but failed to reload",
|
|
3479
|
+
code: "internal_error"
|
|
3480
|
+
});
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
__name(handleVoiceUpdate, "handleVoiceUpdate");
|
|
3484
|
+
async function handleVoiceTranscription(req, res, deps, route) {
|
|
3485
|
+
const contentType = normalizeContentType(req);
|
|
3486
|
+
if (!isSupportedAudioContentType(contentType)) {
|
|
3487
|
+
sendUnsupportedVoiceContentType(res);
|
|
3488
|
+
return;
|
|
3489
|
+
}
|
|
3490
|
+
if (admissionLease(req)?.signal.aborted) {
|
|
3491
|
+
sendWorkspaceDraining(res);
|
|
3492
|
+
return;
|
|
3493
|
+
}
|
|
3494
|
+
const clientId = deps.parseAndValidateClientId(req, res);
|
|
3495
|
+
if (clientId === null) return;
|
|
3496
|
+
const data = readBinaryBody(req);
|
|
3497
|
+
if (!data || data.byteLength === 0) {
|
|
3498
|
+
res.status(400).json({
|
|
3499
|
+
error: "Voice audio body must be non-empty binary data",
|
|
3500
|
+
code: "invalid_voice_audio"
|
|
3501
|
+
});
|
|
3502
|
+
return;
|
|
3503
|
+
}
|
|
3504
|
+
const settings = loadVoiceSettings(deps);
|
|
3505
|
+
if (!isVoiceEnabled(settings)) {
|
|
3506
|
+
res.status(403).json({
|
|
3507
|
+
error: "Voice transcription is disabled for this workspace",
|
|
3508
|
+
code: "voice_disabled"
|
|
3509
|
+
});
|
|
3510
|
+
return;
|
|
3511
|
+
}
|
|
3512
|
+
const queryVoiceModel = req.query["voiceModel"];
|
|
3513
|
+
if (queryVoiceModel !== void 0 && typeof queryVoiceModel !== "string") {
|
|
3514
|
+
res.status(400).json({
|
|
3515
|
+
error: "`voiceModel` query parameter must be a string",
|
|
3516
|
+
code: "invalid_voice_model"
|
|
3517
|
+
});
|
|
3518
|
+
return;
|
|
3519
|
+
}
|
|
3520
|
+
const requestedVoiceModel = typeof queryVoiceModel === "string" ? queryVoiceModel.trim() : "";
|
|
3521
|
+
if (requestedVoiceModel && requestedVoiceModel.length > MAX_VOICE_MODEL_LENGTH) {
|
|
3522
|
+
res.status(400).json({
|
|
3523
|
+
error: `\`voiceModel\` exceeds the ${MAX_VOICE_MODEL_LENGTH}-character limit`,
|
|
3524
|
+
code: "invalid_voice_model"
|
|
3525
|
+
});
|
|
3526
|
+
return;
|
|
3527
|
+
}
|
|
3528
|
+
const voiceModel = requestedVoiceModel || readVoiceModel(settings);
|
|
3529
|
+
if (!voiceModel) {
|
|
3530
|
+
res.status(400).json({
|
|
3531
|
+
error: "A valid voiceModel is required before transcription.",
|
|
3532
|
+
code: "voice_model_required"
|
|
3533
|
+
});
|
|
3534
|
+
return;
|
|
3535
|
+
}
|
|
3536
|
+
try {
|
|
3537
|
+
const releaseOperation = beginVoiceOperation(req);
|
|
3538
|
+
let result;
|
|
3330
3539
|
try {
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
`qwen serve: GET /workspace/voice error: ${err instanceof Error ? err.message : String(err)}`
|
|
3340
|
-
);
|
|
3341
|
-
res.status(500).json({
|
|
3342
|
-
error: "Failed to load voice settings",
|
|
3343
|
-
code: "internal_error"
|
|
3540
|
+
result = await (deps.transcribe ?? transcribeWorkspaceVoiceAudio)({
|
|
3541
|
+
data,
|
|
3542
|
+
mimeType: contentType,
|
|
3543
|
+
voiceModel,
|
|
3544
|
+
settings,
|
|
3545
|
+
workspaceCwd: deps.boundWorkspace,
|
|
3546
|
+
env: deps.env,
|
|
3547
|
+
abortSignal: combinedAbortSignal(req, res)
|
|
3344
3548
|
});
|
|
3549
|
+
} finally {
|
|
3550
|
+
releaseOperation?.();
|
|
3551
|
+
}
|
|
3552
|
+
if (admissionLease(req)?.signal.aborted) {
|
|
3553
|
+
sendWorkspaceDraining(res);
|
|
3554
|
+
return;
|
|
3555
|
+
}
|
|
3556
|
+
res.status(200).json({ v: 1, ...result });
|
|
3557
|
+
} catch (err) {
|
|
3558
|
+
if (admissionLease(req)?.signal.aborted) {
|
|
3559
|
+
sendWorkspaceDraining(res);
|
|
3560
|
+
return;
|
|
3345
3561
|
}
|
|
3562
|
+
if (sendVoiceError(res, err)) return;
|
|
3563
|
+
const message = sanitizeVoiceErrorMessage(
|
|
3564
|
+
err instanceof Error ? err.message : String(err)
|
|
3565
|
+
);
|
|
3566
|
+
writeStderrLine(
|
|
3567
|
+
`qwen serve: ${route} error (workspace=${deps.boundWorkspace}): ${message}`
|
|
3568
|
+
);
|
|
3569
|
+
res.status(502).json({
|
|
3570
|
+
error: "Voice transcription failed",
|
|
3571
|
+
code: "voice_transcription_failed"
|
|
3572
|
+
});
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
__name(handleVoiceTranscription, "handleVoiceTranscription");
|
|
3576
|
+
function admitVoiceTranscription(req, res, deps) {
|
|
3577
|
+
if (!isSupportedAudioContentType(normalizeContentType(req))) {
|
|
3578
|
+
sendUnsupportedVoiceContentType(res);
|
|
3579
|
+
return false;
|
|
3580
|
+
}
|
|
3581
|
+
return installAdmissionLease(req, res, deps);
|
|
3582
|
+
}
|
|
3583
|
+
__name(admitVoiceTranscription, "admitVoiceTranscription");
|
|
3584
|
+
function voiceAudioBodyParser() {
|
|
3585
|
+
const parse = import_express.default.raw({
|
|
3586
|
+
type: /* @__PURE__ */ __name((req) => isSupportedAudioContentType(
|
|
3587
|
+
req.headers["content-type"]?.split(";", 1)[0]?.trim().toLowerCase()
|
|
3588
|
+
), "type"),
|
|
3589
|
+
limit: "10mb"
|
|
3346
3590
|
});
|
|
3591
|
+
return (req, res, next) => {
|
|
3592
|
+
const signal = admissionLease(req)?.signal;
|
|
3593
|
+
let aborted = signal?.aborted === true;
|
|
3594
|
+
const onAbort = /* @__PURE__ */ __name(() => {
|
|
3595
|
+
aborted = true;
|
|
3596
|
+
if (!res.headersSent) sendWorkspaceDraining(res);
|
|
3597
|
+
const destroyRequest = /* @__PURE__ */ __name(() => {
|
|
3598
|
+
if (!req.destroyed) req.destroy();
|
|
3599
|
+
}, "destroyRequest");
|
|
3600
|
+
if (res.writableFinished) destroyRequest();
|
|
3601
|
+
else {
|
|
3602
|
+
res.once("finish", destroyRequest);
|
|
3603
|
+
res.once("close", destroyRequest);
|
|
3604
|
+
}
|
|
3605
|
+
}, "onAbort");
|
|
3606
|
+
if (aborted) {
|
|
3607
|
+
onAbort();
|
|
3608
|
+
return;
|
|
3609
|
+
}
|
|
3610
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
3611
|
+
parse(req, res, (err) => {
|
|
3612
|
+
signal?.removeEventListener("abort", onAbort);
|
|
3613
|
+
if (aborted || signal?.aborted) {
|
|
3614
|
+
if (!res.headersSent) sendWorkspaceDraining(res);
|
|
3615
|
+
return;
|
|
3616
|
+
}
|
|
3617
|
+
next(err);
|
|
3618
|
+
});
|
|
3619
|
+
};
|
|
3620
|
+
}
|
|
3621
|
+
__name(voiceAudioBodyParser, "voiceAudioBodyParser");
|
|
3622
|
+
function registerWorkspaceVoiceRoutes(app, deps) {
|
|
3623
|
+
app.get(
|
|
3624
|
+
"/workspace/voice",
|
|
3625
|
+
(_req, res) => handleVoiceStatus(res, deps, "GET /workspace/voice")
|
|
3626
|
+
);
|
|
3347
3627
|
app.post(
|
|
3348
3628
|
"/workspace/voice",
|
|
3349
3629
|
deps.mutate({ strict: true }),
|
|
3350
3630
|
async (req, res) => {
|
|
3351
|
-
|
|
3352
|
-
res.status(501).json({
|
|
3353
|
-
error: "Workspace voice settings persistence is not available",
|
|
3354
|
-
code: "not_implemented"
|
|
3355
|
-
});
|
|
3356
|
-
return;
|
|
3357
|
-
}
|
|
3358
|
-
const parsed = parseWorkspaceVoiceUpdateParams(deps.safeBody(req));
|
|
3359
|
-
if ("error" in parsed) {
|
|
3360
|
-
res.status(400).json(parsed);
|
|
3361
|
-
return;
|
|
3362
|
-
}
|
|
3363
|
-
const settings = loadSettings(deps.boundWorkspace);
|
|
3364
|
-
try {
|
|
3365
|
-
validateWorkspaceVoiceState(settings, parsed);
|
|
3366
|
-
} catch (err) {
|
|
3367
|
-
if (sendVoiceError(res, err)) return;
|
|
3368
|
-
throw err;
|
|
3369
|
-
}
|
|
3370
|
-
const clientId = deps.parseAndValidateClientId(req, res);
|
|
3371
|
-
if (clientId === null) return;
|
|
3372
|
-
try {
|
|
3373
|
-
const workspaceTrusted = getWorkspaceTrustStatus(settings.merged, deps.boundWorkspace).effective.state === "trusted";
|
|
3374
|
-
await persistVoiceUpdate(
|
|
3375
|
-
deps,
|
|
3376
|
-
settings,
|
|
3377
|
-
parsed,
|
|
3378
|
-
clientId,
|
|
3379
|
-
workspaceTrusted
|
|
3380
|
-
);
|
|
3381
|
-
} catch (err) {
|
|
3382
|
-
writeStderrLine(
|
|
3383
|
-
`qwen serve: POST /workspace/voice persist error (workspace=${deps.boundWorkspace}): ${err instanceof Error ? err.message : String(err)}`
|
|
3384
|
-
);
|
|
3385
|
-
res.status(500).json({
|
|
3386
|
-
error: "Failed to persist voice settings",
|
|
3387
|
-
code: "persist_error"
|
|
3388
|
-
});
|
|
3389
|
-
return;
|
|
3390
|
-
}
|
|
3391
|
-
res.status(200).json(
|
|
3392
|
-
buildWorkspaceVoiceStatus(
|
|
3393
|
-
deps.boundWorkspace,
|
|
3394
|
-
loadSettings(deps.boundWorkspace)
|
|
3395
|
-
)
|
|
3396
|
-
);
|
|
3631
|
+
await handleVoiceUpdate(req, res, deps, "POST /workspace/voice");
|
|
3397
3632
|
}
|
|
3398
3633
|
);
|
|
3399
3634
|
app.post(
|
|
3400
3635
|
"/workspace/voice/transcribe",
|
|
3401
3636
|
deps.mutate({ strict: true }),
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
limit: "10mb"
|
|
3407
|
-
}),
|
|
3637
|
+
(req, res, next) => {
|
|
3638
|
+
if (admitVoiceTranscription(req, res, deps)) next();
|
|
3639
|
+
},
|
|
3640
|
+
voiceAudioBodyParser(),
|
|
3408
3641
|
async (req, res) => {
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
res
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
return;
|
|
3416
|
-
}
|
|
3417
|
-
const audioContentType = contentType;
|
|
3418
|
-
const clientId = deps.parseAndValidateClientId(req, res);
|
|
3419
|
-
if (clientId === null) return;
|
|
3420
|
-
const data = readBinaryBody(req);
|
|
3421
|
-
if (!data || data.byteLength === 0) {
|
|
3422
|
-
res.status(400).json({
|
|
3423
|
-
error: "Voice audio body must be non-empty binary data",
|
|
3424
|
-
code: "invalid_voice_audio"
|
|
3425
|
-
});
|
|
3426
|
-
return;
|
|
3427
|
-
}
|
|
3428
|
-
const settings = loadSettings(deps.boundWorkspace);
|
|
3429
|
-
if (!isVoiceEnabled(settings)) {
|
|
3430
|
-
res.status(403).json({
|
|
3431
|
-
error: "Voice transcription is disabled for this workspace",
|
|
3432
|
-
code: "voice_disabled"
|
|
3433
|
-
});
|
|
3434
|
-
return;
|
|
3435
|
-
}
|
|
3436
|
-
const queryVoiceModel = req.query["voiceModel"];
|
|
3437
|
-
if (queryVoiceModel !== void 0 && typeof queryVoiceModel !== "string") {
|
|
3438
|
-
res.status(400).json({
|
|
3439
|
-
error: "`voiceModel` query parameter must be a string",
|
|
3440
|
-
code: "invalid_voice_model"
|
|
3441
|
-
});
|
|
3442
|
-
return;
|
|
3443
|
-
}
|
|
3444
|
-
const requestedVoiceModel = typeof queryVoiceModel === "string" ? queryVoiceModel.trim() : "";
|
|
3445
|
-
if (requestedVoiceModel && requestedVoiceModel.length > MAX_VOICE_MODEL_LENGTH) {
|
|
3446
|
-
res.status(400).json({
|
|
3447
|
-
error: `\`voiceModel\` exceeds the ${MAX_VOICE_MODEL_LENGTH}-character limit`,
|
|
3448
|
-
code: "invalid_voice_model"
|
|
3449
|
-
});
|
|
3450
|
-
return;
|
|
3451
|
-
}
|
|
3452
|
-
const voiceModel = requestedVoiceModel || readVoiceModel(settings);
|
|
3453
|
-
if (!voiceModel) {
|
|
3454
|
-
res.status(400).json({
|
|
3455
|
-
error: "A valid voiceModel is required before transcription.",
|
|
3456
|
-
code: "voice_model_required"
|
|
3457
|
-
});
|
|
3458
|
-
return;
|
|
3459
|
-
}
|
|
3460
|
-
try {
|
|
3461
|
-
const result = await transcribe({
|
|
3462
|
-
data,
|
|
3463
|
-
mimeType: audioContentType,
|
|
3464
|
-
voiceModel,
|
|
3465
|
-
settings,
|
|
3466
|
-
workspaceCwd: deps.boundWorkspace,
|
|
3467
|
-
abortSignal: requestAbortSignal2(req, res)
|
|
3468
|
-
});
|
|
3469
|
-
res.status(200).json({ v: 1, ...result });
|
|
3470
|
-
} catch (err) {
|
|
3471
|
-
if (sendVoiceError(res, err)) return;
|
|
3472
|
-
const message = err instanceof Error ? sanitizeVoiceErrorMessage(err.message) : sanitizeVoiceErrorMessage(String(err));
|
|
3473
|
-
writeStderrLine(
|
|
3474
|
-
`qwen serve: POST /workspace/voice/transcribe error (workspace=${deps.boundWorkspace}): ${message}`
|
|
3475
|
-
);
|
|
3476
|
-
res.status(502).json({
|
|
3477
|
-
error: "Voice transcription failed",
|
|
3478
|
-
code: "voice_transcription_failed"
|
|
3479
|
-
});
|
|
3480
|
-
}
|
|
3642
|
+
await handleVoiceTranscription(
|
|
3643
|
+
req,
|
|
3644
|
+
res,
|
|
3645
|
+
deps,
|
|
3646
|
+
"POST /workspace/voice/transcribe"
|
|
3647
|
+
);
|
|
3481
3648
|
}
|
|
3482
3649
|
);
|
|
3483
3650
|
}
|
|
3484
3651
|
__name(registerWorkspaceVoiceRoutes, "registerWorkspaceVoiceRoutes");
|
|
3652
|
+
function createRuntimeVoiceDeps(runtime, deps) {
|
|
3653
|
+
const env = runtime.env.mode === "runtime-overlay" ? runtime.env.effectiveEnv ?? {} : runtime.env.effectiveEnv;
|
|
3654
|
+
return {
|
|
3655
|
+
boundWorkspace: runtime.workspaceCwd,
|
|
3656
|
+
mutate: deps.mutate,
|
|
3657
|
+
safeBody: deps.safeBody,
|
|
3658
|
+
persistSetting: deps.persistSetting,
|
|
3659
|
+
persistSettings: deps.persistSettings,
|
|
3660
|
+
transcribe: deps.transcribe,
|
|
3661
|
+
...env ? { env } : {},
|
|
3662
|
+
scopeOverride: "Workspace" /* Workspace */,
|
|
3663
|
+
acquireVoiceLease: /* @__PURE__ */ __name(() => deps.acquireVoiceLease(runtime), "acquireVoiceLease"),
|
|
3664
|
+
broadcastSettingsChanged: /* @__PURE__ */ __name((key, value, scope, clientId) => {
|
|
3665
|
+
if (runtime.primary) deps.invalidateServeFeaturesCache();
|
|
3666
|
+
runtime.bridge.publishWorkspaceEvent({
|
|
3667
|
+
type: "settings_changed",
|
|
3668
|
+
data: { key, value, scope },
|
|
3669
|
+
originatorClientId: clientId
|
|
3670
|
+
});
|
|
3671
|
+
}, "broadcastSettingsChanged"),
|
|
3672
|
+
parseAndValidateClientId: /* @__PURE__ */ __name((req, res) => deps.parseAndValidateClientId(req, res, runtime), "parseAndValidateClientId")
|
|
3673
|
+
};
|
|
3674
|
+
}
|
|
3675
|
+
__name(createRuntimeVoiceDeps, "createRuntimeVoiceDeps");
|
|
3676
|
+
function resolveQualifiedVoiceTarget(req, res, deps, includeDraining = false) {
|
|
3677
|
+
const runtime = includeDraining ? resolveManagedWorkspaceRuntimeFromParam(deps.workspaceRegistry, req, res) : resolveWorkspaceRuntimeFromParam(deps.workspaceRegistry, req, res);
|
|
3678
|
+
if (!runtime || !requireTrustedWorkspaceRuntime(runtime, res)) return;
|
|
3679
|
+
return createRuntimeVoiceDeps(runtime, deps);
|
|
3680
|
+
}
|
|
3681
|
+
__name(resolveQualifiedVoiceTarget, "resolveQualifiedVoiceTarget");
|
|
3682
|
+
function registerWorkspaceQualifiedVoiceRoutes(app, deps) {
|
|
3683
|
+
app.get("/workspaces/:workspace/voice", (req, res) => {
|
|
3684
|
+
const target = resolveQualifiedVoiceTarget(req, res, deps);
|
|
3685
|
+
if (!target) return;
|
|
3686
|
+
handleVoiceStatus(res, target, "GET /workspaces/:workspace/voice");
|
|
3687
|
+
});
|
|
3688
|
+
app.post(
|
|
3689
|
+
"/workspaces/:workspace/voice",
|
|
3690
|
+
deps.mutate({ strict: true }),
|
|
3691
|
+
async (req, res) => {
|
|
3692
|
+
const target = resolveQualifiedVoiceTarget(req, res, deps);
|
|
3693
|
+
if (!target) return;
|
|
3694
|
+
await handleVoiceUpdate(
|
|
3695
|
+
req,
|
|
3696
|
+
res,
|
|
3697
|
+
target,
|
|
3698
|
+
"POST /workspaces/:workspace/voice"
|
|
3699
|
+
);
|
|
3700
|
+
}
|
|
3701
|
+
);
|
|
3702
|
+
app.post(
|
|
3703
|
+
"/workspaces/:workspace/voice/transcribe",
|
|
3704
|
+
deps.mutate({ strict: true }),
|
|
3705
|
+
(req, res, next) => {
|
|
3706
|
+
const target = resolveQualifiedVoiceTarget(req, res, deps, true);
|
|
3707
|
+
if (!target) return;
|
|
3708
|
+
req.voiceRouteDeps = target;
|
|
3709
|
+
if (admitVoiceTranscription(req, res, target)) next();
|
|
3710
|
+
},
|
|
3711
|
+
voiceAudioBodyParser(),
|
|
3712
|
+
async (req, res) => {
|
|
3713
|
+
const target = req.voiceRouteDeps;
|
|
3714
|
+
if (!target) return;
|
|
3715
|
+
await handleVoiceTranscription(
|
|
3716
|
+
req,
|
|
3717
|
+
res,
|
|
3718
|
+
target,
|
|
3719
|
+
"POST /workspaces/:workspace/voice/transcribe"
|
|
3720
|
+
);
|
|
3721
|
+
}
|
|
3722
|
+
);
|
|
3723
|
+
}
|
|
3724
|
+
__name(registerWorkspaceQualifiedVoiceRoutes, "registerWorkspaceQualifiedVoiceRoutes");
|
|
3485
3725
|
|
|
3486
3726
|
// packages/cli/src/serve/workspace-remember.ts
|
|
3487
3727
|
init_esbuild_shims();
|
|
@@ -5757,7 +5997,8 @@ function createDaemonSubagentManager(boundWorkspace, safeMode = false) {
|
|
|
5757
5997
|
getSdkMode: /* @__PURE__ */ __name(() => false, "getSdkMode"),
|
|
5758
5998
|
getProjectRoot: /* @__PURE__ */ __name(() => boundWorkspace, "getProjectRoot"),
|
|
5759
5999
|
getActiveExtensions: /* @__PURE__ */ __name(() => [], "getActiveExtensions"),
|
|
5760
|
-
isSafeMode: /* @__PURE__ */ __name(() => safeMode, "isSafeMode")
|
|
6000
|
+
isSafeMode: /* @__PURE__ */ __name(() => safeMode, "isSafeMode"),
|
|
6001
|
+
getAgentsSettings: /* @__PURE__ */ __name(() => ({}), "getAgentsSettings")
|
|
5761
6002
|
};
|
|
5762
6003
|
const guarded = new Proxy(stub, {
|
|
5763
6004
|
get(target, prop) {
|
|
@@ -11454,8 +11695,9 @@ function isActiveDrainCorrelation(registry, conn, message) {
|
|
|
11454
11695
|
return typeof params?.sessionId === "string" && conn.ownsSession(params.sessionId);
|
|
11455
11696
|
}
|
|
11456
11697
|
__name(isActiveDrainCorrelation, "isActiveDrainCorrelation");
|
|
11457
|
-
var
|
|
11698
|
+
var PLURAL_WS_PREFIX = "/workspaces/";
|
|
11458
11699
|
var PLURAL_ACP_WS_SUFFIX = "/acp";
|
|
11700
|
+
var PLURAL_VOICE_WS_SUFFIX = "/voice/stream";
|
|
11459
11701
|
function rawRequestPathname(reqUrl) {
|
|
11460
11702
|
const target = reqUrl ?? "/";
|
|
11461
11703
|
const qIdx = target.indexOf("?");
|
|
@@ -11466,18 +11708,15 @@ function rawRequestPathname(reqUrl) {
|
|
|
11466
11708
|
return target.slice(0, end);
|
|
11467
11709
|
}
|
|
11468
11710
|
__name(rawRequestPathname, "rawRequestPathname");
|
|
11469
|
-
function
|
|
11711
|
+
function pluralWorkspaceRawSelector(rawPath, suffix) {
|
|
11470
11712
|
let p = rawPath;
|
|
11471
|
-
if (p.endsWith(`${
|
|
11713
|
+
if (p.endsWith(`${suffix}/`)) {
|
|
11472
11714
|
p = p.slice(0, -1);
|
|
11473
11715
|
}
|
|
11474
|
-
if (!p.startsWith(
|
|
11716
|
+
if (!p.startsWith(PLURAL_WS_PREFIX) || !p.endsWith(suffix) || p.length <= PLURAL_WS_PREFIX.length + suffix.length) {
|
|
11475
11717
|
return null;
|
|
11476
11718
|
}
|
|
11477
|
-
const selector = p.slice(
|
|
11478
|
-
PLURAL_ACP_WS_PREFIX.length,
|
|
11479
|
-
p.length - PLURAL_ACP_WS_SUFFIX.length
|
|
11480
|
-
);
|
|
11719
|
+
const selector = p.slice(PLURAL_WS_PREFIX.length, p.length - suffix.length);
|
|
11481
11720
|
if (selector.length === 0 || selector.includes("/") || selector.includes("\\")) {
|
|
11482
11721
|
return null;
|
|
11483
11722
|
}
|
|
@@ -11487,7 +11726,7 @@ function pluralAcpRawSelector(rawPath) {
|
|
|
11487
11726
|
}
|
|
11488
11727
|
return selector;
|
|
11489
11728
|
}
|
|
11490
|
-
__name(
|
|
11729
|
+
__name(pluralWorkspaceRawSelector, "pluralWorkspaceRawSelector");
|
|
11491
11730
|
var CDP_BRIDGE_CLIENT_NAME = "qwen-cdp-bridge";
|
|
11492
11731
|
var CHROME_DEVTOOLS_MCP_SERVER_NAME = "chrome-devtools";
|
|
11493
11732
|
var RUNTIME_MCP_RETRY_DELAY_MS = 250;
|
|
@@ -12216,9 +12455,11 @@ function mountAcpHttp(app, bridge, opts) {
|
|
|
12216
12455
|
const extraRoute = opts.extraWsRoutes?.find(
|
|
12217
12456
|
(route) => route.path === rawPath
|
|
12218
12457
|
);
|
|
12219
|
-
const pluralRawSelector = workspaceQualifiedAcpEnabled ?
|
|
12458
|
+
const pluralRawSelector = workspaceQualifiedAcpEnabled ? pluralWorkspaceRawSelector(rawPath, PLURAL_ACP_WS_SUFFIX) : null;
|
|
12220
12459
|
const isPluralAcpShape = pluralRawSelector !== null;
|
|
12221
|
-
|
|
12460
|
+
const pluralVoiceRawSelector = opts.workspaceVoiceConnection ? pluralWorkspaceRawSelector(rawPath, PLURAL_VOICE_WS_SUFFIX) : null;
|
|
12461
|
+
const isPluralVoiceShape = pluralVoiceRawSelector !== null;
|
|
12462
|
+
if (rawPath !== path15 && !isCdpPath && !extraRoute && !isPluralAcpShape && !isPluralVoiceShape) {
|
|
12222
12463
|
logReject(`unknown-path ${logSafe(rawPath)}`);
|
|
12223
12464
|
socket.destroy();
|
|
12224
12465
|
return;
|
|
@@ -12288,6 +12529,42 @@ function mountAcpHttp(app, bridge, opts) {
|
|
|
12288
12529
|
});
|
|
12289
12530
|
return;
|
|
12290
12531
|
}
|
|
12532
|
+
if (isPluralVoiceShape) {
|
|
12533
|
+
let selector;
|
|
12534
|
+
try {
|
|
12535
|
+
selector = decodeURIComponent(pluralVoiceRawSelector);
|
|
12536
|
+
} catch {
|
|
12537
|
+
logReject("workspace-selector-decode-error");
|
|
12538
|
+
socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
12539
|
+
socket.destroy();
|
|
12540
|
+
return;
|
|
12541
|
+
}
|
|
12542
|
+
const wsRegistry = opts.workspaceRegistry;
|
|
12543
|
+
const runtime = wsRegistry ? wsRegistry.getManagedByWorkspaceId(selector) ?? (isPortableAbsolutePath(selector) ? resolveManagedWorkspaceRuntimeByPathSelector(
|
|
12544
|
+
wsRegistry,
|
|
12545
|
+
selector
|
|
12546
|
+
) : void 0) : void 0;
|
|
12547
|
+
if (!runtime) {
|
|
12548
|
+
logReject(`workspace-mismatch ${logSafe(selector)}`);
|
|
12549
|
+
socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
12550
|
+
socket.destroy();
|
|
12551
|
+
return;
|
|
12552
|
+
}
|
|
12553
|
+
if (!runtime.trusted) {
|
|
12554
|
+
logReject(`untrusted-workspace ${runtime.workspaceId}`);
|
|
12555
|
+
socket.write("HTTP/1.1 403 Forbidden\r\n\r\n");
|
|
12556
|
+
socket.destroy();
|
|
12557
|
+
return;
|
|
12558
|
+
}
|
|
12559
|
+
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
12560
|
+
if (disposed) {
|
|
12561
|
+
ws.close(1012, "Server shutting down");
|
|
12562
|
+
return;
|
|
12563
|
+
}
|
|
12564
|
+
opts.workspaceVoiceConnection(runtime, ws, req);
|
|
12565
|
+
});
|
|
12566
|
+
return;
|
|
12567
|
+
}
|
|
12291
12568
|
let activeMount = primaryMount;
|
|
12292
12569
|
if (isPluralAcpShape) {
|
|
12293
12570
|
let selector;
|
|
@@ -12300,7 +12577,10 @@ function mountAcpHttp(app, bridge, opts) {
|
|
|
12300
12577
|
return;
|
|
12301
12578
|
}
|
|
12302
12579
|
const wsRegistry = opts.workspaceRegistry;
|
|
12303
|
-
const rt = wsRegistry ? wsRegistry.getManagedByWorkspaceId(selector) ??
|
|
12580
|
+
const rt = wsRegistry ? wsRegistry.getManagedByWorkspaceId(selector) ?? (isPortableAbsolutePath(selector) ? resolveManagedWorkspaceRuntimeByPathSelector(
|
|
12581
|
+
wsRegistry,
|
|
12582
|
+
selector
|
|
12583
|
+
) : void 0) : void 0;
|
|
12304
12584
|
if (!rt) {
|
|
12305
12585
|
logReject(`workspace-mismatch ${logSafe(selector)}`);
|
|
12306
12586
|
socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
@@ -12909,7 +13189,6 @@ var debugLogger3 = createDebugLogger("VOICE_WS");
|
|
|
12909
13189
|
var MAX_BATCH_AUDIO_BYTES = 10 * 1024 * 1024;
|
|
12910
13190
|
var MAX_QUEUED_AUDIO_BYTES = MAX_BATCH_AUDIO_BYTES * 2;
|
|
12911
13191
|
var MAX_CONNECTION_MS = 6 * 6e4;
|
|
12912
|
-
var MAX_CONCURRENT_VOICE_SESSIONS = 8;
|
|
12913
13192
|
var GENERIC_TRANSCRIPTION_ERROR = "Voice transcription failed. Please try again.";
|
|
12914
13193
|
var NO_VOICE_MODEL_ERROR = "No voice model is configured for this workspace.";
|
|
12915
13194
|
var SAMPLE_RATE = 16e3;
|
|
@@ -12932,7 +13211,7 @@ function encodeWav(pcm) {
|
|
|
12932
13211
|
return Buffer.concat([header, Buffer.from(pcm)]);
|
|
12933
13212
|
}
|
|
12934
13213
|
__name(encodeWav, "encodeWav");
|
|
12935
|
-
async function defaultOpenStream(ctx, callbacks) {
|
|
13214
|
+
async function defaultOpenStream(ctx, callbacks, abortSignal) {
|
|
12936
13215
|
try {
|
|
12937
13216
|
const cfg = resolveVoiceStreamConfig({
|
|
12938
13217
|
config: ctx.models,
|
|
@@ -12940,9 +13219,10 @@ async function defaultOpenStream(ctx, callbacks) {
|
|
|
12940
13219
|
voiceModel: ctx.voiceModel,
|
|
12941
13220
|
env: ctx.env
|
|
12942
13221
|
});
|
|
12943
|
-
await assertVoiceBaseUrlNetworkAllowed(cfg);
|
|
13222
|
+
await assertVoiceBaseUrlNetworkAllowed(cfg, void 0, abortSignal);
|
|
12944
13223
|
return await openVoiceStreamWithRetry(
|
|
12945
|
-
() => cfg.transport === "qwen-asr-realtime" ? openQwenAsrRealtimeStream(cfg, callbacks) : openVoiceStream(cfg, callbacks)
|
|
13224
|
+
() => cfg.transport === "qwen-asr-realtime" ? openQwenAsrRealtimeStream(cfg, callbacks, { abortSignal }) : openVoiceStream(cfg, callbacks, { abortSignal }),
|
|
13225
|
+
{ abortSignal }
|
|
12946
13226
|
);
|
|
12947
13227
|
} catch (error2) {
|
|
12948
13228
|
debugLogger3.debug(`[voice-ws] stream open error: ${errMessage(error2)}`);
|
|
@@ -12950,14 +13230,15 @@ async function defaultOpenStream(ctx, callbacks) {
|
|
|
12950
13230
|
}
|
|
12951
13231
|
}
|
|
12952
13232
|
__name(defaultOpenStream, "defaultOpenStream");
|
|
12953
|
-
function defaultTranscribe(ctx, pcm) {
|
|
13233
|
+
function defaultTranscribe(ctx, pcm, abortSignal) {
|
|
12954
13234
|
return transcribeVoiceAudio(
|
|
12955
13235
|
{ data: encodeWav(pcm), mimeType: "audio/wav" },
|
|
12956
13236
|
{
|
|
12957
13237
|
config: ctx.models,
|
|
12958
13238
|
settings: ctx.settings,
|
|
12959
13239
|
voiceModel: ctx.voiceModel,
|
|
12960
|
-
env: ctx.env
|
|
13240
|
+
env: ctx.env,
|
|
13241
|
+
abortSignal
|
|
12961
13242
|
}
|
|
12962
13243
|
).catch((error2) => {
|
|
12963
13244
|
debugLogger3.debug(
|
|
@@ -12971,6 +13252,10 @@ function errMessage(error2) {
|
|
|
12971
13252
|
return error2 instanceof Error ? error2.message : String(error2);
|
|
12972
13253
|
}
|
|
12973
13254
|
__name(errMessage, "errMessage");
|
|
13255
|
+
function voiceLeaseCloseReason(signal) {
|
|
13256
|
+
return signal.reason instanceof VoiceLeaseAbortError && signal.reason.kind === "daemon_shutdown" ? "Server shutting down" : "Workspace removed";
|
|
13257
|
+
}
|
|
13258
|
+
__name(voiceLeaseCloseReason, "voiceLeaseCloseReason");
|
|
12974
13259
|
function voiceConfigErrorMessage(error2) {
|
|
12975
13260
|
const message = errMessage(error2);
|
|
12976
13261
|
return message === NO_VOICE_MODEL_ERROR ? message : GENERIC_TRANSCRIPTION_ERROR;
|
|
@@ -13001,15 +13286,35 @@ function createVoiceWsConnectionHandler(boundWorkspace, deps = {}) {
|
|
|
13001
13286
|
const openStream = deps.openStream ?? defaultOpenStream;
|
|
13002
13287
|
const transcribe = deps.transcribe ?? defaultTranscribe;
|
|
13003
13288
|
let activeSessions = 0;
|
|
13004
|
-
|
|
13289
|
+
const acquireLocalLease = /* @__PURE__ */ __name(() => {
|
|
13005
13290
|
if (activeSessions >= MAX_CONCURRENT_VOICE_SESSIONS) {
|
|
13006
|
-
|
|
13007
|
-
|
|
13008
|
-
|
|
13009
|
-
|
|
13010
|
-
|
|
13011
|
-
|
|
13012
|
-
|
|
13291
|
+
return { kind: "rejected", reason: "capacity" };
|
|
13292
|
+
}
|
|
13293
|
+
activeSessions++;
|
|
13294
|
+
const controller = new AbortController();
|
|
13295
|
+
let released = false;
|
|
13296
|
+
const lease = {
|
|
13297
|
+
signal: controller.signal,
|
|
13298
|
+
release: /* @__PURE__ */ __name(() => {
|
|
13299
|
+
if (released) return;
|
|
13300
|
+
released = true;
|
|
13301
|
+
activeSessions--;
|
|
13302
|
+
}, "release")
|
|
13303
|
+
};
|
|
13304
|
+
return { kind: "admitted", lease };
|
|
13305
|
+
}, "acquireLocalLease");
|
|
13306
|
+
return (ws) => {
|
|
13307
|
+
const admission = (deps.acquireVoiceLease ?? acquireLocalLease)();
|
|
13308
|
+
if (admission.kind === "rejected") {
|
|
13309
|
+
writeStderrLine("qwen serve: voice websocket rejected");
|
|
13310
|
+
try {
|
|
13311
|
+
if (admission.reason === "draining") {
|
|
13312
|
+
ws.close(1012, "Workspace removed");
|
|
13313
|
+
return;
|
|
13314
|
+
}
|
|
13315
|
+
ws.send(
|
|
13316
|
+
JSON.stringify({
|
|
13317
|
+
type: "error",
|
|
13013
13318
|
message: "Too many voice sessions in progress; try again shortly."
|
|
13014
13319
|
})
|
|
13015
13320
|
);
|
|
@@ -13018,18 +13323,14 @@ function createVoiceWsConnectionHandler(boundWorkspace, deps = {}) {
|
|
|
13018
13323
|
}
|
|
13019
13324
|
return;
|
|
13020
13325
|
}
|
|
13021
|
-
|
|
13022
|
-
writeStderrLine(
|
|
13023
|
-
`qwen serve: voice websocket accepted; activeSessions=${activeSessions}`
|
|
13024
|
-
);
|
|
13326
|
+
const lease = admission.lease;
|
|
13327
|
+
writeStderrLine("qwen serve: voice websocket accepted");
|
|
13025
13328
|
let released = false;
|
|
13026
13329
|
const releaseSlot = /* @__PURE__ */ __name(() => {
|
|
13027
13330
|
if (!released) {
|
|
13028
13331
|
released = true;
|
|
13029
|
-
|
|
13030
|
-
writeStderrLine(
|
|
13031
|
-
`qwen serve: voice websocket slot released; activeSessions=${activeSessions}`
|
|
13032
|
-
);
|
|
13332
|
+
lease.release();
|
|
13333
|
+
writeStderrLine("qwen serve: voice websocket slot released");
|
|
13033
13334
|
}
|
|
13034
13335
|
}, "releaseSlot");
|
|
13035
13336
|
let state = "idle";
|
|
@@ -13040,6 +13341,7 @@ function createVoiceWsConnectionHandler(boundWorkspace, deps = {}) {
|
|
|
13040
13341
|
let bufferedBytes = 0;
|
|
13041
13342
|
let queuedBytes = 0;
|
|
13042
13343
|
let pendingOperations = 0;
|
|
13344
|
+
const operationController = new AbortController();
|
|
13043
13345
|
let chain = Promise.resolve();
|
|
13044
13346
|
const hardTimer = setTimeout(() => {
|
|
13045
13347
|
if (state !== "closed") fail("Voice session exceeded the time limit.");
|
|
@@ -13060,6 +13362,9 @@ function createVoiceWsConnectionHandler(boundWorkspace, deps = {}) {
|
|
|
13060
13362
|
function cleanup() {
|
|
13061
13363
|
state = "closed";
|
|
13062
13364
|
clearTimeout(hardTimer);
|
|
13365
|
+
if (!operationController.signal.aborted) {
|
|
13366
|
+
operationController.abort(new Error("Voice connection closed."));
|
|
13367
|
+
}
|
|
13063
13368
|
if (session) {
|
|
13064
13369
|
try {
|
|
13065
13370
|
session.abort();
|
|
@@ -13073,6 +13378,19 @@ function createVoiceWsConnectionHandler(boundWorkspace, deps = {}) {
|
|
|
13073
13378
|
queuedBytes = 0;
|
|
13074
13379
|
}
|
|
13075
13380
|
__name(cleanup, "cleanup");
|
|
13381
|
+
lease.signal.addEventListener(
|
|
13382
|
+
"abort",
|
|
13383
|
+
() => {
|
|
13384
|
+
if (state === "closed") return;
|
|
13385
|
+
cleanup();
|
|
13386
|
+
try {
|
|
13387
|
+
ws.close(1012, voiceLeaseCloseReason(lease.signal));
|
|
13388
|
+
} catch {
|
|
13389
|
+
}
|
|
13390
|
+
releaseSlotWhenIdle();
|
|
13391
|
+
},
|
|
13392
|
+
{ once: true }
|
|
13393
|
+
);
|
|
13076
13394
|
function fail(message) {
|
|
13077
13395
|
if (state === "closed") return;
|
|
13078
13396
|
writeStderrLine(`qwen serve: voice websocket failed: ${message}`);
|
|
@@ -13111,7 +13429,7 @@ function createVoiceWsConnectionHandler(boundWorkspace, deps = {}) {
|
|
|
13111
13429
|
fail(GENERIC_TRANSCRIPTION_ERROR);
|
|
13112
13430
|
}, "onError")
|
|
13113
13431
|
};
|
|
13114
|
-
const opening = openStream(ctx, callbacks);
|
|
13432
|
+
const opening = openStream(ctx, callbacks, operationController.signal);
|
|
13115
13433
|
sessionPromise = opening;
|
|
13116
13434
|
const opened = await opening;
|
|
13117
13435
|
if (state === "closed") {
|
|
@@ -13143,11 +13461,16 @@ function createVoiceWsConnectionHandler(boundWorkspace, deps = {}) {
|
|
|
13143
13461
|
}
|
|
13144
13462
|
}
|
|
13145
13463
|
} else if (pcmChunks.length > 0) {
|
|
13146
|
-
transcript = await transcribe(
|
|
13464
|
+
transcript = await transcribe(
|
|
13465
|
+
ctx,
|
|
13466
|
+
Buffer.concat(pcmChunks),
|
|
13467
|
+
operationController.signal
|
|
13468
|
+
);
|
|
13147
13469
|
}
|
|
13148
13470
|
sendJson({ type: "final", text: transcript });
|
|
13149
13471
|
writeStderrLine("qwen serve: voice websocket finalized successfully");
|
|
13150
13472
|
cleanup();
|
|
13473
|
+
releaseSlotWhenIdle();
|
|
13151
13474
|
try {
|
|
13152
13475
|
ws.close(1e3, "done");
|
|
13153
13476
|
} catch {
|
|
@@ -14627,76 +14950,148 @@ __name(registerWorkspaceAuthRoutes, "registerWorkspaceAuthRoutes");
|
|
|
14627
14950
|
|
|
14628
14951
|
// packages/cli/src/serve/routes/workspace-extensions.ts
|
|
14629
14952
|
init_esbuild_shims();
|
|
14953
|
+
import * as crypto2 from "node:crypto";
|
|
14954
|
+
|
|
14955
|
+
// packages/cli/src/serve/extension-operation-scheduler.ts
|
|
14956
|
+
init_esbuild_shims();
|
|
14957
|
+
function abortReason(signal) {
|
|
14958
|
+
return signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
14959
|
+
}
|
|
14960
|
+
__name(abortReason, "abortReason");
|
|
14961
|
+
function createFifoTaskQueue(limit) {
|
|
14962
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
14963
|
+
throw new Error(
|
|
14964
|
+
`Task queue limit must be a positive integer, got ${limit}.`
|
|
14965
|
+
);
|
|
14966
|
+
}
|
|
14967
|
+
let active = 0;
|
|
14968
|
+
const queued = [];
|
|
14969
|
+
const pump = /* @__PURE__ */ __name(() => {
|
|
14970
|
+
while (active < limit && queued.length > 0) {
|
|
14971
|
+
const item = queued.shift();
|
|
14972
|
+
const removeAbortListener = item.removeAbortListener;
|
|
14973
|
+
item.removeAbortListener = void 0;
|
|
14974
|
+
removeAbortListener?.();
|
|
14975
|
+
if (item.signal?.aborted) {
|
|
14976
|
+
item.reject(abortReason(item.signal));
|
|
14977
|
+
continue;
|
|
14978
|
+
}
|
|
14979
|
+
active += 1;
|
|
14980
|
+
try {
|
|
14981
|
+
item.onStart?.();
|
|
14982
|
+
} catch (error2) {
|
|
14983
|
+
active -= 1;
|
|
14984
|
+
item.reject(error2);
|
|
14985
|
+
continue;
|
|
14986
|
+
}
|
|
14987
|
+
let released = false;
|
|
14988
|
+
const release = /* @__PURE__ */ __name(() => {
|
|
14989
|
+
if (released) return;
|
|
14990
|
+
released = true;
|
|
14991
|
+
active -= 1;
|
|
14992
|
+
pump();
|
|
14993
|
+
}, "release");
|
|
14994
|
+
Promise.resolve().then(() => item.task(release)).then(item.resolve, item.reject).finally(release);
|
|
14995
|
+
}
|
|
14996
|
+
}, "pump");
|
|
14997
|
+
const enqueue = /* @__PURE__ */ __name((task, options = {}) => new Promise((resolve5, reject) => {
|
|
14998
|
+
if (options.signal?.aborted) {
|
|
14999
|
+
reject(abortReason(options.signal));
|
|
15000
|
+
return;
|
|
15001
|
+
}
|
|
15002
|
+
const item = {
|
|
15003
|
+
task,
|
|
15004
|
+
...options.signal ? { signal: options.signal } : {},
|
|
15005
|
+
...options.onStart ? { onStart: options.onStart } : {},
|
|
15006
|
+
resolve: resolve5,
|
|
15007
|
+
reject
|
|
15008
|
+
};
|
|
15009
|
+
if (options.signal) {
|
|
15010
|
+
const onAbort = /* @__PURE__ */ __name(() => {
|
|
15011
|
+
const index = queued.indexOf(item);
|
|
15012
|
+
if (index < 0) return;
|
|
15013
|
+
queued.splice(index, 1);
|
|
15014
|
+
item.removeAbortListener?.();
|
|
15015
|
+
reject(abortReason(options.signal));
|
|
15016
|
+
}, "onAbort");
|
|
15017
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
15018
|
+
item.removeAbortListener = () => options.signal?.removeEventListener("abort", onAbort);
|
|
15019
|
+
}
|
|
15020
|
+
queued.push(item);
|
|
15021
|
+
pump();
|
|
15022
|
+
}), "enqueue");
|
|
15023
|
+
const run = /* @__PURE__ */ __name((task, options) => enqueue(async () => await task(), options), "run");
|
|
15024
|
+
return { run, runUntilReleased: enqueue };
|
|
15025
|
+
}
|
|
15026
|
+
__name(createFifoTaskQueue, "createFifoTaskQueue");
|
|
15027
|
+
|
|
15028
|
+
// packages/cli/src/serve/routes/workspace-extensions-controller.ts
|
|
15029
|
+
init_esbuild_shims();
|
|
14630
15030
|
import * as crypto from "node:crypto";
|
|
14631
|
-
|
|
14632
|
-
|
|
14633
|
-
|
|
14634
|
-
|
|
14635
|
-
|
|
14636
|
-
|
|
14637
|
-
|
|
14638
|
-
|
|
14639
|
-
|
|
15031
|
+
var MAX_UNFINISHED_EXTENSION_OPERATIONS = 10;
|
|
15032
|
+
var sanitizeDaemonMessage = /* @__PURE__ */ __name((message) => redactUrlCredentials(stripAnsiAndControl(message)), "sanitizeDaemonMessage");
|
|
15033
|
+
var redactExtensionDisplaySource = /* @__PURE__ */ __name((source) => {
|
|
15034
|
+
const redacted = redactUrlCredentials(source);
|
|
15035
|
+
if (/^[A-Za-z]:[\\/]/.test(redacted)) return redacted;
|
|
15036
|
+
try {
|
|
15037
|
+
const url = new URL(redacted);
|
|
15038
|
+
url.search = "";
|
|
15039
|
+
url.hash = "";
|
|
15040
|
+
return url.toString();
|
|
15041
|
+
} catch {
|
|
15042
|
+
return redacted;
|
|
15043
|
+
}
|
|
15044
|
+
}, "redactExtensionDisplaySource");
|
|
15045
|
+
var EXTENSION_PREPARATION_CONCURRENCY = 2;
|
|
15046
|
+
var EXTENSION_REFRESH_TIMEOUT_MS = 3e4;
|
|
15047
|
+
var RECONCILE_SLOW_MS = 3e4;
|
|
15048
|
+
var EXTENSION_QUEUE_FULL_MESSAGE = "Extension operation queue is full";
|
|
15049
|
+
function createExtensionsController(deps) {
|
|
15050
|
+
const { boundWorkspace, bridge, workspace } = deps;
|
|
14640
15051
|
const maxExtensionOperationHistory = deps.maxExtensionOperationHistory ?? 100;
|
|
14641
|
-
const
|
|
14642
|
-
|
|
14643
|
-
|
|
14644
|
-
const
|
|
14645
|
-
|
|
14646
|
-
|
|
14647
|
-
|
|
14648
|
-
|
|
14649
|
-
|
|
14650
|
-
|
|
14651
|
-
|
|
14652
|
-
|
|
14653
|
-
|
|
14654
|
-
|
|
14655
|
-
|
|
14656
|
-
|
|
14657
|
-
|
|
14658
|
-
|
|
14659
|
-
|
|
14660
|
-
|
|
14661
|
-
|
|
14662
|
-
|
|
14663
|
-
|
|
14664
|
-
|
|
14665
|
-
const withExtensionTimeout = /* @__PURE__ */ __name(async (promise, timeoutMs, operation) => await new Promise((resolve5, reject) => {
|
|
14666
|
-
const timeout = setTimeout(() => {
|
|
14667
|
-
reject(new Error(`${operation} timed out after ${timeoutMs}ms`));
|
|
14668
|
-
}, timeoutMs);
|
|
14669
|
-
promise.then(
|
|
14670
|
-
(value) => {
|
|
14671
|
-
clearTimeout(timeout);
|
|
14672
|
-
resolve5(value);
|
|
14673
|
-
},
|
|
14674
|
-
(err) => {
|
|
14675
|
-
clearTimeout(timeout);
|
|
14676
|
-
reject(err);
|
|
14677
|
-
}
|
|
14678
|
-
);
|
|
14679
|
-
}), "withExtensionTimeout");
|
|
14680
|
-
const createExtensionManager = /* @__PURE__ */ __name(() => new ExtensionManager({
|
|
14681
|
-
workspaceDir: boundWorkspace,
|
|
14682
|
-
isWorkspaceTrusted: getWorkspaceTrustStatus(
|
|
14683
|
-
loadSettings(boundWorkspace).merged,
|
|
14684
|
-
boundWorkspace
|
|
14685
|
-
).effective.state === "trusted",
|
|
15052
|
+
const preparationQueue = createFifoTaskQueue(
|
|
15053
|
+
EXTENSION_PREPARATION_CONCURRENCY
|
|
15054
|
+
);
|
|
15055
|
+
const commitQueue = createFifoTaskQueue(1);
|
|
15056
|
+
let unfinishedOperationCount = 0;
|
|
15057
|
+
const acquireOperationSlot = /* @__PURE__ */ __name((res) => {
|
|
15058
|
+
if (unfinishedOperationCount >= MAX_UNFINISHED_EXTENSION_OPERATIONS) {
|
|
15059
|
+
res.status(429).json({
|
|
15060
|
+
error: EXTENSION_QUEUE_FULL_MESSAGE,
|
|
15061
|
+
code: "extension_queue_full"
|
|
15062
|
+
});
|
|
15063
|
+
return void 0;
|
|
15064
|
+
}
|
|
15065
|
+
unfinishedOperationCount += 1;
|
|
15066
|
+
let released = false;
|
|
15067
|
+
return () => {
|
|
15068
|
+
if (released) return;
|
|
15069
|
+
released = true;
|
|
15070
|
+
unfinishedOperationCount -= 1;
|
|
15071
|
+
};
|
|
15072
|
+
}, "acquireOperationSlot");
|
|
15073
|
+
const createExtensionManager = /* @__PURE__ */ __name((workspaceDir = boundWorkspace, trustedOverride, interactions) => new ExtensionManager({
|
|
15074
|
+
workspaceDir,
|
|
15075
|
+
isWorkspaceTrusted: trustedOverride ?? getWorkspaceTrustStatus(loadSettings(workspaceDir).merged, workspaceDir).effective.state === "trusted",
|
|
14686
15076
|
requestConsent: /* @__PURE__ */ __name(() => Promise.resolve(), "requestConsent"),
|
|
14687
|
-
|
|
15077
|
+
networkPolicy: "public",
|
|
15078
|
+
requestSetting: interactions?.requestSetting ?? (async (setting) => {
|
|
14688
15079
|
throw new Error(
|
|
14689
15080
|
`Extension setting "${setting.envVar}" requires interactive configuration and is not supported over the daemon install endpoint.`
|
|
14690
15081
|
);
|
|
14691
|
-
}
|
|
14692
|
-
requestChoicePlugin:
|
|
15082
|
+
}),
|
|
15083
|
+
requestChoicePlugin: interactions?.requestChoicePlugin ?? (async () => {
|
|
14693
15084
|
throw new Error(
|
|
14694
15085
|
"Marketplace plugin selection is not supported over the daemon install endpoint. Specify a plugin name in the source."
|
|
14695
15086
|
);
|
|
14696
|
-
}
|
|
15087
|
+
})
|
|
14697
15088
|
}), "createExtensionManager");
|
|
14698
|
-
const validateExtensionMutationClient = /* @__PURE__ */ __name((req, res,
|
|
14699
|
-
const clientId = parseAndValidateWorkspaceClientId(
|
|
15089
|
+
const validateExtensionMutationClient = /* @__PURE__ */ __name((req, res, opts = {}) => {
|
|
15090
|
+
const clientId = parseAndValidateWorkspaceClientId(
|
|
15091
|
+
req,
|
|
15092
|
+
res,
|
|
15093
|
+
opts.bridges ?? bridge
|
|
15094
|
+
);
|
|
14700
15095
|
if (clientId === null) return false;
|
|
14701
15096
|
if (clientId === void 0 && opts.requireClientId !== false) {
|
|
14702
15097
|
res.status(400).json({
|
|
@@ -14705,98 +15100,25 @@ function registerWorkspaceExtensionRoutes(app, deps) {
|
|
|
14705
15100
|
});
|
|
14706
15101
|
return false;
|
|
14707
15102
|
}
|
|
14708
|
-
buildWorkspaceCtx(route, clientId);
|
|
14709
15103
|
return true;
|
|
14710
15104
|
}, "validateExtensionMutationClient");
|
|
14711
|
-
const parseExtensionScope = /* @__PURE__ */ __name((body, res) => {
|
|
14712
|
-
const scope = body["scope"];
|
|
14713
|
-
if (scope !== "user" && scope !== "workspace") {
|
|
14714
|
-
res.status(400).json({ error: '`scope` must be either "user" or "workspace"' });
|
|
14715
|
-
return null;
|
|
14716
|
-
}
|
|
14717
|
-
return scope === "user" ? "User" /* User */ : "Workspace" /* Workspace */;
|
|
14718
|
-
}, "parseExtensionScope");
|
|
14719
|
-
const parseExtensionRegistryUrl = /* @__PURE__ */ __name((value, res) => {
|
|
14720
|
-
let parsed;
|
|
14721
|
-
try {
|
|
14722
|
-
parsed = new URL(value);
|
|
14723
|
-
} catch {
|
|
14724
|
-
res.status(400).json({ error: "`registry` must be a valid URL" });
|
|
14725
|
-
return null;
|
|
14726
|
-
}
|
|
14727
|
-
if (parsed.protocol !== "https:") {
|
|
14728
|
-
res.status(400).json({ error: "`registry` must use https" });
|
|
14729
|
-
return null;
|
|
14730
|
-
}
|
|
14731
|
-
if (parsed.username || parsed.password) {
|
|
14732
|
-
res.status(400).json({ error: "`registry` must not include credentials" });
|
|
14733
|
-
return null;
|
|
14734
|
-
}
|
|
14735
|
-
if (isBlockedAuthProviderHost(parsed.hostname)) {
|
|
14736
|
-
res.status(400).json({ error: "`registry` host is not allowed" });
|
|
14737
|
-
return null;
|
|
14738
|
-
}
|
|
14739
|
-
return parsed.toString().replace(/\/$/, "");
|
|
14740
|
-
}, "parseExtensionRegistryUrl");
|
|
14741
|
-
const parsePotentialSourceUrl = /* @__PURE__ */ __name((source) => {
|
|
14742
|
-
if (/^[a-zA-Z]:[\\/]/.test(source)) return null;
|
|
14743
|
-
try {
|
|
14744
|
-
return new URL(source);
|
|
14745
|
-
} catch {
|
|
14746
|
-
const sshMatch = /^(?:[^@]+@)?(\[[^\]]+\]|[^:]+):/.exec(source);
|
|
14747
|
-
if (!sshMatch?.[1]) return null;
|
|
14748
|
-
try {
|
|
14749
|
-
return new URL(`ssh://${sshMatch[1]}`);
|
|
14750
|
-
} catch {
|
|
14751
|
-
return null;
|
|
14752
|
-
}
|
|
14753
|
-
}
|
|
14754
|
-
}, "parsePotentialSourceUrl");
|
|
14755
|
-
const validateExtensionSourceHost = /* @__PURE__ */ __name((source, res) => {
|
|
14756
|
-
const parsed = parsePotentialSourceUrl(source);
|
|
14757
|
-
if (!parsed) return true;
|
|
14758
|
-
if (parsed.username || parsed.password) {
|
|
14759
|
-
res.status(400).json({ error: "`source` must not include credentials" });
|
|
14760
|
-
return false;
|
|
14761
|
-
}
|
|
14762
|
-
if (isBlockedAuthProviderHost(parsed.hostname)) {
|
|
14763
|
-
res.status(400).json({ error: "`source` host is not allowed" });
|
|
14764
|
-
return false;
|
|
14765
|
-
}
|
|
14766
|
-
if (parsed.protocol !== "https:" && parsed.protocol !== "ssh:") {
|
|
14767
|
-
res.status(400).json({ error: "`source` must use https or ssh" });
|
|
14768
|
-
return false;
|
|
14769
|
-
}
|
|
14770
|
-
return true;
|
|
14771
|
-
}, "validateExtensionSourceHost");
|
|
14772
|
-
const validateExtensionSourceMetadata = /* @__PURE__ */ __name((installMetadata) => {
|
|
14773
|
-
if (installMetadata.type !== "git") return true;
|
|
14774
|
-
const parsed = parsePotentialSourceUrl(installMetadata.source);
|
|
14775
|
-
return !!parsed && (parsed.protocol === "https:" || parsed.protocol === "ssh:") && !isBlockedAuthProviderHost(parsed.hostname);
|
|
14776
|
-
}, "validateExtensionSourceMetadata");
|
|
14777
|
-
const findLoadedExtension = /* @__PURE__ */ __name((extensionManager, extensionName) => {
|
|
14778
|
-
const requested = extensionName.toLowerCase();
|
|
14779
|
-
const extensions = extensionManager.getLoadedExtensions();
|
|
14780
|
-
const byName = extensions.find(
|
|
14781
|
-
(extension) => extension.name.toLowerCase() === requested
|
|
14782
|
-
);
|
|
14783
|
-
if (byName) return byName;
|
|
14784
|
-
if (!extensionName.includes("://") && !extensionName.includes("@")) {
|
|
14785
|
-
return void 0;
|
|
14786
|
-
}
|
|
14787
|
-
return extensions.find(
|
|
14788
|
-
(extension) => extension.installMetadata?.source?.toLowerCase() === requested
|
|
14789
|
-
);
|
|
14790
|
-
}, "findLoadedExtension");
|
|
14791
15105
|
const extensionOperations = /* @__PURE__ */ new Map();
|
|
14792
|
-
const isTerminalExtensionOperation = /* @__PURE__ */ __name((operation) => operation.status !== "queued" && operation.status !== "running", "isTerminalExtensionOperation");
|
|
15106
|
+
const isTerminalExtensionOperation = /* @__PURE__ */ __name((operation) => operation.status !== "queued" && operation.status !== "running" && operation.status !== "waiting_for_input", "isTerminalExtensionOperation");
|
|
14793
15107
|
const redactExtensionOperationResult = /* @__PURE__ */ __name((event) => ({
|
|
14794
15108
|
...event,
|
|
14795
|
-
...event.source ? { source:
|
|
15109
|
+
...event.source ? { source: redactExtensionDisplaySource(event.source) } : {}
|
|
14796
15110
|
}), "redactExtensionOperationResult");
|
|
14797
|
-
const
|
|
14798
|
-
|
|
14799
|
-
|
|
15111
|
+
const bridgeMutationEvent = /* @__PURE__ */ __name((event) => {
|
|
15112
|
+
const redacted = redactExtensionOperationResult(event);
|
|
15113
|
+
if (event.status === "checked" || event.status === "refreshed") {
|
|
15114
|
+
const { status: _status, states: _states, ...bridgeEvent } = redacted;
|
|
15115
|
+
return bridgeEvent;
|
|
15116
|
+
}
|
|
15117
|
+
return redacted;
|
|
15118
|
+
}, "bridgeMutationEvent");
|
|
15119
|
+
const pruneExtensionOperations = /* @__PURE__ */ __name(() => {
|
|
15120
|
+
const terminalCount = /* @__PURE__ */ __name(() => [...extensionOperations.values()].filter(isTerminalExtensionOperation).length, "terminalCount");
|
|
15121
|
+
while (terminalCount() > maxExtensionOperationHistory) {
|
|
14800
15122
|
let evicted = false;
|
|
14801
15123
|
for (const [id, storedOperation] of extensionOperations) {
|
|
14802
15124
|
if (!isTerminalExtensionOperation(storedOperation)) continue;
|
|
@@ -14806,6 +15128,10 @@ function registerWorkspaceExtensionRoutes(app, deps) {
|
|
|
14806
15128
|
}
|
|
14807
15129
|
if (!evicted) break;
|
|
14808
15130
|
}
|
|
15131
|
+
}, "pruneExtensionOperations");
|
|
15132
|
+
const rememberExtensionOperation = /* @__PURE__ */ __name((operation) => {
|
|
15133
|
+
extensionOperations.set(operation.operationId, operation);
|
|
15134
|
+
pruneExtensionOperations();
|
|
14809
15135
|
}, "rememberExtensionOperation");
|
|
14810
15136
|
const updateExtensionOperation = /* @__PURE__ */ __name((operationId, patch) => {
|
|
14811
15137
|
const current = extensionOperations.get(operationId);
|
|
@@ -14815,12 +15141,43 @@ function registerWorkspaceExtensionRoutes(app, deps) {
|
|
|
14815
15141
|
...patch,
|
|
14816
15142
|
updatedAt: Date.now()
|
|
14817
15143
|
});
|
|
15144
|
+
pruneExtensionOperations();
|
|
14818
15145
|
}, "updateExtensionOperation");
|
|
14819
|
-
|
|
14820
|
-
|
|
14821
|
-
|
|
14822
|
-
|
|
15146
|
+
let extensionsStatusCache;
|
|
15147
|
+
const refreshExtensionsForAllSessions = /* @__PURE__ */ __name(async () => {
|
|
15148
|
+
const queueAbort = new AbortController();
|
|
15149
|
+
let releaseCommitLane;
|
|
15150
|
+
const refresh = commitQueue.runUntilReleased(
|
|
15151
|
+
async (release) => {
|
|
15152
|
+
releaseCommitLane = release;
|
|
15153
|
+
extensionsStatusCache = void 0;
|
|
15154
|
+
return await workspace.refreshExtensionsForAllSessions();
|
|
15155
|
+
},
|
|
15156
|
+
{ signal: queueAbort.signal }
|
|
15157
|
+
);
|
|
15158
|
+
let timer;
|
|
15159
|
+
try {
|
|
15160
|
+
return await Promise.race([
|
|
15161
|
+
refresh,
|
|
15162
|
+
new Promise((_resolve, reject) => {
|
|
15163
|
+
timer = setTimeout(() => {
|
|
15164
|
+
const error2 = new Error(
|
|
15165
|
+
`extension refresh timed out after ${EXTENSION_REFRESH_TIMEOUT_MS}ms`
|
|
15166
|
+
);
|
|
15167
|
+
releaseCommitLane?.();
|
|
15168
|
+
queueAbort.abort(error2);
|
|
15169
|
+
reject(error2);
|
|
15170
|
+
}, EXTENSION_REFRESH_TIMEOUT_MS);
|
|
15171
|
+
timer.unref?.();
|
|
15172
|
+
})
|
|
15173
|
+
]);
|
|
15174
|
+
} finally {
|
|
15175
|
+
if (timer) clearTimeout(timer);
|
|
14823
15176
|
}
|
|
15177
|
+
}, "refreshExtensionsForAllSessions");
|
|
15178
|
+
const runQueuedExtensionMutation = /* @__PURE__ */ __name((operation, failureContext, res, run, options = {}) => {
|
|
15179
|
+
const releaseOperationSlot = acquireOperationSlot(res);
|
|
15180
|
+
if (!releaseOperationSlot) return;
|
|
14824
15181
|
const operationId = crypto.randomUUID();
|
|
14825
15182
|
const now = Date.now();
|
|
14826
15183
|
rememberExtensionOperation({
|
|
@@ -14830,76 +15187,391 @@ function registerWorkspaceExtensionRoutes(app, deps) {
|
|
|
14830
15187
|
status: "queued",
|
|
14831
15188
|
createdAt: now,
|
|
14832
15189
|
updatedAt: now,
|
|
14833
|
-
...failureContext.source ? { source:
|
|
15190
|
+
...failureContext.source ? { source: redactExtensionDisplaySource(failureContext.source) } : {},
|
|
14834
15191
|
...failureContext.name ? { name: failureContext.name } : {}
|
|
14835
15192
|
});
|
|
14836
|
-
|
|
14837
|
-
|
|
15193
|
+
const operationBasePath = options.operationBasePath ?? "/workspace/extensions/operations";
|
|
15194
|
+
try {
|
|
15195
|
+
res.status(202).location(`${operationBasePath}/${operationId}`).set("Retry-After", "1").json({ accepted: true, operationId });
|
|
15196
|
+
} catch {
|
|
15197
|
+
extensionOperations.delete(operationId);
|
|
15198
|
+
releaseOperationSlot();
|
|
15199
|
+
return;
|
|
15200
|
+
}
|
|
15201
|
+
void (async () => {
|
|
15202
|
+
let deadline;
|
|
15203
|
+
let committedGeneration;
|
|
15204
|
+
let reconciliationReservation;
|
|
15205
|
+
let mutationEvent;
|
|
15206
|
+
const commitWarnings = [];
|
|
15207
|
+
const runReconciliation = /* @__PURE__ */ __name(async (task) => {
|
|
15208
|
+
const reservation = reconciliationReservation;
|
|
15209
|
+
reconciliationReservation = void 0;
|
|
15210
|
+
return reservation ? await reservation.run(task) : await task();
|
|
15211
|
+
}, "runReconciliation");
|
|
14838
15212
|
try {
|
|
14839
|
-
updateExtensionOperation(operationId, {
|
|
14840
|
-
|
|
15213
|
+
updateExtensionOperation(operationId, {
|
|
15214
|
+
status: "running",
|
|
15215
|
+
phase: "preparing"
|
|
15216
|
+
});
|
|
15217
|
+
const extensionManager = options.manager ?? options.createManager?.(operationId) ?? createExtensionManager();
|
|
15218
|
+
const deadlineController = new AbortController();
|
|
15219
|
+
let deadlineStarted = false;
|
|
15220
|
+
const startDeadline = /* @__PURE__ */ __name(() => {
|
|
15221
|
+
if (deadlineStarted) return;
|
|
15222
|
+
deadlineStarted = true;
|
|
15223
|
+
if (options.deadlineMs) {
|
|
15224
|
+
deadline = setTimeout(() => {
|
|
15225
|
+
const error2 = new Error(
|
|
15226
|
+
`Extension ${operation} exceeded its ${options.deadlineMs}ms preparation deadline.`
|
|
15227
|
+
);
|
|
15228
|
+
error2.code = "extension_prepare_timeout";
|
|
15229
|
+
deadlineController.abort(error2);
|
|
15230
|
+
}, options.deadlineMs);
|
|
15231
|
+
deadline.unref?.();
|
|
15232
|
+
}
|
|
15233
|
+
}, "startDeadline");
|
|
15234
|
+
let pendingPreparations = 0;
|
|
15235
|
+
let activePreparations = 0;
|
|
15236
|
+
const updatePreparationState = /* @__PURE__ */ __name(() => {
|
|
15237
|
+
if (activePreparations > 0) {
|
|
15238
|
+
updateExtensionOperation(operationId, {
|
|
15239
|
+
status: "running",
|
|
15240
|
+
phase: "preparing"
|
|
15241
|
+
});
|
|
15242
|
+
} else if (pendingPreparations > 0) {
|
|
15243
|
+
updateExtensionOperation(operationId, {
|
|
15244
|
+
status: "queued",
|
|
15245
|
+
phase: void 0
|
|
15246
|
+
});
|
|
15247
|
+
}
|
|
15248
|
+
}, "updatePreparationState");
|
|
15249
|
+
const context = {
|
|
15250
|
+
prepare: /* @__PURE__ */ __name(async (task) => {
|
|
15251
|
+
pendingPreparations += 1;
|
|
15252
|
+
let started = false;
|
|
15253
|
+
updatePreparationState();
|
|
15254
|
+
try {
|
|
15255
|
+
const prepared = await preparationQueue.run(
|
|
15256
|
+
async () => {
|
|
15257
|
+
try {
|
|
15258
|
+
return await task(deadlineController.signal);
|
|
15259
|
+
} finally {
|
|
15260
|
+
activePreparations -= 1;
|
|
15261
|
+
updatePreparationState();
|
|
15262
|
+
}
|
|
15263
|
+
},
|
|
15264
|
+
{
|
|
15265
|
+
signal: deadlineController.signal,
|
|
15266
|
+
onStart: /* @__PURE__ */ __name(() => {
|
|
15267
|
+
startDeadline();
|
|
15268
|
+
started = true;
|
|
15269
|
+
pendingPreparations -= 1;
|
|
15270
|
+
activePreparations += 1;
|
|
15271
|
+
updatePreparationState();
|
|
15272
|
+
}, "onStart")
|
|
15273
|
+
}
|
|
15274
|
+
);
|
|
15275
|
+
deadlineController.signal.throwIfAborted();
|
|
15276
|
+
return prepared;
|
|
15277
|
+
} catch (error2) {
|
|
15278
|
+
if (!started) {
|
|
15279
|
+
pendingPreparations -= 1;
|
|
15280
|
+
updatePreparationState();
|
|
15281
|
+
}
|
|
15282
|
+
if (deadlineController.signal.aborted) {
|
|
15283
|
+
throw deadlineController.signal.reason;
|
|
15284
|
+
}
|
|
15285
|
+
throw error2;
|
|
15286
|
+
}
|
|
15287
|
+
}, "prepare"),
|
|
15288
|
+
commit: /* @__PURE__ */ __name(async (task) => {
|
|
15289
|
+
updateExtensionOperation(operationId, {
|
|
15290
|
+
status: "running",
|
|
15291
|
+
phase: "committing"
|
|
15292
|
+
});
|
|
15293
|
+
const result = await commitQueue.runUntilReleased(
|
|
15294
|
+
async (release) => await task((generation) => {
|
|
15295
|
+
reconciliationReservation ??= options.reserveRuntimeReconciliation?.();
|
|
15296
|
+
committedGeneration = generation;
|
|
15297
|
+
release();
|
|
15298
|
+
})
|
|
15299
|
+
);
|
|
15300
|
+
if (committedGeneration === void 0) {
|
|
15301
|
+
reconciliationReservation ??= options.reserveRuntimeReconciliation?.();
|
|
15302
|
+
committedGeneration = result.generation;
|
|
15303
|
+
}
|
|
15304
|
+
for (const warning of result.warnings ?? []) {
|
|
15305
|
+
commitWarnings.push({
|
|
15306
|
+
workspaceCwd: boundWorkspace,
|
|
15307
|
+
code: warning.code,
|
|
15308
|
+
error: sanitizeDaemonMessage(warning.error).slice(0, 500)
|
|
15309
|
+
});
|
|
15310
|
+
}
|
|
15311
|
+
return result;
|
|
15312
|
+
}, "commit")
|
|
15313
|
+
};
|
|
14841
15314
|
await extensionManager.refreshCache();
|
|
14842
|
-
const event = await
|
|
14843
|
-
|
|
14844
|
-
|
|
14845
|
-
|
|
15315
|
+
const event = await run(
|
|
15316
|
+
extensionManager,
|
|
15317
|
+
deadlineController.signal,
|
|
15318
|
+
context,
|
|
15319
|
+
operationId
|
|
14846
15320
|
);
|
|
15321
|
+
mutationEvent = event;
|
|
15322
|
+
if (deadline) clearTimeout(deadline);
|
|
14847
15323
|
extensionsStatusCache = void 0;
|
|
14848
|
-
|
|
14849
|
-
|
|
14850
|
-
|
|
15324
|
+
if (options.skipRefresh || event.updated === false) {
|
|
15325
|
+
reconciliationReservation?.release();
|
|
15326
|
+
reconciliationReservation = void 0;
|
|
14851
15327
|
updateExtensionOperation(operationId, {
|
|
14852
|
-
status: "succeeded",
|
|
14853
|
-
|
|
14854
|
-
|
|
14855
|
-
|
|
14856
|
-
failed: result.failed
|
|
14857
|
-
}
|
|
15328
|
+
status: commitWarnings.length > 0 ? "succeeded_with_warnings" : "succeeded",
|
|
15329
|
+
phase: void 0,
|
|
15330
|
+
result: redactExtensionOperationResult(event),
|
|
15331
|
+
...commitWarnings.length > 0 ? { warnings: commitWarnings } : {}
|
|
14858
15332
|
});
|
|
14859
|
-
|
|
14860
|
-
|
|
14861
|
-
|
|
14862
|
-
|
|
14863
|
-
|
|
14864
|
-
|
|
15333
|
+
return;
|
|
15334
|
+
}
|
|
15335
|
+
if (committedGeneration === void 0) {
|
|
15336
|
+
committedGeneration = (await extensionManager.getExtensionStoreSnapshot()).generation;
|
|
15337
|
+
reconciliationReservation ??= options.reserveRuntimeReconciliation?.();
|
|
15338
|
+
}
|
|
15339
|
+
updateExtensionOperation(operationId, {
|
|
15340
|
+
status: "running",
|
|
15341
|
+
phase: "reconciling"
|
|
15342
|
+
});
|
|
15343
|
+
const refreshTargets = typeof options.refreshRuntimes === "function" ? options.refreshRuntimes() : options.refreshRuntimes;
|
|
15344
|
+
if (refreshTargets) {
|
|
15345
|
+
const results = await runReconciliation(
|
|
15346
|
+
async () => await Promise.all(
|
|
15347
|
+
refreshTargets.map(async (runtime) => {
|
|
15348
|
+
const startedAt = Date.now();
|
|
15349
|
+
try {
|
|
15350
|
+
runtime.workspaceService.invalidateWorkspaceSkillsStatus();
|
|
15351
|
+
return {
|
|
15352
|
+
status: "fulfilled",
|
|
15353
|
+
result: await runtime.bridge.refreshExtensionsForAllSessions(
|
|
15354
|
+
bridgeMutationEvent(event)
|
|
15355
|
+
),
|
|
15356
|
+
elapsedMs: Date.now() - startedAt
|
|
15357
|
+
};
|
|
15358
|
+
} catch (reason) {
|
|
15359
|
+
return {
|
|
15360
|
+
status: "rejected",
|
|
15361
|
+
reason,
|
|
15362
|
+
elapsedMs: Date.now() - startedAt
|
|
15363
|
+
};
|
|
15364
|
+
}
|
|
15365
|
+
})
|
|
15366
|
+
)
|
|
14865
15367
|
);
|
|
15368
|
+
let refreshed = 0;
|
|
15369
|
+
let failed = 0;
|
|
15370
|
+
const warnings = [
|
|
15371
|
+
...commitWarnings
|
|
15372
|
+
];
|
|
15373
|
+
for (let index = 0; index < results.length; index += 1) {
|
|
15374
|
+
const settled = results[index];
|
|
15375
|
+
const runtime = refreshTargets[index];
|
|
15376
|
+
if (settled.status === "fulfilled") {
|
|
15377
|
+
refreshed += settled.result.refreshed;
|
|
15378
|
+
failed += settled.result.failed;
|
|
15379
|
+
if (settled.result.failed > 0) {
|
|
15380
|
+
warnings.push({
|
|
15381
|
+
workspaceId: runtime.workspaceId,
|
|
15382
|
+
workspaceCwd: runtime.workspaceCwd,
|
|
15383
|
+
error: `${settled.result.failed} session refresh(es) failed`
|
|
15384
|
+
});
|
|
15385
|
+
} else {
|
|
15386
|
+
options.onRuntimeReconciled?.(runtime, committedGeneration);
|
|
15387
|
+
}
|
|
15388
|
+
} else {
|
|
15389
|
+
failed += 1;
|
|
15390
|
+
const message = sanitizeDaemonMessage(
|
|
15391
|
+
settled.reason instanceof Error ? settled.reason.message : String(settled.reason)
|
|
15392
|
+
);
|
|
15393
|
+
warnings.push({
|
|
15394
|
+
workspaceId: runtime.workspaceId,
|
|
15395
|
+
workspaceCwd: runtime.workspaceCwd,
|
|
15396
|
+
error: message.slice(0, 500)
|
|
15397
|
+
});
|
|
15398
|
+
try {
|
|
15399
|
+
runtime.bridge.broadcastExtensionsChanged({
|
|
15400
|
+
...bridgeMutationEvent(event),
|
|
15401
|
+
refreshed: 0,
|
|
15402
|
+
failed: 1,
|
|
15403
|
+
error: message.slice(0, 500)
|
|
15404
|
+
});
|
|
15405
|
+
} catch {
|
|
15406
|
+
}
|
|
15407
|
+
}
|
|
15408
|
+
if (settled.elapsedMs > RECONCILE_SLOW_MS) {
|
|
15409
|
+
warnings.push({
|
|
15410
|
+
workspaceId: runtime.workspaceId,
|
|
15411
|
+
workspaceCwd: runtime.workspaceCwd,
|
|
15412
|
+
code: "reconcile_slow",
|
|
15413
|
+
error: `Runtime reconciliation took ${settled.elapsedMs}ms.`
|
|
15414
|
+
});
|
|
15415
|
+
}
|
|
15416
|
+
}
|
|
14866
15417
|
updateExtensionOperation(operationId, {
|
|
14867
|
-
status: "
|
|
15418
|
+
status: warnings.length > 0 ? "succeeded_with_warnings" : "succeeded",
|
|
15419
|
+
phase: void 0,
|
|
14868
15420
|
result: {
|
|
14869
15421
|
...redactExtensionOperationResult(event),
|
|
14870
|
-
refreshed
|
|
14871
|
-
failed
|
|
14872
|
-
|
|
15422
|
+
refreshed,
|
|
15423
|
+
failed
|
|
15424
|
+
},
|
|
15425
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
15426
|
+
});
|
|
15427
|
+
} else {
|
|
15428
|
+
try {
|
|
15429
|
+
const { result, elapsedMs } = await runReconciliation(async () => {
|
|
15430
|
+
workspace.invalidateWorkspaceSkillsStatus();
|
|
15431
|
+
const startedAt = Date.now();
|
|
15432
|
+
const result2 = await bridge.refreshExtensionsForAllSessions(
|
|
15433
|
+
bridgeMutationEvent(event)
|
|
15434
|
+
);
|
|
15435
|
+
return { result: result2, elapsedMs: Date.now() - startedAt };
|
|
15436
|
+
});
|
|
15437
|
+
const warnings = [...commitWarnings];
|
|
15438
|
+
if (result.failed > 0) {
|
|
15439
|
+
warnings.push({
|
|
15440
|
+
workspaceCwd: boundWorkspace,
|
|
15441
|
+
error: `${result.failed} session refresh(es) failed`
|
|
15442
|
+
});
|
|
15443
|
+
}
|
|
15444
|
+
if (elapsedMs > RECONCILE_SLOW_MS) {
|
|
15445
|
+
warnings.push({
|
|
15446
|
+
workspaceCwd: boundWorkspace,
|
|
15447
|
+
code: "reconcile_slow",
|
|
15448
|
+
error: `Runtime reconciliation took ${elapsedMs}ms.`
|
|
15449
|
+
});
|
|
15450
|
+
}
|
|
15451
|
+
updateExtensionOperation(operationId, {
|
|
15452
|
+
status: warnings.length > 0 ? "succeeded_with_warnings" : "succeeded",
|
|
15453
|
+
phase: void 0,
|
|
15454
|
+
result: {
|
|
15455
|
+
...redactExtensionOperationResult(event),
|
|
15456
|
+
refreshed: result.refreshed,
|
|
15457
|
+
failed: result.failed
|
|
15458
|
+
},
|
|
15459
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
15460
|
+
});
|
|
15461
|
+
writeStderrLine(
|
|
15462
|
+
`qwen serve: [${boundWorkspace}] extensions ${operation}: refreshed ${result.refreshed} session(s), ${result.failed} failed`
|
|
15463
|
+
);
|
|
15464
|
+
} catch (refreshErr) {
|
|
15465
|
+
const message = sanitizeDaemonMessage(
|
|
15466
|
+
refreshErr instanceof Error ? refreshErr.message : String(refreshErr)
|
|
15467
|
+
);
|
|
15468
|
+
updateExtensionOperation(operationId, {
|
|
15469
|
+
status: "succeeded_with_warnings",
|
|
15470
|
+
phase: void 0,
|
|
15471
|
+
result: {
|
|
15472
|
+
...redactExtensionOperationResult(event),
|
|
15473
|
+
refreshed: 0,
|
|
15474
|
+
failed: 1,
|
|
15475
|
+
error: message.slice(0, 500)
|
|
15476
|
+
},
|
|
15477
|
+
warnings: [
|
|
15478
|
+
...commitWarnings,
|
|
15479
|
+
{
|
|
15480
|
+
workspaceCwd: boundWorkspace,
|
|
15481
|
+
error: message.slice(0, 500)
|
|
15482
|
+
}
|
|
15483
|
+
]
|
|
15484
|
+
});
|
|
15485
|
+
try {
|
|
15486
|
+
bridge.broadcastExtensionsChanged({
|
|
15487
|
+
...bridgeMutationEvent(event),
|
|
15488
|
+
refreshed: 0,
|
|
15489
|
+
failed: 1,
|
|
15490
|
+
error: message.slice(0, 500)
|
|
15491
|
+
});
|
|
15492
|
+
} catch (broadcastErr) {
|
|
15493
|
+
writeStderrLine(
|
|
15494
|
+
`qwen serve: [${boundWorkspace}] extensions ${operation}: failed to broadcast refresh failure: ${sanitizeDaemonMessage(
|
|
15495
|
+
broadcastErr instanceof Error ? broadcastErr.message : String(broadcastErr)
|
|
15496
|
+
)}`
|
|
15497
|
+
);
|
|
15498
|
+
}
|
|
15499
|
+
writeStderrLine(
|
|
15500
|
+
`qwen serve: [${boundWorkspace}] extensions ${operation}: mutation succeeded but refresh failed: ${message}`
|
|
15501
|
+
);
|
|
15502
|
+
}
|
|
15503
|
+
}
|
|
15504
|
+
} catch (err) {
|
|
15505
|
+
const message = sanitizeDaemonMessage(
|
|
15506
|
+
err instanceof Error ? err.message : String(err)
|
|
15507
|
+
);
|
|
15508
|
+
const code = err && typeof err === "object" && typeof err.code === "string" ? err.code : void 0;
|
|
15509
|
+
if (committedGeneration !== void 0) {
|
|
15510
|
+
extensionsStatusCache = void 0;
|
|
15511
|
+
const error2 = `Commit succeeded but post-commit work failed: ${message}`.slice(
|
|
15512
|
+
0,
|
|
15513
|
+
500
|
|
15514
|
+
);
|
|
15515
|
+
const warnings = [
|
|
15516
|
+
...commitWarnings,
|
|
15517
|
+
{
|
|
15518
|
+
workspaceCwd: boundWorkspace,
|
|
15519
|
+
code: "post_commit_failed",
|
|
15520
|
+
error: error2
|
|
14873
15521
|
}
|
|
15522
|
+
];
|
|
15523
|
+
try {
|
|
15524
|
+
workspace.invalidateWorkspaceSkillsStatus();
|
|
15525
|
+
} catch (invalidationError) {
|
|
15526
|
+
warnings.push({
|
|
15527
|
+
workspaceCwd: boundWorkspace,
|
|
15528
|
+
code: "status_invalidation_failed",
|
|
15529
|
+
error: sanitizeDaemonMessage(
|
|
15530
|
+
invalidationError instanceof Error ? invalidationError.message : String(invalidationError)
|
|
15531
|
+
).slice(0, 500)
|
|
15532
|
+
});
|
|
15533
|
+
}
|
|
15534
|
+
updateExtensionOperation(operationId, {
|
|
15535
|
+
status: "succeeded_with_warnings",
|
|
15536
|
+
phase: void 0,
|
|
15537
|
+
...mutationEvent ? { result: redactExtensionOperationResult(mutationEvent) } : {},
|
|
15538
|
+
warnings
|
|
14874
15539
|
});
|
|
14875
15540
|
try {
|
|
14876
15541
|
bridge.broadcastExtensionsChanged({
|
|
14877
|
-
...
|
|
15542
|
+
...mutationEvent ? bridgeMutationEvent(mutationEvent) : {
|
|
15543
|
+
...failureContext.source ? {
|
|
15544
|
+
source: redactExtensionDisplaySource(
|
|
15545
|
+
failureContext.source
|
|
15546
|
+
)
|
|
15547
|
+
} : {},
|
|
15548
|
+
...failureContext.name ? { name: failureContext.name } : {}
|
|
15549
|
+
},
|
|
14878
15550
|
refreshed: 0,
|
|
14879
15551
|
failed: 1,
|
|
14880
|
-
error:
|
|
15552
|
+
error: error2
|
|
14881
15553
|
});
|
|
14882
|
-
} catch
|
|
15554
|
+
} catch {
|
|
15555
|
+
}
|
|
15556
|
+
try {
|
|
14883
15557
|
writeStderrLine(
|
|
14884
|
-
`qwen serve: extensions ${operation}:
|
|
15558
|
+
`qwen serve: [${boundWorkspace}] extensions ${operation}: ${error2}`
|
|
14885
15559
|
);
|
|
15560
|
+
} catch {
|
|
14886
15561
|
}
|
|
14887
|
-
|
|
14888
|
-
`qwen serve: extensions ${operation}: mutation succeeded but refresh failed: ${message}`
|
|
14889
|
-
);
|
|
15562
|
+
return;
|
|
14890
15563
|
}
|
|
14891
|
-
} catch (err) {
|
|
14892
|
-
const message = redactUrlCredentials(
|
|
14893
|
-
err instanceof Error ? err.message : String(err)
|
|
14894
|
-
);
|
|
14895
15564
|
updateExtensionOperation(operationId, {
|
|
14896
15565
|
status: "failed",
|
|
14897
|
-
|
|
15566
|
+
phase: void 0,
|
|
15567
|
+
interaction: void 0,
|
|
15568
|
+
error: message.slice(0, 500),
|
|
15569
|
+
...code ? { code } : {}
|
|
14898
15570
|
});
|
|
14899
15571
|
try {
|
|
14900
15572
|
bridge.broadcastExtensionsChanged({
|
|
14901
15573
|
status: "failed",
|
|
14902
|
-
...failureContext.source ? { source:
|
|
15574
|
+
...failureContext.source ? { source: redactExtensionDisplaySource(failureContext.source) } : {},
|
|
14903
15575
|
...failureContext.name ? { name: failureContext.name } : {},
|
|
14904
15576
|
refreshed: 0,
|
|
14905
15577
|
failed: 0,
|
|
@@ -14907,33 +15579,25 @@ function registerWorkspaceExtensionRoutes(app, deps) {
|
|
|
14907
15579
|
});
|
|
14908
15580
|
} catch (broadcastErr) {
|
|
14909
15581
|
writeStderrLine(
|
|
14910
|
-
`qwen serve: extensions ${operation}: failed to broadcast failure: ${
|
|
15582
|
+
`qwen serve: [${boundWorkspace}] extensions ${operation}: failed to broadcast failure: ${sanitizeDaemonMessage(
|
|
15583
|
+
broadcastErr instanceof Error ? broadcastErr.message : String(broadcastErr)
|
|
15584
|
+
)}`
|
|
14911
15585
|
);
|
|
14912
15586
|
}
|
|
14913
15587
|
try {
|
|
14914
15588
|
writeStderrLine(
|
|
14915
|
-
`qwen serve: extensions ${operation}: background task failed: ${message}`
|
|
15589
|
+
`qwen serve: [${boundWorkspace}] extensions ${operation}: background task failed: ${message}`
|
|
14916
15590
|
);
|
|
14917
15591
|
} catch {
|
|
14918
15592
|
}
|
|
15593
|
+
} finally {
|
|
15594
|
+
if (deadline) clearTimeout(deadline);
|
|
15595
|
+
reconciliationReservation?.release();
|
|
15596
|
+
options.onSettled?.(operationId);
|
|
15597
|
+
releaseOperationSlot();
|
|
14919
15598
|
}
|
|
14920
|
-
})
|
|
14921
|
-
const message = redactUrlCredentials(
|
|
14922
|
-
err instanceof Error ? err.message : String(err)
|
|
14923
|
-
);
|
|
14924
|
-
updateExtensionOperation(operationId, {
|
|
14925
|
-
status: "failed",
|
|
14926
|
-
error: message.slice(0, 500)
|
|
14927
|
-
});
|
|
14928
|
-
try {
|
|
14929
|
-
writeStderrLine(
|
|
14930
|
-
`qwen serve: extensions ${operation}: queued task failed: ${message}`
|
|
14931
|
-
);
|
|
14932
|
-
} catch {
|
|
14933
|
-
}
|
|
14934
|
-
});
|
|
15599
|
+
})();
|
|
14935
15600
|
}, "runQueuedExtensionMutation");
|
|
14936
|
-
let extensionsStatusCache;
|
|
14937
15601
|
const buildLocalExtensionsStatus = /* @__PURE__ */ __name(async () => {
|
|
14938
15602
|
const now = Date.now();
|
|
14939
15603
|
if (extensionsStatusCache && extensionsStatusCache.expiresAt > now) {
|
|
@@ -14964,7 +15628,11 @@ function registerWorkspaceExtensionRoutes(app, deps) {
|
|
|
14964
15628
|
version: ext.version,
|
|
14965
15629
|
isActive: ext.isActive,
|
|
14966
15630
|
path: ext.path,
|
|
14967
|
-
...ext.installMetadata?.source ? {
|
|
15631
|
+
...ext.installMetadata?.source ? {
|
|
15632
|
+
source: redactExtensionDisplaySource(
|
|
15633
|
+
ext.installMetadata.source
|
|
15634
|
+
)
|
|
15635
|
+
} : {},
|
|
14968
15636
|
...ext.installMetadata?.type ? { installType: ext.installMetadata.type } : {},
|
|
14969
15637
|
...ext.installMetadata?.originSource ? { originSource: ext.installMetadata.originSource } : {},
|
|
14970
15638
|
...ext.installMetadata?.ref ? { ref: ext.installMetadata.ref } : {},
|
|
@@ -14988,52 +15656,470 @@ function registerWorkspaceExtensionRoutes(app, deps) {
|
|
|
14988
15656
|
extensions: entries
|
|
14989
15657
|
};
|
|
14990
15658
|
extensionsStatusCache = {
|
|
14991
|
-
expiresAt: now + 2e3,
|
|
15659
|
+
expiresAt: Date.now() + 2e3,
|
|
14992
15660
|
value: status
|
|
14993
15661
|
};
|
|
14994
15662
|
return status;
|
|
14995
15663
|
}, "buildLocalExtensionsStatus");
|
|
14996
|
-
|
|
14997
|
-
|
|
14998
|
-
|
|
14999
|
-
|
|
15000
|
-
|
|
15001
|
-
|
|
15002
|
-
|
|
15003
|
-
|
|
15004
|
-
|
|
15005
|
-
|
|
15006
|
-
|
|
15007
|
-
|
|
15008
|
-
|
|
15009
|
-
|
|
15010
|
-
|
|
15664
|
+
return {
|
|
15665
|
+
boundWorkspace,
|
|
15666
|
+
workspace,
|
|
15667
|
+
createExtensionManager,
|
|
15668
|
+
buildLocalExtensionsStatus,
|
|
15669
|
+
refreshExtensionsForAllSessions,
|
|
15670
|
+
getOperation: /* @__PURE__ */ __name((operationId) => extensionOperations.get(operationId), "getOperation"),
|
|
15671
|
+
getActiveOperations: /* @__PURE__ */ __name(() => [...extensionOperations.values()].filter(
|
|
15672
|
+
(operation) => !isTerminalExtensionOperation(operation)
|
|
15673
|
+
), "getActiveOperations"),
|
|
15674
|
+
updateOperation: updateExtensionOperation,
|
|
15675
|
+
preparationQueue,
|
|
15676
|
+
acquireOperationSlot,
|
|
15677
|
+
validateExtensionMutationClient,
|
|
15678
|
+
runQueuedExtensionMutation
|
|
15679
|
+
};
|
|
15680
|
+
}
|
|
15681
|
+
__name(createExtensionsController, "createExtensionsController");
|
|
15682
|
+
|
|
15683
|
+
// packages/cli/src/serve/routes/workspace-extensions.ts
|
|
15684
|
+
var EXTENSION_PREPARE_DEADLINE_MS = 10 * 6e4;
|
|
15685
|
+
var EXTENSION_INTERACTION_DEADLINE_MS = 10 * 6e4;
|
|
15686
|
+
var EXTENSION_INTERACTIVE_PREPARE_DEADLINE_MS = EXTENSION_PREPARE_DEADLINE_MS + EXTENSION_INTERACTION_DEADLINE_MS;
|
|
15687
|
+
var EXTENSION_UPDATE_CHECK_DEADLINE_MS = 2 * 6e4;
|
|
15688
|
+
var parseExtensionScope = /* @__PURE__ */ __name((body, res) => {
|
|
15689
|
+
const scope = body["scope"];
|
|
15690
|
+
if (scope !== "user" && scope !== "workspace") {
|
|
15691
|
+
res.status(400).json({ error: '`scope` must be either "user" or "workspace"' });
|
|
15692
|
+
return null;
|
|
15693
|
+
}
|
|
15694
|
+
return scope === "user" ? "User" /* User */ : "Workspace" /* Workspace */;
|
|
15695
|
+
}, "parseExtensionScope");
|
|
15696
|
+
var parseExtensionRegistryUrl = /* @__PURE__ */ __name((value, res) => {
|
|
15697
|
+
let parsed;
|
|
15698
|
+
try {
|
|
15699
|
+
parsed = new URL(value);
|
|
15700
|
+
} catch {
|
|
15701
|
+
res.status(400).json({ error: "`registry` must be a valid URL" });
|
|
15702
|
+
return null;
|
|
15703
|
+
}
|
|
15704
|
+
if (parsed.protocol !== "https:") {
|
|
15705
|
+
res.status(400).json({ error: "`registry` must use https" });
|
|
15706
|
+
return null;
|
|
15707
|
+
}
|
|
15708
|
+
if (parsed.username || parsed.password) {
|
|
15709
|
+
res.status(400).json({ error: "`registry` must not include credentials" });
|
|
15710
|
+
return null;
|
|
15711
|
+
}
|
|
15712
|
+
if (isBlockedAuthProviderHost(parsed.hostname)) {
|
|
15713
|
+
res.status(400).json({ error: "`registry` host is not allowed" });
|
|
15714
|
+
return null;
|
|
15715
|
+
}
|
|
15716
|
+
return parsed.toString().replace(/\/$/, "");
|
|
15717
|
+
}, "parseExtensionRegistryUrl");
|
|
15718
|
+
var parsePotentialSourceUrl = /* @__PURE__ */ __name((source) => {
|
|
15719
|
+
if (/^[a-zA-Z]:[\\/]/.test(source)) return null;
|
|
15720
|
+
try {
|
|
15721
|
+
return new URL(source);
|
|
15722
|
+
} catch {
|
|
15723
|
+
const colonIndex = source.indexOf(":");
|
|
15724
|
+
if (colonIndex >= 0 && source.slice(0, colonIndex).includes("/")) {
|
|
15725
|
+
return null;
|
|
15726
|
+
}
|
|
15727
|
+
const sshMatch = /^(?:[^@]+@)?(\[[^\]]+\]|[^:]+):/.exec(source);
|
|
15728
|
+
if (!sshMatch?.[1]) return null;
|
|
15729
|
+
try {
|
|
15730
|
+
return new URL(`ssh://${sshMatch[1]}`);
|
|
15731
|
+
} catch {
|
|
15732
|
+
return null;
|
|
15733
|
+
}
|
|
15734
|
+
}
|
|
15735
|
+
}, "parsePotentialSourceUrl");
|
|
15736
|
+
var validateExtensionSourceHost = /* @__PURE__ */ __name((source, res) => {
|
|
15737
|
+
const parsed = parsePotentialSourceUrl(source);
|
|
15738
|
+
if (!parsed) return true;
|
|
15739
|
+
if (parsed.username || parsed.password) {
|
|
15740
|
+
res.status(400).json({ error: "`source` must not include credentials" });
|
|
15741
|
+
return false;
|
|
15742
|
+
}
|
|
15743
|
+
if (isBlockedAuthProviderHost(parsed.hostname)) {
|
|
15744
|
+
res.status(400).json({ error: "`source` host is not allowed" });
|
|
15745
|
+
return false;
|
|
15746
|
+
}
|
|
15747
|
+
if (parsed.protocol !== "https:") {
|
|
15748
|
+
res.status(400).json({ error: "`source` must use https" });
|
|
15749
|
+
return false;
|
|
15750
|
+
}
|
|
15751
|
+
return true;
|
|
15752
|
+
}, "validateExtensionSourceHost");
|
|
15753
|
+
var validateExtensionSourceMetadata = /* @__PURE__ */ __name((installMetadata) => {
|
|
15754
|
+
if (installMetadata.type !== "git") return true;
|
|
15755
|
+
const parsed = parsePotentialSourceUrl(installMetadata.source);
|
|
15756
|
+
return !!parsed && (installMetadata.networkPolicy === "public" ? parsed.protocol === "https:" : parsed.protocol === "https:" || parsed.protocol === "ssh:") && !isBlockedAuthProviderHost(parsed.hostname);
|
|
15757
|
+
}, "validateExtensionSourceMetadata");
|
|
15758
|
+
var findLoadedExtension = /* @__PURE__ */ __name((extensionManager, extensionName) => {
|
|
15759
|
+
const requested = extensionName.toLowerCase();
|
|
15760
|
+
const extensions = extensionManager.getLoadedExtensions();
|
|
15761
|
+
const byName = extensions.find(
|
|
15762
|
+
(extension) => extension.name.toLowerCase() === requested
|
|
15763
|
+
);
|
|
15764
|
+
if (byName) return byName;
|
|
15765
|
+
if (!extensionName.includes("://") && !extensionName.includes("@")) {
|
|
15766
|
+
return void 0;
|
|
15767
|
+
}
|
|
15768
|
+
return extensions.find(
|
|
15769
|
+
(extension) => extension.installMetadata?.source?.toLowerCase() === requested
|
|
15770
|
+
);
|
|
15771
|
+
}, "findLoadedExtension");
|
|
15772
|
+
function registerWorkspaceExtensionRoutes(app, deps) {
|
|
15773
|
+
const {
|
|
15774
|
+
boundWorkspace,
|
|
15775
|
+
bridge,
|
|
15776
|
+
workspace,
|
|
15777
|
+
mutate,
|
|
15778
|
+
safeBody: safeBody2,
|
|
15779
|
+
sendBridgeError: sendBridgeError2,
|
|
15780
|
+
workspaceRegistry
|
|
15781
|
+
} = deps;
|
|
15782
|
+
const maxExtensionOperationHistory = deps.maxExtensionOperationHistory;
|
|
15783
|
+
const controllerDeps = /* @__PURE__ */ __name((ws, wsBridge, wsService) => ({
|
|
15784
|
+
boundWorkspace: ws,
|
|
15785
|
+
bridge: wsBridge,
|
|
15786
|
+
workspace: wsService,
|
|
15787
|
+
...maxExtensionOperationHistory === void 0 ? {} : { maxExtensionOperationHistory }
|
|
15788
|
+
}), "controllerDeps");
|
|
15789
|
+
const primaryController = createExtensionsController(
|
|
15790
|
+
controllerDeps(boundWorkspace, bridge, workspace)
|
|
15791
|
+
);
|
|
15792
|
+
const pendingExtensionInteractions = /* @__PURE__ */ new Map();
|
|
15793
|
+
const supersededInstallOperations = /* @__PURE__ */ new Set();
|
|
15794
|
+
const cancelPendingExtensionInteraction = /* @__PURE__ */ __name((operationId, reason) => {
|
|
15795
|
+
const pending = pendingExtensionInteractions.get(operationId);
|
|
15796
|
+
if (!pending) return;
|
|
15797
|
+
clearTimeout(pending.timeout);
|
|
15798
|
+
pendingExtensionInteractions.delete(operationId);
|
|
15799
|
+
pending.reject(new Error(reason));
|
|
15800
|
+
}, "cancelPendingExtensionInteraction");
|
|
15801
|
+
const supersedeActiveInstallOperations = /* @__PURE__ */ __name((controller, currentOperationId) => {
|
|
15802
|
+
for (const operation of controller.getActiveOperations()) {
|
|
15803
|
+
if (operation.operation !== "install" || operation.operationId === currentOperationId) {
|
|
15804
|
+
continue;
|
|
15011
15805
|
}
|
|
15012
|
-
|
|
15013
|
-
|
|
15014
|
-
|
|
15015
|
-
|
|
15016
|
-
|
|
15806
|
+
supersededInstallOperations.add(operation.operationId);
|
|
15807
|
+
cancelPendingExtensionInteraction(
|
|
15808
|
+
operation.operationId,
|
|
15809
|
+
"Extension installation cancelled by a new install request"
|
|
15810
|
+
);
|
|
15811
|
+
}
|
|
15812
|
+
}, "supersedeActiveInstallOperations");
|
|
15813
|
+
const waitForExtensionInteraction = /* @__PURE__ */ __name((controller, operationId, interaction) => {
|
|
15814
|
+
if (supersededInstallOperations.delete(operationId)) {
|
|
15815
|
+
return Promise.reject(
|
|
15816
|
+
new Error("Extension installation superseded by a new install request")
|
|
15817
|
+
);
|
|
15818
|
+
}
|
|
15819
|
+
const operation = controller.getOperation(operationId);
|
|
15820
|
+
if (operation && operation.status !== "queued" && operation.status !== "running" && operation.status !== "waiting_for_input") {
|
|
15821
|
+
return Promise.reject(new Error("Extension operation already completed"));
|
|
15822
|
+
}
|
|
15823
|
+
const pendingInteraction = {
|
|
15824
|
+
...interaction,
|
|
15825
|
+
id: crypto2.randomUUID()
|
|
15826
|
+
};
|
|
15827
|
+
return new Promise((resolve5, reject) => {
|
|
15828
|
+
const timeout = setTimeout(() => {
|
|
15829
|
+
pendingExtensionInteractions.delete(operationId);
|
|
15830
|
+
controller.updateOperation(operationId, {
|
|
15831
|
+
status: "failed",
|
|
15832
|
+
phase: void 0,
|
|
15833
|
+
interaction: void 0,
|
|
15834
|
+
error: "Extension interaction timed out"
|
|
15017
15835
|
});
|
|
15018
|
-
|
|
15836
|
+
reject(new Error("Extension interaction timed out"));
|
|
15837
|
+
}, EXTENSION_INTERACTION_DEADLINE_MS);
|
|
15838
|
+
timeout.unref?.();
|
|
15839
|
+
pendingExtensionInteractions.set(operationId, {
|
|
15840
|
+
interaction: pendingInteraction,
|
|
15841
|
+
resolve: resolve5,
|
|
15842
|
+
reject,
|
|
15843
|
+
timeout
|
|
15844
|
+
});
|
|
15845
|
+
controller.updateOperation(operationId, {
|
|
15846
|
+
status: "waiting_for_input",
|
|
15847
|
+
phase: void 0,
|
|
15848
|
+
interaction: pendingInteraction
|
|
15849
|
+
});
|
|
15850
|
+
});
|
|
15851
|
+
}, "waitForExtensionInteraction");
|
|
15852
|
+
const extensionInteractionHandlers = /* @__PURE__ */ __name((controller, operationId) => ({
|
|
15853
|
+
requestSetting: /* @__PURE__ */ __name((setting) => waitForExtensionInteraction(controller, operationId, {
|
|
15854
|
+
kind: "setting",
|
|
15855
|
+
setting: {
|
|
15856
|
+
name: setting.name,
|
|
15857
|
+
description: setting.description,
|
|
15858
|
+
sensitive: setting.sensitive === true
|
|
15859
|
+
}
|
|
15860
|
+
}), "requestSetting"),
|
|
15861
|
+
requestChoicePlugin: /* @__PURE__ */ __name((marketplace) => {
|
|
15862
|
+
if (marketplace.plugins.length === 0) {
|
|
15863
|
+
return Promise.reject(
|
|
15864
|
+
new Error(`Marketplace "${marketplace.name}" has no plugins`)
|
|
15865
|
+
);
|
|
15019
15866
|
}
|
|
15020
|
-
|
|
15021
|
-
|
|
15022
|
-
|
|
15023
|
-
|
|
15867
|
+
return waitForExtensionInteraction(controller, operationId, {
|
|
15868
|
+
kind: "marketplace_plugin",
|
|
15869
|
+
marketplace: { name: marketplace.name },
|
|
15870
|
+
plugins: marketplace.plugins.map((plugin) => ({
|
|
15871
|
+
name: plugin.name,
|
|
15872
|
+
...plugin.description ? { description: plugin.description } : {},
|
|
15873
|
+
source: redactExtensionDisplaySource(
|
|
15874
|
+
typeof plugin.source === "string" ? plugin.source : plugin.source.source === "github" ? plugin.source.repo : plugin.source.url
|
|
15875
|
+
),
|
|
15876
|
+
...plugin.category ? { category: plugin.category } : {},
|
|
15877
|
+
...plugin.tags ? { tags: plugin.tags } : {}
|
|
15878
|
+
}))
|
|
15024
15879
|
});
|
|
15025
|
-
}
|
|
15026
|
-
});
|
|
15027
|
-
|
|
15028
|
-
|
|
15029
|
-
|
|
15030
|
-
|
|
15880
|
+
}, "requestChoicePlugin")
|
|
15881
|
+
}), "extensionInteractionHandlers");
|
|
15882
|
+
const runtimeReconciliationQueue = createFifoTaskQueue(1);
|
|
15883
|
+
const reserveRuntimeReconciliation = /* @__PURE__ */ __name(() => {
|
|
15884
|
+
let provideTask;
|
|
15885
|
+
const task = new Promise(
|
|
15886
|
+
(resolve5) => {
|
|
15887
|
+
provideTask = resolve5;
|
|
15888
|
+
}
|
|
15889
|
+
);
|
|
15890
|
+
const queued = runtimeReconciliationQueue.run(async () => {
|
|
15891
|
+
const run = await task;
|
|
15892
|
+
return run ? await run() : void 0;
|
|
15893
|
+
});
|
|
15894
|
+
let used = false;
|
|
15895
|
+
return {
|
|
15896
|
+
run: /* @__PURE__ */ __name(async (run) => {
|
|
15897
|
+
if (used) throw new Error("Runtime reconciliation already released");
|
|
15898
|
+
used = true;
|
|
15899
|
+
provideTask(run);
|
|
15900
|
+
return await queued;
|
|
15901
|
+
}, "run"),
|
|
15902
|
+
release: /* @__PURE__ */ __name(() => {
|
|
15903
|
+
if (used) return;
|
|
15904
|
+
used = true;
|
|
15905
|
+
provideTask(void 0);
|
|
15906
|
+
}, "release")
|
|
15907
|
+
};
|
|
15908
|
+
}, "reserveRuntimeReconciliation");
|
|
15909
|
+
const appliedGenerationByWorkspaceId = /* @__PURE__ */ new Map();
|
|
15910
|
+
const onRuntimeReconciled = /* @__PURE__ */ __name((runtime, generation) => {
|
|
15911
|
+
appliedGenerationByWorkspaceId.set(runtime.workspaceId, generation);
|
|
15912
|
+
}, "onRuntimeReconciled");
|
|
15913
|
+
const globalReconciliationOptions = /* @__PURE__ */ __name(() => workspaceRegistry ? {
|
|
15914
|
+
refreshRuntimes: /* @__PURE__ */ __name(() => workspaceRegistry.list(), "refreshRuntimes"),
|
|
15915
|
+
reserveRuntimeReconciliation,
|
|
15916
|
+
onRuntimeReconciled
|
|
15917
|
+
} : {}, "globalReconciliationOptions");
|
|
15918
|
+
const workspaceReconciliationOptions = /* @__PURE__ */ __name(() => workspaceRegistry ? {
|
|
15919
|
+
refreshRuntimes: [workspaceRegistry.primary],
|
|
15920
|
+
reserveRuntimeReconciliation,
|
|
15921
|
+
onRuntimeReconciled
|
|
15922
|
+
} : {}, "workspaceReconciliationOptions");
|
|
15923
|
+
const mutationClientBridges = /* @__PURE__ */ __name((runtimes) => (typeof runtimes === "function" ? runtimes() : runtimes ?? workspaceRegistry?.list())?.map((runtime) => runtime.bridge) ?? [bridge], "mutationClientBridges");
|
|
15924
|
+
if (workspaceRegistry) {
|
|
15925
|
+
let observedGeneration;
|
|
15926
|
+
let reconciling = false;
|
|
15927
|
+
const reconcileExternalGeneration = /* @__PURE__ */ __name(async () => {
|
|
15928
|
+
if (reconciling) return;
|
|
15929
|
+
reconciling = true;
|
|
15031
15930
|
try {
|
|
15032
|
-
|
|
15033
|
-
|
|
15034
|
-
|
|
15035
|
-
|
|
15036
|
-
))
|
|
15931
|
+
const manager = primaryController.createExtensionManager(
|
|
15932
|
+
boundWorkspace,
|
|
15933
|
+
true
|
|
15934
|
+
);
|
|
15935
|
+
const generation = (await manager.getExtensionStoreSnapshot()).generation;
|
|
15936
|
+
const pendingRuntimes = workspaceRegistry.list().filter(
|
|
15937
|
+
(runtime) => (appliedGenerationByWorkspaceId.get(runtime.workspaceId) ?? 0) !== generation
|
|
15938
|
+
);
|
|
15939
|
+
if (generation === observedGeneration && pendingRuntimes.length === 0)
|
|
15940
|
+
return;
|
|
15941
|
+
const runtimes = pendingRuntimes;
|
|
15942
|
+
if (runtimes.length === 0) return;
|
|
15943
|
+
const results = await runtimeReconciliationQueue.run(
|
|
15944
|
+
async () => await Promise.allSettled(
|
|
15945
|
+
runtimes.map(async (runtime) => {
|
|
15946
|
+
runtime.workspaceService.invalidateWorkspaceSkillsStatus();
|
|
15947
|
+
const result = await runtime.bridge.refreshExtensionsForAllSessions();
|
|
15948
|
+
if (result.failed > 0) {
|
|
15949
|
+
throw new Error(
|
|
15950
|
+
`${result.failed} extension session refresh(es) failed`
|
|
15951
|
+
);
|
|
15952
|
+
}
|
|
15953
|
+
})
|
|
15954
|
+
)
|
|
15955
|
+
);
|
|
15956
|
+
results.forEach((result, index) => {
|
|
15957
|
+
if (result.status === "fulfilled") {
|
|
15958
|
+
const workspaceId = runtimes[index].workspaceId;
|
|
15959
|
+
appliedGenerationByWorkspaceId.set(workspaceId, generation);
|
|
15960
|
+
} else {
|
|
15961
|
+
writeStderrLine(
|
|
15962
|
+
`qwen serve: extension generation reconciliation failed for workspace ${runtimes[index].workspaceId}: ${redactUrlCredentials(
|
|
15963
|
+
result.reason instanceof Error ? result.reason.message : String(result.reason)
|
|
15964
|
+
)}`
|
|
15965
|
+
);
|
|
15966
|
+
}
|
|
15967
|
+
});
|
|
15968
|
+
if (runtimes.length === pendingRuntimes.length && results.every((result) => result.status === "fulfilled")) {
|
|
15969
|
+
observedGeneration = generation;
|
|
15970
|
+
}
|
|
15971
|
+
} catch (error2) {
|
|
15972
|
+
writeStderrLine(
|
|
15973
|
+
`qwen serve: extension generation reconciliation failed: ${redactUrlCredentials(
|
|
15974
|
+
error2 instanceof Error ? error2.message : String(error2)
|
|
15975
|
+
)}`
|
|
15976
|
+
);
|
|
15977
|
+
} finally {
|
|
15978
|
+
reconciling = false;
|
|
15979
|
+
}
|
|
15980
|
+
}, "reconcileExternalGeneration");
|
|
15981
|
+
const generationPoller = setInterval(
|
|
15982
|
+
() => void reconcileExternalGeneration(),
|
|
15983
|
+
3e4
|
|
15984
|
+
);
|
|
15985
|
+
generationPoller.unref();
|
|
15986
|
+
app.locals.stopExtensionGenerationReconciler = () => clearInterval(generationPoller);
|
|
15987
|
+
}
|
|
15988
|
+
const registerFor = /* @__PURE__ */ __name((base, resolve5) => {
|
|
15989
|
+
app.get(base, async (req, res) => {
|
|
15990
|
+
const ctrl = resolve5(req, res, false);
|
|
15991
|
+
if (!ctrl) return;
|
|
15992
|
+
try {
|
|
15993
|
+
res.status(200).json(await ctrl.buildLocalExtensionsStatus());
|
|
15994
|
+
} catch (err) {
|
|
15995
|
+
sendBridgeError2(res, err, { route: `GET ${base}` });
|
|
15996
|
+
}
|
|
15997
|
+
});
|
|
15998
|
+
app.get(`${base}/operations`, async (req, res) => {
|
|
15999
|
+
const ctrl = resolve5(req, res, false);
|
|
16000
|
+
if (!ctrl) return;
|
|
16001
|
+
res.status(200).json({ v: 1, operations: ctrl.getActiveOperations() });
|
|
16002
|
+
});
|
|
16003
|
+
app.get(`${base}/operations/:operationId`, async (req, res) => {
|
|
16004
|
+
const ctrl = resolve5(req, res, false);
|
|
16005
|
+
if (!ctrl) return;
|
|
16006
|
+
try {
|
|
16007
|
+
const operationId = req.params["operationId"];
|
|
16008
|
+
if (!operationId) {
|
|
16009
|
+
res.status(400).json({ error: "Missing extension operation id" });
|
|
16010
|
+
return;
|
|
16011
|
+
}
|
|
16012
|
+
const operation = ctrl.getOperation(operationId);
|
|
16013
|
+
if (!operation) {
|
|
16014
|
+
res.status(404).json({
|
|
16015
|
+
error: `Extension operation "${operationId}" not found`,
|
|
16016
|
+
code: "extension_operation_not_found"
|
|
16017
|
+
});
|
|
16018
|
+
return;
|
|
16019
|
+
}
|
|
16020
|
+
if (base === "/workspace/extensions" && operation.status === "succeeded_with_warnings") {
|
|
16021
|
+
const warningError = operation.warnings?.find((warning) => warning.code === void 0)?.error ?? operation.warnings?.[0]?.error ?? operation.result?.error;
|
|
16022
|
+
const legacyOperation = {
|
|
16023
|
+
...operation,
|
|
16024
|
+
status: "succeeded_with_refresh_error"
|
|
16025
|
+
};
|
|
16026
|
+
if (operation.result && warningError) {
|
|
16027
|
+
legacyOperation.result = {
|
|
16028
|
+
...operation.result,
|
|
16029
|
+
error: warningError
|
|
16030
|
+
};
|
|
16031
|
+
} else if (warningError) {
|
|
16032
|
+
legacyOperation.error = warningError;
|
|
16033
|
+
}
|
|
16034
|
+
res.status(200).json(legacyOperation);
|
|
16035
|
+
return;
|
|
16036
|
+
}
|
|
16037
|
+
res.status(200).json(operation);
|
|
16038
|
+
} catch (err) {
|
|
16039
|
+
sendBridgeError2(res, err, {
|
|
16040
|
+
route: `GET ${base}/operations/:operationId`
|
|
16041
|
+
});
|
|
16042
|
+
}
|
|
16043
|
+
});
|
|
16044
|
+
app.post(
|
|
16045
|
+
`${base}/operations/:operationId/interactions/:interactionId`,
|
|
16046
|
+
mutate({ strict: true }),
|
|
16047
|
+
async (req, res) => {
|
|
16048
|
+
const ctrl = resolve5(req, res, false);
|
|
16049
|
+
if (!ctrl) return;
|
|
16050
|
+
try {
|
|
16051
|
+
if (!ctrl.validateExtensionMutationClient(req, res, {
|
|
16052
|
+
requireClientId: false
|
|
16053
|
+
})) {
|
|
16054
|
+
return;
|
|
16055
|
+
}
|
|
16056
|
+
const operationId = req.params["operationId"];
|
|
16057
|
+
const interactionId = req.params["interactionId"];
|
|
16058
|
+
const pending = operationId ? pendingExtensionInteractions.get(operationId) : void 0;
|
|
16059
|
+
if (!operationId || !interactionId || !pending) {
|
|
16060
|
+
res.status(404).json({ error: "Extension interaction not found" });
|
|
16061
|
+
return;
|
|
16062
|
+
}
|
|
16063
|
+
if (pending.interaction.id !== interactionId) {
|
|
16064
|
+
res.status(404).json({ error: "Extension interaction not found" });
|
|
16065
|
+
return;
|
|
16066
|
+
}
|
|
16067
|
+
const body = safeBody2(req);
|
|
16068
|
+
let value;
|
|
16069
|
+
if (body["cancelled"] === true) {
|
|
16070
|
+
clearTimeout(pending.timeout);
|
|
16071
|
+
pendingExtensionInteractions.delete(operationId);
|
|
16072
|
+
ctrl.updateOperation(operationId, {
|
|
16073
|
+
status: "failed",
|
|
16074
|
+
phase: void 0,
|
|
16075
|
+
interaction: void 0,
|
|
16076
|
+
error: "Extension operation cancelled"
|
|
16077
|
+
});
|
|
16078
|
+
pending.reject(new Error("Extension operation cancelled"));
|
|
16079
|
+
res.status(200).json({ accepted: true });
|
|
16080
|
+
return;
|
|
16081
|
+
}
|
|
16082
|
+
if (pending.interaction.kind === "marketplace_plugin") {
|
|
16083
|
+
const pluginName = body["pluginName"];
|
|
16084
|
+
if (typeof pluginName !== "string" || !pending.interaction.plugins.some(
|
|
16085
|
+
(plugin) => plugin.name === pluginName
|
|
16086
|
+
)) {
|
|
16087
|
+
res.status(400).json({ error: "Invalid marketplace plugin name" });
|
|
16088
|
+
return;
|
|
16089
|
+
}
|
|
16090
|
+
value = pluginName;
|
|
16091
|
+
} else {
|
|
16092
|
+
const settingValue = body["value"];
|
|
16093
|
+
if (typeof settingValue !== "string") {
|
|
16094
|
+
res.status(400).json({ error: "Extension setting value must be a string" });
|
|
16095
|
+
return;
|
|
16096
|
+
}
|
|
16097
|
+
value = settingValue;
|
|
16098
|
+
}
|
|
16099
|
+
clearTimeout(pending.timeout);
|
|
16100
|
+
pendingExtensionInteractions.delete(operationId);
|
|
16101
|
+
ctrl.updateOperation(operationId, {
|
|
16102
|
+
status: "running",
|
|
16103
|
+
phase: "preparing",
|
|
16104
|
+
interaction: void 0,
|
|
16105
|
+
error: void 0
|
|
16106
|
+
});
|
|
16107
|
+
pending.resolve(value);
|
|
16108
|
+
res.status(200).json({ accepted: true });
|
|
16109
|
+
} catch (err) {
|
|
16110
|
+
sendBridgeError2(res, err, {
|
|
16111
|
+
route: `POST ${base}/operations/:operationId/interactions/:interactionId`
|
|
16112
|
+
});
|
|
16113
|
+
}
|
|
16114
|
+
}
|
|
16115
|
+
);
|
|
16116
|
+
app.post(`${base}/install`, mutate({ strict: true }), async (req, res) => {
|
|
16117
|
+
const ctrl = resolve5(req, res, true);
|
|
16118
|
+
if (!ctrl) return;
|
|
16119
|
+
try {
|
|
16120
|
+
if (!ctrl.validateExtensionMutationClient(req, res, {
|
|
16121
|
+
requireClientId: false
|
|
16122
|
+
})) {
|
|
15037
16123
|
return;
|
|
15038
16124
|
}
|
|
15039
16125
|
const body = safeBody2(req);
|
|
@@ -15083,139 +16169,405 @@ function registerWorkspaceExtensionRoutes(app, deps) {
|
|
|
15083
16169
|
if (!validateExtensionSourceHost(sourceValue, res)) {
|
|
15084
16170
|
return;
|
|
15085
16171
|
}
|
|
15086
|
-
|
|
16172
|
+
const localSource = /^[A-Za-z]:[\\/]/.test(sourceValue) || sourceValue.startsWith("/") || sourceValue.startsWith(".");
|
|
16173
|
+
if (localSource) {
|
|
16174
|
+
try {
|
|
16175
|
+
const metadata = await parseInstallSource(sourceValue, {
|
|
16176
|
+
networkPolicy: "public"
|
|
16177
|
+
});
|
|
16178
|
+
if (metadata.type !== "git" && metadata.type !== "github-release" && metadata.type !== "npm") {
|
|
16179
|
+
res.status(400).json({
|
|
16180
|
+
error: "Only GitHub, Git, and npm extension installs are supported over the daemon endpoint."
|
|
16181
|
+
});
|
|
16182
|
+
return;
|
|
16183
|
+
}
|
|
16184
|
+
} catch (error2) {
|
|
16185
|
+
const message = error2 instanceof Error ? error2.message : "Invalid install source";
|
|
16186
|
+
res.status(400).json({
|
|
16187
|
+
error: redactUrlCredentials(
|
|
16188
|
+
message.replaceAll(
|
|
16189
|
+
sourceValue,
|
|
16190
|
+
redactExtensionDisplaySource(sourceValue)
|
|
16191
|
+
)
|
|
16192
|
+
)
|
|
16193
|
+
});
|
|
16194
|
+
return;
|
|
16195
|
+
}
|
|
16196
|
+
}
|
|
16197
|
+
const npmSource = sourceValue.startsWith("@");
|
|
16198
|
+
const remoteSource = npmSource || sourceValue.startsWith("https://") || /^[^/\s]+\/[^/\s]+/.test(sourceValue);
|
|
16199
|
+
if (!localSource && !remoteSource) {
|
|
16200
|
+
res.status(400).json({
|
|
16201
|
+
error: `Install source not found: ${redactExtensionDisplaySource(
|
|
16202
|
+
sourceValue
|
|
16203
|
+
)}`
|
|
16204
|
+
});
|
|
16205
|
+
return;
|
|
16206
|
+
}
|
|
16207
|
+
if (npmSource && refValue) {
|
|
16208
|
+
res.status(400).json({ error: "--ref is not applicable for npm extensions." });
|
|
16209
|
+
return;
|
|
16210
|
+
}
|
|
16211
|
+
if (!npmSource && registryValue) {
|
|
16212
|
+
res.status(400).json({
|
|
16213
|
+
error: "--registry is only applicable for npm extensions."
|
|
16214
|
+
});
|
|
16215
|
+
return;
|
|
16216
|
+
}
|
|
16217
|
+
ctrl.runQueuedExtensionMutation(
|
|
15087
16218
|
"install",
|
|
15088
16219
|
{ source: sourceValue },
|
|
15089
16220
|
res,
|
|
15090
|
-
async (extensionManager) => {
|
|
15091
|
-
const
|
|
15092
|
-
|
|
15093
|
-
|
|
15094
|
-
|
|
16221
|
+
async (extensionManager, _signal, context, operationId) => {
|
|
16222
|
+
const prepared = await context.prepare(async (signal) => {
|
|
16223
|
+
const installMetadata = await parseInstallSource(sourceValue, {
|
|
16224
|
+
networkPolicy: "public"
|
|
16225
|
+
});
|
|
16226
|
+
if (installMetadata.type !== "git" && installMetadata.type !== "github-release" && installMetadata.type !== "npm") {
|
|
16227
|
+
throw new Error(
|
|
16228
|
+
"Only GitHub, Git, and npm extension installs are supported over the daemon endpoint."
|
|
16229
|
+
);
|
|
16230
|
+
}
|
|
16231
|
+
if (installMetadata.type === "npm" && refValue) {
|
|
16232
|
+
throw new Error("--ref is not applicable for npm extensions.");
|
|
16233
|
+
}
|
|
16234
|
+
if (installMetadata.type !== "npm" && registryValue) {
|
|
16235
|
+
throw new Error(
|
|
16236
|
+
"--registry is only applicable for npm extensions."
|
|
16237
|
+
);
|
|
16238
|
+
}
|
|
16239
|
+
if (!validateExtensionSourceMetadata(installMetadata)) {
|
|
16240
|
+
throw new Error("`source` host is not allowed");
|
|
16241
|
+
}
|
|
16242
|
+
if (installMetadata.type === "npm" && registryUrl) {
|
|
16243
|
+
installMetadata.registryUrl = registryUrl;
|
|
16244
|
+
}
|
|
16245
|
+
supersedeActiveInstallOperations(ctrl, operationId);
|
|
16246
|
+
return await extensionManager.prepareExtensionInstall({
|
|
16247
|
+
installMetadata: {
|
|
16248
|
+
...installMetadata,
|
|
16249
|
+
ref: refValue,
|
|
16250
|
+
autoUpdate: autoUpdateValue,
|
|
16251
|
+
allowPreRelease: allowPreReleaseValue
|
|
16252
|
+
},
|
|
16253
|
+
initialActivation: { scope: "user" },
|
|
16254
|
+
requestConsent: /* @__PURE__ */ __name(() => Promise.resolve(), "requestConsent"),
|
|
16255
|
+
signal
|
|
16256
|
+
});
|
|
16257
|
+
});
|
|
16258
|
+
try {
|
|
16259
|
+
const committed = await context.commit(
|
|
16260
|
+
async (onCommitted) => await extensionManager.commitPreparedExtension(
|
|
16261
|
+
prepared,
|
|
16262
|
+
onCommitted
|
|
16263
|
+
)
|
|
15095
16264
|
);
|
|
16265
|
+
return {
|
|
16266
|
+
status: "installed",
|
|
16267
|
+
source: sourceValue,
|
|
16268
|
+
name: committed.identity.name,
|
|
16269
|
+
version: committed.version
|
|
16270
|
+
};
|
|
16271
|
+
} finally {
|
|
16272
|
+
await extensionManager.disposePreparedExtension(prepared);
|
|
15096
16273
|
}
|
|
15097
|
-
|
|
15098
|
-
|
|
15099
|
-
|
|
15100
|
-
|
|
15101
|
-
|
|
15102
|
-
|
|
16274
|
+
},
|
|
16275
|
+
{
|
|
16276
|
+
createManager: /* @__PURE__ */ __name((operationId) => ctrl.createExtensionManager(
|
|
16277
|
+
void 0,
|
|
16278
|
+
void 0,
|
|
16279
|
+
extensionInteractionHandlers(ctrl, operationId)
|
|
16280
|
+
), "createManager"),
|
|
16281
|
+
onSettled: /* @__PURE__ */ __name((operationId) => {
|
|
16282
|
+
supersededInstallOperations.delete(operationId);
|
|
16283
|
+
cancelPendingExtensionInteraction(
|
|
16284
|
+
operationId,
|
|
16285
|
+
"Extension operation ended"
|
|
15103
16286
|
);
|
|
15104
|
-
}
|
|
15105
|
-
|
|
15106
|
-
|
|
15107
|
-
}
|
|
15108
|
-
if (installMetadata.type === "npm" && registryUrl) {
|
|
15109
|
-
installMetadata.registryUrl = registryUrl;
|
|
15110
|
-
}
|
|
15111
|
-
const extension = await extensionManager.installExtension(
|
|
15112
|
-
{
|
|
15113
|
-
...installMetadata,
|
|
15114
|
-
ref: refValue,
|
|
15115
|
-
autoUpdate: autoUpdateValue,
|
|
15116
|
-
allowPreRelease: allowPreReleaseValue
|
|
15117
|
-
},
|
|
15118
|
-
() => Promise.resolve()
|
|
15119
|
-
);
|
|
15120
|
-
return {
|
|
15121
|
-
status: "installed",
|
|
15122
|
-
source: sourceValue,
|
|
15123
|
-
name: extension.name,
|
|
15124
|
-
version: extension.config.version
|
|
15125
|
-
};
|
|
16287
|
+
}, "onSettled"),
|
|
16288
|
+
deadlineMs: EXTENSION_INTERACTIVE_PREPARE_DEADLINE_MS,
|
|
16289
|
+
...globalReconciliationOptions()
|
|
15126
16290
|
}
|
|
15127
16291
|
);
|
|
15128
16292
|
} catch (err) {
|
|
15129
|
-
sendBridgeError2(res, err, {
|
|
15130
|
-
route: "POST /workspace/extensions/install"
|
|
15131
|
-
});
|
|
16293
|
+
sendBridgeError2(res, err, { route: `POST ${base}/install` });
|
|
15132
16294
|
}
|
|
15133
|
-
}
|
|
15134
|
-
|
|
15135
|
-
|
|
15136
|
-
|
|
15137
|
-
|
|
15138
|
-
|
|
16295
|
+
});
|
|
16296
|
+
app.post(
|
|
16297
|
+
`${base}/check-updates`,
|
|
16298
|
+
mutate({ strict: true }),
|
|
16299
|
+
async (req, res) => {
|
|
16300
|
+
const ctrl = resolve5(req, res, true);
|
|
16301
|
+
if (!ctrl) return;
|
|
16302
|
+
let timer;
|
|
16303
|
+
let releaseOperationSlot;
|
|
16304
|
+
try {
|
|
16305
|
+
if (!ctrl.validateExtensionMutationClient(req, res, {
|
|
16306
|
+
requireClientId: false
|
|
16307
|
+
})) {
|
|
16308
|
+
return;
|
|
16309
|
+
}
|
|
16310
|
+
releaseOperationSlot = ctrl.acquireOperationSlot(res);
|
|
16311
|
+
if (!releaseOperationSlot) return;
|
|
16312
|
+
const extensionManager = ctrl.createExtensionManager();
|
|
16313
|
+
const updateStates = /* @__PURE__ */ Object.create(null);
|
|
16314
|
+
const deadline = new AbortController();
|
|
16315
|
+
timer = setTimeout(() => {
|
|
16316
|
+
const error2 = new Error(
|
|
16317
|
+
"Extension update check exceeded its preparation deadline."
|
|
16318
|
+
);
|
|
16319
|
+
error2.code = "extension_prepare_timeout";
|
|
16320
|
+
deadline.abort(error2);
|
|
16321
|
+
}, EXTENSION_UPDATE_CHECK_DEADLINE_MS);
|
|
16322
|
+
timer.unref();
|
|
16323
|
+
let rejectRefreshOnAbort;
|
|
16324
|
+
try {
|
|
16325
|
+
await Promise.race([
|
|
16326
|
+
extensionManager.refreshCache(),
|
|
16327
|
+
new Promise((_resolve, reject) => {
|
|
16328
|
+
rejectRefreshOnAbort = /* @__PURE__ */ __name(() => reject(deadline.signal.reason), "rejectRefreshOnAbort");
|
|
16329
|
+
deadline.signal.addEventListener(
|
|
16330
|
+
"abort",
|
|
16331
|
+
rejectRefreshOnAbort,
|
|
16332
|
+
{ once: true }
|
|
16333
|
+
);
|
|
16334
|
+
})
|
|
16335
|
+
]);
|
|
16336
|
+
} finally {
|
|
16337
|
+
if (rejectRefreshOnAbort) {
|
|
16338
|
+
deadline.signal.removeEventListener(
|
|
16339
|
+
"abort",
|
|
16340
|
+
rejectRefreshOnAbort
|
|
16341
|
+
);
|
|
16342
|
+
}
|
|
16343
|
+
}
|
|
16344
|
+
await extensionManager.checkForAllExtensionUpdates(
|
|
16345
|
+
(name, state) => {
|
|
16346
|
+
updateStates[name] = state;
|
|
16347
|
+
},
|
|
16348
|
+
deadline.signal,
|
|
16349
|
+
async (task) => await ctrl.preparationQueue.run(task, {
|
|
16350
|
+
signal: deadline.signal
|
|
16351
|
+
})
|
|
16352
|
+
);
|
|
16353
|
+
const states = updateStates;
|
|
16354
|
+
res.status(200).json({ states });
|
|
16355
|
+
} catch (err) {
|
|
16356
|
+
sendBridgeError2(res, err, { route: `POST ${base}/check-updates` });
|
|
16357
|
+
} finally {
|
|
16358
|
+
if (timer) clearTimeout(timer);
|
|
16359
|
+
releaseOperationSlot?.();
|
|
16360
|
+
}
|
|
16361
|
+
}
|
|
16362
|
+
);
|
|
16363
|
+
app.post(`${base}/refresh`, mutate({ strict: true }), async (req, res) => {
|
|
16364
|
+
const ctrl = resolve5(req, res, true);
|
|
16365
|
+
if (!ctrl) return;
|
|
15139
16366
|
try {
|
|
15140
|
-
if (!validateExtensionMutationClient(
|
|
15141
|
-
|
|
15142
|
-
|
|
15143
|
-
"POST /workspace/extensions/check-updates"
|
|
15144
|
-
)) {
|
|
16367
|
+
if (!ctrl.validateExtensionMutationClient(req, res, {
|
|
16368
|
+
requireClientId: false
|
|
16369
|
+
})) {
|
|
15145
16370
|
return;
|
|
15146
16371
|
}
|
|
15147
|
-
const
|
|
15148
|
-
|
|
15149
|
-
|
|
15150
|
-
|
|
15151
|
-
|
|
15152
|
-
|
|
15153
|
-
|
|
15154
|
-
(name, state) => {
|
|
15155
|
-
updateStates[name] = state;
|
|
15156
|
-
}
|
|
15157
|
-
);
|
|
15158
|
-
return updateStates;
|
|
15159
|
-
})(),
|
|
15160
|
-
EXTENSION_REFRESH_TIMEOUT_MS,
|
|
15161
|
-
"extension update check"
|
|
15162
|
-
)
|
|
15163
|
-
);
|
|
15164
|
-
res.status(200).json({ states });
|
|
15165
|
-
} catch (err) {
|
|
15166
|
-
if (isExtensionQueueFullError(err)) {
|
|
15167
|
-
sendExtensionQueueFull(res);
|
|
15168
|
-
return;
|
|
16372
|
+
const releaseOperationSlot = ctrl.acquireOperationSlot(res);
|
|
16373
|
+
if (!releaseOperationSlot) return;
|
|
16374
|
+
try {
|
|
16375
|
+
const result = await ctrl.refreshExtensionsForAllSessions();
|
|
16376
|
+
res.status(200).json(result);
|
|
16377
|
+
} finally {
|
|
16378
|
+
releaseOperationSlot();
|
|
15169
16379
|
}
|
|
15170
|
-
|
|
15171
|
-
|
|
15172
|
-
});
|
|
16380
|
+
} catch (err) {
|
|
16381
|
+
sendBridgeError2(res, err, { route: `POST ${base}/refresh` });
|
|
15173
16382
|
}
|
|
15174
|
-
}
|
|
15175
|
-
|
|
15176
|
-
|
|
15177
|
-
|
|
15178
|
-
|
|
15179
|
-
|
|
15180
|
-
|
|
15181
|
-
|
|
15182
|
-
req,
|
|
15183
|
-
|
|
15184
|
-
|
|
15185
|
-
|
|
15186
|
-
|
|
16383
|
+
});
|
|
16384
|
+
app.post(
|
|
16385
|
+
`${base}/:name/enable`,
|
|
16386
|
+
mutate({ strict: true }),
|
|
16387
|
+
async (req, res) => {
|
|
16388
|
+
const ctrl = resolve5(req, res, true);
|
|
16389
|
+
if (!ctrl) return;
|
|
16390
|
+
try {
|
|
16391
|
+
if (!ctrl.validateExtensionMutationClient(req, res, {
|
|
16392
|
+
requireClientId: false
|
|
16393
|
+
})) {
|
|
16394
|
+
return;
|
|
16395
|
+
}
|
|
16396
|
+
const name = req.params["name"];
|
|
16397
|
+
if (!name) {
|
|
16398
|
+
res.status(400).json({ error: "Missing extension name" });
|
|
16399
|
+
return;
|
|
16400
|
+
}
|
|
16401
|
+
const scope = parseExtensionScope(safeBody2(req), res);
|
|
16402
|
+
if (scope === null) return;
|
|
16403
|
+
ctrl.runQueuedExtensionMutation(
|
|
16404
|
+
"enable",
|
|
16405
|
+
{ name },
|
|
16406
|
+
res,
|
|
16407
|
+
async (extensionManager, _signal, context) => {
|
|
16408
|
+
const extension = findLoadedExtension(extensionManager, name);
|
|
16409
|
+
if (!extension) {
|
|
16410
|
+
throw new Error(`Extension "${name}" not found`);
|
|
16411
|
+
}
|
|
16412
|
+
await context.commit(
|
|
16413
|
+
async (onCommitted) => await extensionManager.enableExtension(
|
|
16414
|
+
extension.name,
|
|
16415
|
+
scope,
|
|
16416
|
+
ctrl.boundWorkspace,
|
|
16417
|
+
onCommitted
|
|
16418
|
+
)
|
|
16419
|
+
);
|
|
16420
|
+
return { status: "enabled", name: extension.name };
|
|
16421
|
+
},
|
|
16422
|
+
{
|
|
16423
|
+
...scope === "User" /* User */ ? globalReconciliationOptions() : workspaceReconciliationOptions()
|
|
16424
|
+
}
|
|
16425
|
+
);
|
|
16426
|
+
} catch (err) {
|
|
16427
|
+
sendBridgeError2(res, err, { route: `POST ${base}/:name/enable` });
|
|
15187
16428
|
}
|
|
15188
|
-
|
|
15189
|
-
|
|
15190
|
-
|
|
15191
|
-
|
|
15192
|
-
|
|
15193
|
-
|
|
15194
|
-
);
|
|
15195
|
-
|
|
15196
|
-
|
|
15197
|
-
|
|
15198
|
-
|
|
15199
|
-
|
|
15200
|
-
|
|
16429
|
+
}
|
|
16430
|
+
);
|
|
16431
|
+
app.post(
|
|
16432
|
+
`${base}/:name/disable`,
|
|
16433
|
+
mutate({ strict: true }),
|
|
16434
|
+
async (req, res) => {
|
|
16435
|
+
const ctrl = resolve5(req, res, true);
|
|
16436
|
+
if (!ctrl) return;
|
|
16437
|
+
try {
|
|
16438
|
+
if (!ctrl.validateExtensionMutationClient(req, res, {
|
|
16439
|
+
requireClientId: false
|
|
16440
|
+
})) {
|
|
16441
|
+
return;
|
|
16442
|
+
}
|
|
16443
|
+
const name = req.params["name"];
|
|
16444
|
+
if (!name) {
|
|
16445
|
+
res.status(400).json({ error: "Missing extension name" });
|
|
16446
|
+
return;
|
|
16447
|
+
}
|
|
16448
|
+
const scope = parseExtensionScope(safeBody2(req), res);
|
|
16449
|
+
if (scope === null) return;
|
|
16450
|
+
ctrl.runQueuedExtensionMutation(
|
|
16451
|
+
"disable",
|
|
16452
|
+
{ name },
|
|
16453
|
+
res,
|
|
16454
|
+
async (extensionManager, _signal, context) => {
|
|
16455
|
+
const extension = findLoadedExtension(extensionManager, name);
|
|
16456
|
+
if (!extension) {
|
|
16457
|
+
throw new Error(`Extension "${name}" not found`);
|
|
16458
|
+
}
|
|
16459
|
+
await context.commit(
|
|
16460
|
+
async (onCommitted) => await extensionManager.disableExtension(
|
|
16461
|
+
extension.name,
|
|
16462
|
+
scope,
|
|
16463
|
+
ctrl.boundWorkspace,
|
|
16464
|
+
onCommitted
|
|
16465
|
+
)
|
|
16466
|
+
);
|
|
16467
|
+
return { status: "disabled", name: extension.name };
|
|
16468
|
+
},
|
|
16469
|
+
{
|
|
16470
|
+
...scope === "User" /* User */ ? globalReconciliationOptions() : workspaceReconciliationOptions()
|
|
16471
|
+
}
|
|
16472
|
+
);
|
|
16473
|
+
} catch (err) {
|
|
16474
|
+
sendBridgeError2(res, err, { route: `POST ${base}/:name/disable` });
|
|
15201
16475
|
}
|
|
15202
|
-
sendBridgeError2(res, err, {
|
|
15203
|
-
route: "POST /workspace/extensions/refresh"
|
|
15204
|
-
});
|
|
15205
16476
|
}
|
|
15206
|
-
|
|
15207
|
-
|
|
15208
|
-
|
|
15209
|
-
|
|
15210
|
-
|
|
15211
|
-
|
|
16477
|
+
);
|
|
16478
|
+
app.post(
|
|
16479
|
+
`${base}/:name/update`,
|
|
16480
|
+
mutate({ strict: true }),
|
|
16481
|
+
async (req, res) => {
|
|
16482
|
+
const ctrl = resolve5(req, res, true);
|
|
16483
|
+
if (!ctrl) return;
|
|
16484
|
+
try {
|
|
16485
|
+
if (!ctrl.validateExtensionMutationClient(req, res, {
|
|
16486
|
+
requireClientId: false
|
|
16487
|
+
})) {
|
|
16488
|
+
return;
|
|
16489
|
+
}
|
|
16490
|
+
const name = req.params["name"];
|
|
16491
|
+
if (!name) {
|
|
16492
|
+
res.status(400).json({ error: "Missing extension name" });
|
|
16493
|
+
return;
|
|
16494
|
+
}
|
|
16495
|
+
ctrl.runQueuedExtensionMutation(
|
|
16496
|
+
"update",
|
|
16497
|
+
{ name },
|
|
16498
|
+
res,
|
|
16499
|
+
async (extensionManager, _signal, context) => {
|
|
16500
|
+
const extension = findLoadedExtension(extensionManager, name);
|
|
16501
|
+
if (!extension) {
|
|
16502
|
+
throw new Error(`Extension "${name}" not found`);
|
|
16503
|
+
}
|
|
16504
|
+
let preparedResult;
|
|
16505
|
+
try {
|
|
16506
|
+
preparedResult = await context.prepare(
|
|
16507
|
+
async (signal) => await extensionManager.prepareExtensionUpdate({
|
|
16508
|
+
extension,
|
|
16509
|
+
signal
|
|
16510
|
+
})
|
|
16511
|
+
);
|
|
16512
|
+
} catch (error2) {
|
|
16513
|
+
const wrapped = new Error(
|
|
16514
|
+
`Update check failed for extension "${extension.name}": ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
16515
|
+
{ cause: error2 }
|
|
16516
|
+
);
|
|
16517
|
+
if (error2 && typeof error2 === "object" && typeof error2.code === "string") {
|
|
16518
|
+
wrapped.code = error2.code;
|
|
16519
|
+
}
|
|
16520
|
+
throw wrapped;
|
|
16521
|
+
}
|
|
16522
|
+
if (preparedResult.upToDate) {
|
|
16523
|
+
throw new Error(`Extension "${extension.name}" has no update`);
|
|
16524
|
+
}
|
|
16525
|
+
try {
|
|
16526
|
+
const committed = await context.commit(
|
|
16527
|
+
async (onCommitted) => await extensionManager.commitPreparedExtension(
|
|
16528
|
+
preparedResult.prepared,
|
|
16529
|
+
onCommitted
|
|
16530
|
+
)
|
|
16531
|
+
);
|
|
16532
|
+
return {
|
|
16533
|
+
status: "updated",
|
|
16534
|
+
name: extension.name,
|
|
16535
|
+
version: committed.version
|
|
16536
|
+
};
|
|
16537
|
+
} finally {
|
|
16538
|
+
await extensionManager.disposePreparedExtension(
|
|
16539
|
+
preparedResult.prepared
|
|
16540
|
+
);
|
|
16541
|
+
}
|
|
16542
|
+
},
|
|
16543
|
+
{
|
|
16544
|
+
createManager: /* @__PURE__ */ __name((operationId) => ctrl.createExtensionManager(
|
|
16545
|
+
void 0,
|
|
16546
|
+
void 0,
|
|
16547
|
+
extensionInteractionHandlers(ctrl, operationId)
|
|
16548
|
+
), "createManager"),
|
|
16549
|
+
onSettled: /* @__PURE__ */ __name((operationId) => {
|
|
16550
|
+
cancelPendingExtensionInteraction(
|
|
16551
|
+
operationId,
|
|
16552
|
+
"Extension operation ended"
|
|
16553
|
+
);
|
|
16554
|
+
}, "onSettled"),
|
|
16555
|
+
deadlineMs: EXTENSION_INTERACTIVE_PREPARE_DEADLINE_MS,
|
|
16556
|
+
...globalReconciliationOptions()
|
|
16557
|
+
}
|
|
16558
|
+
);
|
|
16559
|
+
} catch (err) {
|
|
16560
|
+
sendBridgeError2(res, err, { route: `POST ${base}/:name/update` });
|
|
16561
|
+
}
|
|
16562
|
+
}
|
|
16563
|
+
);
|
|
16564
|
+
app.delete(`${base}/:name`, mutate({ strict: true }), async (req, res) => {
|
|
16565
|
+
const ctrl = resolve5(req, res, true);
|
|
16566
|
+
if (!ctrl) return;
|
|
15212
16567
|
try {
|
|
15213
|
-
if (!validateExtensionMutationClient(
|
|
15214
|
-
|
|
15215
|
-
|
|
15216
|
-
"POST /workspace/extensions/:name/enable",
|
|
15217
|
-
{ requireClientId: false }
|
|
15218
|
-
)) {
|
|
16568
|
+
if (!ctrl.validateExtensionMutationClient(req, res, {
|
|
16569
|
+
requireClientId: false
|
|
16570
|
+
})) {
|
|
15219
16571
|
return;
|
|
15220
16572
|
}
|
|
15221
16573
|
const name = req.params["name"];
|
|
@@ -15223,184 +16575,604 @@ function registerWorkspaceExtensionRoutes(app, deps) {
|
|
|
15223
16575
|
res.status(400).json({ error: "Missing extension name" });
|
|
15224
16576
|
return;
|
|
15225
16577
|
}
|
|
15226
|
-
|
|
15227
|
-
|
|
15228
|
-
runQueuedExtensionMutation(
|
|
15229
|
-
"enable",
|
|
16578
|
+
ctrl.runQueuedExtensionMutation(
|
|
16579
|
+
"uninstall",
|
|
15230
16580
|
{ name },
|
|
15231
16581
|
res,
|
|
15232
|
-
async (extensionManager) => {
|
|
16582
|
+
async (extensionManager, _signal, context) => {
|
|
15233
16583
|
const extension = findLoadedExtension(extensionManager, name);
|
|
15234
16584
|
if (!extension) {
|
|
15235
16585
|
throw new Error(`Extension "${name}" not found`);
|
|
15236
16586
|
}
|
|
15237
|
-
await
|
|
15238
|
-
|
|
15239
|
-
|
|
15240
|
-
|
|
16587
|
+
await context.commit(
|
|
16588
|
+
async (onCommitted) => await extensionManager.uninstallExtension(
|
|
16589
|
+
extension.name,
|
|
16590
|
+
false,
|
|
16591
|
+
ctrl.boundWorkspace,
|
|
16592
|
+
onCommitted
|
|
16593
|
+
)
|
|
15241
16594
|
);
|
|
15242
|
-
return { status: "
|
|
16595
|
+
return { status: "uninstalled", name: extension.name };
|
|
16596
|
+
},
|
|
16597
|
+
{
|
|
16598
|
+
...globalReconciliationOptions()
|
|
15243
16599
|
}
|
|
15244
16600
|
);
|
|
15245
16601
|
} catch (err) {
|
|
15246
|
-
sendBridgeError2(res, err, {
|
|
15247
|
-
|
|
16602
|
+
sendBridgeError2(res, err, { route: `DELETE ${base}/:name` });
|
|
16603
|
+
}
|
|
16604
|
+
});
|
|
16605
|
+
}, "registerFor");
|
|
16606
|
+
registerFor("/workspace/extensions", () => primaryController);
|
|
16607
|
+
const extensionById = /* @__PURE__ */ __name((manager, extensionId) => manager.getLoadedExtensions().find((extension) => extension.id === extensionId), "extensionById");
|
|
16608
|
+
const parseExtensionId = /* @__PURE__ */ __name((req, res) => {
|
|
16609
|
+
const extensionId = req.params["extensionId"];
|
|
16610
|
+
if (!extensionId || !/^[a-f0-9]{64}$/.test(extensionId)) {
|
|
16611
|
+
res.status(400).json({
|
|
16612
|
+
error: "Invalid extension id",
|
|
16613
|
+
code: "invalid_extension_id"
|
|
16614
|
+
});
|
|
16615
|
+
return null;
|
|
16616
|
+
}
|
|
16617
|
+
return extensionId;
|
|
16618
|
+
}, "parseExtensionId");
|
|
16619
|
+
const parseActivationState = /* @__PURE__ */ __name((req, res) => {
|
|
16620
|
+
const state = safeBody2(req)["state"];
|
|
16621
|
+
if (state !== "enabled" && state !== "disabled") {
|
|
16622
|
+
res.status(400).json({
|
|
16623
|
+
error: '`state` must be either "enabled" or "disabled"',
|
|
16624
|
+
code: "invalid_extension_activation"
|
|
16625
|
+
});
|
|
16626
|
+
return null;
|
|
16627
|
+
}
|
|
16628
|
+
return state;
|
|
16629
|
+
}, "parseActivationState");
|
|
16630
|
+
const sendOperation = /* @__PURE__ */ __name((req, res, route, manager, operation, failureContext, run, options = {}) => {
|
|
16631
|
+
if (!primaryController.validateExtensionMutationClient(req, res, {
|
|
16632
|
+
requireClientId: false,
|
|
16633
|
+
bridges: mutationClientBridges(options.refreshRuntimes)
|
|
16634
|
+
})) {
|
|
16635
|
+
return;
|
|
16636
|
+
}
|
|
16637
|
+
primaryController.runQueuedExtensionMutation(
|
|
16638
|
+
operation,
|
|
16639
|
+
failureContext,
|
|
16640
|
+
res,
|
|
16641
|
+
run,
|
|
16642
|
+
{
|
|
16643
|
+
manager,
|
|
16644
|
+
operationBasePath: "/extensions/operations",
|
|
16645
|
+
onRuntimeReconciled,
|
|
16646
|
+
reserveRuntimeReconciliation,
|
|
16647
|
+
...options
|
|
16648
|
+
}
|
|
16649
|
+
);
|
|
16650
|
+
}, "sendOperation");
|
|
16651
|
+
app.get("/extensions", async (_req, res) => {
|
|
16652
|
+
try {
|
|
16653
|
+
const manager = primaryController.createExtensionManager(
|
|
16654
|
+
boundWorkspace,
|
|
16655
|
+
true
|
|
16656
|
+
);
|
|
16657
|
+
const snapshot = await manager.refreshCacheWithSnapshot();
|
|
16658
|
+
res.status(200).json({
|
|
16659
|
+
v: 1,
|
|
16660
|
+
generation: snapshot.generation,
|
|
16661
|
+
extensions: manager.getLoadedExtensions().map((extension) => {
|
|
16662
|
+
const policy = snapshot.extensions[extension.id];
|
|
16663
|
+
return {
|
|
16664
|
+
id: extension.id,
|
|
16665
|
+
name: extension.name,
|
|
16666
|
+
version: extension.version,
|
|
16667
|
+
...extension.installMetadata?.type ? { installType: extension.installMetadata.type } : {},
|
|
16668
|
+
defaultActivation: policy?.defaultActivation ?? "enabled",
|
|
16669
|
+
workspaceOverrideCount: Object.values(
|
|
16670
|
+
policy?.workspaceOverrides ?? {}
|
|
16671
|
+
).filter((activation) => activation !== "inherit").length
|
|
16672
|
+
};
|
|
16673
|
+
})
|
|
16674
|
+
});
|
|
16675
|
+
} catch (error2) {
|
|
16676
|
+
sendBridgeError2(res, error2, { route: "GET /extensions" });
|
|
16677
|
+
}
|
|
16678
|
+
});
|
|
16679
|
+
app.get("/extensions/operations/:operationId", (req, res) => {
|
|
16680
|
+
const operationId = req.params["operationId"];
|
|
16681
|
+
if (!operationId) {
|
|
16682
|
+
res.status(400).json({ error: "Missing extension operation id" });
|
|
16683
|
+
return;
|
|
16684
|
+
}
|
|
16685
|
+
const operation = primaryController.getOperation(operationId);
|
|
16686
|
+
if (!operation) {
|
|
16687
|
+
res.status(404).json({
|
|
16688
|
+
error: `Extension operation "${operationId}" not found`,
|
|
16689
|
+
code: "extension_operation_not_found"
|
|
16690
|
+
});
|
|
16691
|
+
return;
|
|
16692
|
+
}
|
|
16693
|
+
res.status(200).json(operation);
|
|
16694
|
+
});
|
|
16695
|
+
app.put(
|
|
16696
|
+
"/extensions/:extensionId/activation",
|
|
16697
|
+
mutate({ strict: true }),
|
|
16698
|
+
async (req, res) => {
|
|
16699
|
+
const extensionId = parseExtensionId(req, res);
|
|
16700
|
+
if (!extensionId) return;
|
|
16701
|
+
const state = parseActivationState(req, res);
|
|
16702
|
+
if (!state) return;
|
|
16703
|
+
const manager = primaryController.createExtensionManager(
|
|
16704
|
+
boundWorkspace,
|
|
16705
|
+
true
|
|
16706
|
+
);
|
|
16707
|
+
sendOperation(
|
|
16708
|
+
req,
|
|
16709
|
+
res,
|
|
16710
|
+
"PUT /extensions/:extensionId/activation",
|
|
16711
|
+
manager,
|
|
16712
|
+
"activation",
|
|
16713
|
+
{ name: extensionId },
|
|
16714
|
+
async (extensionManager, _signal, context) => {
|
|
16715
|
+
const extension = extensionById(extensionManager, extensionId);
|
|
16716
|
+
if (!extension)
|
|
16717
|
+
throw new Error(`Extension "${extensionId}" not found`);
|
|
16718
|
+
await context.commit(
|
|
16719
|
+
async (onCommitted) => await extensionManager.setExtensionDefaultActivation(
|
|
16720
|
+
extensionId,
|
|
16721
|
+
state,
|
|
16722
|
+
onCommitted
|
|
16723
|
+
)
|
|
16724
|
+
);
|
|
16725
|
+
return {
|
|
16726
|
+
status: state === "enabled" ? "enabled" : "disabled",
|
|
16727
|
+
name: extension.name
|
|
16728
|
+
};
|
|
16729
|
+
},
|
|
16730
|
+
{
|
|
16731
|
+
...workspaceRegistry ? { refreshRuntimes: /* @__PURE__ */ __name(() => workspaceRegistry.list(), "refreshRuntimes") } : {}
|
|
16732
|
+
}
|
|
16733
|
+
);
|
|
16734
|
+
}
|
|
16735
|
+
);
|
|
16736
|
+
app.post("/extensions/install", mutate({ strict: true }), (req, res) => {
|
|
16737
|
+
const body = safeBody2(req);
|
|
16738
|
+
const source = body["source"];
|
|
16739
|
+
const activation = body["activation"];
|
|
16740
|
+
const ref = body["ref"];
|
|
16741
|
+
const autoUpdate = body["autoUpdate"];
|
|
16742
|
+
const allowPreRelease = body["allowPreRelease"];
|
|
16743
|
+
const registry = body["registry"];
|
|
16744
|
+
if (typeof source !== "string" || !source) {
|
|
16745
|
+
res.status(400).json({ error: "Missing or invalid source" });
|
|
16746
|
+
return;
|
|
16747
|
+
}
|
|
16748
|
+
if (ref !== void 0 && (typeof ref !== "string" || !ref)) {
|
|
16749
|
+
res.status(400).json({ error: "`ref` must be a non-empty string" });
|
|
16750
|
+
return;
|
|
16751
|
+
}
|
|
16752
|
+
if (typeof ref === "string" && ref.startsWith("-")) {
|
|
16753
|
+
res.status(400).json({ error: '`ref` must not start with "-"' });
|
|
16754
|
+
return;
|
|
16755
|
+
}
|
|
16756
|
+
if (autoUpdate !== void 0 && typeof autoUpdate !== "boolean") {
|
|
16757
|
+
res.status(400).json({ error: "`autoUpdate` must be a boolean" });
|
|
16758
|
+
return;
|
|
16759
|
+
}
|
|
16760
|
+
if (allowPreRelease !== void 0 && typeof allowPreRelease !== "boolean") {
|
|
16761
|
+
res.status(400).json({ error: "`allowPreRelease` must be a boolean" });
|
|
16762
|
+
return;
|
|
16763
|
+
}
|
|
16764
|
+
if (registry !== void 0 && typeof registry !== "string") {
|
|
16765
|
+
res.status(400).json({ error: "`registry` must be a string" });
|
|
16766
|
+
return;
|
|
16767
|
+
}
|
|
16768
|
+
const registryUrl = typeof registry === "string" ? parseExtensionRegistryUrl(registry, res) : void 0;
|
|
16769
|
+
if (registryUrl === null) return;
|
|
16770
|
+
if (body["consent"] !== true) {
|
|
16771
|
+
res.status(400).json({
|
|
16772
|
+
error: "Extension installation requires explicit consent"
|
|
16773
|
+
});
|
|
16774
|
+
return;
|
|
16775
|
+
}
|
|
16776
|
+
if (!validateExtensionSourceHost(source, res)) return;
|
|
16777
|
+
if (!activation || typeof activation !== "object") {
|
|
16778
|
+
res.status(400).json({ error: "Missing initial activation" });
|
|
16779
|
+
return;
|
|
16780
|
+
}
|
|
16781
|
+
const activationRecord = activation;
|
|
16782
|
+
let initialActivation;
|
|
16783
|
+
if (activationRecord["scope"] === "user") {
|
|
16784
|
+
initialActivation = { scope: "user" };
|
|
16785
|
+
} else if (activationRecord["scope"] === "workspace" && typeof activationRecord["workspaceId"] === "string" && workspaceRegistry) {
|
|
16786
|
+
const runtime = workspaceRegistry.getByWorkspaceId(
|
|
16787
|
+
activationRecord["workspaceId"]
|
|
16788
|
+
);
|
|
16789
|
+
if (!runtime) {
|
|
16790
|
+
res.status(400).json({
|
|
16791
|
+
error: "Unknown activation workspace",
|
|
16792
|
+
code: "workspace_mismatch"
|
|
15248
16793
|
});
|
|
16794
|
+
return;
|
|
15249
16795
|
}
|
|
16796
|
+
if (!requireTrustedWorkspaceRuntime(runtime, res)) return;
|
|
16797
|
+
initialActivation = {
|
|
16798
|
+
scope: "workspace",
|
|
16799
|
+
workspacePath: runtime.workspaceCwd
|
|
16800
|
+
};
|
|
16801
|
+
} else {
|
|
16802
|
+
res.status(400).json({ error: "Invalid initial activation" });
|
|
16803
|
+
return;
|
|
16804
|
+
}
|
|
16805
|
+
const manager = primaryController.createExtensionManager(
|
|
16806
|
+
boundWorkspace,
|
|
16807
|
+
true
|
|
16808
|
+
);
|
|
16809
|
+
sendOperation(
|
|
16810
|
+
req,
|
|
16811
|
+
res,
|
|
16812
|
+
"POST /extensions/install",
|
|
16813
|
+
manager,
|
|
16814
|
+
"install",
|
|
16815
|
+
{ source },
|
|
16816
|
+
async (extensionManager, _signal, context) => {
|
|
16817
|
+
const prepared = await context.prepare(async (signal) => {
|
|
16818
|
+
const metadata = await parseInstallSource(source, {
|
|
16819
|
+
networkPolicy: "public"
|
|
16820
|
+
});
|
|
16821
|
+
if (metadata.type !== "git" && metadata.type !== "github-release" && metadata.type !== "npm") {
|
|
16822
|
+
throw new Error(
|
|
16823
|
+
"Only GitHub, Git, and npm extension installs are supported over the daemon endpoint."
|
|
16824
|
+
);
|
|
16825
|
+
}
|
|
16826
|
+
if (!validateExtensionSourceMetadata(metadata)) {
|
|
16827
|
+
throw new Error("`source` host is not allowed");
|
|
16828
|
+
}
|
|
16829
|
+
if (metadata.type === "npm" && ref !== void 0) {
|
|
16830
|
+
throw new Error("--ref is not applicable for npm extensions.");
|
|
16831
|
+
}
|
|
16832
|
+
if (metadata.type !== "npm" && registryUrl !== void 0) {
|
|
16833
|
+
throw new Error(
|
|
16834
|
+
"--registry is only applicable for npm extensions."
|
|
16835
|
+
);
|
|
16836
|
+
}
|
|
16837
|
+
if (metadata.type === "npm" && registryUrl) {
|
|
16838
|
+
metadata.registryUrl = registryUrl;
|
|
16839
|
+
}
|
|
16840
|
+
return await extensionManager.prepareExtensionInstall({
|
|
16841
|
+
installMetadata: {
|
|
16842
|
+
...metadata,
|
|
16843
|
+
...typeof ref === "string" ? { ref } : {},
|
|
16844
|
+
...typeof autoUpdate === "boolean" ? { autoUpdate } : {},
|
|
16845
|
+
...typeof allowPreRelease === "boolean" ? { allowPreRelease } : {}
|
|
16846
|
+
},
|
|
16847
|
+
requestConsent: /* @__PURE__ */ __name(() => Promise.resolve(), "requestConsent"),
|
|
16848
|
+
cwd: boundWorkspace,
|
|
16849
|
+
initialActivation,
|
|
16850
|
+
signal
|
|
16851
|
+
});
|
|
16852
|
+
});
|
|
16853
|
+
try {
|
|
16854
|
+
const committed = await context.commit(
|
|
16855
|
+
async (onCommitted) => await extensionManager.commitPreparedExtension(
|
|
16856
|
+
prepared,
|
|
16857
|
+
onCommitted
|
|
16858
|
+
)
|
|
16859
|
+
);
|
|
16860
|
+
return {
|
|
16861
|
+
status: "installed",
|
|
16862
|
+
source,
|
|
16863
|
+
name: committed.identity.name,
|
|
16864
|
+
version: committed.version
|
|
16865
|
+
};
|
|
16866
|
+
} finally {
|
|
16867
|
+
await extensionManager.disposePreparedExtension(prepared);
|
|
16868
|
+
}
|
|
16869
|
+
},
|
|
16870
|
+
{
|
|
16871
|
+
deadlineMs: EXTENSION_PREPARE_DEADLINE_MS,
|
|
16872
|
+
...workspaceRegistry ? { refreshRuntimes: /* @__PURE__ */ __name(() => workspaceRegistry.list(), "refreshRuntimes") } : {}
|
|
16873
|
+
}
|
|
16874
|
+
);
|
|
16875
|
+
});
|
|
16876
|
+
app.post(
|
|
16877
|
+
"/extensions/check-updates",
|
|
16878
|
+
mutate({ strict: true }),
|
|
16879
|
+
(req, res) => {
|
|
16880
|
+
const manager = primaryController.createExtensionManager(
|
|
16881
|
+
boundWorkspace,
|
|
16882
|
+
true
|
|
16883
|
+
);
|
|
16884
|
+
sendOperation(
|
|
16885
|
+
req,
|
|
16886
|
+
res,
|
|
16887
|
+
"POST /extensions/check-updates",
|
|
16888
|
+
manager,
|
|
16889
|
+
"check-updates",
|
|
16890
|
+
{},
|
|
16891
|
+
async (extensionManager, signal, context) => {
|
|
16892
|
+
const states = /* @__PURE__ */ Object.create(null);
|
|
16893
|
+
await extensionManager.checkForAllExtensionUpdates(
|
|
16894
|
+
(name, state) => {
|
|
16895
|
+
states[name] = state;
|
|
16896
|
+
},
|
|
16897
|
+
signal,
|
|
16898
|
+
async (task) => await context.prepare(async () => await task())
|
|
16899
|
+
);
|
|
16900
|
+
return { status: "checked", states };
|
|
16901
|
+
},
|
|
16902
|
+
{
|
|
16903
|
+
skipRefresh: true,
|
|
16904
|
+
deadlineMs: EXTENSION_UPDATE_CHECK_DEADLINE_MS
|
|
16905
|
+
}
|
|
16906
|
+
);
|
|
15250
16907
|
}
|
|
15251
16908
|
);
|
|
15252
16909
|
app.post(
|
|
15253
|
-
"/
|
|
16910
|
+
"/extensions/:extensionId/update",
|
|
16911
|
+
mutate({ strict: true }),
|
|
16912
|
+
(req, res) => {
|
|
16913
|
+
const extensionId = parseExtensionId(req, res);
|
|
16914
|
+
if (!extensionId) return;
|
|
16915
|
+
const manager = primaryController.createExtensionManager(
|
|
16916
|
+
boundWorkspace,
|
|
16917
|
+
true
|
|
16918
|
+
);
|
|
16919
|
+
sendOperation(
|
|
16920
|
+
req,
|
|
16921
|
+
res,
|
|
16922
|
+
"POST /extensions/:extensionId/update",
|
|
16923
|
+
manager,
|
|
16924
|
+
"update",
|
|
16925
|
+
{ name: extensionId },
|
|
16926
|
+
async (extensionManager, _signal, context) => {
|
|
16927
|
+
const extension = extensionById(extensionManager, extensionId);
|
|
16928
|
+
if (!extension)
|
|
16929
|
+
throw new Error(`Extension "${extensionId}" not found`);
|
|
16930
|
+
if (extension.installMetadata?.type !== "git" && extension.installMetadata?.type !== "archive-url" && extension.installMetadata?.type !== "github-release" && extension.installMetadata?.type !== "npm") {
|
|
16931
|
+
throw new Error(
|
|
16932
|
+
`Extension "${extension.name}" is not remotely updatable.`
|
|
16933
|
+
);
|
|
16934
|
+
}
|
|
16935
|
+
const preparedResult = await context.prepare(
|
|
16936
|
+
async (signal) => await extensionManager.prepareExtensionUpdate({
|
|
16937
|
+
extension,
|
|
16938
|
+
signal
|
|
16939
|
+
})
|
|
16940
|
+
);
|
|
16941
|
+
if (preparedResult.upToDate) {
|
|
16942
|
+
return {
|
|
16943
|
+
status: "checked",
|
|
16944
|
+
name: extension.name,
|
|
16945
|
+
updated: false,
|
|
16946
|
+
reason: "up_to_date"
|
|
16947
|
+
};
|
|
16948
|
+
}
|
|
16949
|
+
try {
|
|
16950
|
+
const committed = await context.commit(
|
|
16951
|
+
async (onCommitted) => await extensionManager.commitPreparedExtension(
|
|
16952
|
+
preparedResult.prepared,
|
|
16953
|
+
onCommitted
|
|
16954
|
+
)
|
|
16955
|
+
);
|
|
16956
|
+
return {
|
|
16957
|
+
status: "updated",
|
|
16958
|
+
name: extension.name,
|
|
16959
|
+
updated: true,
|
|
16960
|
+
version: committed.version
|
|
16961
|
+
};
|
|
16962
|
+
} finally {
|
|
16963
|
+
await extensionManager.disposePreparedExtension(
|
|
16964
|
+
preparedResult.prepared
|
|
16965
|
+
);
|
|
16966
|
+
}
|
|
16967
|
+
},
|
|
16968
|
+
{
|
|
16969
|
+
deadlineMs: EXTENSION_PREPARE_DEADLINE_MS,
|
|
16970
|
+
...workspaceRegistry ? { refreshRuntimes: /* @__PURE__ */ __name(() => workspaceRegistry.list(), "refreshRuntimes") } : {}
|
|
16971
|
+
}
|
|
16972
|
+
);
|
|
16973
|
+
}
|
|
16974
|
+
);
|
|
16975
|
+
app.delete(
|
|
16976
|
+
"/extensions/:extensionId",
|
|
15254
16977
|
mutate({ strict: true }),
|
|
15255
16978
|
async (req, res) => {
|
|
16979
|
+
const extensionId = parseExtensionId(req, res);
|
|
16980
|
+
if (!extensionId) return;
|
|
16981
|
+
const route = "DELETE /extensions/:extensionId";
|
|
16982
|
+
if (!primaryController.validateExtensionMutationClient(req, res, {
|
|
16983
|
+
requireClientId: false,
|
|
16984
|
+
bridges: mutationClientBridges()
|
|
16985
|
+
})) {
|
|
16986
|
+
return;
|
|
16987
|
+
}
|
|
15256
16988
|
try {
|
|
15257
|
-
|
|
15258
|
-
|
|
15259
|
-
|
|
15260
|
-
|
|
15261
|
-
|
|
15262
|
-
|
|
15263
|
-
|
|
15264
|
-
|
|
15265
|
-
const name = req.params["name"];
|
|
15266
|
-
if (!name) {
|
|
15267
|
-
res.status(400).json({ error: "Missing extension name" });
|
|
16989
|
+
const manager = primaryController.createExtensionManager(
|
|
16990
|
+
boundWorkspace,
|
|
16991
|
+
true
|
|
16992
|
+
);
|
|
16993
|
+
const snapshot = await manager.getExtensionStoreSnapshot();
|
|
16994
|
+
const policy = snapshot.extensions[extensionId];
|
|
16995
|
+
if (!policy) {
|
|
16996
|
+
res.status(204).end();
|
|
15268
16997
|
return;
|
|
15269
16998
|
}
|
|
15270
|
-
|
|
15271
|
-
|
|
15272
|
-
runQueuedExtensionMutation(
|
|
15273
|
-
"disable",
|
|
15274
|
-
{ name },
|
|
16999
|
+
sendOperation(
|
|
17000
|
+
req,
|
|
15275
17001
|
res,
|
|
15276
|
-
|
|
15277
|
-
|
|
15278
|
-
|
|
15279
|
-
|
|
15280
|
-
|
|
15281
|
-
await
|
|
15282
|
-
|
|
15283
|
-
|
|
15284
|
-
|
|
17002
|
+
route,
|
|
17003
|
+
manager,
|
|
17004
|
+
"uninstall",
|
|
17005
|
+
{ name: policy.name },
|
|
17006
|
+
async (extensionManager, _signal, context) => {
|
|
17007
|
+
await context.commit(
|
|
17008
|
+
async (onCommitted) => await extensionManager.uninstallExtensionById(
|
|
17009
|
+
extensionId,
|
|
17010
|
+
false,
|
|
17011
|
+
void 0,
|
|
17012
|
+
onCommitted
|
|
17013
|
+
)
|
|
15285
17014
|
);
|
|
15286
|
-
return { status: "
|
|
17015
|
+
return { status: "uninstalled", name: policy.name };
|
|
17016
|
+
},
|
|
17017
|
+
{
|
|
17018
|
+
...workspaceRegistry ? { refreshRuntimes: /* @__PURE__ */ __name(() => workspaceRegistry.list(), "refreshRuntimes") } : {}
|
|
15287
17019
|
}
|
|
15288
17020
|
);
|
|
15289
|
-
} catch (
|
|
15290
|
-
sendBridgeError2(res,
|
|
15291
|
-
route: "POST /workspace/extensions/:name/disable"
|
|
15292
|
-
});
|
|
17021
|
+
} catch (error2) {
|
|
17022
|
+
sendBridgeError2(res, error2, { route });
|
|
15293
17023
|
}
|
|
15294
17024
|
}
|
|
15295
17025
|
);
|
|
15296
|
-
|
|
15297
|
-
|
|
15298
|
-
|
|
15299
|
-
|
|
17026
|
+
if (workspaceRegistry) {
|
|
17027
|
+
const registry = workspaceRegistry;
|
|
17028
|
+
app.get("/workspaces/:workspace/extensions", async (req, res) => {
|
|
17029
|
+
const runtime = resolveWorkspaceRuntimeFromParam(registry, req, res);
|
|
17030
|
+
if (!runtime) return;
|
|
15300
17031
|
try {
|
|
15301
|
-
|
|
15302
|
-
|
|
15303
|
-
|
|
15304
|
-
|
|
15305
|
-
)
|
|
15306
|
-
|
|
15307
|
-
|
|
15308
|
-
|
|
15309
|
-
|
|
15310
|
-
|
|
15311
|
-
|
|
15312
|
-
|
|
15313
|
-
|
|
15314
|
-
|
|
15315
|
-
|
|
17032
|
+
const manager = primaryController.createExtensionManager(
|
|
17033
|
+
runtime.workspaceCwd,
|
|
17034
|
+
runtime.trusted
|
|
17035
|
+
);
|
|
17036
|
+
const snapshot = await manager.refreshCacheWithSnapshot();
|
|
17037
|
+
const extensions = manager.getLoadedExtensions().map((extension) => {
|
|
17038
|
+
const activation = manager.getExtensionActivationFromSnapshot(
|
|
17039
|
+
extension.id,
|
|
17040
|
+
snapshot,
|
|
17041
|
+
runtime.workspaceCwd
|
|
17042
|
+
);
|
|
17043
|
+
return {
|
|
17044
|
+
extensionId: extension.id,
|
|
17045
|
+
name: extension.name,
|
|
17046
|
+
version: extension.version,
|
|
17047
|
+
defaultActivation: activation.default,
|
|
17048
|
+
workspaceActivation: activation.workspace === "inherit" ? null : activation.workspace,
|
|
17049
|
+
effectiveActivation: activation.effective,
|
|
17050
|
+
activationSource: activation.source
|
|
17051
|
+
};
|
|
17052
|
+
});
|
|
17053
|
+
res.status(200).json({
|
|
17054
|
+
v: 1,
|
|
17055
|
+
workspaceId: runtime.workspaceId,
|
|
17056
|
+
workspaceCwd: runtime.workspaceCwd,
|
|
17057
|
+
trusted: runtime.trusted,
|
|
17058
|
+
desiredGeneration: snapshot.generation,
|
|
17059
|
+
appliedGeneration: appliedGenerationByWorkspaceId.get(runtime.workspaceId) ?? 0,
|
|
17060
|
+
extensions
|
|
17061
|
+
});
|
|
17062
|
+
} catch (error2) {
|
|
17063
|
+
sendBridgeError2(res, error2, {
|
|
17064
|
+
route: "GET /workspaces/:workspace/extensions"
|
|
17065
|
+
});
|
|
17066
|
+
}
|
|
17067
|
+
});
|
|
17068
|
+
app.put(
|
|
17069
|
+
"/workspaces/:workspace/extensions/:extensionId/activation",
|
|
17070
|
+
mutate({ strict: true }),
|
|
17071
|
+
(req, res) => {
|
|
17072
|
+
const runtime = resolveWorkspaceRuntimeFromParam(registry, req, res);
|
|
17073
|
+
if (!runtime || !requireTrustedWorkspaceRuntime(runtime, res)) return;
|
|
17074
|
+
const extensionId = parseExtensionId(req, res);
|
|
17075
|
+
if (!extensionId) return;
|
|
17076
|
+
const state = parseActivationState(req, res);
|
|
17077
|
+
if (!state) return;
|
|
17078
|
+
const manager = primaryController.createExtensionManager(
|
|
17079
|
+
runtime.workspaceCwd,
|
|
17080
|
+
true
|
|
17081
|
+
);
|
|
17082
|
+
sendOperation(
|
|
17083
|
+
req,
|
|
15316
17084
|
res,
|
|
15317
|
-
|
|
15318
|
-
|
|
17085
|
+
"PUT /workspaces/:workspace/extensions/:extensionId/activation",
|
|
17086
|
+
manager,
|
|
17087
|
+
"activation",
|
|
17088
|
+
{ name: extensionId },
|
|
17089
|
+
async (extensionManager, _signal, context) => {
|
|
17090
|
+
const extension = extensionById(extensionManager, extensionId);
|
|
15319
17091
|
if (!extension) {
|
|
15320
|
-
throw new Error(`Extension "${
|
|
15321
|
-
}
|
|
15322
|
-
let updateError;
|
|
15323
|
-
const updateState = await withExtensionTimeout(
|
|
15324
|
-
checkForExtensionUpdate(extension, extensionManager).catch(
|
|
15325
|
-
(err) => {
|
|
15326
|
-
updateError = err;
|
|
15327
|
-
return "error" /* ERROR */;
|
|
15328
|
-
}
|
|
15329
|
-
),
|
|
15330
|
-
EXTENSION_REFRESH_TIMEOUT_MS,
|
|
15331
|
-
"extension update check"
|
|
15332
|
-
);
|
|
15333
|
-
if (updateState === "error" /* ERROR */) {
|
|
15334
|
-
const message = updateError === void 0 ? void 0 : redactUrlCredentials(
|
|
15335
|
-
updateError instanceof Error ? updateError.message : String(updateError)
|
|
15336
|
-
);
|
|
15337
|
-
throw new Error(
|
|
15338
|
-
`Update check failed for extension "${extension.name}"${message ? `: ${message}` : ""}`
|
|
15339
|
-
);
|
|
17092
|
+
throw new Error(`Extension "${extensionId}" not found`);
|
|
15340
17093
|
}
|
|
15341
|
-
|
|
15342
|
-
|
|
15343
|
-
|
|
15344
|
-
|
|
15345
|
-
|
|
15346
|
-
|
|
15347
|
-
|
|
17094
|
+
await context.commit(
|
|
17095
|
+
async (onCommitted) => await extensionManager.setExtensionWorkspaceActivation(
|
|
17096
|
+
extensionId,
|
|
17097
|
+
runtime.workspaceCwd,
|
|
17098
|
+
state,
|
|
17099
|
+
onCommitted
|
|
17100
|
+
)
|
|
15348
17101
|
);
|
|
15349
17102
|
return {
|
|
15350
|
-
status: "
|
|
15351
|
-
name: extension.name
|
|
15352
|
-
...info?.updatedVersion ? { version: info.updatedVersion } : {}
|
|
17103
|
+
status: state === "enabled" ? "enabled" : "disabled",
|
|
17104
|
+
name: extension.name
|
|
15353
17105
|
};
|
|
15354
|
-
}
|
|
17106
|
+
},
|
|
17107
|
+
{ refreshRuntimes: [runtime] }
|
|
15355
17108
|
);
|
|
15356
|
-
} catch (err) {
|
|
15357
|
-
sendBridgeError2(res, err, {
|
|
15358
|
-
route: "POST /workspace/extensions/:name/update"
|
|
15359
|
-
});
|
|
15360
17109
|
}
|
|
15361
|
-
|
|
15362
|
-
|
|
15363
|
-
|
|
15364
|
-
|
|
15365
|
-
|
|
15366
|
-
|
|
15367
|
-
|
|
15368
|
-
|
|
17110
|
+
);
|
|
17111
|
+
app.delete(
|
|
17112
|
+
"/workspaces/:workspace/extensions/:extensionId/activation",
|
|
17113
|
+
mutate({ strict: true }),
|
|
17114
|
+
(req, res) => {
|
|
17115
|
+
const runtime = resolveWorkspaceRuntimeFromParam(registry, req, res);
|
|
17116
|
+
if (!runtime || !requireTrustedWorkspaceRuntime(runtime, res)) return;
|
|
17117
|
+
const extensionId = parseExtensionId(req, res);
|
|
17118
|
+
if (!extensionId) return;
|
|
17119
|
+
const manager = primaryController.createExtensionManager(
|
|
17120
|
+
runtime.workspaceCwd,
|
|
17121
|
+
true
|
|
17122
|
+
);
|
|
17123
|
+
sendOperation(
|
|
15369
17124
|
req,
|
|
15370
17125
|
res,
|
|
15371
|
-
"DELETE /workspace/extensions/:
|
|
15372
|
-
|
|
15373
|
-
|
|
15374
|
-
|
|
15375
|
-
|
|
15376
|
-
|
|
15377
|
-
res.status(400).json({ error: "Missing extension name" });
|
|
15378
|
-
return;
|
|
15379
|
-
}
|
|
15380
|
-
runQueuedExtensionMutation(
|
|
15381
|
-
"uninstall",
|
|
15382
|
-
{ name },
|
|
15383
|
-
res,
|
|
15384
|
-
async (extensionManager) => {
|
|
15385
|
-
const extension = findLoadedExtension(extensionManager, name);
|
|
17126
|
+
"DELETE /workspaces/:workspace/extensions/:extensionId/activation",
|
|
17127
|
+
manager,
|
|
17128
|
+
"activation",
|
|
17129
|
+
{ name: extensionId },
|
|
17130
|
+
async (extensionManager, _signal, context) => {
|
|
17131
|
+
const extension = extensionById(extensionManager, extensionId);
|
|
15386
17132
|
if (!extension) {
|
|
15387
|
-
throw new Error(`Extension "${
|
|
17133
|
+
throw new Error(`Extension "${extensionId}" not found`);
|
|
15388
17134
|
}
|
|
15389
|
-
await
|
|
15390
|
-
|
|
15391
|
-
|
|
15392
|
-
|
|
17135
|
+
const snapshot = await context.commit(
|
|
17136
|
+
async (onCommitted) => await extensionManager.clearExtensionWorkspaceActivation(
|
|
17137
|
+
extensionId,
|
|
17138
|
+
runtime.workspaceCwd,
|
|
17139
|
+
onCommitted
|
|
17140
|
+
)
|
|
15393
17141
|
);
|
|
15394
|
-
|
|
15395
|
-
|
|
17142
|
+
const activation = extensionManager.getExtensionActivationFromSnapshot(
|
|
17143
|
+
extensionId,
|
|
17144
|
+
snapshot,
|
|
17145
|
+
runtime.workspaceCwd
|
|
17146
|
+
);
|
|
17147
|
+
return { status: activation.effective, name: extension.name };
|
|
17148
|
+
},
|
|
17149
|
+
{ refreshRuntimes: [runtime] }
|
|
15396
17150
|
);
|
|
15397
|
-
} catch (err) {
|
|
15398
|
-
sendBridgeError2(res, err, {
|
|
15399
|
-
route: "DELETE /workspace/extensions/:name"
|
|
15400
|
-
});
|
|
15401
17151
|
}
|
|
15402
|
-
|
|
15403
|
-
|
|
17152
|
+
);
|
|
17153
|
+
app.post(
|
|
17154
|
+
"/workspaces/:workspace/extensions/refresh",
|
|
17155
|
+
mutate({ strict: true }),
|
|
17156
|
+
(req, res) => {
|
|
17157
|
+
const runtime = resolveWorkspaceRuntimeFromParam(registry, req, res);
|
|
17158
|
+
if (!runtime || !requireTrustedWorkspaceRuntime(runtime, res)) return;
|
|
17159
|
+
const manager = primaryController.createExtensionManager(
|
|
17160
|
+
runtime.workspaceCwd,
|
|
17161
|
+
true
|
|
17162
|
+
);
|
|
17163
|
+
sendOperation(
|
|
17164
|
+
req,
|
|
17165
|
+
res,
|
|
17166
|
+
"POST /workspaces/:workspace/extensions/refresh",
|
|
17167
|
+
manager,
|
|
17168
|
+
"refresh",
|
|
17169
|
+
{},
|
|
17170
|
+
async () => ({ status: "refreshed" }),
|
|
17171
|
+
{ refreshRuntimes: [runtime] }
|
|
17172
|
+
);
|
|
17173
|
+
}
|
|
17174
|
+
);
|
|
17175
|
+
}
|
|
15404
17176
|
}
|
|
15405
17177
|
__name(registerWorkspaceExtensionRoutes, "registerWorkspaceExtensionRoutes");
|
|
15406
17178
|
|
|
@@ -16011,7 +17783,7 @@ __name(registerPermissionRoutes, "registerPermissionRoutes");
|
|
|
16011
17783
|
|
|
16012
17784
|
// packages/cli/src/serve/routes/session.ts
|
|
16013
17785
|
init_esbuild_shims();
|
|
16014
|
-
import * as
|
|
17786
|
+
import * as crypto3 from "node:crypto";
|
|
16015
17787
|
import * as path11 from "node:path";
|
|
16016
17788
|
|
|
16017
17789
|
// packages/cli/src/serve/server/prompt-deadline.ts
|
|
@@ -16714,14 +18486,14 @@ function registerSessionRoutes(app, deps) {
|
|
|
16714
18486
|
sessionShellCommandEnabled
|
|
16715
18487
|
} = deps;
|
|
16716
18488
|
const LANGUAGE_CODES = deps.languageCodes;
|
|
16717
|
-
const transcriptCursorMasterKey =
|
|
18489
|
+
const transcriptCursorMasterKey = crypto3.randomBytes(32);
|
|
16718
18490
|
const transcriptCursorCodecs = /* @__PURE__ */ new Map();
|
|
16719
18491
|
const getTranscriptCursorCodec = /* @__PURE__ */ __name((runtime) => {
|
|
16720
18492
|
const canonicalCwd = canonicalizeWorkspace(runtime.workspaceCwd);
|
|
16721
18493
|
const cacheKey = `${runtime.workspaceId}\0${canonicalCwd}`;
|
|
16722
18494
|
const cached = transcriptCursorCodecs.get(cacheKey);
|
|
16723
18495
|
if (cached) return cached;
|
|
16724
|
-
const derivedKey =
|
|
18496
|
+
const derivedKey = crypto3.hkdfSync(
|
|
16725
18497
|
"sha256",
|
|
16726
18498
|
transcriptCursorMasterKey,
|
|
16727
18499
|
Buffer.alloc(0),
|
|
@@ -16881,6 +18653,51 @@ function registerSessionRoutes(app, deps) {
|
|
|
16881
18653
|
}
|
|
16882
18654
|
return runtime;
|
|
16883
18655
|
}, "requireTrustedRuntimeForWorkspaceRoute");
|
|
18656
|
+
const handleSessionExport = /* @__PURE__ */ __name(async (req, res, target) => {
|
|
18657
|
+
const sessionId = requireSessionId(req, res);
|
|
18658
|
+
if (sessionId === null) return;
|
|
18659
|
+
const rawFormat = req.query["format"];
|
|
18660
|
+
const format = parseSessionExportFormat(rawFormat);
|
|
18661
|
+
if (!format) {
|
|
18662
|
+
res.status(400).json({
|
|
18663
|
+
error: "Invalid export format",
|
|
18664
|
+
code: "invalid_export_format",
|
|
18665
|
+
format: typeof rawFormat === "string" ? rawFormat : String(rawFormat),
|
|
18666
|
+
allowedFormats: sessionExportFormatValues()
|
|
18667
|
+
});
|
|
18668
|
+
return;
|
|
18669
|
+
}
|
|
18670
|
+
try {
|
|
18671
|
+
const result = await archiveCoordinator.runSharedMany(
|
|
18672
|
+
[sessionId],
|
|
18673
|
+
async () => {
|
|
18674
|
+
await assertSessionLoadable(target.workspaceCwd, sessionId);
|
|
18675
|
+
return exportSessionTranscript({
|
|
18676
|
+
workspaceCwd: target.workspaceCwd,
|
|
18677
|
+
sessionId,
|
|
18678
|
+
format,
|
|
18679
|
+
config: { getChannel: /* @__PURE__ */ __name(() => "daemon", "getChannel") }
|
|
18680
|
+
});
|
|
18681
|
+
}
|
|
18682
|
+
);
|
|
18683
|
+
const filename = result.filename.replace(/["\\\r\n]/g, "_");
|
|
18684
|
+
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);
|
|
18685
|
+
} catch (err) {
|
|
18686
|
+
if (target.workspaceQualified && err instanceof SessionNotFoundError) {
|
|
18687
|
+
res.status(404).json({
|
|
18688
|
+
error: err.message,
|
|
18689
|
+
code: "session_not_found",
|
|
18690
|
+
sessionId: err.sessionId
|
|
18691
|
+
});
|
|
18692
|
+
return;
|
|
18693
|
+
}
|
|
18694
|
+
sendBridgeError2(res, err, {
|
|
18695
|
+
route: target.route,
|
|
18696
|
+
sessionId,
|
|
18697
|
+
...target.workspaceQualified ? { workspaceCwd: target.workspaceCwd } : {}
|
|
18698
|
+
});
|
|
18699
|
+
}
|
|
18700
|
+
}, "handleSessionExport");
|
|
16884
18701
|
const sendAmbiguousSessionOwner = /* @__PURE__ */ __name((res, route, sessionId, runtimes) => {
|
|
16885
18702
|
const workspaceIds = runtimes.map((runtime) => runtime.workspaceId);
|
|
16886
18703
|
logSessionRoutingFailure(route, "ambiguous", {
|
|
@@ -17447,40 +19264,20 @@ function registerSessionRoutes(app, deps) {
|
|
|
17447
19264
|
}
|
|
17448
19265
|
});
|
|
17449
19266
|
app.get("/session/:id/export", async (req, res) => {
|
|
17450
|
-
|
|
17451
|
-
|
|
17452
|
-
|
|
17453
|
-
|
|
17454
|
-
|
|
17455
|
-
|
|
17456
|
-
|
|
17457
|
-
|
|
17458
|
-
|
|
17459
|
-
|
|
17460
|
-
|
|
17461
|
-
|
|
17462
|
-
|
|
17463
|
-
|
|
17464
|
-
const result = await archiveCoordinator.runSharedMany(
|
|
17465
|
-
[sessionId],
|
|
17466
|
-
async () => {
|
|
17467
|
-
await assertSessionLoadable(boundWorkspace, sessionId);
|
|
17468
|
-
return exportSessionTranscript({
|
|
17469
|
-
workspaceCwd: boundWorkspace,
|
|
17470
|
-
sessionId,
|
|
17471
|
-
format,
|
|
17472
|
-
config: { getChannel: /* @__PURE__ */ __name(() => "daemon", "getChannel") }
|
|
17473
|
-
});
|
|
17474
|
-
}
|
|
17475
|
-
);
|
|
17476
|
-
const filename = result.filename.replace(/["\\\r\n]/g, "_");
|
|
17477
|
-
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);
|
|
17478
|
-
} catch (err) {
|
|
17479
|
-
sendBridgeError2(res, err, {
|
|
17480
|
-
route: "GET /session/:id/export",
|
|
17481
|
-
sessionId
|
|
17482
|
-
});
|
|
17483
|
-
}
|
|
19267
|
+
await handleSessionExport(req, res, {
|
|
19268
|
+
route: "GET /session/:id/export",
|
|
19269
|
+
workspaceCwd: boundWorkspace
|
|
19270
|
+
});
|
|
19271
|
+
});
|
|
19272
|
+
app.get("/workspaces/:workspace/session/:id/export", async (req, res) => {
|
|
19273
|
+
const route = "GET /workspaces/:workspace/session/:id/export";
|
|
19274
|
+
const runtime = requireTrustedRuntimeForWorkspaceRoute(req, res, route);
|
|
19275
|
+
if (!runtime) return;
|
|
19276
|
+
await handleSessionExport(req, res, {
|
|
19277
|
+
route,
|
|
19278
|
+
workspaceCwd: runtime.workspaceCwd,
|
|
19279
|
+
workspaceQualified: true
|
|
19280
|
+
});
|
|
17484
19281
|
});
|
|
17485
19282
|
app.get("/session/:id/transcript", async (req, res) => {
|
|
17486
19283
|
const route = "GET /session/:id/transcript";
|
|
@@ -17825,7 +19622,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
17825
19622
|
async (req, res, sessionId, runtime) => {
|
|
17826
19623
|
const clientId = parseClientIdHeader(req, res);
|
|
17827
19624
|
if (clientId === null) return;
|
|
17828
|
-
const promptId =
|
|
19625
|
+
const promptId = crypto3.randomUUID();
|
|
17829
19626
|
res.status(200).json(
|
|
17830
19627
|
await runtime.bridge.continueSession(sessionId, {
|
|
17831
19628
|
...clientId !== void 0 ? { clientId } : {},
|
|
@@ -17872,7 +19669,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
17872
19669
|
}
|
|
17873
19670
|
const clientId = parseClientIdHeader(req, res);
|
|
17874
19671
|
if (clientId === null) return;
|
|
17875
|
-
const promptId =
|
|
19672
|
+
const promptId = crypto3.randomUUID();
|
|
17876
19673
|
const forwardedBody = { ...body };
|
|
17877
19674
|
delete forwardedBody["deadlineMs"];
|
|
17878
19675
|
const lastEventId = ownerBridge.getSessionLastEventId(sessionId);
|
|
@@ -20258,7 +22055,11 @@ function createServeFeatures(deps) {
|
|
|
20258
22055
|
cachedVoiceTranscriptionAvailable = void 0;
|
|
20259
22056
|
}, "invalidateServeFeaturesCache");
|
|
20260
22057
|
const getCachedVoiceTranscriptionAvailable = /* @__PURE__ */ __name(() => {
|
|
20261
|
-
cachedVoiceTranscriptionAvailable ??= isWorkspaceVoiceTranscriptionAvailable(
|
|
22058
|
+
cachedVoiceTranscriptionAvailable ??= isWorkspaceVoiceTranscriptionAvailable(
|
|
22059
|
+
boundWorkspace,
|
|
22060
|
+
env,
|
|
22061
|
+
deps.env !== void 0
|
|
22062
|
+
);
|
|
20262
22063
|
return cachedVoiceTranscriptionAvailable;
|
|
20263
22064
|
}, "getCachedVoiceTranscriptionAvailable");
|
|
20264
22065
|
return {
|
|
@@ -20297,10 +22098,11 @@ function createServeFeatures(deps) {
|
|
|
20297
22098
|
};
|
|
20298
22099
|
}
|
|
20299
22100
|
__name(createServeFeatures, "createServeFeatures");
|
|
20300
|
-
function isWorkspaceVoiceTranscriptionAvailable(boundWorkspace) {
|
|
22101
|
+
function isWorkspaceVoiceTranscriptionAvailable(boundWorkspace, env, skipLoadEnvironment) {
|
|
20301
22102
|
try {
|
|
20302
22103
|
return hasConfiguredBatchVoiceTranscriptionModel(
|
|
20303
|
-
loadSettings(boundWorkspace)
|
|
22104
|
+
loadSettings(boundWorkspace, { skipLoadEnvironment }),
|
|
22105
|
+
{ env }
|
|
20304
22106
|
);
|
|
20305
22107
|
} catch (err) {
|
|
20306
22108
|
writeStderrLine(
|
|
@@ -20344,15 +22146,13 @@ function registerCapabilitiesRoutes(app, deps) {
|
|
|
20344
22146
|
maxTotalSessions: deps.maxTotalSessions === void 0 || deps.maxTotalSessions === 0 || deps.maxTotalSessions === Number.POSITIVE_INFINITY ? null : deps.maxTotalSessions
|
|
20345
22147
|
} : {}
|
|
20346
22148
|
},
|
|
20347
|
-
|
|
20348
|
-
|
|
20349
|
-
|
|
20350
|
-
|
|
20351
|
-
|
|
20352
|
-
|
|
20353
|
-
|
|
20354
|
-
}))
|
|
20355
|
-
} : {},
|
|
22149
|
+
workspaces: runtimes.map((runtime) => ({
|
|
22150
|
+
id: runtime.workspaceId,
|
|
22151
|
+
cwd: runtime.workspaceCwd,
|
|
22152
|
+
primary: runtime.primary,
|
|
22153
|
+
trusted: runtime.trusted,
|
|
22154
|
+
...runtimeRemoval ? { removable: runtime.removable === true } : {}
|
|
22155
|
+
})),
|
|
20356
22156
|
supportedLanguages: deps.languageCodes
|
|
20357
22157
|
};
|
|
20358
22158
|
res.status(200).json(envelope);
|
|
@@ -20942,6 +22742,24 @@ function sendPermissionVoteError(res, err, ctx, daemonLog) {
|
|
|
20942
22742
|
}
|
|
20943
22743
|
__name(sendPermissionVoteError, "sendPermissionVoteError");
|
|
20944
22744
|
function sendBridgeError(res, err, ctx, daemonLog) {
|
|
22745
|
+
if (err instanceof WorkspaceSkillNotFoundError) {
|
|
22746
|
+
res.status(404).json({
|
|
22747
|
+
error: err.message,
|
|
22748
|
+
code: "skill_not_found",
|
|
22749
|
+
skillName: err.skillName
|
|
22750
|
+
});
|
|
22751
|
+
return;
|
|
22752
|
+
}
|
|
22753
|
+
if (err instanceof WorkspaceSkillNotToggleableError) {
|
|
22754
|
+
res.status(409).json({
|
|
22755
|
+
error: err.message,
|
|
22756
|
+
code: "skill_not_toggleable",
|
|
22757
|
+
skillName: err.skillName,
|
|
22758
|
+
reason: err.reason,
|
|
22759
|
+
...err.lockedScope ? { lockedScope: err.lockedScope } : {}
|
|
22760
|
+
});
|
|
22761
|
+
return;
|
|
22762
|
+
}
|
|
20945
22763
|
if (err instanceof InvalidSessionTranscriptCursorError) {
|
|
20946
22764
|
res.status(400).json({
|
|
20947
22765
|
error: err.message,
|
|
@@ -22221,11 +24039,20 @@ function resolveDaemonTelemetryRoute(req) {
|
|
|
22221
24039
|
sessionId: workspaceTranscript[1]
|
|
22222
24040
|
};
|
|
22223
24041
|
}
|
|
24042
|
+
const workspaceExport = path15.match(
|
|
24043
|
+
/^\/workspaces\/[^/]+\/session\/([^/]+)\/export$/
|
|
24044
|
+
);
|
|
24045
|
+
if (workspaceExport?.[1] && req.method === "GET") {
|
|
24046
|
+
return {
|
|
24047
|
+
route: "GET /workspaces/:workspace/session/:id/export",
|
|
24048
|
+
sessionId: workspaceExport[1]
|
|
24049
|
+
};
|
|
24050
|
+
}
|
|
22224
24051
|
const pluralWorkspacePrefix = /^\/workspaces\/[^/]+/;
|
|
22225
24052
|
if (pluralWorkspacePrefix.test(path15)) {
|
|
22226
24053
|
const suffix = path15.replace(pluralWorkspacePrefix, "/workspace");
|
|
22227
24054
|
if (req.method === "GET") {
|
|
22228
|
-
if (suffix === "/workspace/mcp" || suffix === "/workspace/skills" || suffix === "/workspace/tools" || suffix === "/workspace/providers" || suffix === "/workspace/env" || suffix === "/workspace/preflight" || suffix === "/workspace/hooks" || suffix === "/workspace/settings" || suffix === "/workspace/permissions" || suffix === "/workspace/trust" || suffix === "/workspace/memory" || suffix === "/workspace/agents") {
|
|
24055
|
+
if (suffix === "/workspace/mcp" || suffix === "/workspace/skills" || suffix === "/workspace/tools" || suffix === "/workspace/providers" || suffix === "/workspace/env" || suffix === "/workspace/preflight" || suffix === "/workspace/hooks" || suffix === "/workspace/settings" || suffix === "/workspace/voice" || suffix === "/workspace/permissions" || suffix === "/workspace/trust" || suffix === "/workspace/memory" || suffix === "/workspace/agents") {
|
|
22229
24056
|
return { route: `GET ${suffix}` };
|
|
22230
24057
|
}
|
|
22231
24058
|
if (/^\/workspace\/agents\/[^/]+$/.test(suffix)) {
|
|
@@ -22246,7 +24073,7 @@ function resolveDaemonTelemetryRoute(req) {
|
|
|
22246
24073
|
}
|
|
22247
24074
|
}
|
|
22248
24075
|
if (req.method === "POST") {
|
|
22249
|
-
if (suffix === "/workspace/settings" || suffix === "/workspace/permissions" || suffix === "/workspace/trust/request" || suffix === "/workspace/init" || suffix === "/workspace/reload" || suffix === "/workspace/file/write" || suffix === "/workspace/file/edit" || suffix === "/workspace/mcp/servers" || suffix === "/workspace/memory" || suffix === "/workspace/agents" || suffix === "/workspace/sessions/delete" || suffix === "/workspace/sessions/archive" || suffix === "/workspace/sessions/unarchive" || suffix === "/workspace/session-groups") {
|
|
24076
|
+
if (suffix === "/workspace/settings" || suffix === "/workspace/voice" || suffix === "/workspace/voice/transcribe" || suffix === "/workspace/permissions" || suffix === "/workspace/trust/request" || suffix === "/workspace/init" || suffix === "/workspace/reload" || suffix === "/workspace/file/write" || suffix === "/workspace/file/edit" || suffix === "/workspace/mcp/servers" || suffix === "/workspace/memory" || suffix === "/workspace/agents" || suffix === "/workspace/sessions/delete" || suffix === "/workspace/sessions/archive" || suffix === "/workspace/sessions/unarchive" || suffix === "/workspace/session-groups") {
|
|
22250
24077
|
return { route: `POST ${suffix}` };
|
|
22251
24078
|
}
|
|
22252
24079
|
if (/^\/workspace\/tools\/[^/]+\/enable$/.test(suffix)) {
|
|
@@ -23265,7 +25092,8 @@ function registerWorkspaceManagementRoutes(app, deps) {
|
|
|
23265
25092
|
const workspaceActivity = /* @__PURE__ */ __name((runtime) => {
|
|
23266
25093
|
const controllerActivity = runtimeRemoval?.getActivity(runtime) ?? {
|
|
23267
25094
|
pendingSessionStarts: 0,
|
|
23268
|
-
channelWorkers: 0
|
|
25095
|
+
channelWorkers: 0,
|
|
25096
|
+
voiceSessions: 0
|
|
23269
25097
|
};
|
|
23270
25098
|
const acpActivity = getAcpHandle?.()?.getWorkspaceActivity(
|
|
23271
25099
|
runtime.workspaceId
|
|
@@ -23276,7 +25104,8 @@ function registerWorkspaceManagementRoutes(app, deps) {
|
|
|
23276
25104
|
activePrompts: runtime.bridge.activePromptCount,
|
|
23277
25105
|
acpConnections: acpActivity.acpConnections,
|
|
23278
25106
|
memoryTasks: acpActivity.memoryTasks,
|
|
23279
|
-
channelWorkers: controllerActivity.channelWorkers
|
|
25107
|
+
channelWorkers: controllerActivity.channelWorkers,
|
|
25108
|
+
voiceSessions: controllerActivity.voiceSessions
|
|
23280
25109
|
};
|
|
23281
25110
|
}, "workspaceActivity");
|
|
23282
25111
|
const isBusy = /* @__PURE__ */ __name((activity) => Object.values(activity).some((count) => count > 0), "isBusy");
|
|
@@ -24479,6 +26308,106 @@ function registerWorkspaceQualifiedToolsRoutes(app, deps) {
|
|
|
24479
26308
|
}
|
|
24480
26309
|
__name(registerWorkspaceQualifiedToolsRoutes, "registerWorkspaceQualifiedToolsRoutes");
|
|
24481
26310
|
|
|
26311
|
+
// packages/cli/src/serve/routes/workspace-skills.ts
|
|
26312
|
+
init_esbuild_shims();
|
|
26313
|
+
function parseSkillToggleRequest(req, res, safeBody2) {
|
|
26314
|
+
const rawSkillName = req.params["name"];
|
|
26315
|
+
if (!rawSkillName || typeof rawSkillName !== "string") {
|
|
26316
|
+
res.status(400).json({
|
|
26317
|
+
error: "Skill name path parameter is required",
|
|
26318
|
+
code: "invalid_skill_name"
|
|
26319
|
+
});
|
|
26320
|
+
return void 0;
|
|
26321
|
+
}
|
|
26322
|
+
const skillName = rawSkillName.trim();
|
|
26323
|
+
if (skillName.length === 0) {
|
|
26324
|
+
res.status(400).json({
|
|
26325
|
+
error: "Skill name path parameter is required",
|
|
26326
|
+
code: "invalid_skill_name"
|
|
26327
|
+
});
|
|
26328
|
+
return void 0;
|
|
26329
|
+
}
|
|
26330
|
+
if (skillName.length > MAX_SKILL_NAME_LENGTH) {
|
|
26331
|
+
res.status(400).json({
|
|
26332
|
+
error: `Skill name exceeds ${MAX_SKILL_NAME_LENGTH}-character limit`,
|
|
26333
|
+
code: "invalid_skill_name"
|
|
26334
|
+
});
|
|
26335
|
+
return void 0;
|
|
26336
|
+
}
|
|
26337
|
+
const enabled = safeBody2(req)["enabled"];
|
|
26338
|
+
if (typeof enabled !== "boolean") {
|
|
26339
|
+
res.status(400).json({
|
|
26340
|
+
error: "`enabled` is required and must be a boolean",
|
|
26341
|
+
code: "invalid_enabled_flag"
|
|
26342
|
+
});
|
|
26343
|
+
return void 0;
|
|
26344
|
+
}
|
|
26345
|
+
return { skillName, enabled };
|
|
26346
|
+
}
|
|
26347
|
+
__name(parseSkillToggleRequest, "parseSkillToggleRequest");
|
|
26348
|
+
function registerWorkspaceSkillsRoutes(app, deps) {
|
|
26349
|
+
const buildWorkspaceCtx = createBuildWorkspaceCtx(
|
|
26350
|
+
deps.workspaceRuntime.workspaceCwd
|
|
26351
|
+
);
|
|
26352
|
+
const route = "POST /workspace/skills/:name/enable";
|
|
26353
|
+
app.post(
|
|
26354
|
+
"/workspace/skills/:name/enable",
|
|
26355
|
+
deps.mutate({ strict: true }),
|
|
26356
|
+
async (req, res) => {
|
|
26357
|
+
if (!requireTrustedWorkspaceRuntime(deps.workspaceRuntime, res)) return;
|
|
26358
|
+
const input = parseSkillToggleRequest(req, res, deps.safeBody);
|
|
26359
|
+
if (!input) return;
|
|
26360
|
+
const clientId = deps.parseAndValidateClientId(req, res);
|
|
26361
|
+
if (clientId === null) return;
|
|
26362
|
+
try {
|
|
26363
|
+
const result = await deps.workspaceRuntime.workspaceService.setWorkspaceSkillEnabled(
|
|
26364
|
+
buildWorkspaceCtx(route, clientId),
|
|
26365
|
+
input.skillName,
|
|
26366
|
+
input.enabled
|
|
26367
|
+
);
|
|
26368
|
+
res.status(200).json(result);
|
|
26369
|
+
} catch (err) {
|
|
26370
|
+
deps.sendBridgeError(res, err, { route });
|
|
26371
|
+
}
|
|
26372
|
+
}
|
|
26373
|
+
);
|
|
26374
|
+
}
|
|
26375
|
+
__name(registerWorkspaceSkillsRoutes, "registerWorkspaceSkillsRoutes");
|
|
26376
|
+
function registerWorkspaceQualifiedSkillsRoutes(app, deps) {
|
|
26377
|
+
const route = "POST /workspaces/:workspace/skills/:name/enable";
|
|
26378
|
+
app.post(
|
|
26379
|
+
"/workspaces/:workspace/skills/:name/enable",
|
|
26380
|
+
deps.mutate({ strict: true }),
|
|
26381
|
+
async (req, res) => {
|
|
26382
|
+
const runtime = resolveWorkspaceRuntimeFromParam(
|
|
26383
|
+
deps.workspaceRegistry,
|
|
26384
|
+
req,
|
|
26385
|
+
res
|
|
26386
|
+
);
|
|
26387
|
+
if (!runtime || !requireTrustedWorkspaceRuntime(runtime, res)) return;
|
|
26388
|
+
const input = parseSkillToggleRequest(req, res, deps.safeBody);
|
|
26389
|
+
if (!input) return;
|
|
26390
|
+
const clientId = parseAndValidateWorkspaceClientId(
|
|
26391
|
+
req,
|
|
26392
|
+
res,
|
|
26393
|
+
runtime.bridge
|
|
26394
|
+
);
|
|
26395
|
+
if (clientId === null) return;
|
|
26396
|
+
try {
|
|
26397
|
+
const result = await runtime.workspaceService.setWorkspaceSkillEnabled(
|
|
26398
|
+
createBuildWorkspaceCtx(runtime.workspaceCwd)(route, clientId),
|
|
26399
|
+
input.skillName,
|
|
26400
|
+
input.enabled
|
|
26401
|
+
);
|
|
26402
|
+
res.status(200).json(result);
|
|
26403
|
+
} catch (err) {
|
|
26404
|
+
deps.sendBridgeError(res, err, { route });
|
|
26405
|
+
}
|
|
26406
|
+
}
|
|
26407
|
+
);
|
|
26408
|
+
}
|
|
26409
|
+
__name(registerWorkspaceQualifiedSkillsRoutes, "registerWorkspaceQualifiedSkillsRoutes");
|
|
26410
|
+
|
|
24482
26411
|
// packages/cli/src/serve/routes/channel-webhooks.ts
|
|
24483
26412
|
init_esbuild_shims();
|
|
24484
26413
|
var import_express4 = __toESM(require_express(), 1);
|
|
@@ -24772,7 +26701,10 @@ function describeRegistryPrimaryForConflict(registry) {
|
|
|
24772
26701
|
}
|
|
24773
26702
|
__name(describeRegistryPrimaryForConflict, "describeRegistryPrimaryForConflict");
|
|
24774
26703
|
function getRuntimeEffectiveEnv(metadata) {
|
|
24775
|
-
|
|
26704
|
+
if (!metadata || metadata.mode === "parent-process") {
|
|
26705
|
+
return metadata?.effectiveEnv;
|
|
26706
|
+
}
|
|
26707
|
+
return metadata.effectiveEnv ?? {};
|
|
24776
26708
|
}
|
|
24777
26709
|
__name(getRuntimeEffectiveEnv, "getRuntimeEffectiveEnv");
|
|
24778
26710
|
var DEFAULT_SESSION_IDLE_TIMEOUT_MS = 30 * 6e4;
|
|
@@ -24788,6 +26720,11 @@ function computeKeepaliveIntervalMs(idleTimeoutMs) {
|
|
|
24788
26720
|
}
|
|
24789
26721
|
__name(computeKeepaliveIntervalMs, "computeKeepaliveIntervalMs");
|
|
24790
26722
|
function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
26723
|
+
if (deps.workspaceRuntimeRemoval && !deps.voiceCoordinator) {
|
|
26724
|
+
throw new Error(
|
|
26725
|
+
"createServeApp: deps.workspaceRuntimeRemoval requires the matching deps.voiceCoordinator."
|
|
26726
|
+
);
|
|
26727
|
+
}
|
|
24791
26728
|
const app = (0, import_express5.default)();
|
|
24792
26729
|
const injectedWorkspaceRegistry = deps.workspaceRegistry;
|
|
24793
26730
|
const boundWorkspace = injectedWorkspaceRegistry?.primary.workspaceCwd ?? deps.boundWorkspace ?? canonicalizeWorkspace(opts.workspace ?? process.cwd());
|
|
@@ -24880,6 +26817,7 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
24880
26817
|
workspaceRuntimeRemovalAvailable: deps.workspaceRuntimeRemoval !== void 0,
|
|
24881
26818
|
...primaryEffectiveEnv ? { env: primaryEffectiveEnv } : {}
|
|
24882
26819
|
});
|
|
26820
|
+
app.locals.invalidateServeFeaturesCache = invalidateServeFeaturesCache;
|
|
24883
26821
|
const statusProvider = deps.statusProvider ?? createDaemonStatusProvider(
|
|
24884
26822
|
primaryEffectiveEnv ? { env: primaryEffectiveEnv } : {}
|
|
24885
26823
|
);
|
|
@@ -24945,12 +26883,18 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
24945
26883
|
primaryEffectiveEnv ? { env: primaryEffectiveEnv } : {}
|
|
24946
26884
|
),
|
|
24947
26885
|
workspaceSkillsStatusProvider: createWorkspaceSkillsStatusProvider(),
|
|
26886
|
+
...primaryEffectiveEnv ? { voiceEnv: primaryEffectiveEnv } : {},
|
|
24948
26887
|
isChannelLive: /* @__PURE__ */ __name(() => bridge.isChannelLive(), "isChannelLive"),
|
|
24949
26888
|
persistDisabledTools: deps.persistDisabledTools ?? (async () => {
|
|
24950
26889
|
throw new Error(
|
|
24951
26890
|
"setWorkspaceToolEnabled requires persistDisabledTools in ServeAppDeps"
|
|
24952
26891
|
);
|
|
24953
26892
|
}),
|
|
26893
|
+
persistDisabledSkills: deps.persistDisabledSkills ?? (async () => {
|
|
26894
|
+
throw new Error(
|
|
26895
|
+
"setWorkspaceSkillEnabled requires persistDisabledSkills in ServeAppDeps"
|
|
26896
|
+
);
|
|
26897
|
+
}),
|
|
24954
26898
|
queryWorkspaceStatus: /* @__PURE__ */ __name((method, idle) => bridge.queryWorkspaceStatus(method, idle), "queryWorkspaceStatus"),
|
|
24955
26899
|
invokeWorkspaceCommand: /* @__PURE__ */ __name((method, params, invokeOpts) => bridge.invokeWorkspaceCommand(method, params, invokeOpts), "invokeWorkspaceCommand"),
|
|
24956
26900
|
preheatAcpChild: /* @__PURE__ */ __name(() => bridge.preheat(), "preheatAcpChild"),
|
|
@@ -24983,6 +26927,7 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
24983
26927
|
);
|
|
24984
26928
|
app.locals.workspaceRegistry = workspaceRegistry;
|
|
24985
26929
|
const primaryRuntime = workspaceRegistry.primary;
|
|
26930
|
+
const voiceCoordinator = deps.voiceCoordinator ?? new WorkspaceVoiceCoordinator();
|
|
24986
26931
|
const primaryBoundWorkspace = primaryRuntime.workspaceCwd;
|
|
24987
26932
|
const primaryBridge = primaryRuntime.bridge;
|
|
24988
26933
|
const primaryWorkspace = primaryRuntime.workspaceService;
|
|
@@ -25218,6 +27163,7 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
25218
27163
|
mutate,
|
|
25219
27164
|
safeBody,
|
|
25220
27165
|
sendBridgeError: sendBridgeError2,
|
|
27166
|
+
workspaceRegistry,
|
|
25221
27167
|
...deps.maxExtensionOperationHistory === void 0 ? {} : { maxExtensionOperationHistory: deps.maxExtensionOperationHistory }
|
|
25222
27168
|
});
|
|
25223
27169
|
registerWorkspaceFileReadRoutes(app, {
|
|
@@ -25319,9 +27265,22 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
25319
27265
|
persistSetting: deps.persistSetting,
|
|
25320
27266
|
persistSettings: deps.persistSettings,
|
|
25321
27267
|
transcribe: deps.voiceTranscriber,
|
|
27268
|
+
env: getRuntimeEffectiveEnv(primaryRuntime.env),
|
|
27269
|
+
acquireVoiceLease: /* @__PURE__ */ __name(() => voiceCoordinator.acquire(primaryRuntime), "acquireVoiceLease"),
|
|
25322
27270
|
broadcastSettingsChanged,
|
|
25323
27271
|
parseAndValidateClientId: /* @__PURE__ */ __name((req, res) => parseAndValidateWorkspaceClientId(req, res, primaryBridge), "parseAndValidateClientId")
|
|
25324
27272
|
});
|
|
27273
|
+
registerWorkspaceQualifiedVoiceRoutes(app, {
|
|
27274
|
+
workspaceRegistry,
|
|
27275
|
+
mutate,
|
|
27276
|
+
safeBody,
|
|
27277
|
+
persistSetting: deps.persistSetting,
|
|
27278
|
+
persistSettings: deps.persistSettings,
|
|
27279
|
+
transcribe: deps.voiceTranscriber,
|
|
27280
|
+
acquireVoiceLease: /* @__PURE__ */ __name((runtime) => voiceCoordinator.acquire(runtime), "acquireVoiceLease"),
|
|
27281
|
+
parseAndValidateClientId: /* @__PURE__ */ __name((req, res, runtime) => parseAndValidateWorkspaceClientId(req, res, runtime.bridge), "parseAndValidateClientId"),
|
|
27282
|
+
invalidateServeFeaturesCache
|
|
27283
|
+
});
|
|
25325
27284
|
if (deps.persistSettings) {
|
|
25326
27285
|
registerWorkspaceModelsRoutes(app, {
|
|
25327
27286
|
boundWorkspace: primaryBoundWorkspace,
|
|
@@ -25435,6 +27394,19 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
25435
27394
|
safeBody,
|
|
25436
27395
|
sendBridgeError: sendBridgeError2
|
|
25437
27396
|
});
|
|
27397
|
+
registerWorkspaceSkillsRoutes(app, {
|
|
27398
|
+
workspaceRuntime: primaryRuntime,
|
|
27399
|
+
mutate,
|
|
27400
|
+
safeBody,
|
|
27401
|
+
sendBridgeError: sendBridgeError2,
|
|
27402
|
+
parseAndValidateClientId: /* @__PURE__ */ __name((req, res) => parseAndValidateWorkspaceClientId(req, res, primaryBridge), "parseAndValidateClientId")
|
|
27403
|
+
});
|
|
27404
|
+
registerWorkspaceQualifiedSkillsRoutes(app, {
|
|
27405
|
+
workspaceRegistry,
|
|
27406
|
+
mutate,
|
|
27407
|
+
safeBody,
|
|
27408
|
+
sendBridgeError: sendBridgeError2
|
|
27409
|
+
});
|
|
25438
27410
|
registerScheduledTasksRoutes(app, {
|
|
25439
27411
|
boundWorkspace: primaryBoundWorkspace,
|
|
25440
27412
|
mutate,
|
|
@@ -25553,10 +27525,15 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
25553
27525
|
{
|
|
25554
27526
|
path: "/voice/stream",
|
|
25555
27527
|
onConnection: createVoiceWsConnectionHandler(primaryBoundWorkspace, {
|
|
25556
|
-
env: getRuntimeEffectiveEnv(primaryRuntime.env)
|
|
27528
|
+
env: getRuntimeEffectiveEnv(primaryRuntime.env),
|
|
27529
|
+
acquireVoiceLease: /* @__PURE__ */ __name(() => voiceCoordinator.acquire(primaryRuntime), "acquireVoiceLease")
|
|
25557
27530
|
})
|
|
25558
27531
|
}
|
|
25559
|
-
]
|
|
27532
|
+
],
|
|
27533
|
+
workspaceVoiceConnection: /* @__PURE__ */ __name((runtime, ws, req) => createVoiceWsConnectionHandler(runtime.workspaceCwd, {
|
|
27534
|
+
env: getRuntimeEffectiveEnv(runtime.env),
|
|
27535
|
+
acquireVoiceLease: /* @__PURE__ */ __name(() => voiceCoordinator.acquire(runtime), "acquireVoiceLease")
|
|
27536
|
+
})(ws, req), "workspaceVoiceConnection")
|
|
25560
27537
|
});
|
|
25561
27538
|
if (acpHandleRef.current) {
|
|
25562
27539
|
app.locals["acpHandle"] = acpHandleRef.current;
|