@qwen-code/qwen-code 0.19.7 → 0.19.8-nightly.20260710.205430235

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 (292) hide show
  1. package/bundled/qc-helper/SKILL.md +1 -0
  2. package/bundled/qc-helper/docs/configuration/auth.md +40 -55
  3. package/bundled/qc-helper/docs/configuration/model-providers.md +224 -218
  4. package/bundled/qc-helper/docs/configuration/settings.md +43 -36
  5. package/bundled/qc-helper/docs/features/_meta.ts +1 -0
  6. package/bundled/qc-helper/docs/features/channels/overview.md +20 -12
  7. package/bundled/qc-helper/docs/features/channels/plugins.md +1 -0
  8. package/bundled/qc-helper/docs/features/code-review.md +2 -1
  9. package/bundled/qc-helper/docs/features/commands.md +39 -38
  10. package/bundled/qc-helper/docs/features/computer-use.md +77 -0
  11. package/bundled/qc-helper/docs/features/hooks.md +36 -34
  12. package/bundled/qc-helper/docs/features/memory.md +1 -1
  13. package/bundled/qc-helper/docs/features/tool-use-summaries.md +19 -23
  14. package/bundled/qc-helper/docs/qwen-serve.md +22 -12
  15. package/bundled/qc-helper/docs/reference/keyboard-shortcuts.md +1 -1
  16. package/bundled/review/DESIGN.md +14 -13
  17. package/bundled/review/SKILL.md +38 -61
  18. package/chunks/{MaxSizedBox-XY5LIO5O.js → MaxSizedBox-LOFYQFYJ.js} +13 -14
  19. package/chunks/{StandaloneSessionPicker-APISIRJZ.js → StandaloneSessionPicker-U5OQBGEB.js} +31 -29
  20. package/chunks/{acpAgent-MMOSC2YA.js → acpAgent-SDIFRIED.js} +637 -131
  21. package/chunks/{agent-TKIBLLQT.js → agent-JIBBVHNQ.js} +10 -10
  22. package/chunks/{agent-headless-VLX4C7KX.js → agent-headless-6LGZYJYU.js} +10 -10
  23. package/chunks/{anthropicContentGenerator-AKYPSZIV.js → anthropicContentGenerator-55REPTTF.js} +30 -3
  24. package/chunks/{artifact-tool-D233YJFH.js → artifact-tool-EIN46DPM.js} +1 -1
  25. package/chunks/{askUserQuestion-MKL5ZTMC.js → askUserQuestion-6YX6ZRX7.js} +1 -1
  26. package/chunks/{bridge-BCU73DZO.js → bridge-HVPFYNM4.js} +17 -16
  27. package/chunks/{ca-HCMB2I54.js → ca-MULEGDVS.js} +89 -5
  28. package/chunks/{channel-worker-supervisor-CRCVYQIE.js → channel-worker-supervisor-OPXXT4JK.js} +20 -3
  29. package/chunks/{chunk-DGHJTO2K.js → chunk-2AF26HVM.js} +31 -2
  30. package/chunks/{chunk-3B26UVCX.js → chunk-2GUVUHJY.js} +6 -4
  31. package/chunks/{chunk-FV7425LN.js → chunk-2SZOMGOR.js} +4 -1
  32. package/chunks/{chunk-CNSF3WO5.js → chunk-32KS5XSX.js} +3 -3
  33. package/chunks/{chunk-4UCTWSTP.js → chunk-34UUM3IN.js} +1 -1
  34. package/chunks/{chunk-G7ZG7CA6.js → chunk-3UP777ZS.js} +8 -7
  35. package/chunks/{chunk-MIG5SQNL.js → chunk-4CSVIZLT.js} +4 -4
  36. package/chunks/{chunk-GD236YPV.js → chunk-4ONWIT5C.js} +1 -1
  37. package/chunks/{chunk-4ZJIPVPB.js → chunk-4XK7TNBP.js} +8 -8
  38. package/chunks/{chunk-I4A4K72C.js → chunk-5F2H3PXG.js} +446 -232
  39. package/chunks/chunk-5HOF2K5U.js +19 -0
  40. package/chunks/{chunk-GMOWQOQQ.js → chunk-5ZVIYNLN.js} +228 -36
  41. package/chunks/chunk-6O6Z2I5Q.js +292 -0
  42. package/chunks/{chunk-RYFZCUJO.js → chunk-6QKT5VNZ.js} +14 -6
  43. package/chunks/{chunk-SD4N3QJ2.js → chunk-6ZPSVWLF.js} +61 -17
  44. package/chunks/{chunk-X265R5C6.js → chunk-7YGSDQ5J.js} +2 -2
  45. package/chunks/{chunk-PRDFOIZF.js → chunk-A6KEUFJO.js} +32 -5
  46. package/chunks/{chunk-OWVIH4HY.js → chunk-B5OAMPJ5.js} +389 -87
  47. package/chunks/{chunk-ME3YSROJ.js → chunk-CV2AT2WJ.js} +22 -10
  48. package/chunks/{chunk-U4FRHCLA.js → chunk-DDKUYKHA.js} +1912 -201
  49. package/chunks/{chunk-YDSYD7UI.js → chunk-E3SA2LY4.js} +2 -2
  50. package/chunks/{chunk-6IAWPIQ6.js → chunk-EDM5O3SX.js} +2 -2
  51. package/chunks/{chunk-CISB3IQF.js → chunk-FONJQQWI.js} +65 -6
  52. package/chunks/{chunk-FRFFNYUY.js → chunk-G3WIMNL4.js} +3 -3
  53. package/chunks/{chunk-B34OV7K2.js → chunk-GGQQA4JW.js} +13 -0
  54. package/chunks/chunk-GLE2YUPU.js +34 -0
  55. package/chunks/{chunk-MJI36HER.js → chunk-GOTMC3BX.js} +1 -1
  56. package/chunks/{chunk-RFIL6REB.js → chunk-GRB4QSBD.js} +168 -31
  57. package/chunks/chunk-GVVY7GSP.js +38 -0
  58. package/chunks/{chunk-7JGISHHZ.js → chunk-HDEUQRAR.js} +14 -156
  59. package/chunks/{chunk-NZEGNV4V.js → chunk-HPRI22FZ.js} +6 -6
  60. package/chunks/{chunk-STAZRKOT.js → chunk-I34IHZFJ.js} +117 -459
  61. package/chunks/{chunk-JF7DFPQ5.js → chunk-I3MBKL6Z.js} +12 -2
  62. package/chunks/{chunk-DNV4FSN5.js → chunk-I5XN6KDI.js} +21 -4
  63. package/chunks/{chunk-VZ7HY3YU.js → chunk-IAMRWMFU.js} +1 -1
  64. package/chunks/{chunk-44NROQYV.js → chunk-JOQMY62N.js} +1 -1
  65. package/chunks/{chunk-VQWAZSXN.js → chunk-JW4IKIZ2.js} +57 -35
  66. package/chunks/{chunk-SAIILPQ3.js → chunk-KJMYXFUR.js} +3 -1
  67. package/chunks/{chunk-AFOLR6YL.js → chunk-KQ6MKCFE.js} +102 -16
  68. package/chunks/{chunk-X3YGAX7V.js → chunk-KS2FRTYI.js} +2 -0
  69. package/chunks/{chunk-IUE7YWC3.js → chunk-LE7EF36E.js} +7 -7
  70. package/chunks/{chunk-B4U2M63D.js → chunk-LRY7HD3Q.js} +2101 -11
  71. package/chunks/{chunk-PUQLHSQ2.js → chunk-ME7E6WZB.js} +6 -8
  72. package/chunks/{chunk-LEIJKM7Q.js → chunk-MEK7ZRJ3.js} +1 -1
  73. package/chunks/{chunk-AJBEAUIS.js → chunk-MG2TNCRX.js} +4 -3
  74. package/chunks/{chunk-GL5TV7B5.js → chunk-MSMQURDV.js} +21 -5
  75. package/chunks/{chunk-6BN7NZ5Z.js → chunk-NXDJLKKX.js} +4 -6
  76. package/chunks/{chunk-FEJ2FZ3U.js → chunk-NY2ZOFMM.js} +1 -1
  77. package/chunks/{chunk-QZF5U5KA.js → chunk-NY5BE643.js} +8 -8
  78. package/chunks/{chunk-4RQIB5UZ.js → chunk-O2YT7VB4.js} +0 -34
  79. package/chunks/{chunk-4KJ2BPIT.js → chunk-OACJLMLD.js} +2 -38
  80. package/chunks/chunk-PCWSPWXZ.js +168 -0
  81. package/chunks/{chunk-XPJH4PKR.js → chunk-PJ74K3DH.js} +1 -1
  82. package/chunks/{chunk-FTFXDZYI.js → chunk-PW7VWRLX.js} +45 -29
  83. package/chunks/chunk-R3SLDK6Y.js +468 -0
  84. package/chunks/{chunk-4O344UEE.js → chunk-SDJOZOYY.js} +38 -18
  85. package/chunks/{chunk-ALZ75L2X.js → chunk-SMYIBRAA.js} +412 -376
  86. package/chunks/{chunk-CCGOFQV3.js → chunk-SS5P6JM6.js} +6 -5
  87. package/chunks/{chunk-AIY5UTFI.js → chunk-T3XHYGMG.js} +925 -2872
  88. package/chunks/{chunk-Q6OEPOOC.js → chunk-TB6GWNV4.js} +1 -1
  89. package/chunks/{chunk-24XYI5EI.js → chunk-TERKDCOZ.js} +3 -3
  90. package/chunks/chunk-TVO5C767.js +159 -0
  91. package/chunks/{chunk-TBAZMRW3.js → chunk-TVW53EK7.js} +1 -1
  92. package/chunks/chunk-TX5QZYK6.js +296 -0
  93. package/chunks/{chunk-JD33UY7E.js → chunk-TZRVTPJ6.js} +15 -9
  94. package/chunks/{chunk-65CFHNSE.js → chunk-UAFB2DNB.js} +59 -59
  95. package/chunks/{chunk-QAKFYMJI.js → chunk-UOXLB2LY.js} +152 -17
  96. package/chunks/chunk-V35MD7KN.js +13 -0
  97. package/chunks/{chunk-XSAFNANZ.js → chunk-VWDJHVXM.js} +12 -12
  98. package/chunks/{chunk-XISQIIT7.js → chunk-W77ZLCSX.js} +1 -1
  99. package/chunks/{chunk-BWVQN2HV.js → chunk-WTEIUA2V.js} +4 -4
  100. package/chunks/{chunk-TDGF35EN.js → chunk-XOHNA2S6.js} +1 -1
  101. package/chunks/{chunk-HJ3GX3Y3.js → chunk-XZVUZSDG.js} +25 -11
  102. package/chunks/{chunk-ADZG46HR.js → chunk-YHGCTQG3.js} +1 -1
  103. package/chunks/{chunk-2NPS2UDH.js → chunk-YUNPEB3Q.js} +478 -43
  104. package/chunks/chunk-Z3I3QJK3.js +47 -0
  105. package/chunks/{chunk-P7I3D7FM.js → chunk-Z466FBSA.js} +1 -1
  106. package/chunks/{chunk-YEGT55NW.js → chunk-ZDAZSR2O.js} +3009 -1220
  107. package/chunks/{chunk-VEB3NYSO.js → chunk-ZJQBTFJ6.js} +6 -1
  108. package/chunks/chunk-ZL5HN75Q.js +61 -0
  109. package/chunks/{computer-use-X4KUU3NM.js → computer-use-IW5XBECT.js} +10 -10
  110. package/chunks/{contextCommand-LAYJZF7H.js → contextCommand-4QPLK2SH.js} +14 -14
  111. package/chunks/create-sub-session-7JK3EYWA.js +212 -0
  112. package/chunks/create-sub-session-EVYZYAE3.js +364 -0
  113. package/chunks/{cron-create-JSXEIPVS.js → cron-create-2CCO2PW6.js} +2 -2
  114. package/chunks/{cron-delete-OIFVEPGU.js → cron-delete-ETVTP5IV.js} +2 -2
  115. package/chunks/{cron-list-D4ZX4IOE.js → cron-list-FS3KWOY5.js} +2 -2
  116. package/chunks/{daemon-F3I2RXP6.js → daemon-NNUYIBWA.js} +461 -0
  117. package/chunks/{daemon-status-provider-XSAFKVDS.js → daemon-status-provider-3EPG6BXX.js} +21 -19
  118. package/chunks/{de-PLLFDERI.js → de-QRG6J2CM.js} +89 -5
  119. package/chunks/{dist-YTRKAVUB.js → dist-3EIIFOJV.js} +1 -1
  120. package/chunks/{dist-PKFTNUHO.js → dist-7F5GWZDZ.js} +2 -1
  121. package/chunks/{dist-FQ5WRJPL.js → dist-CBJ7OJ5R.js} +2 -2
  122. package/chunks/{dist-OT7G2WNM.js → dist-LIFJ2YAI.js} +1 -1
  123. package/chunks/{dist-573HNSVO.js → dist-OKXOQ63L.js} +2 -1
  124. package/chunks/{dist-IMFFBMAT.js → dist-SU5LY5R4.js} +3 -1
  125. package/chunks/{earlyInputCapture-AERYYGXE.js → earlyInputCapture-HP5XAS7A.js} +10 -10
  126. package/chunks/{edit-WZTZDGCI.js → edit-MDICC6KH.js} +11 -11
  127. package/chunks/{en-GA2RMXY5.js → en-HF3KFWZG.js} +91 -5
  128. package/chunks/{enter-worktree-DEPXBJEL.js → enter-worktree-JWTRNEN4.js} +10 -10
  129. package/chunks/{enterPlanMode-JFGXZHHJ.js → enterPlanMode-GMRZMTBQ.js} +10 -10
  130. package/chunks/environment-2CJAFOFN.js +88 -0
  131. package/chunks/{errors-GMZL4QV7.js → errors-UZOVFD2U.js} +13 -13
  132. package/chunks/{exit-worktree-Y7XBK3VV.js → exit-worktree-QQGEG343.js} +10 -10
  133. package/chunks/{exitPlanMode-WQQER65F.js → exitPlanMode-YK2JVAAB.js} +10 -10
  134. package/chunks/{fast-path-W2U6XDNO.js → fast-path-6SD4JNQW.js} +12 -3
  135. package/chunks/{fast-path-settings-RCRXH2JG.js → fast-path-settings-RAQGCIT7.js} +3 -2
  136. package/chunks/{fr-GWNQG4XH.js → fr-RSCO6HHD.js} +89 -5
  137. package/chunks/{gemini-T4WX6SRW.js → gemini-TFQXPUZB.js} +436 -140
  138. package/chunks/{geminiContentGenerator-MKQACXEJ.js → geminiContentGenerator-LJ2BNXHX.js} +1 -1
  139. package/chunks/{glob-4KKSHZDF.js → glob-3IJ6JR5W.js} +10 -10
  140. package/chunks/{grep-GHRYXSLZ.js → grep-UFZ2RFQZ.js} +10 -10
  141. package/chunks/{handleAutoUpdate-HN7ZOSXY.js → handleAutoUpdate-TEZC5ZIR.js} +17 -13
  142. package/chunks/i18n-5QEODTJV.js +84 -0
  143. package/chunks/{initializer-SRN3IM42.js → initializer-222MPCLQ.js} +18 -16
  144. package/chunks/installationInfo-OZ62OHZ2.js +65 -0
  145. package/chunks/{ja-2HOTA47X.js → ja-RRFGG2DC.js} +89 -5
  146. package/chunks/{list-INTVXUPA.js → list-BN5VBIIE.js} +20 -18
  147. package/chunks/{loadedSettingsAdapter-QVHHQPGM.js → loadedSettingsAdapter-EHTUJRFB.js} +18 -16
  148. package/chunks/{loop-wakeup-OHWEMFP5.js → loop-wakeup-SR2KWT4B.js} +3 -3
  149. package/chunks/{ls-L654WOAT.js → ls-RUBQK5Q2.js} +2 -2
  150. package/chunks/{lsp-M35KE54Y.js → lsp-DUTVY2ZA.js} +3 -5
  151. package/chunks/{mcp-7V4T46RG.js → mcp-OPGHD26Y.js} +18 -16
  152. package/chunks/{monitor-464OY2UE.js → monitor-WXBDVYYV.js} +10 -10
  153. package/chunks/{nonInteractiveCli-LJEEODU2.js → nonInteractiveCli-CWIBGJ4U.js} +54 -49
  154. package/chunks/{notebook-edit-BLJYCNDO.js → notebook-edit-T7P7M73F.js} +11 -11
  155. package/chunks/{openaiContentGenerator-PFF6HYB2.js → openaiContentGenerator-OES264C3.js} +7 -7
  156. package/chunks/{pidfile-LIBUENIV.js → pidfile-LJ3NLS2P.js} +10 -10
  157. package/chunks/{pt-2THT3YHA.js → pt-D4CG46JM.js} +89 -5
  158. package/chunks/{qwenContentGenerator-SUUHZ6CR.js → qwenContentGenerator-QJ67LAJ5.js} +12 -12
  159. package/chunks/{read-file-L7TBQVX7.js → read-file-KZD7TLU7.js} +5 -5
  160. package/chunks/{read-mcp-resource-CNQHBSJM.js → read-mcp-resource-Q56IMYOH.js} +1 -1
  161. package/chunks/{record-artifact-PP3LKHTS.js → record-artifact-GZ5D5HZX.js} +4 -4
  162. package/chunks/{ripGrep-CKKX3GRB.js → ripGrep-KUJLX75U.js} +10 -10
  163. package/chunks/{ru-Z67YJHBG.js → ru-O2QUEL22.js} +89 -5
  164. package/chunks/{run-qwen-serve-5OKADVLE.js → run-qwen-serve-M52KVRGI.js} +664 -153
  165. package/chunks/{scheduler-ZRW3AIDU.js → scheduler-FFATTAYZ.js} +10 -10
  166. package/chunks/{send-message-OA6MV4EU.js → send-message-HAGK3VTV.js} +2 -2
  167. package/chunks/{serve-36WS32W6.js → serve-B5S32524.js} +21 -18
  168. package/chunks/{server-M372UENX.js → server-6HK5FJMR.js} +3301 -665
  169. package/chunks/{session-F5KSNP2N.js → session-3WB76VTQ.js} +55 -50
  170. package/chunks/{settings-WFLNS3WT.js → settings-W3IX7NHK.js} +27 -23
  171. package/chunks/{shell-DEDSWN56.js → shell-HZIU2ZG7.js} +10 -10
  172. package/chunks/{skill-A2TL2NPC.js → skill-34OUFB65.js} +5 -5
  173. package/chunks/{spawnChannel-RQHUWTCL.js → spawnChannel-FTZJ3I5O.js} +12 -12
  174. package/chunks/{src-XKZMNHXS.js → src-NELBIIGI.js} +48 -10
  175. package/chunks/standalone-update-LICI2VSK.js +79 -0
  176. package/chunks/{startInteractiveUI-REKSTOHU.js → startInteractiveUI-LZEV5YI2.js} +2203 -1764
  177. package/chunks/stdioHelpers-CA7MG65Z.js +14 -0
  178. package/chunks/{syntheticOutput-F7LSDCAR.js → syntheticOutput-XFFJVCGV.js} +2 -2
  179. package/chunks/{task-create-RPFZNB4B.js → task-create-VINUSVBF.js} +3 -3
  180. package/chunks/{task-list-TGGJX7EA.js → task-list-YZ2AWQEG.js} +1 -1
  181. package/chunks/{task-stop-W7NGTMYQ.js → task-stop-PEXPKUW3.js} +1 -1
  182. package/chunks/{task-update-5WUG5MVQ.js → task-update-BHIX57YH.js} +3 -3
  183. package/chunks/{team-create-TBQVWAG7.js → team-create-2JS2OYSF.js} +10 -10
  184. package/chunks/{team-delete-UHQQ2QQX.js → team-delete-2OHXSBVS.js} +1 -1
  185. package/chunks/{team-plan-approval-FZPGUSHJ.js → team-plan-approval-PHLGYUY7.js} +10 -10
  186. package/chunks/{theme-manager-DQZVNSOC.js → theme-manager-UYXLX37P.js} +10 -10
  187. package/chunks/{todoWrite-C4ZAMSR3.js → todoWrite-7Q4ELT4F.js} +1 -1
  188. package/chunks/{tool-search-FDQ3SLNJ.js → tool-search-2MWA3GWE.js} +6 -6
  189. package/chunks/total-session-admission-PRZK35X4.js +72 -0
  190. package/chunks/{trustedFolders-Z475EIHL.js → trustedFolders-IT4JBNPG.js} +12 -12
  191. package/chunks/{updateCheck-E2ZX7RL7.js → updateCheck-X3ZWDO3Z.js} +17 -13
  192. package/chunks/updateEventEmitter-NXWTRHXY.js +10 -0
  193. package/chunks/{validateNonInterActiveAuth-O4ECBHQL.js → validateNonInterActiveAuth-IAOL7CM3.js} +51 -45
  194. package/chunks/{version-CEDD6ZZR.js → version-25QF6A5H.js} +1 -1
  195. package/chunks/{web-fetch-UYGMOHW4.js → web-fetch-I7FOWHGA.js} +2 -2
  196. package/chunks/{workflow-CQQ4J4I6.js → workflow-LZZ5U3IT.js} +11 -11
  197. package/chunks/{workspace-providers-status-MVYXMYDG.js → workspace-providers-status-7DTOAWEL.js} +21 -18
  198. package/chunks/workspace-registry-BBIPESIZ.js +76 -0
  199. package/chunks/{workspace-service-OMBR6ZLG.js → workspace-service-SY3P3FUY.js} +25 -23
  200. package/chunks/{workspace-skills-status-IMMQ7CAY.js → workspace-skills-status-GTT2A4WN.js} +20 -18
  201. package/chunks/{write-file-VWGY7AJP.js → write-file-M6IHNW3V.js} +11 -11
  202. package/chunks/{zh-7XVVRTDH.js → zh-K2EH6BDD.js} +91 -5
  203. package/chunks/{zh-TW-DPH554OG.js → zh-TW-4GPXJ7ZY.js} +91 -5
  204. package/cli.js +13 -12
  205. package/locales/ca.js +115 -7
  206. package/locales/de.js +114 -7
  207. package/locales/en.js +118 -7
  208. package/locales/fr.js +115 -7
  209. package/locales/ja.js +116 -6
  210. package/locales/pt.js +114 -7
  211. package/locales/ru.js +114 -7
  212. package/locales/zh-TW.js +114 -6
  213. package/locales/zh.js +114 -6
  214. package/package.json +3 -6
  215. package/web-shell/assets/{arc-C7hjaa7g.js → arc-G3HZaqaL.js} +1 -1
  216. package/web-shell/assets/{architectureDiagram-3BPJPVTR-pm9KtMZY.js → architectureDiagram-3BPJPVTR-DhBrwwch.js} +1 -1
  217. package/web-shell/assets/{blockDiagram-GPEHLZMM-CApElKsU.js → blockDiagram-GPEHLZMM-16JbbF0N.js} +1 -1
  218. package/web-shell/assets/{c4Diagram-AAUBKEIU-BeDe0-9N.js → c4Diagram-AAUBKEIU-DQ3-pKgc.js} +1 -1
  219. package/web-shell/assets/channel-CX7ZHuuL.js +1 -0
  220. package/web-shell/assets/{chunk-2J33WTMH-DJEkmLdl.js → chunk-2J33WTMH-ERCn30sp.js} +1 -1
  221. package/web-shell/assets/{chunk-4BX2VUAB-DXX8gdpe.js → chunk-4BX2VUAB-DTysfbFi.js} +1 -1
  222. package/web-shell/assets/{chunk-55IACEB6-DORmk1kQ.js → chunk-55IACEB6-BFGrbTBi.js} +1 -1
  223. package/web-shell/assets/{chunk-727SXJPM-DWmO4mCZ.js → chunk-727SXJPM-DCX4Oar-.js} +1 -1
  224. package/web-shell/assets/{chunk-AQP2D5EJ-C7LRaYw2.js → chunk-AQP2D5EJ-CgT6sBr3.js} +1 -1
  225. package/web-shell/assets/{chunk-FMBD7UC4-CCdXgyC5.js → chunk-FMBD7UC4-C1Uuk1KV.js} +1 -1
  226. package/web-shell/assets/{chunk-ND2GUHAM-SyL_xvWc.js → chunk-ND2GUHAM-DajgESiu.js} +1 -1
  227. package/web-shell/assets/{chunk-QZHKN3VN-BzOuCKxa.js → chunk-QZHKN3VN-DLJSvy9X.js} +1 -1
  228. package/web-shell/assets/classDiagram-4FO5ZUOK-B-oT3UI8.js +1 -0
  229. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-B-oT3UI8.js +1 -0
  230. package/web-shell/assets/{cose-bilkent-S5V4N54A-BMPWyDKc.js → cose-bilkent-S5V4N54A-CH3w3qs7.js} +1 -1
  231. package/web-shell/assets/{dagre-BM42HDAG-B_GnPWik.js → dagre-BM42HDAG-BloXan9B.js} +1 -1
  232. package/web-shell/assets/{diagram-2AECGRRQ-fJqMc3S_.js → diagram-2AECGRRQ-BbZiGcmK.js} +1 -1
  233. package/web-shell/assets/{diagram-5GNKFQAL-ximV3O9D.js → diagram-5GNKFQAL-GQlYvLJv.js} +1 -1
  234. package/web-shell/assets/{diagram-KO2AKTUF-2vVyjzqw.js → diagram-KO2AKTUF-CtIBvVq3.js} +1 -1
  235. package/web-shell/assets/{diagram-LMA3HP47-Dckcx1sT.js → diagram-LMA3HP47-D5TBo9a2.js} +1 -1
  236. package/web-shell/assets/{diagram-OG6HWLK6-BAVv47ga.js → diagram-OG6HWLK6-N0gQhw6R.js} +1 -1
  237. package/web-shell/assets/{erDiagram-TEJ5UH35-Bn3nf5J0.js → erDiagram-TEJ5UH35-zab9vDj3.js} +1 -1
  238. package/web-shell/assets/{flowDiagram-I6XJVG4X-BIbGF9JT.js → flowDiagram-I6XJVG4X-BnnjtWJa.js} +1 -1
  239. package/web-shell/assets/{ganttDiagram-6RSMTGT7-CDbhb3AH.js → ganttDiagram-6RSMTGT7-2T0SQU4f.js} +1 -1
  240. package/web-shell/assets/{gitGraphDiagram-PVQCEYII-CbKG1a_w.js → gitGraphDiagram-PVQCEYII-DAD6_que.js} +1 -1
  241. package/web-shell/assets/index-XLkWfLja.css +5 -0
  242. package/web-shell/assets/index-srlTR1Jg.js +786 -0
  243. package/web-shell/assets/{infoDiagram-5YYISTIA-DUFxTy8m.js → infoDiagram-5YYISTIA-C-j8d0xO.js} +1 -1
  244. package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-D69hrBG8.js → ishikawaDiagram-YF4QCWOH-D97bAKoT.js} +1 -1
  245. package/web-shell/assets/{journeyDiagram-JHISSGLW-DsvEw51Y.js → journeyDiagram-JHISSGLW-BJAHfJmm.js} +1 -1
  246. package/web-shell/assets/{kanban-definition-UN3LZRKU-lFFjZEYd.js → kanban-definition-UN3LZRKU-CZgR5aac.js} +1 -1
  247. package/web-shell/assets/{linear-Bo5bWzvo.js → linear-DOd4kYhA.js} +1 -1
  248. package/web-shell/assets/{mermaid.core-C_HKMJx5.js → mermaid.core-BBaplnXA.js} +5 -5
  249. package/web-shell/assets/{mindmap-definition-RKZ34NQL-DlV-NUTs.js → mindmap-definition-RKZ34NQL-IhBF_BR5.js} +1 -1
  250. package/web-shell/assets/{pieDiagram-4H26LBE5-B2e6FH2a.js → pieDiagram-4H26LBE5-B4R4sYyH.js} +1 -1
  251. package/web-shell/assets/{quadrantDiagram-W4KKPZXB-D75kSk-b.js → quadrantDiagram-W4KKPZXB-DKd1SJ61.js} +1 -1
  252. package/web-shell/assets/{requirementDiagram-4Y6WPE33-CQ6xakhM.js → requirementDiagram-4Y6WPE33-Cw96V0Xb.js} +1 -1
  253. package/web-shell/assets/{sankeyDiagram-5OEKKPKP-J_XTKrcY.js → sankeyDiagram-5OEKKPKP-C6ne_yWi.js} +1 -1
  254. package/web-shell/assets/{sequenceDiagram-3UESZ5HK-D7jTcBtf.js → sequenceDiagram-3UESZ5HK-CCyp4PDp.js} +1 -1
  255. package/web-shell/assets/{stateDiagram-AJRCARHV-CrlM_OT3.js → stateDiagram-AJRCARHV-DHvIqTmx.js} +1 -1
  256. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-DvFhmpwA.js +1 -0
  257. package/web-shell/assets/{timeline-definition-PNZ67QCA-5oZi76Ef.js → timeline-definition-PNZ67QCA-amb3n9zH.js} +1 -1
  258. package/web-shell/assets/{vennDiagram-CIIHVFJN-DO5YptPK.js → vennDiagram-CIIHVFJN-Abf2qRFJ.js} +1 -1
  259. package/web-shell/assets/{wardley-L42UT6IY-4t1eJDvM.js → wardley-L42UT6IY-BBlOp1av.js} +1 -1
  260. package/web-shell/assets/{wardleyDiagram-YWT4CUSO-DKZc661S.js → wardleyDiagram-YWT4CUSO-gE_sZ0Ak.js} +1 -1
  261. package/web-shell/assets/{xychartDiagram-2RQKCTM6-su9hp08G.js → xychartDiagram-2RQKCTM6-BwOni53i.js} +1 -1
  262. package/web-shell/index.html +16 -4
  263. package/chunks/chunk-6SHO7WFF.js +0 -79
  264. package/chunks/chunk-IFEWJXFS.js +0 -37
  265. package/node_modules/@qwen-code/audio-capture/dist/index.d.ts +0 -35
  266. package/node_modules/@qwen-code/audio-capture/dist/index.js +0 -48
  267. package/node_modules/@qwen-code/audio-capture/dist/index.js.map +0 -1
  268. package/node_modules/@qwen-code/audio-capture/dist/platform.d.ts +0 -7
  269. package/node_modules/@qwen-code/audio-capture/dist/platform.js +0 -18
  270. package/node_modules/@qwen-code/audio-capture/dist/platform.js.map +0 -1
  271. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/LICENSE +0 -21
  272. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/README.md +0 -58
  273. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/SECURITY.md +0 -5
  274. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/bin.js +0 -84
  275. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/build-test.js +0 -19
  276. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/index.js +0 -6
  277. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/node-gyp-build.js +0 -207
  278. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/optional.js +0 -7
  279. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/package.json +0 -43
  280. package/node_modules/@qwen-code/audio-capture/package.json +0 -30
  281. package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-arm64/@qwen-code+audio-capture.node +0 -0
  282. package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-x64/@qwen-code+audio-capture.node +0 -0
  283. package/node_modules/@qwen-code/audio-capture/prebuilds/linux-arm64/@qwen-code+audio-capture.node +0 -0
  284. package/node_modules/@qwen-code/audio-capture/prebuilds/linux-x64/@qwen-code+audio-capture.node +0 -0
  285. package/node_modules/@qwen-code/audio-capture/prebuilds/win32-x64/@qwen-code+audio-capture.node +0 -0
  286. package/web-shell/assets/channel-BNf1dW-j.js +0 -1
  287. package/web-shell/assets/classDiagram-4FO5ZUOK-uOIGlzgi.js +0 -1
  288. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-uOIGlzgi.js +0 -1
  289. package/web-shell/assets/index-CnE2ur2L.js +0 -779
  290. package/web-shell/assets/index-hSKXrlSE.css +0 -5
  291. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-Bs4TK_84.js +0 -1
  292. /package/chunks/{chunk-Y6Z2O3WR.js → chunk-JM7BYJO5.js} +0 -0
@@ -22,7 +22,7 @@ import {
22
22
  SessionShellDisabledError,
23
23
  WorkspaceMismatchError,
24
24
  isNotCurrentlyGeneratingCancelError
25
- } from "./chunk-B34OV7K2.js";
25
+ } from "./chunk-GGQQA4JW.js";
26
26
  import {
27
27
  LOAD_REPLAY_BULK_MODE,
28
28
  LOAD_REPLAY_META_KEY,
@@ -36,16 +36,15 @@ import {
36
36
  import {
37
37
  DEFAULT_RING_SIZE,
38
38
  EVENT_SCHEMA_VERSION,
39
- EventBus
40
- } from "./chunk-G7ZG7CA6.js";
39
+ EventBus,
40
+ serializedBridgeEventByteLength
41
+ } from "./chunk-3UP777ZS.js";
41
42
  import {
42
- ClientSideConnection,
43
- PROTOCOL_VERSION,
44
- RequestError
45
- } from "./chunk-VRDMOSQS.js";
43
+ normalizeCompactedReplayMaxBytes
44
+ } from "./chunk-GLE2YUPU.js";
46
45
  import {
47
46
  defaultSpawnChannelFactory
48
- } from "./chunk-AJBEAUIS.js";
47
+ } from "./chunk-MG2TNCRX.js";
49
48
  import {
50
49
  BridgeChannelClosedError,
51
50
  BridgeTimeoutError,
@@ -54,15 +53,28 @@ import {
54
53
  STATUS_SCHEMA_VERSION,
55
54
  createIdleWorkspaceExtensionsStatus,
56
55
  createIdleWorkspaceHooksStatus
57
- } from "./chunk-JF7DFPQ5.js";
56
+ } from "./chunk-I3MBKL6Z.js";
57
+ import {
58
+ ClientSideConnection,
59
+ PROTOCOL_VERSION,
60
+ RequestError
61
+ } from "./chunk-VRDMOSQS.js";
58
62
  import {
63
+ SESSION_ARTIFACT_PERSISTENCE_VERSION,
59
64
  ShellExecutionService,
60
- TrustGateError
61
- } from "./chunk-YEGT55NW.js";
65
+ TrustGateError,
66
+ WORKSPACE_CONTENT_MTIME_MS_METADATA_KEY,
67
+ WORKSPACE_CONTENT_SHA256_METADATA_KEY,
68
+ isPrototypeMetadataKey,
69
+ isReservedWorkspaceMetadataKey,
70
+ metadataBudgetBytes,
71
+ normalizeSnapshotPayload,
72
+ stableSessionArtifactId
73
+ } from "./chunk-ZDAZSR2O.js";
62
74
  import {
63
75
  DAEMON_TRACEPARENT_META_KEY,
64
76
  DAEMON_TRACESTATE_META_KEY
65
- } from "./chunk-GMOWQOQQ.js";
77
+ } from "./chunk-5ZVIYNLN.js";
66
78
  import {
67
79
  init_esbuild_shims
68
80
  } from "./chunk-A4BMJM77.js";
@@ -79,6 +91,7 @@ import * as path3 from "node:path";
79
91
  init_esbuild_shims();
80
92
  var TURN_BOUNDARY_TYPES = /* @__PURE__ */ new Set(["turn_complete", "turn_error"]);
