@qwen-code/qwen-code 0.19.9-nightly.20260714.9dd8389eb → 0.19.9-preview.0

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