@qwen-code/qwen-code 0.19.7 → 0.19.8-nightly.20260711.0ef3a76bd

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 (434) 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 +60 -51
  5. package/bundled/qc-helper/docs/features/_meta.ts +1 -0
  6. package/bundled/qc-helper/docs/features/channels/dingtalk.md +12 -2
  7. package/bundled/qc-helper/docs/features/channels/overview.md +86 -12
  8. package/bundled/qc-helper/docs/features/channels/plugins.md +1 -0
  9. package/bundled/qc-helper/docs/features/channels/wecom.md +6 -0
  10. package/bundled/qc-helper/docs/features/code-review.md +29 -5
  11. package/bundled/qc-helper/docs/features/commands.md +39 -38
  12. package/bundled/qc-helper/docs/features/computer-use.md +77 -0
  13. package/bundled/qc-helper/docs/features/hooks.md +36 -34
  14. package/bundled/qc-helper/docs/features/memory.md +1 -1
  15. package/bundled/qc-helper/docs/features/tool-use-summaries.md +19 -23
  16. package/bundled/qc-helper/docs/integration-vscode.md +2 -2
  17. package/bundled/qc-helper/docs/qwen-serve.md +40 -16
  18. package/bundled/qc-helper/docs/reference/keyboard-shortcuts.md +1 -1
  19. package/bundled/review/DESIGN.md +93 -22
  20. package/bundled/review/SKILL.md +300 -87
  21. package/chunks/MaxSizedBox-4CX4IQWE.js +70 -0
  22. package/chunks/{StandaloneSessionPicker-APISIRJZ.js → StandaloneSessionPicker-TYUSSNJ6.js} +79 -77
  23. package/chunks/{acpAgent-MMOSC2YA.js → acpAgent-5HJEXOPR.js} +978 -205
  24. package/chunks/agent-PZF5IUUB.js +68 -0
  25. package/chunks/agent-headless-L4N7V2SG.js +62 -0
  26. package/chunks/{anthropicContentGenerator-AKYPSZIV.js → anthropicContentGenerator-L73TO3DB.js} +214 -31
  27. package/chunks/{artifact-tool-D233YJFH.js → artifact-tool-U7IRTLIV.js} +5 -5
  28. package/chunks/{askUserQuestion-MKL5ZTMC.js → askUserQuestion-2DLUUZZG.js} +6 -6
  29. package/chunks/bridge-OJL6LD42.js +78 -0
  30. package/chunks/{ca-HCMB2I54.js → ca-WTQOU43B.js} +91 -6
  31. package/chunks/{channel-worker-supervisor-CRCVYQIE.js → channel-worker-supervisor-LF66HCVT.js} +137 -13
  32. package/chunks/{chunk-QQDPRDVW.js → chunk-2HRYPZT5.js} +1 -1
  33. package/chunks/{chunk-JD33UY7E.js → chunk-2K56LTWA.js} +18 -12
  34. package/chunks/{chunk-XISQIIT7.js → chunk-2LV4JUNO.js} +4 -4
  35. package/chunks/chunk-2UU6DW5P.js +296 -0
  36. package/chunks/{chunk-2PSWA5ID.js → chunk-2X56YUSZ.js} +1 -1
  37. package/chunks/chunk-2ZQOQZIS.js +71 -0
  38. package/chunks/{chunk-Q6OEPOOC.js → chunk-3AMVHPEQ.js} +2 -2
  39. package/chunks/{chunk-4RQIB5UZ.js → chunk-3BTRNWWD.js} +1 -35
  40. package/chunks/{chunk-SD4N3QJ2.js → chunk-3QZ6GY6A.js} +66 -22
  41. package/chunks/{chunk-DNV4FSN5.js → chunk-3SJSQXNS.js} +24 -7
  42. package/chunks/{chunk-VQWAZSXN.js → chunk-3XATDZNP.js} +64 -42
  43. package/chunks/{chunk-LEIJKM7Q.js → chunk-3ZAIGBCP.js} +2 -2
  44. package/chunks/{chunk-2NPS2UDH.js → chunk-43JB7OF4.js} +905 -139
  45. package/chunks/chunk-43XJTCHT.js +444 -0
  46. package/chunks/{chunk-CNSF3WO5.js → chunk-47HXCVGG.js} +6 -6
  47. package/chunks/{chunk-VEB3NYSO.js → chunk-4GORAUCN.js} +8 -3
  48. package/chunks/{chunk-55ZMG67I.js → chunk-4LD5APFL.js} +4 -4
  49. package/chunks/{chunk-TLWA7TPR.js → chunk-4QLQE5WJ.js} +2 -2
  50. package/chunks/{chunk-UIDXQNMV.js → chunk-4WGODMVR.js} +1 -1
  51. package/chunks/{chunk-E6US47KI.js → chunk-4WKAA4KS.js} +1 -1
  52. package/chunks/{chunk-H6EMK6QK.js → chunk-4ZVWQHEQ.js} +11913 -5207
  53. package/chunks/{chunk-HW4N45EE.js → chunk-524RQP4Q.js} +29 -8
  54. package/chunks/{chunk-3GONHQOA.js → chunk-57OAHC2Y.js} +1 -1
  55. package/chunks/{chunk-B4U2M63D.js → chunk-5DOUQHL3.js} +2104 -14
  56. package/chunks/{chunk-JZSY4WP3.js → chunk-5DTTYNB7.js} +1 -1
  57. package/chunks/{chunk-A4BMJM77.js → chunk-5O2XNYP6.js} +1 -0
  58. package/chunks/{chunk-P7I3D7FM.js → chunk-5OIXALQ5.js} +2 -2
  59. package/chunks/chunk-5SFOMFG4.js +61 -0
  60. package/chunks/{chunk-QYNNN7BK.js → chunk-5XUCZNSY.js} +2 -2
  61. package/chunks/{chunk-FRFFNYUY.js → chunk-65F3I3PM.js} +5 -5
  62. package/chunks/{chunk-GO6LNQXT.js → chunk-6AM5D4ME.js} +1 -1
  63. package/chunks/{chunk-BWVQN2HV.js → chunk-6CR73ZIK.js} +7 -7
  64. package/chunks/{chunk-BJBWRCSK.js → chunk-6H67XLET.js} +1 -1
  65. package/chunks/{chunk-G7ZG7CA6.js → chunk-6RLROE6G.js} +9 -8
  66. package/chunks/{chunk-TBAZMRW3.js → chunk-6WH2WCSW.js} +2 -2
  67. package/chunks/{chunk-ZERZSAZL.js → chunk-75DOP5OR.js} +2 -2
  68. package/chunks/{chunk-FTFXDZYI.js → chunk-7KZNLIAI.js} +47 -31
  69. package/chunks/{chunk-HJ3GX3Y3.js → chunk-7PW5CTVR.js} +32 -18
  70. package/chunks/{chunk-NQKLOAVJ.js → chunk-7R3CZA3Q.js} +2 -2
  71. package/chunks/{chunk-SAIILPQ3.js → chunk-7X7TEQNX.js} +5 -3
  72. package/chunks/{chunk-IRJWHZWU.js → chunk-A4UCTEB5.js} +2 -2
  73. package/chunks/{chunk-XSAFNANZ.js → chunk-AHUEU3BP.js} +14 -14
  74. package/chunks/{chunk-X3YGAX7V.js → chunk-B5ORX3FG.js} +4 -2
  75. package/chunks/{chunk-ZN5T4BHI.js → chunk-BPZALHVR.js} +2 -2
  76. package/chunks/{chunk-IJRL2S3D.js → chunk-BZIFY5TF.js} +6 -6
  77. package/chunks/{chunk-AJBEAUIS.js → chunk-BZP7HZTK.js} +7 -6
  78. package/chunks/{chunk-OFEVLU4C.js → chunk-CARU2RR2.js} +1 -1
  79. package/chunks/{chunk-MIG5SQNL.js → chunk-CBKR4Q5K.js} +8 -8
  80. package/chunks/chunk-CC2ITGCF.js +176 -0
  81. package/chunks/{chunk-FV7425LN.js → chunk-CEA3E3JB.js} +18 -5
  82. package/chunks/{chunk-GD236YPV.js → chunk-CNOF2IZT.js} +3 -3
  83. package/chunks/chunk-COH43MRJ.js +34 -0
  84. package/chunks/{chunk-E5A7LHNN.js → chunk-CPBF7KYF.js} +1 -1
  85. package/chunks/{chunk-UWCTAVOD.js → chunk-CR3C7WXL.js} +1 -1
  86. package/chunks/{chunk-5P5XGNYH.js → chunk-CU3L64TP.js} +1 -1
  87. package/chunks/{chunk-IWKSG2AR.js → chunk-CXAOG665.js} +1 -1
  88. package/chunks/{chunk-MZ7BABX3.js → chunk-DC5Z3DU6.js} +2 -2
  89. package/chunks/{chunk-64WXLC72.js → chunk-DJ2GSRLV.js} +1 -1
  90. package/chunks/{chunk-4ZJIPVPB.js → chunk-DMN532MJ.js} +11 -11
  91. package/chunks/{chunk-QN5NZ3UQ.js → chunk-DMTGGOSA.js} +2 -2
  92. package/chunks/{chunk-KHDZHZMH.js → chunk-E5Z2AVNV.js} +1 -1
  93. package/chunks/chunk-F4PFZ7WP.js +239 -0
  94. package/chunks/{chunk-ME3YSROJ.js → chunk-FBSBT7JS.js} +25 -13
  95. package/chunks/{chunk-5IFG2VC4.js → chunk-FCMNKVXT.js} +1 -1
  96. package/chunks/{chunk-YDSYD7UI.js → chunk-FD3DA3B4.js} +3 -3
  97. package/chunks/{chunk-GL5TV7B5.js → chunk-GL22M2DU.js} +23 -7
  98. package/chunks/{chunk-AFOLR6YL.js → chunk-GQ4ZEYMI.js} +102 -16
  99. package/chunks/chunk-GRC5HGPI.js +13 -0
  100. package/chunks/{chunk-WMPVYQ4P.js → chunk-GWHYEOV2.js} +2 -2
  101. package/chunks/chunk-GZRTYZ2R.js +292 -0
  102. package/chunks/{chunk-A2X3KLC3.js → chunk-GZXCZHCT.js} +3 -3
  103. package/chunks/{chunk-K5PGHDBN.js → chunk-H6XPXXMH.js} +1 -1
  104. package/chunks/{chunk-24XYI5EI.js → chunk-HACY3IUM.js} +6 -6
  105. package/chunks/{chunk-DGHJTO2K.js → chunk-HIHQ3TWC.js} +38 -9
  106. package/chunks/{chunk-KYMBIKIW.js → chunk-HTHUX2T5.js} +1 -1
  107. package/chunks/{chunk-LQ7TMOCE.js → chunk-HTO4JFDZ.js} +1 -1
  108. package/chunks/{chunk-B34OV7K2.js → chunk-IAVB3HM3.js} +15 -2
  109. package/chunks/{chunk-4UCTWSTP.js → chunk-IDJEZU7C.js} +2 -2
  110. package/chunks/chunk-IEBIKN6A.js +38 -0
  111. package/chunks/{chunk-PRDFOIZF.js → chunk-IJEO2QSK.js} +73 -30
  112. package/chunks/{chunk-MJI36HER.js → chunk-IT6LIIVK.js} +2 -2
  113. package/chunks/{chunk-FEJ2FZ3U.js → chunk-J3JA76CF.js} +2 -2
  114. package/chunks/{chunk-4CGOWHKM.js → chunk-J4MOFEOK.js} +2 -2
  115. package/chunks/{chunk-22MNNNXE.js → chunk-J4TSQ5EQ.js} +3 -2
  116. package/chunks/{chunk-22IFUCVR.js → chunk-JESCGQM3.js} +1 -1
  117. package/chunks/{chunk-CCGOFQV3.js → chunk-JSG7ZSBC.js} +8 -7
  118. package/chunks/{chunk-X265R5C6.js → chunk-K434OPES.js} +5 -5
  119. package/chunks/{chunk-RFIL6REB.js → chunk-KHUQZZJ6.js} +185 -45
  120. package/chunks/{chunk-X2XNQQJI.js → chunk-KKNMOEZH.js} +4 -4
  121. package/chunks/{chunk-6BN7NZ5Z.js → chunk-KL23O5Z5.js} +7 -9
  122. package/chunks/{chunk-MRO43B25.js → chunk-KW7NOTN6.js} +1 -1
  123. package/chunks/{chunk-6IAWPIQ6.js → chunk-L36P3NBD.js} +3 -3
  124. package/chunks/{chunk-54YZOP7P.js → chunk-L573Z2QZ.js} +11894 -5190
  125. package/chunks/{chunk-STAZRKOT.js → chunk-L6GPKRH7.js} +132 -464
  126. package/chunks/{chunk-QAKFYMJI.js → chunk-LD5OWZ5E.js} +155 -19
  127. package/chunks/{chunk-E7KNELIZ.js → chunk-LK7KUE55.js} +2 -2
  128. package/chunks/{chunk-W7EEOR7D.js → chunk-LUODI7WD.js} +5 -5
  129. package/chunks/{chunk-CSOTA7KX.js → chunk-LXNEJD34.js} +2 -2
  130. package/chunks/{chunk-HLKG6CZN.js → chunk-M5GB774H.js} +1 -1
  131. package/chunks/{chunk-3B26UVCX.js → chunk-MC23XKFE.js} +39 -10
  132. package/chunks/{chunk-XPJH4PKR.js → chunk-MPEMTBUZ.js} +2 -2
  133. package/chunks/{chunk-GMOWQOQQ.js → chunk-MUCWGT7O.js} +248 -55
  134. package/chunks/{chunk-PC4ZXHU2.js → chunk-MXL5UP4X.js} +3 -3
  135. package/chunks/{chunk-PUQLHSQ2.js → chunk-N2FGNKOE.js} +10 -12
  136. package/chunks/{chunk-332PWN27.js → chunk-NHDKSOEY.js} +1 -1
  137. package/chunks/{chunk-65CFHNSE.js → chunk-NKHV3OHY.js} +62 -62
  138. package/chunks/{chunk-P7M4GA57.js → chunk-OCPBI7J5.js} +1 -1
  139. package/chunks/{chunk-42XGFRJS.js → chunk-ORBGV7NJ.js} +5 -5
  140. package/chunks/{chunk-ALZ75L2X.js → chunk-OZJLTZKP.js} +415 -379
  141. package/chunks/chunk-P3X7RYUQ.js +468 -0
  142. package/chunks/{chunk-AGHU5VRK.js → chunk-PZL23GTM.js} +1 -1
  143. package/chunks/{chunk-FXGLL2HL.js → chunk-Q7YDKOGY.js} +1 -1
  144. package/chunks/{chunk-LGFAYIKX.js → chunk-QHTIBUWB.js} +1 -1
  145. package/chunks/{chunk-AIY5UTFI.js → chunk-QK2JET6S.js} +934 -2881
  146. package/chunks/{chunk-P3MYAZBY.js → chunk-QMGX2KO2.js} +1 -1
  147. package/chunks/{chunk-QZF5U5KA.js → chunk-QNTUV3PP.js} +11 -11
  148. package/chunks/chunk-QRUIS3DN.js +47 -0
  149. package/chunks/{chunk-ITOGNELQ.js → chunk-QSWDGAIC.js} +2 -2
  150. package/chunks/{chunk-7JGISHHZ.js → chunk-QUJIDGIW.js} +15 -157
  151. package/chunks/{chunk-GHOTR7HL.js → chunk-QUPXZXLV.js} +1 -1
  152. package/chunks/{chunk-TDGF35EN.js → chunk-QYMQSECS.js} +3 -3
  153. package/chunks/{chunk-QYUE6W3T.js → chunk-RDJSGHZE.js} +1 -1
  154. package/chunks/{chunk-TLRYABYP.js → chunk-RKUWKYED.js} +1 -1
  155. package/chunks/{chunk-ADZG46HR.js → chunk-RRNR5U2U.js} +2 -2
  156. package/chunks/{chunk-4O344UEE.js → chunk-RW7CXRUM.js} +39 -19
  157. package/chunks/{chunk-RNBYOUGV.js → chunk-RXFQM6FQ.js} +1 -1
  158. package/chunks/{chunk-QXPI7FEC.js → chunk-S3TWXGKA.js} +4 -4
  159. package/chunks/{chunk-44NROQYV.js → chunk-SJHDYNHF.js} +4 -4
  160. package/chunks/{chunk-MLZQVCF3.js → chunk-SZRN75WQ.js} +1 -1
  161. package/chunks/{chunk-7RYW5LQV.js → chunk-T2NCM2ET.js} +2 -2
  162. package/chunks/{chunk-Z2Z3GUXZ.js → chunk-TB6UDU4T.js} +1 -1
  163. package/chunks/{chunk-SUAKHHK7.js → chunk-TDEXZKMT.js} +3 -3
  164. package/chunks/{chunk-EXPMGZZV.js → chunk-TEGEBB2I.js} +1 -1
  165. package/chunks/{chunk-JF7DFPQ5.js → chunk-TJCEUA3S.js} +15 -3
  166. package/chunks/{chunk-MX2YXRER.js → chunk-TJTKSVIV.js} +1 -1
  167. package/chunks/{chunk-H6BD2ELD.js → chunk-TYAMGABM.js} +2 -2
  168. package/chunks/{chunk-CISB3IQF.js → chunk-U3VSCGEJ.js} +100 -15
  169. package/chunks/{chunk-VRDMOSQS.js → chunk-UDXSXLER.js} +2 -2
  170. package/chunks/{chunk-AHNJGLFV.js → chunk-UOZWKTQG.js} +3 -3
  171. package/chunks/{chunk-CJCKDMWO.js → chunk-URK542T4.js} +1 -1
  172. package/chunks/{chunk-YEGT55NW.js → chunk-UUVCHN62.js} +5697 -2983
  173. package/chunks/{chunk-AHNXYU4O.js → chunk-V6WHFGST.js} +1 -1
  174. package/chunks/{chunk-U4FRHCLA.js → chunk-VAUPLGDZ.js} +2370 -364
  175. package/chunks/{chunk-ZTQ26VBE.js → chunk-VDDGDZMA.js} +1 -1
  176. package/chunks/{chunk-OMX7CUOE.js → chunk-VGC4I5JJ.js} +1 -1
  177. package/chunks/{chunk-IUE7YWC3.js → chunk-VIUKEWUL.js} +8 -8
  178. package/chunks/{chunk-43W4YSO6.js → chunk-VJNBRRZG.js} +2 -2
  179. package/chunks/{chunk-I4A4K72C.js → chunk-VL7GBUQA.js} +1309 -703
  180. package/chunks/{chunk-VZ7HY3YU.js → chunk-W2UIXQDL.js} +3 -3
  181. package/chunks/{chunk-ER3BKOLB.js → chunk-W7FJ3N32.js} +1 -1
  182. package/chunks/{chunk-3HX5LZ6R.js → chunk-WBL3FJEU.js} +2 -2
  183. package/chunks/{chunk-RYFZCUJO.js → chunk-WCUK2QXK.js} +17 -9
  184. package/chunks/{chunk-OWVIH4HY.js → chunk-WFHJDT4S.js} +421 -114
  185. package/chunks/{chunk-BMFMJINR.js → chunk-WL4L7EZH.js} +1 -1
  186. package/chunks/{chunk-NZEGNV4V.js → chunk-WPHYXKFL.js} +8 -51
  187. package/chunks/chunk-X4NKFGFJ.js +81 -0
  188. package/chunks/{chunk-3RW4AZJV.js → chunk-XFSFZCM5.js} +1 -1
  189. package/chunks/{chunk-2MPVVENX.js → chunk-XU457J4V.js} +2 -2
  190. package/chunks/{chunk-SIUQ3YYX.js → chunk-XVNQMZ2I.js} +1 -1
  191. package/chunks/{chunk-SYCJMSIJ.js → chunk-XWRJCPHC.js} +1 -1
  192. package/chunks/chunk-YAF2MQZ6.js +19 -0
  193. package/chunks/chunk-YF4RZPJP.js +168 -0
  194. package/chunks/{chunk-4KJ2BPIT.js → chunk-YLV3XXFB.js} +4 -40
  195. package/chunks/{chunk-BR4QREVK.js → chunk-YQ3U5MUC.js} +1 -1
  196. package/chunks/{chunk-Y6Z2O3WR.js → chunk-YUZI3WAC.js} +1 -1
  197. package/chunks/{chunk-II73RK2S.js → chunk-YVEDCAUE.js} +1 -1
  198. package/chunks/{chunk-SKAZWEV5.js → chunk-ZJE7NSL3.js} +1 -1
  199. package/chunks/{chunk-7LTB54MK.js → chunk-ZLXQN2QS.js} +2 -2
  200. package/chunks/cli-entry-path-4VJ3Y6T2.js +10 -0
  201. package/chunks/{computer-use-X4KUU3NM.js → computer-use-2ZTYDNN4.js} +50 -50
  202. package/chunks/config-utils-ZITWCFBY.js +23 -0
  203. package/chunks/contextCommand-6L4XCZZT.js +68 -0
  204. package/chunks/create-sub-session-INX7TXJI.js +207 -0
  205. package/chunks/create-sub-session-VZIOFS6N.js +375 -0
  206. package/chunks/{cron-create-JSXEIPVS.js → cron-create-AMBKGZX6.js} +9 -9
  207. package/chunks/{cron-delete-OIFVEPGU.js → cron-delete-LRU4OJYG.js} +7 -7
  208. package/chunks/{cron-list-D4ZX4IOE.js → cron-list-WVP2JXER.js} +14 -10
  209. package/chunks/{daemon-F3I2RXP6.js → daemon-QEDXW7ER.js} +554 -4
  210. package/chunks/daemon-status-provider-7XJZ3HPS.js +76 -0
  211. package/chunks/{de-PLLFDERI.js → de-XHBEYUMG.js} +91 -6
  212. package/chunks/{devtools-FM6GJPYG.js → devtools-4QFYJT6U.js} +3 -3
  213. package/chunks/{dist-573HNSVO.js → dist-EFXT7A3G.js} +12 -11
  214. package/chunks/{dist-PKFTNUHO.js → dist-KCRX7TGL.js} +529 -79
  215. package/chunks/{dist-OT7G2WNM.js → dist-MFEAEUV4.js} +1011 -197
  216. package/chunks/{dist-63IS3ZMI.js → dist-PZV5RKM6.js} +4 -4
  217. package/chunks/{dist-FQ5WRJPL.js → dist-QY3THJM5.js} +5 -5
  218. package/chunks/{dist-IMFFBMAT.js → dist-UJOR6KEQ.js} +32 -15
  219. package/chunks/{dist-YTRKAVUB.js → dist-ZWZB4CCN.js} +10 -10
  220. package/chunks/earlyInputCapture-TTWXFIWO.js +69 -0
  221. package/chunks/{edit-WZTZDGCI.js → edit-6UWFRZKY.js} +51 -51
  222. package/chunks/{en-GA2RMXY5.js → en-TKHUFIK7.js} +100 -6
  223. package/chunks/{enter-worktree-DEPXBJEL.js → enter-worktree-LS7SGAII.js} +50 -50
  224. package/chunks/{enterPlanMode-JFGXZHHJ.js → enterPlanMode-TSZYLEEP.js} +66 -51
  225. package/chunks/environment-XCVLHOXG.js +88 -0
  226. package/chunks/errors-KIANLUTC.js +75 -0
  227. package/chunks/{exit-worktree-Y7XBK3VV.js → exit-worktree-2QV6UJSW.js} +50 -50
  228. package/chunks/{exitPlanMode-WQQER65F.js → exitPlanMode-GVZG3F6S.js} +51 -51
  229. package/chunks/{fast-path-W2U6XDNO.js → fast-path-GNFZAMQK.js} +16 -7
  230. package/chunks/{fast-path-settings-RCRXH2JG.js → fast-path-settings-5QZE64HR.js} +6 -5
  231. package/chunks/{fileFromPath-IBEHA3CO.js → fileFromPath-UDALK7FM.js} +3 -3
  232. package/chunks/{fr-GWNQG4XH.js → fr-TBF6JZNE.js} +91 -6
  233. package/chunks/{gemini-T4WX6SRW.js → gemini-N3H6OLHR.js} +501 -197
  234. package/chunks/{geminiContentGenerator-MKQACXEJ.js → geminiContentGenerator-4C257R2N.js} +9 -9
  235. package/chunks/{getMachineId-bsd-4CASPIU4.js → getMachineId-bsd-EXV7SWPA.js} +3 -3
  236. package/chunks/{getMachineId-darwin-HPQPEMZR.js → getMachineId-darwin-IUOMFXU3.js} +3 -3
  237. package/chunks/{getMachineId-linux-AUARKYHL.js → getMachineId-linux-K7XJYFHL.js} +2 -2
  238. package/chunks/{getMachineId-unsupported-S32ZDA2T.js → getMachineId-unsupported-EA5FDRJ6.js} +2 -2
  239. package/chunks/{getMachineId-win-4EFLHYIJ.js → getMachineId-win-KBA6RAI5.js} +3 -3
  240. package/chunks/{glob-4KKSHZDF.js → glob-QTUI5I3Y.js} +95 -74
  241. package/chunks/{grep-GHRYXSLZ.js → grep-Z5MGLRCG.js} +50 -50
  242. package/chunks/handleAutoUpdate-FFF3CZGP.js +69 -0
  243. package/chunks/i18n-QQAJT7A2.js +84 -0
  244. package/chunks/initializer-LGQGC7ZT.js +71 -0
  245. package/chunks/installationInfo-4FEZGXIN.js +65 -0
  246. package/chunks/{ja-2HOTA47X.js → ja-CDXUA2H3.js} +92 -7
  247. package/chunks/{keychain-token-storage-UJV5XP4Z.js → keychain-token-storage-VKUBNCP4.js} +4 -4
  248. package/chunks/{kittyProtocolDetector-OJRCJLIU.js → kittyProtocolDetector-HCNNKJTI.js} +2 -2
  249. package/chunks/list-KIVFF3BB.js +74 -0
  250. package/chunks/loadedSettingsAdapter-EOD3RVZS.js +68 -0
  251. package/chunks/{loop-wakeup-OHWEMFP5.js → loop-wakeup-WX7BLWWP.js} +10 -10
  252. package/chunks/{lowlight-FYAAUU5J.js → lowlight-J2OZNPCA.js} +1 -1
  253. package/chunks/{ls-L654WOAT.js → ls-ZB2BDP6T.js} +6 -6
  254. package/chunks/{lsp-M35KE54Y.js → lsp-V5DN4YUJ.js} +6 -8
  255. package/chunks/mcp-D2P46DB4.js +68 -0
  256. package/chunks/{monitor-464OY2UE.js → monitor-4ZWVOL6J.js} +50 -50
  257. package/chunks/{multipart-parser-AJ4WASWR.js → multipart-parser-AWZKXSQN.js} +3 -3
  258. package/chunks/nonInteractiveCli-NAHUR2X6.js +124 -0
  259. package/chunks/{notebook-edit-BLJYCNDO.js → notebook-edit-RCQ2D2IE.js} +51 -51
  260. package/chunks/openaiContentGenerator-U655T5QC.js +56 -0
  261. package/chunks/pidfile-O3OO2TCP.js +73 -0
  262. package/chunks/{pt-2THT3YHA.js → pt-6IR2VRQY.js} +91 -6
  263. package/chunks/{qwenContentGenerator-SUUHZ6CR.js → qwenContentGenerator-ABGWCRS6.js} +52 -52
  264. package/chunks/{qwenOAuth2-ZV7YLMND.js → qwenOAuth2-PJQ27GSO.js} +9 -9
  265. package/chunks/read-file-X7M4GUPY.js +30 -0
  266. package/chunks/{read-mcp-resource-CNQHBSJM.js → read-mcp-resource-SM5DA4RG.js} +5 -5
  267. package/chunks/{read-package-up-ER5OJUGP.js → read-package-up-TCM6I7S2.js} +2 -2
  268. package/chunks/{record-artifact-PP3LKHTS.js → record-artifact-4XKZX3UW.js} +7 -7
  269. package/chunks/ripGrep-KMPJJK5J.js +60 -0
  270. package/chunks/{ru-Z67YJHBG.js → ru-DBTHQA3F.js} +91 -6
  271. package/chunks/{run-qwen-serve-5OKADVLE.js → run-qwen-serve-O6CMPURG.js} +958 -184
  272. package/chunks/runtime-THE2YO27.js +94 -0
  273. package/chunks/{scheduler-ZRW3AIDU.js → scheduler-XXEA6EFC.js} +51 -51
  274. package/chunks/{send-message-OA6MV4EU.js → send-message-F36YD6KQ.js} +7 -7
  275. package/chunks/serve-W6F44JQQ.js +74 -0
  276. package/chunks/{server-M372UENX.js → server-SAEKMY52.js} +4340 -829
  277. package/chunks/{session-F5KSNP2N.js → session-4GGYGOWY.js} +162 -117
  278. package/chunks/settings-PYEU5HVD.js +118 -0
  279. package/chunks/shell-JGQECKTH.js +68 -0
  280. package/chunks/{skill-A2TL2NPC.js → skill-XPP3C2DS.js} +24 -24
  281. package/chunks/spawnChannel-62QKQBQN.js +70 -0
  282. package/chunks/{src-XKZMNHXS.js → src-2656Q6SN.js} +126 -52
  283. package/chunks/{src-7XL4G4DC.js → src-VLI3TKEY.js} +4 -4
  284. package/chunks/standalone-update-X7AZVG5R.js +79 -0
  285. package/chunks/{startInteractiveUI-REKSTOHU.js → startInteractiveUI-DXR4K6T5.js} +2277 -1834
  286. package/chunks/stdioHelpers-UMY72QRP.js +14 -0
  287. package/chunks/{syntheticOutput-F7LSDCAR.js → syntheticOutput-34H7LGA7.js} +5 -5
  288. package/chunks/task-create-FRGOEIKY.js +22 -0
  289. package/chunks/{task-list-TGGJX7EA.js → task-list-OW766BXZ.js} +10 -10
  290. package/chunks/{task-stop-W7NGTMYQ.js → task-stop-SIR5SNBN.js} +4 -4
  291. package/chunks/{task-update-5WUG5MVQ.js → task-update-YRA532MT.js} +12 -12
  292. package/chunks/{team-create-TBQVWAG7.js → team-create-3EHCUCKA.js} +50 -50
  293. package/chunks/{team-delete-UHQQ2QQX.js → team-delete-6OSECUZC.js} +10 -10
  294. package/chunks/{team-plan-approval-FZPGUSHJ.js → team-plan-approval-KZ7NZIMX.js} +50 -50
  295. package/chunks/theme-manager-J7ANV4JR.js +64 -0
  296. package/chunks/{todoWrite-C4ZAMSR3.js → todoWrite-YDMFEV4G.js} +6 -6
  297. package/chunks/{tool-search-FDQ3SLNJ.js → tool-search-2XLGWG4H.js} +24 -24
  298. package/chunks/total-session-admission-EQIHUOL6.js +72 -0
  299. package/chunks/trustedFolders-BEX5334Z.js +79 -0
  300. package/chunks/{types-QX5C3CHJ.js → types-JNKGKUJT.js} +2 -2
  301. package/chunks/updateCheck-NKPJGJCR.js +69 -0
  302. package/chunks/updateEventEmitter-5MKC7BLQ.js +10 -0
  303. package/chunks/validateNonInterActiveAuth-6DGGGE4A.js +190 -0
  304. package/chunks/{version-CEDD6ZZR.js → version-FRVWU5ZU.js} +3 -3
  305. package/chunks/{web-fetch-UYGMOHW4.js → web-fetch-PAAEQKBW.js} +8 -8
  306. package/chunks/{workflow-CQQ4J4I6.js → workflow-LWFMECLD.js} +51 -51
  307. package/chunks/workspace-providers-status-NE7QWEYS.js +71 -0
  308. package/chunks/workspace-registry-RCWAP57G.js +76 -0
  309. package/chunks/workspace-service-4QUMVGBX.js +80 -0
  310. package/chunks/workspace-skills-status-L34BYRSY.js +70 -0
  311. package/chunks/{write-file-VWGY7AJP.js → write-file-WXO4M5FN.js} +51 -51
  312. package/chunks/{yargs-B64EYTCF.js → yargs-6H2AUULL.js} +2 -2
  313. package/chunks/{zh-7XVVRTDH.js → zh-LJP47LX7.js} +101 -7
  314. package/chunks/{zh-TW-DPH554OG.js → zh-TW-5ISMO3VJ.js} +101 -7
  315. package/cli.js +18 -17
  316. package/locales/ca.js +117 -7
  317. package/locales/de.js +116 -7
  318. package/locales/en.js +133 -7
  319. package/locales/fr.js +117 -7
  320. package/locales/ja.js +119 -7
  321. package/locales/pt.js +116 -7
  322. package/locales/ru.js +116 -7
  323. package/locales/zh-TW.js +130 -7
  324. package/locales/zh.js +130 -7
  325. package/package.json +3 -6
  326. package/web-shell/assets/{arc-C7hjaa7g.js → arc-BRDEF7h6.js} +1 -1
  327. package/web-shell/assets/{architectureDiagram-3BPJPVTR-pm9KtMZY.js → architectureDiagram-3BPJPVTR-Cxnk4Ygq.js} +1 -1
  328. package/web-shell/assets/{blockDiagram-GPEHLZMM-CApElKsU.js → blockDiagram-GPEHLZMM-BZrtayqw.js} +1 -1
  329. package/web-shell/assets/{c4Diagram-AAUBKEIU-BeDe0-9N.js → c4Diagram-AAUBKEIU-Djmbi1yB.js} +1 -1
  330. package/web-shell/assets/channel-C9zfQCn-.js +1 -0
  331. package/web-shell/assets/{chunk-2J33WTMH-DJEkmLdl.js → chunk-2J33WTMH-Cdokj-0U.js} +1 -1
  332. package/web-shell/assets/{chunk-4BX2VUAB-DXX8gdpe.js → chunk-4BX2VUAB-BI9Hx57o.js} +1 -1
  333. package/web-shell/assets/{chunk-55IACEB6-DORmk1kQ.js → chunk-55IACEB6-Cb17cEzj.js} +1 -1
  334. package/web-shell/assets/{chunk-727SXJPM-DWmO4mCZ.js → chunk-727SXJPM-DY1Fc0h_.js} +1 -1
  335. package/web-shell/assets/{chunk-AQP2D5EJ-C7LRaYw2.js → chunk-AQP2D5EJ-B10G71Hd.js} +1 -1
  336. package/web-shell/assets/{chunk-FMBD7UC4-CCdXgyC5.js → chunk-FMBD7UC4-BP6OoB6Z.js} +1 -1
  337. package/web-shell/assets/{chunk-ND2GUHAM-SyL_xvWc.js → chunk-ND2GUHAM-CWIIwUFC.js} +1 -1
  338. package/web-shell/assets/{chunk-QZHKN3VN-BzOuCKxa.js → chunk-QZHKN3VN-DMrqCfGO.js} +1 -1
  339. package/web-shell/assets/classDiagram-4FO5ZUOK-BVNaf5JM.js +1 -0
  340. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-BVNaf5JM.js +1 -0
  341. package/web-shell/assets/{cose-bilkent-S5V4N54A-BMPWyDKc.js → cose-bilkent-S5V4N54A-CF-mpkgI.js} +1 -1
  342. package/web-shell/assets/{dagre-BM42HDAG-B_GnPWik.js → dagre-BM42HDAG-DfsRFsg7.js} +1 -1
  343. package/web-shell/assets/{diagram-2AECGRRQ-fJqMc3S_.js → diagram-2AECGRRQ-DBNwf6Dy.js} +1 -1
  344. package/web-shell/assets/{diagram-5GNKFQAL-ximV3O9D.js → diagram-5GNKFQAL-CD1TqV1j.js} +1 -1
  345. package/web-shell/assets/{diagram-KO2AKTUF-2vVyjzqw.js → diagram-KO2AKTUF-DkWlaLI2.js} +1 -1
  346. package/web-shell/assets/{diagram-LMA3HP47-Dckcx1sT.js → diagram-LMA3HP47-ClrAcxwF.js} +1 -1
  347. package/web-shell/assets/{diagram-OG6HWLK6-BAVv47ga.js → diagram-OG6HWLK6-Dr6dnPLw.js} +1 -1
  348. package/web-shell/assets/{erDiagram-TEJ5UH35-Bn3nf5J0.js → erDiagram-TEJ5UH35-Dm_vj18R.js} +1 -1
  349. package/web-shell/assets/{flowDiagram-I6XJVG4X-BIbGF9JT.js → flowDiagram-I6XJVG4X-lp3cMdZu.js} +1 -1
  350. package/web-shell/assets/{ganttDiagram-6RSMTGT7-CDbhb3AH.js → ganttDiagram-6RSMTGT7-AtdCWZya.js} +3 -3
  351. package/web-shell/assets/{gitGraphDiagram-PVQCEYII-CbKG1a_w.js → gitGraphDiagram-PVQCEYII-BARTJafF.js} +1 -1
  352. package/web-shell/assets/index-BkNgyfu7.js +3 -0
  353. package/web-shell/assets/index-CihmnZZR.js +784 -0
  354. package/web-shell/assets/index-zfdXhnwH.css +5 -0
  355. package/web-shell/assets/{infoDiagram-5YYISTIA-DUFxTy8m.js → infoDiagram-5YYISTIA-CxbJ54_f.js} +1 -1
  356. package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-D69hrBG8.js → ishikawaDiagram-YF4QCWOH-hFoO0HQA.js} +1 -1
  357. package/web-shell/assets/{journeyDiagram-JHISSGLW-DsvEw51Y.js → journeyDiagram-JHISSGLW-Bo6cKnLe.js} +1 -1
  358. package/web-shell/assets/{kanban-definition-UN3LZRKU-lFFjZEYd.js → kanban-definition-UN3LZRKU-DIFrH-XL.js} +1 -1
  359. package/web-shell/assets/{linear-Bo5bWzvo.js → linear-Bjv_GjDd.js} +1 -1
  360. package/web-shell/assets/{mermaid.core-C_HKMJx5.js → mermaid.core-C_OiqQEP.js} +5 -5
  361. package/web-shell/assets/{mindmap-definition-RKZ34NQL-DlV-NUTs.js → mindmap-definition-RKZ34NQL-B79Gk8aB.js} +1 -1
  362. package/web-shell/assets/{pieDiagram-4H26LBE5-B2e6FH2a.js → pieDiagram-4H26LBE5-B_P0gIHr.js} +1 -1
  363. package/web-shell/assets/{quadrantDiagram-W4KKPZXB-D75kSk-b.js → quadrantDiagram-W4KKPZXB-BC7F0ohb.js} +1 -1
  364. package/web-shell/assets/{requirementDiagram-4Y6WPE33-CQ6xakhM.js → requirementDiagram-4Y6WPE33-BrA5_NSW.js} +1 -1
  365. package/web-shell/assets/{sankeyDiagram-5OEKKPKP-J_XTKrcY.js → sankeyDiagram-5OEKKPKP-Q2iqrNI_.js} +1 -1
  366. package/web-shell/assets/{sequenceDiagram-3UESZ5HK-D7jTcBtf.js → sequenceDiagram-3UESZ5HK-6vdIWz4H.js} +1 -1
  367. package/web-shell/assets/{stateDiagram-AJRCARHV-CrlM_OT3.js → stateDiagram-AJRCARHV-DMzXNKcB.js} +1 -1
  368. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-BWFH0OCO.js +1 -0
  369. package/web-shell/assets/{timeline-definition-PNZ67QCA-5oZi76Ef.js → timeline-definition-PNZ67QCA-BykEawmS.js} +1 -1
  370. package/web-shell/assets/{vennDiagram-CIIHVFJN-DO5YptPK.js → vennDiagram-CIIHVFJN-B24KD2X1.js} +1 -1
  371. package/web-shell/assets/{wardley-L42UT6IY-4t1eJDvM.js → wardley-L42UT6IY-C761G_tA.js} +1 -1
  372. package/web-shell/assets/{wardleyDiagram-YWT4CUSO-DKZc661S.js → wardleyDiagram-YWT4CUSO-CfwGJYqw.js} +1 -1
  373. package/web-shell/assets/{xychartDiagram-2RQKCTM6-su9hp08G.js → xychartDiagram-2RQKCTM6-BDu5AZ2D.js} +1 -1
  374. package/web-shell/index.html +16 -4
  375. package/chunks/MaxSizedBox-XY5LIO5O.js +0 -71
  376. package/chunks/agent-TKIBLLQT.js +0 -68
  377. package/chunks/agent-headless-VLX4C7KX.js +0 -62
  378. package/chunks/bridge-BCU73DZO.js +0 -77
  379. package/chunks/chunk-6SHO7WFF.js +0 -79
  380. package/chunks/chunk-IFEWJXFS.js +0 -37
  381. package/chunks/chunk-S6RLAIUR.js +0 -23
  382. package/chunks/contextCommand-LAYJZF7H.js +0 -68
  383. package/chunks/daemon-status-provider-XSAFKVDS.js +0 -74
  384. package/chunks/earlyInputCapture-AERYYGXE.js +0 -69
  385. package/chunks/errors-GMZL4QV7.js +0 -75
  386. package/chunks/handleAutoUpdate-HN7ZOSXY.js +0 -65
  387. package/chunks/initializer-SRN3IM42.js +0 -69
  388. package/chunks/list-INTVXUPA.js +0 -71
  389. package/chunks/loadedSettingsAdapter-QVHHQPGM.js +0 -66
  390. package/chunks/mcp-7V4T46RG.js +0 -66
  391. package/chunks/nonInteractiveCli-LJEEODU2.js +0 -116
  392. package/chunks/openaiContentGenerator-PFF6HYB2.js +0 -56
  393. package/chunks/pidfile-LIBUENIV.js +0 -73
  394. package/chunks/read-file-L7TBQVX7.js +0 -30
  395. package/chunks/ripGrep-CKKX3GRB.js +0 -60
  396. package/chunks/serve-36WS32W6.js +0 -71
  397. package/chunks/settings-WFLNS3WT.js +0 -114
  398. package/chunks/shell-DEDSWN56.js +0 -68
  399. package/chunks/spawnChannel-RQHUWTCL.js +0 -70
  400. package/chunks/task-create-RPFZNB4B.js +0 -22
  401. package/chunks/theme-manager-DQZVNSOC.js +0 -64
  402. package/chunks/trustedFolders-Z475EIHL.js +0 -79
  403. package/chunks/updateCheck-E2ZX7RL7.js +0 -65
  404. package/chunks/validateNonInterActiveAuth-O4ECBHQL.js +0 -181
  405. package/chunks/workspace-providers-status-MVYXMYDG.js +0 -68
  406. package/chunks/workspace-service-OMBR6ZLG.js +0 -78
  407. package/chunks/workspace-skills-status-IMMQ7CAY.js +0 -68
  408. package/node_modules/@qwen-code/audio-capture/dist/index.d.ts +0 -35
  409. package/node_modules/@qwen-code/audio-capture/dist/index.js +0 -48
  410. package/node_modules/@qwen-code/audio-capture/dist/index.js.map +0 -1
  411. package/node_modules/@qwen-code/audio-capture/dist/platform.d.ts +0 -7
  412. package/node_modules/@qwen-code/audio-capture/dist/platform.js +0 -18
  413. package/node_modules/@qwen-code/audio-capture/dist/platform.js.map +0 -1
  414. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/LICENSE +0 -21
  415. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/README.md +0 -58
  416. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/SECURITY.md +0 -5
  417. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/bin.js +0 -84
  418. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/build-test.js +0 -19
  419. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/index.js +0 -6
  420. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/node-gyp-build.js +0 -207
  421. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/optional.js +0 -7
  422. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/package.json +0 -43
  423. package/node_modules/@qwen-code/audio-capture/package.json +0 -30
  424. package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-arm64/@qwen-code+audio-capture.node +0 -0
  425. package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-x64/@qwen-code+audio-capture.node +0 -0
  426. package/node_modules/@qwen-code/audio-capture/prebuilds/linux-arm64/@qwen-code+audio-capture.node +0 -0
  427. package/node_modules/@qwen-code/audio-capture/prebuilds/linux-x64/@qwen-code+audio-capture.node +0 -0
  428. package/node_modules/@qwen-code/audio-capture/prebuilds/win32-x64/@qwen-code+audio-capture.node +0 -0
  429. package/web-shell/assets/channel-BNf1dW-j.js +0 -1
  430. package/web-shell/assets/classDiagram-4FO5ZUOK-uOIGlzgi.js +0 -1
  431. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-uOIGlzgi.js +0 -1
  432. package/web-shell/assets/index-CnE2ur2L.js +0 -779
  433. package/web-shell/assets/index-hSKXrlSE.css +0 -5
  434. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-Bs4TK_84.js +0 -1
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
  import {
4
4
  MCP_RESTART_SERVER_DEADLINE_MS
5
- } from "./chunk-P7M4GA57.js";
5
+ } from "./chunk-OCPBI7J5.js";
6
6
  import {
7
7
  BranchWhilePromptActiveError,
8
8
  CancelSentinelCollisionError,
@@ -22,30 +22,29 @@ import {
22
22
  SessionShellDisabledError,
23
23
  WorkspaceMismatchError,
24
24
  isNotCurrentlyGeneratingCancelError
25
- } from "./chunk-B34OV7K2.js";
25
+ } from "./chunk-IAVB3HM3.js";
26
26
  import {
27
27
  LOAD_REPLAY_BULK_MODE,
28
28
  LOAD_REPLAY_META_KEY,
29
29
  LOAD_REPLAY_MODE_META_KEY,
30
30
  LOAD_REPLAY_VERSION,
31
31
  MID_TURN_QUEUE_DRAIN_METHOD
32
- } from "./chunk-AGHU5VRK.js";
32
+ } from "./chunk-PZL23GTM.js";
33
33
  import {
34
34
  canonicalizeWorkspace
35
- } from "./chunk-ER3BKOLB.js";
35
+ } from "./chunk-W7FJ3N32.js";
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-6RLROE6G.js";
41
42
  import {
42
- ClientSideConnection,
43
- PROTOCOL_VERSION,
44
- RequestError
45
- } from "./chunk-VRDMOSQS.js";
43
+ normalizeCompactedReplayMaxBytes
44
+ } from "./chunk-COH43MRJ.js";
46
45
  import {
47
46
  defaultSpawnChannelFactory
48
- } from "./chunk-AJBEAUIS.js";
47
+ } from "./chunk-BZP7HZTK.js";
49
48
  import {
50
49
  BridgeChannelClosedError,
51
50
  BridgeTimeoutError,
@@ -54,18 +53,31 @@ import {
54
53
  STATUS_SCHEMA_VERSION,
55
54
  createIdleWorkspaceExtensionsStatus,
56
55
  createIdleWorkspaceHooksStatus
57
- } from "./chunk-JF7DFPQ5.js";
56
+ } from "./chunk-TJCEUA3S.js";
57
+ import {
58
+ ClientSideConnection,
59
+ PROTOCOL_VERSION,
60
+ RequestError
61
+ } from "./chunk-UDXSXLER.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-UUVCHN62.js";
62
74
  import {
63
75
  DAEMON_TRACEPARENT_META_KEY,
64
76
  DAEMON_TRACESTATE_META_KEY
65
- } from "./chunk-GMOWQOQQ.js";
77
+ } from "./chunk-MUCWGT7O.js";
66
78
  import {
67
79
  init_esbuild_shims
68
- } from "./chunk-A4BMJM77.js";
80
+ } from "./chunk-5O2XNYP6.js";
69
81
  import {
70
82
  __name
71
83
  } from "./chunk-J2S4EL5Y.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: true
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
 
