@qwen-code/qwen-code 0.19.3-nightly.20260701.a974594d7 → 0.19.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/bundled/loop/loop-tick-resolver.test.ts +42 -0
  2. package/bundled/loop/loop-tick-resolver.ts +1 -1
  3. package/bundled/qc-helper/docs/configuration/auth.md +1 -1
  4. package/bundled/qc-helper/docs/configuration/settings.md +23 -11
  5. package/bundled/qc-helper/docs/features/approval-mode.md +3 -0
  6. package/bundled/qc-helper/docs/features/auto-mode.md +40 -0
  7. package/bundled/qc-helper/docs/features/channels/overview.md +81 -18
  8. package/bundled/qc-helper/docs/features/code-review.md +17 -61
  9. package/bundled/qc-helper/docs/features/commands.md +6 -6
  10. package/bundled/qc-helper/docs/features/mcp.md +3 -1
  11. package/bundled/qc-helper/docs/features/scheduled-tasks.md +10 -0
  12. package/bundled/qc-helper/docs/qwen-serve.md +19 -0
  13. package/bundled/review/DESIGN.md +21 -26
  14. package/bundled/review/SKILL.md +45 -129
  15. package/chunks/{MaxSizedBox-UZHJ73JB.js → MaxSizedBox-ERTNFI22.js} +20 -18
  16. package/chunks/{StandaloneSessionPicker-GGUIZWCE.js → StandaloneSessionPicker-Y5NGRLRY.js} +32 -29
  17. package/chunks/{acpAgent-QYZFICEZ.js → acpAgent-UMN6OYTR.js} +473 -187
  18. package/chunks/{agent-2EN7YNSW.js → agent-56DZU4ZU.js} +17 -15
  19. package/chunks/{agent-headless-SLTEZ5LV.js → agent-headless-SR7N2IJG.js} +17 -15
  20. package/chunks/{anthropicContentGenerator-CA3JTAMO.js → anthropicContentGenerator-5OZFO6WQ.js} +1 -1
  21. package/chunks/{artifact-tool-RM7WINJO.js → artifact-tool-JVZMHA4B.js} +1 -1
  22. package/chunks/{askUserQuestion-J3P6QAJ7.js → askUserQuestion-CMJZ2F3D.js} +1 -1
  23. package/chunks/{bridge-HZVAUTMX.js → bridge-AUHNFOUS.js} +23 -21
  24. package/chunks/channel-worker-supervisor-A74FV522.js +308 -0
  25. package/chunks/{chunk-N4V4VBP6.js → chunk-2X2JUE47.js} +2 -2
  26. package/chunks/{chunk-JV7A66DR.js → chunk-4VZH35PN.js} +528 -358
  27. package/chunks/{chunk-M2U5NM24.js → chunk-54YZOP7P.js} +309 -72
  28. package/chunks/{chunk-WGY3GIPP.js → chunk-6AQR7732.js} +1 -1
  29. package/chunks/{chunk-FR3II765.js → chunk-6CIIP6OE.js} +4 -4
  30. package/chunks/{chunk-IKO44NAT.js → chunk-6TLY5W3B.js} +2 -2
  31. package/chunks/{chunk-VN4CKYUW.js → chunk-73H73ASU.js} +1 -1
  32. package/chunks/{chunk-IBEM64M4.js → chunk-75X2VLSH.js} +36 -25
  33. package/chunks/{chunk-PLQTLKMR.js → chunk-7KPI4LAI.js} +3 -3
  34. package/chunks/{chunk-NVTRORSK.js → chunk-7VPC7D7O.js} +2 -2
  35. package/chunks/chunk-AHNXYU4O.js +24 -0
  36. package/chunks/{chunk-MTJVHQDP.js → chunk-B7KGXIPZ.js} +302 -257
  37. package/chunks/{chunk-35FH347N.js → chunk-BFG6OZN7.js} +16097 -16236
  38. package/chunks/{chunk-T44AF4I6.js → chunk-BNJLLAQ7.js} +1 -1
  39. package/chunks/{chunk-OEY3YAL2.js → chunk-BQNJBEUA.js} +2 -2
  40. package/chunks/{chunk-Q7TDHJ2G.js → chunk-C2IHHGBZ.js} +10 -10
  41. package/chunks/chunk-CG7SVARK.js +367 -0
  42. package/chunks/{chunk-7SP275SH.js → chunk-CS5XYDBO.js} +5 -5
  43. package/chunks/{chunk-V5YJAUHW.js → chunk-CUGEBAKG.js} +1 -1
  44. package/chunks/{chunk-FQDMX5IL.js → chunk-D5KISTLW.js} +309 -72
  45. package/chunks/{chunk-I4MXURND.js → chunk-DQ74W65K.js} +356 -468
  46. package/chunks/chunk-E5A7LHNN.js +51 -0
  47. package/chunks/{chunk-3PJXIDKI.js → chunk-E6US47KI.js} +3 -27
  48. package/chunks/chunk-EDAGQADF.js +21 -0
  49. package/chunks/{chunk-HUGAEYZR.js → chunk-FNLXLI4I.js} +2 -2
  50. package/chunks/{chunk-WJAY4WXR.js → chunk-FYKX6XOT.js} +1 -1
  51. package/chunks/{chunk-CA4ML3VH.js → chunk-G7OQ3RFJ.js} +1 -1
  52. package/chunks/{chunk-VHRREANT.js → chunk-GICCHL2Q.js} +3 -1
  53. package/chunks/{chunk-QHHNFG22.js → chunk-GLPV75ES.js} +1 -1
  54. package/chunks/{chunk-LD2XBG6Z.js → chunk-GO6LNQXT.js} +1 -39
  55. package/chunks/chunk-HI55Z6PR.js +3523 -0
  56. package/chunks/{chunk-B5MPZI4K.js → chunk-HJDF3ZVU.js} +2670 -1233
  57. package/chunks/{chunk-GDIG7TMG.js → chunk-HRZB5CSC.js} +1 -1
  58. package/chunks/{chunk-LDWTYE33.js → chunk-J3F7X3LW.js} +1 -1
  59. package/chunks/{chunk-FV4SQ54H.js → chunk-KVPFPC4J.js} +227 -85
  60. package/chunks/{chunk-Q3JOGB4U.js → chunk-L7QPM2YD.js} +1 -1
  61. package/chunks/{chunk-4722VFFU.js → chunk-L7VH7HQA.js} +6 -6
  62. package/chunks/{chunk-HWG4XLD7.js → chunk-LKWAVAB5.js} +1 -1
  63. package/chunks/{chunk-YCRTJYSQ.js → chunk-MEQWZGK5.js} +1 -1
  64. package/chunks/{chunk-6FIWYVN4.js → chunk-MKHC4BYZ.js} +30 -5
  65. package/chunks/{chunk-YCEHOJZJ.js → chunk-MNDTD5E5.js} +65 -38
  66. package/chunks/{chunk-ZGSV5ORL.js → chunk-MVZXZWGT.js} +809 -759
  67. package/chunks/chunk-MX2YXRER.js +44 -0
  68. package/chunks/{chunk-TMILTLP3.js → chunk-ND25GXEU.js} +21 -4
  69. package/chunks/{chunk-AIBPG5MB.js → chunk-OAFUMTWW.js} +2 -2
  70. package/chunks/chunk-OJCECIJA.js +238 -0
  71. package/chunks/{chunk-EUURTJY7.js → chunk-PIYV6COW.js} +1 -1
  72. package/chunks/{chunk-LXEW6BHF.js → chunk-Q7VHIRDD.js} +1 -1
  73. package/chunks/{chunk-DBR7PF4B.js → chunk-QAFQLTXG.js} +1 -1
  74. package/chunks/{chunk-3TK7FSCP.js → chunk-QMIND2AS.js} +3 -3
  75. package/chunks/{chunk-ESVSWG6Z.js → chunk-QOMAHFYI.js} +1 -1
  76. package/chunks/{chunk-J37FGIOA.js → chunk-QXPI7FEC.js} +38 -4
  77. package/chunks/chunk-RM3PGSGL.js +66 -0
  78. package/chunks/chunk-RNBYOUGV.js +42 -0
  79. package/chunks/chunk-S5EQPDTM.js +91 -0
  80. package/chunks/{chunk-GZKQ36JS.js → chunk-SFN4TEHY.js} +4 -4
  81. package/chunks/{chunk-DHXWNSJA.js → chunk-T22H4V4R.js} +42 -0
  82. package/chunks/{chunk-LXGWLOCM.js → chunk-TENOUWY2.js} +1 -1
  83. package/chunks/{chunk-BLAE2TCO.js → chunk-TTFNVEO5.js} +3 -3
  84. package/chunks/{chunk-FZY7NH37.js → chunk-UANCYOOQ.js} +1 -1
  85. package/chunks/{chunk-E5I6YSNO.js → chunk-UQFP2FEF.js} +1 -1
  86. package/chunks/{chunk-3O2BJMFW.js → chunk-UQYOMM6J.js} +4 -4
  87. package/chunks/{chunk-JERW36RG.js → chunk-UT5JU5DJ.js} +15 -15
  88. package/chunks/{chunk-DJ25MIU2.js → chunk-VKGG724L.js} +8 -8
  89. package/chunks/{chunk-G525INVW.js → chunk-W2ZOAX7F.js} +3 -3
  90. package/chunks/{chunk-HIYUAXBG.js → chunk-W6X4FGWX.js} +1 -1
  91. package/chunks/{chunk-OE5J6UA7.js → chunk-XCUFHOAD.js} +1 -1
  92. package/chunks/{chunk-KXVVXAAC.js → chunk-YBDZ63KI.js} +3 -3
  93. package/chunks/{chunk-Z34RYLRO.js → chunk-ZJP5T7IA.js} +1 -1
  94. package/chunks/{chunk-NOYV2NXI.js → chunk-ZXCVPMIK.js} +411 -43
  95. package/chunks/{computer-use-FCNWHVPW.js → computer-use-VWN5HH6A.js} +20 -18
  96. package/chunks/{contextCommand-FVFMZZEU.js → contextCommand-DFFG5KDJ.js} +19 -17
  97. package/chunks/{cron-create-IY5NMIHQ.js → cron-create-CSSWJ67I.js} +1 -1
  98. package/chunks/{cron-delete-OLNEQWKO.js → cron-delete-E56CFQ7A.js} +1 -1
  99. package/chunks/{cron-list-YAUAUV24.js → cron-list-BKSINY6Y.js} +1 -1
  100. package/chunks/daemon-5T6IHWJV.js +8430 -0
  101. package/chunks/{daemon-status-provider-42JEXENZ.js → daemon-status-provider-V5UY7O7J.js} +26 -24
  102. package/chunks/{dist-4FCNM6KJ.js → dist-2HDL5SHQ.js} +2 -2
  103. package/chunks/{dist-SU6L7ZNJ.js → dist-B7SSDVWT.js} +3 -3
  104. package/chunks/{dist-VD7HXTML.js → dist-BL3SL665.js} +1 -1
  105. package/chunks/{dist-UKPWQJCL.js → dist-TAVAGZU6.js} +37 -13
  106. package/chunks/{dist-K4XZILIO.js → dist-XBOVZ3R5.js} +26 -5
  107. package/chunks/{earlyInputCapture-FFFEC6P4.js → earlyInputCapture-VVYU4FDO.js} +17 -15
  108. package/chunks/{edit-32ILSGZY.js → edit-F35PTMBA.js} +17 -15
  109. package/chunks/{en-U3LWGSFX.js → en-FZREZFPO.js} +18 -0
  110. package/chunks/{enter-worktree-XWZKIN6A.js → enter-worktree-3DIW7EGJ.js} +17 -15
  111. package/chunks/{enterPlanMode-LPFFY7BJ.js → enterPlanMode-QNXT77KP.js} +27 -15
  112. package/chunks/{errors-UXBPYPI2.js → errors-M53YG76R.js} +19 -17
  113. package/chunks/{exit-worktree-IBS4UIU7.js → exit-worktree-BSDWNWHZ.js} +17 -15
  114. package/chunks/{exitPlanMode-JA72YHII.js → exitPlanMode-EL7AY327.js} +41 -23
  115. package/chunks/{fast-path-7UU67Y2W.js → fast-path-DUCSMUFW.js} +2 -2
  116. package/chunks/{gemini-GMVKJNMX.js → gemini-F3IERB37.js} +59 -51
  117. package/chunks/{geminiContentGenerator-7NIFF2L4.js → geminiContentGenerator-6OULAKFQ.js} +1 -1
  118. package/chunks/{glob-4NJCW5HI.js → glob-ZMLFTD4T.js} +17 -15
  119. package/chunks/{grep-PAIMLQEL.js → grep-H5X66EI7.js} +17 -15
  120. package/chunks/{initializer-R6KU4VYM.js → initializer-LEOT2OLH.js} +21 -19
  121. package/chunks/{list-H7LXAO64.js → list-M3DKXML6.js} +23 -21
  122. package/chunks/{loadedSettingsAdapter-H27JMTLU.js → loadedSettingsAdapter-OKMQFGGG.js} +21 -19
  123. package/chunks/{loop-wakeup-Y3OIRQFM.js → loop-wakeup-FPBAVVPR.js} +2 -2
  124. package/chunks/{ls-TVJEYS7O.js → ls-UGWQXPTY.js} +1 -1
  125. package/chunks/{lsp-JZSDVR3F.js → lsp-24WV7NXB.js} +1 -1
  126. package/chunks/{monitor-JSIHDPXP.js → monitor-ARL5P45R.js} +19 -16
  127. package/chunks/{nonInteractiveCli-Y4CHOETG.js → nonInteractiveCli-NUVLF3YR.js} +53 -45
  128. package/chunks/{notebook-edit-SHRNL2UM.js → notebook-edit-ANTZ2DDY.js} +17 -15
  129. package/chunks/{openaiContentGenerator-ORZZ7VFO.js → openaiContentGenerator-TFGMKGHN.js} +6 -6
  130. package/chunks/pidfile-AWTL4C63.js +72 -0
  131. package/chunks/{qwenContentGenerator-H643PL2G.js → qwenContentGenerator-6QWAYTWS.js} +18 -16
  132. package/chunks/{qwenOAuth2-OI2B7PES.js → qwenOAuth2-LWLA6MQG.js} +3 -3
  133. package/chunks/{read-file-5PRNPTTY.js → read-file-EJY73XQ3.js} +4 -4
  134. package/chunks/{read-mcp-resource-QTXGKTEV.js → read-mcp-resource-NCBTPP7X.js} +1 -1
  135. package/chunks/{ripGrep-JIKZMABJ.js → ripGrep-HC4SA6CQ.js} +17 -15
  136. package/chunks/{run-qwen-serve-PQV7QOHZ.js → run-qwen-serve-AGLZ642J.js} +291 -55
  137. package/chunks/{scheduler-OS3R5MGU.js → scheduler-TURO7NGY.js} +17 -15
  138. package/chunks/{send-message-DKGHWSTF.js → send-message-3S2NYS3O.js} +5 -3
  139. package/chunks/{serve-LVYZDKCZ.js → serve-5DAMOIDW.js} +22 -19
  140. package/chunks/{server-P4YIH35I.js → server-4AGEK2HC.js} +2268 -1222
  141. package/chunks/{session-DE4TZBKH.js → session-7ZOQUC4Y.js} +54 -46
  142. package/chunks/{settings-DFTYMJYF.js → settings-DR4V6L5Y.js} +20 -18
  143. package/chunks/{shell-3GNQLNV7.js → shell-H5WFKUXH.js} +17 -15
  144. package/chunks/{skill-NCNG4KKZ.js → skill-ANHU5ZCR.js} +4 -4
  145. package/chunks/{spawnChannel-DHRIMDVL.js → spawnChannel-VN5LJV7Z.js} +19 -17
  146. package/chunks/{src-PNZYGM3R.js → src-OVZLTVVM.js} +49 -17
  147. package/chunks/{startInteractiveUI-P6GZBFQW.js → startInteractiveUI-OA22V32P.js} +11110 -10670
  148. package/chunks/{syntheticOutput-X7L5HWLN.js → syntheticOutput-SV7GBHZ5.js} +2 -2
  149. package/chunks/{task-create-AWEUI4GI.js → task-create-W2H2UJCH.js} +6 -5
  150. package/chunks/{task-list-KZ3VJLDU.js → task-list-THBOPV4G.js} +5 -4
  151. package/chunks/{task-stop-GUNOZKJ5.js → task-stop-33FVCNCJ.js} +1 -1
  152. package/chunks/{task-update-Y56GP6DU.js → task-update-LVFQLXEI.js} +6 -5
  153. package/chunks/{team-create-6JMW3KQK.js → team-create-OYL5CAQO.js} +17 -15
  154. package/chunks/{team-delete-6AFRGDJY.js → team-delete-NTUQ25UY.js} +5 -4
  155. package/chunks/{theme-manager-FEHQO22G.js → theme-manager-XCMPF2SI.js} +17 -15
  156. package/chunks/{todoWrite-JRS6NOEU.js → todoWrite-SEPO2RI3.js} +1 -1
  157. package/chunks/{tool-search-IXAEKS7J.js → tool-search-YXW55X6Z.js} +29 -5
  158. package/chunks/{trustedFolders-SJRXNHZE.js → trustedFolders-TEPGM5YL.js} +18 -16
  159. package/chunks/{validateNonInterActiveAuth-HCH7OBDH.js → validateNonInterActiveAuth-OQIS7WUE.js} +50 -42
  160. package/chunks/{web-fetch-RELNL37A.js → web-fetch-OE7LHURA.js} +4 -4
  161. package/chunks/{workflow-M2COBI7R.js → workflow-RTUWORAP.js} +48 -27
  162. package/chunks/{workspace-providers-status-GOCWYDMK.js → workspace-providers-status-DM2XANVB.js} +23 -21
  163. package/chunks/{workspace-service-34C7SUBU.js → workspace-service-VKBI2KZD.js} +27 -25
  164. package/chunks/{write-file-E52WRXZL.js → write-file-WGALQ53L.js} +17 -15
  165. package/chunks/{zh-TW-LKLBTZHE.js → zh-TW-6GIKXEAN.js} +18 -0
  166. package/chunks/{zh-F5T4WWZO.js → zh-V3B4TFWL.js} +18 -0
  167. package/cli.js +6 -6
  168. package/locales/en.js +30 -0
  169. package/locales/fr.js +1 -2
  170. package/locales/ja.js +1 -2
  171. package/locales/zh-TW.js +29 -0
  172. package/locales/zh.js +29 -0
  173. package/node_modules/@qwen-code/audio-capture/package.json +1 -1
  174. package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-arm64/@qwen-code+audio-capture.node +0 -0
  175. package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-x64/@qwen-code+audio-capture.node +0 -0
  176. package/node_modules/@qwen-code/audio-capture/prebuilds/win32-x64/@qwen-code+audio-capture.node +0 -0
  177. package/package.json +3 -3
  178. package/web-shell/assets/{arc-C2DILvUl.js → arc-Det5Ccum.js} +1 -1
  179. package/web-shell/assets/{architectureDiagram-3BPJPVTR-b8gZ2cNw.js → architectureDiagram-3BPJPVTR-DqNWVFpl.js} +1 -1
  180. package/web-shell/assets/{blockDiagram-GPEHLZMM-BuRpghMj.js → blockDiagram-GPEHLZMM-CYl7tYBi.js} +1 -1
  181. package/web-shell/assets/{c4Diagram-AAUBKEIU-2ItdhFmE.js → c4Diagram-AAUBKEIU-DaAEi0NQ.js} +1 -1
  182. package/web-shell/assets/channel-Bm0bFauD.js +1 -0
  183. package/web-shell/assets/{chunk-2J33WTMH-B1QXa0q3.js → chunk-2J33WTMH-Df2CDEE2.js} +1 -1
  184. package/web-shell/assets/{chunk-4BX2VUAB-DXfhKlNq.js → chunk-4BX2VUAB-DNBFcyL2.js} +1 -1
  185. package/web-shell/assets/{chunk-55IACEB6-ClSNjxLo.js → chunk-55IACEB6-BKjF-0HI.js} +1 -1
  186. package/web-shell/assets/{chunk-727SXJPM-Dsh2wisB.js → chunk-727SXJPM-DYwAwfIM.js} +1 -1
  187. package/web-shell/assets/{chunk-AQP2D5EJ-C1TkY5AR.js → chunk-AQP2D5EJ-C6FswLqy.js} +1 -1
  188. package/web-shell/assets/{chunk-FMBD7UC4-CGtN9XR6.js → chunk-FMBD7UC4-DRWAcZSY.js} +1 -1
  189. package/web-shell/assets/{chunk-ND2GUHAM-BHiPi1SS.js → chunk-ND2GUHAM-AoL16mNR.js} +1 -1
  190. package/web-shell/assets/{chunk-QZHKN3VN-CzG3I5o1.js → chunk-QZHKN3VN-a8PtLMck.js} +1 -1
  191. package/web-shell/assets/classDiagram-4FO5ZUOK--NgUfb55.js +1 -0
  192. package/web-shell/assets/classDiagram-v2-Q7XG4LA2--NgUfb55.js +1 -0
  193. package/web-shell/assets/{cose-bilkent-S5V4N54A-EgrIoH_K.js → cose-bilkent-S5V4N54A-C4Aeu5qf.js} +1 -1
  194. package/web-shell/assets/{dagre-BM42HDAG-BUnpdX8S.js → dagre-BM42HDAG-BOulamjo.js} +1 -1
  195. package/web-shell/assets/{diagram-2AECGRRQ-BlLUCVYu.js → diagram-2AECGRRQ-D1wt2ykO.js} +1 -1
  196. package/web-shell/assets/{diagram-5GNKFQAL-FG-enmjQ.js → diagram-5GNKFQAL-BRseus-O.js} +1 -1
  197. package/web-shell/assets/{diagram-KO2AKTUF-BlzmTONk.js → diagram-KO2AKTUF-XSMkNzpZ.js} +1 -1
  198. package/web-shell/assets/{diagram-LMA3HP47-CoBQ8mIx.js → diagram-LMA3HP47-21KNLqoi.js} +1 -1
  199. package/web-shell/assets/{diagram-OG6HWLK6-DvxV6sX8.js → diagram-OG6HWLK6-DnUI8F5G.js} +1 -1
  200. package/web-shell/assets/{erDiagram-TEJ5UH35-Jq1tpAoj.js → erDiagram-TEJ5UH35-BVSOMuM2.js} +1 -1
  201. package/web-shell/assets/{flowDiagram-I6XJVG4X-CbtYOry5.js → flowDiagram-I6XJVG4X-fYtDPJsW.js} +1 -1
  202. package/web-shell/assets/{ganttDiagram-6RSMTGT7-ZEn2VEKR.js → ganttDiagram-6RSMTGT7-CmRXosW_.js} +1 -1
  203. package/web-shell/assets/{gitGraphDiagram-PVQCEYII-BZh-LkhJ.js → gitGraphDiagram-PVQCEYII-DPrmySxO.js} +1 -1
  204. package/web-shell/assets/index-BpKnmyFV.js +717 -0
  205. package/web-shell/assets/index-fLrtxpOG.css +5 -0
  206. package/web-shell/assets/{infoDiagram-5YYISTIA-B9HuZygK.js → infoDiagram-5YYISTIA-x3VbEuCV.js} +1 -1
  207. package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-D76cNpYG.js → ishikawaDiagram-YF4QCWOH-SmitHRMC.js} +1 -1
  208. package/web-shell/assets/{journeyDiagram-JHISSGLW-6bGo7P1t.js → journeyDiagram-JHISSGLW-BxvA551S.js} +1 -1
  209. package/web-shell/assets/{kanban-definition-UN3LZRKU-DuYyRBvB.js → kanban-definition-UN3LZRKU-DNHC16jY.js} +1 -1
  210. package/web-shell/assets/{linear-CpdSW1BC.js → linear-CQ6irBL9.js} +1 -1
  211. package/web-shell/assets/{mermaid.core-s5xbZMMj.js → mermaid.core-DtB6NO1e.js} +72 -72
  212. package/web-shell/assets/{mindmap-definition-RKZ34NQL-go01fZ2y.js → mindmap-definition-RKZ34NQL-QKMAgV2v.js} +1 -1
  213. package/web-shell/assets/{pieDiagram-4H26LBE5-BfuZiEHO.js → pieDiagram-4H26LBE5-bvb6dGn6.js} +1 -1
  214. package/web-shell/assets/{quadrantDiagram-W4KKPZXB-DsoINqV6.js → quadrantDiagram-W4KKPZXB-DLgdlG4D.js} +1 -1
  215. package/web-shell/assets/{requirementDiagram-4Y6WPE33-B7FU4LiU.js → requirementDiagram-4Y6WPE33-2M9YH4GF.js} +1 -1
  216. package/web-shell/assets/{sankeyDiagram-5OEKKPKP-BEDjFnGB.js → sankeyDiagram-5OEKKPKP-DzwfclTH.js} +1 -1
  217. package/web-shell/assets/{sequenceDiagram-3UESZ5HK-gzmmL5tK.js → sequenceDiagram-3UESZ5HK-BvPmTuy2.js} +1 -1
  218. package/web-shell/assets/{stateDiagram-AJRCARHV-BtHD4IN_.js → stateDiagram-AJRCARHV-BjwUmrJF.js} +1 -1
  219. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-C4L8zXeL.js +1 -0
  220. package/web-shell/assets/{timeline-definition-PNZ67QCA-T_8gmMCf.js → timeline-definition-PNZ67QCA-CXzS6qqz.js} +1 -1
  221. package/web-shell/assets/{vennDiagram-CIIHVFJN-DdjuqnjO.js → vennDiagram-CIIHVFJN-yRSolF5B.js} +1 -1
  222. package/web-shell/assets/{wardley-L42UT6IY-Cgtrvb37.js → wardley-L42UT6IY-ClLJjHPG.js} +1 -1
  223. package/web-shell/assets/{wardleyDiagram-YWT4CUSO-BW3f3-yW.js → wardleyDiagram-YWT4CUSO-VIa6t2p2.js} +1 -1
  224. package/web-shell/assets/{xychartDiagram-2RQKCTM6-BUS9IKkc.js → xychartDiagram-2RQKCTM6-CtmrkezW.js} +1 -1
  225. package/web-shell/index.html +18 -2
  226. package/chunks/chunk-2JKEQWDZ.js +0 -1596
  227. package/chunks/chunk-32MCHZQC.js +0 -73
  228. package/chunks/chunk-UAKXW3DN.js +0 -30
  229. package/web-shell/assets/channel-eRvn2UA3.js +0 -1
  230. package/web-shell/assets/classDiagram-4FO5ZUOK-YZNwy1rx.js +0 -1
  231. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-YZNwy1rx.js +0 -1
  232. package/web-shell/assets/index-5FhE0ORO.css +0 -5
  233. package/web-shell/assets/index-CDTGmqVm.js +0 -720
  234. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-DFy6gLDd.js +0 -1
@@ -1,34 +1,93 @@
1
1
  // Force strict mode and setup for ESM
2
2
  "use strict";
3
3
  import {
4
+ MAX_REMEMBER_CONTENT_BYTES,
5
+ extractRememberErrorCode,
4
6
  isValidServerName
5
- } from "./chunk-UAKXW3DN.js";
7
+ } from "./chunk-RM3PGSGL.js";
6
8
  import {
7
9
  openQwenAsrRealtimeStream,
8
10
  openVoiceStream,
9
11
  openVoiceStreamWithRetry
10
- } from "./chunk-V5YJAUHW.js";
12
+ } from "./chunk-CUGEBAKG.js";
11
13
  import {
12
14
  SetupGithubError,
13
15
  setupGithub
14
- } from "./chunk-Q3JOGB4U.js";
15
- import "./chunk-M2U5NM24.js";
16
- import "./chunk-GDIG7TMG.js";
16
+ } from "./chunk-L7QPM2YD.js";
17
+ import "./chunk-54YZOP7P.js";
18
+ import "./chunk-HRZB5CSC.js";
19
+ import {
20
+ CAPABILITIES_SCHEMA_VERSION,
21
+ CLIENT_ID_HEADER,
22
+ CLIENT_ID_RE,
23
+ ClientMcpSenderRegistry,
24
+ DeviceFlowRegistry,
25
+ MAX_CLIENT_ID_LENGTH,
26
+ MAX_SERVER_NAME_LENGTH,
27
+ MAX_TOOL_NAME_LENGTH,
28
+ TooManyActiveDeviceFlowsError,
29
+ UnsupportedDeviceFlowProviderError,
30
+ UpstreamDeviceFlowError,
31
+ allowOriginCors,
32
+ bearerAuth,
33
+ brandSecret,
34
+ buildDaemonStatusResponse,
35
+ createBridgeFileSystemAdapter,
36
+ createBuildWorkspaceCtx,
37
+ createClientMcpServerProvider,
38
+ createMutationGate,
39
+ createRateLimiter,
40
+ denyBrowserOriginCors,
41
+ detectFromLoopback,
42
+ getAdvertisedServeFeatures,
43
+ getServeProtocolVersions,
44
+ hostAllowlist,
45
+ parseAllowOriginPatterns,
46
+ parseAndValidateWorkspaceClientId,
47
+ parseClientIdHeader,
48
+ parseDaemonStatusDetail,
49
+ parseLastEventId,
50
+ parseMaxQueuedQuery,
51
+ parseOptionalWorkspaceCwd,
52
+ parsePermissionVoteBody,
53
+ requireSessionId,
54
+ require_express,
55
+ safeBody,
56
+ safeLogValue,
57
+ sanitizeForStderr,
58
+ sendJsonBodyParserError,
59
+ setDeviceFlowRegistry,
60
+ setRateLimiter,
61
+ unsafeRevealSecret,
62
+ validateMcpRuntimeServerName
63
+ } from "./chunk-B7KGXIPZ.js";
64
+ import {
65
+ isLoopbackBind
66
+ } from "./chunk-RNBYOUGV.js";
67
+ import "./chunk-3HX5LZ6R.js";
68
+ import "./chunk-7RYW5LQV.js";
17
69
  import {
18
70
  createDaemonStatusProvider
19
- } from "./chunk-FR3II765.js";
20
- import "./chunk-G525INVW.js";
71
+ } from "./chunk-6CIIP6OE.js";
72
+ import "./chunk-W2ZOAX7F.js";
21
73
  import "./chunk-KHDZHZMH.js";
