@qwen-code/qwen-code 0.19.9-nightly.20260714.9dd8389eb → 0.19.10
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 +6 -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-67ODZ4SZ.js} +5 -6
- package/chunks/{StandaloneSessionPicker-D3R6YYX3.js → StandaloneSessionPicker-SIPBQT5Z.js} +17 -18
- package/chunks/{acpAgent-ENHBW72A.js → acpAgent-VPZJXLFX.js} +141 -80
- package/chunks/{agent-57LX4KMG.js → agent-Z7NWL6XL.js} +2 -3
- package/chunks/{agent-headless-AC6EVNAP.js → agent-headless-73HZ6ROQ.js} +2 -3
- package/chunks/{bridge-4OZH44MK.js → bridge-XFKW6S7Q.js} +5 -6
- package/chunks/{chunk-DPSWJWZI.js → chunk-2W3OOD4W.js} +1 -0
- package/chunks/{chunk-FDL4PB5H.js → chunk-3XJFUTNM.js} +8 -8
- package/chunks/{chunk-AAS2Q2TV.js → chunk-422XAKCK.js} +2 -2
- package/chunks/{chunk-7U7UIO5W.js → chunk-4FMG7EDJ.js} +19 -4
- package/chunks/{chunk-X7EBAABW.js → chunk-4YWLED2A.js} +1 -1
- package/chunks/{chunk-WN4D7YCI.js → chunk-5EBKKSBX.js} +1 -1
- package/chunks/{chunk-G6QTKQN4.js → chunk-5LROA5W4.js} +3 -3
- package/chunks/{chunk-SUWGUGAD.js → chunk-65N2J6V2.js} +2 -2
- package/chunks/{chunk-T2L6SGQY.js → chunk-6GETGQWP.js} +26 -6
- package/chunks/{chunk-T5C2OUCI.js → chunk-6GKMJPOC.js} +3 -3
- package/chunks/{chunk-K3PPUB46.js → chunk-6W6DI34P.js} +1 -1
- package/chunks/{chunk-WM5HY72H.js → chunk-7L4XBYVW.js} +2 -2
- package/chunks/{chunk-CLTWL6HD.js → chunk-7MJAZMXK.js} +5 -5
- package/chunks/{chunk-6HHAADP2.js → chunk-AAR5SGMD.js} +4 -4
- package/chunks/{chunk-P3S67A5R.js → chunk-B5I2IIWY.js} +3 -3
- package/chunks/{chunk-4DYYRLIE.js → chunk-CEW4HDPK.js} +639 -317
- package/chunks/{chunk-JR63TOV4.js → chunk-CJFNEBYB.js} +1 -1
- package/chunks/{chunk-PMRH4YYW.js → chunk-CUUXANEQ.js} +2 -1
- package/chunks/{chunk-Z56JO4JZ.js → chunk-DK33QJ3V.js} +6 -6
- package/chunks/{chunk-N7FCSERB.js → chunk-DPFBNG72.js} +1 -1
- package/chunks/{chunk-HZYUNZDQ.js → chunk-EQY6POVO.js} +1 -1
- package/chunks/{chunk-MBJ3D6SF.js → chunk-F7NXQH4Y.js} +1 -1
- package/chunks/{chunk-C4CXFRKY.js → chunk-FXMC7SL2.js} +25 -4
- package/chunks/{chunk-CSACSGAW.js → chunk-GGJII3FM.js} +2 -2
- package/chunks/{chunk-EU3U7USF.js → chunk-H3FQOPPS.js} +2 -2
- package/chunks/{chunk-YVEDCAUE.js → chunk-HGT6JR3U.js} +19 -0
- package/chunks/{chunk-MBP3K324.js → chunk-HHEFI4RP.js} +32 -11
- package/chunks/{chunk-B6ABUBYY.js → chunk-JSJFKYWT.js} +67 -5
- package/chunks/{chunk-QABSSDQH.js → chunk-K4PZ5F5T.js} +1 -1
- package/chunks/{chunk-SSFDPGFN.js → chunk-K7CRWKOE.js} +7879 -5472
- package/chunks/{chunk-J2TDDOSM.js → chunk-L37RC7MQ.js} +1 -1
- package/chunks/{chunk-V75IOTWF.js → chunk-L62SEJWY.js} +4 -4
- package/chunks/{chunk-HE54PGVI.js → chunk-LNUXFLLJ.js} +2 -2
- package/chunks/{chunk-PHYCWSDG.js → chunk-LQU7EVFA.js} +1 -1
- package/chunks/{chunk-AUGSSJX3.js → chunk-LTNI523B.js} +2 -2
- package/chunks/{chunk-4LJNI7MU.js → chunk-MO7O5722.js} +262 -7
- package/chunks/{chunk-S2QXYLUB.js → chunk-N6FXRR6P.js} +1 -1
- package/chunks/{chunk-WL4L7EZH.js → chunk-OA4JVLSA.js} +27 -1
- package/chunks/{chunk-XHW5VUIQ.js → chunk-OFEVHGVU.js} +157 -55
- package/chunks/{chunk-T7FSRWQ5.js → chunk-P6LART6G.js} +107 -6
- package/chunks/{chunk-GYGGUR4Q.js → chunk-Q5GLXH5Q.js} +3 -3
- package/chunks/{chunk-SE74CIZD.js → chunk-RDRVMDWV.js} +1 -1
- package/chunks/{chunk-DKXW66XA.js → chunk-SPUYQB4T.js} +1 -1
- package/chunks/{chunk-AGBFWLA5.js → chunk-TISZXPAX.js} +1 -1
- package/chunks/{chunk-ZLM3KXBT.js → chunk-TTUO4DNG.js} +14 -13
- package/chunks/{chunk-4QOPEEL2.js → chunk-UFOKA4PR.js} +1 -1
- package/chunks/{chunk-2MF6ICOL.js → chunk-V7EQTZYR.js} +36 -8
- package/chunks/{chunk-WWQ3QIJS.js → chunk-VBRRNQB7.js} +10 -10
- package/chunks/{chunk-OCC3UTWA.js → chunk-W34A5MJH.js} +1 -1
- package/chunks/{chunk-LAM6CGP5.js → chunk-WDW64OW3.js} +3 -3
- package/chunks/{chunk-DMTQY5GY.js → chunk-WDWJ4OQJ.js} +3 -3
- package/chunks/{chunk-B6KHOCU7.js → chunk-WIASPMLX.js} +8 -8
- package/chunks/{chunk-4FPUAYTT.js → chunk-WLXYAKV7.js} +4 -4
- package/chunks/{chunk-YWUK7OS7.js → chunk-WTSXKLG5.js} +1 -1
- package/chunks/{chunk-7BIZARI2.js → chunk-X362TMXZ.js} +4 -4
- package/chunks/{chunk-ASVORVFT.js → chunk-XLIRF7IK.js} +1 -1
- package/chunks/{chunk-DNKX35GS.js → chunk-XXJKGHZV.js} +4 -4
- package/chunks/{chunk-UI3W56WF.js → chunk-YU56QGXX.js} +1 -1
- package/chunks/{chunk-RJL5T4WJ.js → chunk-YX4LLZ3D.js} +47 -35
- package/chunks/{chunk-YH4J5XI3.js → chunk-YYPXHMWO.js} +4 -4
- package/chunks/{chunk-AAWKZ5R7.js → chunk-ZKMGAFYV.js} +1 -1
- package/chunks/{chunk-EUNRLYJG.js → chunk-ZQTZXVHP.js} +4 -4
- package/chunks/{computer-use-DIP5BWXP.js → computer-use-IYTX45WA.js} +2 -3
- package/chunks/{contextCommand-QU4GJWWD.js → contextCommand-272V77WU.js} +4 -5
- package/chunks/{create-sub-session-TCLIYVGR.js → create-sub-session-CBJMQLYP.js} +2 -3
- package/chunks/{daemon-CEZ27OH4.js → daemon-RNGFPMAB.js} +315 -36
- package/chunks/{daemon-status-provider-SIBFMLBQ.js → daemon-status-provider-L4STG5VO.js} +9 -10
- package/chunks/{earlyInputCapture-L66BEG2C.js → earlyInputCapture-EN4BRREQ.js} +2 -3
- package/chunks/{edit-Y5LAJSST.js → edit-OMAF2QDR.js} +2 -3
- package/chunks/{enter-worktree-X5LML7T4.js → enter-worktree-BBZX52HG.js} +2 -3
- package/chunks/{enterPlanMode-XWF32QOW.js → enterPlanMode-5NNFMBUY.js} +2 -3
- package/chunks/{environment-YVMA5F4I.js → environment-7TEWMJYN.js} +4 -5
- package/chunks/{errors-4Y5SUAOC.js → errors-I4VKVX3F.js} +4 -5
- package/chunks/{exit-worktree-SFHU7XYC.js → exit-worktree-FUWRJI33.js} +2 -3
- package/chunks/{exitPlanMode-FP3NAN6E.js → exitPlanMode-IHCJ5EEI.js} +2 -3
- package/chunks/{fast-path-ZJNFFCBM.js → fast-path-ZA57TOH4.js} +2 -2
- package/chunks/{gemini-5CAB22BI.js → gemini-UOAKVXBG.js} +91 -33
- package/chunks/{geminiContentGenerator-GE3HPIFB.js → geminiContentGenerator-G6UCE6MW.js} +1 -1
- package/chunks/{glob-YPACZAEL.js → glob-4GJQ2UFF.js} +2 -3
- package/chunks/{grep-IPR4XW37.js → grep-5QSYXF2E.js} +2 -3
- package/chunks/{handleAutoUpdate-P2KEP56V.js → handleAutoUpdate-ULHNWL6R.js} +6 -7
- package/chunks/{i18n-BRCT66AA.js → i18n-CYDUTF6F.js} +3 -4
- package/chunks/{initializer-34HXSK3A.js → initializer-NRFKUGIO.js} +7 -8
- package/chunks/{installationInfo-WOTDQMSC.js → installationInfo-WELKRHHE.js} +3 -4
- package/chunks/{list-XOGP7UPP.js → list-JVDXEG2E.js} +10 -11
- package/chunks/{loadedSettingsAdapter-L4YKGU6F.js → loadedSettingsAdapter-WAM4VUTG.js} +7 -8
- package/chunks/{mcp-HH7DIP7Y.js → mcp-YHOZN3ZM.js} +7 -8
- package/chunks/{monitor-25DEBT6U.js → monitor-R7XZPJHQ.js} +2 -3
- package/chunks/{nonInteractiveCli-CT3VSSLK.js → nonInteractiveCli-NSOB6D74.js} +34 -35
- package/chunks/{notebook-edit-TAVXSWQ6.js → notebook-edit-6RNKE2L2.js} +2 -3
- package/chunks/{openaiContentGenerator-6I7DKUAN.js → openaiContentGenerator-XNS5S64I.js} +2 -2
- package/chunks/{pidfile-SIQGCFLZ.js → pidfile-XE4RRR2P.js} +2 -3
- package/chunks/{qwenContentGenerator-5DDIXI2N.js → qwenContentGenerator-APDPJ3VN.js} +3 -4
- package/chunks/{read-file-WDOAYRFU.js → read-file-XSUWXSR7.js} +1 -1
- package/chunks/{ripGrep-MI7PHN3Q.js → ripGrep-APB3CDMU.js} +2 -3
- package/chunks/{run-qwen-serve-PNTYF3RE.js → run-qwen-serve-NWTGPERT.js} +224 -75
- package/chunks/{runtime-V3FBMUTG.js → runtime-EIO3NQKR.js} +8 -9
- package/chunks/{scheduler-4TAUIJBM.js → scheduler-JJDZBDRN.js} +2 -3
- package/chunks/{serve-WB2TR7XM.js → serve-WTIPB3EI.js} +7 -8
- package/chunks/{server-AF6LNFPK.js → server-NJPHQHLH.js} +2458 -755
- package/chunks/{session-BIGK4NRW.js → session-O4AO7FTS.js} +35 -36
- package/chunks/{settings-IXF5FIQO.js → settings-HQNDK7TG.js} +6 -7
- package/chunks/{shell-5VWJ2PPG.js → shell-UIXM6PWG.js} +2 -3
- package/chunks/{skill-SI667Y3B.js → skill-3XMB4ULV.js} +1 -1
- package/chunks/{spawnChannel-IKX6DD7J.js → spawnChannel-3R632QRM.js} +4 -5
- package/chunks/{src-55DZWDAK.js → src-USZKPUOC.js} +33 -4
- package/chunks/{standalone-update-MHLSFHE6.js → standalone-update-L5XW3D7K.js} +4 -5
- package/chunks/{startInteractiveUI-V2EDWLAX.js → startInteractiveUI-CPZOL2WB.js} +174 -122
- package/chunks/{team-create-IZAMLBJS.js → team-create-XXPRF3IQ.js} +2 -3
- package/chunks/{team-plan-approval-3R4RCKCP.js → team-plan-approval-EOTTKHDW.js} +2 -3
- package/chunks/{theme-manager-LCOXZ5DO.js → theme-manager-P6UITO2L.js} +2 -3
- package/chunks/{tool-search-NYNCD5LF.js → tool-search-DDNGK2LR.js} +1 -1
- package/chunks/{total-session-admission-PJWGOMQE.js → total-session-admission-PL5N7DNY.js} +5 -6
- package/chunks/{trustedFolders-DOJDHNYP.js → trustedFolders-C5LMF3UR.js} +3 -4
- package/chunks/types-ML3TRJQ5.js +16 -0
- package/chunks/{updateCheck-ZXL66KIG.js → updateCheck-QTVVISH7.js} +4 -5
- package/chunks/{validateNonInterActiveAuth-JSNJZCYY.js → validateNonInterActiveAuth-RY4RKBQG.js} +30 -31
- package/chunks/{version-3NLBENG5.js → version-U45RYOSD.js} +1 -1
- package/chunks/{workflow-BQYLF7XP.js → workflow-TCIAQYOA.js} +3 -4
- package/chunks/{workspace-providers-status-FA2DGEZW.js → workspace-providers-status-I3G2HYLY.js} +10 -11
- package/chunks/{workspace-registration-store-TARTAGMI.js → workspace-registration-store-IH5GLZ6Y.js} +1 -1
- package/chunks/{workspace-registry-AGE7WAXX.js → workspace-registry-QEKBELIX.js} +5 -6
- package/chunks/{workspace-service-ERC5VR2B.js → workspace-service-ORKHGLP3.js} +17 -14
- package/chunks/{workspace-skills-status-FKYWGNCN.js → workspace-skills-status-D7QCNDYY.js} +9 -10
- package/chunks/{write-file-S47KSK7Z.js → write-file-JGRQR5VM.js} +2 -3
- package/cli.js +11 -11
- package/package.json +3 -3
- package/web-shell/assets/{arc-Zj4EkBUY.js → arc-CUeDlxvE.js} +1 -1
- package/web-shell/assets/{architectureDiagram-3BPJPVTR-DAk1Ua3Y.js → architectureDiagram-3BPJPVTR-PiUszkQA.js} +1 -1
- package/web-shell/assets/{blockDiagram-GPEHLZMM-DBNqUthM.js → blockDiagram-GPEHLZMM-s2wYSO7e.js} +1 -1
- package/web-shell/assets/{c4Diagram-AAUBKEIU-Dq5QGJ53.js → c4Diagram-AAUBKEIU-DyLxZoz9.js} +1 -1
- package/web-shell/assets/channel-DkuJJ0qd.js +1 -0
- package/web-shell/assets/{chunk-2J33WTMH-DY0AeGru.js → chunk-2J33WTMH-Dsdai7Tk.js} +1 -1
- package/web-shell/assets/{chunk-4BX2VUAB-COX3OyIi.js → chunk-4BX2VUAB-DEosOjV7.js} +1 -1
- package/web-shell/assets/{chunk-55IACEB6-Csoyddbh.js → chunk-55IACEB6-sNsRLTXf.js} +1 -1
- package/web-shell/assets/{chunk-727SXJPM-BJPONdxX.js → chunk-727SXJPM-DkRNZzmh.js} +1 -1
- package/web-shell/assets/{chunk-AQP2D5EJ-syLVH6fg.js → chunk-AQP2D5EJ-BrsRNpzd.js} +1 -1
- package/web-shell/assets/{chunk-FMBD7UC4-Cfm59Nhw.js → chunk-FMBD7UC4-CI039lpl.js} +1 -1
- package/web-shell/assets/{chunk-ND2GUHAM-DZf6tAqS.js → chunk-ND2GUHAM-M-pkeIfu.js} +1 -1
- package/web-shell/assets/{chunk-QZHKN3VN-DKBp_v5q.js → chunk-QZHKN3VN-C4ckGNbH.js} +1 -1
- package/web-shell/assets/classDiagram-4FO5ZUOK-CwqHc0TB.js +1 -0
- package/web-shell/assets/classDiagram-v2-Q7XG4LA2-CwqHc0TB.js +1 -0
- package/web-shell/assets/{cose-bilkent-S5V4N54A-DUU3mMQ5.js → cose-bilkent-S5V4N54A-BciKUAFq.js} +1 -1
- package/web-shell/assets/{dagre-BM42HDAG-BUgNfbh-.js → dagre-BM42HDAG-C1nTSgU6.js} +1 -1
- package/web-shell/assets/{diagram-2AECGRRQ-DT7MVfAg.js → diagram-2AECGRRQ-BDPNWr1m.js} +1 -1
- package/web-shell/assets/{diagram-5GNKFQAL-Cv4YG-ec.js → diagram-5GNKFQAL-CNFhTszL.js} +1 -1
- package/web-shell/assets/{diagram-KO2AKTUF-BAe9LLkn.js → diagram-KO2AKTUF-CjPa4p5a.js} +1 -1
- package/web-shell/assets/{diagram-LMA3HP47-DW8wqTIP.js → diagram-LMA3HP47-ZzPykSyG.js} +1 -1
- package/web-shell/assets/{diagram-OG6HWLK6-CmkuWbaK.js → diagram-OG6HWLK6-0UVtZ9A4.js} +1 -1
- package/web-shell/assets/{erDiagram-TEJ5UH35-DwvdHjZ9.js → erDiagram-TEJ5UH35-CajDZCat.js} +1 -1
- package/web-shell/assets/{flowDiagram-I6XJVG4X-CSsXrhW_.js → flowDiagram-I6XJVG4X-BRuFBIvL.js} +1 -1
- package/web-shell/assets/{ganttDiagram-6RSMTGT7-BtuznCdi.js → ganttDiagram-6RSMTGT7-BevGmJqG.js} +1 -1
- package/web-shell/assets/{gitGraphDiagram-PVQCEYII-DGmAm06g.js → gitGraphDiagram-PVQCEYII-DvPMpBMb.js} +1 -1
- package/web-shell/assets/index-Cnvv4VcI.css +5 -0
- package/web-shell/assets/index-D1Uyklxa.js +1088 -0
- package/web-shell/assets/{index-BEtz1cvN.js → index-DzHwQkQq.js} +1 -1
- package/web-shell/assets/{infoDiagram-5YYISTIA-OAiZ1q0K.js → infoDiagram-5YYISTIA-Oixn7tA9.js} +1 -1
- package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-DyK0KTIT.js → ishikawaDiagram-YF4QCWOH-BgKpeg-j.js} +1 -1
- package/web-shell/assets/{journeyDiagram-JHISSGLW-DzYJlwfO.js → journeyDiagram-JHISSGLW-74mwxIaA.js} +1 -1
- package/web-shell/assets/{kanban-definition-UN3LZRKU-CU_tcDwi.js → kanban-definition-UN3LZRKU-D4D6cEqA.js} +1 -1
- package/web-shell/assets/{linear-DvZXjF17.js → linear-atZRE_L2.js} +1 -1
- package/web-shell/assets/{mermaid.core-_ZFEQlOl.js → mermaid.core-KKudX-JC.js} +5 -5
- package/web-shell/assets/{mindmap-definition-RKZ34NQL-Or4p64HB.js → mindmap-definition-RKZ34NQL-CMCej9yG.js} +1 -1
- package/web-shell/assets/{pieDiagram-4H26LBE5-lXE-zZ6b.js → pieDiagram-4H26LBE5-nhx40X7q.js} +1 -1
- package/web-shell/assets/{quadrantDiagram-W4KKPZXB-DUXb9mUV.js → quadrantDiagram-W4KKPZXB-CUmVcN3M.js} +1 -1
- package/web-shell/assets/{requirementDiagram-4Y6WPE33-D_10uHfo.js → requirementDiagram-4Y6WPE33-C_vyEwyy.js} +1 -1
- package/web-shell/assets/{sankeyDiagram-5OEKKPKP-E8c0fPqe.js → sankeyDiagram-5OEKKPKP-DU6G4VWR.js} +1 -1
- package/web-shell/assets/{sequenceDiagram-3UESZ5HK-CJMtFttK.js → sequenceDiagram-3UESZ5HK-DdeRR_KJ.js} +1 -1
- package/web-shell/assets/{stateDiagram-AJRCARHV-DhbviRbB.js → stateDiagram-AJRCARHV-C6iCI8ps.js} +1 -1
- package/web-shell/assets/stateDiagram-v2-BHNVJYJU-C0_6nR6C.js +1 -0
- package/web-shell/assets/{timeline-definition-PNZ67QCA-CM5yMkFK.js → timeline-definition-PNZ67QCA-B_Ea8Cb7.js} +1 -1
- package/web-shell/assets/{vennDiagram-CIIHVFJN-BCi-zWNN.js → vennDiagram-CIIHVFJN-BPUVZEJA.js} +1 -1
- package/web-shell/assets/{wardley-L42UT6IY-BJ_yJNjJ.js → wardley-L42UT6IY-BWEj0lKY.js} +1 -1
- package/web-shell/assets/{wardleyDiagram-YWT4CUSO-TjtLk-qN.js → wardleyDiagram-YWT4CUSO-BvDsdIQw.js} +1 -1
- package/web-shell/assets/{xychartDiagram-2RQKCTM6-D1TjyoQ3.js → xychartDiagram-2RQKCTM6-crz0QhF_.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-JSJFKYWT.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-6W6DI34P.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-4FMG7EDJ.js";
|
|
31
|
+
import "./chunk-SPUYQB4T.js";
|
|
32
|
+
import "./chunk-WTSXKLG5.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-ZQTZXVHP.js";
|
|
95
|
+
import "./chunk-WDWJ4OQJ.js";
|
|
92
96
|
import "./chunk-E5Z2AVNV.js";
|
|
93
97
|
import {
|
|
94
98
|
createWorkspaceProvidersStatusProvider
|
|
95
|
-
} from "./chunk-
|
|
99
|
+
} from "./chunk-7MJAZMXK.js";
|
|
96
100
|
import {
|
|
97
101
|
getAuthTypeFromEnv,
|
|
98
102
|
resolveCliGenerationConfig
|
|
99
|
-
} from "./chunk-
|
|
103
|
+
} from "./chunk-GGJII3FM.js";
|
|
100
104
|
import {
|
|
101
105
|
snapshotProcessEnv
|
|
102
|
-
} from "./chunk-
|
|
106
|
+
} from "./chunk-ZKMGAFYV.js";
|
|
103
107
|
import {
|
|
104
108
|
createWorkspaceSkillsStatusProvider
|
|
105
|
-
} from "./chunk-
|
|
106
|
-
import "./chunk-
|
|
109
|
+
} from "./chunk-WLXYAKV7.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-OFEVHGVU.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-N6FXRR6P.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-FXMC7SL2.js";
|
|
133
137
|
import {
|
|
134
138
|
isVoiceEnabled,
|
|
135
139
|
isVoiceMode,
|
|
136
140
|
readVoiceModel
|
|
137
|
-
} from "./chunk-
|
|
141
|
+
} from "./chunk-DPFBNG72.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-HHEFI4RP.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-Q5GLXH5Q.js";
|
|
204
|
+
import "./chunk-AAR5SGMD.js";
|
|
199
205
|
import "./chunk-57OAHC2Y.js";
|
|
200
206
|
import {
|
|
201
207
|
parseChannelWebhookConfigLenient
|
|
202
208
|
} from "./chunk-OATTJGPJ.js";
|
|
203
209
|
import "./chunk-ZRKBI3DP.js";
|
|
204
210
|
import "./chunk-V6WHFGST.js";
|
|
205
|
-
import "./chunk-
|
|
211
|
+
import "./chunk-UFOKA4PR.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-CUUXANEQ.js";
|
|
212
218
|
import {
|
|
213
219
|
MAX_REGISTERED_WORKSPACES,
|
|
214
220
|
WorkspaceRegistrationStoreCommittedError,
|
|
215
221
|
WorkspaceRegistrationStoreLimitError,
|
|
216
222
|
workspaceRegistrationId
|
|
217
|
-
} from "./chunk-
|
|
223
|
+
} from "./chunk-L37RC7MQ.js";
|
|
218
224
|
import {
|
|
219
225
|
MAX_WORKSPACE_PATH_LENGTH,
|
|
220
226
|
canonicalizeWorkspace,
|
|
@@ -245,18 +251,18 @@ import {
|
|
|
245
251
|
getWritableScopes,
|
|
246
252
|
loadSettings,
|
|
247
253
|
validateSettingValue
|
|
248
|
-
} from "./chunk-
|
|
254
|
+
} from "./chunk-6GETGQWP.js";
|
|
249
255
|
import "./chunk-RKUWKYED.js";
|
|
250
256
|
import {
|
|
251
257
|
SUPPORTED_LANGUAGES
|
|
252
|
-
} from "./chunk-
|
|
253
|
-
import "./chunk-
|
|
258
|
+
} from "./chunk-RDRVMDWV.js";
|
|
259
|
+
import "./chunk-5EBKKSBX.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-4YWLED2A.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-YU56QGXX.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,11 +311,11 @@ 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-K7CRWKOE.js";
|
|
314
319
|
import "./chunk-H6XPXXMH.js";
|
|
315
320
|
import "./chunk-5M6IDOMF.js";
|
|
316
321
|
import {
|
|
@@ -361,7 +366,7 @@ import {
|
|
|
361
366
|
recordDaemonHttpRequest,
|
|
362
367
|
recordDaemonHttpResponse,
|
|
363
368
|
withDaemonRequestSpan
|
|
364
|
-
} from "./chunk-
|
|
369
|
+
} from "./chunk-P6LART6G.js";
|
|
365
370
|
import "./chunk-4WKAA4KS.js";
|
|
366
371
|
import "./chunk-CR3C7WXL.js";
|
|
367
372
|
import "./chunk-CARU2RR2.js";
|
|
@@ -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,63 +14950,118 @@ __name(registerWorkspaceAuthRoutes, "registerWorkspaceAuthRoutes");
|
|
|
14627
14950
|
|
|
14628
14951
|
// packages/cli/src/serve/routes/workspace-extensions.ts
|
|
14629
14952
|
init_esbuild_shims();
|
|
14953
|
+
|
|
14954
|
+
// packages/cli/src/serve/extension-operation-scheduler.ts
|
|
14955
|
+
init_esbuild_shims();
|
|
14956
|
+
function abortReason(signal) {
|
|
14957
|
+
return signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
14958
|
+
}
|
|
14959
|
+
__name(abortReason, "abortReason");
|
|
14960
|
+
function createFifoTaskQueue(limit) {
|
|
14961
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
14962
|
+
throw new Error(
|
|
14963
|
+
`Task queue limit must be a positive integer, got ${limit}.`
|
|
14964
|
+
);
|
|
14965
|
+
}
|
|
14966
|
+
let active = 0;
|
|
14967
|
+
const queued = [];
|
|
14968
|
+
const pump = /* @__PURE__ */ __name(() => {
|
|
14969
|
+
while (active < limit && queued.length > 0) {
|
|
14970
|
+
const item = queued.shift();
|
|
14971
|
+
const removeAbortListener = item.removeAbortListener;
|
|
14972
|
+
item.removeAbortListener = void 0;
|
|
14973
|
+
removeAbortListener?.();
|
|
14974
|
+
if (item.signal?.aborted) {
|
|
14975
|
+
item.reject(abortReason(item.signal));
|
|
14976
|
+
continue;
|
|
14977
|
+
}
|
|
14978
|
+
active += 1;
|
|
14979
|
+
try {
|
|
14980
|
+
item.onStart?.();
|
|
14981
|
+
} catch (error2) {
|
|
14982
|
+
active -= 1;
|
|
14983
|
+
item.reject(error2);
|
|
14984
|
+
continue;
|
|
14985
|
+
}
|
|
14986
|
+
let released = false;
|
|
14987
|
+
const release = /* @__PURE__ */ __name(() => {
|
|
14988
|
+
if (released) return;
|
|
14989
|
+
released = true;
|
|
14990
|
+
active -= 1;
|
|
14991
|
+
pump();
|
|
14992
|
+
}, "release");
|
|
14993
|
+
Promise.resolve().then(() => item.task(release)).then(item.resolve, item.reject).finally(release);
|
|
14994
|
+
}
|
|
14995
|
+
}, "pump");
|
|
14996
|
+
const enqueue = /* @__PURE__ */ __name((task, options = {}) => new Promise((resolve5, reject) => {
|
|
14997
|
+
if (options.signal?.aborted) {
|
|
14998
|
+
reject(abortReason(options.signal));
|
|
14999
|
+
return;
|
|
15000
|
+
}
|
|
15001
|
+
const item = {
|
|
15002
|
+
task,
|
|
15003
|
+
...options.signal ? { signal: options.signal } : {},
|
|
15004
|
+
...options.onStart ? { onStart: options.onStart } : {},
|
|
15005
|
+
resolve: resolve5,
|
|
15006
|
+
reject
|
|
15007
|
+
};
|
|
15008
|
+
if (options.signal) {
|
|
15009
|
+
const onAbort = /* @__PURE__ */ __name(() => {
|
|
15010
|
+
const index = queued.indexOf(item);
|
|
15011
|
+
if (index < 0) return;
|
|
15012
|
+
queued.splice(index, 1);
|
|
15013
|
+
item.removeAbortListener?.();
|
|
15014
|
+
reject(abortReason(options.signal));
|
|
15015
|
+
}, "onAbort");
|
|
15016
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
15017
|
+
item.removeAbortListener = () => options.signal?.removeEventListener("abort", onAbort);
|
|
15018
|
+
}
|
|
15019
|
+
queued.push(item);
|
|
15020
|
+
pump();
|
|
15021
|
+
}), "enqueue");
|
|
15022
|
+
const run = /* @__PURE__ */ __name((task, options) => enqueue(async () => await task(), options), "run");
|
|
15023
|
+
return { run, runUntilReleased: enqueue };
|
|
15024
|
+
}
|
|
15025
|
+
__name(createFifoTaskQueue, "createFifoTaskQueue");
|
|
15026
|
+
|
|
15027
|
+
// packages/cli/src/serve/routes/workspace-extensions-controller.ts
|
|
15028
|
+
init_esbuild_shims();
|
|
14630
15029
|
import * as crypto from "node:crypto";
|
|
14631
|
-
|
|
14632
|
-
|
|
14633
|
-
|
|
14634
|
-
|
|
14635
|
-
|
|
14636
|
-
|
|
14637
|
-
|
|
14638
|
-
|
|
14639
|
-
} = deps;
|
|
15030
|
+
var MAX_UNFINISHED_EXTENSION_OPERATIONS = 10;
|
|
15031
|
+
var sanitizeDaemonMessage = /* @__PURE__ */ __name((message) => redactUrlCredentials(stripAnsiAndControl(message)), "sanitizeDaemonMessage");
|
|
15032
|
+
var EXTENSION_PREPARATION_CONCURRENCY = 2;
|
|
15033
|
+
var EXTENSION_REFRESH_TIMEOUT_MS = 3e4;
|
|
15034
|
+
var RECONCILE_SLOW_MS = 3e4;
|
|
15035
|
+
var EXTENSION_QUEUE_FULL_MESSAGE = "Extension operation queue is full";
|
|
15036
|
+
function createExtensionsController(deps) {
|
|
15037
|
+
const { boundWorkspace, bridge, workspace } = deps;
|
|
14640
15038
|
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",
|
|
15039
|
+
const preparationQueue = createFifoTaskQueue(
|
|
15040
|
+
EXTENSION_PREPARATION_CONCURRENCY
|
|
15041
|
+
);
|
|
15042
|
+
const commitQueue = createFifoTaskQueue(1);
|
|
15043
|
+
let unfinishedOperationCount = 0;
|
|
15044
|
+
const acquireOperationSlot = /* @__PURE__ */ __name((res) => {
|
|
15045
|
+
if (unfinishedOperationCount >= MAX_UNFINISHED_EXTENSION_OPERATIONS) {
|
|
15046
|
+
res.status(429).json({
|
|
15047
|
+
error: EXTENSION_QUEUE_FULL_MESSAGE,
|
|
15048
|
+
code: "extension_queue_full"
|
|
15049
|
+
});
|
|
15050
|
+
return void 0;
|
|
15051
|
+
}
|
|
15052
|
+
unfinishedOperationCount += 1;
|
|
15053
|
+
let released = false;
|
|
15054
|
+
return () => {
|
|
15055
|
+
if (released) return;
|
|
15056
|
+
released = true;
|
|
15057
|
+
unfinishedOperationCount -= 1;
|
|
15058
|
+
};
|
|
15059
|
+
}, "acquireOperationSlot");
|
|
15060
|
+
const createExtensionManager = /* @__PURE__ */ __name((workspaceDir = boundWorkspace, trustedOverride) => new ExtensionManager({
|
|
15061
|
+
workspaceDir,
|
|
15062
|
+
isWorkspaceTrusted: trustedOverride ?? getWorkspaceTrustStatus(loadSettings(workspaceDir).merged, workspaceDir).effective.state === "trusted",
|
|
14686
15063
|
requestConsent: /* @__PURE__ */ __name(() => Promise.resolve(), "requestConsent"),
|
|
15064
|
+
networkPolicy: "public",
|
|
14687
15065
|
requestSetting: /* @__PURE__ */ __name(async (setting) => {
|
|
14688
15066
|
throw new Error(
|
|
14689
15067
|
`Extension setting "${setting.envVar}" requires interactive configuration and is not supported over the daemon install endpoint.`
|
|
@@ -14695,8 +15073,12 @@ function registerWorkspaceExtensionRoutes(app, deps) {
|
|
|
14695
15073
|
);
|
|
14696
15074
|
}, "requestChoicePlugin")
|
|
14697
15075
|
}), "createExtensionManager");
|
|
14698
|
-
const validateExtensionMutationClient = /* @__PURE__ */ __name((req, res,
|
|
14699
|
-
const clientId = parseAndValidateWorkspaceClientId(
|
|
15076
|
+
const validateExtensionMutationClient = /* @__PURE__ */ __name((req, res, opts = {}) => {
|
|
15077
|
+
const clientId = parseAndValidateWorkspaceClientId(
|
|
15078
|
+
req,
|
|
15079
|
+
res,
|
|
15080
|
+
opts.bridges ?? bridge
|
|
15081
|
+
);
|
|
14700
15082
|
if (clientId === null) return false;
|
|
14701
15083
|
if (clientId === void 0 && opts.requireClientId !== false) {
|
|
14702
15084
|
res.status(400).json({
|
|
@@ -14705,98 +15087,25 @@ function registerWorkspaceExtensionRoutes(app, deps) {
|
|
|
14705
15087
|
});
|
|
14706
15088
|
return false;
|
|
14707
15089
|
}
|
|
14708
|
-
buildWorkspaceCtx(route, clientId);
|
|
14709
15090
|
return true;
|
|
14710
15091
|
}, "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
15092
|
const extensionOperations = /* @__PURE__ */ new Map();
|
|
14792
15093
|
const isTerminalExtensionOperation = /* @__PURE__ */ __name((operation) => operation.status !== "queued" && operation.status !== "running", "isTerminalExtensionOperation");
|
|
14793
15094
|
const redactExtensionOperationResult = /* @__PURE__ */ __name((event) => ({
|
|
14794
15095
|
...event,
|
|
14795
15096
|
...event.source ? { source: redactUrlCredentials(event.source) } : {}
|
|
14796
15097
|
}), "redactExtensionOperationResult");
|
|
14797
|
-
const
|
|
14798
|
-
|
|
14799
|
-
|
|
15098
|
+
const bridgeMutationEvent = /* @__PURE__ */ __name((event) => {
|
|
15099
|
+
const redacted = redactExtensionOperationResult(event);
|
|
15100
|
+
if (event.status === "checked" || event.status === "refreshed") {
|
|
15101
|
+
const { status: _status, states: _states, ...bridgeEvent } = redacted;
|
|
15102
|
+
return bridgeEvent;
|
|
15103
|
+
}
|
|
15104
|
+
return redacted;
|
|
15105
|
+
}, "bridgeMutationEvent");
|
|
15106
|
+
const pruneExtensionOperations = /* @__PURE__ */ __name(() => {
|
|
15107
|
+
const terminalCount = /* @__PURE__ */ __name(() => [...extensionOperations.values()].filter(isTerminalExtensionOperation).length, "terminalCount");
|
|
15108
|
+
while (terminalCount() > maxExtensionOperationHistory) {
|
|
14800
15109
|
let evicted = false;
|
|
14801
15110
|
for (const [id, storedOperation] of extensionOperations) {
|
|
14802
15111
|
if (!isTerminalExtensionOperation(storedOperation)) continue;
|
|
@@ -14806,6 +15115,10 @@ function registerWorkspaceExtensionRoutes(app, deps) {
|
|
|
14806
15115
|
}
|
|
14807
15116
|
if (!evicted) break;
|
|
14808
15117
|
}
|
|
15118
|
+
}, "pruneExtensionOperations");
|
|
15119
|
+
const rememberExtensionOperation = /* @__PURE__ */ __name((operation) => {
|
|
15120
|
+
extensionOperations.set(operation.operationId, operation);
|
|
15121
|
+
pruneExtensionOperations();
|
|
14809
15122
|
}, "rememberExtensionOperation");
|
|
14810
15123
|
const updateExtensionOperation = /* @__PURE__ */ __name((operationId, patch) => {
|
|
14811
15124
|
const current = extensionOperations.get(operationId);
|
|
@@ -14815,12 +15128,43 @@ function registerWorkspaceExtensionRoutes(app, deps) {
|
|
|
14815
15128
|
...patch,
|
|
14816
15129
|
updatedAt: Date.now()
|
|
14817
15130
|
});
|
|
15131
|
+
pruneExtensionOperations();
|
|
14818
15132
|
}, "updateExtensionOperation");
|
|
14819
|
-
|
|
14820
|
-
|
|
14821
|
-
|
|
14822
|
-
|
|
15133
|
+
let extensionsStatusCache;
|
|
15134
|
+
const refreshExtensionsForAllSessions = /* @__PURE__ */ __name(async () => {
|
|
15135
|
+
const queueAbort = new AbortController();
|
|
15136
|
+
let releaseCommitLane;
|
|
15137
|
+
const refresh = commitQueue.runUntilReleased(
|
|
15138
|
+
async (release) => {
|
|
15139
|
+
releaseCommitLane = release;
|
|
15140
|
+
extensionsStatusCache = void 0;
|
|
15141
|
+
return await workspace.refreshExtensionsForAllSessions();
|
|
15142
|
+
},
|
|
15143
|
+
{ signal: queueAbort.signal }
|
|
15144
|
+
);
|
|
15145
|
+
let timer;
|
|
15146
|
+
try {
|
|
15147
|
+
return await Promise.race([
|
|
15148
|
+
refresh,
|
|
15149
|
+
new Promise((_resolve, reject) => {
|
|
15150
|
+
timer = setTimeout(() => {
|
|
15151
|
+
const error2 = new Error(
|
|
15152
|
+
`extension refresh timed out after ${EXTENSION_REFRESH_TIMEOUT_MS}ms`
|
|
15153
|
+
);
|
|
15154
|
+
releaseCommitLane?.();
|
|
15155
|
+
queueAbort.abort(error2);
|
|
15156
|
+
reject(error2);
|
|
15157
|
+
}, EXTENSION_REFRESH_TIMEOUT_MS);
|
|
15158
|
+
timer.unref?.();
|
|
15159
|
+
})
|
|
15160
|
+
]);
|
|
15161
|
+
} finally {
|
|
15162
|
+
if (timer) clearTimeout(timer);
|
|
14823
15163
|
}
|
|
15164
|
+
}, "refreshExtensionsForAllSessions");
|
|
15165
|
+
const runQueuedExtensionMutation = /* @__PURE__ */ __name((operation, failureContext, res, run, options = {}) => {
|
|
15166
|
+
const releaseOperationSlot = acquireOperationSlot(res);
|
|
15167
|
+
if (!releaseOperationSlot) return;
|
|
14824
15168
|
const operationId = crypto.randomUUID();
|
|
14825
15169
|
const now = Date.now();
|
|
14826
15170
|
rememberExtensionOperation({
|
|
@@ -14833,68 +15177,379 @@ function registerWorkspaceExtensionRoutes(app, deps) {
|
|
|
14833
15177
|
...failureContext.source ? { source: redactUrlCredentials(failureContext.source) } : {},
|
|
14834
15178
|
...failureContext.name ? { name: failureContext.name } : {}
|
|
14835
15179
|
});
|
|
14836
|
-
|
|
14837
|
-
|
|
15180
|
+
const operationBasePath = options.operationBasePath ?? "/workspace/extensions/operations";
|
|
15181
|
+
try {
|
|
15182
|
+
res.status(202).location(`${operationBasePath}/${operationId}`).set("Retry-After", "1").json({ accepted: true, operationId });
|
|
15183
|
+
} catch {
|
|
15184
|
+
extensionOperations.delete(operationId);
|
|
15185
|
+
releaseOperationSlot();
|
|
15186
|
+
return;
|
|
15187
|
+
}
|
|
15188
|
+
void (async () => {
|
|
15189
|
+
let deadline;
|
|
15190
|
+
let committedGeneration;
|
|
15191
|
+
let reconciliationReservation;
|
|
15192
|
+
let mutationEvent;
|
|
15193
|
+
const commitWarnings = [];
|
|
15194
|
+
const runReconciliation = /* @__PURE__ */ __name(async (task) => {
|
|
15195
|
+
const reservation = reconciliationReservation;
|
|
15196
|
+
reconciliationReservation = void 0;
|
|
15197
|
+
return reservation ? await reservation.run(task) : await task();
|
|
15198
|
+
}, "runReconciliation");
|
|
14838
15199
|
try {
|
|
14839
|
-
updateExtensionOperation(operationId, {
|
|
14840
|
-
|
|
15200
|
+
updateExtensionOperation(operationId, {
|
|
15201
|
+
status: "running",
|
|
15202
|
+
phase: "preparing"
|
|
15203
|
+
});
|
|
15204
|
+
const extensionManager = options.manager ?? createExtensionManager();
|
|
15205
|
+
const deadlineController = new AbortController();
|
|
15206
|
+
let deadlineStarted = false;
|
|
15207
|
+
const startDeadline = /* @__PURE__ */ __name(() => {
|
|
15208
|
+
if (deadlineStarted) return;
|
|
15209
|
+
deadlineStarted = true;
|
|
15210
|
+
if (options.deadlineMs) {
|
|
15211
|
+
deadline = setTimeout(() => {
|
|
15212
|
+
const error2 = new Error(
|
|
15213
|
+
`Extension ${operation} exceeded its ${options.deadlineMs}ms preparation deadline.`
|
|
15214
|
+
);
|
|
15215
|
+
error2.code = "extension_prepare_timeout";
|
|
15216
|
+
deadlineController.abort(error2);
|
|
15217
|
+
}, options.deadlineMs);
|
|
15218
|
+
deadline.unref?.();
|
|
15219
|
+
}
|
|
15220
|
+
}, "startDeadline");
|
|
15221
|
+
let pendingPreparations = 0;
|
|
15222
|
+
let activePreparations = 0;
|
|
15223
|
+
const updatePreparationState = /* @__PURE__ */ __name(() => {
|
|
15224
|
+
if (activePreparations > 0) {
|
|
15225
|
+
updateExtensionOperation(operationId, {
|
|
15226
|
+
status: "running",
|
|
15227
|
+
phase: "preparing"
|
|
15228
|
+
});
|
|
15229
|
+
} else if (pendingPreparations > 0) {
|
|
15230
|
+
updateExtensionOperation(operationId, {
|
|
15231
|
+
status: "queued",
|
|
15232
|
+
phase: void 0
|
|
15233
|
+
});
|
|
15234
|
+
}
|
|
15235
|
+
}, "updatePreparationState");
|
|
15236
|
+
const context = {
|
|
15237
|
+
prepare: /* @__PURE__ */ __name(async (task) => {
|
|
15238
|
+
pendingPreparations += 1;
|
|
15239
|
+
let started = false;
|
|
15240
|
+
updatePreparationState();
|
|
15241
|
+
try {
|
|
15242
|
+
const prepared = await preparationQueue.run(
|
|
15243
|
+
async () => {
|
|
15244
|
+
try {
|
|
15245
|
+
return await task(deadlineController.signal);
|
|
15246
|
+
} finally {
|
|
15247
|
+
activePreparations -= 1;
|
|
15248
|
+
updatePreparationState();
|
|
15249
|
+
}
|
|
15250
|
+
},
|
|
15251
|
+
{
|
|
15252
|
+
signal: deadlineController.signal,
|
|
15253
|
+
onStart: /* @__PURE__ */ __name(() => {
|
|
15254
|
+
startDeadline();
|
|
15255
|
+
started = true;
|
|
15256
|
+
pendingPreparations -= 1;
|
|
15257
|
+
activePreparations += 1;
|
|
15258
|
+
updatePreparationState();
|
|
15259
|
+
}, "onStart")
|
|
15260
|
+
}
|
|
15261
|
+
);
|
|
15262
|
+
deadlineController.signal.throwIfAborted();
|
|
15263
|
+
return prepared;
|
|
15264
|
+
} catch (error2) {
|
|
15265
|
+
if (!started) {
|
|
15266
|
+
pendingPreparations -= 1;
|
|
15267
|
+
updatePreparationState();
|
|
15268
|
+
}
|
|
15269
|
+
if (deadlineController.signal.aborted) {
|
|
15270
|
+
throw deadlineController.signal.reason;
|
|
15271
|
+
}
|
|
15272
|
+
throw error2;
|
|
15273
|
+
}
|
|
15274
|
+
}, "prepare"),
|
|
15275
|
+
commit: /* @__PURE__ */ __name(async (task) => {
|
|
15276
|
+
updateExtensionOperation(operationId, {
|
|
15277
|
+
status: "running",
|
|
15278
|
+
phase: "committing"
|
|
15279
|
+
});
|
|
15280
|
+
const result = await commitQueue.runUntilReleased(
|
|
15281
|
+
async (release) => await task((generation) => {
|
|
15282
|
+
reconciliationReservation ??= options.reserveRuntimeReconciliation?.();
|
|
15283
|
+
committedGeneration = generation;
|
|
15284
|
+
release();
|
|
15285
|
+
})
|
|
15286
|
+
);
|
|
15287
|
+
if (committedGeneration === void 0) {
|
|
15288
|
+
reconciliationReservation ??= options.reserveRuntimeReconciliation?.();
|
|
15289
|
+
committedGeneration = result.generation;
|
|
15290
|
+
}
|
|
15291
|
+
for (const warning of result.warnings ?? []) {
|
|
15292
|
+
commitWarnings.push({
|
|
15293
|
+
workspaceCwd: boundWorkspace,
|
|
15294
|
+
code: warning.code,
|
|
15295
|
+
error: sanitizeDaemonMessage(warning.error).slice(0, 500)
|
|
15296
|
+
});
|
|
15297
|
+
}
|
|
15298
|
+
return result;
|
|
15299
|
+
}, "commit")
|
|
15300
|
+
};
|
|
14841
15301
|
await extensionManager.refreshCache();
|
|
14842
|
-
const event = await
|
|
14843
|
-
|
|
14844
|
-
|
|
14845
|
-
|
|
15302
|
+
const event = await run(
|
|
15303
|
+
extensionManager,
|
|
15304
|
+
deadlineController.signal,
|
|
15305
|
+
context
|
|
14846
15306
|
);
|
|
15307
|
+
mutationEvent = event;
|
|
15308
|
+
if (deadline) clearTimeout(deadline);
|
|
14847
15309
|
extensionsStatusCache = void 0;
|
|
14848
|
-
|
|
14849
|
-
|
|
14850
|
-
|
|
15310
|
+
if (options.skipRefresh || event.updated === false) {
|
|
15311
|
+
reconciliationReservation?.release();
|
|
15312
|
+
reconciliationReservation = void 0;
|
|
14851
15313
|
updateExtensionOperation(operationId, {
|
|
14852
|
-
status: "succeeded",
|
|
14853
|
-
|
|
14854
|
-
|
|
14855
|
-
|
|
14856
|
-
failed: result.failed
|
|
14857
|
-
}
|
|
15314
|
+
status: commitWarnings.length > 0 ? "succeeded_with_warnings" : "succeeded",
|
|
15315
|
+
phase: void 0,
|
|
15316
|
+
result: redactExtensionOperationResult(event),
|
|
15317
|
+
...commitWarnings.length > 0 ? { warnings: commitWarnings } : {}
|
|
14858
15318
|
});
|
|
14859
|
-
|
|
14860
|
-
|
|
14861
|
-
|
|
14862
|
-
|
|
14863
|
-
|
|
14864
|
-
|
|
15319
|
+
return;
|
|
15320
|
+
}
|
|
15321
|
+
if (committedGeneration === void 0) {
|
|
15322
|
+
committedGeneration = (await extensionManager.getExtensionStoreSnapshot()).generation;
|
|
15323
|
+
reconciliationReservation ??= options.reserveRuntimeReconciliation?.();
|
|
15324
|
+
}
|
|
15325
|
+
updateExtensionOperation(operationId, {
|
|
15326
|
+
status: "running",
|
|
15327
|
+
phase: "reconciling"
|
|
15328
|
+
});
|
|
15329
|
+
const refreshTargets = typeof options.refreshRuntimes === "function" ? options.refreshRuntimes() : options.refreshRuntimes;
|
|
15330
|
+
if (refreshTargets) {
|
|
15331
|
+
const results = await runReconciliation(
|
|
15332
|
+
async () => await Promise.all(
|
|
15333
|
+
refreshTargets.map(async (runtime) => {
|
|
15334
|
+
const startedAt = Date.now();
|
|
15335
|
+
try {
|
|
15336
|
+
runtime.workspaceService.invalidateWorkspaceSkillsStatus();
|
|
15337
|
+
return {
|
|
15338
|
+
status: "fulfilled",
|
|
15339
|
+
result: await runtime.bridge.refreshExtensionsForAllSessions(
|
|
15340
|
+
bridgeMutationEvent(event)
|
|
15341
|
+
),
|
|
15342
|
+
elapsedMs: Date.now() - startedAt
|
|
15343
|
+
};
|
|
15344
|
+
} catch (reason) {
|
|
15345
|
+
return {
|
|
15346
|
+
status: "rejected",
|
|
15347
|
+
reason,
|
|
15348
|
+
elapsedMs: Date.now() - startedAt
|
|
15349
|
+
};
|
|
15350
|
+
}
|
|
15351
|
+
})
|
|
15352
|
+
)
|
|
14865
15353
|
);
|
|
15354
|
+
let refreshed = 0;
|
|
15355
|
+
let failed = 0;
|
|
15356
|
+
const warnings = [
|
|
15357
|
+
...commitWarnings
|
|
15358
|
+
];
|
|
15359
|
+
for (let index = 0; index < results.length; index += 1) {
|
|
15360
|
+
const settled = results[index];
|
|
15361
|
+
const runtime = refreshTargets[index];
|
|
15362
|
+
if (settled.status === "fulfilled") {
|
|
15363
|
+
refreshed += settled.result.refreshed;
|
|
15364
|
+
failed += settled.result.failed;
|
|
15365
|
+
if (settled.result.failed > 0) {
|
|
15366
|
+
warnings.push({
|
|
15367
|
+
workspaceId: runtime.workspaceId,
|
|
15368
|
+
workspaceCwd: runtime.workspaceCwd,
|
|
15369
|
+
error: `${settled.result.failed} session refresh(es) failed`
|
|
15370
|
+
});
|
|
15371
|
+
} else {
|
|
15372
|
+
options.onRuntimeReconciled?.(runtime, committedGeneration);
|
|
15373
|
+
}
|
|
15374
|
+
} else {
|
|
15375
|
+
failed += 1;
|
|
15376
|
+
const message = sanitizeDaemonMessage(
|
|
15377
|
+
settled.reason instanceof Error ? settled.reason.message : String(settled.reason)
|
|
15378
|
+
);
|
|
15379
|
+
warnings.push({
|
|
15380
|
+
workspaceId: runtime.workspaceId,
|
|
15381
|
+
workspaceCwd: runtime.workspaceCwd,
|
|
15382
|
+
error: message.slice(0, 500)
|
|
15383
|
+
});
|
|
15384
|
+
try {
|
|
15385
|
+
runtime.bridge.broadcastExtensionsChanged({
|
|
15386
|
+
...bridgeMutationEvent(event),
|
|
15387
|
+
refreshed: 0,
|
|
15388
|
+
failed: 1,
|
|
15389
|
+
error: message.slice(0, 500)
|
|
15390
|
+
});
|
|
15391
|
+
} catch {
|
|
15392
|
+
}
|
|
15393
|
+
}
|
|
15394
|
+
if (settled.elapsedMs > RECONCILE_SLOW_MS) {
|
|
15395
|
+
warnings.push({
|
|
15396
|
+
workspaceId: runtime.workspaceId,
|
|
15397
|
+
workspaceCwd: runtime.workspaceCwd,
|
|
15398
|
+
code: "reconcile_slow",
|
|
15399
|
+
error: `Runtime reconciliation took ${settled.elapsedMs}ms.`
|
|
15400
|
+
});
|
|
15401
|
+
}
|
|
15402
|
+
}
|
|
14866
15403
|
updateExtensionOperation(operationId, {
|
|
14867
|
-
status: "
|
|
15404
|
+
status: warnings.length > 0 ? "succeeded_with_warnings" : "succeeded",
|
|
15405
|
+
phase: void 0,
|
|
14868
15406
|
result: {
|
|
14869
15407
|
...redactExtensionOperationResult(event),
|
|
14870
|
-
refreshed
|
|
14871
|
-
failed
|
|
14872
|
-
|
|
15408
|
+
refreshed,
|
|
15409
|
+
failed
|
|
15410
|
+
},
|
|
15411
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
15412
|
+
});
|
|
15413
|
+
} else {
|
|
15414
|
+
try {
|
|
15415
|
+
const { result, elapsedMs } = await runReconciliation(async () => {
|
|
15416
|
+
workspace.invalidateWorkspaceSkillsStatus();
|
|
15417
|
+
const startedAt = Date.now();
|
|
15418
|
+
const result2 = await bridge.refreshExtensionsForAllSessions(
|
|
15419
|
+
bridgeMutationEvent(event)
|
|
15420
|
+
);
|
|
15421
|
+
return { result: result2, elapsedMs: Date.now() - startedAt };
|
|
15422
|
+
});
|
|
15423
|
+
const warnings = [...commitWarnings];
|
|
15424
|
+
if (result.failed > 0) {
|
|
15425
|
+
warnings.push({
|
|
15426
|
+
workspaceCwd: boundWorkspace,
|
|
15427
|
+
error: `${result.failed} session refresh(es) failed`
|
|
15428
|
+
});
|
|
15429
|
+
}
|
|
15430
|
+
if (elapsedMs > RECONCILE_SLOW_MS) {
|
|
15431
|
+
warnings.push({
|
|
15432
|
+
workspaceCwd: boundWorkspace,
|
|
15433
|
+
code: "reconcile_slow",
|
|
15434
|
+
error: `Runtime reconciliation took ${elapsedMs}ms.`
|
|
15435
|
+
});
|
|
14873
15436
|
}
|
|
15437
|
+
updateExtensionOperation(operationId, {
|
|
15438
|
+
status: warnings.length > 0 ? "succeeded_with_warnings" : "succeeded",
|
|
15439
|
+
phase: void 0,
|
|
15440
|
+
result: {
|
|
15441
|
+
...redactExtensionOperationResult(event),
|
|
15442
|
+
refreshed: result.refreshed,
|
|
15443
|
+
failed: result.failed
|
|
15444
|
+
},
|
|
15445
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
15446
|
+
});
|
|
15447
|
+
writeStderrLine(
|
|
15448
|
+
`qwen serve: [${boundWorkspace}] extensions ${operation}: refreshed ${result.refreshed} session(s), ${result.failed} failed`
|
|
15449
|
+
);
|
|
15450
|
+
} catch (refreshErr) {
|
|
15451
|
+
const message = sanitizeDaemonMessage(
|
|
15452
|
+
refreshErr instanceof Error ? refreshErr.message : String(refreshErr)
|
|
15453
|
+
);
|
|
15454
|
+
updateExtensionOperation(operationId, {
|
|
15455
|
+
status: "succeeded_with_warnings",
|
|
15456
|
+
phase: void 0,
|
|
15457
|
+
result: {
|
|
15458
|
+
...redactExtensionOperationResult(event),
|
|
15459
|
+
refreshed: 0,
|
|
15460
|
+
failed: 1,
|
|
15461
|
+
error: message.slice(0, 500)
|
|
15462
|
+
},
|
|
15463
|
+
warnings: [
|
|
15464
|
+
...commitWarnings,
|
|
15465
|
+
{
|
|
15466
|
+
workspaceCwd: boundWorkspace,
|
|
15467
|
+
error: message.slice(0, 500)
|
|
15468
|
+
}
|
|
15469
|
+
]
|
|
15470
|
+
});
|
|
15471
|
+
try {
|
|
15472
|
+
bridge.broadcastExtensionsChanged({
|
|
15473
|
+
...bridgeMutationEvent(event),
|
|
15474
|
+
refreshed: 0,
|
|
15475
|
+
failed: 1,
|
|
15476
|
+
error: message.slice(0, 500)
|
|
15477
|
+
});
|
|
15478
|
+
} catch (broadcastErr) {
|
|
15479
|
+
writeStderrLine(
|
|
15480
|
+
`qwen serve: [${boundWorkspace}] extensions ${operation}: failed to broadcast refresh failure: ${sanitizeDaemonMessage(
|
|
15481
|
+
broadcastErr instanceof Error ? broadcastErr.message : String(broadcastErr)
|
|
15482
|
+
)}`
|
|
15483
|
+
);
|
|
15484
|
+
}
|
|
15485
|
+
writeStderrLine(
|
|
15486
|
+
`qwen serve: [${boundWorkspace}] extensions ${operation}: mutation succeeded but refresh failed: ${message}`
|
|
15487
|
+
);
|
|
15488
|
+
}
|
|
15489
|
+
}
|
|
15490
|
+
} catch (err) {
|
|
15491
|
+
const message = sanitizeDaemonMessage(
|
|
15492
|
+
err instanceof Error ? err.message : String(err)
|
|
15493
|
+
);
|
|
15494
|
+
const code = err && typeof err === "object" && typeof err.code === "string" ? err.code : void 0;
|
|
15495
|
+
if (committedGeneration !== void 0) {
|
|
15496
|
+
extensionsStatusCache = void 0;
|
|
15497
|
+
const error2 = `Commit succeeded but post-commit work failed: ${message}`.slice(
|
|
15498
|
+
0,
|
|
15499
|
+
500
|
|
15500
|
+
);
|
|
15501
|
+
const warnings = [
|
|
15502
|
+
...commitWarnings,
|
|
15503
|
+
{
|
|
15504
|
+
workspaceCwd: boundWorkspace,
|
|
15505
|
+
code: "post_commit_failed",
|
|
15506
|
+
error: error2
|
|
15507
|
+
}
|
|
15508
|
+
];
|
|
15509
|
+
try {
|
|
15510
|
+
workspace.invalidateWorkspaceSkillsStatus();
|
|
15511
|
+
} catch (invalidationError) {
|
|
15512
|
+
warnings.push({
|
|
15513
|
+
workspaceCwd: boundWorkspace,
|
|
15514
|
+
code: "status_invalidation_failed",
|
|
15515
|
+
error: sanitizeDaemonMessage(
|
|
15516
|
+
invalidationError instanceof Error ? invalidationError.message : String(invalidationError)
|
|
15517
|
+
).slice(0, 500)
|
|
15518
|
+
});
|
|
15519
|
+
}
|
|
15520
|
+
updateExtensionOperation(operationId, {
|
|
15521
|
+
status: "succeeded_with_warnings",
|
|
15522
|
+
phase: void 0,
|
|
15523
|
+
...mutationEvent ? { result: redactExtensionOperationResult(mutationEvent) } : {},
|
|
15524
|
+
warnings
|
|
14874
15525
|
});
|
|
14875
15526
|
try {
|
|
14876
15527
|
bridge.broadcastExtensionsChanged({
|
|
14877
|
-
...
|
|
15528
|
+
...mutationEvent ? bridgeMutationEvent(mutationEvent) : {
|
|
15529
|
+
...failureContext.source ? {
|
|
15530
|
+
source: redactUrlCredentials(failureContext.source)
|
|
15531
|
+
} : {},
|
|
15532
|
+
...failureContext.name ? { name: failureContext.name } : {}
|
|
15533
|
+
},
|
|
14878
15534
|
refreshed: 0,
|
|
14879
15535
|
failed: 1,
|
|
14880
|
-
error:
|
|
15536
|
+
error: error2
|
|
14881
15537
|
});
|
|
14882
|
-
} catch
|
|
15538
|
+
} catch {
|
|
15539
|
+
}
|
|
15540
|
+
try {
|
|
14883
15541
|
writeStderrLine(
|
|
14884
|
-
`qwen serve: extensions ${operation}:
|
|
15542
|
+
`qwen serve: [${boundWorkspace}] extensions ${operation}: ${error2}`
|
|
14885
15543
|
);
|
|
15544
|
+
} catch {
|
|
14886
15545
|
}
|
|
14887
|
-
|
|
14888
|
-
`qwen serve: extensions ${operation}: mutation succeeded but refresh failed: ${message}`
|
|
14889
|
-
);
|
|
15546
|
+
return;
|
|
14890
15547
|
}
|
|
14891
|
-
} catch (err) {
|
|
14892
|
-
const message = redactUrlCredentials(
|
|
14893
|
-
err instanceof Error ? err.message : String(err)
|
|
14894
|
-
);
|
|
14895
15548
|
updateExtensionOperation(operationId, {
|
|
14896
15549
|
status: "failed",
|
|
14897
|
-
|
|
15550
|
+
phase: void 0,
|
|
15551
|
+
error: message.slice(0, 500),
|
|
15552
|
+
...code ? { code } : {}
|
|
14898
15553
|
});
|
|
14899
15554
|
try {
|
|
14900
15555
|
bridge.broadcastExtensionsChanged({
|
|
@@ -14907,33 +15562,24 @@ function registerWorkspaceExtensionRoutes(app, deps) {
|
|
|
14907
15562
|
});
|
|
14908
15563
|
} catch (broadcastErr) {
|
|
14909
15564
|
writeStderrLine(
|
|
14910
|
-
`qwen serve: extensions ${operation}: failed to broadcast failure: ${
|
|
15565
|
+
`qwen serve: [${boundWorkspace}] extensions ${operation}: failed to broadcast failure: ${sanitizeDaemonMessage(
|
|
15566
|
+
broadcastErr instanceof Error ? broadcastErr.message : String(broadcastErr)
|
|
15567
|
+
)}`
|
|
14911
15568
|
);
|
|
14912
15569
|
}
|
|
14913
15570
|
try {
|
|
14914
15571
|
writeStderrLine(
|
|
14915
|
-
`qwen serve: extensions ${operation}: background task failed: ${message}`
|
|
15572
|
+
`qwen serve: [${boundWorkspace}] extensions ${operation}: background task failed: ${message}`
|
|
14916
15573
|
);
|
|
14917
15574
|
} catch {
|
|
14918
15575
|
}
|
|
15576
|
+
} finally {
|
|
15577
|
+
if (deadline) clearTimeout(deadline);
|
|
15578
|
+
reconciliationReservation?.release();
|
|
15579
|
+
releaseOperationSlot();
|
|
14919
15580
|
}
|
|
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
|
-
});
|
|
15581
|
+
})();
|
|
14935
15582
|
}, "runQueuedExtensionMutation");
|
|
14936
|
-
let extensionsStatusCache;
|
|
14937
15583
|
const buildLocalExtensionsStatus = /* @__PURE__ */ __name(async () => {
|
|
14938
15584
|
const now = Date.now();
|
|
14939
15585
|
if (extensionsStatusCache && extensionsStatusCache.expiresAt > now) {
|
|
@@ -14988,52 +15634,295 @@ function registerWorkspaceExtensionRoutes(app, deps) {
|
|
|
14988
15634
|
extensions: entries
|
|
14989
15635
|
};
|
|
14990
15636
|
extensionsStatusCache = {
|
|
14991
|
-
expiresAt: now + 2e3,
|
|
15637
|
+
expiresAt: Date.now() + 2e3,
|
|
14992
15638
|
value: status
|
|
14993
15639
|
};
|
|
14994
15640
|
return status;
|
|
14995
15641
|
}, "buildLocalExtensionsStatus");
|
|
14996
|
-
|
|
14997
|
-
|
|
14998
|
-
|
|
14999
|
-
|
|
15000
|
-
|
|
15001
|
-
|
|
15002
|
-
|
|
15003
|
-
|
|
15004
|
-
|
|
15005
|
-
|
|
15006
|
-
|
|
15007
|
-
|
|
15008
|
-
|
|
15009
|
-
|
|
15010
|
-
|
|
15011
|
-
|
|
15012
|
-
|
|
15013
|
-
|
|
15014
|
-
|
|
15015
|
-
|
|
15016
|
-
|
|
15017
|
-
|
|
15018
|
-
|
|
15019
|
-
|
|
15020
|
-
|
|
15021
|
-
|
|
15022
|
-
|
|
15023
|
-
|
|
15024
|
-
|
|
15642
|
+
return {
|
|
15643
|
+
boundWorkspace,
|
|
15644
|
+
workspace,
|
|
15645
|
+
createExtensionManager,
|
|
15646
|
+
buildLocalExtensionsStatus,
|
|
15647
|
+
refreshExtensionsForAllSessions,
|
|
15648
|
+
getOperation: /* @__PURE__ */ __name((operationId) => extensionOperations.get(operationId), "getOperation"),
|
|
15649
|
+
preparationQueue,
|
|
15650
|
+
acquireOperationSlot,
|
|
15651
|
+
validateExtensionMutationClient,
|
|
15652
|
+
runQueuedExtensionMutation
|
|
15653
|
+
};
|
|
15654
|
+
}
|
|
15655
|
+
__name(createExtensionsController, "createExtensionsController");
|
|
15656
|
+
|
|
15657
|
+
// packages/cli/src/serve/routes/workspace-extensions.ts
|
|
15658
|
+
var EXTENSION_PREPARE_DEADLINE_MS = 10 * 6e4;
|
|
15659
|
+
var EXTENSION_UPDATE_CHECK_DEADLINE_MS = 2 * 6e4;
|
|
15660
|
+
var parseExtensionScope = /* @__PURE__ */ __name((body, res) => {
|
|
15661
|
+
const scope = body["scope"];
|
|
15662
|
+
if (scope !== "user" && scope !== "workspace") {
|
|
15663
|
+
res.status(400).json({ error: '`scope` must be either "user" or "workspace"' });
|
|
15664
|
+
return null;
|
|
15665
|
+
}
|
|
15666
|
+
return scope === "user" ? "User" /* User */ : "Workspace" /* Workspace */;
|
|
15667
|
+
}, "parseExtensionScope");
|
|
15668
|
+
var parseExtensionRegistryUrl = /* @__PURE__ */ __name((value, res) => {
|
|
15669
|
+
let parsed;
|
|
15670
|
+
try {
|
|
15671
|
+
parsed = new URL(value);
|
|
15672
|
+
} catch {
|
|
15673
|
+
res.status(400).json({ error: "`registry` must be a valid URL" });
|
|
15674
|
+
return null;
|
|
15675
|
+
}
|
|
15676
|
+
if (parsed.protocol !== "https:") {
|
|
15677
|
+
res.status(400).json({ error: "`registry` must use https" });
|
|
15678
|
+
return null;
|
|
15679
|
+
}
|
|
15680
|
+
if (parsed.username || parsed.password) {
|
|
15681
|
+
res.status(400).json({ error: "`registry` must not include credentials" });
|
|
15682
|
+
return null;
|
|
15683
|
+
}
|
|
15684
|
+
if (isBlockedAuthProviderHost(parsed.hostname)) {
|
|
15685
|
+
res.status(400).json({ error: "`registry` host is not allowed" });
|
|
15686
|
+
return null;
|
|
15687
|
+
}
|
|
15688
|
+
return parsed.toString().replace(/\/$/, "");
|
|
15689
|
+
}, "parseExtensionRegistryUrl");
|
|
15690
|
+
var parsePotentialSourceUrl = /* @__PURE__ */ __name((source) => {
|
|
15691
|
+
if (/^[a-zA-Z]:[\\/]/.test(source)) return null;
|
|
15692
|
+
try {
|
|
15693
|
+
return new URL(source);
|
|
15694
|
+
} catch {
|
|
15695
|
+
const colonIndex = source.indexOf(":");
|
|
15696
|
+
if (colonIndex >= 0 && source.slice(0, colonIndex).includes("/")) {
|
|
15697
|
+
return null;
|
|
15025
15698
|
}
|
|
15026
|
-
|
|
15027
|
-
|
|
15028
|
-
|
|
15029
|
-
|
|
15030
|
-
|
|
15699
|
+
const sshMatch = /^(?:[^@]+@)?(\[[^\]]+\]|[^:]+):/.exec(source);
|
|
15700
|
+
if (!sshMatch?.[1]) return null;
|
|
15701
|
+
try {
|
|
15702
|
+
return new URL(`ssh://${sshMatch[1]}`);
|
|
15703
|
+
} catch {
|
|
15704
|
+
return null;
|
|
15705
|
+
}
|
|
15706
|
+
}
|
|
15707
|
+
}, "parsePotentialSourceUrl");
|
|
15708
|
+
var validateExtensionSourceHost = /* @__PURE__ */ __name((source, res) => {
|
|
15709
|
+
const parsed = parsePotentialSourceUrl(source);
|
|
15710
|
+
if (!parsed) return true;
|
|
15711
|
+
if (parsed.username || parsed.password) {
|
|
15712
|
+
res.status(400).json({ error: "`source` must not include credentials" });
|
|
15713
|
+
return false;
|
|
15714
|
+
}
|
|
15715
|
+
if (isBlockedAuthProviderHost(parsed.hostname)) {
|
|
15716
|
+
res.status(400).json({ error: "`source` host is not allowed" });
|
|
15717
|
+
return false;
|
|
15718
|
+
}
|
|
15719
|
+
if (parsed.protocol !== "https:") {
|
|
15720
|
+
res.status(400).json({ error: "`source` must use https" });
|
|
15721
|
+
return false;
|
|
15722
|
+
}
|
|
15723
|
+
return true;
|
|
15724
|
+
}, "validateExtensionSourceHost");
|
|
15725
|
+
var validateExtensionSourceMetadata = /* @__PURE__ */ __name((installMetadata) => {
|
|
15726
|
+
if (installMetadata.type !== "git") return true;
|
|
15727
|
+
const parsed = parsePotentialSourceUrl(installMetadata.source);
|
|
15728
|
+
return !!parsed && (installMetadata.networkPolicy === "public" ? parsed.protocol === "https:" : parsed.protocol === "https:" || parsed.protocol === "ssh:") && !isBlockedAuthProviderHost(parsed.hostname);
|
|
15729
|
+
}, "validateExtensionSourceMetadata");
|
|
15730
|
+
var findLoadedExtension = /* @__PURE__ */ __name((extensionManager, extensionName) => {
|
|
15731
|
+
const requested = extensionName.toLowerCase();
|
|
15732
|
+
const extensions = extensionManager.getLoadedExtensions();
|
|
15733
|
+
const byName = extensions.find(
|
|
15734
|
+
(extension) => extension.name.toLowerCase() === requested
|
|
15735
|
+
);
|
|
15736
|
+
if (byName) return byName;
|
|
15737
|
+
if (!extensionName.includes("://") && !extensionName.includes("@")) {
|
|
15738
|
+
return void 0;
|
|
15739
|
+
}
|
|
15740
|
+
return extensions.find(
|
|
15741
|
+
(extension) => extension.installMetadata?.source?.toLowerCase() === requested
|
|
15742
|
+
);
|
|
15743
|
+
}, "findLoadedExtension");
|
|
15744
|
+
function registerWorkspaceExtensionRoutes(app, deps) {
|
|
15745
|
+
const {
|
|
15746
|
+
boundWorkspace,
|
|
15747
|
+
bridge,
|
|
15748
|
+
workspace,
|
|
15749
|
+
mutate,
|
|
15750
|
+
safeBody: safeBody2,
|
|
15751
|
+
sendBridgeError: sendBridgeError2,
|
|
15752
|
+
workspaceRegistry
|
|
15753
|
+
} = deps;
|
|
15754
|
+
const maxExtensionOperationHistory = deps.maxExtensionOperationHistory;
|
|
15755
|
+
const controllerDeps = /* @__PURE__ */ __name((ws, wsBridge, wsService) => ({
|
|
15756
|
+
boundWorkspace: ws,
|
|
15757
|
+
bridge: wsBridge,
|
|
15758
|
+
workspace: wsService,
|
|
15759
|
+
...maxExtensionOperationHistory === void 0 ? {} : { maxExtensionOperationHistory }
|
|
15760
|
+
}), "controllerDeps");
|
|
15761
|
+
const primaryController = createExtensionsController(
|
|
15762
|
+
controllerDeps(boundWorkspace, bridge, workspace)
|
|
15763
|
+
);
|
|
15764
|
+
const runtimeReconciliationQueue = createFifoTaskQueue(1);
|
|
15765
|
+
const reserveRuntimeReconciliation = /* @__PURE__ */ __name(() => {
|
|
15766
|
+
let provideTask;
|
|
15767
|
+
const task = new Promise(
|
|
15768
|
+
(resolve5) => {
|
|
15769
|
+
provideTask = resolve5;
|
|
15770
|
+
}
|
|
15771
|
+
);
|
|
15772
|
+
const queued = runtimeReconciliationQueue.run(async () => {
|
|
15773
|
+
const run = await task;
|
|
15774
|
+
return run ? await run() : void 0;
|
|
15775
|
+
});
|
|
15776
|
+
let used = false;
|
|
15777
|
+
return {
|
|
15778
|
+
run: /* @__PURE__ */ __name(async (run) => {
|
|
15779
|
+
if (used) throw new Error("Runtime reconciliation already released");
|
|
15780
|
+
used = true;
|
|
15781
|
+
provideTask(run);
|
|
15782
|
+
return await queued;
|
|
15783
|
+
}, "run"),
|
|
15784
|
+
release: /* @__PURE__ */ __name(() => {
|
|
15785
|
+
if (used) return;
|
|
15786
|
+
used = true;
|
|
15787
|
+
provideTask(void 0);
|
|
15788
|
+
}, "release")
|
|
15789
|
+
};
|
|
15790
|
+
}, "reserveRuntimeReconciliation");
|
|
15791
|
+
const appliedGenerationByWorkspaceId = /* @__PURE__ */ new Map();
|
|
15792
|
+
const onRuntimeReconciled = /* @__PURE__ */ __name((runtime, generation) => {
|
|
15793
|
+
appliedGenerationByWorkspaceId.set(runtime.workspaceId, generation);
|
|
15794
|
+
}, "onRuntimeReconciled");
|
|
15795
|
+
const globalReconciliationOptions = /* @__PURE__ */ __name(() => workspaceRegistry ? {
|
|
15796
|
+
refreshRuntimes: /* @__PURE__ */ __name(() => workspaceRegistry.list(), "refreshRuntimes"),
|
|
15797
|
+
reserveRuntimeReconciliation,
|
|
15798
|
+
onRuntimeReconciled
|
|
15799
|
+
} : {}, "globalReconciliationOptions");
|
|
15800
|
+
const workspaceReconciliationOptions = /* @__PURE__ */ __name(() => workspaceRegistry ? {
|
|
15801
|
+
refreshRuntimes: [workspaceRegistry.primary],
|
|
15802
|
+
reserveRuntimeReconciliation,
|
|
15803
|
+
onRuntimeReconciled
|
|
15804
|
+
} : {}, "workspaceReconciliationOptions");
|
|
15805
|
+
const mutationClientBridges = /* @__PURE__ */ __name((runtimes) => (typeof runtimes === "function" ? runtimes() : runtimes ?? workspaceRegistry?.list())?.map((runtime) => runtime.bridge) ?? [bridge], "mutationClientBridges");
|
|
15806
|
+
if (workspaceRegistry) {
|
|
15807
|
+
let observedGeneration;
|
|
15808
|
+
let reconciling = false;
|
|
15809
|
+
const reconcileExternalGeneration = /* @__PURE__ */ __name(async () => {
|
|
15810
|
+
if (reconciling) return;
|
|
15811
|
+
reconciling = true;
|
|
15031
15812
|
try {
|
|
15032
|
-
|
|
15033
|
-
|
|
15034
|
-
|
|
15035
|
-
|
|
15036
|
-
))
|
|
15813
|
+
const manager = primaryController.createExtensionManager(
|
|
15814
|
+
boundWorkspace,
|
|
15815
|
+
true
|
|
15816
|
+
);
|
|
15817
|
+
const generation = (await manager.getExtensionStoreSnapshot()).generation;
|
|
15818
|
+
const pendingRuntimes = workspaceRegistry.list().filter(
|
|
15819
|
+
(runtime) => (appliedGenerationByWorkspaceId.get(runtime.workspaceId) ?? 0) !== generation
|
|
15820
|
+
);
|
|
15821
|
+
if (generation === observedGeneration && pendingRuntimes.length === 0)
|
|
15822
|
+
return;
|
|
15823
|
+
const runtimes = pendingRuntimes;
|
|
15824
|
+
if (runtimes.length === 0) return;
|
|
15825
|
+
const results = await runtimeReconciliationQueue.run(
|
|
15826
|
+
async () => await Promise.allSettled(
|
|
15827
|
+
runtimes.map(async (runtime) => {
|
|
15828
|
+
runtime.workspaceService.invalidateWorkspaceSkillsStatus();
|
|
15829
|
+
const result = await runtime.bridge.refreshExtensionsForAllSessions();
|
|
15830
|
+
if (result.failed > 0) {
|
|
15831
|
+
throw new Error(
|
|
15832
|
+
`${result.failed} extension session refresh(es) failed`
|
|
15833
|
+
);
|
|
15834
|
+
}
|
|
15835
|
+
})
|
|
15836
|
+
)
|
|
15837
|
+
);
|
|
15838
|
+
results.forEach((result, index) => {
|
|
15839
|
+
if (result.status === "fulfilled") {
|
|
15840
|
+
const workspaceId = runtimes[index].workspaceId;
|
|
15841
|
+
appliedGenerationByWorkspaceId.set(workspaceId, generation);
|
|
15842
|
+
} else {
|
|
15843
|
+
writeStderrLine(
|
|
15844
|
+
`qwen serve: extension generation reconciliation failed for workspace ${runtimes[index].workspaceId}: ${redactUrlCredentials(
|
|
15845
|
+
result.reason instanceof Error ? result.reason.message : String(result.reason)
|
|
15846
|
+
)}`
|
|
15847
|
+
);
|
|
15848
|
+
}
|
|
15849
|
+
});
|
|
15850
|
+
if (runtimes.length === pendingRuntimes.length && results.every((result) => result.status === "fulfilled")) {
|
|
15851
|
+
observedGeneration = generation;
|
|
15852
|
+
}
|
|
15853
|
+
} catch (error2) {
|
|
15854
|
+
writeStderrLine(
|
|
15855
|
+
`qwen serve: extension generation reconciliation failed: ${redactUrlCredentials(
|
|
15856
|
+
error2 instanceof Error ? error2.message : String(error2)
|
|
15857
|
+
)}`
|
|
15858
|
+
);
|
|
15859
|
+
} finally {
|
|
15860
|
+
reconciling = false;
|
|
15861
|
+
}
|
|
15862
|
+
}, "reconcileExternalGeneration");
|
|
15863
|
+
const generationPoller = setInterval(
|
|
15864
|
+
() => void reconcileExternalGeneration(),
|
|
15865
|
+
3e4
|
|
15866
|
+
);
|
|
15867
|
+
generationPoller.unref();
|
|
15868
|
+
app.locals.stopExtensionGenerationReconciler = () => clearInterval(generationPoller);
|
|
15869
|
+
}
|
|
15870
|
+
const registerFor = /* @__PURE__ */ __name((base, resolve5) => {
|
|
15871
|
+
app.get(base, async (req, res) => {
|
|
15872
|
+
const ctrl = resolve5(req, res, false);
|
|
15873
|
+
if (!ctrl) return;
|
|
15874
|
+
try {
|
|
15875
|
+
res.status(200).json(await ctrl.buildLocalExtensionsStatus());
|
|
15876
|
+
} catch (err) {
|
|
15877
|
+
sendBridgeError2(res, err, { route: `GET ${base}` });
|
|
15878
|
+
}
|
|
15879
|
+
});
|
|
15880
|
+
app.get(`${base}/operations/:operationId`, async (req, res) => {
|
|
15881
|
+
const ctrl = resolve5(req, res, false);
|
|
15882
|
+
if (!ctrl) return;
|
|
15883
|
+
try {
|
|
15884
|
+
const operationId = req.params["operationId"];
|
|
15885
|
+
if (!operationId) {
|
|
15886
|
+
res.status(400).json({ error: "Missing extension operation id" });
|
|
15887
|
+
return;
|
|
15888
|
+
}
|
|
15889
|
+
const operation = ctrl.getOperation(operationId);
|
|
15890
|
+
if (!operation) {
|
|
15891
|
+
res.status(404).json({
|
|
15892
|
+
error: `Extension operation "${operationId}" not found`,
|
|
15893
|
+
code: "extension_operation_not_found"
|
|
15894
|
+
});
|
|
15895
|
+
return;
|
|
15896
|
+
}
|
|
15897
|
+
if (base === "/workspace/extensions" && operation.status === "succeeded_with_warnings") {
|
|
15898
|
+
const warningError = operation.warnings?.find((warning) => warning.code === void 0)?.error ?? operation.warnings?.[0]?.error ?? operation.result?.error;
|
|
15899
|
+
const legacyOperation = {
|
|
15900
|
+
...operation,
|
|
15901
|
+
status: "succeeded_with_refresh_error"
|
|
15902
|
+
};
|
|
15903
|
+
if (operation.result && warningError) {
|
|
15904
|
+
legacyOperation.result = {
|
|
15905
|
+
...operation.result,
|
|
15906
|
+
error: warningError
|
|
15907
|
+
};
|
|
15908
|
+
} else if (warningError) {
|
|
15909
|
+
legacyOperation.error = warningError;
|
|
15910
|
+
}
|
|
15911
|
+
res.status(200).json(legacyOperation);
|
|
15912
|
+
return;
|
|
15913
|
+
}
|
|
15914
|
+
res.status(200).json(operation);
|
|
15915
|
+
} catch (err) {
|
|
15916
|
+
sendBridgeError2(res, err, {
|
|
15917
|
+
route: `GET ${base}/operations/:operationId`
|
|
15918
|
+
});
|
|
15919
|
+
}
|
|
15920
|
+
});
|
|
15921
|
+
app.post(`${base}/install`, mutate({ strict: true }), async (req, res) => {
|
|
15922
|
+
const ctrl = resolve5(req, res, true);
|
|
15923
|
+
if (!ctrl) return;
|
|
15924
|
+
try {
|
|
15925
|
+
if (!ctrl.validateExtensionMutationClient(req, res)) {
|
|
15037
15926
|
return;
|
|
15038
15927
|
}
|
|
15039
15928
|
const body = safeBody2(req);
|
|
@@ -15083,139 +15972,328 @@ function registerWorkspaceExtensionRoutes(app, deps) {
|
|
|
15083
15972
|
if (!validateExtensionSourceHost(sourceValue, res)) {
|
|
15084
15973
|
return;
|
|
15085
15974
|
}
|
|
15086
|
-
runQueuedExtensionMutation(
|
|
15975
|
+
ctrl.runQueuedExtensionMutation(
|
|
15087
15976
|
"install",
|
|
15088
15977
|
{ source: sourceValue },
|
|
15089
15978
|
res,
|
|
15090
|
-
async (extensionManager) => {
|
|
15091
|
-
const
|
|
15092
|
-
|
|
15093
|
-
|
|
15094
|
-
|
|
15095
|
-
)
|
|
15096
|
-
|
|
15097
|
-
|
|
15098
|
-
|
|
15099
|
-
|
|
15100
|
-
|
|
15101
|
-
|
|
15102
|
-
|
|
15979
|
+
async (extensionManager, _signal, context) => {
|
|
15980
|
+
const prepared = await context.prepare(async (signal) => {
|
|
15981
|
+
const installMetadata = await parseInstallSource(sourceValue, {
|
|
15982
|
+
networkPolicy: "public"
|
|
15983
|
+
});
|
|
15984
|
+
if (installMetadata.type !== "git" && installMetadata.type !== "github-release" && installMetadata.type !== "npm") {
|
|
15985
|
+
throw new Error(
|
|
15986
|
+
"Only GitHub, Git, and npm extension installs are supported over the daemon endpoint."
|
|
15987
|
+
);
|
|
15988
|
+
}
|
|
15989
|
+
if (installMetadata.type === "npm" && refValue) {
|
|
15990
|
+
throw new Error("--ref is not applicable for npm extensions.");
|
|
15991
|
+
}
|
|
15992
|
+
if (installMetadata.type !== "npm" && registryValue) {
|
|
15993
|
+
throw new Error(
|
|
15994
|
+
"--registry is only applicable for npm extensions."
|
|
15995
|
+
);
|
|
15996
|
+
}
|
|
15997
|
+
if (!validateExtensionSourceMetadata(installMetadata)) {
|
|
15998
|
+
throw new Error("`source` host is not allowed");
|
|
15999
|
+
}
|
|
16000
|
+
if (installMetadata.type === "npm" && registryUrl) {
|
|
16001
|
+
installMetadata.registryUrl = registryUrl;
|
|
16002
|
+
}
|
|
16003
|
+
return await extensionManager.prepareExtensionInstall({
|
|
16004
|
+
installMetadata: {
|
|
16005
|
+
...installMetadata,
|
|
16006
|
+
ref: refValue,
|
|
16007
|
+
autoUpdate: autoUpdateValue,
|
|
16008
|
+
allowPreRelease: allowPreReleaseValue
|
|
16009
|
+
},
|
|
16010
|
+
initialActivation: { scope: "user" },
|
|
16011
|
+
requestConsent: /* @__PURE__ */ __name(() => Promise.resolve(), "requestConsent"),
|
|
16012
|
+
signal
|
|
16013
|
+
});
|
|
16014
|
+
});
|
|
16015
|
+
try {
|
|
16016
|
+
const committed = await context.commit(
|
|
16017
|
+
async (onCommitted) => await extensionManager.commitPreparedExtension(
|
|
16018
|
+
prepared,
|
|
16019
|
+
onCommitted
|
|
16020
|
+
)
|
|
15103
16021
|
);
|
|
16022
|
+
return {
|
|
16023
|
+
status: "installed",
|
|
16024
|
+
source: sourceValue,
|
|
16025
|
+
name: committed.identity.name,
|
|
16026
|
+
version: committed.version
|
|
16027
|
+
};
|
|
16028
|
+
} finally {
|
|
16029
|
+
await extensionManager.disposePreparedExtension(prepared);
|
|
15104
16030
|
}
|
|
15105
|
-
|
|
15106
|
-
|
|
15107
|
-
|
|
15108
|
-
|
|
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
|
-
};
|
|
16031
|
+
},
|
|
16032
|
+
{
|
|
16033
|
+
deadlineMs: EXTENSION_PREPARE_DEADLINE_MS,
|
|
16034
|
+
...globalReconciliationOptions()
|
|
15126
16035
|
}
|
|
15127
16036
|
);
|
|
15128
16037
|
} catch (err) {
|
|
15129
|
-
sendBridgeError2(res, err, {
|
|
15130
|
-
route: "POST /workspace/extensions/install"
|
|
15131
|
-
});
|
|
16038
|
+
sendBridgeError2(res, err, { route: `POST ${base}/install` });
|
|
15132
16039
|
}
|
|
15133
|
-
}
|
|
15134
|
-
|
|
15135
|
-
|
|
15136
|
-
|
|
15137
|
-
|
|
15138
|
-
|
|
15139
|
-
|
|
15140
|
-
|
|
15141
|
-
|
|
15142
|
-
|
|
15143
|
-
|
|
15144
|
-
|
|
15145
|
-
|
|
15146
|
-
|
|
15147
|
-
|
|
15148
|
-
|
|
15149
|
-
|
|
15150
|
-
|
|
15151
|
-
|
|
15152
|
-
|
|
15153
|
-
|
|
15154
|
-
|
|
15155
|
-
|
|
15156
|
-
|
|
16040
|
+
});
|
|
16041
|
+
app.post(
|
|
16042
|
+
`${base}/check-updates`,
|
|
16043
|
+
mutate({ strict: true }),
|
|
16044
|
+
async (req, res) => {
|
|
16045
|
+
const ctrl = resolve5(req, res, true);
|
|
16046
|
+
if (!ctrl) return;
|
|
16047
|
+
let timer;
|
|
16048
|
+
let releaseOperationSlot;
|
|
16049
|
+
try {
|
|
16050
|
+
if (!ctrl.validateExtensionMutationClient(req, res)) {
|
|
16051
|
+
return;
|
|
16052
|
+
}
|
|
16053
|
+
releaseOperationSlot = ctrl.acquireOperationSlot(res);
|
|
16054
|
+
if (!releaseOperationSlot) return;
|
|
16055
|
+
const extensionManager = ctrl.createExtensionManager();
|
|
16056
|
+
const updateStates = /* @__PURE__ */ Object.create(null);
|
|
16057
|
+
const deadline = new AbortController();
|
|
16058
|
+
timer = setTimeout(() => {
|
|
16059
|
+
const error2 = new Error(
|
|
16060
|
+
"Extension update check exceeded its preparation deadline."
|
|
16061
|
+
);
|
|
16062
|
+
error2.code = "extension_prepare_timeout";
|
|
16063
|
+
deadline.abort(error2);
|
|
16064
|
+
}, EXTENSION_UPDATE_CHECK_DEADLINE_MS);
|
|
16065
|
+
timer.unref();
|
|
16066
|
+
let rejectRefreshOnAbort;
|
|
16067
|
+
try {
|
|
16068
|
+
await Promise.race([
|
|
16069
|
+
extensionManager.refreshCache(),
|
|
16070
|
+
new Promise((_resolve, reject) => {
|
|
16071
|
+
rejectRefreshOnAbort = /* @__PURE__ */ __name(() => reject(deadline.signal.reason), "rejectRefreshOnAbort");
|
|
16072
|
+
deadline.signal.addEventListener(
|
|
16073
|
+
"abort",
|
|
16074
|
+
rejectRefreshOnAbort,
|
|
16075
|
+
{ once: true }
|
|
16076
|
+
);
|
|
16077
|
+
})
|
|
16078
|
+
]);
|
|
16079
|
+
} finally {
|
|
16080
|
+
if (rejectRefreshOnAbort) {
|
|
16081
|
+
deadline.signal.removeEventListener(
|
|
16082
|
+
"abort",
|
|
16083
|
+
rejectRefreshOnAbort
|
|
15157
16084
|
);
|
|
15158
|
-
|
|
15159
|
-
|
|
15160
|
-
|
|
15161
|
-
|
|
15162
|
-
|
|
15163
|
-
|
|
15164
|
-
|
|
15165
|
-
|
|
15166
|
-
|
|
15167
|
-
|
|
15168
|
-
|
|
16085
|
+
}
|
|
16086
|
+
}
|
|
16087
|
+
await extensionManager.checkForAllExtensionUpdates(
|
|
16088
|
+
(name, state) => {
|
|
16089
|
+
updateStates[name] = state;
|
|
16090
|
+
},
|
|
16091
|
+
deadline.signal,
|
|
16092
|
+
async (task) => await ctrl.preparationQueue.run(task, {
|
|
16093
|
+
signal: deadline.signal
|
|
16094
|
+
})
|
|
16095
|
+
);
|
|
16096
|
+
const states = updateStates;
|
|
16097
|
+
res.status(200).json({ states });
|
|
16098
|
+
} catch (err) {
|
|
16099
|
+
sendBridgeError2(res, err, { route: `POST ${base}/check-updates` });
|
|
16100
|
+
} finally {
|
|
16101
|
+
if (timer) clearTimeout(timer);
|
|
16102
|
+
releaseOperationSlot?.();
|
|
15169
16103
|
}
|
|
15170
|
-
sendBridgeError2(res, err, {
|
|
15171
|
-
route: "POST /workspace/extensions/check-updates"
|
|
15172
|
-
});
|
|
15173
16104
|
}
|
|
15174
|
-
|
|
15175
|
-
|
|
15176
|
-
|
|
15177
|
-
|
|
15178
|
-
mutate({ strict: true }),
|
|
15179
|
-
async (req, res) => {
|
|
16105
|
+
);
|
|
16106
|
+
app.post(`${base}/refresh`, mutate({ strict: true }), async (req, res) => {
|
|
16107
|
+
const ctrl = resolve5(req, res, true);
|
|
16108
|
+
if (!ctrl) return;
|
|
15180
16109
|
try {
|
|
15181
|
-
if (!validateExtensionMutationClient(
|
|
15182
|
-
req,
|
|
15183
|
-
res,
|
|
15184
|
-
"POST /workspace/extensions/refresh"
|
|
15185
|
-
)) {
|
|
16110
|
+
if (!ctrl.validateExtensionMutationClient(req, res)) {
|
|
15186
16111
|
return;
|
|
15187
16112
|
}
|
|
15188
|
-
const
|
|
15189
|
-
|
|
15190
|
-
|
|
15191
|
-
|
|
15192
|
-
|
|
15193
|
-
|
|
15194
|
-
|
|
15195
|
-
|
|
15196
|
-
res.status(200).json(result);
|
|
16113
|
+
const releaseOperationSlot = ctrl.acquireOperationSlot(res);
|
|
16114
|
+
if (!releaseOperationSlot) return;
|
|
16115
|
+
try {
|
|
16116
|
+
const result = await ctrl.refreshExtensionsForAllSessions();
|
|
16117
|
+
res.status(200).json(result);
|
|
16118
|
+
} finally {
|
|
16119
|
+
releaseOperationSlot();
|
|
16120
|
+
}
|
|
15197
16121
|
} catch (err) {
|
|
15198
|
-
|
|
15199
|
-
|
|
15200
|
-
|
|
16122
|
+
sendBridgeError2(res, err, { route: `POST ${base}/refresh` });
|
|
16123
|
+
}
|
|
16124
|
+
});
|
|
16125
|
+
app.post(
|
|
16126
|
+
`${base}/:name/enable`,
|
|
16127
|
+
mutate({ strict: true }),
|
|
16128
|
+
async (req, res) => {
|
|
16129
|
+
const ctrl = resolve5(req, res, true);
|
|
16130
|
+
if (!ctrl) return;
|
|
16131
|
+
try {
|
|
16132
|
+
if (!ctrl.validateExtensionMutationClient(req, res, {
|
|
16133
|
+
requireClientId: false
|
|
16134
|
+
})) {
|
|
16135
|
+
return;
|
|
16136
|
+
}
|
|
16137
|
+
const name = req.params["name"];
|
|
16138
|
+
if (!name) {
|
|
16139
|
+
res.status(400).json({ error: "Missing extension name" });
|
|
16140
|
+
return;
|
|
16141
|
+
}
|
|
16142
|
+
const scope = parseExtensionScope(safeBody2(req), res);
|
|
16143
|
+
if (scope === null) return;
|
|
16144
|
+
ctrl.runQueuedExtensionMutation(
|
|
16145
|
+
"enable",
|
|
16146
|
+
{ name },
|
|
16147
|
+
res,
|
|
16148
|
+
async (extensionManager, _signal, context) => {
|
|
16149
|
+
const extension = findLoadedExtension(extensionManager, name);
|
|
16150
|
+
if (!extension) {
|
|
16151
|
+
throw new Error(`Extension "${name}" not found`);
|
|
16152
|
+
}
|
|
16153
|
+
await context.commit(
|
|
16154
|
+
async (onCommitted) => await extensionManager.enableExtension(
|
|
16155
|
+
extension.name,
|
|
16156
|
+
scope,
|
|
16157
|
+
ctrl.boundWorkspace,
|
|
16158
|
+
onCommitted
|
|
16159
|
+
)
|
|
16160
|
+
);
|
|
16161
|
+
return { status: "enabled", name: extension.name };
|
|
16162
|
+
},
|
|
16163
|
+
{
|
|
16164
|
+
...scope === "User" /* User */ ? globalReconciliationOptions() : workspaceReconciliationOptions()
|
|
16165
|
+
}
|
|
16166
|
+
);
|
|
16167
|
+
} catch (err) {
|
|
16168
|
+
sendBridgeError2(res, err, { route: `POST ${base}/:name/enable` });
|
|
15201
16169
|
}
|
|
15202
|
-
sendBridgeError2(res, err, {
|
|
15203
|
-
route: "POST /workspace/extensions/refresh"
|
|
15204
|
-
});
|
|
15205
16170
|
}
|
|
15206
|
-
|
|
15207
|
-
|
|
15208
|
-
|
|
15209
|
-
|
|
15210
|
-
|
|
15211
|
-
|
|
16171
|
+
);
|
|
16172
|
+
app.post(
|
|
16173
|
+
`${base}/:name/disable`,
|
|
16174
|
+
mutate({ strict: true }),
|
|
16175
|
+
async (req, res) => {
|
|
16176
|
+
const ctrl = resolve5(req, res, true);
|
|
16177
|
+
if (!ctrl) return;
|
|
16178
|
+
try {
|
|
16179
|
+
if (!ctrl.validateExtensionMutationClient(req, res, {
|
|
16180
|
+
requireClientId: false
|
|
16181
|
+
})) {
|
|
16182
|
+
return;
|
|
16183
|
+
}
|
|
16184
|
+
const name = req.params["name"];
|
|
16185
|
+
if (!name) {
|
|
16186
|
+
res.status(400).json({ error: "Missing extension name" });
|
|
16187
|
+
return;
|
|
16188
|
+
}
|
|
16189
|
+
const scope = parseExtensionScope(safeBody2(req), res);
|
|
16190
|
+
if (scope === null) return;
|
|
16191
|
+
ctrl.runQueuedExtensionMutation(
|
|
16192
|
+
"disable",
|
|
16193
|
+
{ name },
|
|
16194
|
+
res,
|
|
16195
|
+
async (extensionManager, _signal, context) => {
|
|
16196
|
+
const extension = findLoadedExtension(extensionManager, name);
|
|
16197
|
+
if (!extension) {
|
|
16198
|
+
throw new Error(`Extension "${name}" not found`);
|
|
16199
|
+
}
|
|
16200
|
+
await context.commit(
|
|
16201
|
+
async (onCommitted) => await extensionManager.disableExtension(
|
|
16202
|
+
extension.name,
|
|
16203
|
+
scope,
|
|
16204
|
+
ctrl.boundWorkspace,
|
|
16205
|
+
onCommitted
|
|
16206
|
+
)
|
|
16207
|
+
);
|
|
16208
|
+
return { status: "disabled", name: extension.name };
|
|
16209
|
+
},
|
|
16210
|
+
{
|
|
16211
|
+
...scope === "User" /* User */ ? globalReconciliationOptions() : workspaceReconciliationOptions()
|
|
16212
|
+
}
|
|
16213
|
+
);
|
|
16214
|
+
} catch (err) {
|
|
16215
|
+
sendBridgeError2(res, err, { route: `POST ${base}/:name/disable` });
|
|
16216
|
+
}
|
|
16217
|
+
}
|
|
16218
|
+
);
|
|
16219
|
+
app.post(
|
|
16220
|
+
`${base}/:name/update`,
|
|
16221
|
+
mutate({ strict: true }),
|
|
16222
|
+
async (req, res) => {
|
|
16223
|
+
const ctrl = resolve5(req, res, true);
|
|
16224
|
+
if (!ctrl) return;
|
|
16225
|
+
try {
|
|
16226
|
+
if (!ctrl.validateExtensionMutationClient(req, res)) {
|
|
16227
|
+
return;
|
|
16228
|
+
}
|
|
16229
|
+
const name = req.params["name"];
|
|
16230
|
+
if (!name) {
|
|
16231
|
+
res.status(400).json({ error: "Missing extension name" });
|
|
16232
|
+
return;
|
|
16233
|
+
}
|
|
16234
|
+
ctrl.runQueuedExtensionMutation(
|
|
16235
|
+
"update",
|
|
16236
|
+
{ name },
|
|
16237
|
+
res,
|
|
16238
|
+
async (extensionManager, _signal, context) => {
|
|
16239
|
+
const extension = findLoadedExtension(extensionManager, name);
|
|
16240
|
+
if (!extension) {
|
|
16241
|
+
throw new Error(`Extension "${name}" not found`);
|
|
16242
|
+
}
|
|
16243
|
+
let preparedResult;
|
|
16244
|
+
try {
|
|
16245
|
+
preparedResult = await context.prepare(
|
|
16246
|
+
async (signal) => await extensionManager.prepareExtensionUpdate({
|
|
16247
|
+
extension,
|
|
16248
|
+
signal
|
|
16249
|
+
})
|
|
16250
|
+
);
|
|
16251
|
+
} catch (error2) {
|
|
16252
|
+
const wrapped = new Error(
|
|
16253
|
+
`Update check failed for extension "${extension.name}": ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
16254
|
+
{ cause: error2 }
|
|
16255
|
+
);
|
|
16256
|
+
if (error2 && typeof error2 === "object" && typeof error2.code === "string") {
|
|
16257
|
+
wrapped.code = error2.code;
|
|
16258
|
+
}
|
|
16259
|
+
throw wrapped;
|
|
16260
|
+
}
|
|
16261
|
+
if (preparedResult.upToDate) {
|
|
16262
|
+
throw new Error(`Extension "${extension.name}" has no update`);
|
|
16263
|
+
}
|
|
16264
|
+
try {
|
|
16265
|
+
const committed = await context.commit(
|
|
16266
|
+
async (onCommitted) => await extensionManager.commitPreparedExtension(
|
|
16267
|
+
preparedResult.prepared,
|
|
16268
|
+
onCommitted
|
|
16269
|
+
)
|
|
16270
|
+
);
|
|
16271
|
+
return {
|
|
16272
|
+
status: "updated",
|
|
16273
|
+
name: extension.name,
|
|
16274
|
+
version: committed.version
|
|
16275
|
+
};
|
|
16276
|
+
} finally {
|
|
16277
|
+
await extensionManager.disposePreparedExtension(
|
|
16278
|
+
preparedResult.prepared
|
|
16279
|
+
);
|
|
16280
|
+
}
|
|
16281
|
+
},
|
|
16282
|
+
{
|
|
16283
|
+
deadlineMs: EXTENSION_PREPARE_DEADLINE_MS,
|
|
16284
|
+
...globalReconciliationOptions()
|
|
16285
|
+
}
|
|
16286
|
+
);
|
|
16287
|
+
} catch (err) {
|
|
16288
|
+
sendBridgeError2(res, err, { route: `POST ${base}/:name/update` });
|
|
16289
|
+
}
|
|
16290
|
+
}
|
|
16291
|
+
);
|
|
16292
|
+
app.delete(`${base}/:name`, mutate({ strict: true }), async (req, res) => {
|
|
16293
|
+
const ctrl = resolve5(req, res, true);
|
|
16294
|
+
if (!ctrl) return;
|
|
15212
16295
|
try {
|
|
15213
|
-
if (!validateExtensionMutationClient(
|
|
15214
|
-
req,
|
|
15215
|
-
res,
|
|
15216
|
-
"POST /workspace/extensions/:name/enable",
|
|
15217
|
-
{ requireClientId: false }
|
|
15218
|
-
)) {
|
|
16296
|
+
if (!ctrl.validateExtensionMutationClient(req, res)) {
|
|
15219
16297
|
return;
|
|
15220
16298
|
}
|
|
15221
16299
|
const name = req.params["name"];
|
|
@@ -15223,184 +16301,604 @@ function registerWorkspaceExtensionRoutes(app, deps) {
|
|
|
15223
16301
|
res.status(400).json({ error: "Missing extension name" });
|
|
15224
16302
|
return;
|
|
15225
16303
|
}
|
|
15226
|
-
|
|
15227
|
-
|
|
15228
|
-
runQueuedExtensionMutation(
|
|
15229
|
-
"enable",
|
|
16304
|
+
ctrl.runQueuedExtensionMutation(
|
|
16305
|
+
"uninstall",
|
|
15230
16306
|
{ name },
|
|
15231
16307
|
res,
|
|
15232
|
-
async (extensionManager) => {
|
|
16308
|
+
async (extensionManager, _signal, context) => {
|
|
15233
16309
|
const extension = findLoadedExtension(extensionManager, name);
|
|
15234
16310
|
if (!extension) {
|
|
15235
16311
|
throw new Error(`Extension "${name}" not found`);
|
|
15236
16312
|
}
|
|
15237
|
-
await
|
|
15238
|
-
|
|
15239
|
-
|
|
15240
|
-
|
|
16313
|
+
await context.commit(
|
|
16314
|
+
async (onCommitted) => await extensionManager.uninstallExtension(
|
|
16315
|
+
extension.name,
|
|
16316
|
+
false,
|
|
16317
|
+
ctrl.boundWorkspace,
|
|
16318
|
+
onCommitted
|
|
16319
|
+
)
|
|
15241
16320
|
);
|
|
15242
|
-
return { status: "
|
|
16321
|
+
return { status: "uninstalled", name: extension.name };
|
|
16322
|
+
},
|
|
16323
|
+
{
|
|
16324
|
+
...globalReconciliationOptions()
|
|
15243
16325
|
}
|
|
15244
16326
|
);
|
|
15245
16327
|
} catch (err) {
|
|
15246
|
-
sendBridgeError2(res, err, {
|
|
15247
|
-
|
|
16328
|
+
sendBridgeError2(res, err, { route: `DELETE ${base}/:name` });
|
|
16329
|
+
}
|
|
16330
|
+
});
|
|
16331
|
+
}, "registerFor");
|
|
16332
|
+
registerFor("/workspace/extensions", () => primaryController);
|
|
16333
|
+
const extensionById = /* @__PURE__ */ __name((manager, extensionId) => manager.getLoadedExtensions().find((extension) => extension.id === extensionId), "extensionById");
|
|
16334
|
+
const parseExtensionId = /* @__PURE__ */ __name((req, res) => {
|
|
16335
|
+
const extensionId = req.params["extensionId"];
|
|
16336
|
+
if (!extensionId || !/^[a-f0-9]{64}$/.test(extensionId)) {
|
|
16337
|
+
res.status(400).json({
|
|
16338
|
+
error: "Invalid extension id",
|
|
16339
|
+
code: "invalid_extension_id"
|
|
16340
|
+
});
|
|
16341
|
+
return null;
|
|
16342
|
+
}
|
|
16343
|
+
return extensionId;
|
|
16344
|
+
}, "parseExtensionId");
|
|
16345
|
+
const parseActivationState = /* @__PURE__ */ __name((req, res) => {
|
|
16346
|
+
const state = safeBody2(req)["state"];
|
|
16347
|
+
if (state !== "enabled" && state !== "disabled") {
|
|
16348
|
+
res.status(400).json({
|
|
16349
|
+
error: '`state` must be either "enabled" or "disabled"',
|
|
16350
|
+
code: "invalid_extension_activation"
|
|
16351
|
+
});
|
|
16352
|
+
return null;
|
|
16353
|
+
}
|
|
16354
|
+
return state;
|
|
16355
|
+
}, "parseActivationState");
|
|
16356
|
+
const sendOperation = /* @__PURE__ */ __name((req, res, route, manager, operation, failureContext, run, options = {}) => {
|
|
16357
|
+
if (!primaryController.validateExtensionMutationClient(req, res, {
|
|
16358
|
+
requireClientId: false,
|
|
16359
|
+
bridges: mutationClientBridges(options.refreshRuntimes)
|
|
16360
|
+
})) {
|
|
16361
|
+
return;
|
|
16362
|
+
}
|
|
16363
|
+
primaryController.runQueuedExtensionMutation(
|
|
16364
|
+
operation,
|
|
16365
|
+
failureContext,
|
|
16366
|
+
res,
|
|
16367
|
+
run,
|
|
16368
|
+
{
|
|
16369
|
+
manager,
|
|
16370
|
+
operationBasePath: "/extensions/operations",
|
|
16371
|
+
onRuntimeReconciled,
|
|
16372
|
+
reserveRuntimeReconciliation,
|
|
16373
|
+
...options
|
|
16374
|
+
}
|
|
16375
|
+
);
|
|
16376
|
+
}, "sendOperation");
|
|
16377
|
+
app.get("/extensions", async (_req, res) => {
|
|
16378
|
+
try {
|
|
16379
|
+
const manager = primaryController.createExtensionManager(
|
|
16380
|
+
boundWorkspace,
|
|
16381
|
+
true
|
|
16382
|
+
);
|
|
16383
|
+
const snapshot = await manager.refreshCacheWithSnapshot();
|
|
16384
|
+
res.status(200).json({
|
|
16385
|
+
v: 1,
|
|
16386
|
+
generation: snapshot.generation,
|
|
16387
|
+
extensions: manager.getLoadedExtensions().map((extension) => {
|
|
16388
|
+
const policy = snapshot.extensions[extension.id];
|
|
16389
|
+
return {
|
|
16390
|
+
id: extension.id,
|
|
16391
|
+
name: extension.name,
|
|
16392
|
+
version: extension.version,
|
|
16393
|
+
...extension.installMetadata?.type ? { installType: extension.installMetadata.type } : {},
|
|
16394
|
+
defaultActivation: policy?.defaultActivation ?? "enabled",
|
|
16395
|
+
workspaceOverrideCount: Object.values(
|
|
16396
|
+
policy?.workspaceOverrides ?? {}
|
|
16397
|
+
).filter((activation) => activation !== "inherit").length
|
|
16398
|
+
};
|
|
16399
|
+
})
|
|
16400
|
+
});
|
|
16401
|
+
} catch (error2) {
|
|
16402
|
+
sendBridgeError2(res, error2, { route: "GET /extensions" });
|
|
16403
|
+
}
|
|
16404
|
+
});
|
|
16405
|
+
app.get("/extensions/operations/:operationId", (req, res) => {
|
|
16406
|
+
const operationId = req.params["operationId"];
|
|
16407
|
+
if (!operationId) {
|
|
16408
|
+
res.status(400).json({ error: "Missing extension operation id" });
|
|
16409
|
+
return;
|
|
16410
|
+
}
|
|
16411
|
+
const operation = primaryController.getOperation(operationId);
|
|
16412
|
+
if (!operation) {
|
|
16413
|
+
res.status(404).json({
|
|
16414
|
+
error: `Extension operation "${operationId}" not found`,
|
|
16415
|
+
code: "extension_operation_not_found"
|
|
16416
|
+
});
|
|
16417
|
+
return;
|
|
16418
|
+
}
|
|
16419
|
+
res.status(200).json(operation);
|
|
16420
|
+
});
|
|
16421
|
+
app.put(
|
|
16422
|
+
"/extensions/:extensionId/activation",
|
|
16423
|
+
mutate({ strict: true }),
|
|
16424
|
+
async (req, res) => {
|
|
16425
|
+
const extensionId = parseExtensionId(req, res);
|
|
16426
|
+
if (!extensionId) return;
|
|
16427
|
+
const state = parseActivationState(req, res);
|
|
16428
|
+
if (!state) return;
|
|
16429
|
+
const manager = primaryController.createExtensionManager(
|
|
16430
|
+
boundWorkspace,
|
|
16431
|
+
true
|
|
16432
|
+
);
|
|
16433
|
+
sendOperation(
|
|
16434
|
+
req,
|
|
16435
|
+
res,
|
|
16436
|
+
"PUT /extensions/:extensionId/activation",
|
|
16437
|
+
manager,
|
|
16438
|
+
"activation",
|
|
16439
|
+
{ name: extensionId },
|
|
16440
|
+
async (extensionManager, _signal, context) => {
|
|
16441
|
+
const extension = extensionById(extensionManager, extensionId);
|
|
16442
|
+
if (!extension)
|
|
16443
|
+
throw new Error(`Extension "${extensionId}" not found`);
|
|
16444
|
+
await context.commit(
|
|
16445
|
+
async (onCommitted) => await extensionManager.setExtensionDefaultActivation(
|
|
16446
|
+
extensionId,
|
|
16447
|
+
state,
|
|
16448
|
+
onCommitted
|
|
16449
|
+
)
|
|
16450
|
+
);
|
|
16451
|
+
return {
|
|
16452
|
+
status: state === "enabled" ? "enabled" : "disabled",
|
|
16453
|
+
name: extension.name
|
|
16454
|
+
};
|
|
16455
|
+
},
|
|
16456
|
+
{
|
|
16457
|
+
...workspaceRegistry ? { refreshRuntimes: /* @__PURE__ */ __name(() => workspaceRegistry.list(), "refreshRuntimes") } : {}
|
|
16458
|
+
}
|
|
16459
|
+
);
|
|
16460
|
+
}
|
|
16461
|
+
);
|
|
16462
|
+
app.post("/extensions/install", mutate({ strict: true }), (req, res) => {
|
|
16463
|
+
const body = safeBody2(req);
|
|
16464
|
+
const source = body["source"];
|
|
16465
|
+
const activation = body["activation"];
|
|
16466
|
+
const ref = body["ref"];
|
|
16467
|
+
const autoUpdate = body["autoUpdate"];
|
|
16468
|
+
const allowPreRelease = body["allowPreRelease"];
|
|
16469
|
+
const registry = body["registry"];
|
|
16470
|
+
if (typeof source !== "string" || !source) {
|
|
16471
|
+
res.status(400).json({ error: "Missing or invalid source" });
|
|
16472
|
+
return;
|
|
16473
|
+
}
|
|
16474
|
+
if (ref !== void 0 && (typeof ref !== "string" || !ref)) {
|
|
16475
|
+
res.status(400).json({ error: "`ref` must be a non-empty string" });
|
|
16476
|
+
return;
|
|
16477
|
+
}
|
|
16478
|
+
if (typeof ref === "string" && ref.startsWith("-")) {
|
|
16479
|
+
res.status(400).json({ error: '`ref` must not start with "-"' });
|
|
16480
|
+
return;
|
|
16481
|
+
}
|
|
16482
|
+
if (autoUpdate !== void 0 && typeof autoUpdate !== "boolean") {
|
|
16483
|
+
res.status(400).json({ error: "`autoUpdate` must be a boolean" });
|
|
16484
|
+
return;
|
|
16485
|
+
}
|
|
16486
|
+
if (allowPreRelease !== void 0 && typeof allowPreRelease !== "boolean") {
|
|
16487
|
+
res.status(400).json({ error: "`allowPreRelease` must be a boolean" });
|
|
16488
|
+
return;
|
|
16489
|
+
}
|
|
16490
|
+
if (registry !== void 0 && typeof registry !== "string") {
|
|
16491
|
+
res.status(400).json({ error: "`registry` must be a string" });
|
|
16492
|
+
return;
|
|
16493
|
+
}
|
|
16494
|
+
const registryUrl = typeof registry === "string" ? parseExtensionRegistryUrl(registry, res) : void 0;
|
|
16495
|
+
if (registryUrl === null) return;
|
|
16496
|
+
if (body["consent"] !== true) {
|
|
16497
|
+
res.status(400).json({
|
|
16498
|
+
error: "Extension installation requires explicit consent"
|
|
16499
|
+
});
|
|
16500
|
+
return;
|
|
16501
|
+
}
|
|
16502
|
+
if (!validateExtensionSourceHost(source, res)) return;
|
|
16503
|
+
if (!activation || typeof activation !== "object") {
|
|
16504
|
+
res.status(400).json({ error: "Missing initial activation" });
|
|
16505
|
+
return;
|
|
16506
|
+
}
|
|
16507
|
+
const activationRecord = activation;
|
|
16508
|
+
let initialActivation;
|
|
16509
|
+
if (activationRecord["scope"] === "user") {
|
|
16510
|
+
initialActivation = { scope: "user" };
|
|
16511
|
+
} else if (activationRecord["scope"] === "workspace" && typeof activationRecord["workspaceId"] === "string" && workspaceRegistry) {
|
|
16512
|
+
const runtime = workspaceRegistry.getByWorkspaceId(
|
|
16513
|
+
activationRecord["workspaceId"]
|
|
16514
|
+
);
|
|
16515
|
+
if (!runtime) {
|
|
16516
|
+
res.status(400).json({
|
|
16517
|
+
error: "Unknown activation workspace",
|
|
16518
|
+
code: "workspace_mismatch"
|
|
16519
|
+
});
|
|
16520
|
+
return;
|
|
16521
|
+
}
|
|
16522
|
+
if (!requireTrustedWorkspaceRuntime(runtime, res)) return;
|
|
16523
|
+
initialActivation = {
|
|
16524
|
+
scope: "workspace",
|
|
16525
|
+
workspacePath: runtime.workspaceCwd
|
|
16526
|
+
};
|
|
16527
|
+
} else {
|
|
16528
|
+
res.status(400).json({ error: "Invalid initial activation" });
|
|
16529
|
+
return;
|
|
16530
|
+
}
|
|
16531
|
+
const manager = primaryController.createExtensionManager(
|
|
16532
|
+
boundWorkspace,
|
|
16533
|
+
true
|
|
16534
|
+
);
|
|
16535
|
+
sendOperation(
|
|
16536
|
+
req,
|
|
16537
|
+
res,
|
|
16538
|
+
"POST /extensions/install",
|
|
16539
|
+
manager,
|
|
16540
|
+
"install",
|
|
16541
|
+
{ source },
|
|
16542
|
+
async (extensionManager, _signal, context) => {
|
|
16543
|
+
const prepared = await context.prepare(async (signal) => {
|
|
16544
|
+
const metadata = await parseInstallSource(source, {
|
|
16545
|
+
networkPolicy: "public"
|
|
16546
|
+
});
|
|
16547
|
+
if (metadata.type !== "git" && metadata.type !== "github-release" && metadata.type !== "npm") {
|
|
16548
|
+
throw new Error(
|
|
16549
|
+
"Only GitHub, Git, and npm extension installs are supported over the daemon endpoint."
|
|
16550
|
+
);
|
|
16551
|
+
}
|
|
16552
|
+
if (!validateExtensionSourceMetadata(metadata)) {
|
|
16553
|
+
throw new Error("`source` host is not allowed");
|
|
16554
|
+
}
|
|
16555
|
+
if (metadata.type === "npm" && ref !== void 0) {
|
|
16556
|
+
throw new Error("--ref is not applicable for npm extensions.");
|
|
16557
|
+
}
|
|
16558
|
+
if (metadata.type !== "npm" && registryUrl !== void 0) {
|
|
16559
|
+
throw new Error(
|
|
16560
|
+
"--registry is only applicable for npm extensions."
|
|
16561
|
+
);
|
|
16562
|
+
}
|
|
16563
|
+
if (metadata.type === "npm" && registryUrl) {
|
|
16564
|
+
metadata.registryUrl = registryUrl;
|
|
16565
|
+
}
|
|
16566
|
+
return await extensionManager.prepareExtensionInstall({
|
|
16567
|
+
installMetadata: {
|
|
16568
|
+
...metadata,
|
|
16569
|
+
...typeof ref === "string" ? { ref } : {},
|
|
16570
|
+
...typeof autoUpdate === "boolean" ? { autoUpdate } : {},
|
|
16571
|
+
...typeof allowPreRelease === "boolean" ? { allowPreRelease } : {}
|
|
16572
|
+
},
|
|
16573
|
+
requestConsent: /* @__PURE__ */ __name(() => Promise.resolve(), "requestConsent"),
|
|
16574
|
+
cwd: boundWorkspace,
|
|
16575
|
+
initialActivation,
|
|
16576
|
+
signal
|
|
16577
|
+
});
|
|
15248
16578
|
});
|
|
16579
|
+
try {
|
|
16580
|
+
const committed = await context.commit(
|
|
16581
|
+
async (onCommitted) => await extensionManager.commitPreparedExtension(
|
|
16582
|
+
prepared,
|
|
16583
|
+
onCommitted
|
|
16584
|
+
)
|
|
16585
|
+
);
|
|
16586
|
+
return {
|
|
16587
|
+
status: "installed",
|
|
16588
|
+
source,
|
|
16589
|
+
name: committed.identity.name,
|
|
16590
|
+
version: committed.version
|
|
16591
|
+
};
|
|
16592
|
+
} finally {
|
|
16593
|
+
await extensionManager.disposePreparedExtension(prepared);
|
|
16594
|
+
}
|
|
16595
|
+
},
|
|
16596
|
+
{
|
|
16597
|
+
deadlineMs: EXTENSION_PREPARE_DEADLINE_MS,
|
|
16598
|
+
...workspaceRegistry ? { refreshRuntimes: /* @__PURE__ */ __name(() => workspaceRegistry.list(), "refreshRuntimes") } : {}
|
|
15249
16599
|
}
|
|
16600
|
+
);
|
|
16601
|
+
});
|
|
16602
|
+
app.post(
|
|
16603
|
+
"/extensions/check-updates",
|
|
16604
|
+
mutate({ strict: true }),
|
|
16605
|
+
(req, res) => {
|
|
16606
|
+
const manager = primaryController.createExtensionManager(
|
|
16607
|
+
boundWorkspace,
|
|
16608
|
+
true
|
|
16609
|
+
);
|
|
16610
|
+
sendOperation(
|
|
16611
|
+
req,
|
|
16612
|
+
res,
|
|
16613
|
+
"POST /extensions/check-updates",
|
|
16614
|
+
manager,
|
|
16615
|
+
"check-updates",
|
|
16616
|
+
{},
|
|
16617
|
+
async (extensionManager, signal, context) => {
|
|
16618
|
+
const states = /* @__PURE__ */ Object.create(null);
|
|
16619
|
+
await extensionManager.checkForAllExtensionUpdates(
|
|
16620
|
+
(name, state) => {
|
|
16621
|
+
states[name] = state;
|
|
16622
|
+
},
|
|
16623
|
+
signal,
|
|
16624
|
+
async (task) => await context.prepare(async () => await task())
|
|
16625
|
+
);
|
|
16626
|
+
return { status: "checked", states };
|
|
16627
|
+
},
|
|
16628
|
+
{
|
|
16629
|
+
skipRefresh: true,
|
|
16630
|
+
deadlineMs: EXTENSION_UPDATE_CHECK_DEADLINE_MS
|
|
16631
|
+
}
|
|
16632
|
+
);
|
|
15250
16633
|
}
|
|
15251
16634
|
);
|
|
15252
16635
|
app.post(
|
|
15253
|
-
"/
|
|
16636
|
+
"/extensions/:extensionId/update",
|
|
16637
|
+
mutate({ strict: true }),
|
|
16638
|
+
(req, res) => {
|
|
16639
|
+
const extensionId = parseExtensionId(req, res);
|
|
16640
|
+
if (!extensionId) return;
|
|
16641
|
+
const manager = primaryController.createExtensionManager(
|
|
16642
|
+
boundWorkspace,
|
|
16643
|
+
true
|
|
16644
|
+
);
|
|
16645
|
+
sendOperation(
|
|
16646
|
+
req,
|
|
16647
|
+
res,
|
|
16648
|
+
"POST /extensions/:extensionId/update",
|
|
16649
|
+
manager,
|
|
16650
|
+
"update",
|
|
16651
|
+
{ name: extensionId },
|
|
16652
|
+
async (extensionManager, _signal, context) => {
|
|
16653
|
+
const extension = extensionById(extensionManager, extensionId);
|
|
16654
|
+
if (!extension)
|
|
16655
|
+
throw new Error(`Extension "${extensionId}" not found`);
|
|
16656
|
+
if (extension.installMetadata?.type !== "git" && extension.installMetadata?.type !== "archive-url" && extension.installMetadata?.type !== "github-release" && extension.installMetadata?.type !== "npm") {
|
|
16657
|
+
throw new Error(
|
|
16658
|
+
`Extension "${extension.name}" is not remotely updatable.`
|
|
16659
|
+
);
|
|
16660
|
+
}
|
|
16661
|
+
const preparedResult = await context.prepare(
|
|
16662
|
+
async (signal) => await extensionManager.prepareExtensionUpdate({
|
|
16663
|
+
extension,
|
|
16664
|
+
signal
|
|
16665
|
+
})
|
|
16666
|
+
);
|
|
16667
|
+
if (preparedResult.upToDate) {
|
|
16668
|
+
return {
|
|
16669
|
+
status: "checked",
|
|
16670
|
+
name: extension.name,
|
|
16671
|
+
updated: false,
|
|
16672
|
+
reason: "up_to_date"
|
|
16673
|
+
};
|
|
16674
|
+
}
|
|
16675
|
+
try {
|
|
16676
|
+
const committed = await context.commit(
|
|
16677
|
+
async (onCommitted) => await extensionManager.commitPreparedExtension(
|
|
16678
|
+
preparedResult.prepared,
|
|
16679
|
+
onCommitted
|
|
16680
|
+
)
|
|
16681
|
+
);
|
|
16682
|
+
return {
|
|
16683
|
+
status: "updated",
|
|
16684
|
+
name: extension.name,
|
|
16685
|
+
updated: true,
|
|
16686
|
+
version: committed.version
|
|
16687
|
+
};
|
|
16688
|
+
} finally {
|
|
16689
|
+
await extensionManager.disposePreparedExtension(
|
|
16690
|
+
preparedResult.prepared
|
|
16691
|
+
);
|
|
16692
|
+
}
|
|
16693
|
+
},
|
|
16694
|
+
{
|
|
16695
|
+
deadlineMs: EXTENSION_PREPARE_DEADLINE_MS,
|
|
16696
|
+
...workspaceRegistry ? { refreshRuntimes: /* @__PURE__ */ __name(() => workspaceRegistry.list(), "refreshRuntimes") } : {}
|
|
16697
|
+
}
|
|
16698
|
+
);
|
|
16699
|
+
}
|
|
16700
|
+
);
|
|
16701
|
+
app.delete(
|
|
16702
|
+
"/extensions/:extensionId",
|
|
15254
16703
|
mutate({ strict: true }),
|
|
15255
16704
|
async (req, res) => {
|
|
16705
|
+
const extensionId = parseExtensionId(req, res);
|
|
16706
|
+
if (!extensionId) return;
|
|
16707
|
+
const route = "DELETE /extensions/:extensionId";
|
|
16708
|
+
if (!primaryController.validateExtensionMutationClient(req, res, {
|
|
16709
|
+
requireClientId: false,
|
|
16710
|
+
bridges: mutationClientBridges()
|
|
16711
|
+
})) {
|
|
16712
|
+
return;
|
|
16713
|
+
}
|
|
15256
16714
|
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" });
|
|
16715
|
+
const manager = primaryController.createExtensionManager(
|
|
16716
|
+
boundWorkspace,
|
|
16717
|
+
true
|
|
16718
|
+
);
|
|
16719
|
+
const snapshot = await manager.getExtensionStoreSnapshot();
|
|
16720
|
+
const policy = snapshot.extensions[extensionId];
|
|
16721
|
+
if (!policy) {
|
|
16722
|
+
res.status(204).end();
|
|
15268
16723
|
return;
|
|
15269
16724
|
}
|
|
15270
|
-
|
|
15271
|
-
|
|
15272
|
-
runQueuedExtensionMutation(
|
|
15273
|
-
"disable",
|
|
15274
|
-
{ name },
|
|
16725
|
+
sendOperation(
|
|
16726
|
+
req,
|
|
15275
16727
|
res,
|
|
15276
|
-
|
|
15277
|
-
|
|
15278
|
-
|
|
15279
|
-
|
|
15280
|
-
|
|
15281
|
-
await
|
|
15282
|
-
|
|
15283
|
-
|
|
15284
|
-
|
|
16728
|
+
route,
|
|
16729
|
+
manager,
|
|
16730
|
+
"uninstall",
|
|
16731
|
+
{ name: policy.name },
|
|
16732
|
+
async (extensionManager, _signal, context) => {
|
|
16733
|
+
await context.commit(
|
|
16734
|
+
async (onCommitted) => await extensionManager.uninstallExtensionById(
|
|
16735
|
+
extensionId,
|
|
16736
|
+
false,
|
|
16737
|
+
void 0,
|
|
16738
|
+
onCommitted
|
|
16739
|
+
)
|
|
15285
16740
|
);
|
|
15286
|
-
return { status: "
|
|
16741
|
+
return { status: "uninstalled", name: policy.name };
|
|
16742
|
+
},
|
|
16743
|
+
{
|
|
16744
|
+
...workspaceRegistry ? { refreshRuntimes: /* @__PURE__ */ __name(() => workspaceRegistry.list(), "refreshRuntimes") } : {}
|
|
15287
16745
|
}
|
|
15288
16746
|
);
|
|
15289
|
-
} catch (
|
|
15290
|
-
sendBridgeError2(res,
|
|
15291
|
-
route: "POST /workspace/extensions/:name/disable"
|
|
15292
|
-
});
|
|
16747
|
+
} catch (error2) {
|
|
16748
|
+
sendBridgeError2(res, error2, { route });
|
|
15293
16749
|
}
|
|
15294
16750
|
}
|
|
15295
16751
|
);
|
|
15296
|
-
|
|
15297
|
-
|
|
15298
|
-
|
|
15299
|
-
|
|
16752
|
+
if (workspaceRegistry) {
|
|
16753
|
+
const registry = workspaceRegistry;
|
|
16754
|
+
app.get("/workspaces/:workspace/extensions", async (req, res) => {
|
|
16755
|
+
const runtime = resolveWorkspaceRuntimeFromParam(registry, req, res);
|
|
16756
|
+
if (!runtime) return;
|
|
15300
16757
|
try {
|
|
15301
|
-
|
|
15302
|
-
|
|
15303
|
-
|
|
15304
|
-
|
|
15305
|
-
)
|
|
15306
|
-
|
|
15307
|
-
|
|
15308
|
-
|
|
15309
|
-
|
|
15310
|
-
|
|
15311
|
-
|
|
15312
|
-
|
|
15313
|
-
|
|
15314
|
-
|
|
15315
|
-
|
|
16758
|
+
const manager = primaryController.createExtensionManager(
|
|
16759
|
+
runtime.workspaceCwd,
|
|
16760
|
+
runtime.trusted
|
|
16761
|
+
);
|
|
16762
|
+
const snapshot = await manager.refreshCacheWithSnapshot();
|
|
16763
|
+
const extensions = manager.getLoadedExtensions().map((extension) => {
|
|
16764
|
+
const activation = manager.getExtensionActivationFromSnapshot(
|
|
16765
|
+
extension.id,
|
|
16766
|
+
snapshot,
|
|
16767
|
+
runtime.workspaceCwd
|
|
16768
|
+
);
|
|
16769
|
+
return {
|
|
16770
|
+
extensionId: extension.id,
|
|
16771
|
+
name: extension.name,
|
|
16772
|
+
version: extension.version,
|
|
16773
|
+
defaultActivation: activation.default,
|
|
16774
|
+
workspaceActivation: activation.workspace === "inherit" ? null : activation.workspace,
|
|
16775
|
+
effectiveActivation: activation.effective,
|
|
16776
|
+
activationSource: activation.source
|
|
16777
|
+
};
|
|
16778
|
+
});
|
|
16779
|
+
res.status(200).json({
|
|
16780
|
+
v: 1,
|
|
16781
|
+
workspaceId: runtime.workspaceId,
|
|
16782
|
+
workspaceCwd: runtime.workspaceCwd,
|
|
16783
|
+
trusted: runtime.trusted,
|
|
16784
|
+
desiredGeneration: snapshot.generation,
|
|
16785
|
+
appliedGeneration: appliedGenerationByWorkspaceId.get(runtime.workspaceId) ?? 0,
|
|
16786
|
+
extensions
|
|
16787
|
+
});
|
|
16788
|
+
} catch (error2) {
|
|
16789
|
+
sendBridgeError2(res, error2, {
|
|
16790
|
+
route: "GET /workspaces/:workspace/extensions"
|
|
16791
|
+
});
|
|
16792
|
+
}
|
|
16793
|
+
});
|
|
16794
|
+
app.put(
|
|
16795
|
+
"/workspaces/:workspace/extensions/:extensionId/activation",
|
|
16796
|
+
mutate({ strict: true }),
|
|
16797
|
+
(req, res) => {
|
|
16798
|
+
const runtime = resolveWorkspaceRuntimeFromParam(registry, req, res);
|
|
16799
|
+
if (!runtime || !requireTrustedWorkspaceRuntime(runtime, res)) return;
|
|
16800
|
+
const extensionId = parseExtensionId(req, res);
|
|
16801
|
+
if (!extensionId) return;
|
|
16802
|
+
const state = parseActivationState(req, res);
|
|
16803
|
+
if (!state) return;
|
|
16804
|
+
const manager = primaryController.createExtensionManager(
|
|
16805
|
+
runtime.workspaceCwd,
|
|
16806
|
+
true
|
|
16807
|
+
);
|
|
16808
|
+
sendOperation(
|
|
16809
|
+
req,
|
|
15316
16810
|
res,
|
|
15317
|
-
|
|
15318
|
-
|
|
16811
|
+
"PUT /workspaces/:workspace/extensions/:extensionId/activation",
|
|
16812
|
+
manager,
|
|
16813
|
+
"activation",
|
|
16814
|
+
{ name: extensionId },
|
|
16815
|
+
async (extensionManager, _signal, context) => {
|
|
16816
|
+
const extension = extensionById(extensionManager, extensionId);
|
|
15319
16817
|
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
|
-
);
|
|
16818
|
+
throw new Error(`Extension "${extensionId}" not found`);
|
|
15340
16819
|
}
|
|
15341
|
-
|
|
15342
|
-
|
|
15343
|
-
|
|
15344
|
-
|
|
15345
|
-
|
|
15346
|
-
|
|
15347
|
-
|
|
16820
|
+
await context.commit(
|
|
16821
|
+
async (onCommitted) => await extensionManager.setExtensionWorkspaceActivation(
|
|
16822
|
+
extensionId,
|
|
16823
|
+
runtime.workspaceCwd,
|
|
16824
|
+
state,
|
|
16825
|
+
onCommitted
|
|
16826
|
+
)
|
|
15348
16827
|
);
|
|
15349
16828
|
return {
|
|
15350
|
-
status: "
|
|
15351
|
-
name: extension.name
|
|
15352
|
-
...info?.updatedVersion ? { version: info.updatedVersion } : {}
|
|
16829
|
+
status: state === "enabled" ? "enabled" : "disabled",
|
|
16830
|
+
name: extension.name
|
|
15353
16831
|
};
|
|
15354
|
-
}
|
|
16832
|
+
},
|
|
16833
|
+
{ refreshRuntimes: [runtime] }
|
|
15355
16834
|
);
|
|
15356
|
-
} catch (err) {
|
|
15357
|
-
sendBridgeError2(res, err, {
|
|
15358
|
-
route: "POST /workspace/extensions/:name/update"
|
|
15359
|
-
});
|
|
15360
16835
|
}
|
|
15361
|
-
|
|
15362
|
-
|
|
15363
|
-
|
|
15364
|
-
|
|
15365
|
-
|
|
15366
|
-
|
|
15367
|
-
|
|
15368
|
-
|
|
16836
|
+
);
|
|
16837
|
+
app.delete(
|
|
16838
|
+
"/workspaces/:workspace/extensions/:extensionId/activation",
|
|
16839
|
+
mutate({ strict: true }),
|
|
16840
|
+
(req, res) => {
|
|
16841
|
+
const runtime = resolveWorkspaceRuntimeFromParam(registry, req, res);
|
|
16842
|
+
if (!runtime || !requireTrustedWorkspaceRuntime(runtime, res)) return;
|
|
16843
|
+
const extensionId = parseExtensionId(req, res);
|
|
16844
|
+
if (!extensionId) return;
|
|
16845
|
+
const manager = primaryController.createExtensionManager(
|
|
16846
|
+
runtime.workspaceCwd,
|
|
16847
|
+
true
|
|
16848
|
+
);
|
|
16849
|
+
sendOperation(
|
|
15369
16850
|
req,
|
|
15370
16851
|
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);
|
|
16852
|
+
"DELETE /workspaces/:workspace/extensions/:extensionId/activation",
|
|
16853
|
+
manager,
|
|
16854
|
+
"activation",
|
|
16855
|
+
{ name: extensionId },
|
|
16856
|
+
async (extensionManager, _signal, context) => {
|
|
16857
|
+
const extension = extensionById(extensionManager, extensionId);
|
|
15386
16858
|
if (!extension) {
|
|
15387
|
-
throw new Error(`Extension "${
|
|
16859
|
+
throw new Error(`Extension "${extensionId}" not found`);
|
|
15388
16860
|
}
|
|
15389
|
-
await
|
|
15390
|
-
|
|
15391
|
-
|
|
15392
|
-
|
|
16861
|
+
const snapshot = await context.commit(
|
|
16862
|
+
async (onCommitted) => await extensionManager.clearExtensionWorkspaceActivation(
|
|
16863
|
+
extensionId,
|
|
16864
|
+
runtime.workspaceCwd,
|
|
16865
|
+
onCommitted
|
|
16866
|
+
)
|
|
15393
16867
|
);
|
|
15394
|
-
|
|
15395
|
-
|
|
16868
|
+
const activation = extensionManager.getExtensionActivationFromSnapshot(
|
|
16869
|
+
extensionId,
|
|
16870
|
+
snapshot,
|
|
16871
|
+
runtime.workspaceCwd
|
|
16872
|
+
);
|
|
16873
|
+
return { status: activation.effective, name: extension.name };
|
|
16874
|
+
},
|
|
16875
|
+
{ refreshRuntimes: [runtime] }
|
|
15396
16876
|
);
|
|
15397
|
-
} catch (err) {
|
|
15398
|
-
sendBridgeError2(res, err, {
|
|
15399
|
-
route: "DELETE /workspace/extensions/:name"
|
|
15400
|
-
});
|
|
15401
16877
|
}
|
|
15402
|
-
|
|
15403
|
-
|
|
16878
|
+
);
|
|
16879
|
+
app.post(
|
|
16880
|
+
"/workspaces/:workspace/extensions/refresh",
|
|
16881
|
+
mutate({ strict: true }),
|
|
16882
|
+
(req, res) => {
|
|
16883
|
+
const runtime = resolveWorkspaceRuntimeFromParam(registry, req, res);
|
|
16884
|
+
if (!runtime || !requireTrustedWorkspaceRuntime(runtime, res)) return;
|
|
16885
|
+
const manager = primaryController.createExtensionManager(
|
|
16886
|
+
runtime.workspaceCwd,
|
|
16887
|
+
true
|
|
16888
|
+
);
|
|
16889
|
+
sendOperation(
|
|
16890
|
+
req,
|
|
16891
|
+
res,
|
|
16892
|
+
"POST /workspaces/:workspace/extensions/refresh",
|
|
16893
|
+
manager,
|
|
16894
|
+
"refresh",
|
|
16895
|
+
{},
|
|
16896
|
+
async () => ({ status: "refreshed" }),
|
|
16897
|
+
{ refreshRuntimes: [runtime] }
|
|
16898
|
+
);
|
|
16899
|
+
}
|
|
16900
|
+
);
|
|
16901
|
+
}
|
|
15404
16902
|
}
|
|
15405
16903
|
__name(registerWorkspaceExtensionRoutes, "registerWorkspaceExtensionRoutes");
|
|
15406
16904
|
|
|
@@ -16881,6 +18379,51 @@ function registerSessionRoutes(app, deps) {
|
|
|
16881
18379
|
}
|
|
16882
18380
|
return runtime;
|
|
16883
18381
|
}, "requireTrustedRuntimeForWorkspaceRoute");
|
|
18382
|
+
const handleSessionExport = /* @__PURE__ */ __name(async (req, res, target) => {
|
|
18383
|
+
const sessionId = requireSessionId(req, res);
|
|
18384
|
+
if (sessionId === null) return;
|
|
18385
|
+
const rawFormat = req.query["format"];
|
|
18386
|
+
const format = parseSessionExportFormat(rawFormat);
|
|
18387
|
+
if (!format) {
|
|
18388
|
+
res.status(400).json({
|
|
18389
|
+
error: "Invalid export format",
|
|
18390
|
+
code: "invalid_export_format",
|
|
18391
|
+
format: typeof rawFormat === "string" ? rawFormat : String(rawFormat),
|
|
18392
|
+
allowedFormats: sessionExportFormatValues()
|
|
18393
|
+
});
|
|
18394
|
+
return;
|
|
18395
|
+
}
|
|
18396
|
+
try {
|
|
18397
|
+
const result = await archiveCoordinator.runSharedMany(
|
|
18398
|
+
[sessionId],
|
|
18399
|
+
async () => {
|
|
18400
|
+
await assertSessionLoadable(target.workspaceCwd, sessionId);
|
|
18401
|
+
return exportSessionTranscript({
|
|
18402
|
+
workspaceCwd: target.workspaceCwd,
|
|
18403
|
+
sessionId,
|
|
18404
|
+
format,
|
|
18405
|
+
config: { getChannel: /* @__PURE__ */ __name(() => "daemon", "getChannel") }
|
|
18406
|
+
});
|
|
18407
|
+
}
|
|
18408
|
+
);
|
|
18409
|
+
const filename = result.filename.replace(/["\\\r\n]/g, "_");
|
|
18410
|
+
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);
|
|
18411
|
+
} catch (err) {
|
|
18412
|
+
if (target.workspaceQualified && err instanceof SessionNotFoundError) {
|
|
18413
|
+
res.status(404).json({
|
|
18414
|
+
error: err.message,
|
|
18415
|
+
code: "session_not_found",
|
|
18416
|
+
sessionId: err.sessionId
|
|
18417
|
+
});
|
|
18418
|
+
return;
|
|
18419
|
+
}
|
|
18420
|
+
sendBridgeError2(res, err, {
|
|
18421
|
+
route: target.route,
|
|
18422
|
+
sessionId,
|
|
18423
|
+
...target.workspaceQualified ? { workspaceCwd: target.workspaceCwd } : {}
|
|
18424
|
+
});
|
|
18425
|
+
}
|
|
18426
|
+
}, "handleSessionExport");
|
|
16884
18427
|
const sendAmbiguousSessionOwner = /* @__PURE__ */ __name((res, route, sessionId, runtimes) => {
|
|
16885
18428
|
const workspaceIds = runtimes.map((runtime) => runtime.workspaceId);
|
|
16886
18429
|
logSessionRoutingFailure(route, "ambiguous", {
|
|
@@ -17447,40 +18990,20 @@ function registerSessionRoutes(app, deps) {
|
|
|
17447
18990
|
}
|
|
17448
18991
|
});
|
|
17449
18992
|
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
|
-
}
|
|
18993
|
+
await handleSessionExport(req, res, {
|
|
18994
|
+
route: "GET /session/:id/export",
|
|
18995
|
+
workspaceCwd: boundWorkspace
|
|
18996
|
+
});
|
|
18997
|
+
});
|
|
18998
|
+
app.get("/workspaces/:workspace/session/:id/export", async (req, res) => {
|
|
18999
|
+
const route = "GET /workspaces/:workspace/session/:id/export";
|
|
19000
|
+
const runtime = requireTrustedRuntimeForWorkspaceRoute(req, res, route);
|
|
19001
|
+
if (!runtime) return;
|
|
19002
|
+
await handleSessionExport(req, res, {
|
|
19003
|
+
route,
|
|
19004
|
+
workspaceCwd: runtime.workspaceCwd,
|
|
19005
|
+
workspaceQualified: true
|
|
19006
|
+
});
|
|
17484
19007
|
});
|
|
17485
19008
|
app.get("/session/:id/transcript", async (req, res) => {
|
|
17486
19009
|
const route = "GET /session/:id/transcript";
|
|
@@ -20258,7 +21781,11 @@ function createServeFeatures(deps) {
|
|
|
20258
21781
|
cachedVoiceTranscriptionAvailable = void 0;
|
|
20259
21782
|
}, "invalidateServeFeaturesCache");
|
|
20260
21783
|
const getCachedVoiceTranscriptionAvailable = /* @__PURE__ */ __name(() => {
|
|
20261
|
-
cachedVoiceTranscriptionAvailable ??= isWorkspaceVoiceTranscriptionAvailable(
|
|
21784
|
+
cachedVoiceTranscriptionAvailable ??= isWorkspaceVoiceTranscriptionAvailable(
|
|
21785
|
+
boundWorkspace,
|
|
21786
|
+
env,
|
|
21787
|
+
deps.env !== void 0
|
|
21788
|
+
);
|
|
20262
21789
|
return cachedVoiceTranscriptionAvailable;
|
|
20263
21790
|
}, "getCachedVoiceTranscriptionAvailable");
|
|
20264
21791
|
return {
|
|
@@ -20297,10 +21824,11 @@ function createServeFeatures(deps) {
|
|
|
20297
21824
|
};
|
|
20298
21825
|
}
|
|
20299
21826
|
__name(createServeFeatures, "createServeFeatures");
|
|
20300
|
-
function isWorkspaceVoiceTranscriptionAvailable(boundWorkspace) {
|
|
21827
|
+
function isWorkspaceVoiceTranscriptionAvailable(boundWorkspace, env, skipLoadEnvironment) {
|
|
20301
21828
|
try {
|
|
20302
21829
|
return hasConfiguredBatchVoiceTranscriptionModel(
|
|
20303
|
-
loadSettings(boundWorkspace)
|
|
21830
|
+
loadSettings(boundWorkspace, { skipLoadEnvironment }),
|
|
21831
|
+
{ env }
|
|
20304
21832
|
);
|
|
20305
21833
|
} catch (err) {
|
|
20306
21834
|
writeStderrLine(
|
|
@@ -20344,15 +21872,13 @@ function registerCapabilitiesRoutes(app, deps) {
|
|
|
20344
21872
|
maxTotalSessions: deps.maxTotalSessions === void 0 || deps.maxTotalSessions === 0 || deps.maxTotalSessions === Number.POSITIVE_INFINITY ? null : deps.maxTotalSessions
|
|
20345
21873
|
} : {}
|
|
20346
21874
|
},
|
|
20347
|
-
|
|
20348
|
-
|
|
20349
|
-
|
|
20350
|
-
|
|
20351
|
-
|
|
20352
|
-
|
|
20353
|
-
|
|
20354
|
-
}))
|
|
20355
|
-
} : {},
|
|
21875
|
+
workspaces: runtimes.map((runtime) => ({
|
|
21876
|
+
id: runtime.workspaceId,
|
|
21877
|
+
cwd: runtime.workspaceCwd,
|
|
21878
|
+
primary: runtime.primary,
|
|
21879
|
+
trusted: runtime.trusted,
|
|
21880
|
+
...runtimeRemoval ? { removable: runtime.removable === true } : {}
|
|
21881
|
+
})),
|
|
20356
21882
|
supportedLanguages: deps.languageCodes
|
|
20357
21883
|
};
|
|
20358
21884
|
res.status(200).json(envelope);
|
|
@@ -20942,6 +22468,24 @@ function sendPermissionVoteError(res, err, ctx, daemonLog) {
|
|
|
20942
22468
|
}
|
|
20943
22469
|
__name(sendPermissionVoteError, "sendPermissionVoteError");
|
|
20944
22470
|
function sendBridgeError(res, err, ctx, daemonLog) {
|
|
22471
|
+
if (err instanceof WorkspaceSkillNotFoundError) {
|
|
22472
|
+
res.status(404).json({
|
|
22473
|
+
error: err.message,
|
|
22474
|
+
code: "skill_not_found",
|
|
22475
|
+
skillName: err.skillName
|
|
22476
|
+
});
|
|
22477
|
+
return;
|
|
22478
|
+
}
|
|
22479
|
+
if (err instanceof WorkspaceSkillNotToggleableError) {
|
|
22480
|
+
res.status(409).json({
|
|
22481
|
+
error: err.message,
|
|
22482
|
+
code: "skill_not_toggleable",
|
|
22483
|
+
skillName: err.skillName,
|
|
22484
|
+
reason: err.reason,
|
|
22485
|
+
...err.lockedScope ? { lockedScope: err.lockedScope } : {}
|
|
22486
|
+
});
|
|
22487
|
+
return;
|
|
22488
|
+
}
|
|
20945
22489
|
if (err instanceof InvalidSessionTranscriptCursorError) {
|
|
20946
22490
|
res.status(400).json({
|
|
20947
22491
|
error: err.message,
|
|
@@ -22221,11 +23765,20 @@ function resolveDaemonTelemetryRoute(req) {
|
|
|
22221
23765
|
sessionId: workspaceTranscript[1]
|
|
22222
23766
|
};
|
|
22223
23767
|
}
|
|
23768
|
+
const workspaceExport = path15.match(
|
|
23769
|
+
/^\/workspaces\/[^/]+\/session\/([^/]+)\/export$/
|
|
23770
|
+
);
|
|
23771
|
+
if (workspaceExport?.[1] && req.method === "GET") {
|
|
23772
|
+
return {
|
|
23773
|
+
route: "GET /workspaces/:workspace/session/:id/export",
|
|
23774
|
+
sessionId: workspaceExport[1]
|
|
23775
|
+
};
|
|
23776
|
+
}
|
|
22224
23777
|
const pluralWorkspacePrefix = /^\/workspaces\/[^/]+/;
|
|
22225
23778
|
if (pluralWorkspacePrefix.test(path15)) {
|
|
22226
23779
|
const suffix = path15.replace(pluralWorkspacePrefix, "/workspace");
|
|
22227
23780
|
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") {
|
|
23781
|
+
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
23782
|
return { route: `GET ${suffix}` };
|
|
22230
23783
|
}
|
|
22231
23784
|
if (/^\/workspace\/agents\/[^/]+$/.test(suffix)) {
|
|
@@ -22246,7 +23799,7 @@ function resolveDaemonTelemetryRoute(req) {
|
|
|
22246
23799
|
}
|
|
22247
23800
|
}
|
|
22248
23801
|
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") {
|
|
23802
|
+
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
23803
|
return { route: `POST ${suffix}` };
|
|
22251
23804
|
}
|
|
22252
23805
|
if (/^\/workspace\/tools\/[^/]+\/enable$/.test(suffix)) {
|
|
@@ -23265,7 +24818,8 @@ function registerWorkspaceManagementRoutes(app, deps) {
|
|
|
23265
24818
|
const workspaceActivity = /* @__PURE__ */ __name((runtime) => {
|
|
23266
24819
|
const controllerActivity = runtimeRemoval?.getActivity(runtime) ?? {
|
|
23267
24820
|
pendingSessionStarts: 0,
|
|
23268
|
-
channelWorkers: 0
|
|
24821
|
+
channelWorkers: 0,
|
|
24822
|
+
voiceSessions: 0
|
|
23269
24823
|
};
|
|
23270
24824
|
const acpActivity = getAcpHandle?.()?.getWorkspaceActivity(
|
|
23271
24825
|
runtime.workspaceId
|
|
@@ -23276,7 +24830,8 @@ function registerWorkspaceManagementRoutes(app, deps) {
|
|
|
23276
24830
|
activePrompts: runtime.bridge.activePromptCount,
|
|
23277
24831
|
acpConnections: acpActivity.acpConnections,
|
|
23278
24832
|
memoryTasks: acpActivity.memoryTasks,
|
|
23279
|
-
channelWorkers: controllerActivity.channelWorkers
|
|
24833
|
+
channelWorkers: controllerActivity.channelWorkers,
|
|
24834
|
+
voiceSessions: controllerActivity.voiceSessions
|
|
23280
24835
|
};
|
|
23281
24836
|
}, "workspaceActivity");
|
|
23282
24837
|
const isBusy = /* @__PURE__ */ __name((activity) => Object.values(activity).some((count) => count > 0), "isBusy");
|
|
@@ -24479,6 +26034,106 @@ function registerWorkspaceQualifiedToolsRoutes(app, deps) {
|
|
|
24479
26034
|
}
|
|
24480
26035
|
__name(registerWorkspaceQualifiedToolsRoutes, "registerWorkspaceQualifiedToolsRoutes");
|
|
24481
26036
|
|
|
26037
|
+
// packages/cli/src/serve/routes/workspace-skills.ts
|
|
26038
|
+
init_esbuild_shims();
|
|
26039
|
+
function parseSkillToggleRequest(req, res, safeBody2) {
|
|
26040
|
+
const rawSkillName = req.params["name"];
|
|
26041
|
+
if (!rawSkillName || typeof rawSkillName !== "string") {
|
|
26042
|
+
res.status(400).json({
|
|
26043
|
+
error: "Skill name path parameter is required",
|
|
26044
|
+
code: "invalid_skill_name"
|
|
26045
|
+
});
|
|
26046
|
+
return void 0;
|
|
26047
|
+
}
|
|
26048
|
+
const skillName = rawSkillName.trim();
|
|
26049
|
+
if (skillName.length === 0) {
|
|
26050
|
+
res.status(400).json({
|
|
26051
|
+
error: "Skill name path parameter is required",
|
|
26052
|
+
code: "invalid_skill_name"
|
|
26053
|
+
});
|
|
26054
|
+
return void 0;
|
|
26055
|
+
}
|
|
26056
|
+
if (skillName.length > MAX_SKILL_NAME_LENGTH) {
|
|
26057
|
+
res.status(400).json({
|
|
26058
|
+
error: `Skill name exceeds ${MAX_SKILL_NAME_LENGTH}-character limit`,
|
|
26059
|
+
code: "invalid_skill_name"
|
|
26060
|
+
});
|
|
26061
|
+
return void 0;
|
|
26062
|
+
}
|
|
26063
|
+
const enabled = safeBody2(req)["enabled"];
|
|
26064
|
+
if (typeof enabled !== "boolean") {
|
|
26065
|
+
res.status(400).json({
|
|
26066
|
+
error: "`enabled` is required and must be a boolean",
|
|
26067
|
+
code: "invalid_enabled_flag"
|
|
26068
|
+
});
|
|
26069
|
+
return void 0;
|
|
26070
|
+
}
|
|
26071
|
+
return { skillName, enabled };
|
|
26072
|
+
}
|
|
26073
|
+
__name(parseSkillToggleRequest, "parseSkillToggleRequest");
|
|
26074
|
+
function registerWorkspaceSkillsRoutes(app, deps) {
|
|
26075
|
+
const buildWorkspaceCtx = createBuildWorkspaceCtx(
|
|
26076
|
+
deps.workspaceRuntime.workspaceCwd
|
|
26077
|
+
);
|
|
26078
|
+
const route = "POST /workspace/skills/:name/enable";
|
|
26079
|
+
app.post(
|
|
26080
|
+
"/workspace/skills/:name/enable",
|
|
26081
|
+
deps.mutate({ strict: true }),
|
|
26082
|
+
async (req, res) => {
|
|
26083
|
+
if (!requireTrustedWorkspaceRuntime(deps.workspaceRuntime, res)) return;
|
|
26084
|
+
const input = parseSkillToggleRequest(req, res, deps.safeBody);
|
|
26085
|
+
if (!input) return;
|
|
26086
|
+
const clientId = deps.parseAndValidateClientId(req, res);
|
|
26087
|
+
if (clientId === null) return;
|
|
26088
|
+
try {
|
|
26089
|
+
const result = await deps.workspaceRuntime.workspaceService.setWorkspaceSkillEnabled(
|
|
26090
|
+
buildWorkspaceCtx(route, clientId),
|
|
26091
|
+
input.skillName,
|
|
26092
|
+
input.enabled
|
|
26093
|
+
);
|
|
26094
|
+
res.status(200).json(result);
|
|
26095
|
+
} catch (err) {
|
|
26096
|
+
deps.sendBridgeError(res, err, { route });
|
|
26097
|
+
}
|
|
26098
|
+
}
|
|
26099
|
+
);
|
|
26100
|
+
}
|
|
26101
|
+
__name(registerWorkspaceSkillsRoutes, "registerWorkspaceSkillsRoutes");
|
|
26102
|
+
function registerWorkspaceQualifiedSkillsRoutes(app, deps) {
|
|
26103
|
+
const route = "POST /workspaces/:workspace/skills/:name/enable";
|
|
26104
|
+
app.post(
|
|
26105
|
+
"/workspaces/:workspace/skills/:name/enable",
|
|
26106
|
+
deps.mutate({ strict: true }),
|
|
26107
|
+
async (req, res) => {
|
|
26108
|
+
const runtime = resolveWorkspaceRuntimeFromParam(
|
|
26109
|
+
deps.workspaceRegistry,
|
|
26110
|
+
req,
|
|
26111
|
+
res
|
|
26112
|
+
);
|
|
26113
|
+
if (!runtime || !requireTrustedWorkspaceRuntime(runtime, res)) return;
|
|
26114
|
+
const input = parseSkillToggleRequest(req, res, deps.safeBody);
|
|
26115
|
+
if (!input) return;
|
|
26116
|
+
const clientId = parseAndValidateWorkspaceClientId(
|
|
26117
|
+
req,
|
|
26118
|
+
res,
|
|
26119
|
+
runtime.bridge
|
|
26120
|
+
);
|
|
26121
|
+
if (clientId === null) return;
|
|
26122
|
+
try {
|
|
26123
|
+
const result = await runtime.workspaceService.setWorkspaceSkillEnabled(
|
|
26124
|
+
createBuildWorkspaceCtx(runtime.workspaceCwd)(route, clientId),
|
|
26125
|
+
input.skillName,
|
|
26126
|
+
input.enabled
|
|
26127
|
+
);
|
|
26128
|
+
res.status(200).json(result);
|
|
26129
|
+
} catch (err) {
|
|
26130
|
+
deps.sendBridgeError(res, err, { route });
|
|
26131
|
+
}
|
|
26132
|
+
}
|
|
26133
|
+
);
|
|
26134
|
+
}
|
|
26135
|
+
__name(registerWorkspaceQualifiedSkillsRoutes, "registerWorkspaceQualifiedSkillsRoutes");
|
|
26136
|
+
|
|
24482
26137
|
// packages/cli/src/serve/routes/channel-webhooks.ts
|
|
24483
26138
|
init_esbuild_shims();
|
|
24484
26139
|
var import_express4 = __toESM(require_express(), 1);
|
|
@@ -24772,7 +26427,10 @@ function describeRegistryPrimaryForConflict(registry) {
|
|
|
24772
26427
|
}
|
|
24773
26428
|
__name(describeRegistryPrimaryForConflict, "describeRegistryPrimaryForConflict");
|
|
24774
26429
|
function getRuntimeEffectiveEnv(metadata) {
|
|
24775
|
-
|
|
26430
|
+
if (!metadata || metadata.mode === "parent-process") {
|
|
26431
|
+
return metadata?.effectiveEnv;
|
|
26432
|
+
}
|
|
26433
|
+
return metadata.effectiveEnv ?? {};
|
|
24776
26434
|
}
|
|
24777
26435
|
__name(getRuntimeEffectiveEnv, "getRuntimeEffectiveEnv");
|
|
24778
26436
|
var DEFAULT_SESSION_IDLE_TIMEOUT_MS = 30 * 6e4;
|
|
@@ -24788,6 +26446,11 @@ function computeKeepaliveIntervalMs(idleTimeoutMs) {
|
|
|
24788
26446
|
}
|
|
24789
26447
|
__name(computeKeepaliveIntervalMs, "computeKeepaliveIntervalMs");
|
|
24790
26448
|
function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
26449
|
+
if (deps.workspaceRuntimeRemoval && !deps.voiceCoordinator) {
|
|
26450
|
+
throw new Error(
|
|
26451
|
+
"createServeApp: deps.workspaceRuntimeRemoval requires the matching deps.voiceCoordinator."
|
|
26452
|
+
);
|
|
26453
|
+
}
|
|
24791
26454
|
const app = (0, import_express5.default)();
|
|
24792
26455
|
const injectedWorkspaceRegistry = deps.workspaceRegistry;
|
|
24793
26456
|
const boundWorkspace = injectedWorkspaceRegistry?.primary.workspaceCwd ?? deps.boundWorkspace ?? canonicalizeWorkspace(opts.workspace ?? process.cwd());
|
|
@@ -24880,6 +26543,7 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
24880
26543
|
workspaceRuntimeRemovalAvailable: deps.workspaceRuntimeRemoval !== void 0,
|
|
24881
26544
|
...primaryEffectiveEnv ? { env: primaryEffectiveEnv } : {}
|
|
24882
26545
|
});
|
|
26546
|
+
app.locals.invalidateServeFeaturesCache = invalidateServeFeaturesCache;
|
|
24883
26547
|
const statusProvider = deps.statusProvider ?? createDaemonStatusProvider(
|
|
24884
26548
|
primaryEffectiveEnv ? { env: primaryEffectiveEnv } : {}
|
|
24885
26549
|
);
|
|
@@ -24945,12 +26609,18 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
24945
26609
|
primaryEffectiveEnv ? { env: primaryEffectiveEnv } : {}
|
|
24946
26610
|
),
|
|
24947
26611
|
workspaceSkillsStatusProvider: createWorkspaceSkillsStatusProvider(),
|
|
26612
|
+
...primaryEffectiveEnv ? { voiceEnv: primaryEffectiveEnv } : {},
|
|
24948
26613
|
isChannelLive: /* @__PURE__ */ __name(() => bridge.isChannelLive(), "isChannelLive"),
|
|
24949
26614
|
persistDisabledTools: deps.persistDisabledTools ?? (async () => {
|
|
24950
26615
|
throw new Error(
|
|
24951
26616
|
"setWorkspaceToolEnabled requires persistDisabledTools in ServeAppDeps"
|
|
24952
26617
|
);
|
|
24953
26618
|
}),
|
|
26619
|
+
persistDisabledSkills: deps.persistDisabledSkills ?? (async () => {
|
|
26620
|
+
throw new Error(
|
|
26621
|
+
"setWorkspaceSkillEnabled requires persistDisabledSkills in ServeAppDeps"
|
|
26622
|
+
);
|
|
26623
|
+
}),
|
|
24954
26624
|
queryWorkspaceStatus: /* @__PURE__ */ __name((method, idle) => bridge.queryWorkspaceStatus(method, idle), "queryWorkspaceStatus"),
|
|
24955
26625
|
invokeWorkspaceCommand: /* @__PURE__ */ __name((method, params, invokeOpts) => bridge.invokeWorkspaceCommand(method, params, invokeOpts), "invokeWorkspaceCommand"),
|
|
24956
26626
|
preheatAcpChild: /* @__PURE__ */ __name(() => bridge.preheat(), "preheatAcpChild"),
|
|
@@ -24983,6 +26653,7 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
24983
26653
|
);
|
|
24984
26654
|
app.locals.workspaceRegistry = workspaceRegistry;
|
|
24985
26655
|
const primaryRuntime = workspaceRegistry.primary;
|
|
26656
|
+
const voiceCoordinator = deps.voiceCoordinator ?? new WorkspaceVoiceCoordinator();
|
|
24986
26657
|
const primaryBoundWorkspace = primaryRuntime.workspaceCwd;
|
|
24987
26658
|
const primaryBridge = primaryRuntime.bridge;
|
|
24988
26659
|
const primaryWorkspace = primaryRuntime.workspaceService;
|
|
@@ -25218,6 +26889,7 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
25218
26889
|
mutate,
|
|
25219
26890
|
safeBody,
|
|
25220
26891
|
sendBridgeError: sendBridgeError2,
|
|
26892
|
+
workspaceRegistry,
|
|
25221
26893
|
...deps.maxExtensionOperationHistory === void 0 ? {} : { maxExtensionOperationHistory: deps.maxExtensionOperationHistory }
|
|
25222
26894
|
});
|
|
25223
26895
|
registerWorkspaceFileReadRoutes(app, {
|
|
@@ -25319,9 +26991,22 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
25319
26991
|
persistSetting: deps.persistSetting,
|
|
25320
26992
|
persistSettings: deps.persistSettings,
|
|
25321
26993
|
transcribe: deps.voiceTranscriber,
|
|
26994
|
+
env: getRuntimeEffectiveEnv(primaryRuntime.env),
|
|
26995
|
+
acquireVoiceLease: /* @__PURE__ */ __name(() => voiceCoordinator.acquire(primaryRuntime), "acquireVoiceLease"),
|
|
25322
26996
|
broadcastSettingsChanged,
|
|
25323
26997
|
parseAndValidateClientId: /* @__PURE__ */ __name((req, res) => parseAndValidateWorkspaceClientId(req, res, primaryBridge), "parseAndValidateClientId")
|
|
25324
26998
|
});
|
|
26999
|
+
registerWorkspaceQualifiedVoiceRoutes(app, {
|
|
27000
|
+
workspaceRegistry,
|
|
27001
|
+
mutate,
|
|
27002
|
+
safeBody,
|
|
27003
|
+
persistSetting: deps.persistSetting,
|
|
27004
|
+
persistSettings: deps.persistSettings,
|
|
27005
|
+
transcribe: deps.voiceTranscriber,
|
|
27006
|
+
acquireVoiceLease: /* @__PURE__ */ __name((runtime) => voiceCoordinator.acquire(runtime), "acquireVoiceLease"),
|
|
27007
|
+
parseAndValidateClientId: /* @__PURE__ */ __name((req, res, runtime) => parseAndValidateWorkspaceClientId(req, res, runtime.bridge), "parseAndValidateClientId"),
|
|
27008
|
+
invalidateServeFeaturesCache
|
|
27009
|
+
});
|
|
25325
27010
|
if (deps.persistSettings) {
|
|
25326
27011
|
registerWorkspaceModelsRoutes(app, {
|
|
25327
27012
|
boundWorkspace: primaryBoundWorkspace,
|
|
@@ -25435,6 +27120,19 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
25435
27120
|
safeBody,
|
|
25436
27121
|
sendBridgeError: sendBridgeError2
|
|
25437
27122
|
});
|
|
27123
|
+
registerWorkspaceSkillsRoutes(app, {
|
|
27124
|
+
workspaceRuntime: primaryRuntime,
|
|
27125
|
+
mutate,
|
|
27126
|
+
safeBody,
|
|
27127
|
+
sendBridgeError: sendBridgeError2,
|
|
27128
|
+
parseAndValidateClientId: /* @__PURE__ */ __name((req, res) => parseAndValidateWorkspaceClientId(req, res, primaryBridge), "parseAndValidateClientId")
|
|
27129
|
+
});
|
|
27130
|
+
registerWorkspaceQualifiedSkillsRoutes(app, {
|
|
27131
|
+
workspaceRegistry,
|
|
27132
|
+
mutate,
|
|
27133
|
+
safeBody,
|
|
27134
|
+
sendBridgeError: sendBridgeError2
|
|
27135
|
+
});
|
|
25438
27136
|
registerScheduledTasksRoutes(app, {
|
|
25439
27137
|
boundWorkspace: primaryBoundWorkspace,
|
|
25440
27138
|
mutate,
|
|
@@ -25553,10 +27251,15 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
25553
27251
|
{
|
|
25554
27252
|
path: "/voice/stream",
|
|
25555
27253
|
onConnection: createVoiceWsConnectionHandler(primaryBoundWorkspace, {
|
|
25556
|
-
env: getRuntimeEffectiveEnv(primaryRuntime.env)
|
|
27254
|
+
env: getRuntimeEffectiveEnv(primaryRuntime.env),
|
|
27255
|
+
acquireVoiceLease: /* @__PURE__ */ __name(() => voiceCoordinator.acquire(primaryRuntime), "acquireVoiceLease")
|
|
25557
27256
|
})
|
|
25558
27257
|
}
|
|
25559
|
-
]
|
|
27258
|
+
],
|
|
27259
|
+
workspaceVoiceConnection: /* @__PURE__ */ __name((runtime, ws, req) => createVoiceWsConnectionHandler(runtime.workspaceCwd, {
|
|
27260
|
+
env: getRuntimeEffectiveEnv(runtime.env),
|
|
27261
|
+
acquireVoiceLease: /* @__PURE__ */ __name(() => voiceCoordinator.acquire(runtime), "acquireVoiceLease")
|
|
27262
|
+
})(ws, req), "workspaceVoiceConnection")
|
|
25560
27263
|
});
|
|
25561
27264
|
if (acpHandleRef.current) {
|
|
25562
27265
|
app.locals["acpHandle"] = acpHandleRef.current;
|