@qwen-code/qwen-code 0.19.6 → 0.19.7-nightly.20260708.394c1a289

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