74
+ import "./chunk-W6X4FGWX.js";
22
75
  import {
23
76
  createAcpSessionBridge
24
- } from "./chunk-FV4SQ54H.js";
77
+ } from "./chunk-KVPFPC4J.js";
78
+ import "./chunk-EWALXUAD.js";
79
+ import {
80
+ EVENT_SCHEMA_VERSION,
81
+ SubscriberLimitExceededError
82
+ } from "./chunk-ALX6BUYW.js";
83
+ import "./chunk-YBDZ63KI.js";
25
84
  import {
26
85
  createWorkspaceProvidersStatusProvider
27
- } from "./chunk-4722VFFU.js";
86
+ } from "./chunk-L7VH7HQA.js";
28
87
  import {
29
88
  getAuthTypeFromEnv,
30
89
  resolveCliGenerationConfig
31
- } from "./chunk-OEY3YAL2.js";
90
+ } from "./chunk-BQNJBEUA.js";
32
91
  import {
33
92
  EMPTY_WORKSPACE_VOICE_UPDATE_ERROR,
34
93
  WorkspaceVoiceError,
@@ -39,14 +98,14 @@ import {
39
98
  transcribeWorkspaceVoiceAudio,
40
99
  validateWorkspaceVoiceState,
41
100
  voiceSettingsScopeToWire
42
- } from "./chunk-JERW36RG.js";
101
+ } from "./chunk-UT5JU5DJ.js";
43
102
  import {
44
103
  PermissionRulesValidationError,
45
104
  buildPermissionSettings,
46
105
  isPermissionRuleType,
47
106
  normalizePermissionRules,
48
107
  readPermissionRuleSet
49
- } from "./chunk-DBR7PF4B.js";
108
+ } from "./chunk-QAFQLTXG.js";
50
109
  import {
51
110
  assertVoiceBaseUrlNetworkAllowed,
52
111
  isStreamingVoiceModel,
@@ -54,12 +113,12 @@ import {
54
113
  resolveVoiceTranscriptionConfig,
55
114
  sanitizeVoiceErrorMessage,
56
115
  transcribeVoiceAudio
57
- } from "./chunk-T44AF4I6.js";
116
+ } from "./chunk-BNJLLAQ7.js";
58
117
  import {
59
118
  isVoiceEnabled,
60
119
  isVoiceMode,
61
120
  readVoiceModel
62
- } from "./chunk-LXEW6BHF.js";
121
+ } from "./chunk-Q7VHIRDD.js";
63
122
  import "./chunk-NIOPRZNN.js";
64
123
  import "./chunk-P7M4GA57.js";
65
124
  import {
@@ -77,7 +136,10 @@ import {
77
136
  PermissionPolicyNotImplementedError,
78
137
  PromptQueueFullError,
79
138
  RestoreInProgressError,
139
+ SessionArchivedError,
140
+ SessionArchivingError,
80
141
  SessionBusyError,
142
+ SessionConflictError,
81
143
  SessionLimitExceededError,
82
144
  SessionNotFoundError,
83
145
  SessionShellClientRequiredError,
@@ -87,91 +149,36 @@ import {
87
149
  WorkspaceInitRaceError,
88
150
  WorkspaceInitSymlinkError,
89
151
  WorkspaceMismatchError
90
- } from "./chunk-DHXWNSJA.js";
91
- import {
92
- WorkspacePermissionRulesSessionRequiredError,
93
- WorkspaceSettingsPartialPersistError
94
- } from "./chunk-BMFMJINR.js";
95
- import {
96
- CAPABILITIES_SCHEMA_VERSION,
97
- CLIENT_ID_HEADER,
98
- CLIENT_ID_RE,
99
- ClientMcpSenderRegistry,
100
- DeviceFlowRegistry,
101
- MAX_CLIENT_ID_LENGTH,
102
- MAX_SERVER_NAME_LENGTH,
103
- MAX_TOOL_NAME_LENGTH,
104
- TooManyActiveDeviceFlowsError,
105
- UnsupportedDeviceFlowProviderError,
106
- UpstreamDeviceFlowError,
107
- allowOriginCors,
108
- bearerAuth,
109
- brandSecret,
110
- buildDaemonStatusResponse,
111
- createBridgeFileSystemAdapter,
112
- createBuildWorkspaceCtx,
113
- createClientMcpServerProvider,
114
- createMutationGate,
115
- createRateLimiter,
116
- denyBrowserOriginCors,
117
- detectFromLoopback,
118
- getAdvertisedServeFeatures,
119
- getServeProtocolVersions,
120
- hostAllowlist,
121
- isLoopbackBind,
122
- parseAllowOriginPatterns,
123
- parseAndValidateWorkspaceClientId,
124
- parseClientIdHeader,
125
- parseDaemonStatusDetail,
126
- parseLastEventId,
127
- parseMaxQueuedQuery,
128
- parseOptionalWorkspaceCwd,
129
- parsePermissionVoteBody,
130
- requireSessionId,
131
- require_express,
132
- safeBody,
133
- safeLogValue,
134
- sanitizeForStderr,
135
- sendJsonBodyParserError,
136
- setDeviceFlowRegistry,
137
- setRateLimiter,
138
- unsafeRevealSecret,
139
- validateMcpRuntimeServerName
140
- } from "./chunk-MTJVHQDP.js";
141
- import "./chunk-3HX5LZ6R.js";
142
- import "./chunk-7RYW5LQV.js";
143
- import "./chunk-HIYUAXBG.js";
144
- import "./chunk-EWALXUAD.js";
152
+ } from "./chunk-T22H4V4R.js";
145
153
  import {
146
154
  MAX_WORKSPACE_PATH_LENGTH,
147
155
  canonicalizeWorkspace
148
156
  } from "./chunk-SEZC2725.js";
149
157
  import {
150
- EVENT_SCHEMA_VERSION,
151
- SubscriberLimitExceededError
152
- } from "./chunk-ALX6BUYW.js";
158
+ STATUS_SCHEMA_VERSION,
159
+ createIdleWorkspaceMemoryStatus,
160
+ mapDomainErrorToErrorKind
161
+ } from "./chunk-GICCHL2Q.js";
162
+ import {
163
+ WorkspacePermissionRulesSessionRequiredError,
164
+ WorkspaceSettingsPartialPersistError
165
+ } from "./chunk-BMFMJINR.js";
153
166
  import {
154
167
  getDialogSettingKeys,
155
168
  getNestedProperty,
156
169
  getSettingDefinition,
157
170
  loadSettings,
158
171
  validateSettingValue
159
- } from "./chunk-6FIWYVN4.js";
172
+ } from "./chunk-MKHC4BYZ.js";
160
173
  import {
161
174
  SUPPORTED_LANGUAGES
162
- } from "./chunk-3O2BJMFW.js";
175
+ } from "./chunk-UQYOMM6J.js";
163
176
  import "./chunk-TLRYABYP.js";
164
- import "./chunk-KXVVXAAC.js";
165
177
  import "./chunk-UFC57OYT.js";
166
- import {
167
- STATUS_SCHEMA_VERSION,
168
- createIdleWorkspaceMemoryStatus,
169
- mapDomainErrorToErrorKind
170
- } from "./chunk-VHRREANT.js";
171
178
  import "./chunk-PPFWEV7G.js";
172
179
  import {
173
180
  getWorkspaceTrustStatus
174
- } from "./chunk-Z34RYLRO.js";
181
+ } from "./chunk-ZJP5T7IA.js";
175
182
  import {
176
183
  writeStderrLine
177
184
  } from "./chunk-Y6Z2O3WR.js";
@@ -185,6 +192,7 @@ import {
185
192
  ClientMcpRegistrar,
186
193
  ExtensionManager,
187
194
  ModelsConfig,
195
+ RUNTIME_MCP_IF_ABSENT_CONFIG_FLAG,
188
196
  SessionService,
189
197
  StandardFileSystemService,
190
198
  StdioClientTransport,
@@ -204,21 +212,23 @@ import {
204
212
  redactUrlCredentials,
205
213
  shouldShowStep,
206
214
  writeWorkspaceContextFile
207
- } from "./chunk-35FH347N.js";
208
- import "./chunk-K5PGHDBN.js";
215
+ } from "./chunk-BFG6OZN7.js";
209
216
  import "./chunk-5P5XGNYH.js";
217
+ import "./chunk-K5PGHDBN.js";
210
218
  import "./chunk-SKAZWEV5.js";
211
- import "./chunk-ESVSWG6Z.js";
212
- import "./chunk-CA4ML3VH.js";
213
- import "./chunk-HWG4XLD7.js";
214
- import "./chunk-YCRTJYSQ.js";
215
- import "./chunk-FZY7NH37.js";
219
+ import "./chunk-QOMAHFYI.js";
220
+ import "./chunk-G7OQ3RFJ.js";
221
+ import "./chunk-LKWAVAB5.js";
222
+ import "./chunk-MEQWZGK5.js";
223
+ import "./chunk-UANCYOOQ.js";
216
224
  import "./chunk-2MPVVENX.js";
217
225
  import "./chunk-ZTQ26VBE.js";
218
226
  import "./chunk-SJBIFVD2.js";
219
227
  import "./chunk-MLZQVCF3.js";
228
+ import "./chunk-S5EQPDTM.js";
220
229
  import "./chunk-LQ7TMOCE.js";
221
- import "./chunk-LD2XBG6Z.js";
230
+ import "./chunk-E5A7LHNN.js";
231
+ import "./chunk-GO6LNQXT.js";
222
232
  import "./chunk-B236XPGF.js";
223
233
  import "./chunk-YXK4GPYO.js";
224
234
  import "./chunk-TOAUDIS7.js";
@@ -236,15 +246,15 @@ import {
236
246
  recordDaemonHttpRequest,
237
247
  recordDaemonHttpResponse,
238
248
  withDaemonRequestSpan
239
- } from "./chunk-B5MPZI4K.js";
240
- import "./chunk-3PJXIDKI.js";
249
+ } from "./chunk-HJDF3ZVU.js";
250
+ import "./chunk-E6US47KI.js";
241
251
  import "./chunk-UWCTAVOD.js";
242
252
  import "./chunk-OFEVLU4C.js";
243
253
  import "./chunk-OMX7CUOE.js";
244
254
  import "./chunk-MOUXUCXQ.js";
245
255
  import "./chunk-BZHLWC6T.js";
246
- import "./chunk-E5I6YSNO.js";
247
- import "./chunk-JV7A66DR.js";
256
+ import "./chunk-UQFP2FEF.js";
257
+ import "./chunk-4VZH35PN.js";
248
258
  import {
249
259
  QwenOAuth2Client,
250
260
  QwenOAuthPollError,
@@ -253,8 +263,8 @@ import {
253
263
  isDeviceAuthorizationSuccess,
254
264
  isDeviceTokenPending,
255
265
  isDeviceTokenSuccess
256
- } from "./chunk-LXGWLOCM.js";
257
- import "./chunk-QHHNFG22.js";
266
+ } from "./chunk-TENOUWY2.js";
267
+ import "./chunk-GLPV75ES.js";
258
268
  import "./chunk-22IFUCVR.js";
259
269
  import "./chunk-T42ACHFI.js";
260
270
  import "./chunk-YTFBURQD.js";
@@ -267,7 +277,7 @@ import {
267
277
  } from "./chunk-H6BD2ELD.js";
268
278
  import "./chunk-5IFG2VC4.js";
269
279
  import "./chunk-64WXLC72.js";