81
93
  var TRANSIENT_TYPES = /* @__PURE__ */ new Set([
94
+ "history_truncated",
82
95
  "slow_client_warning",
83
96
  "client_evicted",
84
97
  "replay_complete",
@@ -88,17 +101,28 @@ var LATEST_WINS_UPDATES = /* @__PURE__ */ new Set([
88
101
  "available_commands_update",
89
102
  "current_mode_update"
90
103
  ]);
104
+ var REPLAY_SEGMENT_COMPACT_THRESHOLD = 64;
91
105
  var TurnBoundaryCompactionEngine = class {
92
106
  static {
93
107
  __name(this, "TurnBoundaryCompactionEngine");
94
108
  }
95
- compactedTurns = [];
109
+ maxReplayBytes;
110
+ onReplayWindowEviction;
111
+ replaySegments = [];
112
+ replaySegmentStart = 0;
113
+ replayBytes = 0;
96
114
  liveJournal = [];
97
115
  lastEventId = 0;
98
116
  closed = false;
117
+ truncatedEvents = 0;
118
+ truncatedTurns = 0;
99
119
  slots = [];
100
120
  toolSlotIndex = /* @__PURE__ */ new Map();
101
121
  textSlotIndex = /* @__PURE__ */ new Map();
122
+ constructor(opts = {}) {
123
+ this.maxReplayBytes = normalizeCompactedReplayMaxBytes(opts.maxReplayBytes);
124
+ this.onReplayWindowEviction = opts.onReplayWindowEviction;
125
+ }
102
126
  ingest(event) {
103
127
  if (this.closed) return;
104
128
  if (event.id !== void 0) {
@@ -117,16 +141,39 @@ var TurnBoundaryCompactionEngine = class {
117
141
  this.slots.push({ kind: "misc", event });
118
142
  }
119
143
  snapshot() {
144
+ const compactedTurns = this.flattenReplaySegments();
145
+ if (this.truncatedEvents > 0) {
146
+ compactedTurns.unshift(
147
+ this.makeHistoryTruncatedEvent(compactedTurns.length)
148
+ );
149
+ }
120
150
  return {
121
- compactedTurns: this.compactedTurns.slice(),
151
+ compactedTurns,
122
152
  liveJournal: this.liveJournal.slice(),
123
153
  lastEventId: this.lastEventId
124
154
  };
125
155
  }
126
156
  seed(snapshot) {
127
157
  if (this.closed) return;
128
- this.compactedTurns = snapshot.compactedTurns.slice();
158
+ this.resetReplayWindow();
129
159
  this.lastEventId = snapshot.lastEventId;
160
+ for (const event of snapshot.compactedTurns) {
161
+ if (TRANSIENT_TYPES.has(event.type)) continue;
162
+ this.addReplaySegment([event], 0);
163
+ }
164
+ this.liveJournal = [];
165
+ this.slots = [];
166
+ this.toolSlotIndex.clear();
167
+ this.textSlotIndex.clear();
168
+ }
169
+ seedReplayEvents(events) {
170
+ if (this.closed) return;
171
+ this.resetReplayWindow();
172
+ for (const event of events) {
173
+ this.recordLastEventId(event);
174
+ if (TRANSIENT_TYPES.has(event.type)) continue;
175
+ this.addReplaySegment([event], 0);
176
+ }
130
177
  this.liveJournal = [];
131
178
  this.slots = [];
132
179
  this.toolSlotIndex.clear();
@@ -135,7 +182,7 @@ var TurnBoundaryCompactionEngine = class {
135
182
  close() {
136
183
  if (this.closed) return;
137
184
  this.closed = true;
138
- this.compactedTurns = [];
185
+ this.resetReplayWindow();
139
186
  this.liveJournal = [];
140
187
  this.slots = [];
141
188
  this.toolSlotIndex.clear();
@@ -272,12 +319,94 @@ var TurnBoundaryCompactionEngine = class {
272
319
  }
273
320
  }
274
321
  compacted.push(boundaryEvent);
275
- this.compactedTurns.push(...compacted);
322
+ this.addReplaySegment(compacted, 1);
276
323
  this.liveJournal = [];
277
324
  this.slots = [];
278
325
  this.toolSlotIndex.clear();
279
326
  this.textSlotIndex.clear();
280
327
  }
328
+ recordLastEventId(event) {
329
+ if (event.id !== void 0) {
330
+ this.lastEventId = event.id;
331
+ }
332
+ }
333
+ addReplaySegment(events, turnCount) {
334
+ if (events.length === 0) return;
335
+ const bytes = events.reduce(
336
+ (sum, event) => sum + serializedBridgeEventByteLength(event),
337
+ 0
338
+ );
339
+ this.replaySegments.push({ events: events.slice(), bytes, turnCount });
340
+ this.replayBytes += bytes;
341
+ this.enforceReplayWindow();
342
+ }
343
+ enforceReplayWindow() {
344
+ let droppedSegmentCount = 0;
345
+ let droppedBytes = 0;
346
+ let droppedEvents = 0;
347
+ let droppedTurns = 0;
348
+ while (this.replayBytes > this.maxReplayBytes && this.activeReplaySegmentCount() > 1) {
349
+ const dropped = this.replaySegments[this.replaySegmentStart];
350
+ this.replaySegmentStart += 1;
351
+ droppedSegmentCount += 1;
352
+ droppedBytes += dropped.bytes;
353
+ droppedEvents += dropped.events.length;
354
+ droppedTurns += dropped.turnCount;
355
+ this.replayBytes -= dropped.bytes;
356
+ this.truncatedEvents += dropped.events.length;
357
+ this.truncatedTurns += dropped.turnCount;
358
+ }
359
+ if (droppedSegmentCount > 0) {
360
+ this.compactReplaySegmentQueueIfNeeded();
361
+ this.notifyReplayWindowEviction({
362
+ droppedBytes,
363
+ droppedEvents,
364
+ droppedSegments: droppedSegmentCount,
365
+ droppedTurns,
366
+ maxBytes: this.maxReplayBytes,
367
+ retainedBytes: this.replayBytes,
368
+ retainedEvents: this.flattenReplaySegments().length
369
+ });
370
+ }
371
+ }
372
+ flattenReplaySegments() {
373
+ return this.replaySegments.slice(this.replaySegmentStart).flatMap((segment) => segment.events);
374
+ }
375
+ activeReplaySegmentCount() {
376
+ return this.replaySegments.length - this.replaySegmentStart;
377
+ }
378
+ compactReplaySegmentQueueIfNeeded() {
379
+ if (this.replaySegmentStart < REPLAY_SEGMENT_COMPACT_THRESHOLD) return;
380
+ this.replaySegments.splice(0, this.replaySegmentStart);
381
+ this.replaySegmentStart = 0;
382
+ }
383
+ notifyReplayWindowEviction(eviction) {
384
+ try {
385
+ this.onReplayWindowEviction?.(eviction);
386
+ } catch {
387
+ }
388
+ }
389
+ makeHistoryTruncatedEvent(retainedEvents) {
390
+ return {
391
+ v: EVENT_SCHEMA_VERSION,
392
+ type: "history_truncated",
393
+ data: {
394
+ reason: "replay_window_exceeded",
395
+ truncatedEvents: this.truncatedEvents,
396
+ retainedEvents,
397
+ maxBytes: this.maxReplayBytes,
398
+ ...this.truncatedTurns > 0 ? { truncatedTurns: this.truncatedTurns } : {},
399
+ fullTranscriptAvailable: false
400
+ }
401
+ };
402
+ }
403
+ resetReplayWindow() {
404
+ this.replaySegments = [];
405
+ this.replaySegmentStart = 0;
406
+ this.replayBytes = 0;
407
+ this.truncatedEvents = 0;
408
+ this.truncatedTurns = 0;
409
+ }
281
410
  };
282
411
  function makeMergedSessionUpdateEvent(sessionUpdate, text, eventId, meta, envelopeMeta) {
283
412
  return {
@@ -362,6 +491,11 @@ import * as path from "node:path";
362
491
  init_esbuild_shims();
363
492
  var MID_TURN_MESSAGE_INJECTED_EVENT = "mid_turn_message_injected";
364
493
 
494
+ // packages/acp-bridge/src/bridgeOptions.ts
495
+ init_esbuild_shims();
496
+ var MAX_SUB_SESSION_PROMPT_CHARS = 1e5;
497
+ var MAX_SUB_SESSION_NAME_CHARS = 200;
498
+
365
499
  // packages/acp-bridge/src/permissionMediator.ts
366
500
  init_esbuild_shims();
367
501
 
@@ -1059,7 +1193,8 @@ function artifactPayloadFields(artifact) {
1059
1193
  url: artifact["url"],
1060
1194
  mimeType: artifact["mimeType"],
1061
1195
  sizeBytes: artifact["sizeBytes"],
1062
- metadata: artifact["metadata"]
1196
+ metadata: artifact["metadata"],
1197
+ retention: artifact["retention"]
1063
1198
  };
1064
1199
  }
1065
1200
  __name(artifactPayloadFields, "artifactPayloadFields");
@@ -1202,7 +1337,7 @@ function sliceLineRange(content, startLine, endLine) {
1202
1337
  }
1203
1338
  __name(sliceLineRange, "sliceLineRange");
1204
1339
  var BridgeClient = class {
1205
- constructor(resolveEntry, resolvePendingRestoreEvents, mediator, permissionTimeoutMs, maxPendingPerSession, fileSystem, onModelPromoted, onModePromoted, clientMcpSender, ownsSession = () => true, onTokenUsage) {
1340
+ constructor(resolveEntry, resolvePendingRestoreEvents, mediator, permissionTimeoutMs, maxPendingPerSession, fileSystem, onModelPromoted, onModePromoted, clientMcpSender, ownsSession = () => true, onTokenUsage, onCreateSubSession) {
1206
1341
  this.resolveEntry = resolveEntry;
1207
1342
  this.resolvePendingRestoreEvents = resolvePendingRestoreEvents;
1208
1343
  this.mediator = mediator;
@@ -1214,6 +1349,7 @@ var BridgeClient = class {
1214
1349
  this.clientMcpSender = clientMcpSender;
1215
1350
  this.ownsSession = ownsSession;
1216
1351
  this.onTokenUsage = onTokenUsage;
1352
+ this.onCreateSubSession = onCreateSubSession;
1217
1353
  }
1218
1354
  static {
1219
1355
  __name(this, "BridgeClient");
@@ -1337,7 +1473,7 @@ var BridgeClient = class {
1337
1473
  trustedPublisher: isTrustedArtifactToolUpdate(params, updateMeta)
1338
1474
  };
1339
1475
  }
1340
- async seedSessionUpdates(entry, updates) {
1476
+ async seedSessionUpdates(entry, updates, options = {}) {
1341
1477
  const frames = [];
1342
1478
  const artifactBatches = [];
1343
1479
  for (const update of updates) {
@@ -1346,7 +1482,7 @@ var BridgeClient = class {
1346
1482
  entry
1347
1483
  );
1348
1484
  frames.push(...prepared.frames);
1349
- if (prepared.artifacts.length > 0) {
1485
+ if (options.ingestArtifacts !== false && prepared.artifacts.length > 0) {
1350
1486
  artifactBatches.push({
1351
1487
  artifacts: prepared.artifacts,
1352
1488
  trustedPublisher: prepared.trustedPublisher
@@ -1435,9 +1571,12 @@ var BridgeClient = class {
1435
1571
  inFlightRestoreIds = /* @__PURE__ */ new Set();
1436
1572
  /**
1437
1573
  * Handle child->bridge ACP `extMethod` requests (calls that expect a
1438
- * response, unlike `extNotification`). The only method served today is
1439
- * `craft/drainMidTurnQueue`: the ACP child calls it between tool batches to
1440
- * pull any messages the browser queued mid-turn. We splice the per-session
1574
+ * response, unlike `extNotification`). Served methods:
1575
+ * `qwen/control/client_mcp/message` (reverse tool channel),
1576
+ * `qwen/control/create-sub-session` (the `create_sub_session` tool daemon
1577
+ * spawns a sub-session and, for `'first-turn'`, returns its first-turn
1578
+ * result), and `craft/drainMidTurnQueue`: the ACP child calls the last one
1579
+ * between tool batches to pull any messages the browser queued mid-turn. We splice the per-session
1441
1580
  * queue, return them to the child as the response, and — when non-empty —
1442
1581
  * publish a `mid_turn_message_injected` SSE frame so the browser can move
1443
1582
  * those messages out of its pending queue (a dedupe signal, not a transcript
@@ -1450,6 +1589,9 @@ var BridgeClient = class {
1450
1589
  if (method === SERVE_CONTROL_EXT_METHODS.clientMcpMessage) {
1451
1590
  return this.handleClientMcpMessage(params);
1452
1591
  }
1592
+ if (method === SERVE_CONTROL_EXT_METHODS.createSubSession) {
1593
+ return this.handleCreateSubSession(params);
1594
+ }
1453
1595
  if (method !== MID_TURN_QUEUE_DRAIN_METHOD) {
1454
1596
  throw RequestError.methodNotFound(method);
1455
1597
  }
@@ -1525,6 +1667,67 @@ var BridgeClient = class {
1525
1667
  const response = await send(payload);
1526
1668
  return { payload: response };
1527
1669
  }
1670
+ /**
1671
+ * Handle the `create_sub_session` tool's request: validate, then forward to
1672
+ * the daemon-host `onCreateSubSession` callback (which spawns a fresh
1673
+ * top-level sub-session and, for `'first-turn'`, waits for its first turn and
1674
+ * returns the result). No host wired → `methodNotFound`, which the tool
1675
+ * surfaces as "daemon-only".
1676
+ */
1677
+ async handleCreateSubSession(params) {
1678
+ if (!this.onCreateSubSession) {
1679
+ throw RequestError.methodNotFound(
1680
+ SERVE_CONTROL_EXT_METHODS.createSubSession
1681
+ );
1682
+ }
1683
+ const prompt = params["prompt"];
1684
+ if (typeof prompt !== "string" || prompt.length === 0) {
1685
+ throw RequestError.invalidParams(
1686
+ void 0,
1687
+ "`prompt` must be a non-empty string"
1688
+ );
1689
+ }
1690
+ if (prompt.length > MAX_SUB_SESSION_PROMPT_CHARS) {
1691
+ throw RequestError.invalidParams(
1692
+ void 0,
1693
+ `\`prompt\` exceeds the ${MAX_SUB_SESSION_PROMPT_CHARS}-character limit`
1694
+ );
1695
+ }
1696
+ const completion = params["completion"];
1697
+ if (completion !== "sent" && completion !== "first-turn") {
1698
+ throw RequestError.invalidParams(
1699
+ void 0,
1700
+ "`completion` must be 'sent' or 'first-turn'"
1701
+ );
1702
+ }
1703
+ const name = params["name"];
1704
+ if (typeof name === "string" && name.length > MAX_SUB_SESSION_NAME_CHARS) {
1705
+ throw RequestError.invalidParams(
1706
+ void 0,
1707
+ `\`name\` exceeds the ${MAX_SUB_SESSION_NAME_CHARS}-character limit`
1708
+ );
1709
+ }
1710
+ const callerSessionId = params["callerSessionId"];
1711
+ if (typeof callerSessionId !== "string" || callerSessionId.length === 0 || !this.ownsSession(callerSessionId)) {
1712
+ throw RequestError.invalidParams(
1713
+ void 0,
1714
+ "`callerSessionId` is required and must name a session owned by this connection"
1715
+ );
1716
+ }
1717
+ const model = params["model"];
1718
+ const result = await this.onCreateSubSession({
1719
+ prompt,
1720
+ completion,
1721
+ ...typeof model === "string" && model.length > 0 && model.length <= 128 ? { model } : {},
1722
+ ...typeof name === "string" && name.length > 0 ? { name } : {},
1723
+ callerSessionId
1724
+ });
1725
+ return {
1726
+ sessionId: result.sessionId,
1727
+ ...result.result !== void 0 ? { result: result.result } : {},
1728
+ ...result.stopReason !== void 0 ? { stopReason: result.stopReason } : {}
1729
+ };
1730
+ }
1528
1731
  /**
1529
1732
  * Handle child->bridge ACP `extNotification` calls. Recognized methods are
1530
1733
  * `qwen/notify/session/model-update`,
@@ -1534,8 +1737,8 @@ var BridgeClient = class {
1534
1737
  * `qwen/notify/session/artifact-event` (hook artifacts),
1535
1738
  * `qwen/notify/session/terminal-sequence`, and
1536
1739
  * `qwen/notify/session/mcp-budget-event` — each translated into a
1537
- * session-scoped SSE frame. Unknown methods are dropped silently
1538
- * for forward-compat.
1740
+ * session-scoped SSE frame. Unknown methods are dropped silently for
1741
+ * forward-compat.
1539
1742
  */
1540
1743
  async extNotification(method, params) {
1541
1744
  if (method === "qwen/notify/session/model-update") {
@@ -2118,7 +2321,7 @@ __name(extractA2uiToolUpdate, "extractA2uiToolUpdate");
2118
2321
  // packages/acp-bridge/src/sessionArtifacts.ts
2119
2322
  init_esbuild_shims();
2120
2323
  import { createHash } from "node:crypto";
2121
- import { promises as fs2 } from "node:fs";
2324
+ import { constants as fsConstants, promises as fs2 } from "node:fs";
2122
2325
  import path2 from "node:path";
2123
2326
  var SOURCE_RESERVATIONS = {
2124
2327
  tool: 100,
@@ -2127,6 +2330,18 @@ var SOURCE_RESERVATIONS = {
2127
2330
  };
2128
2331
  var WORKSPACE_STATUS_REFRESH_TTL_MS = 5e3;
2129
2332
  var WORKSPACE_STATUS_REFRESH_BATCH_SIZE = 20;
2333
+ var SNAPSHOT_AFTER_DURABLE_EVENTS = 50;
2334
+ var MAX_SNAPSHOT_BACKOFF_MULTIPLIER = 4;
2335
+ var MAX_TOMBSTONED_IDS = 500;
2336
+ var MAX_STICKY_EPHEMERAL_IDS = 500;
2337
+ var MAX_WORKSPACE_HASH_BYTES = 100 * 1024 * 1024;
2338
+ var SECRET_TOKEN_VALUE_PATTERN = /(?:^|\s)(?:bearer\s+\S{8,}|sk-[A-Za-z0-9_-]{12,}|(?:gh[pousr]|github_pat)_[A-Za-z0-9_/-]{12,}|[a-f0-9]{40,}|[A-Za-z0-9+/]{48,}={0,2})(?:$|\s)/i;
2339
+ var RESTORE_FAILED_WARNING_PREFIX = "artifact snapshot restore failed";
2340
+ var RESTORE_PARTIAL_FAILED_WARNING_PREFIX = "artifact snapshot restore partially failed";
2341
+ function isArtifactRestoreFailureWarning(warning) {
2342
+ return warning.startsWith(RESTORE_FAILED_WARNING_PREFIX) || warning.startsWith(RESTORE_PARTIAL_FAILED_WARNING_PREFIX);
2343
+ }
2344
+ __name(isArtifactRestoreFailureWarning, "isArtifactRestoreFailureWarning");
2130
2345
  var SessionArtifactValidationError = class extends Error {
2131
2346
  constructor(message, field) {
2132
2347
  super(message);
@@ -2138,6 +2353,20 @@ var SessionArtifactValidationError = class extends Error {
2138
2353
  }
2139
2354
  code = "VALIDATION_FAILED";
2140
2355
  };
2356
+ var SessionArtifactAuthorizationError = class extends Error {
2357
+ constructor(sessionId, artifactId, ownerClientId, requesterClientId) {
2358
+ super(`artifact ${artifactId} is owned by a different client`);
2359
+ this.sessionId = sessionId;
2360
+ this.artifactId = artifactId;
2361
+ this.ownerClientId = ownerClientId;
2362
+ this.requesterClientId = requesterClientId;
2363
+ this.name = "SessionArtifactAuthorizationError";
2364
+ }
2365
+ static {
2366
+ __name(this, "SessionArtifactAuthorizationError");
2367
+ }
2368
+ code = "SESSION_ARTIFACT_FORBIDDEN";
2369
+ };
2141
2370
  var SessionArtifactStore = class {
2142
2371
  static {
2143
2372
  __name(this, "SessionArtifactStore");
@@ -2145,15 +2374,26 @@ var SessionArtifactStore = class {
2145
2374
  sessionId;
2146
2375
  workspaceCwd;
2147
2376
  maxArtifacts;
2377
+ persistence;
2148
2378
  artifacts = /* @__PURE__ */ new Map();
2149
2379
  receivedSeq = 0;
2150
2380
  insertSeq = 0;
2381
+ persistenceSeq = 0;
2382
+ durableEventsSinceSnapshot = 0;
2383
+ consecutiveSnapshotFailures = 0;
2151
2384
  realWorkspaceCwdPromise;
2152
2385
  operationQueue = Promise.resolve();
2386
+ tombstonedIds = /* @__PURE__ */ new Set();
2387
+ tombstonedClientIds = /* @__PURE__ */ new Map();
2388
+ stickyEphemeralIds = /* @__PURE__ */ new Set();
2389
+ markerArtifacts = /* @__PURE__ */ new Map();
2390
+ lastRestoreWarnings = [];
2391
+ lastRestoreWarningDetails = [];
2153
2392
  constructor(options) {
2154
2393
  this.sessionId = options.sessionId;
2155
2394
  this.workspaceCwd = options.workspaceCwd;
2156
2395
  this.maxArtifacts = options.maxArtifacts ?? 200;
2396
+ this.persistence = options.persistence;
2157
2397
  }
2158
2398
  inputBatchLimit() {
2159
2399
  return this.maxArtifacts * 2;
@@ -2166,13 +2406,30 @@ var SessionArtifactStore = class {
2166
2406
  sessionId: this.sessionId,
2167
2407
  artifacts: Array.from(this.artifacts.values()).sort((a, b) => a.insertSeq - b.insertSeq).map(toPublicArtifact),
2168
2408
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2169
- limits: { maxArtifacts: this.maxArtifacts }
2409
+ limits: { maxArtifacts: this.maxArtifacts },
2410
+ ...this.lastRestoreWarnings.length > 0 ? { warnings: [...this.lastRestoreWarnings] } : {},
2411
+ ...this.lastRestoreWarningDetails.length > 0 ? { warningDetails: [...this.lastRestoreWarningDetails] } : {}
2170
2412
  };
2171
2413
  });
2172
2414
  }
2415
+ async get(artifactId) {
2416
+ return this.enqueue(async () => {
2417
+ const artifact = this.artifacts.get(artifactId);
2418
+ if (!artifact) return void 0;
2419
+ if (artifact.workspacePath && shouldRefreshWorkspaceStatus(artifact, Date.now())) {
2420
+ await this.refreshWorkspaceStatus(artifact, { onError: "missing" });
2421
+ }
2422
+ return toPublicArtifact(artifact);
2423
+ });
2424
+ }
2173
2425
  async upsertMany(inputs, options = {}) {
2174
2426
  return this.enqueue(async () => {
2427
+ const validationStrict = options.validationStrict ?? options.strict;
2428
+ const persistenceStrict = options.persistenceStrict ?? options.strict;
2429
+ const before = this.cloneState();
2175
2430
  const normalizedResults = [];
2431
+ const warnings = [];
2432
+ const warningDetails = [];
2176
2433
  for (const input of inputs) {
2177
2434
  try {
2178
2435
  normalizedResults.push(
@@ -2183,7 +2440,7 @@ var SessionArtifactStore = class {
2183
2440
  )
2184
2441
  );
2185
2442
  } catch (error) {
2186
- if (options.strict) {
2443
+ if (validationStrict) {
2187
2444
  throw error;
2188
2445
  }
2189
2446
  const message = error instanceof Error ? error.message : String(error);
@@ -2195,39 +2452,140 @@ var SessionArtifactStore = class {
2195
2452
  }
2196
2453
  }
2197
2454
  const changes = [];
2198
- for (const artifact of coalesceByIdentity(normalizedResults)) {
2199
- const existing = this.artifacts.get(artifact.id) ?? this.findPublishedUpgradeTarget(artifact) ?? this.findPublishedWorkspaceTarget(artifact);
2200
- if (!existing) {
2201
- const stored = {
2202
- ...artifact,
2203
- insertSeq: ++this.insertSeq
2204
- };
2205
- this.artifacts.set(stored.id, stored);
2206
- changes.push({
2207
- action: "created",
2208
- artifactId: stored.id,
2209
- artifact: toPublicArtifact(stored)
2210
- });
2211
- continue;
2455
+ try {
2456
+ for (const normalized of coalesceByIdentity(normalizedResults)) {
2457
+ const artifact = this.applyStickyEphemeralOverride(normalized);
2458
+ if (this.shouldSuppressTombstonedUpsert(artifact)) {
2459
+ writeStderrLine(
2460
+ `[artifacts] session=${this.sessionId} action=tombstone_replay_suppressed artifactId=${artifact.id}`
2461
+ );
2462
+ continue;
2463
+ }
2464
+ const existing = this.artifacts.get(artifact.id) ?? this.findPublishedUpgradeTarget(artifact) ?? this.findPublishedWorkspaceTarget(artifact);
2465
+ if (!existing) {
2466
+ const stored = {
2467
+ ...artifact,
2468
+ insertSeq: ++this.insertSeq
2469
+ };
2470
+ this.artifacts.set(stored.id, stored);
2471
+ changes.push({
2472
+ action: "created",
2473
+ artifactId: stored.id,
2474
+ artifact: toPublicArtifact(stored)
2475
+ });
2476
+ continue;
2477
+ }
2478
+ try {
2479
+ this.denyCrossClientMutation("upsert", existing.id, existing, {
2480
+ clientId: artifact.clientId
2481
+ });
2482
+ } catch (error) {
2483
+ if (validationStrict) {
2484
+ throw error;
2485
+ }
2486
+ if (error instanceof SessionArtifactAuthorizationError) {
2487
+ warnings.push(error.message);
2488
+ continue;
2489
+ }
2490
+ throw error;
2491
+ }
2492
+ const updated = mergeArtifact(existing, artifact);
2493
+ if (updated.changed) {
2494
+ if (updated.artifact.id !== existing.id) {
2495
+ const removeChange = {
2496
+ action: "removed",
2497
+ artifactId: existing.id,
2498
+ artifact: toPublicArtifact(existing),
2499
+ reason: "explicit",
2500
+ durableTombstoneRequired: existing.durableTombstoneRequired || existing.persistedAt !== void 0,
2501
+ removedClientId: existing.clientId
2502
+ };
2503
+ changes.push(removeChange);
2504
+ this.artifacts.delete(existing.id);
2505
+ } else if (shouldRecordEphemeralUnpin(existing, artifact)) {
2506
+ changes.push({
2507
+ action: "removed",
2508
+ artifactId: existing.id,
2509
+ artifact: toPublicArtifact(existing),
2510
+ reason: "unpin_to_ephemeral",
2511
+ durableTombstoneRequired: true
2512
+ });
2513
+ }
2514
+ this.artifacts.set(updated.artifact.id, updated.artifact);
2515
+ changes.push({
2516
+ action: "updated",
2517
+ artifactId: updated.artifact.id,
2518
+ artifact: toPublicArtifact(updated.artifact)
2519
+ });
2520
+ }
2212
2521
  }
2213
- const updated = mergeArtifact(existing, artifact);
2214
- if (updated.changed) {
2215
- if (updated.artifact.id !== existing.id) {
2216
- this.artifacts.delete(existing.id);
2522
+ const createdIds = new Set(
2523
+ changes.filter((change) => change.action === "created").map((change) => change.artifactId)
2524
+ );
2525
+ changes.push(
2526
+ ...await this.evictOverflow(createdIds, changes, persistenceStrict)
2527
+ );
2528
+ const hasStrictDurableTransition = changes.some(
2529
+ shouldCommitBeforeDurablePersistence
2530
+ );
2531
+ let persistenceWarnings;
2532
+ if (hasStrictDurableTransition && !persistenceStrict) {
2533
+ try {
2534
+ persistenceWarnings = await this.persistChanges(changes, true);
2535
+ } catch (error) {
2536
+ this.restoreState(before);
2537
+ const artifactIds = changes.filter(shouldCommitBeforeDurablePersistence).map((change) => change.artifactId);
2538
+ const warning = "artifact durable removal not persisted; live changes rolled back";
2539
+ writeStderrLine(
2540
+ `[artifacts] session=${this.sessionId} action=upsert_rollback warning=${JSON.stringify(
2541
+ warning
2542
+ )} artifactIds=${JSON.stringify(artifactIds)} error=${JSON.stringify(
2543
+ error instanceof Error ? error.message : String(error)
2544
+ )}`
2545
+ );
2546
+ return {
2547
+ v: 1,
2548
+ sessionId: this.sessionId,
2549
+ changes: [],
2550
+ warnings: [warning],
2551
+ warningDetails: [
2552
+ persistenceFailureDetail({
2553
+ message: warning,
2554
+ operation: "upsert",
2555
+ artifactIds,
2556
+ error
2557
+ })
2558
+ ]
2559
+ };
2217
2560
  }
2218
- this.artifacts.set(updated.artifact.id, updated.artifact);
2219
- changes.push({
2220
- action: "updated",
2221
- artifactId: updated.artifact.id,
2222
- artifact: toPublicArtifact(updated.artifact)
2223
- });
2561
+ } else {
2562
+ persistenceWarnings = await this.persistChanges(
2563
+ changes,
2564
+ persistenceStrict
2565
+ );
2224
2566
  }
2567
+ warnings.push(...persistenceWarnings);
2568
+ warningDetails.push(
2569
+ ...detailsForPersistenceWarnings(
2570
+ persistenceWarnings,
2571
+ changes,
2572
+ "upsert"
2573
+ )
2574
+ );
2575
+ stripDurableTombstoneMarkers(changes);
2576
+ } catch (error) {
2577
+ if (validationStrict || persistenceStrict || error instanceof SessionArtifactAuthorizationError) {
2578
+ this.restoreState(before);
2579
+ }
2580
+ throw error;
2225
2581
  }
2226
- const createdIds = new Set(
2227
- changes.filter((change) => change.action === "created").map((change) => change.artifactId)
2228
- );
2229
- changes.push(...await this.evictOverflow(createdIds, changes));
2230
- return { v: 1, sessionId: this.sessionId, changes };
2582
+ return {
2583
+ v: 1,
2584
+ sessionId: this.sessionId,
2585
+ changes,
2586
+ ...warnings.length > 0 ? { warnings } : {},
2587
+ ...warningDetails.length > 0 ? { warningDetails } : {}
2588
+ };
2231
2589
  });
2232
2590
  }
2233
2591
  findPublishedUpgradeTarget(artifact) {
@@ -2235,7 +2593,7 @@ var SessionArtifactStore = class {
2235
2593
  return void 0;
2236
2594
  }
2237
2595
  const byUrl = this.artifacts.get(
2238
- stableArtifactId(this.sessionId, `url:${artifact.url}`)
2596
+ stableSessionArtifactId(this.sessionId, `url:${artifact.url}`)
2239
2597
  );
2240
2598
  if (byUrl && (byUrl.storage !== "published" || byUrl.managedId === artifact.managedId)) {
2241
2599
  return byUrl;
@@ -2271,27 +2629,552 @@ var SessionArtifactStore = class {
2271
2629
  if (!existing) {
2272
2630
  return { v: 1, sessionId: this.sessionId, changes: [] };
2273
2631
  }
2274
- if (existing.source === "client" && existing.clientId !== void 0 && existing.clientId !== options?.clientId) {
2275
- writeStderrLine(
2276
- `[artifacts] session=${this.sessionId} action=remove_denied artifactId=${artifactId} owner=${existing.clientId} requester=${options?.clientId ?? "<anonymous>"}`
2277
- );
2278
- return { v: 1, sessionId: this.sessionId, changes: [] };
2632
+ this.denyCrossClientMutation("remove", artifactId, existing, options);
2633
+ const removeChange = {
2634
+ action: "removed",
2635
+ artifactId,
2636
+ artifact: toPublicArtifact(existing),
2637
+ reason: "explicit",
2638
+ durableTombstoneRequired: existing.durableTombstoneRequired || existing.retention !== "ephemeral" ? true : void 0,
2639
+ removedClientId: existing.clientId
2640
+ };
2641
+ const changes = [removeChange];
2642
+ const needsDurableTombstone = removeChange.durableTombstoneRequired === true;
2643
+ if (needsDurableTombstone) {
2644
+ const before = this.cloneState();
2645
+ this.artifacts.delete(artifactId);
2646
+ try {
2647
+ await this.persistChanges(changes, true);
2648
+ } catch (error) {
2649
+ this.restoreState(before);
2650
+ const warning = "artifact removal not persisted; live artifact kept";
2651
+ return {
2652
+ v: 1,
2653
+ sessionId: this.sessionId,
2654
+ changes: [],
2655
+ warnings: [warning],
2656
+ warningDetails: [
2657
+ persistenceFailureDetail({
2658
+ message: warning,
2659
+ operation: "remove",
2660
+ artifactIds: [artifactId],
2661
+ error
2662
+ })
2663
+ ]
2664
+ };
2665
+ }
2666
+ }
2667
+ if (!needsDurableTombstone) {
2668
+ this.artifacts.delete(artifactId);
2279
2669
  }
2280
- this.artifacts.delete(artifactId);
2670
+ const warnings = needsDurableTombstone ? [] : await this.persistChanges(changes, false);
2671
+ stripDurableTombstoneMarkers(changes);
2672
+ const warningDetails = detailsForPersistenceWarnings(
2673
+ warnings,
2674
+ changes,
2675
+ "remove"
2676
+ );
2281
2677
  return {
2282
2678
  v: 1,
2283
2679
  sessionId: this.sessionId,
2284
- changes: [
2285
- {
2286
- action: "removed",
2287
- artifactId,
2288
- artifact: toPublicArtifact(existing),
2289
- reason: "explicit"
2290
- }
2291
- ]
2680
+ changes,
2681
+ ...warnings.length > 0 ? { warnings } : {},
2682
+ ...warningDetails.length > 0 ? { warningDetails } : {}
2292
2683
  };
2293
2684
  });
2294
2685
  }
2686
+ async restore(snapshot, options = {}) {
2687
+ if (!snapshot) return [];
2688
+ return this.enqueue(async () => {
2689
+ const warnings = [...snapshot?.warnings ?? []];
2690
+ const baselineWarnings = [...warnings];
2691
+ const previousState = this.cloneState();
2692
+ const preservedLiveEphemeralArtifacts = options.preserveLiveEphemeral ? Array.from(this.artifacts.values()).filter((artifact) => artifact.retention === "ephemeral").map(cloneStoredArtifact) : [];
2693
+ let restoredCount = 0;
2694
+ this.artifacts.clear();
2695
+ this.tombstonedIds.clear();
2696
+ this.tombstonedClientIds.clear();
2697
+ this.stickyEphemeralIds.clear();
2698
+ this.markerArtifacts.clear();
2699
+ if (snapshot) {
2700
+ this.insertSeq = 0;
2701
+ this.persistenceSeq = snapshot.sequence;
2702
+ this.durableEventsSinceSnapshot = 0;
2703
+ this.consecutiveSnapshotFailures = 0;
2704
+ for (const id of snapshot.tombstonedIds) {
2705
+ this.tombstonedIds.add(id);
2706
+ }
2707
+ for (const id of snapshot.stickyEphemeralIds.slice(
2708
+ -MAX_STICKY_EPHEMERAL_IDS
2709
+ )) {
2710
+ this.stickyEphemeralIds.add(id);
2711
+ }
2712
+ const markerIds = /* @__PURE__ */ new Set([
2713
+ ...this.tombstonedIds,
2714
+ ...this.stickyEphemeralIds
2715
+ ]);
2716
+ for (const artifact of snapshot.markerArtifacts ?? []) {
2717
+ if (!markerIds.has(artifact.id)) continue;
2718
+ const markerArtifact = await this.normalizeRestoredMarkerArtifact(
2719
+ artifact,
2720
+ warnings
2721
+ );
2722
+ if (markerArtifact)
2723
+ this.markerArtifacts.set(artifact.id, markerArtifact);
2724
+ }
2725
+ }
2726
+ for (const artifact of snapshot?.artifacts ?? []) {
2727
+ try {
2728
+ const input = persistedArtifactToInput(artifact);
2729
+ if (input.retention === "pinned") {
2730
+ input.retention = "restorable";
2731
+ warnings.push(
2732
+ `pinned artifact ${artifact.id} downgraded to restorable; runtime does not support pinned retention`
2733
+ );
2734
+ }
2735
+ let normalized = await this.normalizeInput(
2736
+ input,
2737
+ ++this.receivedSeq,
2738
+ artifact.storage === "published" && !isFileArtifactUrl(artifact.url),
2739
+ {
2740
+ metadataBudget: "persisted",
2741
+ workspaceExpected: workspaceExpectedFromArtifact(artifact),
2742
+ hashWorkspaceContent: false
2743
+ }
2744
+ );
2745
+ if (this.stickyEphemeralIds.has(normalized.id) && normalized.retention !== "ephemeral") {
2746
+ normalized = {
2747
+ ...normalized,
2748
+ retention: "ephemeral",
2749
+ persistenceWarning: "sticky_override_active"
2750
+ };
2751
+ }
2752
+ const stickyApplied = normalized.persistenceWarning === "sticky_override_active";
2753
+ if (normalized.id !== artifact.id) {
2754
+ warnings.push(`skipped artifact with mismatched id ${artifact.id}`);
2755
+ continue;
2756
+ }
2757
+ const retention = normalized.retention;
2758
+ let persistenceWarning;
2759
+ if (stickyApplied) {
2760
+ persistenceWarning = "sticky_override_active";
2761
+ } else if (normalized.status !== "available") {
2762
+ persistenceWarning = "metadata_only_restore";
2763
+ }
2764
+ const stored = {
2765
+ ...normalized,
2766
+ retention,
2767
+ clientRetained: artifact.clientRetained,
2768
+ createdAt: artifact.createdAt,
2769
+ updatedAt: artifact.updatedAt,
2770
+ persistedAt: artifact.persistedAt,
2771
+ status: normalized.status,
2772
+ restoreState: "restored",
2773
+ persistenceWarning,
2774
+ durableTombstoneRequired: retention !== "ephemeral" ? true : void 0,
2775
+ insertSeq: ++this.insertSeq
2776
+ };
2777
+ this.artifacts.set(stored.id, stored);
2778
+ restoredCount++;
2779
+ } catch (error) {
2780
+ warnings.push(
2781
+ `skipped artifact restore: ${error instanceof Error ? error.message : String(error)}`
2782
+ );
2783
+ }
2784
+ }
2785
+ if (snapshot.artifacts.length > 0 && restoredCount === 0) {
2786
+ this.restoreState(previousState);
2787
+ const rollbackWarnings = [
2788
+ ...baselineWarnings,
2789
+ `${RESTORE_FAILED_WARNING_PREFIX}; kept existing live artifacts`
2790
+ ];
2791
+ this.setLastRestoreWarnings(rollbackWarnings);
2792
+ return rollbackWarnings;
2793
+ }
2794
+ if (previousState.artifacts.size > 0 && baselineWarnings.some(isArtifactSnapshotCompletenessWarning)) {
2795
+ this.restoreState(previousState);
2796
+ const rollbackWarnings = [
2797
+ ...baselineWarnings,
2798
+ restoredCount === 0 ? `${RESTORE_FAILED_WARNING_PREFIX}; kept existing live artifacts` : `${RESTORE_PARTIAL_FAILED_WARNING_PREFIX}; kept existing live artifacts`
2799
+ ];
2800
+ this.setLastRestoreWarnings(rollbackWarnings);
2801
+ return rollbackWarnings;
2802
+ }
2803
+ for (const artifact of preservedLiveEphemeralArtifacts) {
2804
+ if (this.artifacts.has(artifact.id) || this.tombstonedIds.has(artifact.id)) {
2805
+ continue;
2806
+ }
2807
+ this.artifacts.set(artifact.id, {
2808
+ ...artifact,
2809
+ insertSeq: ++this.insertSeq
2810
+ });
2811
+ }
2812
+ const evicted = await this.evictOverflow(/* @__PURE__ */ new Set(), []);
2813
+ if (evicted.length > 0) {
2814
+ warnings.push("restored artifact list pruned to live limit");
2815
+ warnings.push(...await this.persistChanges(evicted, false));
2816
+ }
2817
+ this.setLastRestoreWarnings(warnings);
2818
+ return warnings;
2819
+ });
2820
+ }
2821
+ async recordSnapshot() {
2822
+ const persistence = this.persistence;
2823
+ if (!persistence) {
2824
+ return [
2825
+ "artifact persistence unavailable; restored artifacts not snapshotted"
2826
+ ];
2827
+ }
2828
+ return this.enqueue(async () => {
2829
+ const recordedAt = (/* @__PURE__ */ new Date()).toISOString();
2830
+ const sequence = this.persistenceSeq + 1;
2831
+ try {
2832
+ await persistence.recordSnapshot(
2833
+ this.buildSnapshotPayload(recordedAt, sequence)
2834
+ );
2835
+ this.persistenceSeq = sequence;
2836
+ this.durableEventsSinceSnapshot = 0;
2837
+ this.consecutiveSnapshotFailures = 0;
2838
+ return [];
2839
+ } catch (error) {
2840
+ writeStderrLine(
2841
+ `[artifacts] session=${this.sessionId} action=snapshot_failed reason=${JSON.stringify(
2842
+ error instanceof Error ? error.message : String(error)
2843
+ )}`
2844
+ );
2845
+ return ["artifact snapshot not persisted"];
2846
+ }
2847
+ });
2848
+ }
2849
+ async normalizeRestoredMarkerArtifact(artifact, warnings) {
2850
+ try {
2851
+ const input = persistedArtifactToInput(artifact);
2852
+ if (input.retention === "pinned") {
2853
+ input.retention = "restorable";
2854
+ warnings.push(
2855
+ `pinned marker artifact ${artifact.id} downgraded to restorable; runtime does not support pinned retention`
2856
+ );
2857
+ }
2858
+ const normalized = await this.normalizeInput(
2859
+ input,
2860
+ ++this.receivedSeq,
2861
+ artifact.storage === "published" && !isFileArtifactUrl(artifact.url),
2862
+ {
2863
+ metadataBudget: "persisted",
2864
+ workspaceExpected: workspaceExpectedFromArtifact(artifact),
2865
+ hashWorkspaceContent: false
2866
+ }
2867
+ );
2868
+ if (normalized.id !== artifact.id) {
2869
+ warnings.push(
2870
+ `skipped marker artifact with mismatched id ${artifact.id}`
2871
+ );
2872
+ return void 0;
2873
+ }
2874
+ return toPersistedArtifact(
2875
+ {
2876
+ ...normalized,
2877
+ clientRetained: artifact.clientRetained,
2878
+ createdAt: artifact.createdAt,
2879
+ updatedAt: artifact.updatedAt,
2880
+ persistedAt: artifact.persistedAt
2881
+ },
2882
+ artifact.persistedAt ?? artifact.updatedAt
2883
+ );
2884
+ } catch (error) {
2885
+ warnings.push(
2886
+ `skipped marker artifact ${artifact.id}: ${error instanceof Error ? error.message : String(error)}`
2887
+ );
2888
+ return void 0;
2889
+ }
2890
+ }
2891
+ cloneState() {
2892
+ return {
2893
+ artifacts: new Map(
2894
+ Array.from(this.artifacts.entries()).map(([id, artifact]) => [
2895
+ id,
2896
+ cloneStoredArtifact(artifact)
2897
+ ])
2898
+ ),
2899
+ receivedSeq: this.receivedSeq,
2900
+ insertSeq: this.insertSeq,
2901
+ persistenceSeq: this.persistenceSeq,
2902
+ durableEventsSinceSnapshot: this.durableEventsSinceSnapshot,
2903
+ consecutiveSnapshotFailures: this.consecutiveSnapshotFailures,
2904
+ tombstonedIds: new Set(this.tombstonedIds),
2905
+ tombstonedClientIds: new Map(this.tombstonedClientIds),
2906
+ stickyEphemeralIds: new Set(this.stickyEphemeralIds),
2907
+ markerArtifacts: new Map(this.markerArtifacts),
2908
+ lastRestoreWarnings: [...this.lastRestoreWarnings],
2909
+ lastRestoreWarningDetails: [...this.lastRestoreWarningDetails]
2910
+ };
2911
+ }
2912
+ restoreState(state) {
2913
+ this.artifacts.clear();
2914
+ for (const [id, artifact] of state.artifacts) {
2915
+ this.artifacts.set(id, cloneStoredArtifact(artifact));
2916
+ }
2917
+ this.receivedSeq = state.receivedSeq;
2918
+ this.insertSeq = state.insertSeq;
2919
+ this.persistenceSeq = state.persistenceSeq;
2920
+ this.durableEventsSinceSnapshot = state.durableEventsSinceSnapshot;
2921
+ this.consecutiveSnapshotFailures = state.consecutiveSnapshotFailures;
2922
+ this.tombstonedIds.clear();
2923
+ for (const id of state.tombstonedIds) {
2924
+ this.tombstonedIds.add(id);
2925
+ }
2926
+ this.tombstonedClientIds.clear();
2927
+ for (const [id, clientId] of state.tombstonedClientIds) {
2928
+ this.tombstonedClientIds.set(id, clientId);
2929
+ }
2930
+ this.stickyEphemeralIds.clear();
2931
+ for (const id of state.stickyEphemeralIds) {
2932
+ this.stickyEphemeralIds.add(id);
2933
+ }
2934
+ this.markerArtifacts.clear();
2935
+ for (const [id, artifact] of state.markerArtifacts) {
2936
+ this.markerArtifacts.set(id, artifact);
2937
+ }
2938
+ this.setLastRestoreWarnings(state.lastRestoreWarnings);
2939
+ this.lastRestoreWarningDetails = [...state.lastRestoreWarningDetails];
2940
+ }
2941
+ async persistChanges(changes, strict = false) {
2942
+ const durableChanges = changes.filter(isDurablePersistenceChange);
2943
+ if (durableChanges.length === 0) {
2944
+ return [];
2945
+ }
2946
+ if (!this.persistence) {
2947
+ if (strict) {
2948
+ throw new SessionArtifactValidationError(
2949
+ "artifact persistence is unavailable",
2950
+ "retention"
2951
+ );
2952
+ }
2953
+ const warnings = this.downgradeDurableChanges(durableChanges);
2954
+ this.applyDurableMarkers(durableChanges);
2955
+ return warnings;
2956
+ }
2957
+ const recordedAt = (/* @__PURE__ */ new Date()).toISOString();
2958
+ for (const change of durableChanges) {
2959
+ if (change.action === "removed") continue;
2960
+ const stored = this.artifacts.get(change.artifactId);
2961
+ if (!stored) continue;
2962
+ const artifact = {
2963
+ ...toPublicArtifact(stored),
2964
+ persistedAt: recordedAt
2965
+ };
2966
+ change.artifact = artifact;
2967
+ }
2968
+ const sequence = this.persistenceSeq + 1;
2969
+ const payload = {
2970
+ v: SESSION_ARTIFACT_PERSISTENCE_VERSION,
2971
+ sessionId: this.sessionId,
2972
+ sequence,
2973
+ recordedAt,
2974
+ changes: durableChanges.map(
2975
+ (change) => toPersistedChange(change, recordedAt)
2976
+ )
2977
+ };
2978
+ try {
2979
+ await this.persistence.recordEvent(payload);
2980
+ this.persistenceSeq = sequence;
2981
+ for (const change of durableChanges) {
2982
+ if (change.action === "removed") continue;
2983
+ const stored = this.artifacts.get(change.artifactId);
2984
+ if (!stored) continue;
2985
+ stored.persistedAt = recordedAt;
2986
+ stored.durableTombstoneRequired = true;
2987
+ change.artifact = toPublicArtifact(stored);
2988
+ }
2989
+ this.applyDurableMarkers(durableChanges);
2990
+ await this.maybeRecordSnapshot(recordedAt);
2991
+ return [];
2992
+ } catch (error) {
2993
+ if (strict) {
2994
+ throw error;
2995
+ }
2996
+ const reason = error instanceof Error ? error.message : String(error);
2997
+ const artifactIds = durableChanges.map((change) => change.artifactId);
2998
+ writeStderrLine(
2999
+ `[artifacts] session=${this.sessionId} action=persist_failed sequence=${payload.sequence} artifactIds=${JSON.stringify(
3000
+ artifactIds
3001
+ )} reason=${JSON.stringify(reason)}`
3002
+ );
3003
+ const warnings = this.downgradeDurableChanges(durableChanges);
3004
+ this.applyDurableMarkers(durableChanges);
3005
+ return warnings;
3006
+ }
3007
+ }
3008
+ async maybeRecordSnapshot(recordedAt) {
3009
+ if (!this.persistence) return;
3010
+ this.durableEventsSinceSnapshot++;
3011
+ const snapshotThreshold = SNAPSHOT_AFTER_DURABLE_EVENTS * Math.min(
3012
+ MAX_SNAPSHOT_BACKOFF_MULTIPLIER,
3013
+ 2 ** this.consecutiveSnapshotFailures
3014
+ );
3015
+ if (this.durableEventsSinceSnapshot < snapshotThreshold) {
3016
+ return;
3017
+ }
3018
+ const sequence = this.persistenceSeq + 1;
3019
+ try {
3020
+ await this.persistence.recordSnapshot(
3021
+ this.buildSnapshotPayload(recordedAt, sequence)
3022
+ );
3023
+ this.persistenceSeq = sequence;
3024
+ this.durableEventsSinceSnapshot = 0;
3025
+ this.consecutiveSnapshotFailures = 0;
3026
+ } catch (error) {
3027
+ this.consecutiveSnapshotFailures = Math.min(
3028
+ this.consecutiveSnapshotFailures + 1,
3029
+ Math.log2(MAX_SNAPSHOT_BACKOFF_MULTIPLIER)
3030
+ );
3031
+ writeStderrLine(
3032
+ `[artifacts] session=${this.sessionId} action=snapshot_failed reason=${JSON.stringify(
3033
+ error instanceof Error ? error.message : String(error)
3034
+ )}`
3035
+ );
3036
+ }
3037
+ }
3038
+ buildSnapshotPayload(recordedAt, sequence) {
3039
+ const artifacts = Array.from(this.artifacts.values()).filter((artifact) => artifact.retention !== "ephemeral").sort((a, b) => a.insertSeq - b.insertSeq).map((artifact) => toPersistedArtifact(artifact, recordedAt));
3040
+ const stickyEphemeralIds = Array.from(this.stickyEphemeralIds).filter(
3041
+ (id) => this.artifacts.has(id) || this.markerArtifacts.has(id)
3042
+ );
3043
+ const markerArtifacts = this.buildMarkerArtifacts(
3044
+ recordedAt,
3045
+ stickyEphemeralIds
3046
+ );
3047
+ return {
3048
+ v: SESSION_ARTIFACT_PERSISTENCE_VERSION,
3049
+ sessionId: this.sessionId,
3050
+ sequence,
3051
+ recordedAt,
3052
+ artifacts,
3053
+ tombstonedIds: Array.from(this.tombstonedIds),
3054
+ stickyEphemeralIds,
3055
+ ...markerArtifacts.length > 0 ? { markerArtifacts } : {}
3056
+ };
3057
+ }
3058
+ buildMarkerArtifacts(recordedAt, stickyEphemeralIds) {
3059
+ const markerIds = [...this.tombstonedIds, ...stickyEphemeralIds];
3060
+ const artifacts = [];
3061
+ const seen = /* @__PURE__ */ new Set();
3062
+ for (const id of markerIds) {
3063
+ if (seen.has(id)) continue;
3064
+ seen.add(id);
3065
+ const live = this.artifacts.get(id);
3066
+ if (live) {
3067
+ artifacts.push(toPersistedArtifact(toPublicArtifact(live), recordedAt));
3068
+ continue;
3069
+ }
3070
+ const markerArtifact = this.markerArtifacts.get(id);
3071
+ if (markerArtifact) {
3072
+ artifacts.push(markerArtifact);
3073
+ }
3074
+ }
3075
+ return artifacts;
3076
+ }
3077
+ downgradeDurableChanges(changes) {
3078
+ let downgraded = false;
3079
+ let removalNotPersisted = false;
3080
+ for (const change of changes) {
3081
+ if (change.action === "removed") {
3082
+ removalNotPersisted = true;
3083
+ if (change.reason === "explicit") {
3084
+ this.rememberTombstone(change);
3085
+ this.stickyEphemeralIds.delete(change.artifactId);
3086
+ }
3087
+ continue;
3088
+ }
3089
+ const stored = this.artifacts.get(change.artifactId);
3090
+ if (!stored) continue;
3091
+ stored.retention = "ephemeral";
3092
+ stored.persistenceWarning = "persistence_unavailable";
3093
+ stored.durableTombstoneRequired = stored.durableTombstoneRequired || stored.persistedAt !== void 0;
3094
+ delete stored.persistedAt;
3095
+ change.artifact = toPublicArtifact(stored);
3096
+ downgraded = true;
3097
+ }
3098
+ const warnings = [];
3099
+ if (downgraded) {
3100
+ warnings.push(
3101
+ "artifact persistence unavailable; durable artifacts kept ephemeral"
3102
+ );
3103
+ }
3104
+ if (removalNotPersisted) {
3105
+ warnings.push("artifact removal not persisted; live removal kept");
3106
+ }
3107
+ return warnings;
3108
+ }
3109
+ applyDurableMarkers(changes) {
3110
+ for (const change of changes) {
3111
+ if (change.action === "removed") {
3112
+ if (change.reason === "explicit") {
3113
+ this.rememberTombstone(change);
3114
+ this.stickyEphemeralIds.delete(change.artifactId);
3115
+ } else if (change.reason === "eviction") {
3116
+ this.stickyEphemeralIds.delete(change.artifactId);
3117
+ this.markerArtifacts.delete(change.artifactId);
3118
+ } else if (change.reason === "unpin_to_ephemeral") {
3119
+ this.rememberStickyEphemeral(change);
3120
+ }
3121
+ continue;
3122
+ }
3123
+ if (change.artifact && change.artifact.retention !== "ephemeral") {
3124
+ this.tombstonedIds.delete(change.artifactId);
3125
+ this.tombstonedClientIds.delete(change.artifactId);
3126
+ this.stickyEphemeralIds.delete(change.artifactId);
3127
+ this.markerArtifacts.delete(change.artifactId);
3128
+ }
3129
+ }
3130
+ }
3131
+ rememberStickyEphemeral(change) {
3132
+ const artifactId = change.artifactId;
3133
+ this.stickyEphemeralIds.delete(artifactId);
3134
+ this.stickyEphemeralIds.add(artifactId);
3135
+ if (change.artifact) {
3136
+ this.markerArtifacts.set(
3137
+ artifactId,
3138
+ toPersistedArtifact(change.artifact, change.artifact.updatedAt)
3139
+ );
3140
+ }
3141
+ while (this.stickyEphemeralIds.size > MAX_STICKY_EPHEMERAL_IDS) {
3142
+ const oldest2 = this.stickyEphemeralIds.values().next().value;
3143
+ if (oldest2 === void 0) break;
3144
+ this.stickyEphemeralIds.delete(oldest2);
3145
+ this.markerArtifacts.delete(oldest2);
3146
+ }
3147
+ }
3148
+ rememberTombstone(change) {
3149
+ this.tombstonedIds.delete(change.artifactId);
3150
+ this.tombstonedIds.add(change.artifactId);
3151
+ this.tombstonedClientIds.set(
3152
+ change.artifactId,
3153
+ change.removedClientId ?? this.artifacts.get(change.artifactId)?.clientId
3154
+ );
3155
+ if (change.artifact) {
3156
+ this.markerArtifacts.set(
3157
+ change.artifactId,
3158
+ toPersistedArtifact(change.artifact, change.artifact.updatedAt)
3159
+ );
3160
+ }
3161
+ while (this.tombstonedIds.size > MAX_TOMBSTONED_IDS) {
3162
+ const oldest2 = this.tombstonedIds.values().next().value;
3163
+ if (oldest2 === void 0) break;
3164
+ writeStderrLine(
3165
+ `[artifacts] session=${this.sessionId} action=tombstone_evicted artifactId=${JSON.stringify(
3166
+ oldest2
3167
+ )} limit=${MAX_TOMBSTONED_IDS}`
3168
+ );
3169
+ this.tombstonedIds.delete(oldest2);
3170
+ this.tombstonedClientIds.delete(oldest2);
3171
+ this.markerArtifacts.delete(oldest2);
3172
+ }
3173
+ }
3174
+ setLastRestoreWarnings(warnings) {
3175
+ this.lastRestoreWarnings = [...warnings];
3176
+ this.lastRestoreWarningDetails = detailsForRestoreWarnings(warnings);
3177
+ }
2295
3178
  enqueue(operation) {
2296
3179
  const result = this.operationQueue.then(operation, operation);
2297
3180
  this.operationQueue = result.then(
@@ -2300,7 +3183,21 @@ var SessionArtifactStore = class {
2300
3183
  );
2301
3184
  return result;
2302
3185
  }
2303
- async normalizeInput(input, receivedSeq, trustedPublisherFromCaller) {
3186
+ denyCrossClientMutation(action, artifactId, existing, options) {
3187
+ if (existing.source !== "client" || existing.clientId === void 0 || existing.clientId === options?.clientId) {
3188
+ return;
3189
+ }
3190
+ writeStderrLine(
3191
+ `[artifacts] session=${this.sessionId} action=${action}_denied artifactId=${artifactId} owner=${existing.clientId} requester=${options?.clientId ?? "<anonymous>"}`
3192
+ );
3193
+ throw new SessionArtifactAuthorizationError(
3194
+ this.sessionId,
3195
+ artifactId,
3196
+ existing.clientId,
3197
+ options?.clientId
3198
+ );
3199
+ }
3200
+ async normalizeInput(input, receivedSeq, trustedPublisherFromCaller, options = {}) {
2304
3201
  if (!input || typeof input !== "object") {
2305
3202
  throw new SessionArtifactValidationError("Artifact must be an object");
2306
3203
  }
@@ -2319,7 +3216,10 @@ var SessionArtifactStore = class {
2319
3216
  );
2320
3217
  }
2321
3218
  const trustedPublisher = trustedPublisherFromCaller;
2322
- const workspacePath = input.workspacePath ? normalizeWorkspacePath(input.workspacePath, this.workspaceCwd) : void 0;
3219
+ const workspacePath = input.workspacePath ? normalizeWorkspacePath(
3220
+ input.workspacePath,
3221
+ await this.getRealWorkspaceCwdForValidation()
3222
+ ) : void 0;
2323
3223
  const managedId = normalizeManagedId(input.managedId);
2324
3224
  const rawStorage = input.storage;
2325
3225
  const storage = inferStorage(rawStorage, {
@@ -2335,14 +3235,26 @@ var SessionArtifactStore = class {
2335
3235
  url,
2336
3236
  trustedPublisher
2337
3237
  });
2338
- const metadata = normalizeMetadata(input.metadata);
2339
- const workspaceStatus = workspacePath ? await this.getInitialWorkspaceStatus(workspacePath) : void 0;
3238
+ const retention = normalizeRetention(input.retention, {
3239
+ persistenceAvailable: this.persistence !== void 0
3240
+ });
3241
+ const workspaceStatus = workspacePath ? await this.getInitialWorkspaceStatus(
3242
+ workspacePath,
3243
+ options.workspaceExpected,
3244
+ { hashContent: options.hashWorkspaceContent !== false }
3245
+ ) : void 0;
2340
3246
  if (workspaceStatus?.escaped) {
2341
3247
  throw new SessionArtifactValidationError(
2342
3248
  "workspacePath must stay inside the workspace",
2343
3249
  "workspacePath"
2344
3250
  );
2345
3251
  }
3252
+ const metadata = withWorkspaceContentHashMetadata(
3253
+ normalizeMetadata(input.metadata, {
3254
+ budget: options.metadataBudget ?? "user"
3255
+ }),
3256
+ workspaceStatus
3257
+ );
2346
3258
  const kind = normalizeKind(
2347
3259
  input.kind ?? inferKind({ storage, workspacePath, url })
2348
3260
  );
@@ -2353,11 +3265,12 @@ var SessionArtifactStore = class {
2353
3265
  managedId,
2354
3266
  url
2355
3267
  });
2356
- const id = stableArtifactId(this.sessionId, identityKey);
3268
+ const id = stableSessionArtifactId(this.sessionId, identityKey);
2357
3269
  return {
2358
3270
  id,
2359
3271
  identityKey,
2360
3272
  receivedSeq,
3273
+ retentionExplicit: input.retention !== void 0,
2361
3274
  retentionSource: source,
2362
3275
  trustedPublisher,
2363
3276
  kind,
@@ -2371,9 +3284,12 @@ var SessionArtifactStore = class {
2371
3284
  managedId,
2372
3285
  url,
2373
3286
  mimeType: normalizeString(input.mimeType, "mimeType", 120, false),
2374
- sizeBytes: input.sizeBytes !== void 0 ? normalizeSizeBytes(input.sizeBytes) : workspaceStatus?.sizeBytes,
3287
+ sizeBytes: workspaceStatus?.sizeBytes !== void 0 ? workspaceStatus.sizeBytes : input.sizeBytes !== void 0 ? normalizeSizeBytes(input.sizeBytes) : void 0,
2375
3288
  metadata,
2376
- clientRetained: source === "client",
3289
+ retention,
3290
+ restoreState: "live",
3291
+ ...this.persistence === void 0 && retention !== "ephemeral" ? { persistenceWarning: "persistence_unavailable" } : {},
3292
+ clientRetained: source === "client" && (input.clientRetained !== void 0 ? input.clientRetained === true : true),
2377
3293
  createdAt: now,
2378
3294
  updatedAt: now,
2379
3295
  toolCallId: normalizeString(input.toolCallId, "toolCallId", 200, false),
@@ -2387,6 +3303,16 @@ var SessionArtifactStore = class {
2387
3303
  clientId: normalizeString(input.clientId, "clientId", 200, false)
2388
3304
  };
2389
3305
  }
3306
+ shouldSuppressTombstonedUpsert(artifact) {
3307
+ if (!this.tombstonedIds.has(artifact.id)) {
3308
+ return false;
3309
+ }
3310
+ const tombstonedClientId = this.tombstonedClientIds.get(artifact.id);
3311
+ if (artifact.source !== "client") {
3312
+ return false;
3313
+ }
3314
+ return !(artifact.retentionExplicit && artifact.clientId !== void 0 && artifact.clientId === tombstonedClientId);
3315
+ }
2390
3316
  async refreshWorkspaceStatuses() {
2391
3317
  const now = Date.now();
2392
3318
  const staleWorkspaceArtifacts = Array.from(this.artifacts.values()).filter((artifact) => artifact.workspacePath).filter((artifact) => shouldRefreshWorkspaceStatus(artifact, now));
@@ -2399,12 +3325,24 @@ var SessionArtifactStore = class {
2399
3325
  })
2400
3326
  );
2401
3327
  }
2402
- async getInitialWorkspaceStatus(workspacePath) {
3328
+ applyStickyEphemeralOverride(artifact) {
3329
+ if (!this.stickyEphemeralIds.has(artifact.id) || artifact.retentionExplicit || artifact.retention === "ephemeral") {
3330
+ return artifact;
3331
+ }
3332
+ return {
3333
+ ...artifact,
3334
+ retention: "ephemeral",
3335
+ retentionExplicit: true,
3336
+ persistenceWarning: "sticky_override_active"
3337
+ };
3338
+ }
3339
+ async getInitialWorkspaceStatus(workspacePath, expected, options = { hashContent: true }) {
2403
3340
  try {
2404
3341
  return await getWorkspaceStatus(
2405
- this.workspaceCwd,
2406
3342
  workspacePath,
2407
- this.getRealWorkspaceCwd()
3343
+ this.getRealWorkspaceCwd(),
3344
+ expected,
3345
+ options
2408
3346
  );
2409
3347
  } catch (error) {
2410
3348
  const reason = error instanceof Error ? error.message : String(error);
@@ -2420,16 +3358,23 @@ var SessionArtifactStore = class {
2420
3358
  }
2421
3359
  try {
2422
3360
  const status = await getWorkspaceStatus(
2423
- this.workspaceCwd,
2424
3361
  artifact.workspacePath,
2425
- this.getRealWorkspaceCwd()
3362
+ this.getRealWorkspaceCwd(),
3363
+ {
3364
+ sizeBytes: artifact.sizeBytes,
3365
+ mtimeMs: artifact.metadata?.[WORKSPACE_CONTENT_MTIME_MS_METADATA_KEY],
3366
+ sha256: artifact.metadata?.[WORKSPACE_CONTENT_SHA256_METADATA_KEY]
3367
+ }
2426
3368
  );
2427
- artifact.status = status.status;
2428
- artifact.sizeBytes = status.sizeBytes;
3369
+ const changed = isWorkspaceContentChanged(artifact, status);
3370
+ artifact.status = changed ? "changed" : status.status;
3371
+ if (!changed) {
3372
+ artifact.sizeBytes = status.sizeBytes;
3373
+ }
2429
3374
  if (status.escaped) {
2430
3375
  artifact.status = "missing";
2431
3376
  artifact.sizeBytes = void 0;
2432
- delete artifact.workspacePath;
3377
+ artifact.hideWorkspacePath = true;
2433
3378
  }
2434
3379
  artifact.lastStatAt = options.now ?? Date.now();
2435
3380
  } catch (error) {
@@ -2458,7 +3403,18 @@ var SessionArtifactStore = class {
2458
3403
  }
2459
3404
  return this.realWorkspaceCwdPromise;
2460
3405
  }
2461
- async evictOverflow(createdIds, changes) {
3406
+ async getRealWorkspaceCwdForValidation() {
3407
+ try {
3408
+ return await this.getRealWorkspaceCwd();
3409
+ } catch (error) {
3410
+ const reason = error instanceof Error ? error.message : String(error);
3411
+ throw new SessionArtifactValidationError(
3412
+ `workspacePath could not be inspected: ${reason}`,
3413
+ "workspacePath"
3414
+ );
3415
+ }
3416
+ }
3417
+ async evictOverflow(createdIds, changes, strict = false) {
2462
3418
  const removed = [];
2463
3419
  if (this.artifacts.size <= this.maxArtifacts) {
2464
3420
  return removed;
@@ -2486,10 +3442,17 @@ var SessionArtifactStore = class {
2486
3442
  action: "removed",
2487
3443
  artifactId: artifact.id,
2488
3444
  artifact: toPublicArtifact(artifact),
3445
+ durableTombstoneRequired: artifact.durableTombstoneRequired,
2489
3446
  reason: "eviction"
2490
3447
  });
2491
3448
  }
2492
3449
  const overflowCreated = Array.from(this.artifacts.values()).filter((artifact) => createdInThisBatch.has(artifact.id)).sort((a, b) => b.receivedSeq - a.receivedSeq);
3450
+ if (strict && overflowCreated.length > 0 && this.artifacts.size > this.maxArtifacts) {
3451
+ throw new SessionArtifactValidationError(
3452
+ "artifact store is full; no eviction candidate is available",
3453
+ "artifactId"
3454
+ );
3455
+ }
2493
3456
  for (const artifact of overflowCreated) {
2494
3457
  if (this.artifacts.size <= this.maxArtifacts) {
2495
3458
  break;
@@ -2538,7 +3501,12 @@ function mergeBatchArtifact(existing, next) {
2538
3501
  trustedPublisher: true,
2539
3502
  createdAt: existing.createdAt,
2540
3503
  receivedSeq: existing.receivedSeq,
3504
+ retentionExplicit: existing.retentionExplicit || next.retentionExplicit,
2541
3505
  retentionSource: existing.retentionSource,
3506
+ retention: mergeRetention(existing, next),
3507
+ restoreState: "live",
3508
+ persistenceWarning: existing.persistenceWarning ?? next.persistenceWarning,
3509
+ persistedAt: existing.persistedAt ?? next.persistedAt,
2542
3510
  clientRetained: existing.clientRetained || next.clientRetained,
2543
3511
  lastStatAt: void 0
2544
3512
  };
@@ -2552,6 +3520,8 @@ function mergeBatchArtifact(existing, next) {
2552
3520
  metadata: mergeMetadata(existing, next),
2553
3521
  clientRetained: existing.clientRetained || next.clientRetained,
2554
3522
  trustedPublisher: existing.trustedPublisher || next.trustedPublisher,
3523
+ retentionExplicit: existing.retentionExplicit || next.retentionExplicit,
3524
+ retention: mergeRetention(existing, next),
2555
3525
  lastStatAt: next.lastStatAt ?? existing.lastStatAt
2556
3526
  };
2557
3527
  }
@@ -2571,9 +3541,14 @@ function mergeArtifact(existing, incoming) {
2571
3541
  managedId: publishedUpdate ? incoming.managedId ?? existing.managedId : existing.managedId,
2572
3542
  url: publishedUpdate ? incoming.url ?? existing.url : existing.url,
2573
3543
  workspacePath: publishedUpdate || existing.storage === "published" ? void 0 : existing.workspacePath ?? incoming.workspacePath,
3544
+ hideWorkspacePath: publishedUpdate || existing.storage === "published" || incoming.workspacePath ? void 0 : existing.hideWorkspacePath,
2574
3545
  mimeType: publishedUpdate ? incoming.mimeType ?? existing.mimeType : existing.mimeType,
2575
3546
  sizeBytes: existing.storage === "published" && !publishedUpdate ? existing.sizeBytes : mergeSizeBytes(existing, incoming),
2576
3547
  metadata: existing.storage === "published" && !publishedUpdate ? existing.metadata : mergeMetadata(existing, incoming),
3548
+ retention: mergeRetention(existing, incoming),
3549
+ restoreState: "live",
3550
+ persistenceWarning: incoming.retentionExplicit && incoming.retention !== "ephemeral" ? incoming.persistenceWarning : existing.persistenceWarning ?? incoming.persistenceWarning,
3551
+ persistedAt: incoming.retentionExplicit && incoming.retention === "ephemeral" ? void 0 : existing.persistedAt ?? incoming.persistedAt,
2577
3552
  source: existing.source,
2578
3553
  retentionSource: existing.retentionSource,
2579
3554
  trustedPublisher: existing.trustedPublisher || incoming.trustedPublisher,
@@ -2585,6 +3560,7 @@ function mergeArtifact(existing, incoming) {
2585
3560
  next.title = incoming.title;
2586
3561
  next.description = incoming.description;
2587
3562
  delete next.workspacePath;
3563
+ delete next.hideWorkspacePath;
2588
3564
  }
2589
3565
  const changed = !publicArtifactsEqual(
2590
3566
  toPublicArtifact(existing),
@@ -2596,8 +3572,12 @@ function mergeArtifact(existing, incoming) {
2596
3572
  return { artifact: changed ? next : existing, changed };
2597
3573
  }
2598
3574
  __name(mergeArtifact, "mergeArtifact");
3575
+ function shouldRecordEphemeralUnpin(existing, incoming) {
3576
+ return incoming.retentionExplicit && incoming.retention === "ephemeral" && (existing.retention !== "ephemeral" || existing.persistedAt !== void 0 || existing.durableTombstoneRequired === true);
3577
+ }
3578
+ __name(shouldRecordEphemeralUnpin, "shouldRecordEphemeralUnpin");
2599
3579
  function publicArtifactsEqual(a, b) {
2600
- return a.id === b.id && a.kind === b.kind && a.storage === b.storage && a.source === b.source && a.status === b.status && a.title === b.title && a.description === b.description && a.workspacePath === b.workspacePath && a.managedId === b.managedId && a.url === b.url && a.mimeType === b.mimeType && a.sizeBytes === b.sizeBytes && metadataEqual(a.metadata, b.metadata) && a.clientRetained === b.clientRetained && a.createdAt === b.createdAt && a.toolCallId === b.toolCallId && a.toolName === b.toolName && a.hookEventName === b.hookEventName && a.clientId === b.clientId;
3580
+ return a.id === b.id && a.kind === b.kind && a.storage === b.storage && a.source === b.source && a.status === b.status && a.title === b.title && a.description === b.description && a.workspacePath === b.workspacePath && a.managedId === b.managedId && a.url === b.url && a.mimeType === b.mimeType && a.sizeBytes === b.sizeBytes && metadataEqual(a.metadata, b.metadata) && a.retention === b.retention && a.restoreState === b.restoreState && a.persistenceWarning === b.persistenceWarning && a.persistedAt === b.persistedAt && a.clientRetained === b.clientRetained && a.createdAt === b.createdAt && a.updatedAt === b.updatedAt && a.toolCallId === b.toolCallId && a.toolName === b.toolName && a.hookEventName === b.hookEventName && a.clientId === b.clientId;
2601
3581
  }
2602
3582
  __name(publicArtifactsEqual, "publicArtifactsEqual");
2603
3583
  function metadataEqual(a, b) {
@@ -2619,6 +3599,24 @@ function mergeSizeBytes(existing, incoming) {
2619
3599
  return existing.sizeBytes;
2620
3600
  }
2621
3601
  __name(mergeSizeBytes, "mergeSizeBytes");
3602
+ function strongestRetention(a, b) {
3603
+ const rank = {
3604
+ ephemeral: 0,
3605
+ restorable: 1
3606
+ };
3607
+ return rank[b] > rank[a] ? b : a;
3608
+ }
3609
+ __name(strongestRetention, "strongestRetention");
3610
+ function mergeRetention(existing, incoming) {
3611
+ if (incoming.retentionExplicit && incoming.retention === "ephemeral") {
3612
+ return "ephemeral";
3613
+ }
3614
+ if (existing.retentionExplicit && existing.retention === "ephemeral" && !incoming.retentionExplicit) {
3615
+ return "ephemeral";
3616
+ }
3617
+ return strongestRetention(existing.retention, incoming.retention);
3618
+ }
3619
+ __name(mergeRetention, "mergeRetention");
2622
3620
  function mergeMetadata(existing, incoming) {
2623
3621
  if (!incoming.metadata || incoming.source === "hook" || incoming.source !== existing.source) {
2624
3622
  return existing.metadata;
@@ -2626,7 +3624,10 @@ function mergeMetadata(existing, incoming) {
2626
3624
  const merged = { ...existing.metadata ?? {} };
2627
3625
  let changed = false;
2628
3626
  for (const [key, value] of Object.entries(incoming.metadata)) {
2629
- if (!Object.hasOwn(merged, key)) {
3627
+ if ((key === WORKSPACE_CONTENT_SHA256_METADATA_KEY || key === WORKSPACE_CONTENT_MTIME_MS_METADATA_KEY) && merged[key] !== value) {
3628
+ merged[key] = value;
3629
+ changed = true;
3630
+ } else if (!Object.hasOwn(merged, key)) {
2630
3631
  merged[key] = value;
2631
3632
  changed = true;
2632
3633
  }
@@ -2644,7 +3645,7 @@ function mergeMetadata(existing, incoming) {
2644
3645
  }
2645
3646
  __name(mergeMetadata, "mergeMetadata");
2646
3647
  function isMetadataWithinLimit(metadata) {
2647
- return Buffer.byteLength(JSON.stringify(metadata), "utf8") <= 4096;
3648
+ return metadataBudgetBytes(metadata, "persisted") <= 4096;
2648
3649
  }
2649
3650
  __name(isMetadataWithinLimit, "isMetadataWithinLimit");
2650
3651
  function countByRetentionSource(artifacts) {
@@ -2700,6 +3701,7 @@ function removePriorChange(changes, artifactId) {
2700
3701
  }
2701
3702
  __name(removePriorChange, "removePriorChange");
2702
3703
  function toPublicArtifact(artifact) {
3704
+ const hideWorkspacePath = "hideWorkspacePath" in artifact && artifact.hideWorkspacePath === true;
2703
3705
  const {
2704
3706
  id,
2705
3707
  kind,
@@ -2714,38 +3716,224 @@ function toPublicArtifact(artifact) {
2714
3716
  mimeType,
2715
3717
  sizeBytes,
2716
3718
  metadata,
3719
+ retention,
3720
+ restoreState,
3721
+ persistenceWarning,
3722
+ persistedAt,
2717
3723
  clientRetained,
2718
3724
  createdAt,
2719
3725
  updatedAt,
2720
3726
  toolCallId,
2721
3727
  toolName,
2722
- hookEventName,
2723
- clientId
3728
+ hookEventName
2724
3729
  } = artifact;
2725
3730
  return {
2726
- id,
2727
- kind,
2728
- storage,
2729
- source,
2730
- status,
2731
- title,
2732
- ...description ? { description } : {},
2733
- ...workspacePath ? { workspacePath } : {},
2734
- ...managedId ? { managedId } : {},
2735
- ...url ? { url } : {},
2736
- ...mimeType ? { mimeType } : {},
2737
- ...sizeBytes !== void 0 ? { sizeBytes } : {},
2738
- ...metadata ? { metadata } : {},
2739
- clientRetained,
2740
- createdAt,
2741
- updatedAt,
2742
- ...toolCallId ? { toolCallId } : {},
2743
- ...toolName ? { toolName } : {},
2744
- ...hookEventName ? { hookEventName } : {},
2745
- ...clientId ? { clientId } : {}
3731
+ id,
3732
+ kind,
3733
+ storage,
3734
+ source,
3735
+ status,
3736
+ title,
3737
+ ...description ? { description } : {},
3738
+ ...workspacePath && !hideWorkspacePath ? { workspacePath } : {},
3739
+ ...managedId ? { managedId } : {},
3740
+ ...url ? { url } : {},
3741
+ ...mimeType ? { mimeType } : {},
3742
+ ...sizeBytes !== void 0 ? { sizeBytes } : {},
3743
+ ...metadata ? { metadata } : {},
3744
+ retention,
3745
+ ...restoreState ? { restoreState } : {},
3746
+ ...persistenceWarning ? { persistenceWarning } : {},
3747
+ ...persistedAt ? { persistedAt } : {},
3748
+ clientRetained,
3749
+ createdAt,
3750
+ updatedAt,
3751
+ ...toolCallId ? { toolCallId } : {},
3752
+ ...toolName ? { toolName } : {},
3753
+ ...hookEventName ? { hookEventName } : {}
3754
+ };
3755
+ }
3756
+ __name(toPublicArtifact, "toPublicArtifact");
3757
+ function persistedArtifactToInput(artifact) {
3758
+ return {
3759
+ title: artifact.title,
3760
+ kind: artifact.kind,
3761
+ storage: artifact.storage,
3762
+ description: artifact.description,
3763
+ workspacePath: artifact.workspacePath,
3764
+ managedId: artifact.managedId,
3765
+ url: artifact.url,
3766
+ mimeType: artifact.mimeType,
3767
+ sizeBytes: artifact.sizeBytes,
3768
+ metadata: artifact.metadata,
3769
+ source: artifact.source,
3770
+ retention: artifact.retention,
3771
+ clientRetained: artifact.clientRetained,
3772
+ toolCallId: artifact.toolCallId,
3773
+ toolName: artifact.toolName,
3774
+ hookEventName: artifact.hookEventName
3775
+ };
3776
+ }
3777
+ __name(persistedArtifactToInput, "persistedArtifactToInput");
3778
+ function workspaceExpectedFromArtifact(artifact) {
3779
+ if (!artifact.workspacePath) {
3780
+ return void 0;
3781
+ }
3782
+ return {
3783
+ sizeBytes: artifact.sizeBytes,
3784
+ mtimeMs: artifact.metadata?.[WORKSPACE_CONTENT_MTIME_MS_METADATA_KEY],
3785
+ sha256: artifact.metadata?.[WORKSPACE_CONTENT_SHA256_METADATA_KEY]
3786
+ };
3787
+ }
3788
+ __name(workspaceExpectedFromArtifact, "workspaceExpectedFromArtifact");
3789
+ function toPersistedChange(change, recordedAt) {
3790
+ return {
3791
+ action: change.action,
3792
+ artifactId: change.artifactId,
3793
+ ...change.artifact ? { artifact: toPersistedArtifact(change.artifact, recordedAt) } : {},
3794
+ ...change.reason ? { reason: change.reason } : {}
3795
+ };
3796
+ }
3797
+ __name(toPersistedChange, "toPersistedChange");
3798
+ function toPersistedArtifact(artifact, recordedAt) {
3799
+ return {
3800
+ id: artifact.id,
3801
+ kind: artifact.kind,
3802
+ storage: artifact.storage,
3803
+ source: artifact.source,
3804
+ status: artifact.status === "changed" ? "available" : artifact.status,
3805
+ title: artifact.title,
3806
+ ...artifact.description ? { description: artifact.description } : {},
3807
+ ...artifact.workspacePath ? { workspacePath: artifact.workspacePath } : {},
3808
+ ...artifact.managedId ? { managedId: artifact.managedId } : {},
3809
+ ...artifact.url ? { url: artifact.url } : {},
3810
+ ...artifact.mimeType ? { mimeType: artifact.mimeType } : {},
3811
+ ...artifact.sizeBytes !== void 0 ? { sizeBytes: artifact.sizeBytes } : {},
3812
+ ...artifact.metadata ? { metadata: artifact.metadata } : {},
3813
+ retention: artifact.retention,
3814
+ clientRetained: artifact.clientRetained,
3815
+ createdAt: artifact.createdAt,
3816
+ updatedAt: artifact.updatedAt,
3817
+ persistedAt: artifact.persistedAt ?? recordedAt,
3818
+ ...artifact.toolCallId ? { toolCallId: artifact.toolCallId } : {},
3819
+ ...artifact.toolName ? { toolName: artifact.toolName } : {},
3820
+ ...artifact.hookEventName ? { hookEventName: artifact.hookEventName } : {}
3821
+ };
3822
+ }
3823
+ __name(toPersistedArtifact, "toPersistedArtifact");
3824
+ function isFileArtifactUrl(raw) {
3825
+ if (typeof raw !== "string") return false;
3826
+ try {
3827
+ return new URL(raw).protocol === "file:";
3828
+ } catch {
3829
+ return false;
3830
+ }
3831
+ }
3832
+ __name(isFileArtifactUrl, "isFileArtifactUrl");
3833
+ function isArtifactSnapshotCompletenessWarning(warning) {
3834
+ if (warning.startsWith("skipped stale event sequence ")) return false;
3835
+ return warning.startsWith("skipped ") || warning.includes(" list truncated to ");
3836
+ }
3837
+ __name(isArtifactSnapshotCompletenessWarning, "isArtifactSnapshotCompletenessWarning");
3838
+ function shouldCommitBeforeDurablePersistence(change) {
3839
+ return change.action === "removed" && change.durableTombstoneRequired === true;
3840
+ }
3841
+ __name(shouldCommitBeforeDurablePersistence, "shouldCommitBeforeDurablePersistence");
3842
+ function isDurablePersistenceChange(change) {
3843
+ if (change.action === "removed" && change.durableTombstoneRequired === true) {
3844
+ return true;
3845
+ }
3846
+ if (!change.artifact) return false;
3847
+ return change.artifact.retention !== "ephemeral" || change.artifact.persistenceWarning === "persistence_unavailable";
3848
+ }
3849
+ __name(isDurablePersistenceChange, "isDurablePersistenceChange");
3850
+ function stripDurableTombstoneMarkers(changes) {
3851
+ for (const change of changes) {
3852
+ delete change.durableTombstoneRequired;
3853
+ delete change.removedClientId;
3854
+ }
3855
+ }
3856
+ __name(stripDurableTombstoneMarkers, "stripDurableTombstoneMarkers");
3857
+ function detailsForRestoreWarnings(warnings) {
3858
+ return warnings.map((warning) => {
3859
+ if (warning.startsWith(RESTORE_FAILED_WARNING_PREFIX)) {
3860
+ return {
3861
+ code: "ARTIFACT_RESTORE_FAILED",
3862
+ operation: "restore",
3863
+ durability: "unavailable",
3864
+ retryable: true,
3865
+ message: warning
3866
+ };
3867
+ }
3868
+ if (warning.startsWith(RESTORE_PARTIAL_FAILED_WARNING_PREFIX)) {
3869
+ return {
3870
+ code: "ARTIFACT_RESTORE_PARTIAL_FAILED",
3871
+ operation: "restore",
3872
+ durability: "live_only",
3873
+ retryable: true,
3874
+ message: warning
3875
+ };
3876
+ }
3877
+ return {
3878
+ code: "ARTIFACT_WARNING",
3879
+ operation: "restore",
3880
+ message: warning
3881
+ };
3882
+ });
3883
+ }
3884
+ __name(detailsForRestoreWarnings, "detailsForRestoreWarnings");
3885
+ function detailsForPersistenceWarnings(warnings, changes, operation) {
3886
+ if (warnings.length === 0) return [];
3887
+ const artifactIds = changes.filter(isDurablePersistenceChange).map((change) => change.artifactId);
3888
+ return warnings.map((warning) => {
3889
+ if (warning === "artifact persistence unavailable; durable artifacts kept ephemeral") {
3890
+ return {
3891
+ code: "ARTIFACT_PERSISTENCE_UNAVAILABLE",
3892
+ operation,
3893
+ artifactIds,
3894
+ durability: "live_only",
3895
+ retryable: false,
3896
+ message: warning
3897
+ };
3898
+ }
3899
+ if (warning === "artifact removal not persisted; live removal kept") {
3900
+ return {
3901
+ code: "ARTIFACT_REMOVAL_NOT_PERSISTED",
3902
+ operation,
3903
+ artifactIds,
3904
+ durability: "live_only",
3905
+ retryable: true,
3906
+ message: warning
3907
+ };
3908
+ }
3909
+ return {
3910
+ code: "ARTIFACT_WARNING",
3911
+ operation,
3912
+ artifactIds,
3913
+ message: warning
3914
+ };
3915
+ });
3916
+ }
3917
+ __name(detailsForPersistenceWarnings, "detailsForPersistenceWarnings");
3918
+ function persistenceFailureDetail(options) {
3919
+ const unavailable = options.error instanceof SessionArtifactValidationError && options.error.message === "artifact persistence is unavailable";
3920
+ return {
3921
+ code: unavailable ? "ARTIFACT_PERSISTENCE_UNAVAILABLE" : "ARTIFACT_PERSISTENCE_WRITE_FAILED",
3922
+ operation: options.operation,
3923
+ artifactIds: options.artifactIds,
3924
+ durability: "unavailable",
3925
+ retryable: !unavailable,
3926
+ message: options.message
2746
3927
  };
2747
3928
  }
2748
- __name(toPublicArtifact, "toPublicArtifact");
3929
+ __name(persistenceFailureDetail, "persistenceFailureDetail");
3930
+ function cloneStoredArtifact(artifact) {
3931
+ return {
3932
+ ...artifact,
3933
+ ...artifact.metadata ? { metadata: { ...artifact.metadata } } : {}
3934
+ };
3935
+ }
3936
+ __name(cloneStoredArtifact, "cloneStoredArtifact");
2749
3937
  function inferStorage(requested, locators) {
2750
3938
  if (requested !== void 0 && typeof requested !== "string") {
2751
3939
  throw new SessionArtifactValidationError(
@@ -2839,10 +4027,6 @@ function buildIdentityKey(input) {
2839
4027
  );
2840
4028
  }
2841
4029
  __name(buildIdentityKey, "buildIdentityKey");
2842
- function stableArtifactId(sessionId, identityKey) {
2843
- return createHash("sha256").update(`${sessionId}:${identityKey}`).digest("hex").slice(0, 16);
2844
- }
2845
- __name(stableArtifactId, "stableArtifactId");
2846
4030
  function managedIdForWorkspacePath(workspaceCwd, workspacePath) {
2847
4031
  return createHash("sha1").update(path2.resolve(workspaceCwd, workspacePath)).digest("hex").slice(0, 16);
2848
4032
  }
@@ -2956,6 +4140,12 @@ function normalizeArtifactUrl(raw, allowFile) {
2956
4140
  "url"
2957
4141
  );
2958
4142
  }
4143
+ if (hasSecretLikeUrlComponent(parsed)) {
4144
+ throw new SessionArtifactValidationError(
4145
+ "url must not include secret-like components",
4146
+ "url"
4147
+ );
4148
+ }
2959
4149
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:" && !(allowFile && parsed.protocol === "file:")) {
2960
4150
  throw new SessionArtifactValidationError(
2961
4151
  allowFile ? "url must use http, https, or file" : "url must use http or https",
@@ -2965,7 +4155,64 @@ function normalizeArtifactUrl(raw, allowFile) {
2965
4155
  return parsed.href;
2966
4156
  }
2967
4157
  __name(normalizeArtifactUrl, "normalizeArtifactUrl");
2968
- function normalizeMetadata(metadata) {
4158
+ function hasSecretLikeUrlComponent(parsed) {
4159
+ for (const [key, value] of parsed.searchParams) {
4160
+ if (isSecretLikeUrlText(key) || isSecretLikeUrlValue(value)) {
4161
+ return true;
4162
+ }
4163
+ }
4164
+ for (const segment of parsed.pathname.split("/").filter(Boolean)) {
4165
+ let decodedSegment = segment;
4166
+ try {
4167
+ decodedSegment = decodeURIComponent(segment);
4168
+ } catch {
4169
+ }
4170
+ if (isSecretLikeUrlValue(decodedSegment)) {
4171
+ return true;
4172
+ }
4173
+ }
4174
+ const fragment = parsed.hash.slice(1);
4175
+ return hasSecretLikeUrlFragment(fragment);
4176
+ }
4177
+ __name(hasSecretLikeUrlComponent, "hasSecretLikeUrlComponent");
4178
+ function hasSecretLikeUrlFragment(fragment) {
4179
+ if (!fragment) return false;
4180
+ const candidates = /* @__PURE__ */ new Set([fragment]);
4181
+ try {
4182
+ candidates.add(decodeURIComponent(fragment));
4183
+ } catch {
4184
+ }
4185
+ for (const candidate of candidates) {
4186
+ if (isSecretLikeUrlText(candidate) || isSecretLikeUrlValue(candidate)) {
4187
+ return true;
4188
+ }
4189
+ for (const [key, value] of new URLSearchParams(candidate)) {
4190
+ if (isSecretLikeUrlText(key) || isSecretLikeUrlValue(value)) {
4191
+ return true;
4192
+ }
4193
+ }
4194
+ }
4195
+ return false;
4196
+ }
4197
+ __name(hasSecretLikeUrlFragment, "hasSecretLikeUrlFragment");
4198
+ function isSecretLikeUrlText(value) {
4199
+ const normalized = value.replace(/([a-z])([A-Z])/g, "$1-$2");
4200
+ return /(?:^|[-_.])(token|secret|password|passwd|pwd|cookie|authorization|credential|signature|sig|api[-_]?key|access[-_]?key)(?:$|[-_.=&#])/i.test(
4201
+ normalized
4202
+ );
4203
+ }
4204
+ __name(isSecretLikeUrlText, "isSecretLikeUrlText");
4205
+ function isSecretLikeUrlValue(value) {
4206
+ return SECRET_TOKEN_VALUE_PATTERN.test(value.trim());
4207
+ }
4208
+ __name(isSecretLikeUrlValue, "isSecretLikeUrlValue");
4209
+ function isSecretLikeMetadataValue(value) {
4210
+ return /^(?:bearer\s+\S{8,}|sk-[A-Za-z0-9_-]{12,}|(?:gh[pousr]|github_pat)_[A-Za-z0-9_/-]{12,})$/i.test(
4211
+ value.trim()
4212
+ );
4213
+ }
4214
+ __name(isSecretLikeMetadataValue, "isSecretLikeMetadataValue");
4215
+ function normalizeMetadata(metadata, options = {}) {
2969
4216
  if (metadata === void 0) {
2970
4217
  return void 0;
2971
4218
  }
@@ -2977,6 +4224,12 @@ function normalizeMetadata(metadata) {
2977
4224
  }
2978
4225
  const normalized = {};
2979
4226
  for (const [key, value] of Object.entries(metadata)) {
4227
+ if (isPrototypeMetadataKey(key)) {
4228
+ continue;
4229
+ }
4230
+ if (options.budget !== "persisted" && isReservedWorkspaceMetadataKey(key)) {
4231
+ continue;
4232
+ }
2980
4233
  if (!key) {
2981
4234
  throw new SessionArtifactValidationError(
2982
4235
  "metadata keys must not be empty",
@@ -2995,6 +4248,12 @@ function normalizeMetadata(metadata) {
2995
4248
  "metadata"
2996
4249
  );
2997
4250
  }
4251
+ if (isSecretLikeUrlText(key)) {
4252
+ throw new SessionArtifactValidationError(
4253
+ "metadata keys must not contain secret-like names",
4254
+ "metadata"
4255
+ );
4256
+ }
2998
4257
  if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
2999
4258
  throw new SessionArtifactValidationError(
3000
4259
  "metadata values must be primitive",
@@ -3013,12 +4272,18 @@ function normalizeMetadata(metadata) {
3013
4272
  "metadata"
3014
4273
  );
3015
4274
  }
4275
+ if (typeof value === "string" && !isReservedWorkspaceMetadataKey(key) && isSecretLikeMetadataValue(value)) {
4276
+ throw new SessionArtifactValidationError(
4277
+ "metadata string values must not contain secret-like tokens",
4278
+ "metadata"
4279
+ );
4280
+ }
3016
4281
  normalized[key] = value;
3017
4282
  }
3018
4283
  if (Object.keys(normalized).length === 0) {
3019
4284
  return void 0;
3020
4285
  }
3021
- if (Buffer.byteLength(JSON.stringify(normalized), "utf8") > 4096) {
4286
+ if (metadataBudgetBytes(normalized, options.budget ?? "user") > 4096) {
3022
4287
  throw new SessionArtifactValidationError(
3023
4288
  "metadata must be 4096 bytes or fewer",
3024
4289
  "metadata"
@@ -3027,6 +4292,25 @@ function normalizeMetadata(metadata) {
3027
4292
  return normalized;
3028
4293
  }
3029
4294
  __name(normalizeMetadata, "normalizeMetadata");
4295
+ function normalizeRetention(value, options) {
4296
+ if (value === void 0) {
4297
+ return options.persistenceAvailable ? "restorable" : "ephemeral";
4298
+ }
4299
+ if (value === "ephemeral" || value === "restorable") {
4300
+ return value;
4301
+ }
4302
+ if (value === "pinned") {
4303
+ throw new SessionArtifactValidationError(
4304
+ "pinned retention is not supported by session_artifacts_persistence",
4305
+ "retention"
4306
+ );
4307
+ }
4308
+ throw new SessionArtifactValidationError(
4309
+ "retention must be ephemeral or restorable",
4310
+ "retention"
4311
+ );
4312
+ }
4313
+ __name(normalizeRetention, "normalizeRetention");
3030
4314
  function normalizeSizeBytes(value) {
3031
4315
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
3032
4316
  throw new SessionArtifactValidationError(
@@ -3052,21 +4336,83 @@ function inferKind(input) {
3052
4336
  return input.workspacePath ? "file" : "other";
3053
4337
  }
3054
4338
  __name(inferKind, "inferKind");
3055
- async function getWorkspaceStatus(workspaceCwd, workspacePath, realWorkspaceCwd) {
3056
- const absolutePath = path2.resolve(workspaceCwd, workspacePath);
4339
+ async function getWorkspaceStatus(workspacePath, realWorkspaceCwd, expected, options = { hashContent: true }) {
3057
4340
  const realWorkspace = await realWorkspaceCwd;
4341
+ const absolutePath = path2.resolve(realWorkspace, workspacePath);
3058
4342
  try {
3059
4343
  const realPath = await fs2.realpath(absolutePath);
3060
4344
  const relative = path2.relative(realWorkspace, realPath);
3061
4345
  if (!relative || isOutsidePath(relative)) {
3062
4346
  return { status: "missing", escaped: true };
3063
4347
  }
3064
- const stat = await fs2.stat(realPath);
3065
- return {
3066
- status: "available",
3067
- ...stat.isFile() ? { sizeBytes: stat.size } : {}
3068
- };
4348
+ const preOpenStat = await fs2.lstat(realPath);
4349
+ const handle = await fs2.open(
4350
+ realPath,
4351
+ fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW
4352
+ );
4353
+ try {
4354
+ const stat = await handle.stat();
4355
+ if (!isSameFile(preOpenStat, stat)) {
4356
+ return { status: "missing", escaped: true };
4357
+ }
4358
+ if (stat.isFile()) {
4359
+ const expectedMtimeMs = typeof expected?.mtimeMs === "number" ? expected.mtimeMs : void 0;
4360
+ const expectedSha256 = typeof expected?.sha256 === "string" ? expected.sha256 : void 0;
4361
+ const unchanged = expected?.sizeBytes === stat.size && expectedMtimeMs === stat.mtimeMs;
4362
+ const sizeChanged = expected?.sizeBytes !== void 0 && expected.sizeBytes !== stat.size;
4363
+ if (sizeChanged) {
4364
+ return {
4365
+ status: "changed",
4366
+ sizeBytes: stat.size,
4367
+ mtimeMs: stat.mtimeMs
4368
+ };
4369
+ }
4370
+ if (unchanged) {
4371
+ return {
4372
+ status: "available",
4373
+ sizeBytes: stat.size,
4374
+ mtimeMs: stat.mtimeMs
4375
+ };
4376
+ }
4377
+ if (stat.size > MAX_WORKSPACE_HASH_BYTES) {
4378
+ return {
4379
+ status: expectedSha256 ? "changed" : "available",
4380
+ sizeBytes: stat.size,
4381
+ mtimeMs: stat.mtimeMs
4382
+ };
4383
+ }
4384
+ if (!options.hashContent) {
4385
+ return {
4386
+ status: expectedSha256 ? "changed" : "available",
4387
+ sizeBytes: stat.size,
4388
+ mtimeMs: stat.mtimeMs
4389
+ };
4390
+ }
4391
+ const sha256 = await hashFile(handle);
4392
+ if (expectedSha256 && sha256 !== expectedSha256) {
4393
+ return {
4394
+ status: "changed",
4395
+ sizeBytes: stat.size,
4396
+ mtimeMs: stat.mtimeMs
4397
+ };
4398
+ }
4399
+ return {
4400
+ status: "available",
4401
+ sizeBytes: stat.size,
4402
+ mtimeMs: stat.mtimeMs,
4403
+ sha256
4404
+ };
4405
+ }
4406
+ return {
4407
+ status: "available"
4408
+ };
4409
+ } finally {
4410
+ await handle.close();
4411
+ }
3069
4412
  } catch (error) {
4413
+ if (isNoFollowSymlinkError(error)) {
4414
+ return { status: "missing", escaped: true };
4415
+ }
3070
4416
  if (!isNotFoundError(error)) {
3071
4417
  throw error;
3072
4418
  }
@@ -3077,6 +4423,41 @@ async function getWorkspaceStatus(workspaceCwd, workspacePath, realWorkspaceCwd)
3077
4423
  }
3078
4424
  }
3079
4425
  __name(getWorkspaceStatus, "getWorkspaceStatus");
4426
+ function isSameFile(before, after) {
4427
+ return before.dev === after.dev && before.ino === after.ino;
4428
+ }
4429
+ __name(isSameFile, "isSameFile");
4430
+ async function hashFile(handle) {
4431
+ const hash = createHash("sha256");
4432
+ for await (const chunk of handle.createReadStream({ start: 0 })) {
4433
+ hash.update(chunk);
4434
+ }
4435
+ return hash.digest("hex");
4436
+ }
4437
+ __name(hashFile, "hashFile");
4438
+ function withWorkspaceContentHashMetadata(metadata, workspaceStatus) {
4439
+ if (workspaceStatus?.status !== "available" || !workspaceStatus.sha256) {
4440
+ return metadata;
4441
+ }
4442
+ const next = {
4443
+ ...metadata ?? {},
4444
+ [WORKSPACE_CONTENT_SHA256_METADATA_KEY]: workspaceStatus.sha256,
4445
+ ...workspaceStatus.mtimeMs !== void 0 ? { [WORKSPACE_CONTENT_MTIME_MS_METADATA_KEY]: workspaceStatus.mtimeMs } : {}
4446
+ };
4447
+ return next;
4448
+ }
4449
+ __name(withWorkspaceContentHashMetadata, "withWorkspaceContentHashMetadata");
4450
+ function isWorkspaceContentChanged(artifact, status) {
4451
+ if (status.status !== "available") {
4452
+ return false;
4453
+ }
4454
+ const expectedSha256 = artifact.metadata?.[WORKSPACE_CONTENT_SHA256_METADATA_KEY];
4455
+ if (artifact.sizeBytes !== void 0 && status.sizeBytes !== void 0 && status.sizeBytes !== artifact.sizeBytes) {
4456
+ return true;
4457
+ }
4458
+ return typeof expectedSha256 === "string" && status.sha256 !== void 0 && status.sha256 !== expectedSha256;
4459
+ }
4460
+ __name(isWorkspaceContentChanged, "isWorkspaceContentChanged");
3080
4461
  async function danglingSymlinkEscapesWorkspace(absolutePath, realWorkspace) {
3081
4462
  try {
3082
4463
  const stat = await fs2.lstat(absolutePath);
@@ -3109,6 +4490,13 @@ function isNotFoundError(error) {
3109
4490
  return code === "ENOENT" || code === "ENOTDIR";
3110
4491
  }
3111
4492
  __name(isNotFoundError, "isNotFoundError");
4493
+ function isNoFollowSymlinkError(error) {
4494
+ if (typeof error !== "object" || error === null || !("code" in error)) {
4495
+ return false;
4496
+ }
4497
+ return error.code === "ELOOP";
4498
+ }
4499
+ __name(isNoFollowSymlinkError, "isNoFollowSymlinkError");
3112
4500
  function isOutsidePath(relative) {
3113
4501
  return relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative);
3114
4502
  }
@@ -3276,6 +4664,18 @@ function isBridgeAutoMemoryTopic(value) {
3276
4664
  return value === "user" || value === "feedback" || value === "project" || value === "reference";
3277
4665
  }
3278
4666
  __name(isBridgeAutoMemoryTopic, "isBridgeAutoMemoryTopic");
4667
+ function touchedScopesFromTopics(topics) {
4668
+ const scopes = /* @__PURE__ */ new Set();
4669
+ for (const topic of topics) {
4670
+ scopes.add(topic === "user" || topic === "feedback" ? "user" : "project");
4671
+ }
4672
+ return ["user", "project"].filter((scope) => scopes.has(scope));
4673
+ }
4674
+ __name(touchedScopesFromTopics, "touchedScopesFromTopics");
4675
+ function isBridgeMemoryScope(value) {
4676
+ return value === "user" || value === "project";
4677
+ }
4678
+ __name(isBridgeMemoryScope, "isBridgeMemoryScope");
3279
4679
  function parseWorkspaceMemoryForgetMatch(value) {
3280
4680
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
3281
4681
  return null;
@@ -3299,14 +4699,17 @@ function parseWorkspaceMemoryForgetResult(response) {
3299
4699
  const summary = record["summary"];
3300
4700
  const removedEntries = record["removedEntries"];
3301
4701
  const touchedTopics = record["touchedTopics"];
4702
+ const touchedScopes = record["touchedScopes"];
3302
4703
  const parsedRemovedEntries = Array.isArray(removedEntries) ? removedEntries.map(parseWorkspaceMemoryForgetMatch) : [];
3303
- if (summary !== void 0 && typeof summary !== "string" || !Array.isArray(removedEntries) || parsedRemovedEntries.some((entry) => entry === null) || !Array.isArray(touchedTopics) || !touchedTopics.every(isBridgeAutoMemoryTopic)) {
4704
+ if (summary !== void 0 && typeof summary !== "string" || !Array.isArray(removedEntries) || parsedRemovedEntries.some((entry) => entry === null) || !Array.isArray(touchedTopics) || !touchedTopics.every(isBridgeAutoMemoryTopic) || touchedScopes !== void 0 && (!Array.isArray(touchedScopes) || !touchedScopes.every(isBridgeMemoryScope))) {
3304
4705
  throw new Error("Malformed workspace memory forget response");
3305
4706
  }
4707
+ const parsedTouchedTopics = touchedTopics;
3306
4708
  return {
3307
4709
  ...summary === void 0 ? {} : { summary },
3308
4710
  removedEntries: parsedRemovedEntries,
3309
- touchedTopics
4711
+ touchedTopics: parsedTouchedTopics,
4712
+ touchedScopes: touchedScopes === void 0 ? touchedScopesFromTopics(parsedTouchedTopics) : touchedScopes
3310
4713
  };
3311
4714
  }
3312
4715
  __name(parseWorkspaceMemoryForgetResult, "parseWorkspaceMemoryForgetResult");
@@ -3523,6 +4926,27 @@ function createAcpSessionBridge(opts) {
3523
4926
  } else {
3524
4927
  maxSessions = opts.maxSessions;
3525
4928
  }
4929
+ const reserveFreshSession = /* @__PURE__ */ __name((context) => opts.freshSessionAdmission?.(context), "reserveFreshSession");
4930
+ const releaseFreshSessionReservation = /* @__PURE__ */ __name((reservation) => {
4931
+ if (!reservation) return;
4932
+ try {
4933
+ reservation.release();
4934
+ } catch (err) {
4935
+ opts.onDiagnosticLine?.(
4936
+ `qwen serve: fresh session admission release failed: ${err instanceof Error ? err.message : String(err)}`,
4937
+ "warn"
4938
+ );
4939
+ }
4940
+ }, "releaseFreshSessionReservation");
4941
+ const emitSessionLifecycle = /* @__PURE__ */ __name((event) => {
4942
+ try {
4943
+ opts.sessionLifecycle?.(event);
4944
+ } catch (err) {
4945
+ const message = `qwen serve: session lifecycle callback failed: ${err instanceof Error ? err.message : String(err)}`;
4946
+ opts.onDiagnosticLine?.(message, "warn");
4947
+ writeStderrLine(message);
4948
+ }
4949
+ }, "emitSessionLifecycle");
3526
4950
  if (defaultSessionScope !== "single" && defaultSessionScope !== "thread") {
3527
4951
  throw new TypeError(
3528
4952
  `Invalid sessionScope: ${JSON.stringify(defaultSessionScope)}. Expected 'single' or 'thread'.`
@@ -3534,6 +4958,9 @@ function createAcpSessionBridge(opts) {
3534
4958
  `Invalid eventRingSize: ${opts.eventRingSize}. Must be a positive integer in [1, ${MAX_EVENT_RING_SIZE}].`
3535
4959
  );
3536
4960
  }
4961
+ const compactedReplayMaxBytes = normalizeCompactedReplayMaxBytes(
4962
+ opts.compactedReplayMaxBytes
4963
+ );
3537
4964
  const channelFactory = opts.channelFactory ?? defaultSpawnChannelFactory;
3538
4965
  const childEnvOverrides = opts.childEnvOverrides ? Object.freeze({ ...opts.childEnvOverrides }) : Object.freeze({});
3539
4966
  const initTimeoutMs = opts.initializeTimeoutMs ?? DEFAULT_INIT_TIMEOUT_MS;
@@ -3850,7 +5277,11 @@ function createAcpSessionBridge(opts) {
3850
5277
  inputTokens,
3851
5278
  outputTokens,
3852
5279
  durationMs
3853
- )
5280
+ ),
5281
+ // `create_sub_session` tool: forward the request/response hook so a child
5282
+ // tool can ask the daemon to spawn a sub-session and (for 'first-turn')
5283
+ // return its result. Omitted → the method reports daemon-only.
5284
+ opts.onCreateSubSession
3854
5285
  );
3855
5286
  const connection = new ClientSideConnection(() => client, channel.stream);
3856
5287
  const info = {
@@ -3915,6 +5346,12 @@ function createAcpSessionBridge(opts) {
3915
5346
  }
3916
5347
  byId.delete(sid);
3917
5348
  telemetry.metrics?.sessionLifecycle("die");
5349
+ emitSessionLifecycle({
5350
+ type: "removed",
5351
+ sessionId: sid,
5352
+ workspaceCwd: sessEntry.workspaceCwd,
5353
+ reason: "channel_closed"
5354
+ });
3918
5355
  info.client.markSessionClosed(sid);
3919
5356
  if (defaultEntry === sessEntry) defaultEntry = void 0;
3920
5357
  sessEntry.events.close();
@@ -3963,7 +5400,7 @@ function createAcpSessionBridge(opts) {
3963
5400
  }
3964
5401
  }
3965
5402
  __name(ensureChannel, "ensureChannel");
3966
- async function doSpawn(modelServiceId, effectiveScope, requestedClientId) {
5403
+ async function doSpawn(modelServiceId, effectiveScope, requestedClientId, onSessionRegistered) {
3967
5404
  const ci = await ensureChannel();
3968
5405
  ci.sessionSpawnsInFlight++;
3969
5406
  let sessionRegistered = false;
@@ -4004,6 +5441,7 @@ function createAcpSessionBridge(opts) {
4004
5441
  boundWorkspace
4005
5442
  );
4006
5443
  sessionRegistered = true;
5444
+ onSessionRegistered?.();
4007
5445
  seedSnapshotCaches(entry, newSessionResp);
4008
5446
  const clientId = registerClient(entry, requestedClientId);
4009
5447
  if (effectiveScope === "single" && !defaultEntry) defaultEntry = entry;
@@ -4280,7 +5718,18 @@ function createAcpSessionBridge(opts) {
4280
5718
  );
4281
5719
  }
4282
5720
  }, "broadcastWorkspaceEvent");
4283
- const createSessionEventBus = /* @__PURE__ */ __name(() => new EventBus(eventRingSize, void 0, new TurnBoundaryCompactionEngine()), "createSessionEventBus");
5721
+ const createSessionEventBus = /* @__PURE__ */ __name(() => new EventBus(
5722
+ eventRingSize,
5723
+ void 0,
5724
+ new TurnBoundaryCompactionEngine({
5725
+ maxReplayBytes: compactedReplayMaxBytes,
5726
+ onReplayWindowEviction: /* @__PURE__ */ __name((eviction) => {
5727
+ teeServeDebugLine(
5728
+ `replay window evicted ${JSON.stringify(eviction)}`
5729
+ );
5730
+ }, "onReplayWindowEviction")
5731
+ })
5732
+ ), "createSessionEventBus");
4284
5733
  const publishModelSwitched = /* @__PURE__ */ __name((entry, modelId, originatorClientId) => {
4285
5734
  entry.currentModelId = modelId;
4286
5735
  entry.modelPublishGeneration++;
@@ -4369,7 +5818,11 @@ function createAcpSessionBridge(opts) {
4369
5818
  channel: ci.channel,
4370
5819
  connection: ci.connection,
4371
5820
  events,
4372
- artifacts: new SessionArtifactStore({ sessionId, workspaceCwd }),
5821
+ artifacts: new SessionArtifactStore({
5822
+ sessionId,
5823
+ workspaceCwd,
5824
+ persistence: createSessionArtifactPersistence(ci.connection, sessionId)
5825
+ }),
4373
5826
  promptQueue: Promise.resolve(),
4374
5827
  pendingPromptCount: 0,
4375
5828
  pendingPromptList: [],
@@ -4390,6 +5843,12 @@ function createAcpSessionBridge(opts) {
4390
5843
  byId.set(entry.sessionId, entry);
4391
5844
  touchActivity();
4392
5845
  telemetry.metrics?.sessionLifecycle("spawn");
5846
+ emitSessionLifecycle({
5847
+ type: "registered",
5848
+ sessionId: entry.sessionId,
5849
+ workspaceCwd: entry.workspaceCwd,
5850
+ reason: options.lifecycleReason ?? "spawn"
5851
+ });
4393
5852
  if (options.drainEarlyEvents !== false) {
4394
5853
  ci.client.drainEarlyEvents(entry.sessionId, entry);
4395
5854
  }
@@ -4404,6 +5863,42 @@ function createAcpSessionBridge(opts) {
4404
5863
  });
4405
5864
  }
4406
5865
  }, "publishArtifactChanges");
5866
+ const artifactReseedChanges = /* @__PURE__ */ __name((before, after) => {
5867
+ const beforeById = new Map(
5868
+ before.map((artifact) => [artifact.id, artifact])
5869
+ );
5870
+ const afterById = new Map(after.map((artifact) => [artifact.id, artifact]));
5871
+ const changes = [];
5872
+ for (const artifact of before) {
5873
+ if (!afterById.has(artifact.id)) {
5874
+ changes.push({
5875
+ action: "removed",
5876
+ artifactId: artifact.id,
5877
+ artifact,
5878
+ reason: "eviction"
5879
+ });
5880
+ }
5881
+ }
5882
+ for (const artifact of after) {
5883
+ const previous = beforeById.get(artifact.id);
5884
+ if (!previous) {
5885
+ changes.push({
5886
+ action: "created",
5887
+ artifactId: artifact.id,
5888
+ artifact
5889
+ });
5890
+ continue;
5891
+ }
5892
+ if (!publicArtifactsEqual(previous, artifact)) {
5893
+ changes.push({
5894
+ action: "updated",
5895
+ artifactId: artifact.id,
5896
+ artifact
5897
+ });
5898
+ }
5899
+ }
5900
+ return changes;
5901
+ }, "artifactReseedChanges");
4407
5902
  const makeClientArtifactInput = /* @__PURE__ */ __name((artifact, clientId) => {
4408
5903
  const input = {
4409
5904
  title: artifact.title,
@@ -4416,6 +5911,8 @@ function createAcpSessionBridge(opts) {
4416
5911
  mimeType: artifact.mimeType,
4417
5912
  sizeBytes: artifact.sizeBytes,
4418
5913
  metadata: artifact.metadata,
5914
+ retention: artifact.retention,
5915
+ clientRetained: artifact.clientRetained,
4419
5916
  source: "client"
4420
5917
  };
4421
5918
  if (clientId) {
@@ -4423,6 +5920,31 @@ function createAcpSessionBridge(opts) {
4423
5920
  }
4424
5921
  return input;
4425
5922
  }, "makeClientArtifactInput");
5923
+ function createSessionArtifactPersistence(connection, sessionId) {
5924
+ return {
5925
+ recordEvent: /* @__PURE__ */ __name(async (payload) => {
5926
+ await connection.extMethod(
5927
+ SERVE_CONTROL_EXT_METHODS.sessionArtifactsPersist,
5928
+ {
5929
+ sessionId,
5930
+ kind: "event",
5931
+ payload
5932
+ }
5933
+ );
5934
+ }, "recordEvent"),
5935
+ recordSnapshot: /* @__PURE__ */ __name(async (payload) => {
5936
+ await connection.extMethod(
5937
+ SERVE_CONTROL_EXT_METHODS.sessionArtifactsPersist,
5938
+ {
5939
+ sessionId,
5940
+ kind: "snapshot",
5941
+ payload
5942
+ }
5943
+ );
5944
+ }, "recordSnapshot")
5945
+ };
5946
+ }
5947
+ __name(createSessionArtifactPersistence, "createSessionArtifactPersistence");
4426
5948
  const seedSnapshotCaches = /* @__PURE__ */ __name((entry, resp) => {
4427
5949
  const model = resp.models?.currentModelId;
4428
5950
  if (typeof model === "string" && model.length > 0) {
@@ -4470,7 +5992,38 @@ function createAcpSessionBridge(opts) {
4470
5992
  }
4471
5993
  return { lastEventId: snapshot.lastEventId, ...replayStatus };
4472
5994
  }, "replayFieldsFor");
4473
- async function restoreSession(action, req) {
5995
+ const restoredArtifactSnapshotFromState = /* @__PURE__ */ __name((state) => {
5996
+ const candidate = state.artifactSnapshot;
5997
+ const warnings = [];
5998
+ const snapshot = normalizeSnapshotPayload(candidate, warnings);
5999
+ if (!snapshot) return void 0;
6000
+ const snapshotWarnings = isRecord2(candidate) && Array.isArray(candidate["warnings"]) ? candidate["warnings"].filter(
6001
+ (warning) => typeof warning === "string" && warning.length <= 1e3
6002
+ ).slice(-500) : [];
6003
+ return {
6004
+ v: SESSION_ARTIFACT_PERSISTENCE_VERSION,
6005
+ sessionId: snapshot.sessionId,
6006
+ sequence: snapshot.sequence,
6007
+ artifacts: snapshot.artifacts,
6008
+ ...snapshot.markerArtifacts ? { markerArtifacts: snapshot.markerArtifacts } : {},
6009
+ tombstonedIds: snapshot.tombstonedIds ?? [],
6010
+ stickyEphemeralIds: snapshot.stickyEphemeralIds ?? [],
6011
+ warnings: [...warnings, ...snapshotWarnings]
6012
+ };
6013
+ }, "restoredArtifactSnapshotFromState");
6014
+ const artifactSnapshotUnavailableReason = /* @__PURE__ */ __name((state) => {
6015
+ const reason = state.artifactSnapshotUnavailable;
6016
+ return typeof reason === "string" && reason ? reason : void 0;
6017
+ }, "artifactSnapshotUnavailableReason");
6018
+ const publicRestoreState = /* @__PURE__ */ __name((state) => {
6019
+ const {
6020
+ artifactSnapshot: _artifactSnapshot,
6021
+ artifactSnapshotUnavailable: _artifactSnapshotUnavailable,
6022
+ ...publicState
6023
+ } = state;
6024
+ return publicState;
6025
+ }, "publicRestoreState");
6026
+ async function restoreSession(action, req, options = {}) {
4474
6027
  if (shuttingDown) {
4475
6028
  throw new Error("AcpSessionBridge is shutting down");
4476
6029
  }
@@ -4533,6 +6086,17 @@ function createAcpSessionBridge(opts) {
4533
6086
  let registeredEntry;
4534
6087
  let ci;
4535
6088
  const coalesceState = { count: 0 };
6089
+ const admission = options.skipFreshSessionAdmission === true ? void 0 : reserveFreshSession({
6090
+ operation: action,
6091
+ workspaceCwd: workspaceKey,
6092
+ sessionId: req.sessionId
6093
+ });
6094
+ let admissionReleased = false;
6095
+ const releaseAdmissionOnce = /* @__PURE__ */ __name(() => {
6096
+ if (admissionReleased) return;
6097
+ admissionReleased = true;
6098
+ releaseFreshSessionReservation(admission);
6099
+ }, "releaseAdmissionOnce");
4536
6100
  const promise = (async () => {
4537
6101
  pendingRestoreEvents.set(req.sessionId, restoreEvents);
4538
6102
  ci = await ensureChannel();
@@ -4636,18 +6200,39 @@ function createAcpSessionBridge(opts) {
4636
6200
  req.sessionId,
4637
6201
  workspaceKey,
4638
6202
  restoreEvents,
4639
- { drainEarlyEvents: replayUpdates.length === 0 }
6203
+ {
6204
+ drainEarlyEvents: replayUpdates.length === 0,
6205
+ lifecycleReason: action
6206
+ }
4640
6207
  );
4641
- entry.restoreState = state;
6208
+ releaseAdmissionOnce();
6209
+ const restoredArtifactSnapshot = restoredArtifactSnapshotFromState(state);
6210
+ const publicState = publicRestoreState(state);
6211
+ entry.restoreState = publicState;
4642
6212
  if (replayPartial === true) {
4643
6213
  entry.restoreReplayPartial = true;
4644
6214
  }
4645
6215
  if (replayError !== void 0) {
4646
6216
  entry.restoreReplayError = replayError;
4647
6217
  }
4648
- seedSnapshotCaches(entry, state);
6218
+ seedSnapshotCaches(entry, publicState);
6219
+ const artifactRestoreWarnings = await entry.artifacts.restore(
6220
+ restoredArtifactSnapshot
6221
+ );
6222
+ for (const warning of artifactRestoreWarnings) {
6223
+ writeStderrLine(
6224
+ `[artifacts] session=${entry.sessionId} action=restore_warning warning=${JSON.stringify(
6225
+ warning
6226
+ )}`
6227
+ );
6228
+ }
6229
+ const artifactRestoreFailed = artifactRestoreWarnings.some(
6230
+ (warning) => isArtifactRestoreFailureWarning(warning)
6231
+ );
4649
6232
  if (replayUpdates.length > 0) {
4650
- await ci.client.seedSessionUpdates(entry, replayUpdates);
6233
+ await ci.client.seedSessionUpdates(entry, replayUpdates, {
6234
+ ingestArtifacts: restoredArtifactSnapshot === void 0 || artifactRestoreFailed
6235
+ });
4651
6236
  ci.client.drainEarlyEvents(entry.sessionId, entry);
4652
6237
  }
4653
6238
  const clientId = registerClient(entry, req.clientId);
@@ -4659,20 +6244,29 @@ function createAcpSessionBridge(opts) {
4659
6244
  attached: false,
4660
6245
  clientId,
4661
6246
  createdAt: entry.createdAt,
4662
- state,
6247
+ state: publicState,
6248
+ ...artifactRestoreWarnings.length > 0 ? { artifactWarnings: artifactRestoreWarnings } : {},
4663
6249
  hasActivePrompt: entry.promptActive,
4664
6250
  ...replayFieldsFor(entry, action)
4665
6251
  };
4666
6252
  })().finally(async () => {
6253
+ releaseAdmissionOnce();
4667
6254
  ci?.pendingRestoreIds.delete(req.sessionId);
4668
6255
  ci?.client.clearRestoreInFlight(req.sessionId);
4669
6256
  pendingRestoreEvents.delete(req.sessionId);
4670
6257
  if (!registeredEntry) {
4671
6258
  restoreEvents.close();
4672
6259
  let removedRestoreEntry = false;
4673
- if (byId.get(req.sessionId)?.events === restoreEvents) {
6260
+ const restoreEntry = byId.get(req.sessionId);
6261
+ if (restoreEntry?.events === restoreEvents) {
4674
6262
  byId.delete(req.sessionId);
4675
6263
  ci?.sessionIds.delete(req.sessionId);
6264
+ emitSessionLifecycle({
6265
+ type: "removed",
6266
+ sessionId: req.sessionId,
6267
+ workspaceCwd: restoreEntry.workspaceCwd,
6268
+ reason: "restore_failed"
6269
+ });
4676
6270
  removedRestoreEntry = true;
4677
6271
  }
4678
6272
  if (removedRestoreEntry && ci && hasNoChannelWork(ci)) {
@@ -4740,6 +6334,12 @@ function createAcpSessionBridge(opts) {
4740
6334
  }
4741
6335
  byId.delete(sessionId);
4742
6336
  telemetry.metrics?.sessionLifecycle("close");
6337
+ emitSessionLifecycle({
6338
+ type: "removed",
6339
+ sessionId,
6340
+ workspaceCwd: entry.workspaceCwd,
6341
+ reason
6342
+ });
4743
6343
  ci?.client.markSessionClosed(sessionId);
4744
6344
  try {
4745
6345
  entry.events.publish({
@@ -4788,6 +6388,7 @@ function createAcpSessionBridge(opts) {
4788
6388
  maxSessions: maxSessions === Infinity ? null : maxSessions,
4789
6389
  maxPendingPromptsPerSession: maxPendingPromptsPerSession === Infinity ? null : maxPendingPromptsPerSession,
4790
6390
  eventRingSize,
6391
+ compactedReplayMaxBytes,
4791
6392
  channelIdleTimeoutMs: resolvedChannelIdleTimeoutMs(),
4792
6393
  sessionIdleTimeoutMs
4793
6394
  },
@@ -4917,12 +6518,28 @@ function createAcpSessionBridge(opts) {
4917
6518
  if (byId.size + inFlightSpawns.size + inFlightRestores.size >= maxSessions) {
4918
6519
  throw new SessionLimitExceededError(maxSessions);
4919
6520
  }
4920
- const promise = doSpawn(req.modelServiceId, effectiveScope, req.clientId);
6521
+ const admission = reserveFreshSession({
6522
+ operation: "spawn",
6523
+ workspaceCwd: workspaceKey
6524
+ });
6525
+ let admissionReleased = false;
6526
+ const releaseAdmissionOnce = /* @__PURE__ */ __name(() => {
6527
+ if (admissionReleased) return;
6528
+ admissionReleased = true;
6529
+ releaseFreshSessionReservation(admission);
6530
+ }, "releaseAdmissionOnce");
6531
+ const promise = doSpawn(
6532
+ req.modelServiceId,
6533
+ effectiveScope,
6534
+ req.clientId,
6535
+ releaseAdmissionOnce
6536
+ );
4921
6537
  const tracker = effectiveScope === "single" ? workspaceKey : `${workspaceKey}#${randomUUID2()}`;
4922
6538
  inFlightSpawns.set(tracker, promise);
4923
6539
  try {
4924
6540
  return await promise;
4925
6541
  } finally {
6542
+ releaseAdmissionOnce();
4926
6543
  inFlightSpawns.delete(tracker);
4927
6544
  }
4928
6545
  },
@@ -5379,67 +6996,89 @@ function createAcpSessionBridge(opts) {
5379
6996
  if (byId.size + inFlightSpawns.size + inFlightRestores.size >= maxSessions) {
5380
6997
  throw new SessionLimitExceededError(maxSessions);
5381
6998
  }
5382
- const ci = await ensureChannel();
5383
- const result = await withTimeout(
5384
- ci.connection.extMethod(SERVE_CONTROL_EXT_METHODS.sessionBranch, {
5385
- sessionId,
5386
- cwd: boundWorkspace,
5387
- name: req.name
5388
- }),
5389
- initTimeoutMs,
5390
- "branchSession"
5391
- );
5392
- if (!result || typeof result.newSessionId !== "string") {
5393
- throw new Error(
5394
- `branchSession: agent returned invalid response: ${JSON.stringify(result)}`
5395
- );
5396
- }
5397
- const rawBranchName = result.displayName ?? result.title;
5398
- const branchDisplayName = typeof rawBranchName === "string" ? rawBranchName : result.newSessionId.slice(0, 8);
5399
- let restored;
6999
+ const admission = reserveFreshSession({
7000
+ operation: "branch",
7001
+ workspaceCwd: boundWorkspace,
7002
+ sourceSessionId: sessionId
7003
+ });
7004
+ let admissionReleased = false;
7005
+ const releaseAdmissionOnce = /* @__PURE__ */ __name(() => {
7006
+ if (admissionReleased) return;
7007
+ admissionReleased = true;
7008
+ releaseFreshSessionReservation(admission);
7009
+ }, "releaseAdmissionOnce");
5400
7010
  try {
5401
- restored = await restoreSession("load", {
5402
- sessionId: result.newSessionId,
5403
- workspaceCwd: boundWorkspace,
5404
- clientId: context?.clientId
5405
- });
5406
- } catch (restoreErr) {
5407
- writeStderrLine(
5408
- `qwen serve: branchSession load failed for ${result.newSessionId}, attempting cleanup...`
7011
+ const ci = await ensureChannel();
7012
+ const result = await withTimeout(
7013
+ ci.connection.extMethod(SERVE_CONTROL_EXT_METHODS.sessionBranch, {
7014
+ sessionId,
7015
+ cwd: boundWorkspace,
7016
+ name: req.name
7017
+ }),
7018
+ initTimeoutMs,
7019
+ "branchSession"
5409
7020
  );
7021
+ if (!result || typeof result.newSessionId !== "string") {
7022
+ throw new Error(
7023
+ `branchSession: agent returned invalid response: ${JSON.stringify(result)}`
7024
+ );
7025
+ }
7026
+ const rawBranchName = result.displayName ?? result.title;
7027
+ const branchDisplayName = typeof rawBranchName === "string" ? rawBranchName : result.newSessionId.slice(0, 8);
7028
+ let restored;
5410
7029
  try {
5411
- await ci.connection.extMethod(
5412
- SERVE_CONTROL_EXT_METHODS.sessionClose,
5413
- { sessionId: result.newSessionId, cwd: boundWorkspace }
7030
+ restored = await restoreSession(
7031
+ "load",
7032
+ {
7033
+ sessionId: result.newSessionId,
7034
+ workspaceCwd: boundWorkspace,
7035
+ clientId: context?.clientId
7036
+ },
7037
+ {
7038
+ skipFreshSessionAdmission: true
7039
+ }
5414
7040
  );
5415
- } catch (cleanupErr) {
7041
+ releaseAdmissionOnce();
7042
+ } catch (restoreErr) {
5416
7043
  writeStderrLine(
5417
- `qwen serve: branchSession cleanup of ${result.newSessionId} failed: ${cleanupErr instanceof Error ? cleanupErr.message : cleanupErr}`
7044
+ `qwen serve: branchSession load failed for ${result.newSessionId}, attempting cleanup...`
5418
7045
  );
7046
+ try {
7047
+ await ci.connection.extMethod(
7048
+ SERVE_CONTROL_EXT_METHODS.sessionClose,
7049
+ { sessionId: result.newSessionId, cwd: boundWorkspace }
7050
+ );
7051
+ } catch (cleanupErr) {
7052
+ writeStderrLine(
7053
+ `qwen serve: branchSession cleanup of ${result.newSessionId} failed: ${cleanupErr instanceof Error ? cleanupErr.message : cleanupErr}`
7054
+ );
7055
+ }
7056
+ throw restoreErr;
5419
7057
  }
5420
- throw restoreErr;
7058
+ const newEntry = byId.get(result.newSessionId);
7059
+ if (newEntry) newEntry.displayName = branchDisplayName;
7060
+ const eventData = {
7061
+ sourceSessionId: sessionId,
7062
+ newSessionId: result.newSessionId,
7063
+ displayName: branchDisplayName
7064
+ };
7065
+ const branchEnvelope = {
7066
+ type: "session_branched",
7067
+ data: eventData,
7068
+ ...originatorClientId ? { originatorClientId } : {}
7069
+ };
7070
+ newEntry?.events.publish(branchEnvelope);
7071
+ return {
7072
+ ...restored,
7073
+ displayName: branchDisplayName,
7074
+ forkedFrom: {
7075
+ sessionId,
7076
+ displayName: entry.displayName ?? sessionId.slice(0, 8)
7077
+ }
7078
+ };
7079
+ } finally {
7080
+ releaseAdmissionOnce();
5421
7081
  }
5422
- const newEntry = byId.get(result.newSessionId);
5423
- if (newEntry) newEntry.displayName = branchDisplayName;
5424
- const eventData = {
5425
- sourceSessionId: sessionId,
5426
- newSessionId: result.newSessionId,
5427
- displayName: branchDisplayName
5428
- };
5429
- const branchEnvelope = {
5430
- type: "session_branched",
5431
- data: eventData,
5432
- ...originatorClientId ? { originatorClientId } : {}
5433
- };
5434
- newEntry?.events.publish(branchEnvelope);
5435
- return {
5436
- ...restored,
5437
- displayName: branchDisplayName,
5438
- forkedFrom: {
5439
- sessionId,
5440
- displayName: entry.displayName ?? sessionId.slice(0, 8)
5441
- }
5442
- };
5443
7082
  });
5444
7083
  entry.promptQueue = branchResult.then(
5445
7084
  () => void 0,
@@ -5556,9 +7195,10 @@ function createAcpSessionBridge(opts) {
5556
7195
  }
5557
7196
  return { displayName: entry.displayName };
5558
7197
  },
5559
- async getSessionArtifacts(sessionId) {
7198
+ async getSessionArtifacts(sessionId, context) {
5560
7199
  const entry = byId.get(sessionId);
5561
7200
  if (!entry) throw new SessionNotFoundError(sessionId);
7201
+ resolveTrustedClientId(entry, context?.clientId);
5562
7202
  return entry.artifacts.list();
5563
7203
  },
5564
7204
  async addSessionArtifact(sessionId, artifact, context) {
@@ -5566,9 +7206,13 @@ function createAcpSessionBridge(opts) {
5566
7206
  if (!entry) throw new SessionNotFoundError(sessionId);
5567
7207
  const clientId = resolveTrustedClientId(entry, context?.clientId);
5568
7208
  const input = makeClientArtifactInput(artifact, clientId);
5569
- const result = await entry.artifacts.upsertMany([input], { strict: true });
7209
+ const result = await entry.artifacts.upsertMany([input], {
7210
+ validationStrict: true,
7211
+ persistenceStrict: false
7212
+ });
5570
7213
  publishArtifactChanges(entry, result.changes, clientId);
5571
- return result;
7214
+ const warnings = [...result.warnings ?? []];
7215
+ return warnings.length > 0 ? { ...result, warnings } : result;
5572
7216
  },
5573
7217
  async removeSessionArtifact(sessionId, artifactId, context) {
5574
7218
  const entry = byId.get(sessionId);
@@ -5576,7 +7220,8 @@ function createAcpSessionBridge(opts) {
5576
7220
  const clientId = resolveTrustedClientId(entry, context?.clientId);
5577
7221
  const result = await entry.artifacts.remove(artifactId, { clientId });
5578
7222
  publishArtifactChanges(entry, result.changes, clientId);
5579
- return result;
7223
+ const warnings = [...result.warnings ?? []];
7224
+ return warnings.length > 0 ? { ...result, warnings } : result;
5580
7225
  },
5581
7226
  listWorkspaceSessions(workspaceCwd) {
5582
7227
  if (!path3.isAbsolute(workspaceCwd)) return [];
@@ -6554,6 +8199,48 @@ function createAcpSessionBridge(opts) {
6554
8199
  const targetTurnIndex = response["targetTurnIndex"] ?? 0;
6555
8200
  const filesChanged = response["filesChanged"] ?? [];
6556
8201
  const filesFailed = response["filesFailed"] ?? [];
8202
+ const artifactSnapshot = restoredArtifactSnapshotFromState(
8203
+ response
8204
+ );
8205
+ const artifactSnapshotUnavailable = artifactSnapshotUnavailableReason(
8206
+ response
8207
+ );
8208
+ const beforeArtifacts = (await entry.artifacts.list()).artifacts;
8209
+ const shouldRestoreArtifactSnapshot = artifactSnapshot !== void 0 && artifactSnapshotUnavailable === void 0;
8210
+ const artifactRestoreWarnings = artifactSnapshotUnavailable !== void 0 ? [
8211
+ `artifact snapshot rebuild unavailable during rewind: ${artifactSnapshotUnavailable}`
8212
+ ] : shouldRestoreArtifactSnapshot ? await entry.artifacts.restore(artifactSnapshot, {
8213
+ preserveLiveEphemeral: true
8214
+ }) : [];
8215
+ const artifactRestoreFailed = artifactRestoreWarnings.some(
8216
+ isArtifactRestoreFailureWarning
8217
+ );
8218
+ const shouldRecordArtifactSnapshot = shouldRestoreArtifactSnapshot && !artifactRestoreFailed;
8219
+ const artifactSnapshotWarnings = shouldRecordArtifactSnapshot ? await entry.artifacts.recordSnapshot() : [];
8220
+ const artifactWarnings = [
8221
+ ...artifactRestoreWarnings,
8222
+ ...artifactSnapshotWarnings
8223
+ ];
8224
+ for (const warning of artifactRestoreWarnings) {
8225
+ writeStderrLine(
8226
+ `[artifacts] session=${entry.sessionId} action=rewind_restore_warning warning=${JSON.stringify(
8227
+ warning
8228
+ )}`
8229
+ );
8230
+ }
8231
+ for (const warning of artifactSnapshotWarnings) {
8232
+ writeStderrLine(
8233
+ `[artifacts] session=${entry.sessionId} action=rewind_snapshot_warning warning=${JSON.stringify(
8234
+ warning
8235
+ )}`
8236
+ );
8237
+ }
8238
+ const afterArtifacts = (await entry.artifacts.list()).artifacts;
8239
+ publishArtifactChanges(
8240
+ entry,
8241
+ artifactReseedChanges(beforeArtifacts, afterArtifacts),
8242
+ originatorClientId
8243
+ );
6557
8244
  try {
6558
8245
  entry.events.publish({
6559
8246
  type: "session_rewound",
@@ -6562,7 +8249,8 @@ function createAcpSessionBridge(opts) {
6562
8249
  promptId: req.promptId,
6563
8250
  targetTurnIndex,
6564
8251
  filesChanged,
6565
- filesFailed
8252
+ filesFailed,
8253
+ ...artifactWarnings.length > 0 ? { warnings: artifactWarnings } : {}
6566
8254
  },
6567
8255
  ...originatorClientId ? { originatorClientId } : {}
6568
8256
  });
@@ -6572,7 +8260,8 @@ function createAcpSessionBridge(opts) {
6572
8260
  rewound: filesFailed.length === 0,
6573
8261
  targetTurnIndex,
6574
8262
  filesChanged,
6575
- filesFailed
8263
+ filesFailed,
8264
+ ...artifactWarnings.length > 0 ? { warnings: artifactWarnings } : {}
6576
8265
  };
6577
8266
  },
6578
8267
  async manageMcpServer(serverName, action, originatorClientId) {
@@ -6708,6 +8397,12 @@ function createAcpSessionBridge(opts) {
6708
8397
  if (defaultEntry === entry) defaultEntry = void 0;
6709
8398
  byId.delete(sessionId);
6710
8399
  telemetry.metrics?.sessionLifecycle("die");
8400
+ emitSessionLifecycle({
8401
+ type: "removed",
8402
+ sessionId,
8403
+ workspaceCwd: entry.workspaceCwd,
8404
+ reason: "killed"
8405
+ });
6711
8406
  const ci = channelInfoForEntry(entry);
6712
8407
  if (!ci) {
6713
8408
  writeStderrLine(
@@ -6757,8 +8452,17 @@ function createAcpSessionBridge(opts) {
6757
8452
  cancelIdleTimer();
6758
8453
  stopSessionReaper();
6759
8454
  const channels = Array.from(aliveChannels);
8455
+ const entries = Array.from(byId.values());
6760
8456
  defaultEntry = void 0;
6761
8457
  byId.clear();
8458
+ for (const entry of entries) {
8459
+ emitSessionLifecycle({
8460
+ type: "removed",
8461
+ sessionId: entry.sessionId,
8462
+ workspaceCwd: entry.workspaceCwd,
8463
+ reason: "kill_all"
8464
+ });
8465
+ }
6762
8466
  for (const info of channels) {
6763
8467
  try {
6764
8468
  info.channel.killSync();
@@ -6781,6 +8485,12 @@ function createAcpSessionBridge(opts) {
6781
8485
  byId.clear();
6782
8486
  for (const e of entries) {
6783
8487
  telemetry.metrics?.sessionLifecycle("die");
8488
+ emitSessionLifecycle({
8489
+ type: "removed",
8490
+ sessionId: e.sessionId,
8491
+ workspaceCwd: e.workspaceCwd,
8492
+ reason: "daemon_shutdown"
8493
+ });
6784
8494
  try {
6785
8495
  e.events.publish({
6786
8496
  type: "session_died",
@@ -6842,6 +8552,7 @@ var createHttpAcpBridge = createAcpSessionBridge;
6842
8552
 
6843
8553
  export {
6844
8554
  SessionArtifactValidationError,
8555
+ SessionArtifactAuthorizationError,
6845
8556
  extractErrorMessage,
6846
8557
  extractErrorCode,
6847
8558
  classifyTurnErrorKind,