@@ -1048,6 +1182,60 @@ function isRecord(value) {
1048
1182
  return typeof value === "object" && value !== null && !Array.isArray(value);
1049
1183
  }
1050
1184
  __name(isRecord, "isRecord");
1185
+ function pendingInteractionOptions(options) {
1186
+ return options.map((option) => ({
1187
+ optionId: String(option.optionId ?? ""),
1188
+ ...typeof option.name === "string" ? { label: option.name } : {},
1189
+ ...typeof option.kind === "string" ? { kind: option.kind } : {}
1190
+ }));
1191
+ }
1192
+ __name(pendingInteractionOptions, "pendingInteractionOptions");
1193
+ function pendingInteractionFromRequest(requestId, params) {
1194
+ const toolCall = params.toolCall;
1195
+ const meta = isRecord(toolCall["_meta"]) ? toolCall["_meta"] : void 0;
1196
+ const rawInput = toolCall["rawInput"];
1197
+ const options = pendingInteractionOptions(
1198
+ Array.isArray(params.options) ? params.options : []
1199
+ );
1200
+ const isUserQuestion = meta?.["qwenInteractionKind"] === "user_question";
1201
+ if (isUserQuestion) {
1202
+ const rawQuestions = Array.isArray(meta?.["qwenQuestions"]) ? meta["qwenQuestions"] : isRecord(rawInput) && Array.isArray(rawInput["questions"]) ? rawInput["questions"] : [];
1203
+ return {
1204
+ requestId,
1205
+ kind: "user_question",
1206
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1207
+ ...typeof toolCall["title"] === "string" ? { title: toolCall["title"] } : {},
1208
+ questions: rawQuestions.flatMap(
1209
+ (question, index) => isRecord(question) ? [{ ...question, answerKey: String(index) }] : []
1210
+ ),
1211
+ options
1212
+ };
1213
+ }
1214
+ return {
1215
+ requestId,
1216
+ kind: "permission",
1217
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1218
+ action: {
1219
+ ...typeof toolCall["kind"] === "string" ? { type: toolCall["kind"] } : {},
1220
+ ...typeof toolCall["title"] === "string" ? { title: toolCall["title"] } : {},
1221
+ ...toolCall["content"] !== void 0 ? { content: toolCall["content"] } : {},
1222
+ ...toolCall["locations"] !== void 0 ? { locations: toolCall["locations"] } : {},
1223
+ ...rawInput !== void 0 ? { input: rawInput } : {}
1224
+ },
1225
+ options
1226
+ };
1227
+ }
1228
+ __name(pendingInteractionFromRequest, "pendingInteractionFromRequest");
1229
+ function fallbackPendingPermissionInteraction(requestId, options) {
1230
+ return {
1231
+ requestId,
1232
+ kind: "permission",
1233
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1234
+ action: {},
1235
+ options: pendingInteractionOptions(options)
1236
+ };
1237
+ }
1238
+ __name(fallbackPendingPermissionInteraction, "fallbackPendingPermissionInteraction");
1051
1239
  function artifactPayloadFields(artifact) {
1052
1240
  return {
1053
1241
  title: artifact["title"],
@@ -1059,7 +1247,8 @@ function artifactPayloadFields(artifact) {
1059
1247
  url: artifact["url"],
1060
1248
  mimeType: artifact["mimeType"],
1061
1249
  sizeBytes: artifact["sizeBytes"],
1062
- metadata: artifact["metadata"]
1250
+ metadata: artifact["metadata"],
1251
+ retention: artifact["retention"]
1063
1252
  };
1064
1253
  }
1065
1254
  __name(artifactPayloadFields, "artifactPayloadFields");
@@ -1202,7 +1391,7 @@ function sliceLineRange(content, startLine, endLine) {
1202
1391
  }
1203
1392
  __name(sliceLineRange, "sliceLineRange");
1204
1393
  var BridgeClient = class {
1205
- constructor(resolveEntry, resolvePendingRestoreEvents, mediator, permissionTimeoutMs, maxPendingPerSession, fileSystem, onModelPromoted, onModePromoted, clientMcpSender, ownsSession = () => true, onTokenUsage) {
1394
+ constructor(resolveEntry, resolvePendingRestoreEvents, mediator, permissionTimeoutMs, maxPendingPerSession, fileSystem, onModelPromoted, onModePromoted, clientMcpSender, ownsSession = () => true, onTokenUsage, onCreateSubSession) {
1206
1395
  this.resolveEntry = resolveEntry;
1207
1396
  this.resolvePendingRestoreEvents = resolvePendingRestoreEvents;
1208
1397
  this.mediator = mediator;
@@ -1214,6 +1403,7 @@ var BridgeClient = class {
1214
1403
  this.clientMcpSender = clientMcpSender;
1215
1404
  this.ownsSession = ownsSession;
1216
1405
  this.onTokenUsage = onTokenUsage;
1406
+ this.onCreateSubSession = onCreateSubSession;
1217
1407
  }
1218
1408
  static {
1219
1409
  __name(this, "BridgeClient");
@@ -1227,10 +1417,9 @@ var BridgeClient = class {
1227
1417
  );
1228
1418
  return { outcome: { outcome: "cancelled" } };
1229
1419
  }
1420
+ const options = Array.isArray(params.options) ? params.options : [];
1230
1421
  const allowedOptionIds = new Set(
1231
- params.options.map(
1232
- (o) => String(o.optionId ?? "")
1233
- )
1422
+ options.map((o) => String(o.optionId ?? ""))
1234
1423
  );
1235
1424
  allowedOptionIds.delete("");
1236
1425
  const requestId = randomUUID();
@@ -1243,12 +1432,25 @@ var BridgeClient = class {
1243
1432
  requestId,
1244
1433
  sessionId: entry.sessionId,
1245
1434
  toolCall: params.toolCall,
1246
- options: params.options
1435
+ options
1247
1436
  },
1248
1437
  ...entry.activePromptOriginatorClientId ? { originatorClientId: entry.activePromptOriginatorClientId } : {}
1249
1438
  });
1250
1439
  if (!published) return { outcome: { outcome: "cancelled" } };
1440
+ let interaction;
1441
+ try {
1442
+ interaction = pendingInteractionFromRequest(requestId, {
1443
+ ...params,
1444
+ options
1445
+ });
1446
+ } catch (error) {
1447
+ writeStderrLine(
1448
+ `qwen serve: failed to snapshot pending interaction ${requestId}: ${error instanceof Error ? error.message : String(error)}`
1449
+ );
1450
+ interaction = fallbackPendingPermissionInteraction(requestId, options);
1451
+ }
1251
1452
  entry.pendingPermissionIds.add(requestId);
1453
+ if (interaction) entry.pendingInteractions.set(requestId, interaction);
1252
1454
  try {
1253
1455
  const record = {
1254
1456
  requestId,
@@ -1264,6 +1466,7 @@ var BridgeClient = class {
1264
1466
  return resolutionToAcpResponse(resolution);
1265
1467
  } finally {
1266
1468
  entry.pendingPermissionIds.delete(requestId);
1469
+ entry.pendingInteractions.delete(requestId);
1267
1470
  }
1268
1471
  }
1269
1472
  async sessionUpdate(params) {
@@ -1337,7 +1540,7 @@ var BridgeClient = class {
1337
1540
  trustedPublisher: isTrustedArtifactToolUpdate(params, updateMeta)
1338
1541
  };
1339
1542
  }
1340
- async seedSessionUpdates(entry, updates) {
1543
+ async seedSessionUpdates(entry, updates, options = {}) {
1341
1544
  const frames = [];
1342
1545
  const artifactBatches = [];
1343
1546
  for (const update of updates) {
@@ -1346,7 +1549,7 @@ var BridgeClient = class {
1346
1549
  entry
1347
1550
  );
1348
1551
  frames.push(...prepared.frames);
1349
- if (prepared.artifacts.length > 0) {
1552
+ if (options.ingestArtifacts !== false && prepared.artifacts.length > 0) {
1350
1553
  artifactBatches.push({
1351
1554
  artifacts: prepared.artifacts,
1352
1555
  trustedPublisher: prepared.trustedPublisher
@@ -1435,9 +1638,12 @@ var BridgeClient = class {
1435
1638
  inFlightRestoreIds = /* @__PURE__ */ new Set();
1436
1639
  /**
1437
1640
  * 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
1641
+ * response, unlike `extNotification`). Served methods:
1642
+ * `qwen/control/client_mcp/message` (reverse tool channel),
1643
+ * `qwen/control/create-sub-session` (the `create_sub_session` tool daemon
1644
+ * spawns a sub-session and, for `'first-turn'`, returns its first-turn
1645
+ * result), and `craft/drainMidTurnQueue`: the ACP child calls the last one
1646
+ * between tool batches to pull any messages the browser queued mid-turn. We splice the per-session
1441
1647
  * queue, return them to the child as the response, and — when non-empty —
1442
1648
  * publish a `mid_turn_message_injected` SSE frame so the browser can move
1443
1649
  * those messages out of its pending queue (a dedupe signal, not a transcript
@@ -1450,6 +1656,9 @@ var BridgeClient = class {
1450
1656
  if (method === SERVE_CONTROL_EXT_METHODS.clientMcpMessage) {
1451
1657
  return this.handleClientMcpMessage(params);
1452
1658
  }
1659
+ if (method === SERVE_CONTROL_EXT_METHODS.createSubSession) {
1660
+ return this.handleCreateSubSession(params);
1661
+ }
1453
1662
  if (method !== MID_TURN_QUEUE_DRAIN_METHOD) {
1454
1663
  throw RequestError.methodNotFound(method);
1455
1664
  }
@@ -1525,6 +1734,68 @@ var BridgeClient = class {
1525
1734
  const response = await send(payload);
1526
1735
  return { payload: response };
1527
1736
  }
1737
+ /**
1738
+ * Handle the `create_sub_session` tool's request: validate, then forward to
1739
+ * the daemon-host `onCreateSubSession` callback (which spawns a fresh
1740
+ * top-level sub-session and, for `'first-turn'`, waits for its first turn and
1741
+ * returns the result). No host wired → `methodNotFound`, which the tool
1742
+ * surfaces as "daemon-only".
1743
+ */
1744
+ async handleCreateSubSession(params) {
1745
+ if (!this.onCreateSubSession) {
1746
+ throw RequestError.methodNotFound(
1747
+ SERVE_CONTROL_EXT_METHODS.createSubSession
1748
+ );
1749
+ }
1750
+ const prompt = params["prompt"];
1751
+ if (typeof prompt !== "string" || prompt.length === 0) {
1752
+ throw RequestError.invalidParams(
1753
+ void 0,
1754
+ "`prompt` must be a non-empty string"
1755
+ );
1756
+ }
1757
+ if (prompt.length > MAX_SUB_SESSION_PROMPT_CHARS) {
1758
+ throw RequestError.invalidParams(
1759
+ void 0,
1760
+ `\`prompt\` exceeds the ${MAX_SUB_SESSION_PROMPT_CHARS}-character limit`
1761
+ );
1762
+ }
1763
+ const completion = params["completion"];
1764
+ if (completion !== "sent" && completion !== "first-turn") {
1765
+ throw RequestError.invalidParams(
1766
+ void 0,
1767
+ "`completion` must be 'sent' or 'first-turn'"
1768
+ );
1769
+ }
1770
+ const name = params["name"];
1771
+ if (typeof name === "string" && name.length > MAX_SUB_SESSION_NAME_CHARS) {
1772
+ throw RequestError.invalidParams(
1773
+ void 0,
1774
+ `\`name\` exceeds the ${MAX_SUB_SESSION_NAME_CHARS}-character limit`
1775
+ );
1776
+ }
1777
+ const callerSessionId = params["callerSessionId"];
1778
+ if (typeof callerSessionId !== "string" || callerSessionId.length === 0 || !this.ownsSession(callerSessionId)) {
1779
+ throw RequestError.invalidParams(
1780
+ void 0,
1781
+ "`callerSessionId` is required and must name a session owned by this connection"
1782
+ );
1783
+ }
1784
+ const model = params["model"];
1785
+ const result = await this.onCreateSubSession({
1786
+ prompt,
1787
+ completion,
1788
+ ...typeof model === "string" && model.length > 0 && model.length <= 128 ? { model } : {},
1789
+ ...typeof name === "string" && name.length > 0 ? { name } : {},
1790
+ callerSessionId
1791
+ });
1792
+ return {
1793
+ sessionId: result.sessionId,
1794
+ ...result.result !== void 0 ? { result: result.result } : {},
1795
+ ...result.stopReason !== void 0 ? { stopReason: result.stopReason } : {},
1796
+ ...result.parentSessionPersisted !== void 0 ? { parentSessionPersisted: result.parentSessionPersisted } : {}
1797
+ };
1798
+ }
1528
1799
  /**
1529
1800
  * Handle child->bridge ACP `extNotification` calls. Recognized methods are
1530
1801
  * `qwen/notify/session/model-update`,
@@ -1534,8 +1805,8 @@ var BridgeClient = class {
1534
1805
  * `qwen/notify/session/artifact-event` (hook artifacts),
1535
1806
  * `qwen/notify/session/terminal-sequence`, and
1536
1807
  * `qwen/notify/session/mcp-budget-event` — each translated into a
1537
- * session-scoped SSE frame. Unknown methods are dropped silently
1538
- * for forward-compat.
1808
+ * session-scoped SSE frame. Unknown methods are dropped silently for
1809
+ * forward-compat.
1539
1810
  */
1540
1811
  async extNotification(method, params) {
1541
1812
  if (method === "qwen/notify/session/model-update") {
@@ -2118,7 +2389,7 @@ __name(extractA2uiToolUpdate, "extractA2uiToolUpdate");
2118
2389
  // packages/acp-bridge/src/sessionArtifacts.ts
2119
2390
  init_esbuild_shims();
2120
2391
  import { createHash } from "node:crypto";
2121
- import { promises as fs2 } from "node:fs";
2392
+ import { constants as fsConstants, promises as fs2 } from "node:fs";
2122
2393
  import path2 from "node:path";
2123
2394
  var SOURCE_RESERVATIONS = {
2124
2395
  tool: 100,
@@ -2127,6 +2398,18 @@ var SOURCE_RESERVATIONS = {
2127
2398
  };
2128
2399
  var WORKSPACE_STATUS_REFRESH_TTL_MS = 5e3;
2129
2400
  var WORKSPACE_STATUS_REFRESH_BATCH_SIZE = 20;
2401
+ var SNAPSHOT_AFTER_DURABLE_EVENTS = 50;
2402
+ var MAX_SNAPSHOT_BACKOFF_MULTIPLIER = 4;
2403
+ var MAX_TOMBSTONED_IDS = 500;
2404
+ var MAX_STICKY_EPHEMERAL_IDS = 500;
2405
+ var MAX_WORKSPACE_HASH_BYTES = 100 * 1024 * 1024;
2406
+ 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;
2407
+ var RESTORE_FAILED_WARNING_PREFIX = "artifact snapshot restore failed";
2408
+ var RESTORE_PARTIAL_FAILED_WARNING_PREFIX = "artifact snapshot restore partially failed";
2409
+ function isArtifactRestoreFailureWarning(warning) {
2410
+ return warning.startsWith(RESTORE_FAILED_WARNING_PREFIX) || warning.startsWith(RESTORE_PARTIAL_FAILED_WARNING_PREFIX);
2411
+ }
2412
+ __name(isArtifactRestoreFailureWarning, "isArtifactRestoreFailureWarning");
2130
2413
  var SessionArtifactValidationError = class extends Error {
2131
2414
  constructor(message, field) {
2132
2415
  super(message);
@@ -2138,6 +2421,20 @@ var SessionArtifactValidationError = class extends Error {
2138
2421
  }
2139
2422
  code = "VALIDATION_FAILED";
2140
2423
  };
2424
+ var SessionArtifactAuthorizationError = class extends Error {
2425
+ constructor(sessionId, artifactId, ownerClientId, requesterClientId) {
2426
+ super(`artifact ${artifactId} is owned by a different client`);
2427
+ this.sessionId = sessionId;
2428
+ this.artifactId = artifactId;
2429
+ this.ownerClientId = ownerClientId;
2430
+ this.requesterClientId = requesterClientId;
2431
+ this.name = "SessionArtifactAuthorizationError";
2432
+ }
2433
+ static {
2434
+ __name(this, "SessionArtifactAuthorizationError");
2435
+ }
2436
+ code = "SESSION_ARTIFACT_FORBIDDEN";
2437
+ };
2141
2438
  var SessionArtifactStore = class {
2142
2439
  static {
2143
2440
  __name(this, "SessionArtifactStore");
@@ -2145,15 +2442,26 @@ var SessionArtifactStore = class {
2145
2442
  sessionId;
2146
2443
  workspaceCwd;
2147
2444
  maxArtifacts;
2445
+ persistence;
2148
2446
  artifacts = /* @__PURE__ */ new Map();
2149
2447
  receivedSeq = 0;
2150
2448
  insertSeq = 0;
2449
+ persistenceSeq = 0;
2450
+ durableEventsSinceSnapshot = 0;
2451
+ consecutiveSnapshotFailures = 0;
2151
2452
  realWorkspaceCwdPromise;
2152
2453
  operationQueue = Promise.resolve();
2454
+ tombstonedIds = /* @__PURE__ */ new Set();
2455
+ tombstonedClientIds = /* @__PURE__ */ new Map();
2456
+ stickyEphemeralIds = /* @__PURE__ */ new Set();
2457
+ markerArtifacts = /* @__PURE__ */ new Map();
2458
+ lastRestoreWarnings = [];
2459
+ lastRestoreWarningDetails = [];
2153
2460
  constructor(options) {
2154
2461
  this.sessionId = options.sessionId;
2155
2462
  this.workspaceCwd = options.workspaceCwd;
2156
2463
  this.maxArtifacts = options.maxArtifacts ?? 200;
2464
+ this.persistence = options.persistence;
2157
2465
  }
2158
2466
  inputBatchLimit() {
2159
2467
  return this.maxArtifacts * 2;
@@ -2166,13 +2474,30 @@ var SessionArtifactStore = class {
2166
2474
  sessionId: this.sessionId,
2167
2475
  artifacts: Array.from(this.artifacts.values()).sort((a, b) => a.insertSeq - b.insertSeq).map(toPublicArtifact),
2168
2476
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2169
- limits: { maxArtifacts: this.maxArtifacts }
2477
+ limits: { maxArtifacts: this.maxArtifacts },
2478
+ ...this.lastRestoreWarnings.length > 0 ? { warnings: [...this.lastRestoreWarnings] } : {},
2479
+ ...this.lastRestoreWarningDetails.length > 0 ? { warningDetails: [...this.lastRestoreWarningDetails] } : {}
2170
2480
  };
2171
2481
  });
2172
2482
  }
2483
+ async get(artifactId) {
2484
+ return this.enqueue(async () => {
2485
+ const artifact = this.artifacts.get(artifactId);
2486
+ if (!artifact) return void 0;
2487
+ if (artifact.workspacePath && shouldRefreshWorkspaceStatus(artifact, Date.now())) {
2488
+ await this.refreshWorkspaceStatus(artifact, { onError: "missing" });
2489
+ }
2490
+ return toPublicArtifact(artifact);
2491
+ });
2492
+ }
2173
2493
  async upsertMany(inputs, options = {}) {
2174
2494
  return this.enqueue(async () => {
2495
+ const validationStrict = options.validationStrict ?? options.strict;
2496
+ const persistenceStrict = options.persistenceStrict ?? options.strict;
2497
+ const before = this.cloneState();
2175
2498
  const normalizedResults = [];
2499
+ const warnings = [];
2500
+ const warningDetails = [];
2176
2501
  for (const input of inputs) {
2177
2502
  try {
2178
2503
  normalizedResults.push(
@@ -2183,7 +2508,7 @@ var SessionArtifactStore = class {
2183
2508
  )
2184
2509
  );
2185
2510
  } catch (error) {
2186
- if (options.strict) {
2511
+ if (validationStrict) {
2187
2512
  throw error;
2188
2513
  }
2189
2514
  const message = error instanceof Error ? error.message : String(error);
@@ -2195,39 +2520,140 @@ var SessionArtifactStore = class {
2195
2520
  }
2196
2521
  }
2197
2522
  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;
2523
+ try {
2524
+ for (const normalized of coalesceByIdentity(normalizedResults)) {
2525
+ const artifact = this.applyStickyEphemeralOverride(normalized);
2526
+ if (this.shouldSuppressTombstonedUpsert(artifact)) {
2527
+ writeStderrLine(
2528
+ `[artifacts] session=${this.sessionId} action=tombstone_replay_suppressed artifactId=${artifact.id}`
2529
+ );
2530
+ continue;
2531
+ }
2532
+ const existing = this.artifacts.get(artifact.id) ?? this.findPublishedUpgradeTarget(artifact) ?? this.findPublishedWorkspaceTarget(artifact);
2533
+ if (!existing) {
2534
+ const stored = {
2535
+ ...artifact,
2536
+ insertSeq: ++this.insertSeq
2537
+ };
2538
+ this.artifacts.set(stored.id, stored);
2539
+ changes.push({
2540
+ action: "created",
2541
+ artifactId: stored.id,
2542
+ artifact: toPublicArtifact(stored)
2543
+ });
2544
+ continue;
2545
+ }
2546
+ try {
2547
+ this.denyCrossClientMutation("upsert", existing.id, existing, {
2548
+ clientId: artifact.clientId
2549
+ });
2550
+ } catch (error) {
2551
+ if (validationStrict) {
2552
+ throw error;
2553
+ }
2554
+ if (error instanceof SessionArtifactAuthorizationError) {
2555
+ warnings.push(error.message);
2556
+ continue;
2557
+ }
2558
+ throw error;
2559
+ }
2560
+ const updated = mergeArtifact(existing, artifact);
2561
+ if (updated.changed) {
2562
+ if (updated.artifact.id !== existing.id) {
2563
+ const removeChange = {
2564
+ action: "removed",
2565
+ artifactId: existing.id,
2566
+ artifact: toPublicArtifact(existing),
2567
+ reason: "explicit",
2568
+ durableTombstoneRequired: existing.durableTombstoneRequired || existing.persistedAt !== void 0,
2569
+ removedClientId: existing.clientId
2570
+ };
2571
+ changes.push(removeChange);
2572
+ this.artifacts.delete(existing.id);
2573
+ } else if (shouldRecordEphemeralUnpin(existing, artifact)) {
2574
+ changes.push({
2575
+ action: "removed",
2576
+ artifactId: existing.id,
2577
+ artifact: toPublicArtifact(existing),
2578
+ reason: "unpin_to_ephemeral",
2579
+ durableTombstoneRequired: true
2580
+ });
2581
+ }
2582
+ this.artifacts.set(updated.artifact.id, updated.artifact);
2583
+ changes.push({
2584
+ action: "updated",
2585
+ artifactId: updated.artifact.id,
2586
+ artifact: toPublicArtifact(updated.artifact)
2587
+ });
2588
+ }
2212
2589
  }
2213
- const updated = mergeArtifact(existing, artifact);
2214
- if (updated.changed) {
2215
- if (updated.artifact.id !== existing.id) {
2216
- this.artifacts.delete(existing.id);
2590
+ const createdIds = new Set(
2591
+ changes.filter((change) => change.action === "created").map((change) => change.artifactId)
2592
+ );
2593
+ changes.push(
2594
+ ...await this.evictOverflow(createdIds, changes, persistenceStrict)
2595
+ );
2596
+ const hasStrictDurableTransition = changes.some(
2597
+ shouldCommitBeforeDurablePersistence
2598
+ );
2599
+ let persistenceWarnings;
2600
+ if (hasStrictDurableTransition && !persistenceStrict) {
2601
+ try {
2602
+ persistenceWarnings = await this.persistChanges(changes, true);
2603
+ } catch (error) {
2604
+ this.restoreState(before);
2605
+ const artifactIds = changes.filter(shouldCommitBeforeDurablePersistence).map((change) => change.artifactId);
2606
+ const warning = "artifact durable removal not persisted; live changes rolled back";
2607
+ writeStderrLine(
2608
+ `[artifacts] session=${this.sessionId} action=upsert_rollback warning=${JSON.stringify(
2609
+ warning
2610
+ )} artifactIds=${JSON.stringify(artifactIds)} error=${JSON.stringify(
2611
+ error instanceof Error ? error.message : String(error)
2612
+ )}`
2613
+ );
2614
+ return {
2615
+ v: 1,
2616
+ sessionId: this.sessionId,
2617
+ changes: [],
2618
+ warnings: [warning],
2619
+ warningDetails: [
2620
+ persistenceFailureDetail({
2621
+ message: warning,
2622
+ operation: "upsert",
2623
+ artifactIds,
2624
+ error
2625
+ })
2626
+ ]
2627
+ };
2217
2628
  }
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
- });
2629
+ } else {
2630
+ persistenceWarnings = await this.persistChanges(
2631
+ changes,
2632
+ persistenceStrict
2633
+ );
2634
+ }
2635
+ warnings.push(...persistenceWarnings);
2636
+ warningDetails.push(
2637
+ ...detailsForPersistenceWarnings(
2638
+ persistenceWarnings,
2639
+ changes,
2640
+ "upsert"
2641
+ )
2642
+ );
2643
+ stripDurableTombstoneMarkers(changes);
2644
+ } catch (error) {
2645
+ if (validationStrict || persistenceStrict || error instanceof SessionArtifactAuthorizationError) {
2646
+ this.restoreState(before);
2224
2647
  }
2648
+ throw error;
2225
2649
  }
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 };
2650
+ return {
2651
+ v: 1,
2652
+ sessionId: this.sessionId,
2653
+ changes,
2654
+ ...warnings.length > 0 ? { warnings } : {},
2655
+ ...warningDetails.length > 0 ? { warningDetails } : {}
2656
+ };
2231
2657
  });
2232
2658
  }
2233
2659
  findPublishedUpgradeTarget(artifact) {
@@ -2235,7 +2661,7 @@ var SessionArtifactStore = class {
2235
2661
  return void 0;
2236
2662
  }
2237
2663
  const byUrl = this.artifacts.get(
2238
- stableArtifactId(this.sessionId, `url:${artifact.url}`)
2664
+ stableSessionArtifactId(this.sessionId, `url:${artifact.url}`)
2239
2665
  );
2240
2666
  if (byUrl && (byUrl.storage !== "published" || byUrl.managedId === artifact.managedId)) {
2241
2667
  return byUrl;
@@ -2271,93 +2697,648 @@ var SessionArtifactStore = class {
2271
2697
  if (!existing) {
2272
2698
  return { v: 1, sessionId: this.sessionId, changes: [] };
2273
2699
  }
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: [] };
2700
+ this.denyCrossClientMutation("remove", artifactId, existing, options);
2701
+ const removeChange = {
2702
+ action: "removed",
2703
+ artifactId,
2704
+ artifact: toPublicArtifact(existing),
2705
+ reason: "explicit",
2706
+ durableTombstoneRequired: existing.durableTombstoneRequired || existing.retention !== "ephemeral" ? true : void 0,
2707
+ removedClientId: existing.clientId
2708
+ };
2709
+ const changes = [removeChange];
2710
+ const needsDurableTombstone = removeChange.durableTombstoneRequired === true;
2711
+ if (needsDurableTombstone) {
2712
+ const before = this.cloneState();
2713
+ this.artifacts.delete(artifactId);
2714
+ try {
2715
+ await this.persistChanges(changes, true);
2716
+ } catch (error) {
2717
+ this.restoreState(before);
2718
+ const warning = "artifact removal not persisted; live artifact kept";
2719
+ return {
2720
+ v: 1,
2721
+ sessionId: this.sessionId,
2722
+ changes: [],
2723
+ warnings: [warning],
2724
+ warningDetails: [
2725
+ persistenceFailureDetail({
2726
+ message: warning,
2727
+ operation: "remove",
2728
+ artifactIds: [artifactId],
2729
+ error
2730
+ })
2731
+ ]
2732
+ };
2733
+ }
2279
2734
  }
2280
- this.artifacts.delete(artifactId);
2735
+ if (!needsDurableTombstone) {
2736
+ this.artifacts.delete(artifactId);
2737
+ }
2738
+ const warnings = needsDurableTombstone ? [] : await this.persistChanges(changes, false);
2739
+ stripDurableTombstoneMarkers(changes);
2740
+ const warningDetails = detailsForPersistenceWarnings(
2741
+ warnings,
2742
+ changes,
2743
+ "remove"
2744
+ );
2281
2745
  return {
2282
2746
  v: 1,
2283
2747
  sessionId: this.sessionId,
2284
- changes: [
2285
- {
2286
- action: "removed",
2287
- artifactId,
2288
- artifact: toPublicArtifact(existing),
2289
- reason: "explicit"
2290
- }
2291
- ]
2748
+ changes,
2749
+ ...warnings.length > 0 ? { warnings } : {},
2750
+ ...warningDetails.length > 0 ? { warningDetails } : {}
2292
2751
  };
2293
2752
  });
2294
2753
  }
2295
- enqueue(operation) {
2296
- const result = this.operationQueue.then(operation, operation);
2297
- this.operationQueue = result.then(
2298
- () => void 0,
2299
- () => void 0
2300
- );
2301
- return result;
2754
+ async restore(snapshot, options = {}) {
2755
+ if (!snapshot) return [];
2756
+ return this.enqueue(async () => {
2757
+ const warnings = [...snapshot?.warnings ?? []];
2758
+ const baselineWarnings = [...warnings];
2759
+ const previousState = this.cloneState();
2760
+ const preservedLiveEphemeralArtifacts = options.preserveLiveEphemeral ? Array.from(this.artifacts.values()).filter((artifact) => artifact.retention === "ephemeral").map(cloneStoredArtifact) : [];
2761
+ let restoredCount = 0;
2762
+ this.artifacts.clear();
2763
+ this.tombstonedIds.clear();
2764
+ this.tombstonedClientIds.clear();
2765
+ this.stickyEphemeralIds.clear();
2766
+ this.markerArtifacts.clear();
2767
+ if (snapshot) {
2768
+ this.insertSeq = 0;
2769
+ this.persistenceSeq = snapshot.sequence;
2770
+ this.durableEventsSinceSnapshot = 0;
2771
+ this.consecutiveSnapshotFailures = 0;
2772
+ for (const id of snapshot.tombstonedIds) {
2773
+ this.tombstonedIds.add(id);
2774
+ }
2775
+ for (const id of snapshot.stickyEphemeralIds.slice(
2776
+ -MAX_STICKY_EPHEMERAL_IDS
2777
+ )) {
2778
+ this.stickyEphemeralIds.add(id);
2779
+ }
2780
+ const markerIds = /* @__PURE__ */ new Set([
2781
+ ...this.tombstonedIds,
2782
+ ...this.stickyEphemeralIds
2783
+ ]);
2784
+ for (const artifact of snapshot.markerArtifacts ?? []) {
2785
+ if (!markerIds.has(artifact.id)) continue;
2786
+ const markerArtifact = await this.normalizeRestoredMarkerArtifact(
2787
+ artifact,
2788
+ warnings
2789
+ );
2790
+ if (markerArtifact)
2791
+ this.markerArtifacts.set(artifact.id, markerArtifact);
2792
+ }
2793
+ }
2794
+ for (const artifact of snapshot?.artifacts ?? []) {
2795
+ try {
2796
+ const input = persistedArtifactToInput(artifact);
2797
+ if (input.retention === "pinned") {
2798
+ input.retention = "restorable";
2799
+ warnings.push(
2800
+ `pinned artifact ${artifact.id} downgraded to restorable; runtime does not support pinned retention`
2801
+ );
2802
+ }
2803
+ let normalized = await this.normalizeInput(
2804
+ input,
2805
+ ++this.receivedSeq,
2806
+ artifact.storage === "published" && !isFileArtifactUrl(artifact.url),
2807
+ {
2808
+ metadataBudget: "persisted",
2809
+ workspaceExpected: workspaceExpectedFromArtifact(artifact),
2810
+ hashWorkspaceContent: false
2811
+ }
2812
+ );
2813
+ if (this.stickyEphemeralIds.has(normalized.id) && normalized.retention !== "ephemeral") {
2814
+ normalized = {
2815
+ ...normalized,
2816
+ retention: "ephemeral",
2817
+ persistenceWarning: "sticky_override_active"
2818
+ };
2819
+ }
2820
+ const stickyApplied = normalized.persistenceWarning === "sticky_override_active";
2821
+ if (normalized.id !== artifact.id) {
2822
+ warnings.push(`skipped artifact with mismatched id ${artifact.id}`);
2823
+ continue;
2824
+ }
2825
+ const retention = normalized.retention;
2826
+ let persistenceWarning;
2827
+ if (stickyApplied) {
2828
+ persistenceWarning = "sticky_override_active";
2829
+ } else if (normalized.status !== "available") {
2830
+ persistenceWarning = "metadata_only_restore";
2831
+ }
2832
+ const stored = {
2833
+ ...normalized,
2834
+ retention,
2835
+ clientRetained: artifact.clientRetained,
2836
+ createdAt: artifact.createdAt,
2837
+ updatedAt: artifact.updatedAt,
2838
+ persistedAt: artifact.persistedAt,
2839
+ status: normalized.status,
2840
+ restoreState: "restored",
2841
+ persistenceWarning,
2842
+ durableTombstoneRequired: retention !== "ephemeral" ? true : void 0,
2843
+ insertSeq: ++this.insertSeq
2844
+ };
2845
+ this.artifacts.set(stored.id, stored);
2846
+ restoredCount++;
2847
+ } catch (error) {
2848
+ warnings.push(
2849
+ `skipped artifact restore: ${error instanceof Error ? error.message : String(error)}`
2850
+ );
2851
+ }
2852
+ }
2853
+ if (snapshot.artifacts.length > 0 && restoredCount === 0) {
2854
+ this.restoreState(previousState);
2855
+ const rollbackWarnings = [
2856
+ ...baselineWarnings,
2857
+ `${RESTORE_FAILED_WARNING_PREFIX}; kept existing live artifacts`
2858
+ ];
2859
+ this.setLastRestoreWarnings(rollbackWarnings);
2860
+ return rollbackWarnings;
2861
+ }
2862
+ if (previousState.artifacts.size > 0 && baselineWarnings.some(isArtifactSnapshotCompletenessWarning)) {
2863
+ this.restoreState(previousState);
2864
+ const rollbackWarnings = [
2865
+ ...baselineWarnings,
2866
+ restoredCount === 0 ? `${RESTORE_FAILED_WARNING_PREFIX}; kept existing live artifacts` : `${RESTORE_PARTIAL_FAILED_WARNING_PREFIX}; kept existing live artifacts`
2867
+ ];
2868
+ this.setLastRestoreWarnings(rollbackWarnings);
2869
+ return rollbackWarnings;
2870
+ }
2871
+ for (const artifact of preservedLiveEphemeralArtifacts) {
2872
+ if (this.artifacts.has(artifact.id) || this.tombstonedIds.has(artifact.id)) {
2873
+ continue;
2874
+ }
2875
+ this.artifacts.set(artifact.id, {
2876
+ ...artifact,
2877
+ insertSeq: ++this.insertSeq
2878
+ });
2879
+ }
2880
+ const evicted = await this.evictOverflow(/* @__PURE__ */ new Set(), []);
2881
+ if (evicted.length > 0) {
2882
+ warnings.push("restored artifact list pruned to live limit");
2883
+ warnings.push(...await this.persistChanges(evicted, false));
2884
+ }
2885
+ this.setLastRestoreWarnings(warnings);
2886
+ return warnings;
2887
+ });
2302
2888
  }
2303
- async normalizeInput(input, receivedSeq, trustedPublisherFromCaller) {
2304
- if (!input || typeof input !== "object") {
2305
- throw new SessionArtifactValidationError("Artifact must be an object");
2306
- }
2307
- const title = normalizeString(input.title, "title", 200, true);
2308
- const description = normalizeString(
2309
- input.description,
2310
- "description",
2311
- 1e3,
2312
- false
2313
- );
2314
- const source = input.source ?? "tool";
2315
- if (source !== "tool" && source !== "hook" && source !== "client") {
2316
- throw new SessionArtifactValidationError(
2317
- "source must be tool, hook, or client",
2318
- "source"
2319
- );
2889
+ async recordSnapshot() {
2890
+ const persistence = this.persistence;
2891
+ if (!persistence) {
2892
+ return [
2893
+ "artifact persistence unavailable; restored artifacts not snapshotted"
2894
+ ];
2320
2895
  }
2321
- const trustedPublisher = trustedPublisherFromCaller;
2322
- const workspacePath = input.workspacePath ? normalizeWorkspacePath(input.workspacePath, this.workspaceCwd) : void 0;
2323
- const managedId = normalizeManagedId(input.managedId);
2324
- const rawStorage = input.storage;
2325
- const storage = inferStorage(rawStorage, {
2326
- workspacePath,
2327
- managedId,
2328
- url: input.url,
2329
- trustedPublisher
2330
- });
2331
- const url = input.url ? normalizeArtifactUrl(input.url, trustedPublisher) : void 0;
2332
- validateLocator(storage, {
2333
- workspacePath,
2334
- managedId,
2335
- url,
2336
- trustedPublisher
2896
+ return this.enqueue(async () => {
2897
+ const recordedAt = (/* @__PURE__ */ new Date()).toISOString();
2898
+ const sequence = this.persistenceSeq + 1;
2899
+ try {
2900
+ await persistence.recordSnapshot(
2901
+ this.buildSnapshotPayload(recordedAt, sequence)
2902
+ );
2903
+ this.persistenceSeq = sequence;
2904
+ this.durableEventsSinceSnapshot = 0;
2905
+ this.consecutiveSnapshotFailures = 0;
2906
+ return [];
2907
+ } catch (error) {
2908
+ writeStderrLine(
2909
+ `[artifacts] session=${this.sessionId} action=snapshot_failed reason=${JSON.stringify(
2910
+ error instanceof Error ? error.message : String(error)
2911
+ )}`
2912
+ );
2913
+ return ["artifact snapshot not persisted"];
2914
+ }
2337
2915
  });
2338
- const metadata = normalizeMetadata(input.metadata);
2339
- const workspaceStatus = workspacePath ? await this.getInitialWorkspaceStatus(workspacePath) : void 0;
2340
- if (workspaceStatus?.escaped) {
2341
- throw new SessionArtifactValidationError(
2342
- "workspacePath must stay inside the workspace",
2343
- "workspacePath"
2916
+ }
2917
+ async normalizeRestoredMarkerArtifact(artifact, warnings) {
2918
+ try {
2919
+ const input = persistedArtifactToInput(artifact);
2920
+ if (input.retention === "pinned") {
2921
+ input.retention = "restorable";
2922
+ warnings.push(
2923
+ `pinned marker artifact ${artifact.id} downgraded to restorable; runtime does not support pinned retention`
2924
+ );
2925
+ }
2926
+ const normalized = await this.normalizeInput(
2927
+ input,
2928
+ ++this.receivedSeq,
2929
+ artifact.storage === "published" && !isFileArtifactUrl(artifact.url),
2930
+ {
2931
+ metadataBudget: "persisted",
2932
+ workspaceExpected: workspaceExpectedFromArtifact(artifact),
2933
+ hashWorkspaceContent: false
2934
+ }
2935
+ );
2936
+ if (normalized.id !== artifact.id) {
2937
+ warnings.push(
2938
+ `skipped marker artifact with mismatched id ${artifact.id}`
2939
+ );
2940
+ return void 0;
2941
+ }
2942
+ return toPersistedArtifact(
2943
+ {
2944
+ ...normalized,
2945
+ clientRetained: artifact.clientRetained,
2946
+ createdAt: artifact.createdAt,
2947
+ updatedAt: artifact.updatedAt,
2948
+ persistedAt: artifact.persistedAt
2949
+ },
2950
+ artifact.persistedAt ?? artifact.updatedAt
2951
+ );
2952
+ } catch (error) {
2953
+ warnings.push(
2954
+ `skipped marker artifact ${artifact.id}: ${error instanceof Error ? error.message : String(error)}`
2344
2955
  );
2956
+ return void 0;
2345
2957
  }
2346
- const kind = normalizeKind(
2347
- input.kind ?? inferKind({ storage, workspacePath, url })
2348
- );
2349
- const now = (/* @__PURE__ */ new Date()).toISOString();
2350
- const identityKey = buildIdentityKey({
2351
- storage,
2352
- workspacePath,
2958
+ }
2959
+ cloneState() {
2960
+ return {
2961
+ artifacts: new Map(
2962
+ Array.from(this.artifacts.entries()).map(([id, artifact]) => [
2963
+ id,
2964
+ cloneStoredArtifact(artifact)
2965
+ ])
2966
+ ),
2967
+ receivedSeq: this.receivedSeq,
2968
+ insertSeq: this.insertSeq,
2969
+ persistenceSeq: this.persistenceSeq,
2970
+ durableEventsSinceSnapshot: this.durableEventsSinceSnapshot,
2971
+ consecutiveSnapshotFailures: this.consecutiveSnapshotFailures,
2972
+ tombstonedIds: new Set(this.tombstonedIds),
2973
+ tombstonedClientIds: new Map(this.tombstonedClientIds),
2974
+ stickyEphemeralIds: new Set(this.stickyEphemeralIds),
2975
+ markerArtifacts: new Map(this.markerArtifacts),
2976
+ lastRestoreWarnings: [...this.lastRestoreWarnings],
2977
+ lastRestoreWarningDetails: [...this.lastRestoreWarningDetails]
2978
+ };
2979
+ }
2980
+ restoreState(state) {
2981
+ this.artifacts.clear();
2982
+ for (const [id, artifact] of state.artifacts) {
2983
+ this.artifacts.set(id, cloneStoredArtifact(artifact));
2984
+ }
2985
+ this.receivedSeq = state.receivedSeq;
2986
+ this.insertSeq = state.insertSeq;
2987
+ this.persistenceSeq = state.persistenceSeq;
2988
+ this.durableEventsSinceSnapshot = state.durableEventsSinceSnapshot;
2989
+ this.consecutiveSnapshotFailures = state.consecutiveSnapshotFailures;
2990
+ this.tombstonedIds.clear();
2991
+ for (const id of state.tombstonedIds) {
2992
+ this.tombstonedIds.add(id);
2993
+ }
2994
+ this.tombstonedClientIds.clear();
2995
+ for (const [id, clientId] of state.tombstonedClientIds) {
2996
+ this.tombstonedClientIds.set(id, clientId);
2997
+ }
2998
+ this.stickyEphemeralIds.clear();
2999
+ for (const id of state.stickyEphemeralIds) {
3000
+ this.stickyEphemeralIds.add(id);
3001
+ }
3002
+ this.markerArtifacts.clear();
3003
+ for (const [id, artifact] of state.markerArtifacts) {
3004
+ this.markerArtifacts.set(id, artifact);
3005
+ }
3006
+ this.setLastRestoreWarnings(state.lastRestoreWarnings);
3007
+ this.lastRestoreWarningDetails = [...state.lastRestoreWarningDetails];
3008
+ }
3009
+ async persistChanges(changes, strict = false) {
3010
+ const durableChanges = changes.filter(isDurablePersistenceChange);
3011
+ if (durableChanges.length === 0) {
3012
+ return [];
3013
+ }
3014
+ if (!this.persistence) {
3015
+ if (strict) {
3016
+ throw new SessionArtifactValidationError(
3017
+ "artifact persistence is unavailable",
3018
+ "retention"
3019
+ );
3020
+ }
3021
+ const warnings = this.downgradeDurableChanges(durableChanges);
3022
+ this.applyDurableMarkers(durableChanges);
3023
+ return warnings;
3024
+ }
3025
+ const recordedAt = (/* @__PURE__ */ new Date()).toISOString();
3026
+ for (const change of durableChanges) {
3027
+ if (change.action === "removed") continue;
3028
+ const stored = this.artifacts.get(change.artifactId);
3029
+ if (!stored) continue;
3030
+ const artifact = {
3031
+ ...toPublicArtifact(stored),
3032
+ persistedAt: recordedAt
3033
+ };
3034
+ change.artifact = artifact;
3035
+ }
3036
+ const sequence = this.persistenceSeq + 1;
3037
+ const payload = {
3038
+ v: SESSION_ARTIFACT_PERSISTENCE_VERSION,
3039
+ sessionId: this.sessionId,
3040
+ sequence,
3041
+ recordedAt,
3042
+ changes: durableChanges.map(
3043
+ (change) => toPersistedChange(change, recordedAt)
3044
+ )
3045
+ };
3046
+ try {
3047
+ await this.persistence.recordEvent(payload);
3048
+ this.persistenceSeq = sequence;
3049
+ for (const change of durableChanges) {
3050
+ if (change.action === "removed") continue;
3051
+ const stored = this.artifacts.get(change.artifactId);
3052
+ if (!stored) continue;
3053
+ stored.persistedAt = recordedAt;
3054
+ stored.durableTombstoneRequired = true;
3055
+ change.artifact = toPublicArtifact(stored);
3056
+ }
3057
+ this.applyDurableMarkers(durableChanges);
3058
+ await this.maybeRecordSnapshot(recordedAt);
3059
+ return [];
3060
+ } catch (error) {
3061
+ if (strict) {
3062
+ throw error;
3063
+ }
3064
+ const reason = error instanceof Error ? error.message : String(error);
3065
+ const artifactIds = durableChanges.map((change) => change.artifactId);
3066
+ writeStderrLine(
3067
+ `[artifacts] session=${this.sessionId} action=persist_failed sequence=${payload.sequence} artifactIds=${JSON.stringify(
3068
+ artifactIds
3069
+ )} reason=${JSON.stringify(reason)}`
3070
+ );
3071
+ const warnings = this.downgradeDurableChanges(durableChanges);
3072
+ this.applyDurableMarkers(durableChanges);
3073
+ return warnings;
3074
+ }
3075
+ }
3076
+ async maybeRecordSnapshot(recordedAt) {
3077
+ if (!this.persistence) return;
3078
+ this.durableEventsSinceSnapshot++;
3079
+ const snapshotThreshold = SNAPSHOT_AFTER_DURABLE_EVENTS * Math.min(
3080
+ MAX_SNAPSHOT_BACKOFF_MULTIPLIER,
3081
+ 2 ** this.consecutiveSnapshotFailures
3082
+ );
3083
+ if (this.durableEventsSinceSnapshot < snapshotThreshold) {
3084
+ return;
3085
+ }
3086
+ const sequence = this.persistenceSeq + 1;
3087
+ try {
3088
+ await this.persistence.recordSnapshot(
3089
+ this.buildSnapshotPayload(recordedAt, sequence)
3090
+ );
3091
+ this.persistenceSeq = sequence;
3092
+ this.durableEventsSinceSnapshot = 0;
3093
+ this.consecutiveSnapshotFailures = 0;
3094
+ } catch (error) {
3095
+ this.consecutiveSnapshotFailures = Math.min(
3096
+ this.consecutiveSnapshotFailures + 1,
3097
+ Math.log2(MAX_SNAPSHOT_BACKOFF_MULTIPLIER)
3098
+ );
3099
+ writeStderrLine(
3100
+ `[artifacts] session=${this.sessionId} action=snapshot_failed reason=${JSON.stringify(
3101
+ error instanceof Error ? error.message : String(error)
3102
+ )}`
3103
+ );
3104
+ }
3105
+ }
3106
+ buildSnapshotPayload(recordedAt, sequence) {
3107
+ const artifacts = Array.from(this.artifacts.values()).filter((artifact) => artifact.retention !== "ephemeral").sort((a, b) => a.insertSeq - b.insertSeq).map((artifact) => toPersistedArtifact(artifact, recordedAt));
3108
+ const stickyEphemeralIds = Array.from(this.stickyEphemeralIds).filter(
3109
+ (id) => this.artifacts.has(id) || this.markerArtifacts.has(id)
3110
+ );
3111
+ const markerArtifacts = this.buildMarkerArtifacts(
3112
+ recordedAt,
3113
+ stickyEphemeralIds
3114
+ );
3115
+ return {
3116
+ v: SESSION_ARTIFACT_PERSISTENCE_VERSION,
3117
+ sessionId: this.sessionId,
3118
+ sequence,
3119
+ recordedAt,
3120
+ artifacts,
3121
+ tombstonedIds: Array.from(this.tombstonedIds),
3122
+ stickyEphemeralIds,
3123
+ ...markerArtifacts.length > 0 ? { markerArtifacts } : {}
3124
+ };
3125
+ }
3126
+ buildMarkerArtifacts(recordedAt, stickyEphemeralIds) {
3127
+ const markerIds = [...this.tombstonedIds, ...stickyEphemeralIds];
3128
+ const artifacts = [];
3129
+ const seen = /* @__PURE__ */ new Set();
3130
+ for (const id of markerIds) {
3131
+ if (seen.has(id)) continue;
3132
+ seen.add(id);
3133
+ const live = this.artifacts.get(id);
3134
+ if (live) {
3135
+ artifacts.push(toPersistedArtifact(toPublicArtifact(live), recordedAt));
3136
+ continue;
3137
+ }
3138
+ const markerArtifact = this.markerArtifacts.get(id);
3139
+ if (markerArtifact) {
3140
+ artifacts.push(markerArtifact);
3141
+ }
3142
+ }
3143
+ return artifacts;
3144
+ }
3145
+ downgradeDurableChanges(changes) {
3146
+ let downgraded = false;
3147
+ let removalNotPersisted = false;
3148
+ for (const change of changes) {
3149
+ if (change.action === "removed") {
3150
+ removalNotPersisted = true;
3151
+ if (change.reason === "explicit") {
3152
+ this.rememberTombstone(change);
3153
+ this.stickyEphemeralIds.delete(change.artifactId);
3154
+ }
3155
+ continue;
3156
+ }
3157
+ const stored = this.artifacts.get(change.artifactId);
3158
+ if (!stored) continue;
3159
+ stored.retention = "ephemeral";
3160
+ stored.persistenceWarning = "persistence_unavailable";
3161
+ stored.durableTombstoneRequired = stored.durableTombstoneRequired || stored.persistedAt !== void 0;
3162
+ delete stored.persistedAt;
3163
+ change.artifact = toPublicArtifact(stored);
3164
+ downgraded = true;
3165
+ }
3166
+ const warnings = [];
3167
+ if (downgraded) {
3168
+ warnings.push(
3169
+ "artifact persistence unavailable; durable artifacts kept ephemeral"
3170
+ );
3171
+ }
3172
+ if (removalNotPersisted) {
3173
+ warnings.push("artifact removal not persisted; live removal kept");
3174
+ }
3175
+ return warnings;
3176
+ }
3177
+ applyDurableMarkers(changes) {
3178
+ for (const change of changes) {
3179
+ if (change.action === "removed") {
3180
+ if (change.reason === "explicit") {
3181
+ this.rememberTombstone(change);
3182
+ this.stickyEphemeralIds.delete(change.artifactId);
3183
+ } else if (change.reason === "eviction") {
3184
+ this.stickyEphemeralIds.delete(change.artifactId);
3185
+ this.markerArtifacts.delete(change.artifactId);
3186
+ } else if (change.reason === "unpin_to_ephemeral") {
3187
+ this.rememberStickyEphemeral(change);
3188
+ }
3189
+ continue;
3190
+ }
3191
+ if (change.artifact && change.artifact.retention !== "ephemeral") {
3192
+ this.tombstonedIds.delete(change.artifactId);
3193
+ this.tombstonedClientIds.delete(change.artifactId);
3194
+ this.stickyEphemeralIds.delete(change.artifactId);
3195
+ this.markerArtifacts.delete(change.artifactId);
3196
+ }
3197
+ }
3198
+ }
3199
+ rememberStickyEphemeral(change) {
3200
+ const artifactId = change.artifactId;
3201
+ this.stickyEphemeralIds.delete(artifactId);
3202
+ this.stickyEphemeralIds.add(artifactId);
3203
+ if (change.artifact) {
3204
+ this.markerArtifacts.set(
3205
+ artifactId,
3206
+ toPersistedArtifact(change.artifact, change.artifact.updatedAt)
3207
+ );
3208
+ }
3209
+ while (this.stickyEphemeralIds.size > MAX_STICKY_EPHEMERAL_IDS) {
3210
+ const oldest2 = this.stickyEphemeralIds.values().next().value;
3211
+ if (oldest2 === void 0) break;
3212
+ this.stickyEphemeralIds.delete(oldest2);
3213
+ this.markerArtifacts.delete(oldest2);
3214
+ }
3215
+ }
3216
+ rememberTombstone(change) {
3217
+ this.tombstonedIds.delete(change.artifactId);
3218
+ this.tombstonedIds.add(change.artifactId);
3219
+ this.tombstonedClientIds.set(
3220
+ change.artifactId,
3221
+ change.removedClientId ?? this.artifacts.get(change.artifactId)?.clientId
3222
+ );
3223
+ if (change.artifact) {
3224
+ this.markerArtifacts.set(
3225
+ change.artifactId,
3226
+ toPersistedArtifact(change.artifact, change.artifact.updatedAt)
3227
+ );
3228
+ }
3229
+ while (this.tombstonedIds.size > MAX_TOMBSTONED_IDS) {
3230
+ const oldest2 = this.tombstonedIds.values().next().value;
3231
+ if (oldest2 === void 0) break;
3232
+ writeStderrLine(
3233
+ `[artifacts] session=${this.sessionId} action=tombstone_evicted artifactId=${JSON.stringify(
3234
+ oldest2
3235
+ )} limit=${MAX_TOMBSTONED_IDS}`
3236
+ );
3237
+ this.tombstonedIds.delete(oldest2);
3238
+ this.tombstonedClientIds.delete(oldest2);
3239
+ this.markerArtifacts.delete(oldest2);
3240
+ }
3241
+ }
3242
+ setLastRestoreWarnings(warnings) {
3243
+ this.lastRestoreWarnings = [...warnings];
3244
+ this.lastRestoreWarningDetails = detailsForRestoreWarnings(warnings);
3245
+ }
3246
+ enqueue(operation) {
3247
+ const result = this.operationQueue.then(operation, operation);
3248
+ this.operationQueue = result.then(
3249
+ () => void 0,
3250
+ () => void 0
3251
+ );
3252
+ return result;
3253
+ }
3254
+ denyCrossClientMutation(action, artifactId, existing, options) {
3255
+ if (existing.source !== "client" || existing.clientId === void 0 || existing.clientId === options?.clientId) {
3256
+ return;
3257
+ }
3258
+ writeStderrLine(
3259
+ `[artifacts] session=${this.sessionId} action=${action}_denied artifactId=${artifactId} owner=${existing.clientId} requester=${options?.clientId ?? "<anonymous>"}`
3260
+ );
3261
+ throw new SessionArtifactAuthorizationError(
3262
+ this.sessionId,
3263
+ artifactId,
3264
+ existing.clientId,
3265
+ options?.clientId
3266
+ );
3267
+ }
3268
+ async normalizeInput(input, receivedSeq, trustedPublisherFromCaller, options = {}) {
3269
+ if (!input || typeof input !== "object") {
3270
+ throw new SessionArtifactValidationError("Artifact must be an object");
3271
+ }
3272
+ const title = normalizeString(input.title, "title", 200, true);
3273
+ const description = normalizeString(
3274
+ input.description,
3275
+ "description",
3276
+ 1e3,
3277
+ false
3278
+ );
3279
+ const source = input.source ?? "tool";
3280
+ if (source !== "tool" && source !== "hook" && source !== "client") {
3281
+ throw new SessionArtifactValidationError(
3282
+ "source must be tool, hook, or client",
3283
+ "source"
3284
+ );
3285
+ }
3286
+ const trustedPublisher = trustedPublisherFromCaller;
3287
+ const workspacePath = input.workspacePath ? normalizeWorkspacePath(
3288
+ input.workspacePath,
3289
+ await this.getRealWorkspaceCwdForValidation()
3290
+ ) : void 0;
3291
+ const managedId = normalizeManagedId(input.managedId);
3292
+ const rawStorage = input.storage;
3293
+ const storage = inferStorage(rawStorage, {
3294
+ workspacePath,
3295
+ managedId,
3296
+ url: input.url,
3297
+ trustedPublisher
3298
+ });
3299
+ const url = input.url ? normalizeArtifactUrl(input.url, trustedPublisher) : void 0;
3300
+ validateLocator(storage, {
3301
+ workspacePath,
3302
+ managedId,
3303
+ url,
3304
+ trustedPublisher
3305
+ });
3306
+ const retention = normalizeRetention(input.retention, {
3307
+ persistenceAvailable: this.persistence !== void 0
3308
+ });
3309
+ const workspaceStatus = workspacePath ? await this.getInitialWorkspaceStatus(
3310
+ workspacePath,
3311
+ options.workspaceExpected,
3312
+ { hashContent: options.hashWorkspaceContent !== false }
3313
+ ) : void 0;
3314
+ if (workspaceStatus?.escaped) {
3315
+ throw new SessionArtifactValidationError(
3316
+ "workspacePath must stay inside the workspace",
3317
+ "workspacePath"
3318
+ );
3319
+ }
3320
+ const metadata = withWorkspaceContentHashMetadata(
3321
+ normalizeMetadata(input.metadata, {
3322
+ budget: options.metadataBudget ?? "user"
3323
+ }),
3324
+ workspaceStatus
3325
+ );
3326
+ const kind = normalizeKind(
3327
+ input.kind ?? inferKind({ storage, workspacePath, url })
3328
+ );
3329
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3330
+ const identityKey = buildIdentityKey({
3331
+ storage,
3332
+ workspacePath,
2353
3333
  managedId,
2354
3334
  url
2355
3335
  });
2356
- const id = stableArtifactId(this.sessionId, identityKey);
3336
+ const id = stableSessionArtifactId(this.sessionId, identityKey);
2357
3337
  return {
2358
3338
  id,
2359
3339
  identityKey,
2360
3340
  receivedSeq,
3341
+ retentionExplicit: input.retention !== void 0,
2361
3342
  retentionSource: source,
2362
3343
  trustedPublisher,
2363
3344
  kind,
@@ -2371,9 +3352,12 @@ var SessionArtifactStore = class {
2371
3352
  managedId,
2372
3353
  url,
2373
3354
  mimeType: normalizeString(input.mimeType, "mimeType", 120, false),
2374
- sizeBytes: input.sizeBytes !== void 0 ? normalizeSizeBytes(input.sizeBytes) : workspaceStatus?.sizeBytes,
3355
+ sizeBytes: workspaceStatus?.sizeBytes !== void 0 ? workspaceStatus.sizeBytes : input.sizeBytes !== void 0 ? normalizeSizeBytes(input.sizeBytes) : void 0,
2375
3356
  metadata,
2376
- clientRetained: source === "client",
3357
+ retention,
3358
+ restoreState: "live",
3359
+ ...this.persistence === void 0 && retention !== "ephemeral" ? { persistenceWarning: "persistence_unavailable" } : {},
3360
+ clientRetained: source === "client" && (input.clientRetained !== void 0 ? input.clientRetained === true : true),
2377
3361
  createdAt: now,
2378
3362
  updatedAt: now,
2379
3363
  toolCallId: normalizeString(input.toolCallId, "toolCallId", 200, false),
@@ -2387,6 +3371,16 @@ var SessionArtifactStore = class {
2387
3371
  clientId: normalizeString(input.clientId, "clientId", 200, false)
2388
3372
  };
2389
3373
  }
3374
+ shouldSuppressTombstonedUpsert(artifact) {
3375
+ if (!this.tombstonedIds.has(artifact.id)) {
3376
+ return false;
3377
+ }
3378
+ const tombstonedClientId = this.tombstonedClientIds.get(artifact.id);
3379
+ if (artifact.source !== "client") {
3380
+ return false;
3381
+ }
3382
+ return !(artifact.retentionExplicit && artifact.clientId !== void 0 && artifact.clientId === tombstonedClientId);
3383
+ }
2390
3384
  async refreshWorkspaceStatuses() {
2391
3385
  const now = Date.now();
2392
3386
  const staleWorkspaceArtifacts = Array.from(this.artifacts.values()).filter((artifact) => artifact.workspacePath).filter((artifact) => shouldRefreshWorkspaceStatus(artifact, now));
@@ -2399,12 +3393,24 @@ var SessionArtifactStore = class {
2399
3393
  })
2400
3394
  );
2401
3395
  }
2402
- async getInitialWorkspaceStatus(workspacePath) {
3396
+ applyStickyEphemeralOverride(artifact) {
3397
+ if (!this.stickyEphemeralIds.has(artifact.id) || artifact.retentionExplicit || artifact.retention === "ephemeral") {
3398
+ return artifact;
3399
+ }
3400
+ return {
3401
+ ...artifact,
3402
+ retention: "ephemeral",
3403
+ retentionExplicit: true,
3404
+ persistenceWarning: "sticky_override_active"
3405
+ };
3406
+ }
3407
+ async getInitialWorkspaceStatus(workspacePath, expected, options = { hashContent: true }) {
2403
3408
  try {
2404
3409
  return await getWorkspaceStatus(
2405
- this.workspaceCwd,
2406
3410
  workspacePath,
2407
- this.getRealWorkspaceCwd()
3411
+ this.getRealWorkspaceCwd(),
3412
+ expected,
3413
+ options
2408
3414
  );
2409
3415
  } catch (error) {
2410
3416
  const reason = error instanceof Error ? error.message : String(error);
@@ -2420,16 +3426,23 @@ var SessionArtifactStore = class {
2420
3426
  }
2421
3427
  try {
2422
3428
  const status = await getWorkspaceStatus(
2423
- this.workspaceCwd,
2424
3429
  artifact.workspacePath,
2425
- this.getRealWorkspaceCwd()
3430
+ this.getRealWorkspaceCwd(),
3431
+ {
3432
+ sizeBytes: artifact.sizeBytes,
3433
+ mtimeMs: artifact.metadata?.[WORKSPACE_CONTENT_MTIME_MS_METADATA_KEY],
3434
+ sha256: artifact.metadata?.[WORKSPACE_CONTENT_SHA256_METADATA_KEY]
3435
+ }
2426
3436
  );
2427
- artifact.status = status.status;
2428
- artifact.sizeBytes = status.sizeBytes;
3437
+ const changed = isWorkspaceContentChanged(artifact, status);
3438
+ artifact.status = changed ? "changed" : status.status;
3439
+ if (!changed) {
3440
+ artifact.sizeBytes = status.sizeBytes;
3441
+ }
2429
3442
  if (status.escaped) {
2430
3443
  artifact.status = "missing";
2431
3444
  artifact.sizeBytes = void 0;
2432
- delete artifact.workspacePath;
3445
+ artifact.hideWorkspacePath = true;
2433
3446
  }
2434
3447
  artifact.lastStatAt = options.now ?? Date.now();
2435
3448
  } catch (error) {
@@ -2458,7 +3471,18 @@ var SessionArtifactStore = class {
2458
3471
  }
2459
3472
  return this.realWorkspaceCwdPromise;
2460
3473
  }
2461
- async evictOverflow(createdIds, changes) {
3474
+ async getRealWorkspaceCwdForValidation() {
3475
+ try {
3476
+ return await this.getRealWorkspaceCwd();
3477
+ } catch (error) {
3478
+ const reason = error instanceof Error ? error.message : String(error);
3479
+ throw new SessionArtifactValidationError(
3480
+ `workspacePath could not be inspected: ${reason}`,
3481
+ "workspacePath"
3482
+ );
3483
+ }
3484
+ }
3485
+ async evictOverflow(createdIds, changes, strict = false) {
2462
3486
  const removed = [];
2463
3487
  if (this.artifacts.size <= this.maxArtifacts) {
2464
3488
  return removed;
@@ -2486,10 +3510,17 @@ var SessionArtifactStore = class {
2486
3510
  action: "removed",
2487
3511
  artifactId: artifact.id,
2488
3512
  artifact: toPublicArtifact(artifact),
3513
+ durableTombstoneRequired: artifact.durableTombstoneRequired,
2489
3514
  reason: "eviction"
2490
3515
  });
2491
3516
  }
2492
3517
  const overflowCreated = Array.from(this.artifacts.values()).filter((artifact) => createdInThisBatch.has(artifact.id)).sort((a, b) => b.receivedSeq - a.receivedSeq);
3518
+ if (strict && overflowCreated.length > 0 && this.artifacts.size > this.maxArtifacts) {
3519
+ throw new SessionArtifactValidationError(
3520
+ "artifact store is full; no eviction candidate is available",
3521
+ "artifactId"
3522
+ );
3523
+ }
2493
3524
  for (const artifact of overflowCreated) {
2494
3525
  if (this.artifacts.size <= this.maxArtifacts) {
2495
3526
  break;
@@ -2538,7 +3569,12 @@ function mergeBatchArtifact(existing, next) {
2538
3569
  trustedPublisher: true,
2539
3570
  createdAt: existing.createdAt,
2540
3571
  receivedSeq: existing.receivedSeq,
3572
+ retentionExplicit: existing.retentionExplicit || next.retentionExplicit,
2541
3573
  retentionSource: existing.retentionSource,
3574
+ retention: mergeRetention(existing, next),
3575
+ restoreState: "live",
3576
+ persistenceWarning: existing.persistenceWarning ?? next.persistenceWarning,
3577
+ persistedAt: existing.persistedAt ?? next.persistedAt,
2542
3578
  clientRetained: existing.clientRetained || next.clientRetained,
2543
3579
  lastStatAt: void 0
2544
3580
  };
@@ -2552,6 +3588,8 @@ function mergeBatchArtifact(existing, next) {
2552
3588
  metadata: mergeMetadata(existing, next),
2553
3589
  clientRetained: existing.clientRetained || next.clientRetained,
2554
3590
  trustedPublisher: existing.trustedPublisher || next.trustedPublisher,
3591
+ retentionExplicit: existing.retentionExplicit || next.retentionExplicit,
3592
+ retention: mergeRetention(existing, next),
2555
3593
  lastStatAt: next.lastStatAt ?? existing.lastStatAt
2556
3594
  };
2557
3595
  }
@@ -2571,9 +3609,14 @@ function mergeArtifact(existing, incoming) {
2571
3609
  managedId: publishedUpdate ? incoming.managedId ?? existing.managedId : existing.managedId,
2572
3610
  url: publishedUpdate ? incoming.url ?? existing.url : existing.url,
2573
3611
  workspacePath: publishedUpdate || existing.storage === "published" ? void 0 : existing.workspacePath ?? incoming.workspacePath,
3612
+ hideWorkspacePath: publishedUpdate || existing.storage === "published" || incoming.workspacePath ? void 0 : existing.hideWorkspacePath,
2574
3613
  mimeType: publishedUpdate ? incoming.mimeType ?? existing.mimeType : existing.mimeType,
2575
3614
  sizeBytes: existing.storage === "published" && !publishedUpdate ? existing.sizeBytes : mergeSizeBytes(existing, incoming),
2576
3615
  metadata: existing.storage === "published" && !publishedUpdate ? existing.metadata : mergeMetadata(existing, incoming),
3616
+ retention: mergeRetention(existing, incoming),
3617
+ restoreState: "live",
3618
+ persistenceWarning: incoming.retentionExplicit && incoming.retention !== "ephemeral" ? incoming.persistenceWarning : existing.persistenceWarning ?? incoming.persistenceWarning,
3619
+ persistedAt: incoming.retentionExplicit && incoming.retention === "ephemeral" ? void 0 : existing.persistedAt ?? incoming.persistedAt,
2577
3620
  source: existing.source,
2578
3621
  retentionSource: existing.retentionSource,
2579
3622
  trustedPublisher: existing.trustedPublisher || incoming.trustedPublisher,
@@ -2585,6 +3628,7 @@ function mergeArtifact(existing, incoming) {
2585
3628
  next.title = incoming.title;
2586
3629
  next.description = incoming.description;
2587
3630
  delete next.workspacePath;
3631
+ delete next.hideWorkspacePath;
2588
3632
  }
2589
3633
  const changed = !publicArtifactsEqual(
2590
3634
  toPublicArtifact(existing),
@@ -2596,8 +3640,12 @@ function mergeArtifact(existing, incoming) {
2596
3640
  return { artifact: changed ? next : existing, changed };
2597
3641
  }
2598
3642
  __name(mergeArtifact, "mergeArtifact");
3643
+ function shouldRecordEphemeralUnpin(existing, incoming) {
3644
+ return incoming.retentionExplicit && incoming.retention === "ephemeral" && (existing.retention !== "ephemeral" || existing.persistedAt !== void 0 || existing.durableTombstoneRequired === true);
3645
+ }
3646
+ __name(shouldRecordEphemeralUnpin, "shouldRecordEphemeralUnpin");
2599
3647
  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;
3648
+ 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
3649
  }
2602
3650
  __name(publicArtifactsEqual, "publicArtifactsEqual");
2603
3651
  function metadataEqual(a, b) {
@@ -2619,6 +3667,24 @@ function mergeSizeBytes(existing, incoming) {
2619
3667
  return existing.sizeBytes;
2620
3668
  }
2621
3669
  __name(mergeSizeBytes, "mergeSizeBytes");
3670
+ function strongestRetention(a, b) {
3671
+ const rank = {
3672
+ ephemeral: 0,
3673
+ restorable: 1
3674
+ };
3675
+ return rank[b] > rank[a] ? b : a;
3676
+ }
3677
+ __name(strongestRetention, "strongestRetention");
3678
+ function mergeRetention(existing, incoming) {
3679
+ if (incoming.retentionExplicit && incoming.retention === "ephemeral") {
3680
+ return "ephemeral";
3681
+ }
3682
+ if (existing.retentionExplicit && existing.retention === "ephemeral" && !incoming.retentionExplicit) {
3683
+ return "ephemeral";
3684
+ }
3685
+ return strongestRetention(existing.retention, incoming.retention);
3686
+ }
3687
+ __name(mergeRetention, "mergeRetention");
2622
3688
  function mergeMetadata(existing, incoming) {
2623
3689
  if (!incoming.metadata || incoming.source === "hook" || incoming.source !== existing.source) {
2624
3690
  return existing.metadata;
@@ -2626,7 +3692,10 @@ function mergeMetadata(existing, incoming) {
2626
3692
  const merged = { ...existing.metadata ?? {} };
2627
3693
  let changed = false;
2628
3694
  for (const [key, value] of Object.entries(incoming.metadata)) {
2629
- if (!Object.hasOwn(merged, key)) {
3695
+ if ((key === WORKSPACE_CONTENT_SHA256_METADATA_KEY || key === WORKSPACE_CONTENT_MTIME_MS_METADATA_KEY) && merged[key] !== value) {
3696
+ merged[key] = value;
3697
+ changed = true;
3698
+ } else if (!Object.hasOwn(merged, key)) {
2630
3699
  merged[key] = value;
2631
3700
  changed = true;
2632
3701
  }
@@ -2644,7 +3713,7 @@ function mergeMetadata(existing, incoming) {
2644
3713
  }
2645
3714
  __name(mergeMetadata, "mergeMetadata");
2646
3715
  function isMetadataWithinLimit(metadata) {
2647
- return Buffer.byteLength(JSON.stringify(metadata), "utf8") <= 4096;
3716
+ return metadataBudgetBytes(metadata, "persisted") <= 4096;
2648
3717
  }
2649
3718
  __name(isMetadataWithinLimit, "isMetadataWithinLimit");
2650
3719
  function countByRetentionSource(artifacts) {
@@ -2700,6 +3769,7 @@ function removePriorChange(changes, artifactId) {
2700
3769
  }
2701
3770
  __name(removePriorChange, "removePriorChange");
2702
3771
  function toPublicArtifact(artifact) {
3772
+ const hideWorkspacePath = "hideWorkspacePath" in artifact && artifact.hideWorkspacePath === true;
2703
3773
  const {
2704
3774
  id,
2705
3775
  kind,
@@ -2714,13 +3784,16 @@ function toPublicArtifact(artifact) {
2714
3784
  mimeType,
2715
3785
  sizeBytes,
2716
3786
  metadata,
3787
+ retention,
3788
+ restoreState,
3789
+ persistenceWarning,
3790
+ persistedAt,
2717
3791
  clientRetained,
2718
3792
  createdAt,
2719
3793
  updatedAt,
2720
3794
  toolCallId,
2721
3795
  toolName,
2722
- hookEventName,
2723
- clientId
3796
+ hookEventName
2724
3797
  } = artifact;
2725
3798
  return {
2726
3799
  id,
@@ -2730,22 +3803,205 @@ function toPublicArtifact(artifact) {
2730
3803
  status,
2731
3804
  title,
2732
3805
  ...description ? { description } : {},
2733
- ...workspacePath ? { workspacePath } : {},
3806
+ ...workspacePath && !hideWorkspacePath ? { workspacePath } : {},
2734
3807
  ...managedId ? { managedId } : {},
2735
3808
  ...url ? { url } : {},
2736
3809
  ...mimeType ? { mimeType } : {},
2737
3810
  ...sizeBytes !== void 0 ? { sizeBytes } : {},
2738
3811
  ...metadata ? { metadata } : {},
3812
+ retention,
3813
+ ...restoreState ? { restoreState } : {},
3814
+ ...persistenceWarning ? { persistenceWarning } : {},
3815
+ ...persistedAt ? { persistedAt } : {},
2739
3816
  clientRetained,
2740
3817
  createdAt,
2741
3818
  updatedAt,
2742
3819
  ...toolCallId ? { toolCallId } : {},
2743
3820
  ...toolName ? { toolName } : {},
2744
- ...hookEventName ? { hookEventName } : {},
2745
- ...clientId ? { clientId } : {}
3821
+ ...hookEventName ? { hookEventName } : {}
2746
3822
  };
2747
3823
  }
2748
3824
  __name(toPublicArtifact, "toPublicArtifact");
3825
+ function persistedArtifactToInput(artifact) {
3826
+ return {
3827
+ title: artifact.title,
3828
+ kind: artifact.kind,
3829
+ storage: artifact.storage,
3830
+ description: artifact.description,
3831
+ workspacePath: artifact.workspacePath,
3832
+ managedId: artifact.managedId,
3833
+ url: artifact.url,
3834
+ mimeType: artifact.mimeType,
3835
+ sizeBytes: artifact.sizeBytes,
3836
+ metadata: artifact.metadata,
3837
+ source: artifact.source,
3838
+ retention: artifact.retention,
3839
+ clientRetained: artifact.clientRetained,
3840
+ toolCallId: artifact.toolCallId,
3841
+ toolName: artifact.toolName,
3842
+ hookEventName: artifact.hookEventName
3843
+ };
3844
+ }
3845
+ __name(persistedArtifactToInput, "persistedArtifactToInput");
3846
+ function workspaceExpectedFromArtifact(artifact) {
3847
+ if (!artifact.workspacePath) {
3848
+ return void 0;
3849
+ }
3850
+ return {
3851
+ sizeBytes: artifact.sizeBytes,
3852
+ mtimeMs: artifact.metadata?.[WORKSPACE_CONTENT_MTIME_MS_METADATA_KEY],
3853
+ sha256: artifact.metadata?.[WORKSPACE_CONTENT_SHA256_METADATA_KEY]
3854
+ };
3855
+ }
3856
+ __name(workspaceExpectedFromArtifact, "workspaceExpectedFromArtifact");
3857
+ function toPersistedChange(change, recordedAt) {
3858
+ return {
3859
+ action: change.action,
3860
+ artifactId: change.artifactId,
3861
+ ...change.artifact ? { artifact: toPersistedArtifact(change.artifact, recordedAt) } : {},
3862
+ ...change.reason ? { reason: change.reason } : {}
3863
+ };
3864
+ }
3865
+ __name(toPersistedChange, "toPersistedChange");
3866
+ function toPersistedArtifact(artifact, recordedAt) {
3867
+ return {
3868
+ id: artifact.id,
3869
+ kind: artifact.kind,
3870
+ storage: artifact.storage,
3871
+ source: artifact.source,
3872
+ status: artifact.status === "changed" ? "available" : artifact.status,
3873
+ title: artifact.title,
3874
+ ...artifact.description ? { description: artifact.description } : {},
3875
+ ...artifact.workspacePath ? { workspacePath: artifact.workspacePath } : {},
3876
+ ...artifact.managedId ? { managedId: artifact.managedId } : {},
3877
+ ...artifact.url ? { url: artifact.url } : {},
3878
+ ...artifact.mimeType ? { mimeType: artifact.mimeType } : {},
3879
+ ...artifact.sizeBytes !== void 0 ? { sizeBytes: artifact.sizeBytes } : {},
3880
+ ...artifact.metadata ? { metadata: artifact.metadata } : {},
3881
+ retention: artifact.retention,
3882
+ clientRetained: artifact.clientRetained,
3883
+ createdAt: artifact.createdAt,
3884
+ updatedAt: artifact.updatedAt,
3885
+ persistedAt: artifact.persistedAt ?? recordedAt,
3886
+ ...artifact.toolCallId ? { toolCallId: artifact.toolCallId } : {},
3887
+ ...artifact.toolName ? { toolName: artifact.toolName } : {},
3888
+ ...artifact.hookEventName ? { hookEventName: artifact.hookEventName } : {}
3889
+ };
3890
+ }
3891
+ __name(toPersistedArtifact, "toPersistedArtifact");
3892
+ function isFileArtifactUrl(raw) {
3893
+ if (typeof raw !== "string") return false;
3894
+ try {
3895
+ return new URL(raw).protocol === "file:";
3896
+ } catch {
3897
+ return false;
3898
+ }
3899
+ }
3900
+ __name(isFileArtifactUrl, "isFileArtifactUrl");
3901
+ function isArtifactSnapshotCompletenessWarning(warning) {
3902
+ if (warning.startsWith("skipped stale event sequence ")) return false;
3903
+ return warning.startsWith("skipped ") || warning.includes(" list truncated to ");
3904
+ }
3905
+ __name(isArtifactSnapshotCompletenessWarning, "isArtifactSnapshotCompletenessWarning");
3906
+ function shouldCommitBeforeDurablePersistence(change) {
3907
+ return change.action === "removed" && change.durableTombstoneRequired === true;
3908
+ }
3909
+ __name(shouldCommitBeforeDurablePersistence, "shouldCommitBeforeDurablePersistence");
3910
+ function isDurablePersistenceChange(change) {
3911
+ if (change.action === "removed" && change.durableTombstoneRequired === true) {
3912
+ return true;
3913
+ }
3914
+ if (!change.artifact) return false;
3915
+ return change.artifact.retention !== "ephemeral" || change.artifact.persistenceWarning === "persistence_unavailable";
3916
+ }
3917
+ __name(isDurablePersistenceChange, "isDurablePersistenceChange");
3918
+ function stripDurableTombstoneMarkers(changes) {
3919
+ for (const change of changes) {
3920
+ delete change.durableTombstoneRequired;
3921
+ delete change.removedClientId;
3922
+ }
3923
+ }
3924
+ __name(stripDurableTombstoneMarkers, "stripDurableTombstoneMarkers");
3925
+ function detailsForRestoreWarnings(warnings) {
3926
+ return warnings.map((warning) => {
3927
+ if (warning.startsWith(RESTORE_FAILED_WARNING_PREFIX)) {
3928
+ return {
3929
+ code: "ARTIFACT_RESTORE_FAILED",
3930
+ operation: "restore",
3931
+ durability: "unavailable",
3932
+ retryable: true,
3933
+ message: warning
3934
+ };
3935
+ }
3936
+ if (warning.startsWith(RESTORE_PARTIAL_FAILED_WARNING_PREFIX)) {
3937
+ return {
3938
+ code: "ARTIFACT_RESTORE_PARTIAL_FAILED",
3939
+ operation: "restore",
3940
+ durability: "live_only",
3941
+ retryable: true,
3942
+ message: warning
3943
+ };
3944
+ }
3945
+ return {
3946
+ code: "ARTIFACT_WARNING",
3947
+ operation: "restore",
3948
+ message: warning
3949
+ };
3950
+ });
3951
+ }
3952
+ __name(detailsForRestoreWarnings, "detailsForRestoreWarnings");
3953
+ function detailsForPersistenceWarnings(warnings, changes, operation) {
3954
+ if (warnings.length === 0) return [];
3955
+ const artifactIds = changes.filter(isDurablePersistenceChange).map((change) => change.artifactId);
3956
+ return warnings.map((warning) => {
3957
+ if (warning === "artifact persistence unavailable; durable artifacts kept ephemeral") {
3958
+ return {
3959
+ code: "ARTIFACT_PERSISTENCE_UNAVAILABLE",
3960
+ operation,
3961
+ artifactIds,
3962
+ durability: "live_only",
3963
+ retryable: false,
3964
+ message: warning
3965
+ };
3966
+ }
3967
+ if (warning === "artifact removal not persisted; live removal kept") {
3968
+ return {
3969
+ code: "ARTIFACT_REMOVAL_NOT_PERSISTED",
3970
+ operation,
3971
+ artifactIds,
3972
+ durability: "live_only",
3973
+ retryable: true,
3974
+ message: warning
3975
+ };
3976
+ }
3977
+ return {
3978
+ code: "ARTIFACT_WARNING",
3979
+ operation,
3980
+ artifactIds,
3981
+ message: warning
3982
+ };
3983
+ });
3984
+ }
3985
+ __name(detailsForPersistenceWarnings, "detailsForPersistenceWarnings");
3986
+ function persistenceFailureDetail(options) {
3987
+ const unavailable = options.error instanceof SessionArtifactValidationError && options.error.message === "artifact persistence is unavailable";
3988
+ return {
3989
+ code: unavailable ? "ARTIFACT_PERSISTENCE_UNAVAILABLE" : "ARTIFACT_PERSISTENCE_WRITE_FAILED",
3990
+ operation: options.operation,
3991
+ artifactIds: options.artifactIds,
3992
+ durability: "unavailable",
3993
+ retryable: !unavailable,
3994
+ message: options.message
3995
+ };
3996
+ }
3997
+ __name(persistenceFailureDetail, "persistenceFailureDetail");
3998
+ function cloneStoredArtifact(artifact) {
3999
+ return {
4000
+ ...artifact,
4001
+ ...artifact.metadata ? { metadata: { ...artifact.metadata } } : {}
4002
+ };
4003
+ }
4004
+ __name(cloneStoredArtifact, "cloneStoredArtifact");
2749
4005
  function inferStorage(requested, locators) {
2750
4006
  if (requested !== void 0 && typeof requested !== "string") {
2751
4007
  throw new SessionArtifactValidationError(
@@ -2839,10 +4095,6 @@ function buildIdentityKey(input) {
2839
4095
  );
2840
4096
  }
2841
4097
  __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
4098
  function managedIdForWorkspacePath(workspaceCwd, workspacePath) {
2847
4099
  return createHash("sha1").update(path2.resolve(workspaceCwd, workspacePath)).digest("hex").slice(0, 16);
2848
4100
  }
@@ -2956,6 +4208,12 @@ function normalizeArtifactUrl(raw, allowFile) {
2956
4208
  "url"
2957
4209
  );
2958
4210
  }
4211
+ if (hasSecretLikeUrlComponent(parsed)) {
4212
+ throw new SessionArtifactValidationError(
4213
+ "url must not include secret-like components",
4214
+ "url"
4215
+ );
4216
+ }
2959
4217
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:" && !(allowFile && parsed.protocol === "file:")) {
2960
4218
  throw new SessionArtifactValidationError(
2961
4219
  allowFile ? "url must use http, https, or file" : "url must use http or https",
@@ -2965,7 +4223,64 @@ function normalizeArtifactUrl(raw, allowFile) {
2965
4223
  return parsed.href;
2966
4224
  }
2967
4225
  __name(normalizeArtifactUrl, "normalizeArtifactUrl");
2968
- function normalizeMetadata(metadata) {
4226
+ function hasSecretLikeUrlComponent(parsed) {
4227
+ for (const [key, value] of parsed.searchParams) {
4228
+ if (isSecretLikeUrlText(key) || isSecretLikeUrlValue(value)) {
4229
+ return true;
4230
+ }
4231
+ }
4232
+ for (const segment of parsed.pathname.split("/").filter(Boolean)) {
4233
+ let decodedSegment = segment;
4234
+ try {
4235
+ decodedSegment = decodeURIComponent(segment);
4236
+ } catch {
4237
+ }
4238
+ if (isSecretLikeUrlValue(decodedSegment)) {
4239
+ return true;
4240
+ }
4241
+ }
4242
+ const fragment = parsed.hash.slice(1);
4243
+ return hasSecretLikeUrlFragment(fragment);
4244
+ }
4245
+ __name(hasSecretLikeUrlComponent, "hasSecretLikeUrlComponent");
4246
+ function hasSecretLikeUrlFragment(fragment) {
4247
+ if (!fragment) return false;
4248
+ const candidates = /* @__PURE__ */ new Set([fragment]);
4249
+ try {
4250
+ candidates.add(decodeURIComponent(fragment));
4251
+ } catch {
4252
+ }
4253
+ for (const candidate of candidates) {
4254
+ if (isSecretLikeUrlText(candidate) || isSecretLikeUrlValue(candidate)) {
4255
+ return true;
4256
+ }
4257
+ for (const [key, value] of new URLSearchParams(candidate)) {
4258
+ if (isSecretLikeUrlText(key) || isSecretLikeUrlValue(value)) {
4259
+ return true;
4260
+ }
4261
+ }
4262
+ }
4263
+ return false;
4264
+ }
4265
+ __name(hasSecretLikeUrlFragment, "hasSecretLikeUrlFragment");
4266
+ function isSecretLikeUrlText(value) {
4267
+ const normalized = value.replace(/([a-z])([A-Z])/g, "$1-$2");
4268
+ return /(?:^|[-_.])(token|secret|password|passwd|pwd|cookie|authorization|credential|signature|sig|api[-_]?key|access[-_]?key)(?:$|[-_.=&#])/i.test(
4269
+ normalized
4270
+ );
4271
+ }
4272
+ __name(isSecretLikeUrlText, "isSecretLikeUrlText");
4273
+ function isSecretLikeUrlValue(value) {
4274
+ return SECRET_TOKEN_VALUE_PATTERN.test(value.trim());
4275
+ }
4276
+ __name(isSecretLikeUrlValue, "isSecretLikeUrlValue");
4277
+ function isSecretLikeMetadataValue(value) {
4278
+ return /^(?:bearer\s+\S{8,}|sk-[A-Za-z0-9_-]{12,}|(?:gh[pousr]|github_pat)_[A-Za-z0-9_/-]{12,})$/i.test(
4279
+ value.trim()
4280
+ );
4281
+ }
4282
+ __name(isSecretLikeMetadataValue, "isSecretLikeMetadataValue");
4283
+ function normalizeMetadata(metadata, options = {}) {
2969
4284
  if (metadata === void 0) {
2970
4285
  return void 0;
2971
4286
  }
@@ -2977,6 +4292,12 @@ function normalizeMetadata(metadata) {
2977
4292
  }
2978
4293
  const normalized = {};
2979
4294
  for (const [key, value] of Object.entries(metadata)) {
4295
+ if (isPrototypeMetadataKey(key)) {
4296
+ continue;
4297
+ }
4298
+ if (options.budget !== "persisted" && isReservedWorkspaceMetadataKey(key)) {
4299
+ continue;
4300
+ }
2980
4301
  if (!key) {
2981
4302
  throw new SessionArtifactValidationError(
2982
4303
  "metadata keys must not be empty",
@@ -2995,6 +4316,12 @@ function normalizeMetadata(metadata) {
2995
4316
  "metadata"
2996
4317
  );
2997
4318
  }
4319
+ if (isSecretLikeUrlText(key)) {
4320
+ throw new SessionArtifactValidationError(
4321
+ "metadata keys must not contain secret-like names",
4322
+ "metadata"
4323
+ );
4324
+ }
2998
4325
  if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
2999
4326
  throw new SessionArtifactValidationError(
3000
4327
  "metadata values must be primitive",
@@ -3013,12 +4340,18 @@ function normalizeMetadata(metadata) {
3013
4340
  "metadata"
3014
4341
  );
3015
4342
  }
4343
+ if (typeof value === "string" && !isReservedWorkspaceMetadataKey(key) && isSecretLikeMetadataValue(value)) {
4344
+ throw new SessionArtifactValidationError(
4345
+ "metadata string values must not contain secret-like tokens",
4346
+ "metadata"
4347
+ );
4348
+ }
3016
4349
  normalized[key] = value;
3017
4350
  }
3018
4351
  if (Object.keys(normalized).length === 0) {
3019
4352
  return void 0;
3020
4353
  }
3021
- if (Buffer.byteLength(JSON.stringify(normalized), "utf8") > 4096) {
4354
+ if (metadataBudgetBytes(normalized, options.budget ?? "user") > 4096) {
3022
4355
  throw new SessionArtifactValidationError(
3023
4356
  "metadata must be 4096 bytes or fewer",
3024
4357
  "metadata"
@@ -3027,6 +4360,25 @@ function normalizeMetadata(metadata) {
3027
4360
  return normalized;
3028
4361
  }
3029
4362
  __name(normalizeMetadata, "normalizeMetadata");
4363
+ function normalizeRetention(value, options) {
4364
+ if (value === void 0) {
4365
+ return options.persistenceAvailable ? "restorable" : "ephemeral";
4366
+ }
4367
+ if (value === "ephemeral" || value === "restorable") {
4368
+ return value;
4369
+ }
4370
+ if (value === "pinned") {
4371
+ throw new SessionArtifactValidationError(
4372
+ "pinned retention is not supported by session_artifacts_persistence",
4373
+ "retention"
4374
+ );
4375
+ }
4376
+ throw new SessionArtifactValidationError(
4377
+ "retention must be ephemeral or restorable",
4378
+ "retention"
4379
+ );
4380
+ }
4381
+ __name(normalizeRetention, "normalizeRetention");
3030
4382
  function normalizeSizeBytes(value) {
3031
4383
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
3032
4384
  throw new SessionArtifactValidationError(
@@ -3052,21 +4404,83 @@ function inferKind(input) {
3052
4404
  return input.workspacePath ? "file" : "other";
3053
4405
  }
3054
4406
  __name(inferKind, "inferKind");
3055
- async function getWorkspaceStatus(workspaceCwd, workspacePath, realWorkspaceCwd) {
3056
- const absolutePath = path2.resolve(workspaceCwd, workspacePath);
4407
+ async function getWorkspaceStatus(workspacePath, realWorkspaceCwd, expected, options = { hashContent: true }) {
3057
4408
  const realWorkspace = await realWorkspaceCwd;
4409
+ const absolutePath = path2.resolve(realWorkspace, workspacePath);
3058
4410
  try {
3059
4411
  const realPath = await fs2.realpath(absolutePath);
3060
4412
  const relative = path2.relative(realWorkspace, realPath);
3061
4413
  if (!relative || isOutsidePath(relative)) {
3062
4414
  return { status: "missing", escaped: true };
3063
4415
  }
3064
- const stat = await fs2.stat(realPath);
3065
- return {
3066
- status: "available",
3067
- ...stat.isFile() ? { sizeBytes: stat.size } : {}
3068
- };
4416
+ const preOpenStat = await fs2.lstat(realPath);
4417
+ const handle = await fs2.open(
4418
+ realPath,
4419
+ fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW
4420
+ );
4421
+ try {
4422
+ const stat = await handle.stat();
4423
+ if (!isSameFile(preOpenStat, stat)) {
4424
+ return { status: "missing", escaped: true };
4425
+ }
4426
+ if (stat.isFile()) {
4427
+ const expectedMtimeMs = typeof expected?.mtimeMs === "number" ? expected.mtimeMs : void 0;
4428
+ const expectedSha256 = typeof expected?.sha256 === "string" ? expected.sha256 : void 0;
4429
+ const unchanged = expected?.sizeBytes === stat.size && expectedMtimeMs === stat.mtimeMs;
4430
+ const sizeChanged = expected?.sizeBytes !== void 0 && expected.sizeBytes !== stat.size;
4431
+ if (sizeChanged) {
4432
+ return {
4433
+ status: "changed",
4434
+ sizeBytes: stat.size,
4435
+ mtimeMs: stat.mtimeMs
4436
+ };
4437
+ }
4438
+ if (unchanged) {
4439
+ return {
4440
+ status: "available",
4441
+ sizeBytes: stat.size,
4442
+ mtimeMs: stat.mtimeMs
4443
+ };
4444
+ }
4445
+ if (stat.size > MAX_WORKSPACE_HASH_BYTES) {
4446
+ return {
4447
+ status: expectedSha256 ? "changed" : "available",
4448
+ sizeBytes: stat.size,
4449
+ mtimeMs: stat.mtimeMs
4450
+ };
4451
+ }
4452
+ if (!options.hashContent) {
4453
+ return {
4454
+ status: expectedSha256 ? "changed" : "available",
4455
+ sizeBytes: stat.size,
4456
+ mtimeMs: stat.mtimeMs
4457
+ };
4458
+ }
4459
+ const sha256 = await hashFile(handle);
4460
+ if (expectedSha256 && sha256 !== expectedSha256) {
4461
+ return {
4462
+ status: "changed",
4463
+ sizeBytes: stat.size,
4464
+ mtimeMs: stat.mtimeMs
4465
+ };
4466
+ }
4467
+ return {
4468
+ status: "available",
4469
+ sizeBytes: stat.size,
4470
+ mtimeMs: stat.mtimeMs,
4471
+ sha256
4472
+ };
4473
+ }
4474
+ return {
4475
+ status: "available"
4476
+ };
4477
+ } finally {
4478
+ await handle.close();
4479
+ }
3069
4480
  } catch (error) {
4481
+ if (isNoFollowSymlinkError(error)) {
4482
+ return { status: "missing", escaped: true };
4483
+ }
3070
4484
  if (!isNotFoundError(error)) {
3071
4485
  throw error;
3072
4486
  }
@@ -3077,6 +4491,41 @@ async function getWorkspaceStatus(workspaceCwd, workspacePath, realWorkspaceCwd)
3077
4491
  }
3078
4492
  }
3079
4493
  __name(getWorkspaceStatus, "getWorkspaceStatus");
4494
+ function isSameFile(before, after) {
4495
+ return before.dev === after.dev && before.ino === after.ino;
4496
+ }
4497
+ __name(isSameFile, "isSameFile");
4498
+ async function hashFile(handle) {
4499
+ const hash = createHash("sha256");
4500
+ for await (const chunk of handle.createReadStream({ start: 0 })) {
4501
+ hash.update(chunk);
4502
+ }
4503
+ return hash.digest("hex");
4504
+ }
4505
+ __name(hashFile, "hashFile");
4506
+ function withWorkspaceContentHashMetadata(metadata, workspaceStatus) {
4507
+ if (workspaceStatus?.status !== "available" || !workspaceStatus.sha256) {
4508
+ return metadata;
4509
+ }
4510
+ const next = {
4511
+ ...metadata ?? {},
4512
+ [WORKSPACE_CONTENT_SHA256_METADATA_KEY]: workspaceStatus.sha256,
4513
+ ...workspaceStatus.mtimeMs !== void 0 ? { [WORKSPACE_CONTENT_MTIME_MS_METADATA_KEY]: workspaceStatus.mtimeMs } : {}
4514
+ };
4515
+ return next;
4516
+ }
4517
+ __name(withWorkspaceContentHashMetadata, "withWorkspaceContentHashMetadata");
4518
+ function isWorkspaceContentChanged(artifact, status) {
4519
+ if (status.status !== "available") {
4520
+ return false;
4521
+ }
4522
+ const expectedSha256 = artifact.metadata?.[WORKSPACE_CONTENT_SHA256_METADATA_KEY];
4523
+ if (artifact.sizeBytes !== void 0 && status.sizeBytes !== void 0 && status.sizeBytes !== artifact.sizeBytes) {
4524
+ return true;
4525
+ }
4526
+ return typeof expectedSha256 === "string" && status.sha256 !== void 0 && status.sha256 !== expectedSha256;
4527
+ }
4528
+ __name(isWorkspaceContentChanged, "isWorkspaceContentChanged");
3080
4529
  async function danglingSymlinkEscapesWorkspace(absolutePath, realWorkspace) {
3081
4530
  try {
3082
4531
  const stat = await fs2.lstat(absolutePath);
@@ -3109,6 +4558,13 @@ function isNotFoundError(error) {
3109
4558
  return code === "ENOENT" || code === "ENOTDIR";
3110
4559
  }
3111
4560
  __name(isNotFoundError, "isNotFoundError");
4561
+ function isNoFollowSymlinkError(error) {
4562
+ if (typeof error !== "object" || error === null || !("code" in error)) {
4563
+ return false;
4564
+ }
4565
+ return error.code === "ELOOP";
4566
+ }
4567
+ __name(isNoFollowSymlinkError, "isNoFollowSymlinkError");
3112
4568
  function isOutsidePath(relative) {
3113
4569
  return relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative);
3114
4570
  }
@@ -3276,6 +4732,18 @@ function isBridgeAutoMemoryTopic(value) {
3276
4732
  return value === "user" || value === "feedback" || value === "project" || value === "reference";
3277
4733
  }
3278
4734
  __name(isBridgeAutoMemoryTopic, "isBridgeAutoMemoryTopic");
4735
+ function touchedScopesFromTopics(topics) {
4736
+ const scopes = /* @__PURE__ */ new Set();
4737
+ for (const topic of topics) {
4738
+ scopes.add(topic === "user" || topic === "feedback" ? "user" : "project");
4739
+ }
4740
+ return ["user", "project"].filter((scope) => scopes.has(scope));
4741
+ }
4742
+ __name(touchedScopesFromTopics, "touchedScopesFromTopics");
4743
+ function isBridgeMemoryScope(value) {
4744
+ return value === "user" || value === "project";
4745
+ }
4746
+ __name(isBridgeMemoryScope, "isBridgeMemoryScope");
3279
4747
  function parseWorkspaceMemoryForgetMatch(value) {
3280
4748
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
3281
4749
  return null;
@@ -3299,14 +4767,17 @@ function parseWorkspaceMemoryForgetResult(response) {
3299
4767
  const summary = record["summary"];
3300
4768
  const removedEntries = record["removedEntries"];
3301
4769
  const touchedTopics = record["touchedTopics"];
4770
+ const touchedScopes = record["touchedScopes"];
3302
4771
  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)) {
4772
+ 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
4773
  throw new Error("Malformed workspace memory forget response");
3305
4774
  }
4775
+ const parsedTouchedTopics = touchedTopics;
3306
4776
  return {
3307
4777
  ...summary === void 0 ? {} : { summary },
3308
4778
  removedEntries: parsedRemovedEntries,
3309
- touchedTopics
4779
+ touchedTopics: parsedTouchedTopics,
4780
+ touchedScopes: touchedScopes === void 0 ? touchedScopesFromTopics(parsedTouchedTopics) : touchedScopes
3310
4781
  };
3311
4782
  }
3312
4783
  __name(parseWorkspaceMemoryForgetResult, "parseWorkspaceMemoryForgetResult");
@@ -3446,6 +4917,11 @@ function broadcastTurnError(entry, sessionId, err, promptId, originatorClientId)
3446
4917
  );
3447
4918
  }
3448
4919
  entry.retryAllowed = true;
4920
+ entry.turnError = {
4921
+ message,
4922
+ ...code ? { code } : {},
4923
+ ...errorKind ? { errorKind } : {}
4924
+ };
3449
4925
  try {
3450
4926
  entry.events.publish({
3451
4927
  type: "turn_error",
@@ -3488,6 +4964,7 @@ function extractPromptText(prompt) {
3488
4964
  __name(extractPromptText, "extractPromptText");
3489
4965
  var DEFAULT_INIT_TIMEOUT_MS = 1e4;
3490
4966
  var PERSIST_TIMEOUT_MS = 5e3;
4967
+ var MAX_PARENT_PERSIST_ATTEMPTS = 3;
3491
4968
  var MCP_RESTART_TIMEOUT_MS = 3e5;
3492
4969
  var WORKSPACE_MEMORY_REMEMBER_TIMEOUT_MS = 3e5;
3493
4970
  var MCP_OAUTH_TIMEOUT_MS = 6e5;
@@ -3495,6 +4972,7 @@ var DAEMON_RETRY_META_KEY = "qwen.daemon.retry";
3495
4972
  var DAEMON_CONTINUE_META_KEY = "qwen.daemon.continueLastTurn";
3496
4973
  var SESSION_RECAP_TIMEOUT_MS = 6e4;
3497
4974
  var SESSION_BTW_TIMEOUT_MS = 6e4;
4975
+ var SESSION_TRANSCRIPT_TIMEOUT_MS = 6e4;
3498
4976
  var SHELL_COMMAND_TIMEOUT_MS = 12e4;
3499
4977
  var MAX_SHELL_OUTPUT_FOR_HISTORY = 1e4;
3500
4978
  var MAX_MID_TURN_QUEUE_DEPTH = 20;
@@ -3523,6 +5001,27 @@ function createAcpSessionBridge(opts) {
3523
5001
  } else {
3524
5002
  maxSessions = opts.maxSessions;
3525
5003
  }
5004
+ const reserveFreshSession = /* @__PURE__ */ __name((context) => opts.freshSessionAdmission?.(context), "reserveFreshSession");
5005
+ const releaseFreshSessionReservation = /* @__PURE__ */ __name((reservation) => {
5006
+ if (!reservation) return;
5007
+ try {
5008
+ reservation.release();
5009
+ } catch (err) {
5010
+ opts.onDiagnosticLine?.(
5011
+ `qwen serve: fresh session admission release failed: ${err instanceof Error ? err.message : String(err)}`,
5012
+ "warn"
5013
+ );
5014
+ }
5015
+ }, "releaseFreshSessionReservation");
5016
+ const emitSessionLifecycle = /* @__PURE__ */ __name((event) => {
5017
+ try {
5018
+ opts.sessionLifecycle?.(event);
5019
+ } catch (err) {
5020
+ const message = `qwen serve: session lifecycle callback failed: ${err instanceof Error ? err.message : String(err)}`;
5021
+ opts.onDiagnosticLine?.(message, "warn");
5022
+ writeStderrLine(message);
5023
+ }
5024
+ }, "emitSessionLifecycle");
3526
5025
  if (defaultSessionScope !== "single" && defaultSessionScope !== "thread") {
3527
5026
  throw new TypeError(
3528
5027
  `Invalid sessionScope: ${JSON.stringify(defaultSessionScope)}. Expected 'single' or 'thread'.`
@@ -3534,6 +5033,9 @@ function createAcpSessionBridge(opts) {
3534
5033
  `Invalid eventRingSize: ${opts.eventRingSize}. Must be a positive integer in [1, ${MAX_EVENT_RING_SIZE}].`
3535
5034
  );
3536
5035
  }
5036
+ const compactedReplayMaxBytes = normalizeCompactedReplayMaxBytes(
5037
+ opts.compactedReplayMaxBytes
5038
+ );
3537
5039
  const channelFactory = opts.channelFactory ?? defaultSpawnChannelFactory;
3538
5040
  const childEnvOverrides = opts.childEnvOverrides ? Object.freeze({ ...opts.childEnvOverrides }) : Object.freeze({});
3539
5041
  const initTimeoutMs = opts.initializeTimeoutMs ?? DEFAULT_INIT_TIMEOUT_MS;
@@ -3704,14 +5206,32 @@ function createAcpSessionBridge(opts) {
3704
5206
  const aliveChannels = /* @__PURE__ */ new Set();
3705
5207
  let inFlightChannelSpawn;
3706
5208
  const byId = /* @__PURE__ */ new Map();
3707
- const toSessionSummary = /* @__PURE__ */ __name((entry) => ({
3708
- sessionId: entry.sessionId,
3709
- workspaceCwd: entry.workspaceCwd,
3710
- createdAt: entry.createdAt,
3711
- displayName: entry.displayName,
3712
- clientCount: entry.clientIds.size,
3713
- hasActivePrompt: entry.promptActive
3714
- }), "toSessionSummary");
5209
+ const toSessionSummary = /* @__PURE__ */ __name((entry) => {
5210
+ let isWaitingForPermission = false;
5211
+ let isWaitingForUserQuestion = false;
5212
+ for (const interaction of entry.pendingInteractions.values()) {
5213
+ if (interaction.kind === "user_question") {
5214
+ isWaitingForUserQuestion = true;
5215
+ } else {
5216
+ isWaitingForPermission = true;
5217
+ }
5218
+ }
5219
+ return {
5220
+ sessionId: entry.sessionId,
5221
+ workspaceCwd: entry.workspaceCwd,
5222
+ createdAt: entry.createdAt,
5223
+ displayName: entry.displayName,
5224
+ ...entry.parentSessionId ? { parentSessionId: entry.parentSessionId } : {},
5225
+ clientCount: entry.clientIds.size,
5226
+ hasActivePrompt: entry.promptActive,
5227
+ isWaitingForPermission,
5228
+ isWaitingForUserQuestion,
5229
+ pendingInteractionCount: entry.pendingInteractions.size,
5230
+ hasTurnError: entry.turnError !== void 0,
5231
+ ...entry.turnError !== void 0 ? { turnError: entry.turnError } : {},
5232
+ pendingInteractions: [...entry.pendingInteractions.values()]
5233
+ };
5234
+ }, "toSessionSummary");
3715
5235
  const permissionConsensusQuorum = opts.permissionConsensusQuorum;
3716
5236
  if (permissionConsensusQuorum !== void 0 && (!Number.isInteger(permissionConsensusQuorum) || permissionConsensusQuorum < 1)) {
3717
5237
  throw new Error(
@@ -3770,6 +5290,22 @@ function createAcpSessionBridge(opts) {
3770
5290
  entry.clientIds.set(clientId, count - 1);
3771
5291
  }
3772
5292
  }, "unregisterClient");
5293
+ const rollbackAttachRegistration = /* @__PURE__ */ __name(async (entry, clientId, attachCountDelta = 1) => {
5294
+ entry.attachCount = Math.max(0, entry.attachCount - attachCountDelta);
5295
+ unregisterClient(entry, clientId);
5296
+ if (entry.spawnOwnerWantedKill && entry.attachCount === 0 && entry.events.subscriberCount === 0) {
5297
+ await bridgeApi.killSession(entry.sessionId).catch(() => {
5298
+ });
5299
+ } else if (entry.clientIds.size === 0 && entry.events.subscriberCount === 0 && !entry.promptActive) {
5300
+ await closeSessionImpl(entry.sessionId, void 0, {
5301
+ reason: "last_client_detached"
5302
+ }).catch((err) => {
5303
+ writeStderrLine(
5304
+ `qwen serve: close-on-attach-rollback failed for ${JSON.stringify(entry.sessionId)}: ${err instanceof Error ? err.stack ?? err.message : String(err)}`
5305
+ );
5306
+ });
5307
+ }
5308
+ }, "rollbackAttachRegistration");
3773
5309
  const resolveTrustedClientId = /* @__PURE__ */ __name((entry, clientId) => {
3774
5310
  if (clientId === void 0) return void 0;
3775
5311
  if (!entry.clientIds.has(clientId)) {
@@ -3850,7 +5386,11 @@ function createAcpSessionBridge(opts) {
3850
5386
  inputTokens,
3851
5387
  outputTokens,
3852
5388
  durationMs
3853
- )
5389
+ ),
5390
+ // `create_sub_session` tool: forward the request/response hook so a child
5391
+ // tool can ask the daemon to spawn a sub-session and (for 'first-turn')
5392
+ // return its result. Omitted → the method reports daemon-only.
5393
+ opts.onCreateSubSession
3854
5394
  );
3855
5395
  const connection = new ClientSideConnection(() => client, channel.stream);
3856
5396
  const info = {
@@ -3915,6 +5455,12 @@ function createAcpSessionBridge(opts) {
3915
5455
  }
3916
5456
  byId.delete(sid);
3917
5457
  telemetry.metrics?.sessionLifecycle("die");
5458
+ emitSessionLifecycle({
5459
+ type: "removed",
5460
+ sessionId: sid,
5461
+ workspaceCwd: sessEntry.workspaceCwd,
5462
+ reason: "channel_closed"
5463
+ });
3918
5464
  info.client.markSessionClosed(sid);
3919
5465
  if (defaultEntry === sessEntry) defaultEntry = void 0;
3920
5466
  sessEntry.events.close();
@@ -3963,10 +5509,12 @@ function createAcpSessionBridge(opts) {
3963
5509
  }
3964
5510
  }
3965
5511
  __name(ensureChannel, "ensureChannel");
3966
- async function doSpawn(modelServiceId, effectiveScope, requestedClientId) {
5512
+ async function doSpawn(modelServiceId, effectiveScope, approvalMode, requestedClientId, onSessionRegistered, parentSessionId) {
3967
5513
  const ci = await ensureChannel();
3968
5514
  ci.sessionSpawnsInFlight++;
3969
5515
  let sessionRegistered = false;
5516
+ let sessionRemovedDuringInitialization = false;
5517
+ let initializedSessionId;
3970
5518
  let newSessionResp;
3971
5519
  try {
3972
5520
  try {
@@ -4001,12 +5549,58 @@ function createAcpSessionBridge(opts) {
4001
5549
  const entry = createSessionEntry(
4002
5550
  ci,
4003
5551
  newSessionResp.sessionId,
4004
- boundWorkspace
5552
+ boundWorkspace,
5553
+ void 0,
5554
+ { parentSessionId }
4005
5555
  );
5556
+ initializedSessionId = entry.sessionId;
4006
5557
  sessionRegistered = true;
5558
+ onSessionRegistered?.();
4007
5559
  seedSnapshotCaches(entry, newSessionResp);
4008
5560
  const clientId = registerClient(entry, requestedClientId);
4009
- if (effectiveScope === "single" && !defaultEntry) defaultEntry = entry;
5561
+ let parentSessionPersisted;
5562
+ if (entry.parentSessionId) {
5563
+ const parentDeadline = Date.now() + initTimeoutMs;
5564
+ let lastParentErr;
5565
+ for (let attempt = 1; attempt <= MAX_PARENT_PERSIST_ATTEMPTS; attempt++) {
5566
+ const remaining = parentDeadline - Date.now();
5567
+ if (remaining <= 0) {
5568
+ parentSessionPersisted = false;
5569
+ lastParentErr = "deadline exceeded";
5570
+ break;
5571
+ }
5572
+ try {
5573
+ const parentResult = await Promise.race([
5574
+ withTimeout(
5575
+ entry.connection.extMethod(
5576
+ SERVE_CONTROL_EXT_METHODS.sessionParent,
5577
+ {
5578
+ sessionId: entry.sessionId,
5579
+ parentSessionId: entry.parentSessionId
5580
+ }
5581
+ ),
5582
+ remaining,
5583
+ "sessionParent"
5584
+ ),
5585
+ getTransportClosedReject(entry)
5586
+ ]);
5587
+ parentSessionPersisted = parentResult?.persisted === true;
5588
+ break;
5589
+ } catch (err) {
5590
+ lastParentErr = err instanceof Error ? err.message : String(err);
5591
+ const terminal = err instanceof BridgeTimeoutError || err instanceof BridgeChannelClosedError || attempt === MAX_PARENT_PERSIST_ATTEMPTS;
5592
+ if (terminal) {
5593
+ parentSessionPersisted = false;
5594
+ break;
5595
+ }
5596
+ }
5597
+ }
5598
+ if (parentSessionPersisted === false) {
5599
+ writeStderrLine(
5600
+ `qwen serve: parentSessionId for ${entry.sessionId} was not persisted (${lastParentErr ?? "unknown"}) \u2014 the parent link is live-only until restart (reported to the caller via parentSessionPersisted=false)`
5601
+ );
5602
+ }
5603
+ }
4010
5604
  if (modelServiceId) {
4011
5605
  await applyModelServiceId(
4012
5606
  entry,
@@ -4016,22 +5610,51 @@ function createAcpSessionBridge(opts) {
4016
5610
  ).catch(() => {
4017
5611
  });
4018
5612
  }
5613
+ if (approvalMode) {
5614
+ try {
5615
+ await applyApprovalMode(entry, approvalMode, false, clientId);
5616
+ } catch (err) {
5617
+ try {
5618
+ await closeSessionImpl(entry.sessionId, void 0, {
5619
+ reason: "approval_mode_initialization_failed"
5620
+ });
5621
+ sessionRemovedDuringInitialization = true;
5622
+ } catch {
5623
+ }
5624
+ throw err;
5625
+ }
5626
+ }
4019
5627
  if (!byId.has(entry.sessionId)) {
4020
5628
  throw new Error(
4021
- `Session ${entry.sessionId} died during model-switch initialization`
5629
+ `Session ${entry.sessionId} died during session initialization`
4022
5630
  );
4023
5631
  }
5632
+ if (effectiveScope === "single" && !defaultEntry) defaultEntry = entry;
4024
5633
  return {
4025
5634
  sessionId: entry.sessionId,
4026
5635
  workspaceCwd: entry.workspaceCwd,
4027
5636
  attached: false,
4028
5637
  clientId,
4029
- createdAt: entry.createdAt
5638
+ createdAt: entry.createdAt,
5639
+ ...entry.parentSessionId ? { parentSessionPersisted: parentSessionPersisted === true } : {}
4030
5640
  };
4031
5641
  } finally {
4032
5642
  ci.sessionSpawnsInFlight = Math.max(0, ci.sessionSpawnsInFlight - 1);
4033
5643
  if (!sessionRegistered) {
4034
5644
  await reapPendingEmptyChannel(ci);
5645
+ } else if (sessionRemovedDuringInitialization && hasNoChannelWork(ci)) {
5646
+ await reapPendingEmptyChannel(ci);
5647
+ if (!ci.isDying) {
5648
+ await startIdleTimer(
5649
+ ci,
5650
+ `approval-mode initialization failure "${initializedSessionId}"`
5651
+ );
5652
+ }
5653
+ } else if (sessionRegistered && hasNoChannelWork(ci) && !ci.isDying) {
5654
+ await startIdleTimer(
5655
+ ci,
5656
+ `session orphaned during initialization "${initializedSessionId}"`
5657
+ );
4035
5658
  }
4036
5659
  }
4037
5660
  }
@@ -4072,30 +5695,146 @@ function createAcpSessionBridge(opts) {
4072
5695
  requestedModelId: modelId,
4073
5696
  error: err instanceof Error ? err.message : String(err)
4074
5697
  },
4075
- ...originatorClientId ? { originatorClientId } : {}
4076
- });
4077
- throw err;
5698
+ ...originatorClientId ? { originatorClientId } : {}
5699
+ });
5700
+ throw err;
5701
+ } finally {
5702
+ entry.modelRoundtripInFlight = false;
5703
+ if (succeeded) {
5704
+ void reconcileAfterRoundtrip(entry, "model");
5705
+ } else {
5706
+ writeStderrLine(
5707
+ `[reconcile] session=${entry.sessionId} target=model action=skipped reason=roundtrip_failed`
5708
+ );
5709
+ }
5710
+ }
5711
+ });
5712
+ entry.modelChangeQueue = work.then(
5713
+ () => void 0,
5714
+ () => void 0
5715
+ );
5716
+ return work;
5717
+ }
5718
+ __name(applyModelServiceId, "applyModelServiceId");
5719
+ async function applyApprovalMode(entry, mode, persist, originatorClientId) {
5720
+ if (persist && !persistApprovalMode) {
5721
+ throw new Error(
5722
+ "setSessionApprovalMode called with `persist: true` but no `persistApprovalMode` callback wired in BridgeOptions. runQwenServe wires the production callback; direct embeds and tests must opt in or omit `persist`."
5723
+ );
5724
+ }
5725
+ const approvalWork = entry.approvalModeQueue.then(async () => {
5726
+ entry.approvalModeRoundtripInFlight = true;
5727
+ let succeeded = false;
5728
+ try {
5729
+ const response = await Promise.race([
5730
+ withTimeout(
5731
+ entry.connection.extMethod(
5732
+ SERVE_CONTROL_EXT_METHODS.sessionApprovalMode,
5733
+ { sessionId: entry.sessionId, mode }
5734
+ ),
5735
+ initTimeoutMs,
5736
+ SERVE_CONTROL_EXT_METHODS.sessionApprovalMode
5737
+ ),
5738
+ getTransportClosedReject(entry)
5739
+ ]);
5740
+ if (typeof response.current !== "string" || !KNOWN_APPROVAL_MODES.has(response.current)) {
5741
+ throw new Error(
5742
+ `Agent returned unknown approval mode: ${JSON.stringify(response.current)}`
5743
+ );
5744
+ }
5745
+ let persisted = false;
5746
+ if (persist) {
5747
+ try {
5748
+ await withTimeout(
5749
+ persistApprovalMode?.(boundWorkspace, mode) ?? Promise.resolve(),
5750
+ PERSIST_TIMEOUT_MS,
5751
+ "persistApprovalMode"
5752
+ );
5753
+ persisted = persistApprovalMode !== void 0;
5754
+ } catch (err) {
5755
+ writeStderrLine(
5756
+ `setSessionApprovalMode: persist failed: ${err instanceof Error ? err.message : String(err)}`
5757
+ );
5758
+ }
5759
+ }
5760
+ publishApprovalModeChanged(
5761
+ entry,
5762
+ {
5763
+ previous: response.previous,
5764
+ next: response.current,
5765
+ persisted
5766
+ },
5767
+ originatorClientId
5768
+ );
5769
+ if (persisted) {
5770
+ broadcastWorkspaceEvent(
5771
+ {
5772
+ type: "approval_mode_changed",
5773
+ data: {
5774
+ sessionId: entry.sessionId,
5775
+ previous: response.previous,
5776
+ next: response.current,
5777
+ persisted
5778
+ },
5779
+ ...originatorClientId ? { originatorClientId } : {}
5780
+ },
5781
+ entry.sessionId
5782
+ );
5783
+ for (const peer of byId.values()) {
5784
+ if (peer.sessionId === entry.sessionId) {
5785
+ continue;
5786
+ }
5787
+ peer.currentApprovalMode = response.current;
5788
+ }
5789
+ }
5790
+ succeeded = true;
5791
+ return {
5792
+ sessionId: entry.sessionId,
5793
+ mode: response.current,
5794
+ previous: response.previous,
5795
+ persisted
5796
+ };
4078
5797
  } finally {
4079
- entry.modelRoundtripInFlight = false;
5798
+ entry.approvalModeRoundtripInFlight = false;
4080
5799
  if (succeeded) {
4081
- void reconcileAfterRoundtrip(entry, "model");
5800
+ void reconcileAfterRoundtrip(entry, "approvalMode");
4082
5801
  } else {
4083
5802
  writeStderrLine(
4084
- `[reconcile] session=${entry.sessionId} target=model action=skipped reason=roundtrip_failed`
5803
+ `[reconcile] session=${entry.sessionId} target=approvalMode action=skipped reason=roundtrip_failed`
4085
5804
  );
4086
5805
  }
4087
5806
  }
4088
5807
  });
4089
- entry.modelChangeQueue = work.then(
5808
+ entry.approvalModeQueue = approvalWork.then(
4090
5809
  () => void 0,
4091
5810
  () => void 0
4092
5811
  );
4093
- return work;
5812
+ try {
5813
+ return await approvalWork;
5814
+ } catch (err) {
5815
+ const data = err?.data;
5816
+ if (data && typeof data === "object" && "errorKind" in data && data.errorKind === "trust_gate") {
5817
+ const rawMessage = err?.message;
5818
+ const message = typeof rawMessage === "string" ? rawMessage : "Trust-gate rejection from ACP child";
5819
+ throw new TrustGateError(message);
5820
+ }
5821
+ throw err;
5822
+ }
4094
5823
  }
4095
- __name(applyModelServiceId, "applyModelServiceId");
5824
+ __name(applyApprovalMode, "applyApprovalMode");
5825
+ async function applyApprovalModeForAttach(entry, mode, clientId) {
5826
+ try {
5827
+ await applyApprovalMode(entry, mode, false, clientId);
5828
+ } catch (err) {
5829
+ await rollbackAttachRegistration(entry, clientId);
5830
+ throw err;
5831
+ }
5832
+ }
5833
+ __name(applyApprovalModeForAttach, "applyApprovalModeForAttach");
4096
5834
  const cancelPendingForSession = /* @__PURE__ */ __name((sessionId) => {
4097
5835
  permissionMediator.forgetSession(sessionId);
4098
5836
  byId.get(sessionId)?.pendingPermissionIds.clear();
5837
+ byId.get(sessionId)?.pendingInteractions.clear();
4099
5838
  }, "cancelPendingForSession");
4100
5839
  const getTransportClosedReject = /* @__PURE__ */ __name((entry) => {
4101
5840
  if (!entry.transportClosedReject) {
@@ -4280,7 +6019,18 @@ function createAcpSessionBridge(opts) {
4280
6019
  );
4281
6020
  }
4282
6021
  }, "broadcastWorkspaceEvent");
4283
- const createSessionEventBus = /* @__PURE__ */ __name(() => new EventBus(eventRingSize, void 0, new TurnBoundaryCompactionEngine()), "createSessionEventBus");
6022
+ const createSessionEventBus = /* @__PURE__ */ __name(() => new EventBus(
6023
+ eventRingSize,
6024
+ void 0,
6025
+ new TurnBoundaryCompactionEngine({
6026
+ maxReplayBytes: compactedReplayMaxBytes,
6027
+ onReplayWindowEviction: /* @__PURE__ */ __name((eviction) => {
6028
+ teeServeDebugLine(
6029
+ `replay window evicted ${JSON.stringify(eviction)}`
6030
+ );
6031
+ }, "onReplayWindowEviction")
6032
+ })
6033
+ ), "createSessionEventBus");
4284
6034
  const publishModelSwitched = /* @__PURE__ */ __name((entry, modelId, originatorClientId) => {
4285
6035
  entry.currentModelId = modelId;
4286
6036
  entry.modelPublishGeneration++;
@@ -4366,10 +6116,15 @@ function createAcpSessionBridge(opts) {
4366
6116
  sessionId,
4367
6117
  workspaceCwd,
4368
6118
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6119
+ ...options.parentSessionId ? { parentSessionId: options.parentSessionId } : {},
4369
6120
  channel: ci.channel,
4370
6121
  connection: ci.connection,
4371
6122
  events,
4372
- artifacts: new SessionArtifactStore({ sessionId, workspaceCwd }),
6123
+ artifacts: new SessionArtifactStore({
6124
+ sessionId,
6125
+ workspaceCwd,
6126
+ persistence: createSessionArtifactPersistence(ci.connection, sessionId)
6127
+ }),
4373
6128
  promptQueue: Promise.resolve(),
4374
6129
  pendingPromptCount: 0,
4375
6130
  pendingPromptList: [],
@@ -4379,6 +6134,7 @@ function createAcpSessionBridge(opts) {
4379
6134
  modelPublishGeneration: 0,
4380
6135
  approvalModePublishGeneration: 0,
4381
6136
  pendingPermissionIds: /* @__PURE__ */ new Set(),
6137
+ pendingInteractions: /* @__PURE__ */ new Map(),
4382
6138
  clientIds: /* @__PURE__ */ new Map(),
4383
6139
  clientLastSeenAt: /* @__PURE__ */ new Map(),
4384
6140
  attachCount: 0,
@@ -4390,6 +6146,12 @@ function createAcpSessionBridge(opts) {
4390
6146
  byId.set(entry.sessionId, entry);
4391
6147
  touchActivity();
4392
6148
  telemetry.metrics?.sessionLifecycle("spawn");
6149
+ emitSessionLifecycle({
6150
+ type: "registered",
6151
+ sessionId: entry.sessionId,
6152
+ workspaceCwd: entry.workspaceCwd,
6153
+ reason: options.lifecycleReason ?? "spawn"
6154
+ });
4393
6155
  if (options.drainEarlyEvents !== false) {
4394
6156
  ci.client.drainEarlyEvents(entry.sessionId, entry);
4395
6157
  }
@@ -4404,6 +6166,42 @@ function createAcpSessionBridge(opts) {
4404
6166
  });
4405
6167
  }
4406
6168
  }, "publishArtifactChanges");
6169
+ const artifactReseedChanges = /* @__PURE__ */ __name((before, after) => {
6170
+ const beforeById = new Map(
6171
+ before.map((artifact) => [artifact.id, artifact])
6172
+ );
6173
+ const afterById = new Map(after.map((artifact) => [artifact.id, artifact]));
6174
+ const changes = [];
6175
+ for (const artifact of before) {
6176
+ if (!afterById.has(artifact.id)) {
6177
+ changes.push({
6178
+ action: "removed",
6179
+ artifactId: artifact.id,
6180
+ artifact,
6181
+ reason: "eviction"
6182
+ });
6183
+ }
6184
+ }
6185
+ for (const artifact of after) {
6186
+ const previous = beforeById.get(artifact.id);
6187
+ if (!previous) {
6188
+ changes.push({
6189
+ action: "created",
6190
+ artifactId: artifact.id,
6191
+ artifact
6192
+ });
6193
+ continue;
6194
+ }
6195
+ if (!publicArtifactsEqual(previous, artifact)) {
6196
+ changes.push({
6197
+ action: "updated",
6198
+ artifactId: artifact.id,
6199
+ artifact
6200
+ });
6201
+ }
6202
+ }
6203
+ return changes;
6204
+ }, "artifactReseedChanges");
4407
6205
  const makeClientArtifactInput = /* @__PURE__ */ __name((artifact, clientId) => {
4408
6206
  const input = {
4409
6207
  title: artifact.title,
@@ -4416,6 +6214,8 @@ function createAcpSessionBridge(opts) {
4416
6214
  mimeType: artifact.mimeType,
4417
6215
  sizeBytes: artifact.sizeBytes,
4418
6216
  metadata: artifact.metadata,
6217
+ retention: artifact.retention,
6218
+ clientRetained: artifact.clientRetained,
4419
6219
  source: "client"
4420
6220
  };
4421
6221
  if (clientId) {
@@ -4423,6 +6223,31 @@ function createAcpSessionBridge(opts) {
4423
6223
  }
4424
6224
  return input;
4425
6225
  }, "makeClientArtifactInput");
6226
+ function createSessionArtifactPersistence(connection, sessionId) {
6227
+ return {
6228
+ recordEvent: /* @__PURE__ */ __name(async (payload) => {
6229
+ await connection.extMethod(
6230
+ SERVE_CONTROL_EXT_METHODS.sessionArtifactsPersist,
6231
+ {
6232
+ sessionId,
6233
+ kind: "event",
6234
+ payload
6235
+ }
6236
+ );
6237
+ }, "recordEvent"),
6238
+ recordSnapshot: /* @__PURE__ */ __name(async (payload) => {
6239
+ await connection.extMethod(
6240
+ SERVE_CONTROL_EXT_METHODS.sessionArtifactsPersist,
6241
+ {
6242
+ sessionId,
6243
+ kind: "snapshot",
6244
+ payload
6245
+ }
6246
+ );
6247
+ }, "recordSnapshot")
6248
+ };
6249
+ }
6250
+ __name(createSessionArtifactPersistence, "createSessionArtifactPersistence");
4426
6251
  const seedSnapshotCaches = /* @__PURE__ */ __name((entry, resp) => {
4427
6252
  const model = resp.models?.currentModelId;
4428
6253
  if (typeof model === "string" && model.length > 0) {
@@ -4470,16 +6295,55 @@ function createAcpSessionBridge(opts) {
4470
6295
  }
4471
6296
  return { lastEventId: snapshot.lastEventId, ...replayStatus };
4472
6297
  }, "replayFieldsFor");
4473
- async function restoreSession(action, req) {
6298
+ const restoredArtifactSnapshotFromState = /* @__PURE__ */ __name((state) => {
6299
+ const candidate = state.artifactSnapshot;
6300
+ const warnings = [];
6301
+ const snapshot = normalizeSnapshotPayload(candidate, warnings);
6302
+ if (!snapshot) return void 0;
6303
+ const snapshotWarnings = isRecord2(candidate) && Array.isArray(candidate["warnings"]) ? candidate["warnings"].filter(
6304
+ (warning) => typeof warning === "string" && warning.length <= 1e3
6305
+ ).slice(-500) : [];
6306
+ return {
6307
+ v: SESSION_ARTIFACT_PERSISTENCE_VERSION,
6308
+ sessionId: snapshot.sessionId,
6309
+ sequence: snapshot.sequence,
6310
+ artifacts: snapshot.artifacts,
6311
+ ...snapshot.markerArtifacts ? { markerArtifacts: snapshot.markerArtifacts } : {},
6312
+ tombstonedIds: snapshot.tombstonedIds ?? [],
6313
+ stickyEphemeralIds: snapshot.stickyEphemeralIds ?? [],
6314
+ warnings: [...warnings, ...snapshotWarnings]
6315
+ };
6316
+ }, "restoredArtifactSnapshotFromState");
6317
+ const artifactSnapshotUnavailableReason = /* @__PURE__ */ __name((state) => {
6318
+ const reason = state.artifactSnapshotUnavailable;
6319
+ return typeof reason === "string" && reason ? reason : void 0;
6320
+ }, "artifactSnapshotUnavailableReason");
6321
+ const publicRestoreState = /* @__PURE__ */ __name((state) => {
6322
+ const {
6323
+ artifactSnapshot: _artifactSnapshot,
6324
+ artifactSnapshotUnavailable: _artifactSnapshotUnavailable,
6325
+ ...publicState
6326
+ } = state;
6327
+ return publicState;
6328
+ }, "publicRestoreState");
6329
+ async function restoreSession(action, req, options = {}) {
4474
6330
  if (shuttingDown) {
4475
6331
  throw new Error("AcpSessionBridge is shutting down");
4476
6332
  }
4477
6333
  const workspaceKey = resolveWorkspaceKey(req.workspaceCwd);
6334
+ if (req.approvalMode !== void 0 && !KNOWN_APPROVAL_MODES.has(req.approvalMode)) {
6335
+ throw new Error(
6336
+ `Invalid approvalMode: ${JSON.stringify(req.approvalMode)}`
6337
+ );
6338
+ }
4478
6339
  const historyReplay = action === "load" ? req.historyReplay ?? "stream" : "stream";
4479
6340
  const existing = byId.get(req.sessionId);
4480
6341
  if (existing) {
4481
6342
  existing.attachCount++;
4482
6343
  const clientId = registerClient(existing, req.clientId);
6344
+ if (req.approvalMode) {
6345
+ await applyApprovalModeForAttach(existing, req.approvalMode, clientId);
6346
+ }
4483
6347
  return {
4484
6348
  sessionId: existing.sessionId,
4485
6349
  workspaceCwd: existing.workspaceCwd,
@@ -4518,10 +6382,14 @@ function createAcpSessionBridge(opts) {
4518
6382
  "the agent child likely crashed during session restore \u2014 retry to restore the session"
4519
6383
  );
4520
6384
  }
6385
+ const clientId = registerClient(entry, req.clientId);
6386
+ if (req.approvalMode) {
6387
+ await applyApprovalModeForAttach(entry, req.approvalMode, clientId);
6388
+ }
4521
6389
  return {
4522
6390
  ...restored,
4523
6391
  attached: true,
4524
- clientId: registerClient(entry, req.clientId),
6392
+ clientId,
4525
6393
  createdAt: entry.createdAt,
4526
6394
  hasActivePrompt: entry.promptActive
4527
6395
  };
@@ -4533,6 +6401,17 @@ function createAcpSessionBridge(opts) {
4533
6401
  let registeredEntry;
4534
6402
  let ci;
4535
6403
  const coalesceState = { count: 0 };
6404
+ const admission = options.skipFreshSessionAdmission === true ? void 0 : reserveFreshSession({
6405
+ operation: action,
6406
+ workspaceCwd: workspaceKey,
6407
+ sessionId: req.sessionId
6408
+ });
6409
+ let admissionReleased = false;
6410
+ const releaseAdmissionOnce = /* @__PURE__ */ __name(() => {
6411
+ if (admissionReleased) return;
6412
+ admissionReleased = true;
6413
+ releaseFreshSessionReservation(admission);
6414
+ }, "releaseAdmissionOnce");
4536
6415
  const promise = (async () => {
4537
6416
  pendingRestoreEvents.set(req.sessionId, restoreEvents);
4538
6417
  ci = await ensureChannel();
@@ -4620,6 +6499,23 @@ function createAcpSessionBridge(opts) {
4620
6499
  restoreEvents.close();
4621
6500
  racedEntry.attachCount += 1 + coalesceState.count;
4622
6501
  const clientId2 = registerClient(racedEntry, req.clientId);
6502
+ if (req.approvalMode) {
6503
+ try {
6504
+ await applyApprovalMode(
6505
+ racedEntry,
6506
+ req.approvalMode,
6507
+ false,
6508
+ clientId2
6509
+ );
6510
+ } catch (err) {
6511
+ await rollbackAttachRegistration(
6512
+ racedEntry,
6513
+ clientId2,
6514
+ 1 + coalesceState.count
6515
+ );
6516
+ throw err;
6517
+ }
6518
+ }
4623
6519
  return {
4624
6520
  sessionId: racedEntry.sessionId,
4625
6521
  workspaceCwd: racedEntry.workspaceCwd,
@@ -4636,21 +6532,48 @@ function createAcpSessionBridge(opts) {
4636
6532
  req.sessionId,
4637
6533
  workspaceKey,
4638
6534
  restoreEvents,
4639
- { drainEarlyEvents: replayUpdates.length === 0 }
6535
+ {
6536
+ drainEarlyEvents: replayUpdates.length === 0,
6537
+ lifecycleReason: action,
6538
+ // Re-seed the persisted parent lineage the caller recovered from the
6539
+ // transcript, so a restored sub-session's status reports its parent.
6540
+ ...req.parentSessionId ? { parentSessionId: req.parentSessionId } : {}
6541
+ }
4640
6542
  );
4641
- entry.restoreState = state;
6543
+ releaseAdmissionOnce();
6544
+ const restoredArtifactSnapshot = restoredArtifactSnapshotFromState(state);
6545
+ const publicState = publicRestoreState(state);
6546
+ entry.restoreState = publicState;
4642
6547
  if (replayPartial === true) {
4643
6548
  entry.restoreReplayPartial = true;
4644
6549
  }
4645
6550
  if (replayError !== void 0) {
4646
6551
  entry.restoreReplayError = replayError;
4647
6552
  }
4648
- seedSnapshotCaches(entry, state);
6553
+ seedSnapshotCaches(entry, publicState);
6554
+ const artifactRestoreWarnings = await entry.artifacts.restore(
6555
+ restoredArtifactSnapshot
6556
+ );
6557
+ for (const warning of artifactRestoreWarnings) {
6558
+ writeStderrLine(
6559
+ `[artifacts] session=${entry.sessionId} action=restore_warning warning=${JSON.stringify(
6560
+ warning
6561
+ )}`
6562
+ );
6563
+ }
6564
+ const artifactRestoreFailed = artifactRestoreWarnings.some(
6565
+ (warning) => isArtifactRestoreFailureWarning(warning)
6566
+ );
4649
6567
  if (replayUpdates.length > 0) {
4650
- await ci.client.seedSessionUpdates(entry, replayUpdates);
6568
+ await ci.client.seedSessionUpdates(entry, replayUpdates, {
6569
+ ingestArtifacts: restoredArtifactSnapshot === void 0 || artifactRestoreFailed
6570
+ });
4651
6571
  ci.client.drainEarlyEvents(entry.sessionId, entry);
4652
6572
  }
4653
6573
  const clientId = registerClient(entry, req.clientId);
6574
+ if (req.approvalMode) {
6575
+ await applyApprovalModeForAttach(entry, req.approvalMode, clientId);
6576
+ }
4654
6577
  entry.attachCount = coalesceState.count;
4655
6578
  registeredEntry = entry;
4656
6579
  return {
@@ -4659,20 +6582,29 @@ function createAcpSessionBridge(opts) {
4659
6582
  attached: false,
4660
6583
  clientId,
4661
6584
  createdAt: entry.createdAt,
4662
- state,
6585
+ state: publicState,
6586
+ ...artifactRestoreWarnings.length > 0 ? { artifactWarnings: artifactRestoreWarnings } : {},
4663
6587
  hasActivePrompt: entry.promptActive,
4664
6588
  ...replayFieldsFor(entry, action)
4665
6589
  };
4666
6590
  })().finally(async () => {
6591
+ releaseAdmissionOnce();
4667
6592
  ci?.pendingRestoreIds.delete(req.sessionId);
4668
6593
  ci?.client.clearRestoreInFlight(req.sessionId);
4669
6594
  pendingRestoreEvents.delete(req.sessionId);
4670
6595
  if (!registeredEntry) {
4671
6596
  restoreEvents.close();
4672
6597
  let removedRestoreEntry = false;
4673
- if (byId.get(req.sessionId)?.events === restoreEvents) {
6598
+ const restoreEntry = byId.get(req.sessionId);
6599
+ if (restoreEntry?.events === restoreEvents) {
4674
6600
  byId.delete(req.sessionId);
4675
6601
  ci?.sessionIds.delete(req.sessionId);
6602
+ emitSessionLifecycle({
6603
+ type: "removed",
6604
+ sessionId: req.sessionId,
6605
+ workspaceCwd: restoreEntry.workspaceCwd,
6606
+ reason: "restore_failed"
6607
+ });
4676
6608
  removedRestoreEntry = true;
4677
6609
  }
4678
6610
  if (removedRestoreEntry && ci && hasNoChannelWork(ci)) {
@@ -4733,6 +6665,7 @@ function createAcpSessionBridge(opts) {
4733
6665
  }
4734
6666
  permissionMediator.forgetSession(sessionId);
4735
6667
  entry.pendingPermissionIds.clear();
6668
+ entry.pendingInteractions.clear();
4736
6669
  if (entry.promptActive) {
4737
6670
  entry.promptActive = false;
4738
6671
  activePromptCounter--;
@@ -4740,6 +6673,12 @@ function createAcpSessionBridge(opts) {
4740
6673
  }
4741
6674
  byId.delete(sessionId);
4742
6675
  telemetry.metrics?.sessionLifecycle("close");
6676
+ emitSessionLifecycle({
6677
+ type: "removed",
6678
+ sessionId,
6679
+ workspaceCwd: entry.workspaceCwd,
6680
+ reason
6681
+ });
4743
6682
  ci?.client.markSessionClosed(sessionId);
4744
6683
  try {
4745
6684
  entry.events.publish({
@@ -4788,6 +6727,7 @@ function createAcpSessionBridge(opts) {
4788
6727
  maxSessions: maxSessions === Infinity ? null : maxSessions,
4789
6728
  maxPendingPromptsPerSession: maxPendingPromptsPerSession === Infinity ? null : maxPendingPromptsPerSession,
4790
6729
  eventRingSize,
6730
+ compactedReplayMaxBytes,
4791
6731
  channelIdleTimeoutMs: resolvedChannelIdleTimeoutMs(),
4792
6732
  sessionIdleTimeoutMs
4793
6733
  },
@@ -4862,6 +6802,11 @@ function createAcpSessionBridge(opts) {
4862
6802
  throw new InvalidSessionScopeError(req.sessionScope);
4863
6803
  }
4864
6804
  const effectiveScope = req.sessionScope ?? defaultSessionScope;
6805
+ if (req.approvalMode !== void 0 && !KNOWN_APPROVAL_MODES.has(req.approvalMode)) {
6806
+ throw new Error(
6807
+ `Invalid approvalMode: ${JSON.stringify(req.approvalMode)}`
6808
+ );
6809
+ }
4865
6810
  if (effectiveScope === "single") {
4866
6811
  const existing = defaultEntry;
4867
6812
  if (existing) {
@@ -4876,6 +6821,13 @@ function createAcpSessionBridge(opts) {
4876
6821
  ).catch(() => {
4877
6822
  });
4878
6823
  }
6824
+ if (req.approvalMode) {
6825
+ await applyApprovalModeForAttach(
6826
+ existing,
6827
+ req.approvalMode,
6828
+ clientId
6829
+ );
6830
+ }
4879
6831
  return {
4880
6832
  sessionId: existing.sessionId,
4881
6833
  workspaceCwd: existing.workspaceCwd,
@@ -4906,6 +6858,13 @@ function createAcpSessionBridge(opts) {
4906
6858
  ).catch(() => {
4907
6859
  });
4908
6860
  }
6861
+ if (req.approvalMode) {
6862
+ await applyApprovalModeForAttach(
6863
+ attachedEntry,
6864
+ req.approvalMode,
6865
+ clientId
6866
+ );
6867
+ }
4909
6868
  return {
4910
6869
  ...session,
4911
6870
  attached: true,
@@ -4917,12 +6876,30 @@ function createAcpSessionBridge(opts) {
4917
6876
  if (byId.size + inFlightSpawns.size + inFlightRestores.size >= maxSessions) {
4918
6877
  throw new SessionLimitExceededError(maxSessions);
4919
6878
  }
4920
- const promise = doSpawn(req.modelServiceId, effectiveScope, req.clientId);
6879
+ const admission = reserveFreshSession({
6880
+ operation: "spawn",
6881
+ workspaceCwd: workspaceKey
6882
+ });
6883
+ let admissionReleased = false;
6884
+ const releaseAdmissionOnce = /* @__PURE__ */ __name(() => {
6885
+ if (admissionReleased) return;
6886
+ admissionReleased = true;
6887
+ releaseFreshSessionReservation(admission);
6888
+ }, "releaseAdmissionOnce");
6889
+ const promise = doSpawn(
6890
+ req.modelServiceId,
6891
+ effectiveScope,
6892
+ req.approvalMode,
6893
+ req.clientId,
6894
+ releaseAdmissionOnce,
6895
+ req.parentSessionId
6896
+ );
4921
6897
  const tracker = effectiveScope === "single" ? workspaceKey : `${workspaceKey}#${randomUUID2()}`;
4922
6898
  inFlightSpawns.set(tracker, promise);
4923
6899
  try {
4924
6900
  return await promise;
4925
6901
  } finally {
6902
+ releaseAdmissionOnce();
4926
6903
  inFlightSpawns.delete(tracker);
4927
6904
  }
4928
6905
  },
@@ -5056,6 +7033,7 @@ function createAcpSessionBridge(opts) {
5056
7033
  return copy;
5057
7034
  })();
5058
7035
  entry.promptActive = true;
7036
+ delete entry.turnError;
5059
7037
  activePromptCounter++;
5060
7038
  entry.sessionLastSeenAt = Date.now();
5061
7039
  touchActivity();
@@ -5379,67 +7357,89 @@ function createAcpSessionBridge(opts) {
5379
7357
  if (byId.size + inFlightSpawns.size + inFlightRestores.size >= maxSessions) {
5380
7358
  throw new SessionLimitExceededError(maxSessions);
5381
7359
  }
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;
7360
+ const admission = reserveFreshSession({
7361
+ operation: "branch",
7362
+ workspaceCwd: boundWorkspace,
7363
+ sourceSessionId: sessionId
7364
+ });
7365
+ let admissionReleased = false;
7366
+ const releaseAdmissionOnce = /* @__PURE__ */ __name(() => {
7367
+ if (admissionReleased) return;
7368
+ admissionReleased = true;
7369
+ releaseFreshSessionReservation(admission);
7370
+ }, "releaseAdmissionOnce");
5400
7371
  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...`
7372
+ const ci = await ensureChannel();
7373
+ const result = await withTimeout(
7374
+ ci.connection.extMethod(SERVE_CONTROL_EXT_METHODS.sessionBranch, {
7375
+ sessionId,
7376
+ cwd: boundWorkspace,
7377
+ name: req.name
7378
+ }),
7379
+ initTimeoutMs,
7380
+ "branchSession"
5409
7381
  );
7382
+ if (!result || typeof result.newSessionId !== "string") {
7383
+ throw new Error(
7384
+ `branchSession: agent returned invalid response: ${JSON.stringify(result)}`
7385
+ );
7386
+ }
7387
+ const rawBranchName = result.displayName ?? result.title;
7388
+ const branchDisplayName = typeof rawBranchName === "string" ? rawBranchName : result.newSessionId.slice(0, 8);
7389
+ let restored;
5410
7390
  try {
5411
- await ci.connection.extMethod(
5412
- SERVE_CONTROL_EXT_METHODS.sessionClose,
5413
- { sessionId: result.newSessionId, cwd: boundWorkspace }
7391
+ restored = await restoreSession(
7392
+ "load",
7393
+ {
7394
+ sessionId: result.newSessionId,
7395
+ workspaceCwd: boundWorkspace,
7396
+ clientId: context?.clientId
7397
+ },
7398
+ {
7399
+ skipFreshSessionAdmission: true
7400
+ }
5414
7401
  );
5415
- } catch (cleanupErr) {
7402
+ releaseAdmissionOnce();
7403
+ } catch (restoreErr) {
5416
7404
  writeStderrLine(
5417
- `qwen serve: branchSession cleanup of ${result.newSessionId} failed: ${cleanupErr instanceof Error ? cleanupErr.message : cleanupErr}`
7405
+ `qwen serve: branchSession load failed for ${result.newSessionId}, attempting cleanup...`
5418
7406
  );
7407
+ try {
7408
+ await ci.connection.extMethod(
7409
+ SERVE_CONTROL_EXT_METHODS.sessionClose,
7410
+ { sessionId: result.newSessionId, cwd: boundWorkspace }
7411
+ );
7412
+ } catch (cleanupErr) {
7413
+ writeStderrLine(
7414
+ `qwen serve: branchSession cleanup of ${result.newSessionId} failed: ${cleanupErr instanceof Error ? cleanupErr.message : cleanupErr}`
7415
+ );
7416
+ }
7417
+ throw restoreErr;
5419
7418
  }
5420
- throw restoreErr;
7419
+ const newEntry = byId.get(result.newSessionId);
7420
+ if (newEntry) newEntry.displayName = branchDisplayName;
7421
+ const eventData = {
7422
+ sourceSessionId: sessionId,
7423
+ newSessionId: result.newSessionId,
7424
+ displayName: branchDisplayName
7425
+ };
7426
+ const branchEnvelope = {
7427
+ type: "session_branched",
7428
+ data: eventData,
7429
+ ...originatorClientId ? { originatorClientId } : {}
7430
+ };
7431
+ newEntry?.events.publish(branchEnvelope);
7432
+ return {
7433
+ ...restored,
7434
+ displayName: branchDisplayName,
7435
+ forkedFrom: {
7436
+ sessionId,
7437
+ displayName: entry.displayName ?? sessionId.slice(0, 8)
7438
+ }
7439
+ };
7440
+ } finally {
7441
+ releaseAdmissionOnce();
5421
7442
  }
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
7443
  });
5444
7444
  entry.promptQueue = branchResult.then(
5445
7445
  () => void 0,
@@ -5556,9 +7556,10 @@ function createAcpSessionBridge(opts) {
5556
7556
  }
5557
7557
  return { displayName: entry.displayName };
5558
7558
  },
5559
- async getSessionArtifacts(sessionId) {
7559
+ async getSessionArtifacts(sessionId, context) {
5560
7560
  const entry = byId.get(sessionId);
5561
7561
  if (!entry) throw new SessionNotFoundError(sessionId);
7562
+ resolveTrustedClientId(entry, context?.clientId);
5562
7563
  return entry.artifacts.list();
5563
7564
  },
5564
7565
  async addSessionArtifact(sessionId, artifact, context) {
@@ -5566,9 +7567,13 @@ function createAcpSessionBridge(opts) {
5566
7567
  if (!entry) throw new SessionNotFoundError(sessionId);
5567
7568
  const clientId = resolveTrustedClientId(entry, context?.clientId);
5568
7569
  const input = makeClientArtifactInput(artifact, clientId);
5569
- const result = await entry.artifacts.upsertMany([input], { strict: true });
7570
+ const result = await entry.artifacts.upsertMany([input], {
7571
+ validationStrict: true,
7572
+ persistenceStrict: false
7573
+ });
5570
7574
  publishArtifactChanges(entry, result.changes, clientId);
5571
- return result;
7575
+ const warnings = [...result.warnings ?? []];
7576
+ return warnings.length > 0 ? { ...result, warnings } : result;
5572
7577
  },
5573
7578
  async removeSessionArtifact(sessionId, artifactId, context) {
5574
7579
  const entry = byId.get(sessionId);
@@ -5576,7 +7581,8 @@ function createAcpSessionBridge(opts) {
5576
7581
  const clientId = resolveTrustedClientId(entry, context?.clientId);
5577
7582
  const result = await entry.artifacts.remove(artifactId, { clientId });
5578
7583
  publishArtifactChanges(entry, result.changes, clientId);
5579
- return result;
7584
+ const warnings = [...result.warnings ?? []];
7585
+ return warnings.length > 0 ? { ...result, warnings } : result;
5580
7586
  },
5581
7587
  listWorkspaceSessions(workspaceCwd) {
5582
7588
  if (!path3.isAbsolute(workspaceCwd)) return [];
@@ -5861,6 +7867,35 @@ function createAcpSessionBridge(opts) {
5861
7867
  SERVE_STATUS_EXT_METHODS.sessionLspStatus
5862
7868
  );
5863
7869
  },
7870
+ async getSessionTranscriptPage(req) {
7871
+ const info = await ensureChannel();
7872
+ try {
7873
+ const response = await withWorkspaceControl(
7874
+ info,
7875
+ () => withTimeout(
7876
+ Promise.race([
7877
+ info.connection.extMethod(
7878
+ SERVE_STATUS_EXT_METHODS.sessionTranscript,
7879
+ { ...req, cwd: boundWorkspace }
7880
+ ),
7881
+ getChannelClosedReject(info)
7882
+ ]),
7883
+ Math.max(initTimeoutMs, SESSION_TRANSCRIPT_TIMEOUT_MS),
7884
+ SERVE_STATUS_EXT_METHODS.sessionTranscript
7885
+ )
7886
+ );
7887
+ return response;
7888
+ } catch (err) {
7889
+ if (isAcpSessionResourceNotFound(err, req.sessionId)) {
7890
+ throw new SessionNotFoundError(req.sessionId);
7891
+ }
7892
+ throw err;
7893
+ } finally {
7894
+ if (hasNoChannelWork(info)) {
7895
+ await startIdleTimer(info, "session transcript");
7896
+ }
7897
+ }
7898
+ },
5864
7899
  async cancelSessionTask(sessionId, taskId, taskKind) {
5865
7900
  return requestSessionStatus(
5866
7901
  sessionId,
@@ -6099,109 +8134,12 @@ function createAcpSessionBridge(opts) {
6099
8134
  entry,
6100
8135
  context?.clientId
6101
8136
  );
6102
- if (opts2.persist && !persistApprovalMode) {
6103
- throw new Error(
6104
- "setSessionApprovalMode called with `persist: true` but no `persistApprovalMode` callback wired in BridgeOptions. runQwenServe wires the production callback; direct embeds and tests must opt in or omit `persist`."
6105
- );
6106
- }
6107
- const approvalWork = entry.approvalModeQueue.then(async () => {
6108
- entry.approvalModeRoundtripInFlight = true;
6109
- let succeeded = false;
6110
- try {
6111
- const response = await Promise.race([
6112
- withTimeout(
6113
- entry.connection.extMethod(
6114
- SERVE_CONTROL_EXT_METHODS.sessionApprovalMode,
6115
- { sessionId, mode }
6116
- ),
6117
- initTimeoutMs,
6118
- SERVE_CONTROL_EXT_METHODS.sessionApprovalMode
6119
- ),
6120
- getTransportClosedReject(entry)
6121
- ]);
6122
- if (typeof response.current !== "string" || !KNOWN_APPROVAL_MODES.has(response.current)) {
6123
- throw new Error(
6124
- `Agent returned unknown approval mode: ${JSON.stringify(response.current)}`
6125
- );
6126
- }
6127
- let persisted = false;
6128
- if (opts2.persist) {
6129
- try {
6130
- await withTimeout(
6131
- persistApprovalMode?.(boundWorkspace, mode) ?? Promise.resolve(),
6132
- PERSIST_TIMEOUT_MS,
6133
- "persistApprovalMode"
6134
- );
6135
- persisted = persistApprovalMode !== void 0;
6136
- } catch (err) {
6137
- writeStderrLine(
6138
- `setSessionApprovalMode: persist failed: ${err instanceof Error ? err.message : String(err)}`
6139
- );
6140
- }
6141
- }
6142
- publishApprovalModeChanged(
6143
- entry,
6144
- {
6145
- previous: response.previous,
6146
- next: response.current,
6147
- persisted
6148
- },
6149
- originatorClientId
6150
- );
6151
- if (persisted) {
6152
- broadcastWorkspaceEvent(
6153
- {
6154
- type: "approval_mode_changed",
6155
- data: {
6156
- sessionId: entry.sessionId,
6157
- previous: response.previous,
6158
- next: response.current,
6159
- persisted
6160
- },
6161
- ...originatorClientId ? { originatorClientId } : {}
6162
- },
6163
- entry.sessionId
6164
- );
6165
- for (const peer of byId.values()) {
6166
- if (peer.sessionId === entry.sessionId) {
6167
- continue;
6168
- }
6169
- peer.currentApprovalMode = response.current;
6170
- }
6171
- }
6172
- succeeded = true;
6173
- return {
6174
- sessionId: entry.sessionId,
6175
- mode: response.current,
6176
- previous: response.previous,
6177
- persisted
6178
- };
6179
- } finally {
6180
- entry.approvalModeRoundtripInFlight = false;
6181
- if (succeeded) {
6182
- void reconcileAfterRoundtrip(entry, "approvalMode");
6183
- } else {
6184
- writeStderrLine(
6185
- `[reconcile] session=${entry.sessionId} target=approvalMode action=skipped reason=roundtrip_failed`
6186
- );
6187
- }
6188
- }
6189
- });
6190
- entry.approvalModeQueue = approvalWork.then(
6191
- () => void 0,
6192
- () => void 0
8137
+ return await applyApprovalMode(
8138
+ entry,
8139
+ mode,
8140
+ opts2.persist,
8141
+ originatorClientId
6193
8142
  );
6194
- try {
6195
- return await approvalWork;
6196
- } catch (err) {
6197
- const data = err?.data;
6198
- if (data && typeof data === "object" && "errorKind" in data && data.errorKind === "trust_gate") {
6199
- const rawMessage = err?.message;
6200
- const message = typeof rawMessage === "string" ? rawMessage : "Trust-gate rejection from ACP child";
6201
- throw new TrustGateError(message);
6202
- }
6203
- throw err;
6204
- }
6205
8143
  },
6206
8144
  async generateSessionRecap(sessionId, _context) {
6207
8145
  const entry = byId.get(sessionId);
@@ -6554,6 +8492,48 @@ function createAcpSessionBridge(opts) {
6554
8492
  const targetTurnIndex = response["targetTurnIndex"] ?? 0;
6555
8493
  const filesChanged = response["filesChanged"] ?? [];
6556
8494
  const filesFailed = response["filesFailed"] ?? [];
8495
+ const artifactSnapshot = restoredArtifactSnapshotFromState(
8496
+ response
8497
+ );
8498
+ const artifactSnapshotUnavailable = artifactSnapshotUnavailableReason(
8499
+ response
8500
+ );
8501
+ const beforeArtifacts = (await entry.artifacts.list()).artifacts;
8502
+ const shouldRestoreArtifactSnapshot = artifactSnapshot !== void 0 && artifactSnapshotUnavailable === void 0;
8503
+ const artifactRestoreWarnings = artifactSnapshotUnavailable !== void 0 ? [
8504
+ `artifact snapshot rebuild unavailable during rewind: ${artifactSnapshotUnavailable}`
8505
+ ] : shouldRestoreArtifactSnapshot ? await entry.artifacts.restore(artifactSnapshot, {
8506
+ preserveLiveEphemeral: true
8507
+ }) : [];
8508
+ const artifactRestoreFailed = artifactRestoreWarnings.some(
8509
+ isArtifactRestoreFailureWarning
8510
+ );
8511
+ const shouldRecordArtifactSnapshot = shouldRestoreArtifactSnapshot && !artifactRestoreFailed;
8512
+ const artifactSnapshotWarnings = shouldRecordArtifactSnapshot ? await entry.artifacts.recordSnapshot() : [];
8513
+ const artifactWarnings = [
8514
+ ...artifactRestoreWarnings,
8515
+ ...artifactSnapshotWarnings
8516
+ ];
8517
+ for (const warning of artifactRestoreWarnings) {
8518
+ writeStderrLine(
8519
+ `[artifacts] session=${entry.sessionId} action=rewind_restore_warning warning=${JSON.stringify(
8520
+ warning
8521
+ )}`
8522
+ );
8523
+ }
8524
+ for (const warning of artifactSnapshotWarnings) {
8525
+ writeStderrLine(
8526
+ `[artifacts] session=${entry.sessionId} action=rewind_snapshot_warning warning=${JSON.stringify(
8527
+ warning
8528
+ )}`
8529
+ );
8530
+ }
8531
+ const afterArtifacts = (await entry.artifacts.list()).artifacts;
8532
+ publishArtifactChanges(
8533
+ entry,
8534
+ artifactReseedChanges(beforeArtifacts, afterArtifacts),
8535
+ originatorClientId
8536
+ );
6557
8537
  try {
6558
8538
  entry.events.publish({
6559
8539
  type: "session_rewound",
@@ -6562,7 +8542,8 @@ function createAcpSessionBridge(opts) {
6562
8542
  promptId: req.promptId,
6563
8543
  targetTurnIndex,
6564
8544
  filesChanged,
6565
- filesFailed
8545
+ filesFailed,
8546
+ ...artifactWarnings.length > 0 ? { warnings: artifactWarnings } : {}
6566
8547
  },
6567
8548
  ...originatorClientId ? { originatorClientId } : {}
6568
8549
  });
@@ -6572,7 +8553,8 @@ function createAcpSessionBridge(opts) {
6572
8553
  rewound: filesFailed.length === 0,
6573
8554
  targetTurnIndex,
6574
8555
  filesChanged,
6575
- filesFailed
8556
+ filesFailed,
8557
+ ...artifactWarnings.length > 0 ? { warnings: artifactWarnings } : {}
6576
8558
  };
6577
8559
  },
6578
8560
  async manageMcpServer(serverName, action, originatorClientId) {
@@ -6700,6 +8682,7 @@ function createAcpSessionBridge(opts) {
6700
8682
  }
6701
8683
  permissionMediator.forgetSession(sessionId);
6702
8684
  entry.pendingPermissionIds.clear();
8685
+ entry.pendingInteractions.clear();
6703
8686
  if (entry.promptActive) {
6704
8687
  entry.promptActive = false;
6705
8688
  activePromptCounter--;
@@ -6708,6 +8691,12 @@ function createAcpSessionBridge(opts) {
6708
8691
  if (defaultEntry === entry) defaultEntry = void 0;
6709
8692
  byId.delete(sessionId);
6710
8693
  telemetry.metrics?.sessionLifecycle("die");
8694
+ emitSessionLifecycle({
8695
+ type: "removed",
8696
+ sessionId,
8697
+ workspaceCwd: entry.workspaceCwd,
8698
+ reason: "killed"
8699
+ });
6711
8700
  const ci = channelInfoForEntry(entry);
6712
8701
  if (!ci) {
6713
8702
  writeStderrLine(
@@ -6757,8 +8746,17 @@ function createAcpSessionBridge(opts) {
6757
8746
  cancelIdleTimer();
6758
8747
  stopSessionReaper();
6759
8748
  const channels = Array.from(aliveChannels);
8749
+ const entries = Array.from(byId.values());
6760
8750
  defaultEntry = void 0;
6761
8751
  byId.clear();
8752
+ for (const entry of entries) {
8753
+ emitSessionLifecycle({
8754
+ type: "removed",
8755
+ sessionId: entry.sessionId,
8756
+ workspaceCwd: entry.workspaceCwd,
8757
+ reason: "kill_all"
8758
+ });
8759
+ }
6762
8760
  for (const info of channels) {
6763
8761
  try {
6764
8762
  info.channel.killSync();
@@ -6776,11 +8774,18 @@ function createAcpSessionBridge(opts) {
6776
8774
  for (const e of entries) {
6777
8775
  permissionMediator.forgetSession(e.sessionId);
6778
8776
  e.pendingPermissionIds.clear();
8777
+ e.pendingInteractions.clear();
6779
8778
  }
6780
8779
  defaultEntry = void 0;
6781
8780
  byId.clear();
6782
8781
  for (const e of entries) {
6783
8782
  telemetry.metrics?.sessionLifecycle("die");
8783
+ emitSessionLifecycle({
8784
+ type: "removed",
8785
+ sessionId: e.sessionId,
8786
+ workspaceCwd: e.workspaceCwd,
8787
+ reason: "daemon_shutdown"
8788
+ });
6784
8789
  try {
6785
8790
  e.events.publish({
6786
8791
  type: "session_died",
@@ -6842,6 +8847,7 @@ var createHttpAcpBridge = createAcpSessionBridge;
6842
8847
 
6843
8848
  export {
6844
8849
  SessionArtifactValidationError,
8850
+ SessionArtifactAuthorizationError,
6845
8851
  extractErrorMessage,
6846
8852
  extractErrorCode,
6847
8853
  classifyTurnErrorKind,