270
- import "./chunk-FQDMX5IL.js";
280
+ import "./chunk-D5KISTLW.js";
271
281
  import {
272
282
  FatalConfigError,
273
283
  Storage,
@@ -292,6 +302,8 @@ var import_express4 = __toESM(require_express(), 1);
292
302
  // packages/cli/src/serve/acp-http/index.ts
293
303
  init_esbuild_shims();
294
304
  import { createHash as createHash3, timingSafeEqual } from "node:crypto";
305
+ import { createRequire } from "node:module";
306
+ import * as path9 from "node:path";
295
307
 
296
308
  // packages/cli/src/serve/acp-http/dispatch.ts
297
309
  init_esbuild_shims();
@@ -1500,8 +1512,8 @@ var PathMutexRegistry = class {
1500
1512
  async runExclusive(key, fn) {
1501
1513
  const previous = this.tails.get(key) ?? Promise.resolve();
1502
1514
  let release;
1503
- const current = new Promise((resolve4) => {
1504
- release = resolve4;
1515
+ const current = new Promise((resolve5) => {
1516
+ release = resolve5;
1505
1517
  });
1506
1518
  const tail = previous.catch(() => void 0).then(() => current);
1507
1519
  this.tails.set(key, tail);
@@ -1988,7 +2000,7 @@ async function renameWithRetryLocal(src, dest, retries, delayMs) {
1988
2000
  const retryable = code === "EPERM" || code === "EACCES";
1989
2001
  if (!retryable || attempt === retries) throw err;
1990
2002
  await new Promise(
1991
- (resolve4) => setTimeout(resolve4, delayMs * 2 ** attempt)
2003
+ (resolve5) => setTimeout(resolve5, delayMs * 2 ** attempt)
1992
2004
  );
1993
2005
  }
1994
2006
  }
@@ -4157,6 +4169,316 @@ function createDaemonSubagentManager(boundWorkspace, safeMode = false) {
4157
4169
  }
4158
4170
  __name(createDaemonSubagentManager, "createDaemonSubagentManager");
4159
4171
 
4172
+ // packages/cli/src/serve/server/session-archive.ts
4173
+ init_esbuild_shims();
4174
+ var SessionArchiveCoordinator = class {
4175
+ static {
4176
+ __name(this, "SessionArchiveCoordinator");
4177
+ }
4178
+ exclusive = /* @__PURE__ */ new Set();
4179
+ shared = /* @__PURE__ */ new Map();
4180
+ assertNotTransitioning(sessionId) {
4181
+ if (this.exclusive.has(sessionId)) {
4182
+ throw new SessionArchivingError(sessionId);
4183
+ }
4184
+ }
4185
+ async runExclusiveMany(sessionIds, fn) {
4186
+ const uniqueSessionIds = [...new Set(sessionIds)];
4187
+ for (const sessionId of uniqueSessionIds) {
4188
+ this.assertNotTransitioning(sessionId);
4189
+ if ((this.shared.get(sessionId) ?? 0) > 0) {
4190
+ throw new SessionArchivingError(sessionId, "shared");
4191
+ }
4192
+ }
4193
+ for (const sessionId of uniqueSessionIds) {
4194
+ this.exclusive.add(sessionId);
4195
+ }
4196
+ try {
4197
+ return await fn();
4198
+ } finally {
4199
+ for (const sessionId of uniqueSessionIds) {
4200
+ this.exclusive.delete(sessionId);
4201
+ }
4202
+ }
4203
+ }
4204
+ async runSharedMany(sessionIds, fn) {
4205
+ const uniqueSessionIds = [...new Set(sessionIds)];
4206
+ for (const sessionId of uniqueSessionIds) {
4207
+ this.assertNotTransitioning(sessionId);
4208
+ }
4209
+ for (const sessionId of uniqueSessionIds) {
4210
+ this.shared.set(sessionId, (this.shared.get(sessionId) ?? 0) + 1);
4211
+ }
4212
+ try {
4213
+ return await fn();
4214
+ } finally {
4215
+ for (const sessionId of uniqueSessionIds) {
4216
+ const count = (this.shared.get(sessionId) ?? 1) - 1;
4217
+ if (count <= 0) {
4218
+ this.shared.delete(sessionId);
4219
+ } else {
4220
+ this.shared.set(sessionId, count);
4221
+ }
4222
+ }
4223
+ }
4224
+ }
4225
+ };
4226
+ async function deleteDaemonSessions(params) {
4227
+ const { sessionIds, service, bridge, coordinator, onError } = params;
4228
+ const uniqueSessionIds = [...new Set(sessionIds)];
4229
+ const closeErrors = [];
4230
+ const removed = [];
4231
+ const notFound = [];
4232
+ const removeErrors = [];
4233
+ for (const sessionId of uniqueSessionIds) {
4234
+ coordinator.assertNotTransitioning(sessionId);
4235
+ }
4236
+ await Promise.all(
4237
+ uniqueSessionIds.map(async (sessionId) => {
4238
+ try {
4239
+ await coordinator.runExclusiveMany([sessionId], async () => {
4240
+ let shouldRemove = false;
4241
+ try {
4242
+ await bridge.closeSession(sessionId);
4243
+ shouldRemove = true;
4244
+ } catch (closeErr) {
4245
+ if (closeErr instanceof SessionNotFoundError || closeErr instanceof Error && closeErr.name === "SessionNotFoundError") {
4246
+ shouldRemove = true;
4247
+ } else {
4248
+ const message = closeErr instanceof Error ? closeErr.message : String(closeErr);
4249
+ onError?.({ phase: "close", sessionId, error: message });
4250
+ closeErrors.push({ sessionId, error: message });
4251
+ }
4252
+ }
4253
+ if (!shouldRemove) return;
4254
+ try {
4255
+ if (await service.removeSession(sessionId)) {
4256
+ removed.push(sessionId);
4257
+ } else {
4258
+ notFound.push(sessionId);
4259
+ }
4260
+ } catch (removeErr) {
4261
+ const message = removeErr instanceof Error ? removeErr.message : String(removeErr);
4262
+ onError?.({ phase: "remove", sessionId, error: message });
4263
+ removeErrors.push({ sessionId, error: message });
4264
+ }
4265
+ });
4266
+ } catch (err) {
4267
+ if (err instanceof SessionArchivingError && err.lockKind === "exclusive") {
4268
+ throw err;
4269
+ }
4270
+ const message = err instanceof Error ? err.message : String(err);
4271
+ onError?.({ phase: "delete", sessionId, error: message });
4272
+ closeErrors.push({ sessionId, error: message });
4273
+ }
4274
+ })
4275
+ );
4276
+ return { removed, notFound, errors: [...closeErrors, ...removeErrors] };
4277
+ }
4278
+ __name(deleteDaemonSessions, "deleteDaemonSessions");
4279
+ async function assertSessionLoadable(workspaceCwd, sessionId) {
4280
+ const location = await new SessionService(workspaceCwd).getSessionLocation(
4281
+ sessionId
4282
+ );
4283
+ if (location === "archived") {
4284
+ throw new SessionArchivedError(sessionId);
4285
+ }
4286
+ if (location === "conflict") {
4287
+ throw new SessionConflictError(sessionId);
4288
+ }
4289
+ }
4290
+ __name(assertSessionLoadable, "assertSessionLoadable");
4291
+ function isSessionNotFoundError(err) {
4292
+ return err instanceof SessionNotFoundError || err instanceof Error && err.name === "SessionNotFoundError";
4293
+ }
4294
+ __name(isSessionNotFoundError, "isSessionNotFoundError");
4295
+ async function classifySessionLocations(service, sessionIds) {
4296
+ const result = {
4297
+ active: [],
4298
+ archived: [],
4299
+ notFound: [],
4300
+ errors: []
4301
+ };
4302
+ const locationResults = await Promise.allSettled(
4303
+ sessionIds.map(async (sessionId) => ({
4304
+ sessionId,
4305
+ location: await service.getSessionLocation(sessionId)
4306
+ }))
4307
+ );
4308
+ for (let i = 0; i < locationResults.length; i++) {
4309
+ const sessionId = sessionIds[i];
4310
+ const locationResult = locationResults[i];
4311
+ if (locationResult.status === "rejected") {
4312
+ result.errors.push({ sessionId, error: locationResult.reason });
4313
+ continue;
4314
+ }
4315
+ const location = locationResult.value.location;
4316
+ if (location === void 0) {
4317
+ result.notFound.push(sessionId);
4318
+ } else if (location === "archived") {
4319
+ result.archived.push(sessionId);
4320
+ } else if (location === "conflict") {
4321
+ result.errors.push({
4322
+ sessionId,
4323
+ error: new Error(`Session archive conflict: ${sessionId}`)
4324
+ });
4325
+ } else {
4326
+ result.active.push(sessionId);
4327
+ }
4328
+ }
4329
+ return result;
4330
+ }
4331
+ __name(classifySessionLocations, "classifySessionLocations");
4332
+ function logSessionArchiveResult(action, result) {
4333
+ const changedLabel = action === "archive" ? "archived" : "unarchived";
4334
+ const alreadyLabel = action === "archive" ? "alreadyArchived" : "alreadyActive";
4335
+ const details = [
4336
+ `requested=${result.requested.length} requestedIds=${formatSessionIds(result.requested)}`,
4337
+ `${changedLabel}=${result.changed.length} ${changedLabel}Ids=${formatSessionIds(result.changed)}`,
4338
+ `${alreadyLabel}=${result.already.length} ${alreadyLabel}Ids=${formatSessionIds(result.already)}`,
4339
+ `notFound=${result.notFound.length} notFoundIds=${formatSessionIds(result.notFound)}`,
4340
+ `errors=${result.errors.length} errorIds=${formatSessionErrors(result.errors)}`
4341
+ ].join(" ");
4342
+ writeStderrLine(`qwen serve: sessions ${action} result ${details}`);
4343
+ }
4344
+ __name(logSessionArchiveResult, "logSessionArchiveResult");
4345
+ function formatSessionIds(sessionIds) {
4346
+ return `[${sessionIds.map((sessionId) => safeLogValue(sessionId)).join(",")}]`;
4347
+ }
4348
+ __name(formatSessionIds, "formatSessionIds");
4349
+ function formatSessionErrors(errors) {
4350
+ return `[${errors.map(
4351
+ ({ sessionId, error: error2 }) => `${safeLogValue(sessionId)}:${safeLogValue(errorMessage(error2))}`
4352
+ ).join(",")}]`;
4353
+ }
4354
+ __name(formatSessionErrors, "formatSessionErrors");
4355
+ function errorMessage(error2) {
4356
+ return error2 instanceof Error ? error2.message : String(error2);
4357
+ }
4358
+ __name(errorMessage, "errorMessage");
4359
+ function logSessionArchiveWarning(message) {
4360
+ writeStderrLine(`qwen serve: ${sanitizeLogLine(message)}`);
4361
+ }
4362
+ __name(logSessionArchiveWarning, "logSessionArchiveWarning");
4363
+ var LOG_LINE_UNSAFE_RE = /[\x00-\x1f\x7f-\x9f\u200b-\u200f\u2028-\u202e\u2066-\u2069\ufeff]/g;
4364
+ function sanitizeLogLine(message) {
4365
+ return message.replace(LOG_LINE_UNSAFE_RE, " ").slice(0, 4096);
4366
+ }
4367
+ __name(sanitizeLogLine, "sanitizeLogLine");
4368
+ async function archiveDaemonSessions(params) {
4369
+ const { sessionIds, service, bridge, coordinator } = params;
4370
+ const uniqueSessionIds = [...new Set(sessionIds)];
4371
+ const archived = [];
4372
+ const alreadyArchived = [];
4373
+ const notFound = [];
4374
+ const errors = [];
4375
+ const initial = await classifySessionLocations(service, uniqueSessionIds);
4376
+ const activeIds = initial.active;
4377
+ alreadyArchived.push(...initial.archived);
4378
+ notFound.push(...initial.notFound);
4379
+ errors.push(...initial.errors);
4380
+ if (activeIds.length > 0) {
4381
+ await coordinator.runExclusiveMany(activeIds, async () => {
4382
+ const locked = await classifySessionLocations(service, activeIds);
4383
+ const closableIds = locked.active;
4384
+ alreadyArchived.push(...locked.archived);
4385
+ notFound.push(...locked.notFound);
4386
+ errors.push(...locked.errors);
4387
+ const closeResults = await Promise.allSettled(
4388
+ closableIds.map(async (sessionId) => {
4389
+ try {
4390
+ await bridge.closeSession(sessionId, void 0, {
4391
+ requireAgentClose: true
4392
+ });
4393
+ } catch (err) {
4394
+ if (!isSessionNotFoundError(err)) {
4395
+ throw err;
4396
+ }
4397
+ }
4398
+ })
4399
+ );
4400
+ const archiveIds = [];
4401
+ for (let i = 0; i < closeResults.length; i++) {
4402
+ const sessionId = closableIds[i];
4403
+ const result = closeResults[i];
4404
+ if (result.status === "fulfilled") {
4405
+ archiveIds.push(sessionId);
4406
+ } else {
4407
+ errors.push({ sessionId, error: result.reason });
4408
+ }
4409
+ }
4410
+ try {
4411
+ const archiveResult = await service.archiveSessions(archiveIds, {
4412
+ knownLocation: "active"
4413
+ });
4414
+ archived.push(...archiveResult.archived);
4415
+ alreadyArchived.push(...archiveResult.alreadyArchived);
4416
+ notFound.push(...archiveResult.notFound);
4417
+ errors.push(...archiveResult.errors);
4418
+ } catch (err) {
4419
+ for (const sessionId of archiveIds) {
4420
+ errors.push({ sessionId, error: err });
4421
+ }
4422
+ }
4423
+ });
4424
+ }
4425
+ logSessionArchiveResult("archive", {
4426
+ requested: uniqueSessionIds,
4427
+ changed: archived,
4428
+ already: alreadyArchived,
4429
+ notFound,
4430
+ errors
4431
+ });
4432
+ return { archived, alreadyArchived, notFound, errors };
4433
+ }
4434
+ __name(archiveDaemonSessions, "archiveDaemonSessions");
4435
+ async function unarchiveDaemonSessions(params) {
4436
+ const { sessionIds, service, coordinator } = params;
4437
+ const uniqueSessionIds = [...new Set(sessionIds)];
4438
+ const unarchived = [];
4439
+ const alreadyActive = [];
4440
+ const notFound = [];
4441
+ const errors = [];
4442
+ const initial = await classifySessionLocations(service, uniqueSessionIds);
4443
+ const archivedIds = initial.archived;
4444
+ alreadyActive.push(...initial.active);
4445
+ notFound.push(...initial.notFound);
4446
+ errors.push(...initial.errors);
4447
+ if (archivedIds.length > 0) {
4448
+ await coordinator.runExclusiveMany(archivedIds, async () => {
4449
+ const locked = await classifySessionLocations(service, archivedIds);
4450
+ const unarchiveIds = locked.archived;
4451
+ alreadyActive.push(...locked.active);
4452
+ notFound.push(...locked.notFound);
4453
+ errors.push(...locked.errors);
4454
+ if (unarchiveIds.length > 0) {
4455
+ try {
4456
+ const result = await service.unarchiveSessions(unarchiveIds, {
4457
+ knownLocation: "archived"
4458
+ });
4459
+ unarchived.push(...result.unarchived);
4460
+ alreadyActive.push(...result.alreadyActive);
4461
+ notFound.push(...result.notFound);
4462
+ errors.push(...result.errors);
4463
+ } catch (err) {
4464
+ for (const sessionId of unarchiveIds) {
4465
+ errors.push({ sessionId, error: err });
4466
+ }
4467
+ }
4468
+ }
4469
+ });
4470
+ }
4471
+ logSessionArchiveResult("unarchive", {
4472
+ requested: uniqueSessionIds,
4473
+ changed: unarchived,
4474
+ already: alreadyActive,
4475
+ notFound,
4476
+ errors
4477
+ });
4478
+ return { unarchived, alreadyActive, notFound, errors };
4479
+ }
4480
+ __name(unarchiveDaemonSessions, "unarchiveDaemonSessions");
4481
+
4160
4482
  // packages/cli/src/serve/acp-http/json-rpc.ts
4161
4483
  init_esbuild_shims();
4162
4484
  var QWEN_METHOD_NS = "_qwen/";
@@ -4275,6 +4597,8 @@ var ALL_QWEN_VENDOR_METHODS = [
4275
4597
  // Wave 1: memory
4276
4598
  `${QWEN_METHOD_NS}workspace/memory`,
4277
4599
  `${QWEN_METHOD_NS}workspace/memory/write`,
4600
+ `${QWEN_METHOD_NS}workspace/memory/remember`,
4601
+ `${QWEN_METHOD_NS}workspace/memory/remember/get`,
4278
4602
  // Wave 1: files
4279
4603
  `${QWEN_METHOD_NS}file/read`,
4280
4604
  `${QWEN_METHOD_NS}file/read_bytes`,
@@ -4295,6 +4619,8 @@ var ALL_QWEN_VENDOR_METHODS = [
4295
4619
  `${QWEN_METHOD_NS}workspace/mcp/servers/add`,
4296
4620
  `${QWEN_METHOD_NS}workspace/mcp/servers/remove`,
4297
4621
  `${QWEN_METHOD_NS}sessions/delete`,
4622
+ `${QWEN_METHOD_NS}sessions/archive`,
4623
+ `${QWEN_METHOD_NS}sessions/unarchive`,
4298
4624
  // Wave 2: agents
4299
4625
  `${QWEN_METHOD_NS}workspace/agents/list`,
4300
4626
  `${QWEN_METHOD_NS}workspace/agents/get`,
@@ -4487,6 +4813,33 @@ function toRpcError(err) {
4487
4813
  }
4488
4814
  const name = err instanceof Error ? err.name : "";
4489
4815
  switch (name) {
4816
+ case "SessionArchivedError":
4817
+ return {
4818
+ code: RPC.INTERNAL_ERROR,
4819
+ message: errMsg(err),
4820
+ data: {
4821
+ errorKind: "session_archived",
4822
+ sessionId: err.sessionId
4823
+ }
4824
+ };
4825
+ case "SessionConflictError":
4826
+ return {
4827
+ code: RPC.INTERNAL_ERROR,
4828
+ message: errMsg(err),
4829
+ data: {
4830
+ errorKind: "session_conflict",
4831
+ sessionId: err.sessionId
4832
+ }
4833
+ };
4834
+ case "SessionArchivingError":
4835
+ return {
4836
+ code: RPC.INTERNAL_ERROR,
4837
+ message: errMsg(err),
4838
+ data: {
4839
+ errorKind: "session_archiving",
4840
+ sessionId: err.sessionId
4841
+ }
4842
+ };
4490
4843
  case "SessionNotFoundError":
4491
4844
  case "InvalidSessionScopeError":
4492
4845
  case "WorkspaceMismatchError":
@@ -4523,14 +4876,16 @@ function rpcErrorFrame(id, err) {
4523
4876
  __name(rpcErrorFrame, "rpcErrorFrame");
4524
4877
  var ACP_PROTOCOL_VERSION = 1;
4525
4878
  var AcpDispatcher = class {
4526
- constructor(bridge, boundWorkspace, workspace, fsFactory, deviceFlowRegistry, sessionShellCommandEnabled = false, registry) {
4879
+ constructor(bridge, boundWorkspace, workspace, workspaceRememberLane, fsFactory, deviceFlowRegistry, sessionShellCommandEnabled = false, registry, archiveCoordinator = new SessionArchiveCoordinator()) {
4527
4880
  this.bridge = bridge;
4528
4881
  this.boundWorkspace = boundWorkspace;
4529
4882
  this.workspace = workspace;
4883
+ this.workspaceRememberLane = workspaceRememberLane;
4530
4884
  this.fsFactory = fsFactory;
4531
4885
  this.deviceFlowRegistry = deviceFlowRegistry;
4532
4886
  this.sessionShellCommandEnabled = sessionShellCommandEnabled;
4533
4887
  this.registry = registry;
4888
+ this.archiveCoordinator = archiveCoordinator;
4534
4889
  this.agentManager = createDaemonSubagentManager(boundWorkspace);
4535
4890
  }
4536
4891
  static {
@@ -4556,6 +4911,21 @@ var AcpDispatcher = class {
4556
4911
  workspaceCwd: this.boundWorkspace
4557
4912
  };
4558
4913
  }
4914
+ parseSessionIds(params) {
4915
+ const sessionIds = params["sessionIds"];
4916
+ if (!Array.isArray(sessionIds) || sessionIds.length === 0 || sessionIds.length > 100 || !sessionIds.every((s) => typeof s === "string")) {
4917
+ throw new AcpParamError(
4918
+ "`sessionIds` must be non-empty string array (max 100)"
4919
+ );
4920
+ }
4921
+ return [...new Set(sessionIds)];
4922
+ }
4923
+ serializeSessionErrors(errors) {
4924
+ return errors.map((e) => ({
4925
+ sessionId: e.sessionId,
4926
+ error: errMsg(e.error)
4927
+ }));
4928
+ }
4559
4929
  /**
4560
4930
  * Build the bridge context for a per-session call. Echoes the clientId the
4561
4931
  * bridge STAMPED at create/attach (the connection's own id is unregistered
@@ -4723,6 +5093,12 @@ var AcpDispatcher = class {
4723
5093
  );
4724
5094
  return false;
4725
5095
  }
5096
+ async withMutableOwned(conn, sessionId, id, fn) {
5097
+ if (!this.requireOwned(conn, sessionId, id)) return;
5098
+ await this.archiveCoordinator.runSharedMany([sessionId], async () => {
5099
+ await fn();
5100
+ });
5101
+ }
4726
5102
  findPendingClientRequest(conn, id) {
4727
5103
  const req = conn.pending.get(id);
4728
5104
  if (req) return { conn, id, req };
@@ -4837,17 +5213,23 @@ var AcpDispatcher = class {
4837
5213
  return;
4838
5214
  }
4839
5215
  const cwd = parseOptionalWorkspaceCwd2(params, this.boundWorkspace);
4840
- const restored = method === "session/load" ? await this.bridge.loadSession({
4841
- sessionId,
4842
- workspaceCwd: cwd,
4843
- clientId: conn.clientId
4844
- }) : await this.bridge.resumeSession({
4845
- sessionId,
4846
- workspaceCwd: cwd,
4847
- clientId: conn.clientId
4848
- });
4849
- const closeRaced = conn.closingSessions.has(sessionId);
4850
- if (conn.destroyed || closeRaced) {
5216
+ const restored = await this.archiveCoordinator.runSharedMany(
5217
+ [sessionId],
5218
+ async () => {
5219
+ await assertSessionLoadable(cwd, sessionId);
5220
+ return method === "session/load" ? await this.bridge.loadSession({
5221
+ sessionId,
5222
+ workspaceCwd: cwd,
5223
+ clientId: conn.clientId
5224
+ }) : await this.bridge.resumeSession({
5225
+ sessionId,
5226
+ workspaceCwd: cwd,
5227
+ clientId: conn.clientId
5228
+ });
5229
+ }
5230
+ );
5231
+ const closeRaced = conn.closingSessions.has(sessionId);
5232
+ if (conn.destroyed || closeRaced) {
4851
5233
  const cleanup = restored.attached ? this.bridge.detachClient(sessionId, restored.clientId) : this.bridge.killSession(sessionId, {
4852
5234
  requireZeroAttaches: true
4853
5235
  });
@@ -4881,20 +5263,51 @@ var AcpDispatcher = class {
4881
5263
  return;
4882
5264
  }
4883
5265
  case "session/list": {
5266
+ const rawWorkspace = typeof params["workspaceCwd"] === "string" ? params["workspaceCwd"] : void 0;
5267
+ let workspaceCwd = rawWorkspace === void 0 ? this.boundWorkspace : parseOptionalWorkspaceCwd2(
5268
+ { cwd: rawWorkspace },
5269
+ this.boundWorkspace
5270
+ );
5271
+ if (rawWorkspace !== void 0) {
5272
+ const requestedWorkspace = canonicalizeWorkspace(workspaceCwd);
5273
+ if (requestedWorkspace !== this.boundWorkspace) {
5274
+ throw new WorkspaceMismatchError(
5275
+ this.boundWorkspace,
5276
+ requestedWorkspace
5277
+ );
5278
+ }
5279
+ workspaceCwd = requestedWorkspace;
5280
+ }
4884
5281
  const cursor = typeof params["cursor"] === "string" ? params["cursor"] : void 0;
4885
5282
  const meta = isObject(params["_meta"]) ? params["_meta"] : void 0;
4886
5283
  const metaSize = typeof meta?.["size"] === "number" ? meta["size"] : void 0;
5284
+ const rawArchiveState = typeof params["archiveState"] === "string" ? params["archiveState"] : typeof meta?.["archiveState"] === "string" ? meta["archiveState"] : void 0;
5285
+ let archiveState;
5286
+ if (rawArchiveState !== void 0) {
5287
+ if (rawArchiveState !== "active" && rawArchiveState !== "archived") {
5288
+ throw new AcpParamError(
5289
+ '`archiveState` must be "active" or "archived"'
5290
+ );
5291
+ }
5292
+ archiveState = rawArchiveState;
5293
+ }
4887
5294
  const result = await listWorkspaceSessionsForResponse(
4888
5295
  this.bridge,
4889
- this.boundWorkspace,
4890
- { cursor, size: metaSize }
5296
+ workspaceCwd,
5297
+ { cursor, size: metaSize, archiveState }
4891
5298
  );
4892
5299
  this.replyConn(conn, id, {
4893
5300
  sessions: result.sessions.map((s) => ({
4894
5301
  sessionId: s.sessionId,
5302
+ workspaceCwd: s.workspaceCwd,
4895
5303
  cwd: s.workspaceCwd,
5304
+ createdAt: s.createdAt,
5305
+ updatedAt: s.updatedAt,
5306
+ displayName: s.displayName,
4896
5307
  title: s.displayName,
4897
- updatedAt: s.updatedAt
5308
+ clientCount: s.clientCount,
5309
+ hasActivePrompt: s.hasActivePrompt,
5310
+ isArchived: s.isArchived === true
4898
5311
  })),
4899
5312
  ...result.nextCursor != null ? { nextCursor: result.nextCursor } : {}
4900
5313
  });
@@ -4905,19 +5318,47 @@ var AcpDispatcher = class {
4905
5318
  if (!this.requireOwned(conn, sessionId, id)) return;
4906
5319
  conn.ownedSessions.delete(sessionId);
4907
5320
  conn.closingSessions.add(sessionId);
5321
+ let closeStarted = false;
5322
+ const closeSession = /* @__PURE__ */ __name(async () => {
5323
+ closeStarted = true;
5324
+ try {
5325
+ await this.bridge.closeSession(
5326
+ sessionId,
5327
+ this.sessionCtx(conn, sessionId, loopback)
5328
+ );
5329
+ } finally {
5330
+ try {
5331
+ conn.closeSessionStream(sessionId);
5332
+ } catch (teardownErr) {
5333
+ writeStderrLine(
5334
+ `qwen serve: /acp session/close local teardown failed (${logSafe(sessionId)}): ${logSafe(errMsg(teardownErr))}`
5335
+ );
5336
+ }
5337
+ }
5338
+ }, "closeSession");
4908
5339
  try {
4909
- await this.bridge.closeSession(
4910
- sessionId,
4911
- this.sessionCtx(conn, sessionId, loopback)
4912
- );
4913
- } finally {
4914
5340
  try {
4915
- conn.closeSessionStream(sessionId);
4916
- } catch (teardownErr) {
4917
- writeStderrLine(
4918
- `qwen serve: /acp session/close local teardown failed (${logSafe(sessionId)}): ${logSafe(errMsg(teardownErr))}`
5341
+ await this.archiveCoordinator.runExclusiveMany(
5342
+ [sessionId],
5343
+ closeSession
4919
5344
  );
5345
+ } catch (err) {
5346
+ const promptAbort = conn.sessions.get(sessionId)?.promptAbort;
5347
+ if (err instanceof SessionArchivingError && err.lockKind === "shared" && promptAbort !== void 0) {
5348
+ await this.archiveCoordinator.runSharedMany(
5349
+ [sessionId],
5350
+ closeSession
5351
+ );
5352
+ } else {
5353
+ throw err;
5354
+ }
5355
+ }
5356
+ } catch (err) {
5357
+ if (!closeStarted) {
5358
+ conn.ownedSessions.add(sessionId);
4920
5359
  }
5360
+ throw err;
5361
+ } finally {
4921
5362
  conn.closingSessions.delete(sessionId);
4922
5363
  }
4923
5364
  this.replyConn(conn, id, {});
@@ -4935,51 +5376,54 @@ var AcpDispatcher = class {
4935
5376
  }
4936
5377
  return;
4937
5378
  }
4938
- if (!this.requireOwned(conn, sessionId, id)) return;
4939
- const ctx = this.sessionCtx(conn, sessionId, loopback);
4940
- const result = await this.bridge.branchSession(
4941
- sessionId,
4942
- {
4943
- name: typeof params["name"] === "string" ? params["name"] : void 0
4944
- },
4945
- ctx
4946
- );
4947
- if (conn.destroyed) {
4948
- this.killOrphanSession(result.sessionId);
4949
- return;
4950
- }
4951
- conn.getOrCreateSession(result.sessionId).clientId = result.clientId;
4952
- conn.ownSession(result.sessionId);
4953
- const configOptions = await this.configOptionsFor(result.sessionId);
4954
- const models = this.extractModelState(configOptions);
4955
- const modes = this.extractModeState(configOptions);
4956
- this.replyConn(conn, id, {
4957
- sessionId: result.sessionId,
4958
- ...configOptions ? { configOptions } : {},
4959
- ...models ? { models } : {},
4960
- ...modes ? { modes } : {}
5379
+ await this.withMutableOwned(conn, sessionId, id, async () => {
5380
+ const ctx = this.sessionCtx(conn, sessionId, loopback);
5381
+ const result = await this.bridge.branchSession(
5382
+ sessionId,
5383
+ {
5384
+ name: typeof params["name"] === "string" ? params["name"] : void 0
5385
+ },
5386
+ ctx
5387
+ );
5388
+ if (conn.destroyed) {
5389
+ this.killOrphanSession(result.sessionId);
5390
+ return;
5391
+ }
5392
+ conn.getOrCreateSession(result.sessionId).clientId = result.clientId;
5393
+ conn.ownSession(result.sessionId);
5394
+ const configOptions = await this.configOptionsFor(result.sessionId);
5395
+ const models = this.extractModelState(configOptions);
5396
+ const modes = this.extractModeState(configOptions);
5397
+ this.replyConn(conn, id, {
5398
+ sessionId: result.sessionId,
5399
+ ...configOptions ? { configOptions } : {},
5400
+ ...models ? { models } : {},
5401
+ ...modes ? { modes } : {}
5402
+ });
4961
5403
  });
4962
5404
  return;
4963
5405
  }
4964
5406
  case "session/cancel": {
4965
5407
  const sessionId = String(params["sessionId"] ?? "");
4966
- if (!this.requireOwned(conn, sessionId, id)) return;
4967
- conn.sessions.get(sessionId)?.promptAbort?.abort();
4968
- await this.bridge.cancelSession(
4969
- sessionId,
4970
- // Forward client-supplied cancel fields (reason/context) while
4971
- // force-stamping sessionId — mirrors the REST surface.
4972
- { ...params, sessionId },
4973
- this.sessionCtx(conn, sessionId, loopback)
4974
- );
4975
- if (id !== void 0) this.replySession(conn, sessionId, id, {});
5408
+ await this.withMutableOwned(conn, sessionId, id, async () => {
5409
+ conn.sessions.get(sessionId)?.promptAbort?.abort();
5410
+ await this.bridge.cancelSession(
5411
+ sessionId,
5412
+ // Forward client-supplied cancel fields (reason/context) while
5413
+ // force-stamping sessionId — mirrors the REST surface.
5414
+ { ...params, sessionId },
5415
+ this.sessionCtx(conn, sessionId, loopback)
5416
+ );
5417
+ if (id !== void 0) this.replySession(conn, sessionId, id, {});
5418
+ });
4976
5419
  return;
4977
5420
  }
4978
5421
  case "session/prompt": {
4979
5422
  const sessionId = String(params["sessionId"] ?? "");
4980
- if (!this.requireOwned(conn, sessionId, id)) return;
4981
- validatePrompt(params);
4982
- await this.handlePrompt(conn, sessionId, id, params, loopback);
5423
+ await this.withMutableOwned(conn, sessionId, id, async () => {
5424
+ validatePrompt(params);
5425
+ await this.handlePrompt(conn, sessionId, id, params, loopback);
5426
+ });
4983
5427
  return;
4984
5428
  }
4985
5429
  case "session/permission": {
@@ -5198,34 +5642,11 @@ var AcpDispatcher = class {
5198
5642
  // setters. Replaces the old vendor `_qwen/session/set_model`.
5199
5643
  case "session/set_config_option": {
5200
5644
  const sessionId = String(params["sessionId"] ?? "");
5201
- if (!this.requireOwned(conn, sessionId, id)) return;
5202
- const configId = String(params["configId"] ?? "");
5203
- const rawValue = params["value"];
5204
- const ctx = this.sessionCtx(conn, sessionId, loopback);
5205
- if (typeof rawValue !== "string" || rawValue.length === 0) {
5206
- if (id !== void 0) {
5207
- this.replySession(
5208
- conn,
5209
- sessionId,
5210
- id,
5211
- void 0,
5212
- error(
5213
- id,
5214
- RPC.INVALID_PARAMS,
5215
- "`value` must be a non-empty string"
5216
- )
5217
- );
5218
- }
5219
- return;
5220
- }
5221
- if (configId === "model") {
5222
- await this.bridge.setSessionModel(
5223
- sessionId,
5224
- { modelId: rawValue },
5225
- ctx
5226
- );
5227
- } else if (configId === "mode") {
5228
- if (!APPROVAL_MODES.includes(rawValue)) {
5645
+ await this.withMutableOwned(conn, sessionId, id, async () => {
5646
+ const configId = String(params["configId"] ?? "");
5647
+ const rawValue = params["value"];
5648
+ const ctx = this.sessionCtx(conn, sessionId, loopback);
5649
+ if (typeof rawValue !== "string" || rawValue.length === 0) {
5229
5650
  if (id !== void 0) {
5230
5651
  this.replySession(
5231
5652
  conn,
@@ -5235,32 +5656,60 @@ var AcpDispatcher = class {
5235
5656
  error(
5236
5657
  id,
5237
5658
  RPC.INVALID_PARAMS,
5238
- `invalid mode "${rawValue}" (expected one of: ${APPROVAL_MODES.join(", ")})`
5659
+ "`value` must be a non-empty string"
5239
5660
  )
5240
5661
  );
5241
5662
  }
5242
5663
  return;
5243
5664
  }
5244
- await this.bridge.setSessionApprovalMode(
5245
- sessionId,
5246
- rawValue,
5247
- { persist: params["persist"] === true },
5248
- ctx
5249
- );
5250
- } else {
5251
- if (id !== void 0) {
5252
- this.replySession(
5253
- conn,
5665
+ if (configId === "model") {
5666
+ await this.bridge.setSessionModel(
5667
+ sessionId,
5668
+ { modelId: rawValue },
5669
+ ctx
5670
+ );
5671
+ } else if (configId === "mode") {
5672
+ if (!APPROVAL_MODES.includes(rawValue)) {
5673
+ if (id !== void 0) {
5674
+ this.replySession(
5675
+ conn,
5676
+ sessionId,
5677
+ id,
5678
+ void 0,
5679
+ error(
5680
+ id,
5681
+ RPC.INVALID_PARAMS,
5682
+ `invalid mode "${rawValue}" (expected one of: ${APPROVAL_MODES.join(", ")})`
5683
+ )
5684
+ );
5685
+ }
5686
+ return;
5687
+ }
5688
+ await this.bridge.setSessionApprovalMode(
5254
5689
  sessionId,
5255
- id,
5256
- void 0,
5257
- error(id, RPC.INVALID_PARAMS, `Unknown configId: ${configId}`)
5690
+ rawValue,
5691
+ { persist: params["persist"] === true },
5692
+ ctx
5258
5693
  );
5694
+ } else {
5695
+ if (id !== void 0) {
5696
+ this.replySession(
5697
+ conn,
5698
+ sessionId,
5699
+ id,
5700
+ void 0,
5701
+ error(
5702
+ id,
5703
+ RPC.INVALID_PARAMS,
5704
+ `Unknown configId: ${configId}`
5705
+ )
5706
+ );
5707
+ }
5708
+ return;
5259
5709
  }
5260
- return;
5261
- }
5262
- const configOptions = await this.configOptionsFor(sessionId);
5263
- this.replySession(conn, sessionId, id, { configOptions });
5710
+ const configOptions = await this.configOptionsFor(sessionId);
5711
+ this.replySession(conn, sessionId, id, { configOptions });
5712
+ });
5264
5713
  return;
5265
5714
  }
5266
5715
  // ACP standard: session/set_mode — dedicated method for mode changes.
@@ -5274,32 +5723,33 @@ var AcpDispatcher = class {
5274
5723
  );
5275
5724
  return;
5276
5725
  }
5277
- if (!this.requireOwned(conn, sessionId, id)) return;
5278
- const modeId = String(params["modeId"] ?? "");
5279
- if (!modeId || !APPROVAL_MODES.includes(modeId)) {
5280
- if (id !== void 0) {
5281
- this.replySession(
5282
- conn,
5283
- sessionId,
5284
- id,
5285
- void 0,
5286
- error(
5726
+ await this.withMutableOwned(conn, sessionId, id, async () => {
5727
+ const modeId = String(params["modeId"] ?? "");
5728
+ if (!modeId || !APPROVAL_MODES.includes(modeId)) {
5729
+ if (id !== void 0) {
5730
+ this.replySession(
5731
+ conn,
5732
+ sessionId,
5287
5733
  id,
5288
- RPC.INVALID_PARAMS,
5289
- `invalid modeId "${modeId}" (expected one of: ${APPROVAL_MODES.join(", ")})`
5290
- )
5291
- );
5734
+ void 0,
5735
+ error(
5736
+ id,
5737
+ RPC.INVALID_PARAMS,
5738
+ `invalid modeId "${modeId}" (expected one of: ${APPROVAL_MODES.join(", ")})`
5739
+ )
5740
+ );
5741
+ }
5742
+ return;
5292
5743
  }
5293
- return;
5294
- }
5295
- const ctx = this.sessionCtx(conn, sessionId, loopback);
5296
- await this.bridge.setSessionApprovalMode(
5297
- sessionId,
5298
- modeId,
5299
- { persist: false },
5300
- ctx
5301
- );
5302
- this.replySession(conn, sessionId, id, {});
5744
+ const ctx = this.sessionCtx(conn, sessionId, loopback);
5745
+ await this.bridge.setSessionApprovalMode(
5746
+ sessionId,
5747
+ modeId,
5748
+ { persist: false },
5749
+ ctx
5750
+ );
5751
+ this.replySession(conn, sessionId, id, {});
5752
+ });
5303
5753
  return;
5304
5754
  }
5305
5755
  // ACP standard (unstable): session/set_model — dedicated method for
@@ -5314,27 +5764,28 @@ var AcpDispatcher = class {
5314
5764
  );
5315
5765
  return;
5316
5766
  }
5317
- if (!this.requireOwned(conn, sessionId, id)) return;
5318
- const modelId = String(params["modelId"] ?? "");
5319
- if (!modelId) {
5320
- if (id !== void 0) {
5321
- this.replySession(
5322
- conn,
5323
- sessionId,
5324
- id,
5325
- void 0,
5326
- error(id, RPC.INVALID_PARAMS, "`modelId` is required")
5327
- );
5767
+ await this.withMutableOwned(conn, sessionId, id, async () => {
5768
+ const modelId = String(params["modelId"] ?? "");
5769
+ if (!modelId) {
5770
+ if (id !== void 0) {
5771
+ this.replySession(
5772
+ conn,
5773
+ sessionId,
5774
+ id,
5775
+ void 0,
5776
+ error(id, RPC.INVALID_PARAMS, "`modelId` is required")
5777
+ );
5778
+ }
5779
+ return;
5328
5780
  }
5329
- return;
5330
- }
5331
- const ctx = this.sessionCtx(conn, sessionId, loopback);
5332
- await this.bridge.setSessionModel(
5333
- sessionId,
5334
- { modelId, sessionId },
5335
- ctx
5336
- );
5337
- this.replySession(conn, sessionId, id, {});
5781
+ const ctx = this.sessionCtx(conn, sessionId, loopback);
5782
+ await this.bridge.setSessionModel(
5783
+ sessionId,
5784
+ { modelId, sessionId },
5785
+ ctx
5786
+ );
5787
+ this.replySession(conn, sessionId, id, {});
5788
+ });
5338
5789
  return;
5339
5790
  }
5340
5791
  case `${QWEN_METHOD_NS}session/heartbeat`: {
@@ -5369,14 +5820,15 @@ var AcpDispatcher = class {
5369
5820
  }
5370
5821
  case `${QWEN_METHOD_NS}session/update_metadata`: {
5371
5822
  const sessionId = String(params["sessionId"] ?? "");
5372
- if (!this.requireOwned(conn, sessionId, id)) return;
5373
- const metadata = isObject(params["metadata"]) ? params["metadata"] : {};
5374
- const result = this.bridge.updateSessionMetadata(
5375
- sessionId,
5376
- metadata,
5377
- this.sessionCtx(conn, sessionId, loopback)
5378
- );
5379
- this.replyConn(conn, id, result);
5823
+ await this.withMutableOwned(conn, sessionId, id, async () => {
5824
+ const metadata = isObject(params["metadata"]) ? params["metadata"] : {};
5825
+ const result = this.bridge.updateSessionMetadata(
5826
+ sessionId,
5827
+ metadata,
5828
+ this.sessionCtx(conn, sessionId, loopback)
5829
+ );
5830
+ this.replyConn(conn, id, result);
5831
+ });
5380
5832
  return;
5381
5833
  }
5382
5834
  case `${QWEN_METHOD_NS}workspace/mcp`:
@@ -5718,36 +6170,38 @@ var AcpDispatcher = class {
5718
6170
  // ── Wave 1+2: ACP/REST parity methods ───────────────────────
5719
6171
  case `${QWEN_METHOD_NS}session/recap`: {
5720
6172
  const sessionId = String(params["sessionId"] ?? "");
5721
- if (!this.requireOwned(conn, sessionId, id)) return;
5722
- const result = await this.bridge.generateSessionRecap(
5723
- sessionId,
5724
- this.sessionCtx(conn, sessionId, loopback)
5725
- );
5726
- this.replyConn(conn, id, result);
6173
+ await this.withMutableOwned(conn, sessionId, id, async () => {
6174
+ const result = await this.bridge.generateSessionRecap(
6175
+ sessionId,
6176
+ this.sessionCtx(conn, sessionId, loopback)
6177
+ );
6178
+ this.replyConn(conn, id, result);
6179
+ });
5727
6180
  return;
5728
6181
  }
5729
6182
  case `${QWEN_METHOD_NS}session/btw`: {
5730
6183
  const sessionId = String(params["sessionId"] ?? "");
5731
- if (!this.requireOwned(conn, sessionId, id)) return;
5732
- const rawQ = params["question"];
5733
- if (typeof rawQ !== "string" || rawQ.trim().length === 0 || rawQ.length > BTW_MAX_INPUT_LENGTH) {
5734
- if (id !== void 0)
5735
- conn.sendConn(
5736
- error(
5737
- id,
5738
- RPC.INVALID_PARAMS,
5739
- `\`question\` required, non-empty, max ${BTW_MAX_INPUT_LENGTH} chars`
5740
- )
5741
- );
5742
- return;
5743
- }
5744
- const result = await this.bridge.generateSessionBtw(
5745
- sessionId,
5746
- rawQ.trim(),
5747
- void 0,
5748
- this.sessionCtx(conn, sessionId, loopback)
5749
- );
5750
- this.replyConn(conn, id, result);
6184
+ await this.withMutableOwned(conn, sessionId, id, async () => {
6185
+ const rawQ = params["question"];
6186
+ if (typeof rawQ !== "string" || rawQ.trim().length === 0 || rawQ.length > BTW_MAX_INPUT_LENGTH) {
6187
+ if (id !== void 0)
6188
+ conn.sendConn(
6189
+ error(
6190
+ id,
6191
+ RPC.INVALID_PARAMS,
6192
+ `\`question\` required, non-empty, max ${BTW_MAX_INPUT_LENGTH} chars`
6193
+ )
6194
+ );
6195
+ return;
6196
+ }
6197
+ const result = await this.bridge.generateSessionBtw(
6198
+ sessionId,
6199
+ rawQ.trim(),
6200
+ void 0,
6201
+ this.sessionCtx(conn, sessionId, loopback)
6202
+ );
6203
+ this.replyConn(conn, id, result);
6204
+ });
5751
6205
  return;
5752
6206
  }
5753
6207
  case `${QWEN_METHOD_NS}session/shell`: {
@@ -5758,50 +6212,52 @@ var AcpDispatcher = class {
5758
6212
  }
5759
6213
  return;
5760
6214
  }
5761
- if (!this.requireOwned(conn, sessionId, id)) return;
5762
- const binding = conn.sessions.get(sessionId);
5763
- const clientId = binding?.clientId;
5764
- if (!clientId) {
5765
- if (id !== void 0) {
5766
- conn.sendConn(
5767
- rpcErrorFrame(id, new SessionShellClientRequiredError())
5768
- );
6215
+ await this.withMutableOwned(conn, sessionId, id, async () => {
6216
+ const binding = conn.sessions.get(sessionId);
6217
+ const clientId = binding?.clientId;
6218
+ if (!clientId) {
6219
+ if (id !== void 0) {
6220
+ conn.sendConn(
6221
+ rpcErrorFrame(id, new SessionShellClientRequiredError())
6222
+ );
6223
+ }
6224
+ return;
5769
6225
  }
5770
- return;
5771
- }
5772
- const rawCmd = params["command"];
5773
- if (typeof rawCmd !== "string" || rawCmd.trim().length === 0) {
5774
- if (id !== void 0)
5775
- conn.sendConn(
5776
- error(
5777
- id,
5778
- RPC.INVALID_PARAMS,
5779
- "`command` required and must be non-empty"
5780
- )
5781
- );
5782
- return;
5783
- }
5784
- const logSessionId = logSafe(sessionId.slice(0, 8));
5785
- const logClientId = logSafe(String(conn.clientId?.slice(0, 8)));
5786
- const logCommand = logSafe(rawCmd.slice(0, 120));
5787
- writeStderrLine(
5788
- `qwen serve: /acp session/shell session=${logSessionId} client=${logClientId} cmd=${logCommand}`
5789
- );
5790
- const result = await this.bridge.executeShellCommand(
5791
- sessionId,
5792
- rawCmd,
5793
- binding.abort.signal,
5794
- this.sessionCtx(conn, sessionId, loopback)
5795
- );
5796
- this.replyConn(conn, id, result);
6226
+ const rawCmd = params["command"];
6227
+ if (typeof rawCmd !== "string" || rawCmd.trim().length === 0) {
6228
+ if (id !== void 0)
6229
+ conn.sendConn(
6230
+ error(
6231
+ id,
6232
+ RPC.INVALID_PARAMS,
6233
+ "`command` required and must be non-empty"
6234
+ )
6235
+ );
6236
+ return;
6237
+ }
6238
+ const logSessionId = logSafe(sessionId.slice(0, 8));
6239
+ const logClientId = logSafe(String(conn.clientId?.slice(0, 8)));
6240
+ const logCommand = logSafe(rawCmd.slice(0, 120));
6241
+ writeStderrLine(
6242
+ `qwen serve: /acp session/shell session=${logSessionId} client=${logClientId} cmd=${logCommand}`
6243
+ );
6244
+ const result = await this.bridge.executeShellCommand(
6245
+ sessionId,
6246
+ rawCmd,
6247
+ binding.abort.signal,
6248
+ this.sessionCtx(conn, sessionId, loopback)
6249
+ );
6250
+ this.replyConn(conn, id, result);
6251
+ });
5797
6252
  return;
5798
6253
  }
5799
6254
  case `${QWEN_METHOD_NS}session/detach`: {
5800
6255
  const sessionId = String(params["sessionId"] ?? "");
5801
- if (!this.requireOwned(conn, sessionId, id)) return;
5802
- const ctx = this.sessionCtx(conn, sessionId, loopback);
5803
- await this.bridge.detachClient(sessionId, ctx.clientId);
5804
- this.replyConn(conn, id, { ok: true });
6256
+ await this.withMutableOwned(conn, sessionId, id, async () => {
6257
+ const ctx = this.sessionCtx(conn, sessionId, loopback);
6258
+ await this.bridge.detachClient(sessionId, ctx.clientId);
6259
+ this.replyConn(conn, id, { ok: true });
6260
+ });
5805
6261
  return;
5806
6262
  }
5807
6263
  case `${QWEN_METHOD_NS}session/context_usage`: {
@@ -5913,6 +6369,110 @@ var AcpDispatcher = class {
5913
6369
  }
5914
6370
  return;
5915
6371
  }
6372
+ case `${QWEN_METHOD_NS}workspace/memory/remember`: {
6373
+ const content = params["content"];
6374
+ if (typeof content !== "string" || !content.trim()) {
6375
+ if (id !== void 0) {
6376
+ conn.sendConn(
6377
+ error(
6378
+ id,
6379
+ RPC.INVALID_PARAMS,
6380
+ "`content` must be a non-empty string"
6381
+ )
6382
+ );
6383
+ }
6384
+ return;
6385
+ }
6386
+ if (Buffer.byteLength(content, "utf8") > MAX_REMEMBER_CONTENT_BYTES) {
6387
+ if (id !== void 0) {
6388
+ conn.sendConn(
6389
+ error(
6390
+ id,
6391
+ RPC.INVALID_PARAMS,
6392
+ `\`content\` exceeds the ${MAX_REMEMBER_CONTENT_BYTES}-byte limit`
6393
+ )
6394
+ );
6395
+ }
6396
+ return;
6397
+ }
6398
+ const rawContextMode = params["contextMode"] ?? "workspace";
6399
+ if (rawContextMode !== "workspace" && rawContextMode !== "clean") {
6400
+ if (id !== void 0) {
6401
+ conn.sendConn(
6402
+ error(
6403
+ id,
6404
+ RPC.INVALID_PARAMS,
6405
+ '`contextMode` must be "workspace", "clean", or omitted'
6406
+ )
6407
+ );
6408
+ }
6409
+ return;
6410
+ }
6411
+ try {
6412
+ const available = await this.bridge.isWorkspaceMemoryRememberAvailable();
6413
+ if (!available) {
6414
+ if (id !== void 0) {
6415
+ conn.sendConn(
6416
+ error(
6417
+ id,
6418
+ -32009,
6419
+ "Managed memory is unavailable for this daemon workspace",
6420
+ {
6421
+ errorKind: "managed_memory_unavailable",
6422
+ httpStatus: 409
6423
+ }
6424
+ )
6425
+ );
6426
+ }
6427
+ return;
6428
+ }
6429
+ const task = this.workspaceRememberLane.enqueue({
6430
+ content: content.trim(),
6431
+ contextMode: rawContextMode,
6432
+ ...conn.clientId ? { originatorClientId: conn.clientId } : {}
6433
+ });
6434
+ this.replyConn(conn, id, task);
6435
+ } catch (err) {
6436
+ const code = extractRememberErrorCode(err);
6437
+ if (id !== void 0) {
6438
+ conn.sendConn(
6439
+ error(
6440
+ id,
6441
+ -32099,
6442
+ code === "remember_queue_full" ? "Workspace memory remember queue is full." : code === "managed_memory_unavailable" ? "Managed memory is unavailable for this daemon workspace" : "Workspace memory remember failed.",
6443
+ {
6444
+ errorKind: code,
6445
+ httpStatus: code === "remember_queue_full" ? 429 : code === "managed_memory_unavailable" ? 409 : 500
6446
+ }
6447
+ )
6448
+ );
6449
+ }
6450
+ }
6451
+ return;
6452
+ }
6453
+ case `${QWEN_METHOD_NS}workspace/memory/remember/get`: {
6454
+ const taskId = params["taskId"];
6455
+ if (typeof taskId !== "string" || taskId.length === 0) {
6456
+ if (id !== void 0) {
6457
+ conn.sendConn(error(id, RPC.INVALID_PARAMS, "`taskId` required"));
6458
+ }
6459
+ return;
6460
+ }
6461
+ const task = this.workspaceRememberLane.get(taskId, conn.clientId);
6462
+ if (!task) {
6463
+ if (id !== void 0) {
6464
+ conn.sendConn(
6465
+ error(id, -32004, "Workspace memory remember task not found", {
6466
+ errorKind: "remember_task_not_found",
6467
+ httpStatus: 404
6468
+ })
6469
+ );
6470
+ }
6471
+ return;
6472
+ }
6473
+ this.replyConn(conn, id, task);
6474
+ return;
6475
+ }
5916
6476
  case `${QWEN_METHOD_NS}file/read`: {
5917
6477
  const p = String(params["path"] ?? "");
5918
6478
  if (!p) {
@@ -6428,60 +6988,58 @@ var AcpDispatcher = class {
6428
6988
  return;
6429
6989
  }
6430
6990
  case `${QWEN_METHOD_NS}sessions/delete`: {
6431
- const sessionIds = params["sessionIds"];
6432
- if (!Array.isArray(sessionIds) || sessionIds.length === 0 || sessionIds.length > 100 || !sessionIds.every((s) => typeof s === "string")) {
6433
- if (id !== void 0)
6434
- conn.sendConn(
6435
- error(
6436
- id,
6437
- RPC.INVALID_PARAMS,
6438
- "`sessionIds` must be non-empty string array (max 100)"
6439
- )
6440
- );
6441
- return;
6442
- }
6443
- const ids = [...new Set(sessionIds)];
6444
- const closeErrors = [];
6445
- const closedIds = [];
6446
- await Promise.allSettled(
6447
- ids.map(async (sid) => {
6448
- try {
6449
- await this.bridge.closeSession(sid);
6450
- closedIds.push(sid);
6451
- } catch (err) {
6452
- const msg2 = err instanceof Error ? err.message : String(err);
6453
- if (err instanceof Error && err.name === "SessionNotFoundError") {
6454
- closedIds.push(sid);
6455
- } else {
6456
- const safeSessionId = logSafe(sid.slice(0, 8));
6457
- const safeMessage = logSafe(msg2);
6458
- writeStderrLine(
6459
- `qwen serve: /acp sessions/delete closeSession(${safeSessionId}) failed: ${safeMessage}`
6460
- );
6461
- closeErrors.push({ sessionId: sid, error: msg2 });
6462
- }
6463
- }
6464
- })
6465
- );
6991
+ const ids = this.parseSessionIds(params);
6466
6992
  const svc = new SessionService(this.boundWorkspace);
6467
- const removeResult = await svc.removeSessions(closedIds);
6468
- for (const e of removeResult.errors) {
6469
- const safeSessionId = logSafe(e.sessionId.slice(0, 8));
6470
- const safeMessage = logSafe(errMsg(e.error));
6471
- writeStderrLine(
6472
- `qwen serve: /acp sessions/delete removeSessions(${safeSessionId}) failed: ${safeMessage}`
6473
- );
6474
- }
6993
+ const result = await deleteDaemonSessions({
6994
+ sessionIds: ids,
6995
+ service: svc,
6996
+ bridge: this.bridge,
6997
+ coordinator: this.archiveCoordinator,
6998
+ onError: /* @__PURE__ */ __name(({ phase, sessionId, error: error2 }) => {
6999
+ const safeSessionId = logSafe(sessionId.slice(0, 8));
7000
+ const safeMessage = logSafe(error2);
7001
+ writeStderrLine(
7002
+ `qwen serve: /acp sessions/delete ${phase}Session(${safeSessionId}) failed: ${safeMessage}`
7003
+ );
7004
+ }, "onError")
7005
+ });
7006
+ this.replyConn(conn, id, result);
7007
+ return;
7008
+ }
7009
+ case `${QWEN_METHOD_NS}sessions/archive`: {
7010
+ const ids = this.parseSessionIds(params);
7011
+ const svc = new SessionService(this.boundWorkspace, {
7012
+ onWarning: logSessionArchiveWarning
7013
+ });
7014
+ const result = await archiveDaemonSessions({
7015
+ sessionIds: ids,
7016
+ service: svc,
7017
+ bridge: this.bridge,
7018
+ coordinator: this.archiveCoordinator
7019
+ });
7020
+ this.replyConn(conn, id, {
7021
+ archived: result.archived,
7022
+ alreadyArchived: result.alreadyArchived,
7023
+ notFound: result.notFound,
7024
+ errors: this.serializeSessionErrors(result.errors)
7025
+ });
7026
+ return;
7027
+ }
7028
+ case `${QWEN_METHOD_NS}sessions/unarchive`: {
7029
+ const ids = this.parseSessionIds(params);
7030
+ const svc = new SessionService(this.boundWorkspace, {
7031
+ onWarning: logSessionArchiveWarning
7032
+ });
7033
+ const result = await unarchiveDaemonSessions({
7034
+ sessionIds: ids,
7035
+ service: svc,
7036
+ coordinator: this.archiveCoordinator
7037
+ });
6475
7038
  this.replyConn(conn, id, {
6476
- removed: removeResult.removed,
6477
- notFound: removeResult.notFound,
6478
- errors: [
6479
- ...closeErrors,
6480
- ...removeResult.errors.map((e) => ({
6481
- sessionId: e.sessionId,
6482
- error: errMsg(e.error)
6483
- }))
6484
- ]
7039
+ unarchived: result.unarchived,
7040
+ alreadyActive: result.alreadyActive,
7041
+ notFound: result.notFound,
7042
+ errors: this.serializeSessionErrors(result.errors)
6485
7043
  });
6486
7044
  return;
6487
7045
  }
@@ -7708,9 +8266,9 @@ var SseStream = class {
7708
8266
  return next;
7709
8267
  }
7710
8268
  doWrite(chunk) {
7711
- return new Promise((resolve4, reject) => {
8269
+ return new Promise((resolve5, reject) => {
7712
8270
  if (this.closed || this.res.writableEnded) {
7713
- resolve4();
8271
+ resolve5();
7714
8272
  return;
7715
8273
  }
7716
8274
  let ok;
@@ -7721,7 +8279,7 @@ var SseStream = class {
7721
8279
  return;
7722
8280
  }
7723
8281
  if (ok) {
7724
- resolve4();
8282
+ resolve5();
7725
8283
  return;
7726
8284
  }
7727
8285
  const cleanup = /* @__PURE__ */ __name(() => {
@@ -7731,11 +8289,11 @@ var SseStream = class {
7731
8289
  }, "cleanup");
7732
8290
  const onDrain = /* @__PURE__ */ __name(() => {
7733
8291
  cleanup();
7734
- resolve4();
8292
+ resolve5();
7735
8293
  }, "onDrain");
7736
8294
  const onCloseEv = /* @__PURE__ */ __name(() => {
7737
8295
  cleanup();
7738
- resolve4();
8296
+ resolve5();
7739
8297
  }, "onCloseEv");
7740
8298
  const onErrorEv = /* @__PURE__ */ __name((err) => {
7741
8299
  cleanup();
@@ -7797,14 +8355,14 @@ var WsStream = class {
7797
8355
  send(message, _id) {
7798
8356
  const data = JSON.stringify(message);
7799
8357
  const next = this.writeChain.then(
7800
- () => new Promise((resolve4, reject) => {
8358
+ () => new Promise((resolve5, reject) => {
7801
8359
  if (this._closed) {
7802
- resolve4();
8360
+ resolve5();
7803
8361
  return;
7804
8362
  }
7805
8363
  this.ws.send(data, (err) => {
7806
8364
  if (err) reject(err);
7807
- else resolve4();
8365
+ else resolve5();
7808
8366
  });
7809
8367
  })
7810
8368
  );
@@ -8088,16 +8646,14 @@ var CdpReverseLink = class {
8088
8646
  nextId = 1;
8089
8647
  pending = /* @__PURE__ */ new Map();
8090
8648
  disposed = false;
8649
+ attached = false;
8650
+ attachPromise;
8091
8651
  /** Resolver for the in-flight `cdp_attach` (if any). */
8092
8652
  pendingAttach;
8093
- /**
8094
- * Opens once the initial `cdp_attach` settles (success OR failure).
8095
- * `forwardToTab` awaits this so page-domain commands never race the
8096
- * extension's async `chrome.debugger.attach`. Undefined until {@link attach}.
8097
- */
8098
- attachGate;
8099
8653
  /** Called when the extension reports the tab detached. */
8100
8654
  onDetach;
8655
+ /** Called when lazy `cdp_attach` fails before any page command can run. */
8656
+ onAttachFailure;
8101
8657
  /** Wire the emulator whose `forwardToTab` this link backs. */
8102
8658
  bindEmulator(emulator) {
8103
8659
  this.emulator = emulator;
@@ -8110,10 +8666,8 @@ var CdpReverseLink = class {
8110
8666
  if (this.disposed) {
8111
8667
  throw { code: -32e3, message: "CDP tunnel closed" };
8112
8668
  }
8113
- if (this.attachGate) {
8114
- await this.attachGate;
8115
- }
8116
- return new Promise((resolve4, reject) => {
8669
+ await this.attach();
8670
+ return new Promise((resolve5, reject) => {
8117
8671
  if (this.disposed) {
8118
8672
  reject({ code: -32e3, message: "CDP tunnel closed" });
8119
8673
  return;
@@ -8124,7 +8678,7 @@ var CdpReverseLink = class {
8124
8678
  `CDP command id=${id} method=${method} timed out after ${this.commandTimeoutMs}ms`
8125
8679
  );
8126
8680
  const progressTimer = this.armProgressLog(id, method);
8127
- this.pending.set(id, { resolve: resolve4, reject, timer, progressTimer });
8681
+ this.pending.set(id, { resolve: resolve5, reject, timer, progressTimer });
8128
8682
  try {
8129
8683
  this.log?.(
8130
8684
  `qwen serve: /cdp forwarded command id=${id} method=${method} to extension`
@@ -8148,8 +8702,10 @@ var CdpReverseLink = class {
8148
8702
  * once the extension acks `cdp_attached` (or rejects on error/timeout).
8149
8703
  */
8150
8704
  attach() {
8151
- const result = new Promise(
8152
- (resolve4, reject) => {
8705
+ if (this.attached) return Promise.resolve({});
8706
+ if (this.attachPromise) return this.attachPromise;
8707
+ this.attachPromise = new Promise(
8708
+ (resolve5, reject) => {
8153
8709
  if (this.disposed) {
8154
8710
  reject({ code: -32e3, message: "CDP tunnel closed" });
8155
8711
  return;
@@ -8159,7 +8715,7 @@ var CdpReverseLink = class {
8159
8715
  this.pendingAttach = {
8160
8716
  id,
8161
8717
  pending: {
8162
- resolve: /* @__PURE__ */ __name((result2) => resolve4(result2 ?? {}), "resolve"),
8718
+ resolve: /* @__PURE__ */ __name((result) => resolve5(result ?? {}), "resolve"),
8163
8719
  reject,
8164
8720
  timer
8165
8721
  }
@@ -8175,12 +8731,24 @@ var CdpReverseLink = class {
8175
8731
  });
8176
8732
  }
8177
8733
  }
8734
+ ).then(
8735
+ (info) => {
8736
+ this.emulator?.setTabInfo(info);
8737
+ return info;
8738
+ },
8739
+ (err) => {
8740
+ this.attached = false;
8741
+ this.onAttachFailure?.(
8742
+ err instanceof Error ? err.message : err?.message ?? String(err)
8743
+ );
8744
+ throw err;
8745
+ }
8178
8746
  );
8179
- this.attachGate = result.then(
8180
- () => void 0,
8181
- () => void 0
8182
- );
8183
- return result;
8747
+ void this.attachPromise.finally(() => {
8748
+ this.attachPromise = void 0;
8749
+ }).catch(() => {
8750
+ });
8751
+ return this.attachPromise;
8184
8752
  }
8185
8753
  /**
8186
8754
  * Feed one inbound frame from the extension `/acp` socket. Returns true if
@@ -8236,16 +8804,24 @@ var CdpReverseLink = class {
8236
8804
  this.pendingAttach = void 0;
8237
8805
  clearTimeout(attach.pending.timer);
8238
8806
  if (frame.error) {
8807
+ this.attached = false;
8239
8808
  attach.pending.reject({
8240
8809
  code: -32e3,
8241
8810
  message: frame.error.message ?? "cdp_attach failed"
8242
8811
  });
8243
8812
  return;
8244
8813
  }
8814
+ this.attached = true;
8245
8815
  attach.pending.resolve({ url: frame.url, title: frame.title });
8246
8816
  }
8247
8817
  handleDetach(frame) {
8248
8818
  const reason = typeof frame.reason === "string" ? frame.reason : "tab detached";
8819
+ this.attached = false;
8820
+ if (this.pendingAttach) {
8821
+ clearTimeout(this.pendingAttach.pending.timer);
8822
+ this.pendingAttach.pending.reject({ code: -32e3, message: reason });
8823
+ this.pendingAttach = void 0;
8824
+ }
8249
8825
  this.onDetach?.(reason);
8250
8826
  }
8251
8827
  armTimeout(id, message) {
@@ -8261,13 +8837,13 @@ var CdpReverseLink = class {
8261
8837
  }
8262
8838
  armProgressLog(id, method) {
8263
8839
  if (!this.log) return void 0;
8264
- const delay = Math.min(COMMAND_PROGRESS_LOG_MS, this.commandTimeoutMs);
8265
- if (delay >= this.commandTimeoutMs) return void 0;
8840
+ const delay2 = Math.min(COMMAND_PROGRESS_LOG_MS, this.commandTimeoutMs);
8841
+ if (delay2 >= this.commandTimeoutMs) return void 0;
8266
8842
  const timer = setTimeout(() => {
8267
8843
  this.log?.(
8268
- `qwen serve: /cdp still waiting for command id=${id} method=${method} after ${delay}ms`
8844
+ `qwen serve: /cdp still waiting for command id=${id} method=${method} after ${delay2}ms`
8269
8845
  );
8270
- }, delay);
8846
+ }, delay2);
8271
8847
  timer.unref?.();
8272
8848
  return timer;
8273
8849
  }
@@ -8306,6 +8882,8 @@ var CdpReverseLink = class {
8306
8882
  this.pendingAttach.pending.reject({ code: -32e3, message: reason });
8307
8883
  this.pendingAttach = void 0;
8308
8884
  }
8885
+ this.attached = false;
8886
+ this.attachPromise = void 0;
8309
8887
  this.emulator = void 0;
8310
8888
  }
8311
8889
  };
@@ -8595,6 +9173,14 @@ function attachCdpClient(ws, registry, log) {
8595
9173
  bridge.onExtensionGone = void 0;
8596
9174
  }
8597
9175
  }, "dispose");
9176
+ link.onAttachFailure = (reason) => {
9177
+ log(`qwen serve: /cdp attach failed (${reason}); closing puppeteer socket`);
9178
+ dispose(`cdp_attach failed: ${reason}`, false);
9179
+ try {
9180
+ ws.close(CLOSE_NO_BRIDGE, "cdp attach failed");
9181
+ } catch {
9182
+ }
9183
+ };
8598
9184
  ws.on("pong", () => {
8599
9185
  heartbeatAlive = true;
8600
9186
  });
@@ -8627,15 +9213,6 @@ function attachCdpClient(ws, registry, log) {
8627
9213
  );
8628
9214
  dispose("puppeteer /cdp socket error");
8629
9215
  });
8630
- void link.attach().then((info) => emulator.setTabInfo(info)).catch((err) => {
8631
- log(
8632
- `qwen serve: /cdp attach failed: ${err && typeof err === "object" && "message" in err ? String(err.message) : String(err)}; closing puppeteer socket so a reconnect can retry attach`
8633
- );
8634
- try {
8635
- ws.close(CLOSE_NO_BRIDGE, "cdp_attach failed");
8636
- } catch {
8637
- }
8638
- });
8639
9216
  log("qwen serve: /cdp puppeteer client bound to extension bridge");
8640
9217
  }
8641
9218
  __name(attachCdpClient, "attachCdpClient");
@@ -8645,6 +9222,71 @@ var ACP_CONNECTION_HEADER = "acp-connection-id";
8645
9222
  var ACP_SESSION_HEADER = "acp-session-id";
8646
9223
  var CDP_PATH = "/cdp";
8647
9224
  var CDP_BRIDGE_CLIENT_NAME = "qwen-cdp-bridge";
9225
+ var CHROME_DEVTOOLS_MCP_SERVER_NAME = "chrome-devtools";
9226
+ var RUNTIME_MCP_RETRY_DELAY_MS = 250;
9227
+ var RUNTIME_MCP_RETRY_ATTEMPTS = 20;
9228
+ var requireFromHere = createRequire(import.meta.url);
9229
+ function formatCdpEndpointHost(hostname) {
9230
+ const host = hostname?.trim() || "127.0.0.1";
9231
+ if (host === "0.0.0.0" || host === "::" || host === "[::]") {
9232
+ return "127.0.0.1";
9233
+ }
9234
+ const unbracketed = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
9235
+ return unbracketed.includes(":") ? `[${unbracketed}]` : unbracketed;
9236
+ }
9237
+ __name(formatCdpEndpointHost, "formatCdpEndpointHost");
9238
+ function isAcpChannelUnavailable(err) {
9239
+ return err?.data?.errorKind === "acp_channel_unavailable";
9240
+ }
9241
+ __name(isAcpChannelUnavailable, "isAcpChannelUnavailable");
9242
+ function delay(ms) {
9243
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
9244
+ }
9245
+ __name(delay, "delay");
9246
+ function buildChromeDevToolsMcpRuntimeConfigFromPackage(localPort, pkgJsonPath, pkgBin, hostname) {
9247
+ if (localPort === void 0 || !Number.isInteger(localPort) || localPort <= 0) {
9248
+ return void 0;
9249
+ }
9250
+ const binRel = typeof pkgBin === "string" ? pkgBin : Object.values(pkgBin ?? {})[0];
9251
+ if (!binRel) return void 0;
9252
+ const pkgDir = path9.dirname(pkgJsonPath);
9253
+ const binPath = path9.resolve(pkgDir, binRel);
9254
+ const binRelToPkg = path9.relative(pkgDir, binPath);
9255
+ if (binRelToPkg.startsWith("..") || path9.isAbsolute(binRelToPkg)) {
9256
+ return void 0;
9257
+ }
9258
+ return {
9259
+ command: process.execPath,
9260
+ args: [
9261
+ binPath,
9262
+ "--wsEndpoint",
9263
+ `ws://${formatCdpEndpointHost(hostname)}:${localPort}/cdp`
9264
+ ],
9265
+ alwaysLoadTools: true,
9266
+ [RUNTIME_MCP_IF_ABSENT_CONFIG_FLAG]: true
9267
+ };
9268
+ }
9269
+ __name(buildChromeDevToolsMcpRuntimeConfigFromPackage, "buildChromeDevToolsMcpRuntimeConfigFromPackage");
9270
+ function buildChromeDevToolsMcpRuntimeConfig(localPort, hostname) {
9271
+ try {
9272
+ const pkgJsonPath = requireFromHere.resolve(
9273
+ "chrome-devtools-mcp/package.json"
9274
+ );
9275
+ const pkg = requireFromHere("chrome-devtools-mcp/package.json");
9276
+ return buildChromeDevToolsMcpRuntimeConfigFromPackage(
9277
+ localPort,
9278
+ pkgJsonPath,
9279
+ pkg.bin,
9280
+ hostname
9281
+ );
9282
+ } catch (err) {
9283
+ writeStderrLine(
9284
+ `qwen serve: chrome-devtools-mcp package not resolvable: ${err instanceof Error ? err.message : String(err)}`
9285
+ );
9286
+ return void 0;
9287
+ }
9288
+ }
9289
+ __name(buildChromeDevToolsMcpRuntimeConfig, "buildChromeDevToolsMcpRuntimeConfig");
8648
9290
  var WS_BEARER_SUBPROTOCOL_PREFIX = "qwen-bearer.";
8649
9291
  function extractUpgradeBearer(req) {
8650
9292
  const authHeader = req.headers["authorization"];
@@ -8695,6 +9337,7 @@ var WS_READ_METHODS = /* @__PURE__ */ new Set([
8695
9337
  "_qwen/workspace/agents/list",
8696
9338
  "_qwen/workspace/agents/get",
8697
9339
  "_qwen/workspace/memory",
9340
+ "_qwen/workspace/memory/remember/get",
8698
9341
  "_qwen/workspace/auth/status",
8699
9342
  "_qwen/workspace/auth/device_flow/get",
8700
9343
  "_qwen/file/read",
@@ -8718,7 +9361,7 @@ var MAX_INFLIGHT_MCP_DISPATCH = 8;
8718
9361
  function mountAcpHttp(app, bridge, opts) {
8719
9362
  const enabled = opts.enabled ?? process.env["QWEN_SERVE_ACP_HTTP"] !== "0";
8720
9363
  if (!enabled) return void 0;
8721
- const path12 = opts.path ?? "/acp";
9364
+ const path13 = opts.path ?? "/acp";
8722
9365
  const dispatcherRef = {};
8723
9366
  const registry = new ConnectionRegistry(
8724
9367
  (req, clientId) => {
@@ -8741,17 +9384,121 @@ function mountAcpHttp(app, bridge, opts) {
8741
9384
  },
8742
9385
  opts.maxConnections
8743
9386
  );
9387
+ let cdpMcpRegistered = false;
9388
+ let cdpMcpRegistering;
9389
+ let cdpMcpTerminalSkipLogged = false;
9390
+ async function removeChromeDevToolsMcp(originatorClientId) {
9391
+ if (!cdpMcpRegistered) return;
9392
+ cdpMcpRegistered = false;
9393
+ try {
9394
+ await bridge.removeRuntimeMcpServer(
9395
+ CHROME_DEVTOOLS_MCP_SERVER_NAME,
9396
+ originatorClientId
9397
+ );
9398
+ } catch (err) {
9399
+ writeStderrLine(
9400
+ `qwen serve: failed to remove ${CHROME_DEVTOOLS_MCP_SERVER_NAME} runtime MCP: ${err instanceof Error ? err.message : String(err)}`
9401
+ );
9402
+ }
9403
+ }
9404
+ __name(removeChromeDevToolsMcp, "removeChromeDevToolsMcp");
9405
+ function removeChromeDevToolsMcpIfUnused(originatorClientId) {
9406
+ void (async () => {
9407
+ if (opts.cdpTunnelRegistry?.hasActive()) return;
9408
+ if (cdpMcpRegistering) await cdpMcpRegistering;
9409
+ if (opts.cdpTunnelRegistry?.hasActive()) return;
9410
+ await removeChromeDevToolsMcp(originatorClientId);
9411
+ })().catch((err) => {
9412
+ writeStderrLine(
9413
+ `qwen serve: failed to clean up ${CHROME_DEVTOOLS_MCP_SERVER_NAME} runtime MCP: ${err instanceof Error ? err.message : String(err)}`
9414
+ );
9415
+ });
9416
+ }
9417
+ __name(removeChromeDevToolsMcpIfUnused, "removeChromeDevToolsMcpIfUnused");
9418
+ function ensureChromeDevToolsMcpRegistered(localPort, originatorClientId) {
9419
+ if (opts.token) {
9420
+ if (!cdpMcpTerminalSkipLogged) {
9421
+ writeStderrLine(
9422
+ `qwen serve: ${CHROME_DEVTOOLS_MCP_SERVER_NAME} runtime MCP skipped because /cdp requires bearer auth`
9423
+ );
9424
+ cdpMcpTerminalSkipLogged = true;
9425
+ }
9426
+ return;
9427
+ }
9428
+ if (cdpMcpRegistered || cdpMcpRegistering || cdpMcpTerminalSkipLogged) {
9429
+ return;
9430
+ }
9431
+ const runtimeConfig = buildChromeDevToolsMcpRuntimeConfig(
9432
+ localPort,
9433
+ opts.hostname
9434
+ );
9435
+ if (!runtimeConfig) {
9436
+ cdpMcpTerminalSkipLogged = true;
9437
+ return;
9438
+ }
9439
+ cdpMcpRegistering = (async () => {
9440
+ try {
9441
+ let result;
9442
+ for (let attempt = 0; ; attempt++) {
9443
+ try {
9444
+ result = await bridge.addRuntimeMcpServer(
9445
+ CHROME_DEVTOOLS_MCP_SERVER_NAME,
9446
+ runtimeConfig,
9447
+ originatorClientId
9448
+ );
9449
+ break;
9450
+ } catch (err) {
9451
+ if (!isAcpChannelUnavailable(err) || attempt >= RUNTIME_MCP_RETRY_ATTEMPTS || !opts.cdpTunnelRegistry?.hasActive()) {
9452
+ throw err;
9453
+ }
9454
+ await delay(RUNTIME_MCP_RETRY_DELAY_MS);
9455
+ }
9456
+ }
9457
+ if (result.skipped) {
9458
+ writeStderrLine(
9459
+ `qwen serve: ${CHROME_DEVTOOLS_MCP_SERVER_NAME} runtime MCP skipped: ${result.reason ?? "unknown"}`
9460
+ );
9461
+ return;
9462
+ }
9463
+ if (result.shadowedSettings) {
9464
+ await bridge.removeRuntimeMcpServer(
9465
+ CHROME_DEVTOOLS_MCP_SERVER_NAME,
9466
+ originatorClientId
9467
+ ).catch(() => {
9468
+ });
9469
+ cdpMcpTerminalSkipLogged = true;
9470
+ writeStderrLine(
9471
+ `qwen serve: ${CHROME_DEVTOOLS_MCP_SERVER_NAME} runtime MCP skipped because settings already define it`
9472
+ );
9473
+ return;
9474
+ }
9475
+ cdpMcpRegistered = true;
9476
+ if (!opts.cdpTunnelRegistry?.hasActive()) {
9477
+ await removeChromeDevToolsMcp(originatorClientId);
9478
+ }
9479
+ } catch (err) {
9480
+ writeStderrLine(
9481
+ `qwen serve: failed to add ${CHROME_DEVTOOLS_MCP_SERVER_NAME} runtime MCP: ${err instanceof Error ? err.message : String(err)}`
9482
+ );
9483
+ } finally {
9484
+ cdpMcpRegistering = void 0;
9485
+ }
9486
+ })();
9487
+ }
9488
+ __name(ensureChromeDevToolsMcpRegistered, "ensureChromeDevToolsMcpRegistered");
8744
9489
  const dispatcher = new AcpDispatcher(
8745
9490
  bridge,
8746
9491
  opts.boundWorkspace,
8747
9492
  opts.workspace,
9493
+ opts.workspaceRememberLane,
8748
9494
  opts.fsFactory,
8749
9495
  opts.deviceFlowRegistry,
8750
9496
  opts.sessionShellCommandEnabled === true,
8751
- registry
9497
+ registry,
9498
+ opts.archiveCoordinator ?? new SessionArchiveCoordinator()
8752
9499
  );
8753
9500
  dispatcherRef.current = dispatcher;
8754
- app.post(path12, async (req, res) => {
9501
+ app.post(path13, async (req, res) => {
8755
9502
  const ct = req.headers["content-type"];
8756
9503
  if (!ct || !ct.startsWith("application/json")) {
8757
9504
  res.status(415).json({ error: "Content-Type must be application/json" });
@@ -8855,7 +9602,7 @@ function mountAcpHttp(app, bridge, opts) {
8855
9602
  );
8856
9603
  });
8857
9604
  });
8858
- app.get(path12, (req, res) => {
9605
+ app.get(path13, (req, res) => {
8859
9606
  const accept = req.headers["accept"] ?? "";
8860
9607
  if (!accept.includes("text/event-stream")) {
8861
9608
  res.status(406).json({ error: "Accept header must include text/event-stream" });
@@ -8941,7 +9688,7 @@ function mountAcpHttp(app, bridge, opts) {
8941
9688
  onPumpSettled();
8942
9689
  });
8943
9690
  });
8944
- app.delete(path12, (req, res) => {
9691
+ app.delete(path13, (req, res) => {
8945
9692
  const connectionId = headerOf(req, ACP_CONNECTION_HEADER);
8946
9693
  if (!connectionId) {
8947
9694
  res.status(400).json({ error: "Missing Acp-Connection-Id" });
@@ -8983,6 +9730,7 @@ function mountAcpHttp(app, bridge, opts) {
8983
9730
  const expectedTokenHash = opts.token ? createHash3("sha256").update(opts.token).digest() : void 0;
8984
9731
  upgradeListener = /* @__PURE__ */ __name((req, socket, head) => {
8985
9732
  const rawAddr = socket.remoteAddress ?? "ws-unknown";
9733
+ const localPort = socket.localPort;
8986
9734
  const logReject = /* @__PURE__ */ __name((reason) => {
8987
9735
  writeStderrLine(
8988
9736
  `qwen serve: WebSocket upgrade rejected (${reason}) from ${rawAddr}`
@@ -9003,7 +9751,7 @@ function mountAcpHttp(app, bridge, opts) {
9003
9751
  const extraRoute = opts.extraWsRoutes?.find(
9004
9752
  (route) => route.path === url.pathname
9005
9753
  );
9006
- if (url.pathname !== path12 && !isCdpPath && !extraRoute) {
9754
+ if (url.pathname !== path13 && !isCdpPath && !extraRoute) {
9007
9755
  logReject(`unknown-path ${url.pathname}`);
9008
9756
  socket.destroy();
9009
9757
  return;
@@ -9011,7 +9759,6 @@ function mountAcpHttp(app, bridge, opts) {
9011
9759
  const fromLoopback = isLoopbackSocket(socket);
9012
9760
  if (fromLoopback) {
9013
9761
  const host = (req.headers["host"] ?? "").toLowerCase();
9014
- const localPort = socket.localPort;
9015
9762
  const allowed = /* @__PURE__ */ new Set([
9016
9763
  `localhost:${localPort}`,
9017
9764
  `127.0.0.1:${localPort}`,
@@ -9028,7 +9775,6 @@ function mountAcpHttp(app, bridge, opts) {
9028
9775
  const origin = req.headers["origin"];
9029
9776
  if (origin) {
9030
9777
  try {
9031
- const localPort = socket.localPort;
9032
9778
  const isLoopbackOrigin = isSameLoopbackOrigin(origin, localPort);
9033
9779
  const isAllowlistedOrigin = opts.allowedOrigins !== void 0 && opts.allowedOrigins.origins.has(origin.toLowerCase());
9034
9780
  if (!isLoopbackOrigin && !isAllowlistedOrigin) {
@@ -9101,6 +9847,9 @@ function mountAcpHttp(app, bridge, opts) {
9101
9847
  if (cdpBridgeUnregister) {
9102
9848
  cdpBridgeUnregister();
9103
9849
  cdpBridgeUnregister = void 0;
9850
+ removeChromeDevToolsMcpIfUnused(
9851
+ connRef?.connectionId ?? "cdp-bridge"
9852
+ );
9104
9853
  }
9105
9854
  });
9106
9855
  ws.on("message", (rawData) => {
@@ -9322,6 +10071,7 @@ function mountAcpHttp(app, bridge, opts) {
9322
10071
  routeInbound: /* @__PURE__ */ __name(() => false, "routeInbound")
9323
10072
  };
9324
10073
  cdpBridgeUnregister = opts.cdpTunnelRegistry.register(cdpEndpoint);
10074
+ ensureChromeDevToolsMcpRegistered(localPort, conn2.connectionId);
9325
10075
  writeStderrLine(
9326
10076
  `qwen serve: /acp connection ${conn2.connectionId.slice(0, 8)} registered as CDP bridge`
9327
10077
  );
@@ -9392,7 +10142,7 @@ function mountAcpHttp(app, bridge, opts) {
9392
10142
  });
9393
10143
  }, "upgradeListener");
9394
10144
  httpServer.on("upgrade", upgradeListener);
9395
- writeStderrLine(`qwen serve: /acp WebSocket transport enabled on ${path12}`);
10145
+ writeStderrLine(`qwen serve: /acp WebSocket transport enabled on ${path13}`);
9396
10146
  }
9397
10147
  __name(setupWebSocket, "setupWebSocket");
9398
10148
  return {
@@ -9860,7 +10610,7 @@ var CdpTunnelRegistry = class {
9860
10610
  // packages/cli/src/serve/web-shell-static.ts
9861
10611
  init_esbuild_shims();
9862
10612
  var import_express2 = __toESM(require_express(), 1);
9863
- import * as path9 from "node:path";
10613
+ import * as path10 from "node:path";
9864
10614
  var WEB_SHELL_CSP_DIRECTIVES = [
9865
10615
  "default-src 'self'",
9866
10616
  "script-src 'self' 'unsafe-inline' 'unsafe-eval' 'wasm-unsafe-eval'",
@@ -9888,7 +10638,7 @@ function isDocumentNavigation(req) {
9888
10638
  }
9889
10639
  __name(isDocumentNavigation, "isDocumentNavigation");
9890
10640
  function createSendIndex(webShellDir, frameAncestors = []) {
9891
- const indexPath = path9.join(webShellDir, "index.html");
10641
+ const indexPath = path10.join(webShellDir, "index.html");
9892
10642
  const csp = buildWebShellCsp(frameAncestors);
9893
10643
  return (res) => {
9894
10644
  res.status(200).set("Content-Security-Policy", csp).set("X-Content-Type-Options", "nosniff").set("Referrer-Policy", "no-referrer").set(
@@ -9924,7 +10674,7 @@ function mountWebShellAssets(app, webShellDir, frameAncestors = []) {
9924
10674
  const sendIndex = createSendIndex(webShellDir, frameAncestors);
9925
10675
  app.use(
9926
10676
  "/assets",
9927
- import_express2.default.static(path9.join(webShellDir, "assets"), {
10677
+ import_express2.default.static(path10.join(webShellDir, "assets"), {
9928
10678
  index: false,
9929
10679
  immutable: true,
9930
10680
  maxAge: "1y"
@@ -9956,43 +10706,301 @@ function mountWebShellSpaFallback(app, webShellDir, frameAncestors = []) {
9956
10706
  }
9957
10707
  __name(mountWebShellSpaFallback, "mountWebShellSpaFallback");
9958
10708
 
9959
- // packages/cli/src/serve/routes/daemon-status.ts
10709
+ // packages/cli/src/serve/workspace-remember.ts
9960
10710
  init_esbuild_shims();
9961
- function registerDaemonStatusRoutes(app, deps) {
9962
- app.get("/daemon/status", async (req, res) => {
9963
- const detail = parseDaemonStatusDetail(req.query["detail"]);
9964
- if (!detail.ok || !detail.detail) {
9965
- res.status(400).json({
9966
- error: "detail must be one of: summary, full",
9967
- code: "invalid_detail"
10711
+ import { randomUUID as randomUUID2 } from "node:crypto";
10712
+ var debugLogger2 = createDebugLogger("WORKSPACE_REMEMBER");
10713
+ function nowIso() {
10714
+ return (/* @__PURE__ */ new Date()).toISOString();
10715
+ }
10716
+ __name(nowIso, "nowIso");
10717
+ function createRememberTaskId() {
10718
+ return `remember-${randomUUID2()}`;
10719
+ }
10720
+ __name(createRememberTaskId, "createRememberTaskId");
10721
+ function cloneTask(task) {
10722
+ return {
10723
+ taskId: task.taskId,
10724
+ status: task.status,
10725
+ contextMode: task.contextMode,
10726
+ createdAt: task.createdAt,
10727
+ updatedAt: task.updatedAt,
10728
+ result: task.result ? {
10729
+ ...task.result,
10730
+ filesTouched: [...task.result.filesTouched],
10731
+ touchedScopes: [...task.result.touchedScopes]
10732
+ } : void 0,
10733
+ error: task.error ? { ...task.error } : void 0
10734
+ };
10735
+ }
10736
+ __name(cloneTask, "cloneTask");
10737
+ function publicErrorMessage(code) {
10738
+ if (code === "managed_memory_unavailable") {
10739
+ return "Managed memory is unavailable for this daemon workspace.";
10740
+ }
10741
+ if (code === "remember_path_escape") {
10742
+ return "Remember agent touched a path outside managed memory.";
10743
+ }
10744
+ if (code === "remember_queue_full") {
10745
+ return "Workspace memory remember queue is full.";
10746
+ }
10747
+ if (code === "remember_timeout") {
10748
+ return "Workspace memory remember timed out.";
10749
+ }
10750
+ return "Workspace memory remember failed.";
10751
+ }
10752
+ __name(publicErrorMessage, "publicErrorMessage");
10753
+ function publicErrorStatus(code) {
10754
+ if (code === "remember_queue_full") return 429;
10755
+ if (code === "managed_memory_unavailable") return 409;
10756
+ return 500;
10757
+ }
10758
+ __name(publicErrorStatus, "publicErrorStatus");
10759
+ var WorkspaceRememberTaskLane = class _WorkspaceRememberTaskLane {
10760
+ constructor(bridge) {
10761
+ this.bridge = bridge;
10762
+ }
10763
+ static {
10764
+ __name(this, "WorkspaceRememberTaskLane");
10765
+ }
10766
+ static MAX_TASKS = 1e3;
10767
+ static MAX_PENDING = 16;
10768
+ tasks = /* @__PURE__ */ new Map();
10769
+ tail = Promise.resolve();
10770
+ pendingCount() {
10771
+ let count = 0;
10772
+ for (const task of this.tasks.values()) {
10773
+ if (task.status === "queued" || task.status === "running") count++;
10774
+ }
10775
+ return count;
10776
+ }
10777
+ evictTerminalTasks() {
10778
+ if (this.tasks.size <= _WorkspaceRememberTaskLane.MAX_TASKS) return;
10779
+ for (const [id, task] of this.tasks) {
10780
+ if (task.status === "completed" || task.status === "failed") {
10781
+ this.tasks.delete(id);
10782
+ if (this.tasks.size <= _WorkspaceRememberTaskLane.MAX_TASKS) break;
10783
+ }
10784
+ }
10785
+ }
10786
+ enqueue(params) {
10787
+ if (this.pendingCount() >= _WorkspaceRememberTaskLane.MAX_PENDING) {
10788
+ throw Object.assign(new Error("Remember queue is full"), {
10789
+ code: "remember_queue_full"
9968
10790
  });
9969
- return;
9970
10791
  }
9971
- try {
9972
- res.status(200).json(
9973
- await buildDaemonStatusResponse(detail.detail, {
9974
- opts: deps.opts,
9975
- boundWorkspace: deps.boundWorkspace,
9976
- bridge: deps.bridge,
9977
- workspace: deps.workspace,
9978
- daemonLog: deps.daemonLog,
9979
- startup: deps.startup,
9980
- qwenCodeVersion: deps.qwenCodeVersion,
9981
- acpHandle: deps.getAcpHandle(),
9982
- rateLimiter: deps.getRateLimiter(),
9983
- getRestSseActive: deps.getRestSseActive,
9984
- features: deps.currentServeFeatures(),
9985
- protocolVersions: getServeProtocolVersions(),
9986
- supportedDeviceFlowProviders: deps.getSupportedDeviceFlowProviders(),
9987
- deviceFlowRegistry: deps.deviceFlowRegistry,
9988
- sessionShellCommandEnabled: deps.sessionShellCommandEnabled
9989
- })
9990
- );
9991
- } catch (err) {
9992
- writeStderrLine(
9993
- `qwen serve: /daemon/status failed: ${err instanceof Error ? err.message : String(err)}`
9994
- );
9995
- res.status(500).json({
10792
+ const task = {
10793
+ taskId: createRememberTaskId(),
10794
+ status: "queued",
10795
+ contextMode: params.contextMode,
10796
+ createdAt: nowIso(),
10797
+ updatedAt: nowIso(),
10798
+ ...params.originatorClientId ? { originatorClientId: params.originatorClientId } : {}
10799
+ };
10800
+ this.tasks.set(task.taskId, task);
10801
+ this.evictTerminalTasks();
10802
+ const run = /* @__PURE__ */ __name(async () => {
10803
+ task.status = "running";
10804
+ task.updatedAt = nowIso();
10805
+ try {
10806
+ const result = await this.bridge.runWorkspaceMemoryRemember({
10807
+ content: params.content,
10808
+ contextMode: params.contextMode
10809
+ });
10810
+ task.status = "completed";
10811
+ task.result = {
10812
+ summary: result.filesTouched.length > 0 ? "Memory update completed." : "No memory files updated.",
10813
+ filesTouched: result.filesTouched,
10814
+ touchedScopes: result.touchedScopes
10815
+ };
10816
+ task.updatedAt = nowIso();
10817
+ } catch (err) {
10818
+ const code = extractRememberErrorCode(err);
10819
+ debugLogger2.error("Remember task failed:", err);
10820
+ task.status = "failed";
10821
+ task.error = {
10822
+ code,
10823
+ message: publicErrorMessage(code)
10824
+ };
10825
+ task.updatedAt = nowIso();
10826
+ } finally {
10827
+ this.evictTerminalTasks();
10828
+ }
10829
+ if (task.status === "completed" && task.result && task.result.touchedScopes.length > 0) {
10830
+ try {
10831
+ this.bridge.publishWorkspaceEvent({
10832
+ type: "memory_changed",
10833
+ data: {
10834
+ scope: "managed",
10835
+ source: "workspace_memory_remember",
10836
+ taskId: task.taskId,
10837
+ touchedScopes: task.result.touchedScopes
10838
+ },
10839
+ ...params.originatorClientId ? { originatorClientId: params.originatorClientId } : {}
10840
+ });
10841
+ } catch (err) {
10842
+ debugLogger2.error("Failed to publish memory_changed event:", err);
10843
+ }
10844
+ }
10845
+ }, "run");
10846
+ this.tail = this.tail.then(run, run);
10847
+ void this.tail.catch((err) => {
10848
+ debugLogger2.error("Unhandled task lane error:", err);
10849
+ });
10850
+ return cloneTask(task);
10851
+ }
10852
+ get(taskId, requesterClientId) {
10853
+ const task = this.tasks.get(taskId);
10854
+ if (!task) return void 0;
10855
+ if (task.originatorClientId) {
10856
+ if (task.originatorClientId !== requesterClientId) return void 0;
10857
+ } else if (requesterClientId) {
10858
+ return void 0;
10859
+ }
10860
+ return cloneTask(task);
10861
+ }
10862
+ };
10863
+ function validateOriginatorClientId(deps, req, res) {
10864
+ const clientId = deps.parseClientId(req, res);
10865
+ if (clientId === null) return null;
10866
+ if (clientId === void 0) return void 0;
10867
+ const known = deps.bridge.knownClientIds();
10868
+ if (!known.has(clientId)) {
10869
+ res.status(400).json({
10870
+ error: `Client id "${clientId}" is not registered for this workspace`,
10871
+ code: "invalid_client_id",
10872
+ clientId
10873
+ });
10874
+ return null;
10875
+ }
10876
+ return clientId;
10877
+ }
10878
+ __name(validateOriginatorClientId, "validateOriginatorClientId");
10879
+ function mountWorkspaceMemoryRememberRoutes(app, deps) {
10880
+ app.post(
10881
+ "/workspace/memory/remember",
10882
+ deps.mutate({ strict: true }),
10883
+ async (req, res) => {
10884
+ const body = deps.safeBody(req);
10885
+ const content = body["content"];
10886
+ const trimmedContent = typeof content === "string" ? content.trim() : "";
10887
+ if (!trimmedContent) {
10888
+ res.status(400).json({
10889
+ error: "`content` must be a non-empty string",
10890
+ code: "invalid_content"
10891
+ });
10892
+ return;
10893
+ }
10894
+ if (Buffer.byteLength(trimmedContent, "utf8") > MAX_REMEMBER_CONTENT_BYTES) {
10895
+ res.status(400).json({
10896
+ error: `\`content\` exceeds the ${MAX_REMEMBER_CONTENT_BYTES}-byte limit`,
10897
+ code: "invalid_content"
10898
+ });
10899
+ return;
10900
+ }
10901
+ const contextModeRaw = body["contextMode"] ?? "workspace";
10902
+ if (contextModeRaw !== "workspace" && contextModeRaw !== "clean") {
10903
+ res.status(400).json({
10904
+ error: '`contextMode` must be "workspace", "clean", or omitted',
10905
+ code: "invalid_context_mode"
10906
+ });
10907
+ return;
10908
+ }
10909
+ const originatorClientId = validateOriginatorClientId(deps, req, res);
10910
+ if (originatorClientId === null) return;
10911
+ try {
10912
+ const available = await deps.bridge.isWorkspaceMemoryRememberAvailable();
10913
+ if (!available) {
10914
+ res.status(409).json({
10915
+ error: "Managed memory is unavailable for this daemon workspace",
10916
+ code: "managed_memory_unavailable"
10917
+ });
10918
+ return;
10919
+ }
10920
+ } catch (err) {
10921
+ debugLogger2.error("Availability check failed:", err);
10922
+ res.status(500).json({
10923
+ error: "Workspace memory remember failed.",
10924
+ code: "remember_failed"
10925
+ });
10926
+ return;
10927
+ }
10928
+ let task;
10929
+ try {
10930
+ task = deps.lane.enqueue({
10931
+ content: trimmedContent,
10932
+ contextMode: contextModeRaw,
10933
+ ...originatorClientId ? { originatorClientId } : {}
10934
+ });
10935
+ } catch (err) {
10936
+ const code = extractRememberErrorCode(err);
10937
+ res.status(publicErrorStatus(code)).json({
10938
+ error: publicErrorMessage(code),
10939
+ code
10940
+ });
10941
+ return;
10942
+ }
10943
+ res.status(202).json(task);
10944
+ }
10945
+ );
10946
+ app.get(
10947
+ "/workspace/memory/remember/:taskId",
10948
+ deps.mutate({ strict: true }),
10949
+ (req, res) => {
10950
+ const requesterClientId = validateOriginatorClientId(deps, req, res);
10951
+ if (requesterClientId === null) return;
10952
+ const task = deps.lane.get(req.params["taskId"], requesterClientId);
10953
+ if (!task) {
10954
+ res.status(404).json({
10955
+ error: "Workspace memory remember task not found",
10956
+ code: "remember_task_not_found"
10957
+ });
10958
+ return;
10959
+ }
10960
+ res.status(200).json(task);
10961
+ }
10962
+ );
10963
+ }
10964
+ __name(mountWorkspaceMemoryRememberRoutes, "mountWorkspaceMemoryRememberRoutes");
10965
+
10966
+ // packages/cli/src/serve/routes/daemon-status.ts
10967
+ init_esbuild_shims();
10968
+ function registerDaemonStatusRoutes(app, deps) {
10969
+ app.get("/daemon/status", async (req, res) => {
10970
+ const detail = parseDaemonStatusDetail(req.query["detail"]);
10971
+ if (!detail.ok || !detail.detail) {
10972
+ res.status(400).json({
10973
+ error: "detail must be one of: summary, full",
10974
+ code: "invalid_detail"
10975
+ });
10976
+ return;
10977
+ }
10978
+ try {
10979
+ res.status(200).json(
10980
+ await buildDaemonStatusResponse(detail.detail, {
10981
+ opts: deps.opts,
10982
+ boundWorkspace: deps.boundWorkspace,
10983
+ bridge: deps.bridge,
10984
+ workspace: deps.workspace,
10985
+ daemonLog: deps.daemonLog,
10986
+ startup: deps.startup,
10987
+ qwenCodeVersion: deps.qwenCodeVersion,
10988
+ acpHandle: deps.getAcpHandle(),
10989
+ rateLimiter: deps.getRateLimiter(),
10990
+ getRestSseActive: deps.getRestSseActive,
10991
+ features: deps.currentServeFeatures(),
10992
+ protocolVersions: getServeProtocolVersions(),
10993
+ supportedDeviceFlowProviders: deps.getSupportedDeviceFlowProviders(),
10994
+ deviceFlowRegistry: deps.deviceFlowRegistry,
10995
+ sessionShellCommandEnabled: deps.sessionShellCommandEnabled,
10996
+ getChannelWorkerSnapshot: deps.getChannelWorkerSnapshot
10997
+ })
10998
+ );
10999
+ } catch (err) {
11000
+ writeStderrLine(
11001
+ `qwen serve: /daemon/status failed: ${err instanceof Error ? err.message : String(err)}`
11002
+ );
11003
+ res.status(500).json({
9996
11004
  error: "Failed to build daemon status",
9997
11005
  code: "daemon_status_failed"
9998
11006
  });
@@ -11225,14 +12233,14 @@ function registerWorkspaceExtensionRoutes(app, deps) {
11225
12233
  code: "extension_queue_full"
11226
12234
  });
11227
12235
  }, "sendExtensionQueueFull");
11228
- const withExtensionTimeout = /* @__PURE__ */ __name(async (promise, timeoutMs, operation) => await new Promise((resolve4, reject) => {
12236
+ const withExtensionTimeout = /* @__PURE__ */ __name(async (promise, timeoutMs, operation) => await new Promise((resolve5, reject) => {
11229
12237
  const timeout = setTimeout(() => {
11230
12238
  reject(new Error(`${operation} timed out after ${timeoutMs}ms`));
11231
12239
  }, timeoutMs);
11232
12240
  promise.then(
11233
12241
  (value) => {
11234
12242
  clearTimeout(timeout);
11235
- resolve4(value);
12243
+ resolve5(value);
11236
12244
  },
11237
12245
  (err) => {
11238
12246
  clearTimeout(timeout);
@@ -12348,7 +13356,7 @@ __name(registerPermissionRoutes, "registerPermissionRoutes");
12348
13356
  // packages/cli/src/serve/routes/session.ts
12349
13357
  init_esbuild_shims();
12350
13358
  import * as crypto2 from "node:crypto";
12351
- import * as path10 from "node:path";
13359
+ import * as path11 from "node:path";
12352
13360
 
12353
13361
  // packages/cli/src/serve/server/prompt-deadline.ts
12354
13362
  init_esbuild_shims();
@@ -12407,9 +13415,11 @@ async function listWorkspaceSessionsForResponse(bridge, workspaceCwd, options) {
12407
13415
  }
12408
13416
  const isFirstPage = numericCursor === void 0;
12409
13417
  const sessionService = new SessionService(workspaceCwd);
13418
+ const archiveState = options?.archiveState ?? "active";
12410
13419
  const persisted = await sessionService.listSessions({
12411
13420
  cursor: numericCursor,
12412
- size: pageSize
13421
+ size: pageSize,
13422
+ archiveState
12413
13423
  });
12414
13424
  const bySessionId = /* @__PURE__ */ new Map();
12415
13425
  for (const item of persisted.items) {
@@ -12420,9 +13430,15 @@ async function listWorkspaceSessionsForResponse(bridge, workspaceCwd, options) {
12420
13430
  updatedAt: new Date(item.mtime).toISOString(),
12421
13431
  displayName: item.customTitle || item.prompt,
12422
13432
  clientCount: 0,
12423
- hasActivePrompt: false
13433
+ hasActivePrompt: false,
13434
+ isArchived: item.isArchived === true
12424
13435
  });
12425
13436
  }
13437
+ if (archiveState === "archived") {
13438
+ const sessions2 = [...bySessionId.values()];
13439
+ const nextCursor2 = persisted.nextCursor != null ? String(persisted.nextCursor) : void 0;
13440
+ return { sessions: sessions2, nextCursor: nextCursor2 };
13441
+ }
12426
13442
  const liveSessions = bridge.listWorkspaceSessions(workspaceCwd);
12427
13443
  for (const live of liveSessions) {
12428
13444
  const existing = bySessionId.get(live.sessionId);
@@ -12434,14 +13450,16 @@ async function listWorkspaceSessionsForResponse(bridge, workspaceCwd, options) {
12434
13450
  displayName: live.displayName ?? existing.displayName,
12435
13451
  updatedAt: live.updatedAt ?? existing.updatedAt,
12436
13452
  clientCount: live.clientCount,
12437
- hasActivePrompt: live.hasActivePrompt
13453
+ hasActivePrompt: live.hasActivePrompt,
13454
+ isArchived: false
12438
13455
  });
12439
13456
  } else if (isFirstPage && !await sessionService.sessionExists(live.sessionId)) {
12440
13457
  bySessionId.set(live.sessionId, {
12441
13458
  ...live,
12442
13459
  createdAt: live.createdAt,
12443
13460
  clientCount: live.clientCount,
12444
- hasActivePrompt: live.hasActivePrompt
13461
+ hasActivePrompt: live.hasActivePrompt,
13462
+ isArchived: false
12445
13463
  });
12446
13464
  }
12447
13465
  }
@@ -12470,6 +13488,7 @@ function registerSessionRoutes(app, deps) {
12470
13488
  const {
12471
13489
  boundWorkspace,
12472
13490
  bridge,
13491
+ archiveCoordinator,
12473
13492
  mutate,
12474
13493
  sendBridgeError: sendBridgeError2,
12475
13494
  daemonLog,
@@ -12477,6 +13496,33 @@ function registerSessionRoutes(app, deps) {
12477
13496
  sessionShellCommandEnabled
12478
13497
  } = deps;
12479
13498
  const LANGUAGE_CODES = deps.languageCodes;
13499
+ const parseSessionIdsBody = /* @__PURE__ */ __name((req, res) => {
13500
+ const body = safeBody(req);
13501
+ const sessionIds = body["sessionIds"];
13502
+ if (!Array.isArray(sessionIds) || sessionIds.length === 0 || sessionIds.length > 100 || !sessionIds.every((id) => typeof id === "string")) {
13503
+ res.status(400).json({
13504
+ error: "`sessionIds` must be a non-empty string array (max 100)",
13505
+ code: "invalid_request"
13506
+ });
13507
+ return void 0;
13508
+ }
13509
+ return [...new Set(sessionIds)];
13510
+ }, "parseSessionIdsBody");
13511
+ const serializeSessionErrors = /* @__PURE__ */ __name((errors) => errors.map((e) => ({
13512
+ sessionId: e.sessionId,
13513
+ error: e.error instanceof Error ? e.error.message : String(e.error)
13514
+ })), "serializeSessionErrors");
13515
+ const withMutableSession = /* @__PURE__ */ __name((route, handler) => async (req, res) => {
13516
+ const sessionId = requireSessionId(req, res);
13517
+ if (sessionId === null) return;
13518
+ try {
13519
+ await archiveCoordinator.runSharedMany([sessionId], async () => {
13520
+ await handler(req, res, sessionId);
13521
+ });
13522
+ } catch (err) {
13523
+ sendBridgeError2(res, err, { route, sessionId });
13524
+ }
13525
+ }, "withMutableSession");
12480
13526
  app.post("/session", mutate(), async (req, res) => {
12481
13527
  const body = safeBody(req);
12482
13528
  const cwd = parseOptionalWorkspaceCwd(body, boundWorkspace, res);
@@ -12542,15 +13588,21 @@ function registerSessionRoutes(app, deps) {
12542
13588
  const clientId = parseClientIdHeader(req, res);
12543
13589
  if (clientId === null) return;
12544
13590
  try {
12545
- const session = action === "load" ? await bridge.loadSession({
12546
- sessionId,
12547
- workspaceCwd: cwd,
12548
- ...clientId !== void 0 ? { clientId } : {}
12549
- }) : await bridge.resumeSession({
12550
- sessionId,
12551
- workspaceCwd: cwd,
12552
- ...clientId !== void 0 ? { clientId } : {}
12553
- });
13591
+ const session = await archiveCoordinator.runSharedMany(
13592
+ [sessionId],
13593
+ async () => {
13594
+ await assertSessionLoadable(cwd, sessionId);
13595
+ return action === "load" ? await bridge.loadSession({
13596
+ sessionId,
13597
+ workspaceCwd: cwd,
13598
+ ...clientId !== void 0 ? { clientId } : {}
13599
+ }) : await bridge.resumeSession({
13600
+ sessionId,
13601
+ workspaceCwd: cwd,
13602
+ ...clientId !== void 0 ? { clientId } : {}
13603
+ });
13604
+ }
13605
+ );
12554
13606
  if (daemonLog) {
12555
13607
  daemonLog.info(
12556
13608
  `session ${action}${session.attached ? " (attached)" : ""}`,
@@ -12577,99 +13629,90 @@ function registerSessionRoutes(app, deps) {
12577
13629
  }, "restoreSessionHandler");
12578
13630
  app.post("/session/:id/load", mutate(), restoreSessionHandler("load"));
12579
13631
  app.post("/session/:id/resume", mutate(), restoreSessionHandler("resume"));
12580
- app.post("/session/:id/branch", mutate(), async (req, res) => {
12581
- const sessionId = requireSessionId(req, res);
12582
- if (sessionId === null) return;
12583
- const body = safeBody(req);
12584
- let name = typeof body?.["name"] === "string" ? body["name"] : void 0;
12585
- if (name) {
12586
- name = name.replace(/[\x00-\x1F\x7F-\x9F]/g, "");
12587
- if (name.length > 200) {
12588
- name = name.slice(0, 200);
13632
+ app.post(
13633
+ "/session/:id/branch",
13634
+ mutate(),
13635
+ withMutableSession(
13636
+ "POST /session/:id/branch",
13637
+ async (req, res, sessionId) => {
13638
+ const body = safeBody(req);
13639
+ let name = typeof body?.["name"] === "string" ? body["name"] : void 0;
13640
+ if (name) {
13641
+ name = name.replace(/[\x00-\x1F\x7F-\x9F]/g, "");
13642
+ if (name.length > 200) {
13643
+ name = name.slice(0, 200);
13644
+ }
13645
+ }
13646
+ const clientId = parseClientIdHeader(req, res);
13647
+ if (clientId === null) return;
13648
+ const result = await bridge.branchSession(
13649
+ sessionId,
13650
+ { name },
13651
+ { clientId }
13652
+ );
13653
+ if (!res.writable) {
13654
+ if (!result.attached) {
13655
+ bridge.killSession(result.sessionId, { requireZeroAttaches: true }).catch(() => {
13656
+ });
13657
+ } else {
13658
+ bridge.detachClient(result.sessionId, result.clientId).catch(() => {
13659
+ });
13660
+ }
13661
+ return;
13662
+ }
13663
+ res.status(201).json(result);
12589
13664
  }
12590
- }
12591
- const clientId = parseClientIdHeader(req, res);
12592
- if (clientId === null) return;
12593
- try {
12594
- const result = await bridge.branchSession(
12595
- sessionId,
12596
- { name },
12597
- { clientId }
12598
- );
12599
- if (!res.writable) {
12600
- if (!result.attached) {
12601
- bridge.killSession(result.sessionId, { requireZeroAttaches: true }).catch(() => {
12602
- });
12603
- } else {
12604
- bridge.detachClient(result.sessionId, result.clientId).catch(() => {
13665
+ )
13666
+ );
13667
+ app.post(
13668
+ "/session/:id/fork",
13669
+ mutate(),
13670
+ withMutableSession(
13671
+ "POST /session/:id/fork",
13672
+ async (req, res, sessionId) => {
13673
+ const body = safeBody(req);
13674
+ const directive = body["directive"];
13675
+ if (typeof directive !== "string" || directive.trim().length === 0) {
13676
+ res.status(400).json({
13677
+ error: "`directive` is required and must be a non-empty string",
13678
+ code: "missing_directive"
12605
13679
  });
13680
+ return;
12606
13681
  }
13682
+ const clientId = parseClientIdHeader(req, res);
13683
+ if (clientId === null) return;
13684
+ const result = await bridge.launchSessionForkAgent(
13685
+ sessionId,
13686
+ directive,
13687
+ clientId !== void 0 ? { clientId } : void 0
13688
+ );
13689
+ res.status(202).json(result);
13690
+ }
13691
+ )
13692
+ );
13693
+ app.post(
13694
+ "/session/:id/cd",
13695
+ mutate(),
13696
+ withMutableSession("POST /session/:id/cd", async (req, res, sessionId) => {
13697
+ const body = safeBody(req);
13698
+ const targetPath = body["path"];
13699
+ if (typeof targetPath !== "string" || targetPath.length === 0 || !path11.isAbsolute(targetPath)) {
13700
+ res.status(400).json({
13701
+ error: "`path` is required and must be an absolute path",
13702
+ code: "invalid_path"
13703
+ });
12607
13704
  return;
12608
13705
  }
12609
- res.status(201).json(result);
12610
- } catch (err) {
12611
- sendBridgeError2(res, err, {
12612
- route: "POST /session/:id/branch",
12613
- sessionId
12614
- });
12615
- }
12616
- });
12617
- app.post("/session/:id/fork", mutate(), async (req, res) => {
12618
- const sessionId = requireSessionId(req, res);
12619
- if (sessionId === null) return;
12620
- const body = safeBody(req);
12621
- const directive = body["directive"];
12622
- if (typeof directive !== "string" || directive.trim().length === 0) {
12623
- res.status(400).json({
12624
- error: "`directive` is required and must be a non-empty string",
12625
- code: "missing_directive"
12626
- });
12627
- return;
12628
- }
12629
- const clientId = parseClientIdHeader(req, res);
12630
- if (clientId === null) return;
12631
- try {
12632
- const result = await bridge.launchSessionForkAgent(
12633
- sessionId,
12634
- directive,
12635
- clientId !== void 0 ? { clientId } : void 0
12636
- );
12637
- res.status(202).json(result);
12638
- } catch (err) {
12639
- sendBridgeError2(res, err, {
12640
- route: "POST /session/:id/fork",
12641
- sessionId
12642
- });
12643
- }
12644
- });
12645
- app.post("/session/:id/cd", mutate(), async (req, res) => {
12646
- const sessionId = requireSessionId(req, res);
12647
- if (sessionId === null) return;
12648
- const body = safeBody(req);
12649
- const targetPath = body["path"];
12650
- if (typeof targetPath !== "string" || targetPath.length === 0 || !path10.isAbsolute(targetPath)) {
12651
- res.status(400).json({
12652
- error: "`path` is required and must be an absolute path",
12653
- code: "invalid_path"
12654
- });
12655
- return;
12656
- }
12657
- const clientId = parseClientIdHeader(req, res);
12658
- if (clientId === null) return;
12659
- try {
13706
+ const clientId = parseClientIdHeader(req, res);
13707
+ if (clientId === null) return;
12660
13708
  const result = await bridge.changeSessionCwd(
12661
13709
  sessionId,
12662
13710
  { path: targetPath },
12663
13711
  clientId !== void 0 ? { clientId } : void 0
12664
13712
  );
12665
13713
  res.status(200).json(result);
12666
- } catch (err) {
12667
- sendBridgeError2(res, err, {
12668
- route: "POST /session/:id/cd",
12669
- sessionId
12670
- });
12671
- }
12672
- });
13714
+ })
13715
+ );
12673
13716
  app.get("/session/:id/status", (req, res) => {
12674
13717
  const sessionId = requireSessionId(req, res);
12675
13718
  if (sessionId === null) return;
@@ -12770,283 +13813,252 @@ function registerSessionRoutes(app, deps) {
12770
13813
  app.post(
12771
13814
  "/session/:id/tasks/:taskId/cancel",
12772
13815
  mutate({ strict: true }),
12773
- async (req, res) => {
12774
- const sessionId = req.params["id"];
12775
- const taskId = req.params["taskId"];
12776
- if (!sessionId || !taskId) {
12777
- res.status(400).json({
12778
- error: "`sessionId` and `taskId` route parameters are required"
12779
- });
12780
- return;
12781
- }
12782
- const body = safeBody(req);
12783
- const kind = body["kind"];
12784
- if (kind !== "agent" && kind !== "shell" && kind !== "monitor") {
12785
- res.status(400).json({ error: '`kind` must be "agent", "shell", or "monitor"' });
12786
- return;
12787
- }
12788
- try {
13816
+ withMutableSession(
13817
+ "POST /session/:id/tasks/:taskId/cancel",
13818
+ async (req, res, sessionId) => {
13819
+ const taskId = req.params["taskId"];
13820
+ if (!taskId) {
13821
+ res.status(400).json({
13822
+ error: "`taskId` route parameter is required"
13823
+ });
13824
+ return;
13825
+ }
13826
+ const body = safeBody(req);
13827
+ const kind = body["kind"];
13828
+ if (kind !== "agent" && kind !== "shell" && kind !== "monitor") {
13829
+ res.status(400).json({ error: '`kind` must be "agent", "shell", or "monitor"' });
13830
+ return;
13831
+ }
12789
13832
  res.status(200).json(await bridge.cancelSessionTask(sessionId, taskId, kind));
12790
- } catch (err) {
12791
- sendBridgeError2(res, err, {
12792
- route: "POST /session/:id/tasks/:taskId/cancel",
12793
- sessionId
12794
- });
12795
13833
  }
12796
- }
13834
+ )
12797
13835
  );
12798
13836
  app.post(
12799
13837
  "/session/:id/goal/clear",
12800
13838
  mutate({ strict: true }),
12801
- async (req, res) => {
12802
- const sessionId = req.params["id"];
12803
- if (!sessionId) {
12804
- res.status(400).json({ error: "`sessionId` route parameter is required" });
12805
- return;
12806
- }
12807
- try {
13839
+ withMutableSession(
13840
+ "POST /session/:id/goal/clear",
13841
+ async (_req, res, sessionId) => {
12808
13842
  res.status(200).json(await bridge.clearSessionGoal(sessionId));
12809
- } catch (err) {
12810
- sendBridgeError2(res, err, {
12811
- route: "POST /session/:id/goal/clear",
12812
- sessionId
12813
- });
12814
13843
  }
12815
- }
13844
+ )
12816
13845
  );
12817
13846
  app.post(
12818
13847
  "/session/:id/continue",
12819
13848
  mutate({ strict: true }),
12820
- async (req, res) => {
12821
- const sessionId = req.params["id"];
12822
- if (!sessionId) {
12823
- res.status(400).json({ error: "`sessionId` route parameter is required" });
12824
- return;
12825
- }
12826
- const clientId = parseClientIdHeader(req, res);
12827
- if (clientId === null) return;
12828
- const promptId = crypto2.randomUUID();
12829
- try {
13849
+ withMutableSession(
13850
+ "POST /session/:id/continue",
13851
+ async (req, res, sessionId) => {
13852
+ const clientId = parseClientIdHeader(req, res);
13853
+ if (clientId === null) return;
13854
+ const promptId = crypto2.randomUUID();
12830
13855
  res.status(200).json(
12831
13856
  await bridge.continueSession(sessionId, {
12832
13857
  ...clientId !== void 0 ? { clientId } : {},
12833
13858
  promptId
12834
13859
  })
12835
13860
  );
12836
- } catch (err) {
12837
- sendBridgeError2(res, err, {
12838
- route: "POST /session/:id/continue",
12839
- sessionId
12840
- });
12841
13861
  }
12842
- }
13862
+ )
12843
13863
  );
12844
- app.post("/session/:id/prompt", mutate(), async (req, res) => {
12845
- const sessionId = req.params["id"];
12846
- const body = safeBody(req);
12847
- const prompt = body["prompt"];
12848
- if (!Array.isArray(prompt) || prompt.length === 0) {
12849
- res.status(400).json({
12850
- error: "`prompt` is required and must be a non-empty array of content blocks"
12851
- });
12852
- return;
12853
- }
12854
- if (!prompt.every(
12855
- (item) => typeof item === "object" && item !== null && !Array.isArray(item)
12856
- )) {
12857
- res.status(400).json({
12858
- error: "each `prompt` element must be an object (content block)"
12859
- });
12860
- return;
12861
- }
12862
- const rawRequestDeadline = body["deadlineMs"];
12863
- let requestDeadlineMs;
12864
- if (rawRequestDeadline !== void 0 && rawRequestDeadline !== null) {
12865
- if (typeof rawRequestDeadline !== "number" || !Number.isFinite(rawRequestDeadline) || !Number.isInteger(rawRequestDeadline) || rawRequestDeadline <= 0) {
12866
- res.status(400).json({
12867
- error: "`deadlineMs` must be a positive integer (milliseconds)",
12868
- code: "invalid_deadline_ms"
12869
- });
12870
- return;
12871
- }
12872
- requestDeadlineMs = rawRequestDeadline;
12873
- }
12874
- const clientId = parseClientIdHeader(req, res);
12875
- if (clientId === null) return;
12876
- const promptId = crypto2.randomUUID();
12877
- const forwardedBody = { ...body };
12878
- delete forwardedBody["deadlineMs"];
12879
- let lastEventId;
12880
- try {
12881
- lastEventId = bridge.getSessionLastEventId(sessionId);
12882
- } catch (err) {
12883
- sendBridgeError2(res, err, {
12884
- route: "POST /session/:id/prompt",
12885
- sessionId
12886
- });
12887
- return;
12888
- }
12889
- addDaemonRequestAttribute("qwen-code.prompt_id", promptId);
12890
- const abort = new AbortController();
12891
- let responseFinished = false;
12892
- const onResClose = /* @__PURE__ */ __name(() => {
12893
- if (!responseFinished) abort.abort();
12894
- }, "onResClose");
12895
- const onResFinish = /* @__PURE__ */ __name(() => {
12896
- responseFinished = true;
12897
- res.off("close", onResClose);
12898
- }, "onResFinish");
12899
- res.once("close", onResClose);
12900
- res.once("finish", onResFinish);
12901
- const effectiveDeadlineMs = resolvePromptDeadlineMs(
12902
- promptDeadlineMs,
12903
- requestDeadlineMs
12904
- );
12905
- let deadlineTimer;
12906
- if (effectiveDeadlineMs !== void 0) {
12907
- deadlineTimer = setTimeout(() => {
12908
- if (!abort.signal.aborted) {
12909
- abort.abort(new PromptDeadlineExceededError(effectiveDeadlineMs));
13864
+ app.post(
13865
+ "/session/:id/prompt",
13866
+ mutate(),
13867
+ withMutableSession(
13868
+ "POST /session/:id/prompt",
13869
+ async (req, res, sessionId) => {
13870
+ const body = safeBody(req);
13871
+ const prompt = body["prompt"];
13872
+ if (!Array.isArray(prompt) || prompt.length === 0) {
13873
+ res.status(400).json({
13874
+ error: "`prompt` is required and must be a non-empty array of content blocks"
13875
+ });
13876
+ return;
12910
13877
  }
12911
- }, effectiveDeadlineMs);
12912
- deadlineTimer.unref();
12913
- }
12914
- let promptPromise;
12915
- try {
12916
- promptPromise = bridge.sendPrompt(
12917
- sessionId,
12918
- {
12919
- ...forwardedBody,
12920
- sessionId,
12921
- prompt
12922
- },
12923
- abort.signal,
12924
- {
12925
- ...clientId !== void 0 ? { clientId } : {},
12926
- promptId
13878
+ if (!prompt.every(
13879
+ (item) => typeof item === "object" && item !== null && !Array.isArray(item)
13880
+ )) {
13881
+ res.status(400).json({
13882
+ error: "each `prompt` element must be an object (content block)"
13883
+ });
13884
+ return;
12927
13885
  }
12928
- );
12929
- } catch (err) {
12930
- if (deadlineTimer !== void 0) clearTimeout(deadlineTimer);
12931
- res.off("close", onResClose);
12932
- res.off("finish", onResFinish);
12933
- if (daemonLog && err instanceof PromptQueueFullError) {
12934
- daemonLog.warn("prompt admission rejected: queue full", {
12935
- sessionId,
12936
- promptId,
12937
- ...clientId !== void 0 ? { clientId } : {},
12938
- limit: err.limit,
12939
- pendingCount: err.pendingCount
12940
- });
12941
- }
12942
- if (daemonLog && err instanceof InvalidClientIdError) {
12943
- daemonLog.warn("prompt admission rejected: invalid client id", {
12944
- sessionId,
12945
- promptId,
12946
- ...clientId !== void 0 ? { clientId } : {}
12947
- });
12948
- }
12949
- sendBridgeError2(res, err, {
12950
- route: "POST /session/:id/prompt",
12951
- sessionId
12952
- });
12953
- return;
12954
- }
12955
- res.off("close", onResClose);
12956
- promptPromise.then(
12957
- () => {
12958
- if (daemonLog) {
12959
- daemonLog.info("prompt turn completed", {
13886
+ const rawRequestDeadline = body["deadlineMs"];
13887
+ let requestDeadlineMs;
13888
+ if (rawRequestDeadline !== void 0 && rawRequestDeadline !== null) {
13889
+ if (typeof rawRequestDeadline !== "number" || !Number.isFinite(rawRequestDeadline) || !Number.isInteger(rawRequestDeadline) || rawRequestDeadline <= 0) {
13890
+ res.status(400).json({
13891
+ error: "`deadlineMs` must be a positive integer (milliseconds)",
13892
+ code: "invalid_deadline_ms"
13893
+ });
13894
+ return;
13895
+ }
13896
+ requestDeadlineMs = rawRequestDeadline;
13897
+ }
13898
+ const clientId = parseClientIdHeader(req, res);
13899
+ if (clientId === null) return;
13900
+ const promptId = crypto2.randomUUID();
13901
+ const forwardedBody = { ...body };
13902
+ delete forwardedBody["deadlineMs"];
13903
+ const lastEventId = bridge.getSessionLastEventId(sessionId);
13904
+ addDaemonRequestAttribute("qwen-code.prompt_id", promptId);
13905
+ const abort = new AbortController();
13906
+ let responseFinished = false;
13907
+ const onResClose = /* @__PURE__ */ __name(() => {
13908
+ if (!responseFinished) abort.abort();
13909
+ }, "onResClose");
13910
+ const onResFinish = /* @__PURE__ */ __name(() => {
13911
+ responseFinished = true;
13912
+ res.off("close", onResClose);
13913
+ }, "onResFinish");
13914
+ res.once("close", onResClose);
13915
+ res.once("finish", onResFinish);
13916
+ const effectiveDeadlineMs = resolvePromptDeadlineMs(
13917
+ promptDeadlineMs,
13918
+ requestDeadlineMs
13919
+ );
13920
+ let deadlineTimer;
13921
+ if (effectiveDeadlineMs !== void 0) {
13922
+ deadlineTimer = setTimeout(() => {
13923
+ if (!abort.signal.aborted) {
13924
+ abort.abort(new PromptDeadlineExceededError(effectiveDeadlineMs));
13925
+ }
13926
+ }, effectiveDeadlineMs);
13927
+ deadlineTimer.unref();
13928
+ }
13929
+ let promptPromise;
13930
+ try {
13931
+ promptPromise = bridge.sendPrompt(
12960
13932
  sessionId,
12961
- promptId,
12962
- clientId
13933
+ {
13934
+ ...forwardedBody,
13935
+ sessionId,
13936
+ prompt
13937
+ },
13938
+ abort.signal,
13939
+ {
13940
+ ...clientId !== void 0 ? { clientId } : {},
13941
+ promptId
13942
+ }
13943
+ );
13944
+ } catch (err) {
13945
+ if (deadlineTimer !== void 0) clearTimeout(deadlineTimer);
13946
+ res.off("close", onResClose);
13947
+ res.off("finish", onResFinish);
13948
+ if (daemonLog && err instanceof PromptQueueFullError) {
13949
+ daemonLog.warn("prompt admission rejected: queue full", {
13950
+ sessionId,
13951
+ promptId,
13952
+ ...clientId !== void 0 ? { clientId } : {},
13953
+ limit: err.limit,
13954
+ pendingCount: err.pendingCount
13955
+ });
13956
+ }
13957
+ if (daemonLog && err instanceof InvalidClientIdError) {
13958
+ daemonLog.warn("prompt admission rejected: invalid client id", {
13959
+ sessionId,
13960
+ promptId,
13961
+ ...clientId !== void 0 ? { clientId } : {}
13962
+ });
13963
+ }
13964
+ sendBridgeError2(res, err, {
13965
+ route: "POST /session/:id/prompt",
13966
+ sessionId
12963
13967
  });
13968
+ return;
12964
13969
  }
12965
- },
12966
- (err) => {
13970
+ res.off("close", onResClose);
13971
+ promptPromise.then(
13972
+ () => {
13973
+ if (daemonLog) {
13974
+ daemonLog.info("prompt turn completed", {
13975
+ sessionId,
13976
+ promptId,
13977
+ clientId
13978
+ });
13979
+ }
13980
+ },
13981
+ (err) => {
13982
+ if (daemonLog) {
13983
+ const errName = err instanceof Error ? err.name : void 0;
13984
+ daemonLog.warn(
13985
+ `prompt turn failed: ${errName ? `[${errName}] ` : ""}${err instanceof Error ? err.message : String(err)}`,
13986
+ { sessionId, promptId, clientId }
13987
+ );
13988
+ }
13989
+ }
13990
+ ).finally(() => {
13991
+ if (deadlineTimer !== void 0) clearTimeout(deadlineTimer);
13992
+ }).catch(() => {
13993
+ });
12967
13994
  if (daemonLog) {
12968
- const errName = err instanceof Error ? err.name : void 0;
12969
- daemonLog.warn(
12970
- `prompt turn failed: ${errName ? `[${errName}] ` : ""}${err instanceof Error ? err.message : String(err)}`,
12971
- { sessionId, promptId, clientId }
12972
- );
13995
+ daemonLog.info("prompt enqueued", { sessionId, promptId, clientId });
12973
13996
  }
13997
+ res.status(202).json({ promptId, lastEventId });
12974
13998
  }
12975
- ).finally(() => {
12976
- if (deadlineTimer !== void 0) clearTimeout(deadlineTimer);
12977
- }).catch(() => {
12978
- });
12979
- if (daemonLog) {
12980
- daemonLog.info("prompt enqueued", { sessionId, promptId, clientId });
12981
- }
12982
- res.status(202).json({ promptId, lastEventId });
12983
- });
12984
- app.post("/session/:id/heartbeat", mutate(), (req, res) => {
12985
- const sessionId = requireSessionId(req, res);
12986
- if (sessionId === null) return;
12987
- const clientId = parseClientIdHeader(req, res);
12988
- if (clientId === null) return;
12989
- try {
13999
+ )
14000
+ );
14001
+ app.post(
14002
+ "/session/:id/heartbeat",
14003
+ mutate(),
14004
+ withMutableSession("POST /session/:id/heartbeat", (req, res, sessionId) => {
14005
+ const clientId = parseClientIdHeader(req, res);
14006
+ if (clientId === null) return;
12990
14007
  const result = bridge.recordHeartbeat(
12991
14008
  sessionId,
12992
14009
  clientId !== void 0 ? { clientId } : void 0
12993
14010
  );
12994
14011
  res.status(200).json(result);
12995
- } catch (err) {
12996
- sendBridgeError2(res, err, {
12997
- route: "POST /session/:id/heartbeat",
12998
- sessionId
12999
- });
13000
- }
13001
- });
13002
- app.post("/session/:id/detach", mutate(), async (req, res) => {
13003
- const sessionId = requireSessionId(req, res);
13004
- if (sessionId === null) return;
13005
- const clientId = parseClientIdHeader(req, res);
13006
- if (clientId === null) return;
13007
- try {
13008
- await bridge.detachClient(sessionId, clientId);
13009
- res.status(204).end();
13010
- } catch (err) {
13011
- sendBridgeError2(res, err, {
13012
- route: "POST /session/:id/detach",
13013
- sessionId
13014
- });
13015
- }
13016
- });
13017
- app.post("/session/:id/cancel", mutate(), async (req, res) => {
13018
- const sessionId = req.params["id"];
13019
- const body = safeBody(req);
13020
- const clientId = parseClientIdHeader(req, res);
13021
- if (clientId === null) return;
13022
- try {
13023
- await bridge.cancelSession(
13024
- sessionId,
13025
- {
13026
- ...body,
13027
- sessionId
13028
- },
13029
- clientId !== void 0 ? { clientId } : void 0
13030
- );
13031
- if (daemonLog) {
13032
- daemonLog.info("cancel sent", { sessionId, clientId });
14012
+ })
14013
+ );
14014
+ app.post(
14015
+ "/session/:id/detach",
14016
+ mutate(),
14017
+ withMutableSession(
14018
+ "POST /session/:id/detach",
14019
+ async (req, res, sessionId) => {
14020
+ const clientId = parseClientIdHeader(req, res);
14021
+ if (clientId === null) return;
14022
+ await bridge.detachClient(sessionId, clientId);
14023
+ res.status(204).end();
13033
14024
  }
13034
- res.status(204).end();
13035
- } catch (err) {
13036
- sendBridgeError2(res, err, {
13037
- route: "POST /session/:id/cancel",
13038
- sessionId
13039
- });
13040
- }
13041
- });
14025
+ )
14026
+ );
14027
+ app.post(
14028
+ "/session/:id/cancel",
14029
+ mutate(),
14030
+ withMutableSession(
14031
+ "POST /session/:id/cancel",
14032
+ async (req, res, sessionId) => {
14033
+ const body = safeBody(req);
14034
+ const clientId = parseClientIdHeader(req, res);
14035
+ if (clientId === null) return;
14036
+ await bridge.cancelSession(
14037
+ sessionId,
14038
+ {
14039
+ ...body,
14040
+ sessionId
14041
+ },
14042
+ clientId !== void 0 ? { clientId } : void 0
14043
+ );
14044
+ if (daemonLog) {
14045
+ daemonLog.info("cancel sent", { sessionId, clientId });
14046
+ }
14047
+ res.status(204).end();
14048
+ }
14049
+ )
14050
+ );
13042
14051
  app.delete("/session/:id", async (req, res) => {
13043
14052
  const sessionId = req.params["id"];
13044
14053
  const clientId = parseClientIdHeader(req, res);
13045
14054
  if (clientId === null) return;
13046
14055
  try {
13047
- await bridge.closeSession(
13048
- sessionId,
13049
- clientId !== void 0 ? { clientId } : void 0
14056
+ await archiveCoordinator.runExclusiveMany(
14057
+ [sessionId],
14058
+ async () => bridge.closeSession(
14059
+ sessionId,
14060
+ clientId !== void 0 ? { clientId } : void 0
14061
+ )
13050
14062
  );
13051
14063
  res.status(204).end();
13052
14064
  } catch (err) {
@@ -13059,106 +14071,98 @@ function registerSessionRoutes(app, deps) {
13059
14071
  app.post("/sessions/delete", mutate(), async (req, res) => {
13060
14072
  const clientId = parseClientIdHeader(req, res);
13061
14073
  if (clientId === null) return;
13062
- const body = safeBody(req);
13063
- const sessionIds = body["sessionIds"];
13064
- if (!Array.isArray(sessionIds) || sessionIds.length === 0 || sessionIds.length > 100 || !sessionIds.every((id) => typeof id === "string")) {
13065
- res.status(400).json({
13066
- error: "`sessionIds` must be a non-empty string array (max 100)",
13067
- code: "invalid_request"
14074
+ const uniqueIds = parseSessionIdsBody(req, res);
14075
+ if (uniqueIds === void 0) return;
14076
+ try {
14077
+ const service = new SessionService(boundWorkspace);
14078
+ const result = await deleteDaemonSessions({
14079
+ sessionIds: uniqueIds,
14080
+ service,
14081
+ bridge,
14082
+ coordinator: archiveCoordinator,
14083
+ onError: /* @__PURE__ */ __name(({ phase, sessionId, error: error2 }) => {
14084
+ writeStderrLine(
14085
+ `qwen serve: ${phase}Session failed for ${safeLogValue(sessionId)}: ${safeLogValue(error2)}`
14086
+ );
14087
+ }, "onError")
13068
14088
  });
13069
- return;
14089
+ res.status(200).json(result);
14090
+ } catch (err) {
14091
+ sendBridgeError2(res, err, { route: "POST /sessions/delete" });
13070
14092
  }
14093
+ });
14094
+ app.post("/sessions/archive", mutate(), async (req, res) => {
14095
+ const uniqueIds = parseSessionIdsBody(req, res);
14096
+ if (uniqueIds === void 0) return;
14097
+ const service = new SessionService(boundWorkspace, {
14098
+ onWarning: logSessionArchiveWarning
14099
+ });
13071
14100
  try {
13072
- const uniqueIds = [...new Set(sessionIds)];
13073
- const closeResults = await Promise.allSettled(
13074
- uniqueIds.map(async (id) => {
13075
- await bridge.closeSession(id);
13076
- return id;
13077
- })
13078
- );
13079
- const closeErrors = [];
13080
- const closedIds = [];
13081
- for (let i = 0; i < closeResults.length; i++) {
13082
- const r = closeResults[i];
13083
- const id = uniqueIds[i];
13084
- if (r.status === "fulfilled") {
13085
- closedIds.push(id);
13086
- } else {
13087
- const closeErr = r.reason;
13088
- if (closeErr instanceof SessionNotFoundError) {
13089
- closedIds.push(id);
13090
- } else {
13091
- const msg = closeErr instanceof Error ? closeErr.message : String(closeErr);
13092
- writeStderrLine(
13093
- `qwen serve: closeSession failed for ${safeLogValue(id)}: ${safeLogValue(msg)}`
13094
- );
13095
- closeErrors.push({ sessionId: id, error: msg });
13096
- }
13097
- }
13098
- }
13099
- const result = await new SessionService(boundWorkspace).removeSessions(
13100
- closedIds
13101
- );
13102
- for (const e of result.errors) {
13103
- const msg = e.error instanceof Error ? e.error.message : String(e.error);
13104
- writeStderrLine(
13105
- `qwen serve: removeSession failed for ${safeLogValue(e.sessionId)}: ${safeLogValue(msg)}`
13106
- );
13107
- }
14101
+ const result = await archiveDaemonSessions({
14102
+ sessionIds: uniqueIds,
14103
+ service,
14104
+ bridge,
14105
+ coordinator: archiveCoordinator
14106
+ });
13108
14107
  res.status(200).json({
13109
- removed: result.removed,
14108
+ archived: result.archived,
14109
+ alreadyArchived: result.alreadyArchived,
13110
14110
  notFound: result.notFound,
13111
- errors: [
13112
- ...closeErrors,
13113
- ...result.errors.map((e) => ({
13114
- sessionId: e.sessionId,
13115
- error: e.error instanceof Error ? e.error.message : String(e.error)
13116
- }))
13117
- ]
14111
+ errors: serializeSessionErrors(result.errors)
13118
14112
  });
13119
14113
  } catch (err) {
13120
- const message = err instanceof Error ? err.message : String(err);
13121
- writeStderrLine(
13122
- `qwen serve: failed to batch delete sessions: ${safeLogValue(message)}`
13123
- );
13124
- res.status(500).json({
13125
- error: "Failed to delete sessions",
13126
- code: "sessions_delete_failed"
13127
- });
14114
+ sendBridgeError2(res, err, { route: "POST /sessions/archive" });
13128
14115
  }
13129
14116
  });
13130
- app.patch("/session/:id/metadata", async (req, res) => {
13131
- const sessionId = req.params["id"];
13132
- const body = safeBody(req);
13133
- const clientId = parseClientIdHeader(req, res);
13134
- if (clientId === null) return;
13135
- const rawDisplayName = body["displayName"];
13136
- if (rawDisplayName !== void 0 && typeof rawDisplayName !== "string") {
13137
- res.status(400).json({
13138
- error: "`displayName` must be a string",
13139
- code: "invalid_metadata",
13140
- field: "displayName"
14117
+ app.post("/sessions/unarchive", mutate(), async (req, res) => {
14118
+ const uniqueIds = parseSessionIdsBody(req, res);
14119
+ if (uniqueIds === void 0) return;
14120
+ const service = new SessionService(boundWorkspace, {
14121
+ onWarning: logSessionArchiveWarning
14122
+ });
14123
+ try {
14124
+ const result = await unarchiveDaemonSessions({
14125
+ sessionIds: uniqueIds,
14126
+ service,
14127
+ coordinator: archiveCoordinator
13141
14128
  });
13142
- return;
14129
+ res.status(200).json({
14130
+ unarchived: result.unarchived,
14131
+ alreadyActive: result.alreadyActive,
14132
+ notFound: result.notFound,
14133
+ errors: serializeSessionErrors(result.errors)
14134
+ });
14135
+ } catch (err) {
14136
+ sendBridgeError2(res, err, { route: "POST /sessions/unarchive" });
13143
14137
  }
13144
- const displayName = typeof rawDisplayName === "string" ? rawDisplayName.slice(0, 256) : void 0;
13145
- try {
14138
+ });
14139
+ app.patch(
14140
+ "/session/:id/metadata",
14141
+ withMutableSession("PATCH /session/:id/metadata", (req, res, sessionId) => {
14142
+ const body = safeBody(req);
14143
+ const clientId = parseClientIdHeader(req, res);
14144
+ if (clientId === null) return;
14145
+ const rawDisplayName = body["displayName"];
14146
+ if (rawDisplayName !== void 0 && typeof rawDisplayName !== "string") {
14147
+ res.status(400).json({
14148
+ error: "`displayName` must be a string",
14149
+ code: "invalid_metadata",
14150
+ field: "displayName"
14151
+ });
14152
+ return;
14153
+ }
14154
+ const displayName = typeof rawDisplayName === "string" ? rawDisplayName.slice(0, 256) : void 0;
13146
14155
  const effective = bridge.updateSessionMetadata(
13147
14156
  sessionId,
13148
14157
  { displayName },
13149
14158
  clientId !== void 0 ? { clientId } : void 0
13150
14159
  );
13151
14160
  res.status(200).json({ sessionId, ...effective });
13152
- } catch (err) {
13153
- sendBridgeError2(res, err, {
13154
- route: "PATCH /session/:id/metadata",
13155
- sessionId
13156
- });
13157
- }
13158
- });
14161
+ })
14162
+ );
13159
14163
  app.get("/workspace/:id/sessions", async (req, res) => {
13160
14164
  const workspaceCwd = req.params["id"] ?? "";
13161
- if (!path10.isAbsolute(workspaceCwd)) {
14165
+ if (!path11.isAbsolute(workspaceCwd)) {
13162
14166
  res.status(400).json({ error: "`:id` must decode to an absolute workspace path" });
13163
14167
  return;
13164
14168
  }
@@ -13175,9 +14179,22 @@ function registerSessionRoutes(app, deps) {
13175
14179
  try {
13176
14180
  const cursor = typeof req.query["cursor"] === "string" ? req.query["cursor"] : void 0;
13177
14181
  const size = parseSessionPageSizeQuery(req.query["size"]);
14182
+ const rawArchiveState = req.query["archiveState"];
14183
+ let archiveState;
14184
+ if (rawArchiveState !== void 0) {
14185
+ if (typeof rawArchiveState !== "string" || rawArchiveState !== "active" && rawArchiveState !== "archived") {
14186
+ res.status(400).json({
14187
+ error: '`archiveState` must be "active" or "archived"',
14188
+ code: "invalid_archive_state"
14189
+ });
14190
+ return;
14191
+ }
14192
+ archiveState = rawArchiveState;
14193
+ }
13178
14194
  const result = await listWorkspaceSessionsForResponse(bridge, key, {
13179
14195
  cursor,
13180
- size
14196
+ size,
14197
+ archiveState
13181
14198
  });
13182
14199
  res.status(200).json({
13183
14200
  sessions: result.sessions,
@@ -13202,137 +14219,134 @@ function registerSessionRoutes(app, deps) {
13202
14219
  });
13203
14220
  }
13204
14221
  });
13205
- app.post("/session/:id/model", mutate(), async (req, res) => {
13206
- const sessionId = req.params["id"];
13207
- const body = safeBody(req);
13208
- const modelId = body["modelId"];
13209
- if (typeof modelId !== "string" || !modelId) {
13210
- res.status(400).json({
13211
- error: "`modelId` is required and must be a non-empty string"
13212
- });
13213
- return;
13214
- }
13215
- const clientId = parseClientIdHeader(req, res);
13216
- if (clientId === null) return;
13217
- try {
13218
- const response = await bridge.setSessionModel(
13219
- sessionId,
13220
- {
13221
- ...body,
14222
+ app.post(
14223
+ "/session/:id/model",
14224
+ mutate(),
14225
+ withMutableSession(
14226
+ "POST /session/:id/model",
14227
+ async (req, res, sessionId) => {
14228
+ const body = safeBody(req);
14229
+ const modelId = body["modelId"];
14230
+ if (typeof modelId !== "string" || !modelId) {
14231
+ res.status(400).json({
14232
+ error: "`modelId` is required and must be a non-empty string"
14233
+ });
14234
+ return;
14235
+ }
14236
+ const clientId = parseClientIdHeader(req, res);
14237
+ if (clientId === null) return;
14238
+ const response = await bridge.setSessionModel(
13222
14239
  sessionId,
13223
- modelId
13224
- },
13225
- clientId !== void 0 ? { clientId } : void 0
13226
- );
13227
- res.status(200).json(response);
13228
- } catch (err) {
13229
- sendBridgeError2(res, err, {
13230
- route: "POST /session/:id/model",
13231
- sessionId
13232
- });
13233
- }
13234
- });
13235
- app.post("/session/:id/recap", mutate(), async (req, res) => {
13236
- const sessionId = requireSessionId(req, res);
13237
- if (sessionId === null) return;
13238
- const clientId = parseClientIdHeader(req, res);
13239
- if (clientId === null) return;
13240
- try {
13241
- const response = await bridge.generateSessionRecap(
13242
- sessionId,
13243
- clientId !== void 0 ? { clientId } : void 0
13244
- );
13245
- if (daemonLog) {
13246
- const recap = response.recap;
13247
- daemonLog.info(
13248
- recap ? `recap generated len=${recap.length}` : "recap returned null",
13249
- { sessionId, clientId }
14240
+ {
14241
+ ...body,
14242
+ sessionId,
14243
+ modelId
14244
+ },
14245
+ clientId !== void 0 ? { clientId } : void 0
13250
14246
  );
14247
+ res.status(200).json(response);
13251
14248
  }
13252
- res.status(200).json(response);
13253
- } catch (err) {
13254
- sendBridgeError2(res, err, {
13255
- route: "POST /session/:id/recap",
13256
- sessionId
13257
- });
13258
- }
13259
- });
13260
- app.post("/session/:id/btw", mutate(), async (req, res) => {
13261
- const sessionId = requireSessionId(req, res);
13262
- if (sessionId === null) return;
13263
- const body = safeBody(req);
13264
- const question = body["question"];
13265
- if (typeof question !== "string" || question.trim().length === 0 || question.length > BTW_MAX_INPUT_LENGTH) {
13266
- res.status(400).json({
13267
- error: `\`question\` is required, must be a non-empty string, and at most ${BTW_MAX_INPUT_LENGTH} characters`
13268
- });
13269
- return;
13270
- }
13271
- const abort = new AbortController();
13272
- const onResClose = /* @__PURE__ */ __name(() => {
13273
- if (!res.writableEnded) abort.abort();
13274
- }, "onResClose");
13275
- res.once("close", onResClose);
13276
- const clientId = parseClientIdHeader(req, res);
13277
- if (clientId === null) {
13278
- res.off("close", onResClose);
13279
- return;
13280
- }
13281
- try {
13282
- const result = await bridge.generateSessionBtw(
13283
- sessionId,
13284
- question.trim(),
13285
- abort.signal,
13286
- clientId !== void 0 ? { clientId } : void 0
13287
- );
13288
- res.status(200).json(result);
13289
- } catch (err) {
13290
- if (err instanceof DOMException && err.name === "AbortError" && abort.signal.aborted) {
14249
+ )
14250
+ );
14251
+ app.post(
14252
+ "/session/:id/recap",
14253
+ mutate(),
14254
+ withMutableSession(
14255
+ "POST /session/:id/recap",
14256
+ async (req, res, sessionId) => {
14257
+ const clientId = parseClientIdHeader(req, res);
14258
+ if (clientId === null) return;
14259
+ const response = await bridge.generateSessionRecap(
14260
+ sessionId,
14261
+ clientId !== void 0 ? { clientId } : void 0
14262
+ );
14263
+ if (daemonLog) {
14264
+ const recap = response.recap;
14265
+ daemonLog.info(
14266
+ recap ? `recap generated len=${recap.length}` : "recap returned null",
14267
+ { sessionId, clientId }
14268
+ );
14269
+ }
14270
+ res.status(200).json(response);
14271
+ }
14272
+ )
14273
+ );
14274
+ app.post(
14275
+ "/session/:id/btw",
14276
+ mutate(),
14277
+ withMutableSession("POST /session/:id/btw", async (req, res, sessionId) => {
14278
+ const body = safeBody(req);
14279
+ const question = body["question"];
14280
+ if (typeof question !== "string" || question.trim().length === 0 || question.length > BTW_MAX_INPUT_LENGTH) {
14281
+ res.status(400).json({
14282
+ error: `\`question\` is required, must be a non-empty string, and at most ${BTW_MAX_INPUT_LENGTH} characters`
14283
+ });
14284
+ return;
14285
+ }
14286
+ const abort = new AbortController();
14287
+ const onResClose = /* @__PURE__ */ __name(() => {
14288
+ if (!res.writableEnded) abort.abort();
14289
+ }, "onResClose");
14290
+ res.once("close", onResClose);
14291
+ const clientId = parseClientIdHeader(req, res);
14292
+ if (clientId === null) {
14293
+ res.off("close", onResClose);
13291
14294
  return;
13292
14295
  }
13293
- sendBridgeError2(res, err, {
13294
- route: "POST /session/:id/btw",
13295
- sessionId
13296
- });
13297
- } finally {
13298
- res.off("close", onResClose);
13299
- }
13300
- });
13301
- const MID_TURN_MESSAGE_MAX_LENGTH = 16 * 1024;
13302
- app.post("/session/:id/mid-turn-message", mutate(), (req, res) => {
13303
- const sessionId = requireSessionId(req, res);
13304
- if (sessionId === null) return;
13305
- const body = safeBody(req);
13306
- const message = body["message"];
13307
- const trimmed = typeof message === "string" ? message.trim() : "";
13308
- if (trimmed.length === 0) {
13309
- res.status(400).json({
13310
- error: "`message` is required and must be a non-empty string"
13311
- });
13312
- return;
13313
- }
13314
- if (trimmed.length > MID_TURN_MESSAGE_MAX_LENGTH) {
13315
- res.status(400).json({
13316
- error: `\`message\` must be at most ${MID_TURN_MESSAGE_MAX_LENGTH} characters`
13317
- });
13318
- return;
13319
- }
13320
- const clientId = parseClientIdHeader(req, res);
13321
- if (clientId === null) return;
13322
- try {
13323
- const result = bridge.enqueueMidTurnMessage(
13324
- sessionId,
13325
- trimmed,
13326
- clientId !== void 0 ? { clientId } : void 0
13327
- );
13328
- res.status(200).json(result);
13329
- } catch (err) {
13330
- sendBridgeError2(res, err, {
13331
- route: "POST /session/:id/mid-turn-message",
13332
- sessionId
13333
- });
13334
- }
13335
- });
14296
+ try {
14297
+ const result = await bridge.generateSessionBtw(
14298
+ sessionId,
14299
+ question.trim(),
14300
+ abort.signal,
14301
+ clientId !== void 0 ? { clientId } : void 0
14302
+ );
14303
+ res.status(200).json(result);
14304
+ } catch (err) {
14305
+ if (err instanceof DOMException && err.name === "AbortError" && abort.signal.aborted) {
14306
+ return;
14307
+ }
14308
+ sendBridgeError2(res, err, {
14309
+ route: "POST /session/:id/btw",
14310
+ sessionId
14311
+ });
14312
+ } finally {
14313
+ res.off("close", onResClose);
14314
+ }
14315
+ })
14316
+ );
14317
+ const MID_TURN_MESSAGE_MAX_LENGTH = 16 * 1024;
14318
+ app.post(
14319
+ "/session/:id/mid-turn-message",
14320
+ mutate(),
14321
+ withMutableSession(
14322
+ "POST /session/:id/mid-turn-message",
14323
+ (req, res, sessionId) => {
14324
+ const body = safeBody(req);
14325
+ const message = body["message"];
14326
+ const trimmed = typeof message === "string" ? message.trim() : "";
14327
+ if (trimmed.length === 0) {
14328
+ res.status(400).json({
14329
+ error: "`message` is required and must be a non-empty string"
14330
+ });
14331
+ return;
14332
+ }
14333
+ if (trimmed.length > MID_TURN_MESSAGE_MAX_LENGTH) {
14334
+ res.status(400).json({
14335
+ error: `\`message\` must be at most ${MID_TURN_MESSAGE_MAX_LENGTH} characters`
14336
+ });
14337
+ return;
14338
+ }
14339
+ const clientId = parseClientIdHeader(req, res);
14340
+ if (clientId === null) return;
14341
+ const result = bridge.enqueueMidTurnMessage(
14342
+ sessionId,
14343
+ trimmed,
14344
+ clientId !== void 0 ? { clientId } : void 0
14345
+ );
14346
+ res.status(200).json(result);
14347
+ }
14348
+ )
14349
+ );
13336
14350
  app.get("/session/:id/pending-prompts", (req, res) => {
13337
14351
  const sessionId = requireSessionId(req, res);
13338
14352
  if (sessionId === null) return;
@@ -13351,90 +14365,94 @@ function registerSessionRoutes(app, deps) {
13351
14365
  });
13352
14366
  }
13353
14367
  });
13354
- app.delete("/session/:id/pending-prompts/:promptId", mutate(), (req, res) => {
13355
- const sessionId = requireSessionId(req, res);
13356
- if (sessionId === null) return;
13357
- const clientId = parseClientIdHeader(req, res);
13358
- if (clientId === null) return;
13359
- const promptId = req.params["promptId"];
13360
- if (!promptId) {
13361
- res.status(400).json({ error: "`promptId` route parameter is required" });
13362
- return;
13363
- }
13364
- try {
13365
- const result = bridge.removePendingPrompt(
13366
- sessionId,
13367
- promptId,
13368
- clientId !== void 0 ? { clientId } : void 0
13369
- );
13370
- res.status(200).json(result);
13371
- } catch (err) {
13372
- sendBridgeError2(res, err, {
13373
- route: "DELETE /session/:id/pending-prompts/:promptId",
13374
- sessionId
13375
- });
13376
- }
13377
- });
13378
- app.post("/session/:id/shell", mutate({ strict: true }), async (req, res) => {
13379
- const sessionId = req.params["id"];
13380
- if (!sessionShellCommandEnabled) {
13381
- sendBridgeError2(res, new SessionShellDisabledError(), {
13382
- route: "POST /session/:id/shell",
13383
- sessionId
13384
- });
13385
- return;
13386
- }
13387
- const clientId = parseClientIdHeader(req, res);
13388
- if (clientId === null) {
13389
- return;
13390
- }
13391
- if (clientId === void 0) {
13392
- sendBridgeError2(res, new SessionShellClientRequiredError(), {
13393
- route: "POST /session/:id/shell",
13394
- sessionId
13395
- });
13396
- return;
13397
- }
13398
- const body = safeBody(req);
13399
- const command = body["command"];
13400
- if (typeof command !== "string" || command.trim().length === 0) {
13401
- res.status(400).json({
13402
- error: "`command` is required and must be a non-empty string"
13403
- });
13404
- return;
13405
- }
13406
- const abort = new AbortController();
13407
- const onResClose = /* @__PURE__ */ __name(() => {
13408
- if (!res.writableEnded) abort.abort();
13409
- }, "onResClose");
13410
- res.once("close", onResClose);
13411
- try {
13412
- const result = await bridge.executeShellCommand(
13413
- sessionId,
13414
- command.trim(),
13415
- abort.signal,
13416
- { clientId }
13417
- );
13418
- if (daemonLog) {
13419
- daemonLog.info("shell command completed", {
14368
+ app.delete(
14369
+ "/session/:id/pending-prompts/:promptId",
14370
+ mutate(),
14371
+ withMutableSession(
14372
+ "DELETE /session/:id/pending-prompts/:promptId",
14373
+ (req, res, sessionId) => {
14374
+ const clientId = parseClientIdHeader(req, res);
14375
+ if (clientId === null) return;
14376
+ const promptId = req.params["promptId"];
14377
+ if (!promptId) {
14378
+ res.status(400).json({ error: "`promptId` route parameter is required" });
14379
+ return;
14380
+ }
14381
+ const result = bridge.removePendingPrompt(
13420
14382
  sessionId,
13421
- clientId,
13422
- exitCode: result.exitCode
13423
- });
14383
+ promptId,
14384
+ clientId !== void 0 ? { clientId } : void 0
14385
+ );
14386
+ res.status(200).json(result);
13424
14387
  }
13425
- res.status(200).json(result);
13426
- } catch (err) {
13427
- if (err instanceof DOMException && err.name === "AbortError" && abort.signal.aborted) {
13428
- return;
14388
+ )
14389
+ );
14390
+ app.post(
14391
+ "/session/:id/shell",
14392
+ mutate({ strict: true }),
14393
+ withMutableSession(
14394
+ "POST /session/:id/shell",
14395
+ async (req, res, sessionId) => {
14396
+ if (!sessionShellCommandEnabled) {
14397
+ sendBridgeError2(res, new SessionShellDisabledError(), {
14398
+ route: "POST /session/:id/shell",
14399
+ sessionId
14400
+ });
14401
+ return;
14402
+ }
14403
+ const clientId = parseClientIdHeader(req, res);
14404
+ if (clientId === null) {
14405
+ return;
14406
+ }
14407
+ if (clientId === void 0) {
14408
+ sendBridgeError2(res, new SessionShellClientRequiredError(), {
14409
+ route: "POST /session/:id/shell",
14410
+ sessionId
14411
+ });
14412
+ return;
14413
+ }
14414
+ const body = safeBody(req);
14415
+ const command = body["command"];
14416
+ if (typeof command !== "string" || command.trim().length === 0) {
14417
+ res.status(400).json({
14418
+ error: "`command` is required and must be a non-empty string"
14419
+ });
14420
+ return;
14421
+ }
14422
+ const abort = new AbortController();
14423
+ const onResClose = /* @__PURE__ */ __name(() => {
14424
+ if (!res.writableEnded) abort.abort();
14425
+ }, "onResClose");
14426
+ res.once("close", onResClose);
14427
+ try {
14428
+ const result = await bridge.executeShellCommand(
14429
+ sessionId,
14430
+ command.trim(),
14431
+ abort.signal,
14432
+ { clientId }
14433
+ );
14434
+ if (daemonLog) {
14435
+ daemonLog.info("shell command completed", {
14436
+ sessionId,
14437
+ clientId,
14438
+ exitCode: result.exitCode
14439
+ });
14440
+ }
14441
+ res.status(200).json(result);
14442
+ } catch (err) {
14443
+ if (err instanceof DOMException && err.name === "AbortError" && abort.signal.aborted) {
14444
+ return;
14445
+ }
14446
+ sendBridgeError2(res, err, {
14447
+ route: "POST /session/:id/shell",
14448
+ sessionId
14449
+ });
14450
+ } finally {
14451
+ res.off("close", onResClose);
14452
+ }
13429
14453
  }
13430
- sendBridgeError2(res, err, {
13431
- route: "POST /session/:id/shell",
13432
- sessionId
13433
- });
13434
- } finally {
13435
- res.off("close", onResClose);
13436
- }
13437
- });
14454
+ )
14455
+ );
13438
14456
  app.get("/session/:id/rewind/snapshots", async (req, res) => {
13439
14457
  const sessionId = req.params["id"];
13440
14458
  if (!sessionId) {
@@ -13453,60 +14471,55 @@ function registerSessionRoutes(app, deps) {
13453
14471
  app.post(
13454
14472
  "/session/:id/rewind",
13455
14473
  mutate({ strict: true }),
13456
- async (req, res) => {
13457
- const sessionId = req.params["id"];
13458
- const body = safeBody(req);
13459
- const promptId = body["promptId"];
13460
- if (typeof promptId !== "string" || promptId.length === 0) {
13461
- res.status(400).json({
13462
- error: "`promptId` is required and must be a non-empty string",
13463
- code: "missing_prompt_id"
13464
- });
13465
- return;
13466
- }
13467
- const clientId = parseClientIdHeader(req, res);
13468
- if (clientId === null) return;
13469
- try {
14474
+ withMutableSession(
14475
+ "POST /session/:id/rewind",
14476
+ async (req, res, sessionId) => {
14477
+ const body = safeBody(req);
14478
+ const promptId = body["promptId"];
14479
+ if (typeof promptId !== "string" || promptId.length === 0) {
14480
+ res.status(400).json({
14481
+ error: "`promptId` is required and must be a non-empty string",
14482
+ code: "missing_prompt_id"
14483
+ });
14484
+ return;
14485
+ }
14486
+ const clientId = parseClientIdHeader(req, res);
14487
+ if (clientId === null) return;
13470
14488
  const response = await bridge.rewindSession(
13471
14489
  sessionId,
13472
14490
  { promptId, rewindFiles: body["rewindFiles"] !== false },
13473
14491
  clientId !== void 0 ? { clientId } : void 0
13474
14492
  );
13475
14493
  res.status(200).json(response);
13476
- } catch (err) {
13477
- sendBridgeError2(res, err, {
13478
- route: "POST /session/:id/rewind",
13479
- sessionId
13480
- });
13481
14494
  }
13482
- }
14495
+ )
13483
14496
  );
13484
14497
  app.post(
13485
14498
  "/session/:id/approval-mode",
13486
14499
  mutate({ strict: true }),
13487
- async (req, res) => {
13488
- const sessionId = req.params["id"];
13489
- const body = safeBody(req);
13490
- const mode = body["mode"];
13491
- const persist = body["persist"];
13492
- if (typeof mode !== "string" || !APPROVAL_MODES.includes(mode)) {
13493
- res.status(400).json({
13494
- error: "`mode` is required and must be one of the allowed values",
13495
- code: "invalid_approval_mode",
13496
- allowed: APPROVAL_MODES
13497
- });
13498
- return;
13499
- }
13500
- if (persist !== void 0 && typeof persist !== "boolean") {
13501
- res.status(400).json({
13502
- error: "`persist` must be a boolean when provided",
13503
- code: "invalid_persist_flag"
13504
- });
13505
- return;
13506
- }
13507
- const clientId = parseClientIdHeader(req, res);
13508
- if (clientId === null) return;
13509
- try {
14500
+ withMutableSession(
14501
+ "POST /session/:id/approval-mode",
14502
+ async (req, res, sessionId) => {
14503
+ const body = safeBody(req);
14504
+ const mode = body["mode"];
14505
+ const persist = body["persist"];
14506
+ if (typeof mode !== "string" || !APPROVAL_MODES.includes(mode)) {
14507
+ res.status(400).json({
14508
+ error: "`mode` is required and must be one of the allowed values",
14509
+ code: "invalid_approval_mode",
14510
+ allowed: APPROVAL_MODES
14511
+ });
14512
+ return;
14513
+ }
14514
+ if (persist !== void 0 && typeof persist !== "boolean") {
14515
+ res.status(400).json({
14516
+ error: "`persist` must be a boolean when provided",
14517
+ code: "invalid_persist_flag"
14518
+ });
14519
+ return;
14520
+ }
14521
+ const clientId = parseClientIdHeader(req, res);
14522
+ if (clientId === null) return;
13510
14523
  const response = await bridge.setSessionApprovalMode(
13511
14524
  sessionId,
13512
14525
  mode,
@@ -13514,53 +14527,47 @@ function registerSessionRoutes(app, deps) {
13514
14527
  clientId !== void 0 ? { clientId } : void 0
13515
14528
  );
13516
14529
  res.status(200).json(response);
13517
- } catch (err) {
13518
- sendBridgeError2(res, err, {
13519
- route: "POST /session/:id/approval-mode",
13520
- sessionId
13521
- });
13522
14530
  }
13523
- }
14531
+ )
14532
+ );
14533
+ app.post(
14534
+ "/session/:id/language",
14535
+ mutate(),
14536
+ withMutableSession(
14537
+ "POST /session/:id/language",
14538
+ async (req, res, sessionId) => {
14539
+ const body = safeBody(req);
14540
+ const language = body["language"];
14541
+ const syncOutputLanguage = body["syncOutputLanguage"];
14542
+ if (typeof language !== "string" || !LANGUAGE_CODES.includes(language)) {
14543
+ res.status(400).json({
14544
+ error: "`language` is required and must be one of: " + LANGUAGE_CODES.join(", "),
14545
+ code: "invalid_language",
14546
+ allowed: LANGUAGE_CODES
14547
+ });
14548
+ return;
14549
+ }
14550
+ if (syncOutputLanguage !== void 0 && typeof syncOutputLanguage !== "boolean") {
14551
+ res.status(400).json({
14552
+ error: "`syncOutputLanguage` must be a boolean when provided",
14553
+ code: "invalid_sync_flag"
14554
+ });
14555
+ return;
14556
+ }
14557
+ const clientId = parseClientIdHeader(req, res);
14558
+ if (clientId === null) return;
14559
+ const response = await bridge.setSessionLanguage(
14560
+ sessionId,
14561
+ {
14562
+ language,
14563
+ syncOutputLanguage: syncOutputLanguage === true
14564
+ },
14565
+ clientId !== void 0 ? { clientId } : void 0
14566
+ );
14567
+ res.status(200).json(response);
14568
+ }
14569
+ )
13524
14570
  );
13525
- app.post("/session/:id/language", mutate(), async (req, res) => {
13526
- const sessionId = req.params["id"];
13527
- const body = safeBody(req);
13528
- const language = body["language"];
13529
- const syncOutputLanguage = body["syncOutputLanguage"];
13530
- if (typeof language !== "string" || !LANGUAGE_CODES.includes(language)) {
13531
- res.status(400).json({
13532
- error: "`language` is required and must be one of: " + LANGUAGE_CODES.join(", "),
13533
- code: "invalid_language",
13534
- allowed: LANGUAGE_CODES
13535
- });
13536
- return;
13537
- }
13538
- if (syncOutputLanguage !== void 0 && typeof syncOutputLanguage !== "boolean") {
13539
- res.status(400).json({
13540
- error: "`syncOutputLanguage` must be a boolean when provided",
13541
- code: "invalid_sync_flag"
13542
- });
13543
- return;
13544
- }
13545
- const clientId = parseClientIdHeader(req, res);
13546
- if (clientId === null) return;
13547
- try {
13548
- const response = await bridge.setSessionLanguage(
13549
- sessionId,
13550
- {
13551
- language,
13552
- syncOutputLanguage: syncOutputLanguage === true
13553
- },
13554
- clientId !== void 0 ? { clientId } : void 0
13555
- );
13556
- res.status(200).json(response);
13557
- } catch (err) {
13558
- sendBridgeError2(res, err, {
13559
- route: "POST /session/:id/language",
13560
- sessionId
13561
- });
13562
- }
13563
- });
13564
14571
  }
13565
14572
  __name(registerSessionRoutes, "registerSessionRoutes");
13566
14573
 
@@ -14213,6 +15220,31 @@ function sendBridgeError(res, err, ctx, daemonLog) {
14213
15220
  res.status(404).json({ error: err.message, sessionId: err.sessionId });
14214
15221
  return;
14215
15222
  }
15223
+ if (err instanceof SessionArchivedError) {
15224
+ res.status(409).json({
15225
+ error: err.message,
15226
+ code: "session_archived",
15227
+ sessionId: err.sessionId
15228
+ });
15229
+ return;
15230
+ }
15231
+ if (err instanceof SessionConflictError) {
15232
+ res.status(409).json({
15233
+ error: err.message,
15234
+ code: "session_conflict",
15235
+ sessionId: err.sessionId
15236
+ });
15237
+ return;
15238
+ }
15239
+ if (err instanceof SessionArchivingError) {
15240
+ res.set("Retry-After", "5");
15241
+ res.status(409).json({
15242
+ error: err.message,
15243
+ code: "session_archiving",
15244
+ sessionId: err.sessionId
15245
+ });
15246
+ return;
15247
+ }
14216
15248
  if (err instanceof InvalidClientIdError) {
14217
15249
  res.status(400).json({
14218
15250
  error: err.message,
@@ -14320,7 +15352,7 @@ function sendBridgeError(res, err, ctx, daemonLog) {
14320
15352
  if (kind === "mcp_budget_would_exceed") {
14321
15353
  const d = data;
14322
15354
  res.status(409).json({
14323
- error: errorMessage(err),
15355
+ error: errorMessage2(err),
14324
15356
  code: "mcp_budget_would_exceed",
14325
15357
  serverName: d.serverName
14326
15358
  });
@@ -14329,7 +15361,7 @@ function sendBridgeError(res, err, ctx, daemonLog) {
14329
15361
  if (kind === "mcp_server_spawn_failed") {
14330
15362
  const d = data;
14331
15363
  res.status(502).json({
14332
- error: errorMessage(err),
15364
+ error: errorMessage2(err),
14333
15365
  code: "mcp_server_spawn_failed",
14334
15366
  serverName: d.serverName,
14335
15367
  exitCode: d.exitCode,
@@ -14341,7 +15373,7 @@ function sendBridgeError(res, err, ctx, daemonLog) {
14341
15373
  if (kind === "invalid_config") {
14342
15374
  const d = data;
14343
15375
  res.status(400).json({
14344
- error: errorMessage(err),
15376
+ error: errorMessage2(err),
14345
15377
  code: "invalid_config",
14346
15378
  serverName: d.serverName,
14347
15379
  reason: d.reason
@@ -14350,14 +15382,14 @@ function sendBridgeError(res, err, ctx, daemonLog) {
14350
15382
  }
14351
15383
  if (kind === "acp_channel_unavailable") {
14352
15384
  res.status(503).json({
14353
- error: errorMessage(err),
15385
+ error: errorMessage2(err),
14354
15386
  code: "acp_channel_unavailable"
14355
15387
  });
14356
15388
  return;
14357
15389
  }
14358
15390
  if (kind === "restrictive_sandbox") {
14359
15391
  res.status(403).json({
14360
- error: errorMessage(err),
15392
+ error: errorMessage2(err),
14361
15393
  code: "restrictive_sandbox"
14362
15394
  });
14363
15395
  return;
@@ -14365,7 +15397,7 @@ function sendBridgeError(res, err, ctx, daemonLog) {
14365
15397
  if (kind === "directory_not_found") {
14366
15398
  const d = data;
14367
15399
  res.status(400).json({
14368
- error: errorMessage(err),
15400
+ error: errorMessage2(err),
14369
15401
  code: "directory_not_found",
14370
15402
  path: d.path
14371
15403
  });
@@ -14374,7 +15406,7 @@ function sendBridgeError(res, err, ctx, daemonLog) {
14374
15406
  if (kind === "directory_not_trusted") {
14375
15407
  const d = data;
14376
15408
  res.status(403).json({
14377
- error: errorMessage(err),
15409
+ error: errorMessage2(err),
14378
15410
  code: "directory_not_trusted",
14379
15411
  path: d.path
14380
15412
  });
@@ -14422,7 +15454,7 @@ function sendBridgeError(res, err, ctx, daemonLog) {
14422
15454
  res.status(500).json(errorPayload(err));
14423
15455
  }
14424
15456
  __name(sendBridgeError, "sendBridgeError");
14425
- function errorMessage(err) {
15457
+ function errorMessage2(err) {
14426
15458
  if (err instanceof Error) return err.message;
14427
15459
  if (err && typeof err === "object") {
14428
15460
  const maybe = err.message;
@@ -14434,10 +15466,10 @@ function errorMessage(err) {
14434
15466
  }
14435
15467
  return String(err);
14436
15468
  }
14437
- __name(errorMessage, "errorMessage");
15469
+ __name(errorMessage2, "errorMessage");
14438
15470
  function errorPayload(err) {
14439
15471
  const out = {
14440
- error: errorMessage(err)
15472
+ error: errorMessage2(err)
14441
15473
  };
14442
15474
  if (err && typeof err === "object") {
14443
15475
  const obj = err;
@@ -14540,9 +15572,9 @@ function registerSseEventsRoutes(app, deps) {
14540
15572
  let writeChain = Promise.resolve();
14541
15573
  const trackWriterIdle = writerIdleTimeoutMs !== void 0 && writerIdleTimeoutMs > 0;
14542
15574
  let lastWriteAt = trackWriterIdle ? Date.now() : 0;
14543
- const doWrite = /* @__PURE__ */ __name((chunk) => new Promise((resolve4, reject) => {
15575
+ const doWrite = /* @__PURE__ */ __name((chunk) => new Promise((resolve5, reject) => {
14544
15576
  if (res.writableEnded) {
14545
- resolve4();
15577
+ resolve5();
14546
15578
  return;
14547
15579
  }
14548
15580
  let ok;
@@ -14554,19 +15586,19 @@ function registerSseEventsRoutes(app, deps) {
14554
15586
  }
14555
15587
  if (ok) {
14556
15588
  if (trackWriterIdle) lastWriteAt = Date.now();
14557
- resolve4();
15589
+ resolve5();
14558
15590
  return;
14559
15591
  }
14560
15592
  const onDrain = /* @__PURE__ */ __name(() => {
14561
15593
  res.off("close", onClose);
14562
15594
  res.off("error", onError);
14563
15595
  if (trackWriterIdle) lastWriteAt = Date.now();
14564
- resolve4();
15596
+ resolve5();
14565
15597
  }, "onDrain");
14566
15598
  const onClose = /* @__PURE__ */ __name(() => {
14567
15599
  res.off("drain", onDrain);
14568
15600
  res.off("error", onError);
14569
- resolve4();
15601
+ resolve5();
14570
15602
  }, "onClose");
14571
15603
  const onError = /* @__PURE__ */ __name((err) => {
14572
15604
  res.off("drain", onDrain);
@@ -14662,14 +15694,14 @@ function registerSseEventsRoutes(app, deps) {
14662
15694
  if (!res.writableEnded) {
14663
15695
  const errorKind = mapDomainErrorToErrorKind(err);
14664
15696
  writeStderrLine(
14665
- `qwen serve: bridge iterator error (session ${sessionId}): ${errorMessage(err)}` + (errorKind ? ` [${errorKind}]` : "")
15697
+ `qwen serve: bridge iterator error (session ${sessionId}): ${errorMessage2(err)}` + (errorKind ? ` [${errorKind}]` : "")
14666
15698
  );
14667
15699
  await writeWithBackpressure(
14668
15700
  formatSseFrame({
14669
15701
  v: 1,
14670
15702
  type: "stream_error",
14671
15703
  data: {
14672
- error: errorMessage(err),
15704
+ error: errorMessage2(err),
14673
15705
  ...errorKind ? { errorKind } : {}
14674
15706
  }
14675
15707
  })
@@ -14688,7 +15720,7 @@ __name(registerSseEventsRoutes, "registerSseEventsRoutes");
14688
15720
  // packages/cli/src/serve/routes/a2ui-action.ts
14689
15721
  init_esbuild_shims();
14690
15722
  import * as fsp4 from "node:fs/promises";
14691
- import * as path11 from "node:path";
15723
+ import * as path12 from "node:path";
14692
15724
  var A2UI_MIME = "application/a2ui+json";
14693
15725
  var ACTION_TOOL = "action";
14694
15726
  var CALL_TIMEOUT_MS = 15e3;
@@ -14708,7 +15740,7 @@ __name(usableServerConfig, "usableServerConfig");
14708
15740
  async function findFromSettingsFile(workspaceCwd) {
14709
15741
  try {
14710
15742
  const raw = await fsp4.readFile(
14711
- path11.join(workspaceCwd, ".qwen", "settings.json"),
15743
+ path12.join(workspaceCwd, ".qwen", "settings.json"),
14712
15744
  "utf8"
14713
15745
  );
14714
15746
  const settings = JSON.parse(raw);
@@ -14866,17 +15898,17 @@ __name(resolveBridgeFsFactory, "resolveBridgeFsFactory");
14866
15898
  // packages/cli/src/serve/server/telemetry.ts
14867
15899
  init_esbuild_shims();
14868
15900
  function resolveDaemonTelemetryRoute(req) {
14869
- const path12 = req.path.replace(/\/$/, "") || "/";
14870
- if (req.method === "POST" && path12 === "/session") {
15901
+ const path13 = req.path.replace(/\/$/, "") || "/";
15902
+ if (req.method === "POST" && path13 === "/session") {
14871
15903
  return { route: "POST /session" };
14872
15904
  }
14873
- if (req.method === "POST" && path12 === "/sessions/delete") {
15905
+ if (req.method === "POST" && path13 === "/sessions/delete") {
14874
15906
  return { route: "POST /sessions/delete" };
14875
15907
  }
14876
- if (req.method === "GET" && path12 === "/daemon/status") {
15908
+ if (req.method === "GET" && path13 === "/daemon/status") {
14877
15909
  return { route: "GET /daemon/status" };
14878
15910
  }
14879
- const sessionAction = path12.match(
15911
+ const sessionAction = path13.match(
14880
15912
  /^\/session\/([^/]+)\/(load|resume|prompt|cancel|recap|btw|mid-turn-message|model|shell|detach|rewind|approval-mode|language|a2ui-action)$/
14881
15913
  );
14882
15914
  const sessionActionId = sessionAction?.[1];
@@ -14887,14 +15919,14 @@ function resolveDaemonTelemetryRoute(req) {
14887
15919
  sessionId: sessionActionId
14888
15920
  };
14889
15921
  }
14890
- const sessionMetadata = path12.match(/^\/session\/([^/]+)\/metadata$/);
15922
+ const sessionMetadata = path13.match(/^\/session\/([^/]+)\/metadata$/);
14891
15923
  if (sessionMetadata?.[1] && req.method === "PATCH") {
14892
15924
  return {
14893
15925
  route: "PATCH /session/:id/metadata",
14894
15926
  sessionId: sessionMetadata[1]
14895
15927
  };
14896
15928
  }
14897
- const sessionPermission = path12.match(
15929
+ const sessionPermission = path13.match(
14898
15930
  /^\/session\/([^/]+)\/permission\/([^/]+)$/
14899
15931
  );
14900
15932
  if (sessionPermission?.[1] && sessionPermission?.[2] && req.method === "POST") {
@@ -14905,7 +15937,7 @@ function resolveDaemonTelemetryRoute(req) {
14905
15937
  ...rawRequestId.length <= MAX_CLIENT_ID_LENGTH && CLIENT_ID_RE.test(rawRequestId) ? { permissionRequestId: rawRequestId } : {}
14906
15938
  };
14907
15939
  }
14908
- const globalPermission = path12.match(/^\/permission\/([^/]+)$/);
15940
+ const globalPermission = path13.match(/^\/permission\/([^/]+)$/);
14909
15941
  if (globalPermission?.[1] && req.method === "POST") {
14910
15942
  const rawRequestId = globalPermission[1];
14911
15943
  return {
@@ -14913,66 +15945,66 @@ function resolveDaemonTelemetryRoute(req) {
14913
15945
  ...rawRequestId.length <= MAX_CLIENT_ID_LENGTH && CLIENT_ID_RE.test(rawRequestId) ? { permissionRequestId: rawRequestId } : {}
14914
15946
  };
14915
15947
  }
14916
- const deleteSession = path12.match(/^\/session\/([^/]+)$/);
15948
+ const deleteSession = path13.match(/^\/session\/([^/]+)$/);
14917
15949
  const deleteSessionId = deleteSession?.[1];
14918
15950
  if (deleteSessionId && req.method === "DELETE") {
14919
15951
  return { route: "DELETE /session/:id", sessionId: deleteSessionId };
14920
15952
  }
14921
- if (req.method === "GET" && /^\/workspace\/[^/]+\/sessions$/.test(path12)) {
15953
+ if (req.method === "GET" && /^\/workspace\/[^/]+\/sessions$/.test(path13)) {
14922
15954
  return { route: "GET /workspace/:id/sessions" };
14923
15955
  }
14924
- if (req.method === "POST" && path12 === "/workspace/init") {
15956
+ if (req.method === "POST" && path13 === "/workspace/init") {
14925
15957
  return { route: "POST /workspace/init" };
14926
15958
  }
14927
- if (req.method === "POST" && path12 === "/workspace/setup-github") {
15959
+ if (req.method === "POST" && path13 === "/workspace/setup-github") {
14928
15960
  return { route: "POST /workspace/setup-github" };
14929
15961
  }
14930
- if (req.method === "POST" && path12 === "/workspace/reload") {
15962
+ if (req.method === "POST" && path13 === "/workspace/reload") {
14931
15963
  return { route: "POST /workspace/reload" };
14932
15964
  }
14933
- const mcpRestart = path12.match(/^\/workspace\/mcp\/([^/]+)\/restart$/);
15965
+ const mcpRestart = path13.match(/^\/workspace\/mcp\/([^/]+)\/restart$/);
14934
15966
  if (mcpRestart?.[1] && req.method === "POST") {
14935
15967
  return { route: "POST /workspace/mcp/:server/restart" };
14936
15968
  }
14937
- if (req.method === "POST" && path12 === "/workspace/mcp/servers") {
15969
+ if (req.method === "POST" && path13 === "/workspace/mcp/servers") {
14938
15970
  return { route: "POST /workspace/mcp/servers" };
14939
15971
  }
14940
- const mcpDelete = path12.match(/^\/workspace\/mcp\/servers\/([^/]+)$/);
15972
+ const mcpDelete = path13.match(/^\/workspace\/mcp\/servers\/([^/]+)$/);
14941
15973
  if (mcpDelete?.[1] && req.method === "DELETE") {
14942
15974
  return { route: "DELETE /workspace/mcp/servers/:name" };
14943
15975
  }
14944
- if (req.method === "POST" && path12 === "/workspace/auth/device-flow") {
15976
+ if (req.method === "POST" && path13 === "/workspace/auth/device-flow") {
14945
15977
  return { route: "POST /workspace/auth/device-flow" };
14946
15978
  }
14947
- const deviceFlowDelete = path12.match(
15979
+ const deviceFlowDelete = path13.match(
14948
15980
  /^\/workspace\/auth\/device-flow\/([^/]+)$/
14949
15981
  );
14950
15982
  if (deviceFlowDelete?.[1] && req.method === "DELETE") {
14951
15983
  return { route: "DELETE /workspace/auth/device-flow/:id" };
14952
15984
  }
14953
- const toolEnable = path12.match(/^\/workspace\/tools\/([^/]+)\/enable$/);
15985
+ const toolEnable = path13.match(/^\/workspace\/tools\/([^/]+)\/enable$/);
14954
15986
  if (toolEnable?.[1] && req.method === "POST") {
14955
15987
  return { route: "POST /workspace/tools/:name/enable" };
14956
15988
  }
14957
- if (path12 === "/workspace/settings") {
15989
+ if (path13 === "/workspace/settings") {
14958
15990
  if (req.method === "GET") return { route: "GET /workspace/settings" };
14959
15991
  if (req.method === "POST") return { route: "POST /workspace/settings" };
14960
15992
  }
14961
- if (path12 === "/workspace/permissions") {
15993
+ if (path13 === "/workspace/permissions") {
14962
15994
  if (req.method === "GET") return { route: "GET /workspace/permissions" };
14963
15995
  if (req.method === "POST") return { route: "POST /workspace/permissions" };
14964
15996
  }
14965
- if (path12 === "/workspace/trust") {
15997
+ if (path13 === "/workspace/trust") {
14966
15998
  if (req.method === "GET") return { route: "GET /workspace/trust" };
14967
15999
  }
14968
- if (req.method === "POST" && path12 === "/workspace/trust/request") {
16000
+ if (req.method === "POST" && path13 === "/workspace/trust/request") {
14969
16001
  return { route: "POST /workspace/trust/request" };
14970
16002
  }
14971
- if (path12 === "/workspace/voice") {
16003
+ if (path13 === "/workspace/voice") {
14972
16004
  if (req.method === "GET") return { route: "GET /workspace/voice" };
14973
16005
  if (req.method === "POST") return { route: "POST /workspace/voice" };
14974
16006
  }
14975
- if (req.method === "POST" && path12 === "/workspace/voice/transcribe") {
16007
+ if (req.method === "POST" && path13 === "/workspace/voice/transcribe") {
14976
16008
  return { route: "POST /workspace/voice/transcribe" };
14977
16009
  }
14978
16010
  return void 0;
@@ -14998,7 +16030,7 @@ function daemonTelemetryMiddleware(boundWorkspace) {
14998
16030
  ...route.permissionRequestId ? { permissionRequestId: route.permissionRequestId } : {},
14999
16031
  ...clientId ? { clientId } : {}
15000
16032
  },
15001
- async (span) => await new Promise((resolve4, reject) => {
16033
+ async (span) => await new Promise((resolve5, reject) => {
15002
16034
  let done = false;
15003
16035
  const finish = /* @__PURE__ */ __name(() => {
15004
16036
  if (done) return;
@@ -15009,7 +16041,7 @@ function daemonTelemetryMiddleware(boundWorkspace) {
15009
16041
  route.route,
15010
16042
  res.statusCode
15011
16043
  );
15012
- resolve4();
16044
+ resolve5();
15013
16045
  }, "finish");
15014
16046
  res.once("finish", finish);
15015
16047
  res.once("close", finish);
@@ -15332,10 +16364,10 @@ function installRateLimiter(app, opts, daemonLog) {
15332
16364
  { tier, key: key.slice(0, 64) }
15333
16365
  );
15334
16366
  } : void 0,
15335
- onError: daemonLog ? (err, path12) => {
16367
+ onError: daemonLog ? (err, path13) => {
15336
16368
  daemonLog.warn(
15337
16369
  `rate limiter error (fail-open): ${err instanceof Error ? err.message : String(err)}`,
15338
- { path: path12 }
16370
+ { path: path13 }
15339
16371
  );
15340
16372
  } : void 0
15341
16373
  });
@@ -15716,6 +16748,7 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
15716
16748
  // ext-method by reaching the WS connection that hosts the named server.
15717
16749
  clientMcpSender: clientMcpSenderRegistry.lookup
15718
16750
  });
16751
+ const archiveCoordinator = new SessionArchiveCoordinator();
15719
16752
  installSelfOriginStripMiddleware(app, getPort);
15720
16753
  app.locals.fsFactory = fsFactory;
15721
16754
  app.locals.boundWorkspace = boundWorkspace;
@@ -15794,6 +16827,7 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
15794
16827
  app.use(daemonTelemetryMiddleware(boundWorkspace));
15795
16828
  const buildWorkspaceCtx = createBuildWorkspaceCtx(boundWorkspace);
15796
16829
  const acpHandleRef = {};
16830
+ const workspaceRememberLane = new WorkspaceRememberTaskLane(bridge);
15797
16831
  const cdpTunnelRegistry = opts.cdpTunnelOverWs === true ? new CdpTunnelRegistry() : void 0;
15798
16832
  registerDaemonStatusRoutes(app, {
15799
16833
  opts,
@@ -15809,7 +16843,8 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
15809
16843
  currentServeFeatures,
15810
16844
  getSupportedDeviceFlowProviders,
15811
16845
  deviceFlowRegistry,
15812
- sessionShellCommandEnabled
16846
+ sessionShellCommandEnabled,
16847
+ getChannelWorkerSnapshot: deps.getChannelWorkerSnapshot
15813
16848
  });
15814
16849
  registerCapabilitiesRoutes(app, {
15815
16850
  qwenCodeVersion: deps.qwenCodeVersion,
@@ -15833,6 +16868,13 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
15833
16868
  parseClientId: parseClientIdHeader,
15834
16869
  safeBody
15835
16870
  });
16871
+ mountWorkspaceMemoryRememberRoutes(app, {
16872
+ bridge,
16873
+ lane: workspaceRememberLane,
16874
+ mutate,
16875
+ parseClientId: parseClientIdHeader,
16876
+ safeBody
16877
+ });
15836
16878
  mountWorkspaceAgentsRoutes(app, {
15837
16879
  bridge,
15838
16880
  boundWorkspace,
@@ -15940,6 +16982,7 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
15940
16982
  registerSessionRoutes(app, {
15941
16983
  boundWorkspace,
15942
16984
  bridge,
16985
+ archiveCoordinator,
15943
16986
  mutate,
15944
16987
  sendBridgeError: sendBridgeError2,
15945
16988
  daemonLog,
@@ -15986,6 +17029,7 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
15986
17029
  });
15987
17030
  acpHandleRef.current = mountAcpHttp(app, bridge, {
15988
17031
  boundWorkspace,
17032
+ archiveCoordinator,
15989
17033
  workspace,
15990
17034
  fsFactory,
15991
17035
  deviceFlowRegistry,
@@ -15994,7 +17038,9 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
15994
17038
  // explicitly permitted origin (e.g. the extension's
15995
17039
  // `chrome-extension://<id>`) can open the reverse tool channel.
15996
17040
  allowedOrigins: opts.allowOrigins && opts.allowOrigins.length > 0 ? parseAllowOriginPatterns(opts.allowOrigins) : void 0,
17041
+ hostname: opts.hostname,
15997
17042
  sessionShellCommandEnabled,
17043
+ workspaceRememberLane,
15998
17044
  checkRate: rateLimiter?.checkRate,
15999
17045
  clientMcpOverWs: opts.clientMcpOverWs === true,
16000
17046
  // Reverse tool channel (issue #5626, Phase 2). Per-connection provider: