@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.
Files changed (194) hide show
  1. package/bundled/qc-helper/docs/configuration/settings.md +6 -0
  2. package/bundled/qc-helper/docs/features/sub-agents.md +21 -0
  3. package/bundled/qc-helper/docs/qwen-serve.md +7 -2
  4. package/bundled/review/SKILL.md +23 -11
  5. package/chunks/{MaxSizedBox-QAYHOC4F.js → MaxSizedBox-67ODZ4SZ.js} +5 -6
  6. package/chunks/{StandaloneSessionPicker-D3R6YYX3.js → StandaloneSessionPicker-SIPBQT5Z.js} +17 -18
  7. package/chunks/{acpAgent-ENHBW72A.js → acpAgent-VPZJXLFX.js} +141 -80
  8. package/chunks/{agent-57LX4KMG.js → agent-Z7NWL6XL.js} +2 -3
  9. package/chunks/{agent-headless-AC6EVNAP.js → agent-headless-73HZ6ROQ.js} +2 -3
  10. package/chunks/{bridge-4OZH44MK.js → bridge-XFKW6S7Q.js} +5 -6
  11. package/chunks/{chunk-DPSWJWZI.js → chunk-2W3OOD4W.js} +1 -0
  12. package/chunks/{chunk-FDL4PB5H.js → chunk-3XJFUTNM.js} +8 -8
  13. package/chunks/{chunk-AAS2Q2TV.js → chunk-422XAKCK.js} +2 -2
  14. package/chunks/{chunk-7U7UIO5W.js → chunk-4FMG7EDJ.js} +19 -4
  15. package/chunks/{chunk-X7EBAABW.js → chunk-4YWLED2A.js} +1 -1
  16. package/chunks/{chunk-WN4D7YCI.js → chunk-5EBKKSBX.js} +1 -1
  17. package/chunks/{chunk-G6QTKQN4.js → chunk-5LROA5W4.js} +3 -3
  18. package/chunks/{chunk-SUWGUGAD.js → chunk-65N2J6V2.js} +2 -2
  19. package/chunks/{chunk-T2L6SGQY.js → chunk-6GETGQWP.js} +26 -6
  20. package/chunks/{chunk-T5C2OUCI.js → chunk-6GKMJPOC.js} +3 -3
  21. package/chunks/{chunk-K3PPUB46.js → chunk-6W6DI34P.js} +1 -1
  22. package/chunks/{chunk-WM5HY72H.js → chunk-7L4XBYVW.js} +2 -2
  23. package/chunks/{chunk-CLTWL6HD.js → chunk-7MJAZMXK.js} +5 -5
  24. package/chunks/{chunk-6HHAADP2.js → chunk-AAR5SGMD.js} +4 -4
  25. package/chunks/{chunk-P3S67A5R.js → chunk-B5I2IIWY.js} +3 -3
  26. package/chunks/{chunk-4DYYRLIE.js → chunk-CEW4HDPK.js} +639 -317
  27. package/chunks/{chunk-JR63TOV4.js → chunk-CJFNEBYB.js} +1 -1
  28. package/chunks/{chunk-PMRH4YYW.js → chunk-CUUXANEQ.js} +2 -1
  29. package/chunks/{chunk-Z56JO4JZ.js → chunk-DK33QJ3V.js} +6 -6
  30. package/chunks/{chunk-N7FCSERB.js → chunk-DPFBNG72.js} +1 -1
  31. package/chunks/{chunk-HZYUNZDQ.js → chunk-EQY6POVO.js} +1 -1
  32. package/chunks/{chunk-MBJ3D6SF.js → chunk-F7NXQH4Y.js} +1 -1
  33. package/chunks/{chunk-C4CXFRKY.js → chunk-FXMC7SL2.js} +25 -4
  34. package/chunks/{chunk-CSACSGAW.js → chunk-GGJII3FM.js} +2 -2
  35. package/chunks/{chunk-EU3U7USF.js → chunk-H3FQOPPS.js} +2 -2
  36. package/chunks/{chunk-YVEDCAUE.js → chunk-HGT6JR3U.js} +19 -0
  37. package/chunks/{chunk-MBP3K324.js → chunk-HHEFI4RP.js} +32 -11
  38. package/chunks/{chunk-B6ABUBYY.js → chunk-JSJFKYWT.js} +67 -5
  39. package/chunks/{chunk-QABSSDQH.js → chunk-K4PZ5F5T.js} +1 -1
  40. package/chunks/{chunk-SSFDPGFN.js → chunk-K7CRWKOE.js} +7879 -5472
  41. package/chunks/{chunk-J2TDDOSM.js → chunk-L37RC7MQ.js} +1 -1
  42. package/chunks/{chunk-V75IOTWF.js → chunk-L62SEJWY.js} +4 -4
  43. package/chunks/{chunk-HE54PGVI.js → chunk-LNUXFLLJ.js} +2 -2
  44. package/chunks/{chunk-PHYCWSDG.js → chunk-LQU7EVFA.js} +1 -1
  45. package/chunks/{chunk-AUGSSJX3.js → chunk-LTNI523B.js} +2 -2
  46. package/chunks/{chunk-4LJNI7MU.js → chunk-MO7O5722.js} +262 -7
  47. package/chunks/{chunk-S2QXYLUB.js → chunk-N6FXRR6P.js} +1 -1
  48. package/chunks/{chunk-WL4L7EZH.js → chunk-OA4JVLSA.js} +27 -1
  49. package/chunks/{chunk-XHW5VUIQ.js → chunk-OFEVHGVU.js} +157 -55
  50. package/chunks/{chunk-T7FSRWQ5.js → chunk-P6LART6G.js} +107 -6
  51. package/chunks/{chunk-GYGGUR4Q.js → chunk-Q5GLXH5Q.js} +3 -3
  52. package/chunks/{chunk-SE74CIZD.js → chunk-RDRVMDWV.js} +1 -1
  53. package/chunks/{chunk-DKXW66XA.js → chunk-SPUYQB4T.js} +1 -1
  54. package/chunks/{chunk-AGBFWLA5.js → chunk-TISZXPAX.js} +1 -1
  55. package/chunks/{chunk-ZLM3KXBT.js → chunk-TTUO4DNG.js} +14 -13
  56. package/chunks/{chunk-4QOPEEL2.js → chunk-UFOKA4PR.js} +1 -1
  57. package/chunks/{chunk-2MF6ICOL.js → chunk-V7EQTZYR.js} +36 -8
  58. package/chunks/{chunk-WWQ3QIJS.js → chunk-VBRRNQB7.js} +10 -10
  59. package/chunks/{chunk-OCC3UTWA.js → chunk-W34A5MJH.js} +1 -1
  60. package/chunks/{chunk-LAM6CGP5.js → chunk-WDW64OW3.js} +3 -3
  61. package/chunks/{chunk-DMTQY5GY.js → chunk-WDWJ4OQJ.js} +3 -3
  62. package/chunks/{chunk-B6KHOCU7.js → chunk-WIASPMLX.js} +8 -8
  63. package/chunks/{chunk-4FPUAYTT.js → chunk-WLXYAKV7.js} +4 -4
  64. package/chunks/{chunk-YWUK7OS7.js → chunk-WTSXKLG5.js} +1 -1
  65. package/chunks/{chunk-7BIZARI2.js → chunk-X362TMXZ.js} +4 -4
  66. package/chunks/{chunk-ASVORVFT.js → chunk-XLIRF7IK.js} +1 -1
  67. package/chunks/{chunk-DNKX35GS.js → chunk-XXJKGHZV.js} +4 -4
  68. package/chunks/{chunk-UI3W56WF.js → chunk-YU56QGXX.js} +1 -1
  69. package/chunks/{chunk-RJL5T4WJ.js → chunk-YX4LLZ3D.js} +47 -35
  70. package/chunks/{chunk-YH4J5XI3.js → chunk-YYPXHMWO.js} +4 -4
  71. package/chunks/{chunk-AAWKZ5R7.js → chunk-ZKMGAFYV.js} +1 -1
  72. package/chunks/{chunk-EUNRLYJG.js → chunk-ZQTZXVHP.js} +4 -4
  73. package/chunks/{computer-use-DIP5BWXP.js → computer-use-IYTX45WA.js} +2 -3
  74. package/chunks/{contextCommand-QU4GJWWD.js → contextCommand-272V77WU.js} +4 -5
  75. package/chunks/{create-sub-session-TCLIYVGR.js → create-sub-session-CBJMQLYP.js} +2 -3
  76. package/chunks/{daemon-CEZ27OH4.js → daemon-RNGFPMAB.js} +315 -36
  77. package/chunks/{daemon-status-provider-SIBFMLBQ.js → daemon-status-provider-L4STG5VO.js} +9 -10
  78. package/chunks/{earlyInputCapture-L66BEG2C.js → earlyInputCapture-EN4BRREQ.js} +2 -3
  79. package/chunks/{edit-Y5LAJSST.js → edit-OMAF2QDR.js} +2 -3
  80. package/chunks/{enter-worktree-X5LML7T4.js → enter-worktree-BBZX52HG.js} +2 -3
  81. package/chunks/{enterPlanMode-XWF32QOW.js → enterPlanMode-5NNFMBUY.js} +2 -3
  82. package/chunks/{environment-YVMA5F4I.js → environment-7TEWMJYN.js} +4 -5
  83. package/chunks/{errors-4Y5SUAOC.js → errors-I4VKVX3F.js} +4 -5
  84. package/chunks/{exit-worktree-SFHU7XYC.js → exit-worktree-FUWRJI33.js} +2 -3
  85. package/chunks/{exitPlanMode-FP3NAN6E.js → exitPlanMode-IHCJ5EEI.js} +2 -3
  86. package/chunks/{fast-path-ZJNFFCBM.js → fast-path-ZA57TOH4.js} +2 -2
  87. package/chunks/{gemini-5CAB22BI.js → gemini-UOAKVXBG.js} +91 -33
  88. package/chunks/{geminiContentGenerator-GE3HPIFB.js → geminiContentGenerator-G6UCE6MW.js} +1 -1
  89. package/chunks/{glob-YPACZAEL.js → glob-4GJQ2UFF.js} +2 -3
  90. package/chunks/{grep-IPR4XW37.js → grep-5QSYXF2E.js} +2 -3
  91. package/chunks/{handleAutoUpdate-P2KEP56V.js → handleAutoUpdate-ULHNWL6R.js} +6 -7
  92. package/chunks/{i18n-BRCT66AA.js → i18n-CYDUTF6F.js} +3 -4
  93. package/chunks/{initializer-34HXSK3A.js → initializer-NRFKUGIO.js} +7 -8
  94. package/chunks/{installationInfo-WOTDQMSC.js → installationInfo-WELKRHHE.js} +3 -4
  95. package/chunks/{list-XOGP7UPP.js → list-JVDXEG2E.js} +10 -11
  96. package/chunks/{loadedSettingsAdapter-L4YKGU6F.js → loadedSettingsAdapter-WAM4VUTG.js} +7 -8
  97. package/chunks/{mcp-HH7DIP7Y.js → mcp-YHOZN3ZM.js} +7 -8
  98. package/chunks/{monitor-25DEBT6U.js → monitor-R7XZPJHQ.js} +2 -3
  99. package/chunks/{nonInteractiveCli-CT3VSSLK.js → nonInteractiveCli-NSOB6D74.js} +34 -35
  100. package/chunks/{notebook-edit-TAVXSWQ6.js → notebook-edit-6RNKE2L2.js} +2 -3
  101. package/chunks/{openaiContentGenerator-6I7DKUAN.js → openaiContentGenerator-XNS5S64I.js} +2 -2
  102. package/chunks/{pidfile-SIQGCFLZ.js → pidfile-XE4RRR2P.js} +2 -3
  103. package/chunks/{qwenContentGenerator-5DDIXI2N.js → qwenContentGenerator-APDPJ3VN.js} +3 -4
  104. package/chunks/{read-file-WDOAYRFU.js → read-file-XSUWXSR7.js} +1 -1
  105. package/chunks/{ripGrep-MI7PHN3Q.js → ripGrep-APB3CDMU.js} +2 -3
  106. package/chunks/{run-qwen-serve-PNTYF3RE.js → run-qwen-serve-NWTGPERT.js} +224 -75
  107. package/chunks/{runtime-V3FBMUTG.js → runtime-EIO3NQKR.js} +8 -9
  108. package/chunks/{scheduler-4TAUIJBM.js → scheduler-JJDZBDRN.js} +2 -3
  109. package/chunks/{serve-WB2TR7XM.js → serve-WTIPB3EI.js} +7 -8
  110. package/chunks/{server-AF6LNFPK.js → server-NJPHQHLH.js} +2458 -755
  111. package/chunks/{session-BIGK4NRW.js → session-O4AO7FTS.js} +35 -36
  112. package/chunks/{settings-IXF5FIQO.js → settings-HQNDK7TG.js} +6 -7
  113. package/chunks/{shell-5VWJ2PPG.js → shell-UIXM6PWG.js} +2 -3
  114. package/chunks/{skill-SI667Y3B.js → skill-3XMB4ULV.js} +1 -1
  115. package/chunks/{spawnChannel-IKX6DD7J.js → spawnChannel-3R632QRM.js} +4 -5
  116. package/chunks/{src-55DZWDAK.js → src-USZKPUOC.js} +33 -4
  117. package/chunks/{standalone-update-MHLSFHE6.js → standalone-update-L5XW3D7K.js} +4 -5
  118. package/chunks/{startInteractiveUI-V2EDWLAX.js → startInteractiveUI-CPZOL2WB.js} +174 -122
  119. package/chunks/{team-create-IZAMLBJS.js → team-create-XXPRF3IQ.js} +2 -3
  120. package/chunks/{team-plan-approval-3R4RCKCP.js → team-plan-approval-EOTTKHDW.js} +2 -3
  121. package/chunks/{theme-manager-LCOXZ5DO.js → theme-manager-P6UITO2L.js} +2 -3
  122. package/chunks/{tool-search-NYNCD5LF.js → tool-search-DDNGK2LR.js} +1 -1
  123. package/chunks/{total-session-admission-PJWGOMQE.js → total-session-admission-PL5N7DNY.js} +5 -6
  124. package/chunks/{trustedFolders-DOJDHNYP.js → trustedFolders-C5LMF3UR.js} +3 -4
  125. package/chunks/types-ML3TRJQ5.js +16 -0
  126. package/chunks/{updateCheck-ZXL66KIG.js → updateCheck-QTVVISH7.js} +4 -5
  127. package/chunks/{validateNonInterActiveAuth-JSNJZCYY.js → validateNonInterActiveAuth-RY4RKBQG.js} +30 -31
  128. package/chunks/{version-3NLBENG5.js → version-U45RYOSD.js} +1 -1
  129. package/chunks/{workflow-BQYLF7XP.js → workflow-TCIAQYOA.js} +3 -4
  130. package/chunks/{workspace-providers-status-FA2DGEZW.js → workspace-providers-status-I3G2HYLY.js} +10 -11
  131. package/chunks/{workspace-registration-store-TARTAGMI.js → workspace-registration-store-IH5GLZ6Y.js} +1 -1
  132. package/chunks/{workspace-registry-AGE7WAXX.js → workspace-registry-QEKBELIX.js} +5 -6
  133. package/chunks/{workspace-service-ERC5VR2B.js → workspace-service-ORKHGLP3.js} +17 -14
  134. package/chunks/{workspace-skills-status-FKYWGNCN.js → workspace-skills-status-D7QCNDYY.js} +9 -10
  135. package/chunks/{write-file-S47KSK7Z.js → write-file-JGRQR5VM.js} +2 -3
  136. package/cli.js +11 -11
  137. package/package.json +3 -3
  138. package/web-shell/assets/{arc-Zj4EkBUY.js → arc-CUeDlxvE.js} +1 -1
  139. package/web-shell/assets/{architectureDiagram-3BPJPVTR-DAk1Ua3Y.js → architectureDiagram-3BPJPVTR-PiUszkQA.js} +1 -1
  140. package/web-shell/assets/{blockDiagram-GPEHLZMM-DBNqUthM.js → blockDiagram-GPEHLZMM-s2wYSO7e.js} +1 -1
  141. package/web-shell/assets/{c4Diagram-AAUBKEIU-Dq5QGJ53.js → c4Diagram-AAUBKEIU-DyLxZoz9.js} +1 -1
  142. package/web-shell/assets/channel-DkuJJ0qd.js +1 -0
  143. package/web-shell/assets/{chunk-2J33WTMH-DY0AeGru.js → chunk-2J33WTMH-Dsdai7Tk.js} +1 -1
  144. package/web-shell/assets/{chunk-4BX2VUAB-COX3OyIi.js → chunk-4BX2VUAB-DEosOjV7.js} +1 -1
  145. package/web-shell/assets/{chunk-55IACEB6-Csoyddbh.js → chunk-55IACEB6-sNsRLTXf.js} +1 -1
  146. package/web-shell/assets/{chunk-727SXJPM-BJPONdxX.js → chunk-727SXJPM-DkRNZzmh.js} +1 -1
  147. package/web-shell/assets/{chunk-AQP2D5EJ-syLVH6fg.js → chunk-AQP2D5EJ-BrsRNpzd.js} +1 -1
  148. package/web-shell/assets/{chunk-FMBD7UC4-Cfm59Nhw.js → chunk-FMBD7UC4-CI039lpl.js} +1 -1
  149. package/web-shell/assets/{chunk-ND2GUHAM-DZf6tAqS.js → chunk-ND2GUHAM-M-pkeIfu.js} +1 -1
  150. package/web-shell/assets/{chunk-QZHKN3VN-DKBp_v5q.js → chunk-QZHKN3VN-C4ckGNbH.js} +1 -1
  151. package/web-shell/assets/classDiagram-4FO5ZUOK-CwqHc0TB.js +1 -0
  152. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-CwqHc0TB.js +1 -0
  153. package/web-shell/assets/{cose-bilkent-S5V4N54A-DUU3mMQ5.js → cose-bilkent-S5V4N54A-BciKUAFq.js} +1 -1
  154. package/web-shell/assets/{dagre-BM42HDAG-BUgNfbh-.js → dagre-BM42HDAG-C1nTSgU6.js} +1 -1
  155. package/web-shell/assets/{diagram-2AECGRRQ-DT7MVfAg.js → diagram-2AECGRRQ-BDPNWr1m.js} +1 -1
  156. package/web-shell/assets/{diagram-5GNKFQAL-Cv4YG-ec.js → diagram-5GNKFQAL-CNFhTszL.js} +1 -1
  157. package/web-shell/assets/{diagram-KO2AKTUF-BAe9LLkn.js → diagram-KO2AKTUF-CjPa4p5a.js} +1 -1
  158. package/web-shell/assets/{diagram-LMA3HP47-DW8wqTIP.js → diagram-LMA3HP47-ZzPykSyG.js} +1 -1
  159. package/web-shell/assets/{diagram-OG6HWLK6-CmkuWbaK.js → diagram-OG6HWLK6-0UVtZ9A4.js} +1 -1
  160. package/web-shell/assets/{erDiagram-TEJ5UH35-DwvdHjZ9.js → erDiagram-TEJ5UH35-CajDZCat.js} +1 -1
  161. package/web-shell/assets/{flowDiagram-I6XJVG4X-CSsXrhW_.js → flowDiagram-I6XJVG4X-BRuFBIvL.js} +1 -1
  162. package/web-shell/assets/{ganttDiagram-6RSMTGT7-BtuznCdi.js → ganttDiagram-6RSMTGT7-BevGmJqG.js} +1 -1
  163. package/web-shell/assets/{gitGraphDiagram-PVQCEYII-DGmAm06g.js → gitGraphDiagram-PVQCEYII-DvPMpBMb.js} +1 -1
  164. package/web-shell/assets/index-Cnvv4VcI.css +5 -0
  165. package/web-shell/assets/index-D1Uyklxa.js +1088 -0
  166. package/web-shell/assets/{index-BEtz1cvN.js → index-DzHwQkQq.js} +1 -1
  167. package/web-shell/assets/{infoDiagram-5YYISTIA-OAiZ1q0K.js → infoDiagram-5YYISTIA-Oixn7tA9.js} +1 -1
  168. package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-DyK0KTIT.js → ishikawaDiagram-YF4QCWOH-BgKpeg-j.js} +1 -1
  169. package/web-shell/assets/{journeyDiagram-JHISSGLW-DzYJlwfO.js → journeyDiagram-JHISSGLW-74mwxIaA.js} +1 -1
  170. package/web-shell/assets/{kanban-definition-UN3LZRKU-CU_tcDwi.js → kanban-definition-UN3LZRKU-D4D6cEqA.js} +1 -1
  171. package/web-shell/assets/{linear-DvZXjF17.js → linear-atZRE_L2.js} +1 -1
  172. package/web-shell/assets/{mermaid.core-_ZFEQlOl.js → mermaid.core-KKudX-JC.js} +5 -5
  173. package/web-shell/assets/{mindmap-definition-RKZ34NQL-Or4p64HB.js → mindmap-definition-RKZ34NQL-CMCej9yG.js} +1 -1
  174. package/web-shell/assets/{pieDiagram-4H26LBE5-lXE-zZ6b.js → pieDiagram-4H26LBE5-nhx40X7q.js} +1 -1
  175. package/web-shell/assets/{quadrantDiagram-W4KKPZXB-DUXb9mUV.js → quadrantDiagram-W4KKPZXB-CUmVcN3M.js} +1 -1
  176. package/web-shell/assets/{requirementDiagram-4Y6WPE33-D_10uHfo.js → requirementDiagram-4Y6WPE33-C_vyEwyy.js} +1 -1
  177. package/web-shell/assets/{sankeyDiagram-5OEKKPKP-E8c0fPqe.js → sankeyDiagram-5OEKKPKP-DU6G4VWR.js} +1 -1
  178. package/web-shell/assets/{sequenceDiagram-3UESZ5HK-CJMtFttK.js → sequenceDiagram-3UESZ5HK-DdeRR_KJ.js} +1 -1
  179. package/web-shell/assets/{stateDiagram-AJRCARHV-DhbviRbB.js → stateDiagram-AJRCARHV-C6iCI8ps.js} +1 -1
  180. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-C0_6nR6C.js +1 -0
  181. package/web-shell/assets/{timeline-definition-PNZ67QCA-CM5yMkFK.js → timeline-definition-PNZ67QCA-B_Ea8Cb7.js} +1 -1
  182. package/web-shell/assets/{vennDiagram-CIIHVFJN-BCi-zWNN.js → vennDiagram-CIIHVFJN-BPUVZEJA.js} +1 -1
  183. package/web-shell/assets/{wardley-L42UT6IY-BJ_yJNjJ.js → wardley-L42UT6IY-BWEj0lKY.js} +1 -1
  184. package/web-shell/assets/{wardleyDiagram-YWT4CUSO-TjtLk-qN.js → wardleyDiagram-YWT4CUSO-BvDsdIQw.js} +1 -1
  185. package/web-shell/assets/{xychartDiagram-2RQKCTM6-D1TjyoQ3.js → xychartDiagram-2RQKCTM6-crz0QhF_.js} +1 -1
  186. package/web-shell/index.html +2 -2
  187. package/chunks/chunk-CU3L64TP.js +0 -93
  188. package/chunks/types-JNKGKUJT.js +0 -12
  189. package/web-shell/assets/channel-DWfYijFS.js +0 -1
  190. package/web-shell/assets/classDiagram-4FO5ZUOK-DDht-78J.js +0 -1
  191. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-DDht-78J.js +0 -1
  192. package/web-shell/assets/index-B9Xwf4OS.css +0 -5
  193. package/web-shell/assets/index-BUkM4ZXX.js +0 -1088
  194. 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-B6ABUBYY.js";
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-K3PPUB46.js";
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-7U7UIO5W.js";
31
- import "./chunk-DKXW66XA.js";
32
- import "./chunk-YWUK7OS7.js";
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-4LJNI7MU.js";
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-EUNRLYJG.js";
91
- import "./chunk-DMTQY5GY.js";
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-CLTWL6HD.js";
99
+ } from "./chunk-7MJAZMXK.js";
96
100
  import {
97
101
  getAuthTypeFromEnv,
98
102
  resolveCliGenerationConfig
99
- } from "./chunk-CSACSGAW.js";
103
+ } from "./chunk-GGJII3FM.js";
100
104
  import {
101
105
  snapshotProcessEnv
102
- } from "./chunk-AAWKZ5R7.js";
106
+ } from "./chunk-ZKMGAFYV.js";
103
107
  import {
104
108
  createWorkspaceSkillsStatusProvider
105
- } from "./chunk-4FPUAYTT.js";
106
- import "./chunk-DPSWJWZI.js";
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-XHW5VUIQ.js";
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-S2QXYLUB.js";
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-C4CXFRKY.js";
136
+ } from "./chunk-FXMC7SL2.js";
133
137
  import {
134
138
  isVoiceEnabled,
135
139
  isVoiceMode,
136
140
  readVoiceModel
137
- } from "./chunk-N7FCSERB.js";
141
+ } from "./chunk-DPFBNG72.js";
138
142
  import "./chunk-FSA7ERJ2.js";
139
143
  import {
140
144
  WorkspacePermissionRulesSessionRequiredError,
141
- WorkspaceSettingsPartialPersistError
142
- } from "./chunk-WL4L7EZH.js";
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-MBP3K324.js";
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-GYGGUR4Q.js";
198
- import "./chunk-6HHAADP2.js";
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-4QOPEEL2.js";
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-PMRH4YYW.js";
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-J2TDDOSM.js";
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-T2L6SGQY.js";
254
+ } from "./chunk-6GETGQWP.js";
249
255
  import "./chunk-RKUWKYED.js";
250
256
  import {
251
257
  SUPPORTED_LANGUAGES
252
- } from "./chunk-SE74CIZD.js";
253
- import "./chunk-WN4D7YCI.js";
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-X7EBAABW.js";
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-UI3W56WF.js";
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-SSFDPGFN.js";
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-T7FSRWQ5.js";
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 registerWorkspaceVoiceRoutes(app, deps) {
3328
- const transcribe = deps.transcribe ?? transcribeWorkspaceVoiceAudio;
3329
- app.get("/workspace/voice", (_req, res) => {
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
- res.status(200).json(
3332
- buildWorkspaceVoiceStatus(
3333
- deps.boundWorkspace,
3334
- loadSettings(deps.boundWorkspace)
3335
- )
3336
- );
3337
- } catch (err) {
3338
- writeStderrLine(
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
- if (!deps.persistSettings && !deps.persistSetting) {
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
- import_express.default.raw({
3403
- type: /* @__PURE__ */ __name((req) => isSupportedAudioContentType(
3404
- req.headers["content-type"]?.split(";", 1)[0]?.trim().toLowerCase()
3405
- ), "type"),
3406
- limit: "10mb"
3407
- }),
3637
+ (req, res, next) => {
3638
+ if (admitVoiceTranscription(req, res, deps)) next();
3639
+ },
3640
+ voiceAudioBodyParser(),
3408
3641
  async (req, res) => {
3409
- const contentType = normalizeContentType(req);
3410
- if (!isSupportedAudioContentType(contentType)) {
3411
- res.status(415).json({
3412
- error: "Content-Type must be audio/* or application/octet-stream for voice transcription",
3413
- code: "unsupported_voice_content_type"
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 PLURAL_ACP_WS_PREFIX = "/workspaces/";
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 pluralAcpRawSelector(rawPath) {
11711
+ function pluralWorkspaceRawSelector(rawPath, suffix) {
11470
11712
  let p = rawPath;
11471
- if (p.endsWith(`${PLURAL_ACP_WS_SUFFIX}/`)) {
11713
+ if (p.endsWith(`${suffix}/`)) {
11472
11714
  p = p.slice(0, -1);
11473
11715
  }
11474
- if (!p.startsWith(PLURAL_ACP_WS_PREFIX) || !p.endsWith(PLURAL_ACP_WS_SUFFIX) || p.length <= PLURAL_ACP_WS_PREFIX.length + PLURAL_ACP_WS_SUFFIX.length) {
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(pluralAcpRawSelector, "pluralAcpRawSelector");
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 ? pluralAcpRawSelector(rawPath) : null;
12458
+ const pluralRawSelector = workspaceQualifiedAcpEnabled ? pluralWorkspaceRawSelector(rawPath, PLURAL_ACP_WS_SUFFIX) : null;
12220
12459
  const isPluralAcpShape = pluralRawSelector !== null;
12221
- if (rawPath !== path15 && !isCdpPath && !extraRoute && !isPluralAcpShape) {
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) ?? resolveManagedWorkspaceRuntimeByPathSelector(wsRegistry, selector) : void 0;
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
- return (ws) => {
13289
+ const acquireLocalLease = /* @__PURE__ */ __name(() => {
13005
13290
  if (activeSessions >= MAX_CONCURRENT_VOICE_SESSIONS) {
13006
- writeStderrLine(
13007
- `qwen serve: voice websocket rejected; activeSessions=${activeSessions}`
13008
- );
13009
- try {
13010
- ws.send(
13011
- JSON.stringify({
13012
- type: "error",
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
- activeSessions++;
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
- activeSessions--;
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(ctx, Buffer.concat(pcmChunks));
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
- function registerWorkspaceExtensionRoutes(app, deps) {
14632
- const {
14633
- boundWorkspace,
14634
- bridge,
14635
- workspace,
14636
- mutate,
14637
- safeBody: safeBody2,
14638
- sendBridgeError: sendBridgeError2
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 buildWorkspaceCtx = createBuildWorkspaceCtx(boundWorkspace);
14642
- let extensionInstallQueue = Promise.resolve();
14643
- let extensionInstallQueueDepth = 0;
14644
- const MAX_EXTENSION_INSTALL_QUEUE_DEPTH = 10;
14645
- const enqueueExtensionInstall = /* @__PURE__ */ __name(async (run) => {
14646
- if (extensionInstallQueueDepth >= MAX_EXTENSION_INSTALL_QUEUE_DEPTH) {
14647
- throw new Error("Extension operation queue is full");
14648
- }
14649
- extensionInstallQueueDepth += 1;
14650
- const next = extensionInstallQueue.then(run, run).finally(() => {
14651
- extensionInstallQueueDepth -= 1;
14652
- });
14653
- extensionInstallQueue = next.catch(() => void 0);
14654
- return next;
14655
- }, "enqueueExtensionInstall");
14656
- const EXTENSION_MUTATION_TIMEOUT_MS = 10 * 6e4;
14657
- const EXTENSION_REFRESH_TIMEOUT_MS = 3e4;
14658
- const isExtensionQueueFullError = /* @__PURE__ */ __name((err) => err instanceof Error && err.message === "Extension operation queue is full", "isExtensionQueueFullError");
14659
- const sendExtensionQueueFull = /* @__PURE__ */ __name((res) => {
14660
- res.status(429).json({
14661
- error: "Extension operation queue is full",
14662
- code: "extension_queue_full"
14663
- });
14664
- }, "sendExtensionQueueFull");
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, route, opts = {}) => {
14699
- const clientId = parseAndValidateWorkspaceClientId(req, res, bridge);
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 rememberExtensionOperation = /* @__PURE__ */ __name((operation) => {
14798
- extensionOperations.set(operation.operationId, operation);
14799
- while (extensionOperations.size > maxExtensionOperationHistory) {
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
- const runQueuedExtensionMutation = /* @__PURE__ */ __name((operation, failureContext, res, run) => {
14820
- if (extensionInstallQueueDepth >= MAX_EXTENSION_INSTALL_QUEUE_DEPTH) {
14821
- sendExtensionQueueFull(res);
14822
- return;
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
- res.status(202).json({ accepted: true, operationId });
14837
- void enqueueExtensionInstall(async () => {
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, { status: "running" });
14840
- const extensionManager = createExtensionManager();
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 withExtensionTimeout(
14843
- run(extensionManager),
14844
- EXTENSION_MUTATION_TIMEOUT_MS,
14845
- `extension ${operation}`
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
- workspace.invalidateWorkspaceSkillsStatus();
14849
- try {
14850
- const result = await bridge.refreshExtensionsForAllSessions(event);
15310
+ if (options.skipRefresh || event.updated === false) {
15311
+ reconciliationReservation?.release();
15312
+ reconciliationReservation = void 0;
14851
15313
  updateExtensionOperation(operationId, {
14852
- status: "succeeded",
14853
- result: {
14854
- ...redactExtensionOperationResult(event),
14855
- refreshed: result.refreshed,
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
- writeStderrLine(
14860
- `qwen serve: extensions ${operation}: refreshed ${result.refreshed} session(s), ${result.failed} failed`
14861
- );
14862
- } catch (refreshErr) {
14863
- const message = redactUrlCredentials(
14864
- refreshErr instanceof Error ? refreshErr.message : String(refreshErr)
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: "succeeded_with_refresh_error",
15404
+ status: warnings.length > 0 ? "succeeded_with_warnings" : "succeeded",
15405
+ phase: void 0,
14868
15406
  result: {
14869
15407
  ...redactExtensionOperationResult(event),
14870
- refreshed: 0,
14871
- failed: 1,
14872
- error: message.slice(0, 500)
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
- ...event,
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: message.slice(0, 500)
15536
+ error: error2
14881
15537
  });
14882
- } catch (broadcastErr) {
15538
+ } catch {
15539
+ }
15540
+ try {
14883
15541
  writeStderrLine(
14884
- `qwen serve: extensions ${operation}: failed to broadcast refresh failure: ${broadcastErr instanceof Error ? redactUrlCredentials(broadcastErr.message) : String(broadcastErr)}`
15542
+ `qwen serve: [${boundWorkspace}] extensions ${operation}: ${error2}`
14885
15543
  );
15544
+ } catch {
14886
15545
  }
14887
- writeStderrLine(
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
- error: message.slice(0, 500)
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: ${broadcastErr instanceof Error ? redactUrlCredentials(broadcastErr.message) : String(broadcastErr)}`
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
- }).catch((err) => {
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
- app.get("/workspace/extensions", async (_req, res) => {
14997
- try {
14998
- buildWorkspaceCtx("GET /workspace/extensions");
14999
- res.status(200).json(await buildLocalExtensionsStatus());
15000
- } catch (err) {
15001
- sendBridgeError2(res, err, { route: "GET /workspace/extensions" });
15002
- }
15003
- });
15004
- app.get("/workspace/extensions/operations/:operationId", async (req, res) => {
15005
- try {
15006
- buildWorkspaceCtx("GET /workspace/extensions/operations/:operationId");
15007
- const operationId = req.params["operationId"];
15008
- if (!operationId) {
15009
- res.status(400).json({ error: "Missing extension operation id" });
15010
- return;
15011
- }
15012
- const operation = extensionOperations.get(operationId);
15013
- if (!operation) {
15014
- res.status(404).json({
15015
- error: `Extension operation "${operationId}" not found`,
15016
- code: "extension_operation_not_found"
15017
- });
15018
- return;
15019
- }
15020
- res.status(200).json(operation);
15021
- } catch (err) {
15022
- sendBridgeError2(res, err, {
15023
- route: "GET /workspace/extensions/operations/:operationId"
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
- app.post(
15028
- "/workspace/extensions/install",
15029
- mutate({ strict: true }),
15030
- async (req, res) => {
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
- if (!validateExtensionMutationClient(
15033
- req,
15034
- res,
15035
- "POST /workspace/extensions/install"
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 installMetadata = await parseInstallSource(sourceValue);
15092
- if (installMetadata.type !== "git" && installMetadata.type !== "github-release" && installMetadata.type !== "npm") {
15093
- throw new Error(
15094
- "Only GitHub, Git, and npm extension installs are supported over the daemon endpoint."
15095
- );
15096
- }
15097
- if (installMetadata.type === "npm" && refValue) {
15098
- throw new Error("--ref is not applicable for npm extensions.");
15099
- }
15100
- if (installMetadata.type !== "npm" && registryValue) {
15101
- throw new Error(
15102
- "--registry is only applicable for npm extensions."
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
- if (!validateExtensionSourceMetadata(installMetadata)) {
15106
- throw new Error("`source` host is not allowed");
15107
- }
15108
- if (installMetadata.type === "npm" && registryUrl) {
15109
- installMetadata.registryUrl = registryUrl;
15110
- }
15111
- const extension = await extensionManager.installExtension(
15112
- {
15113
- ...installMetadata,
15114
- ref: refValue,
15115
- autoUpdate: autoUpdateValue,
15116
- allowPreRelease: allowPreReleaseValue
15117
- },
15118
- () => Promise.resolve()
15119
- );
15120
- return {
15121
- status: "installed",
15122
- source: sourceValue,
15123
- name: extension.name,
15124
- version: extension.config.version
15125
- };
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
- app.post(
15136
- "/workspace/extensions/check-updates",
15137
- mutate({ strict: true }),
15138
- async (req, res) => {
15139
- try {
15140
- if (!validateExtensionMutationClient(
15141
- req,
15142
- res,
15143
- "POST /workspace/extensions/check-updates"
15144
- )) {
15145
- return;
15146
- }
15147
- const states = await enqueueExtensionInstall(
15148
- async () => withExtensionTimeout(
15149
- (async () => {
15150
- const extensionManager = createExtensionManager();
15151
- await extensionManager.refreshCache();
15152
- const updateStates = {};
15153
- await extensionManager.checkForAllExtensionUpdates(
15154
- (name, state) => {
15155
- updateStates[name] = state;
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
- return updateStates;
15159
- })(),
15160
- EXTENSION_REFRESH_TIMEOUT_MS,
15161
- "extension update check"
15162
- )
15163
- );
15164
- res.status(200).json({ states });
15165
- } catch (err) {
15166
- if (isExtensionQueueFullError(err)) {
15167
- sendExtensionQueueFull(res);
15168
- return;
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
- app.post(
15177
- "/workspace/extensions/refresh",
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 result = await enqueueExtensionInstall(
15189
- async () => withExtensionTimeout(
15190
- workspace.refreshExtensionsForAllSessions(),
15191
- EXTENSION_REFRESH_TIMEOUT_MS,
15192
- "extension refresh"
15193
- )
15194
- );
15195
- extensionsStatusCache = void 0;
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
- if (isExtensionQueueFullError(err)) {
15199
- sendExtensionQueueFull(res);
15200
- return;
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
- app.post(
15209
- "/workspace/extensions/:name/enable",
15210
- mutate({ strict: true }),
15211
- async (req, res) => {
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
- const scope = parseExtensionScope(safeBody2(req), res);
15227
- if (scope === null) return;
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 extensionManager.enableExtension(
15238
- extension.name,
15239
- scope,
15240
- boundWorkspace
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: "enabled", name: extension.name };
16321
+ return { status: "uninstalled", name: extension.name };
16322
+ },
16323
+ {
16324
+ ...globalReconciliationOptions()
15243
16325
  }
15244
16326
  );
15245
16327
  } catch (err) {
15246
- sendBridgeError2(res, err, {
15247
- route: "POST /workspace/extensions/:name/enable"
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
- "/workspace/extensions/:name/disable",
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
- if (!validateExtensionMutationClient(
15258
- req,
15259
- res,
15260
- "POST /workspace/extensions/:name/disable",
15261
- { requireClientId: false }
15262
- )) {
15263
- return;
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
- const scope = parseExtensionScope(safeBody2(req), res);
15271
- if (scope === null) return;
15272
- runQueuedExtensionMutation(
15273
- "disable",
15274
- { name },
16725
+ sendOperation(
16726
+ req,
15275
16727
  res,
15276
- async (extensionManager) => {
15277
- const extension = findLoadedExtension(extensionManager, name);
15278
- if (!extension) {
15279
- throw new Error(`Extension "${name}" not found`);
15280
- }
15281
- await extensionManager.disableExtension(
15282
- extension.name,
15283
- scope,
15284
- boundWorkspace
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: "disabled", name: extension.name };
16741
+ return { status: "uninstalled", name: policy.name };
16742
+ },
16743
+ {
16744
+ ...workspaceRegistry ? { refreshRuntimes: /* @__PURE__ */ __name(() => workspaceRegistry.list(), "refreshRuntimes") } : {}
15287
16745
  }
15288
16746
  );
15289
- } catch (err) {
15290
- sendBridgeError2(res, err, {
15291
- route: "POST /workspace/extensions/:name/disable"
15292
- });
16747
+ } catch (error2) {
16748
+ sendBridgeError2(res, error2, { route });
15293
16749
  }
15294
16750
  }
15295
16751
  );
15296
- app.post(
15297
- "/workspace/extensions/:name/update",
15298
- mutate({ strict: true }),
15299
- async (req, res) => {
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
- if (!validateExtensionMutationClient(
15302
- req,
15303
- res,
15304
- "POST /workspace/extensions/:name/update"
15305
- )) {
15306
- return;
15307
- }
15308
- const name = req.params["name"];
15309
- if (!name) {
15310
- res.status(400).json({ error: "Missing extension name" });
15311
- return;
15312
- }
15313
- runQueuedExtensionMutation(
15314
- "update",
15315
- { name },
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
- async (extensionManager) => {
15318
- const extension = findLoadedExtension(extensionManager, name);
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 "${name}" not found`);
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
- if (updateState !== "update available" /* UPDATE_AVAILABLE */) {
15342
- throw new Error(`Extension "${extension.name}" has no update`);
15343
- }
15344
- const info = await extensionManager.updateExtension(
15345
- extension,
15346
- updateState,
15347
- () => void 0
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: "updated",
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
- app.delete(
15364
- "/workspace/extensions/:name",
15365
- mutate({ strict: true }),
15366
- async (req, res) => {
15367
- try {
15368
- if (!validateExtensionMutationClient(
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/:name"
15372
- )) {
15373
- return;
15374
- }
15375
- const name = req.params["name"];
15376
- if (!name) {
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 "${name}" not found`);
16859
+ throw new Error(`Extension "${extensionId}" not found`);
15388
16860
  }
15389
- await extensionManager.uninstallExtension(
15390
- extension.name,
15391
- false,
15392
- boundWorkspace
16861
+ const snapshot = await context.commit(
16862
+ async (onCommitted) => await extensionManager.clearExtensionWorkspaceActivation(
16863
+ extensionId,
16864
+ runtime.workspaceCwd,
16865
+ onCommitted
16866
+ )
15393
16867
  );
15394
- return { status: "uninstalled", name: extension.name };
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
- const sessionId = requireSessionId(req, res);
17451
- if (sessionId === null) return;
17452
- const rawFormat = req.query["format"];
17453
- const format = parseSessionExportFormat(rawFormat);
17454
- if (!format) {
17455
- res.status(400).json({
17456
- error: "Invalid export format",
17457
- code: "invalid_export_format",
17458
- format: typeof rawFormat === "string" ? rawFormat : String(rawFormat),
17459
- allowedFormats: sessionExportFormatValues()
17460
- });
17461
- return;
17462
- }
17463
- try {
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(boundWorkspace);
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
- ...multiWorkspace ? {
20348
- workspaces: runtimes.map((runtime) => ({
20349
- id: runtime.workspaceId,
20350
- cwd: runtime.workspaceCwd,
20351
- primary: runtime.primary,
20352
- trusted: runtime.trusted,
20353
- ...runtimeRemoval ? { removable: runtime.removable === true } : {}
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
- return metadata?.effectiveEnv;
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;