@qwen-code/qwen-code 0.19.8 → 0.19.9

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 +9 -2
  7. package/bundled/qc-helper/docs/features/channels/overview.md +71 -4
  8. package/bundled/qc-helper/docs/features/channels/wecom.md +6 -0
  9. package/bundled/qc-helper/docs/features/code-review.md +29 -5
  10. package/bundled/qc-helper/docs/features/commands.md +39 -38
  11. package/bundled/qc-helper/docs/features/computer-use.md +77 -0
  12. package/bundled/qc-helper/docs/features/tool-use-summaries.md +19 -23
  13. package/bundled/qc-helper/docs/integration-vscode.md +2 -2
  14. package/bundled/qc-helper/docs/qwen-serve.md +29 -12
  15. package/bundled/qc-helper/docs/reference/keyboard-shortcuts.md +1 -1
  16. package/bundled/review/DESIGN.md +93 -22
  17. package/bundled/review/SKILL.md +295 -86
  18. package/chunks/MaxSizedBox-6HYJ3ZPJ.js +70 -0
  19. package/chunks/{StandaloneSessionPicker-EYRGH6PJ.js → StandaloneSessionPicker-WS4GXYOG.js} +79 -79
  20. package/chunks/{acpAgent-GRFTTRHW.js → acpAgent-5FWSOZL5.js} +968 -200
  21. package/chunks/agent-HOLHB3RT.js +68 -0
  22. package/chunks/agent-headless-SXL6H56A.js +62 -0
  23. package/chunks/{anthropicContentGenerator-CPF22MA7.js → anthropicContentGenerator-L73TO3DB.js} +187 -31
  24. package/chunks/{artifact-tool-D233YJFH.js → artifact-tool-U7IRTLIV.js} +5 -5
  25. package/chunks/{askUserQuestion-MKL5ZTMC.js → askUserQuestion-2DLUUZZG.js} +6 -6
  26. package/chunks/bridge-BCJQ7BBB.js +78 -0
  27. package/chunks/{ca-MKD32FQL.js → ca-UJ6HC4H2.js} +64 -5
  28. package/chunks/{channel-worker-supervisor-NZQUOEFJ.js → channel-worker-supervisor-WSOAAYUO.js} +137 -13
  29. package/chunks/{chunk-QQDPRDVW.js → chunk-2HRYPZT5.js} +1 -1
  30. package/chunks/{chunk-2PSWA5ID.js → chunk-2X56YUSZ.js} +1 -1
  31. package/chunks/{chunk-DIWVZ3VM.js → chunk-34MW4KTI.js} +5 -5
  32. package/chunks/{chunk-IBY3Q3HG.js → chunk-34Z3U5RQ.js} +508 -126
  33. package/chunks/{chunk-UXEW557D.js → chunk-3AHXV67U.js} +3 -3
  34. package/chunks/{chunk-O2YT7VB4.js → chunk-3BTRNWWD.js} +1 -1
  35. package/chunks/chunk-3G7CIHVV.js +71 -0
  36. package/chunks/{chunk-FBET5XZ2.js → chunk-3SJSQXNS.js} +20 -5
  37. package/chunks/{chunk-BUZ6HGL4.js → chunk-3VAGG5T5.js} +248 -55
  38. package/chunks/{chunk-M7S4GK5Z.js → chunk-4C5BQ2D2.js} +74 -27
  39. package/chunks/{chunk-5MBO76SL.js → chunk-4GORAUCN.js} +3 -3
  40. package/chunks/{chunk-55ZMG67I.js → chunk-4LD5APFL.js} +4 -4
  41. package/chunks/{chunk-TLWA7TPR.js → chunk-4QLQE5WJ.js} +2 -2
  42. package/chunks/{chunk-UIDXQNMV.js → chunk-4WGODMVR.js} +1 -1
  43. package/chunks/{chunk-E6US47KI.js → chunk-4WKAA4KS.js} +1 -1
  44. package/chunks/{chunk-H6EMK6QK.js → chunk-4ZVWQHEQ.js} +11913 -5207
  45. package/chunks/{chunk-HW4N45EE.js → chunk-524RQP4Q.js} +29 -8
  46. package/chunks/{chunk-3GONHQOA.js → chunk-57OAHC2Y.js} +1 -1
  47. package/chunks/{chunk-JZSY4WP3.js → chunk-5DTTYNB7.js} +1 -1
  48. package/chunks/{chunk-YDOXPLYL.js → chunk-5HOMWMCL.js} +1147 -738
  49. package/chunks/{chunk-A4BMJM77.js → chunk-5O2XNYP6.js} +1 -0
  50. package/chunks/{chunk-ZL5HN75Q.js → chunk-5SFOMFG4.js} +2 -2
  51. package/chunks/{chunk-2EM7ECZD.js → chunk-5U27BZ72.js} +179 -63
  52. package/chunks/{chunk-JRQ7EVGE.js → chunk-5VXNZWGF.js} +2 -2
  53. package/chunks/{chunk-QYNNN7BK.js → chunk-5XUCZNSY.js} +2 -2
  54. package/chunks/{chunk-GO6LNQXT.js → chunk-6AM5D4ME.js} +1 -1
  55. package/chunks/{chunk-BJBWRCSK.js → chunk-6H67XLET.js} +1 -1
  56. package/chunks/{chunk-PUVCVELY.js → chunk-6PESIEKJ.js} +4 -4
  57. package/chunks/{chunk-3UP777ZS.js → chunk-6RLROE6G.js} +1 -1
  58. package/chunks/{chunk-ZERZSAZL.js → chunk-75DOP5OR.js} +2 -2
  59. package/chunks/{chunk-RMLZAUWH.js → chunk-77XJ3SWN.js} +10 -12
  60. package/chunks/{chunk-L46XKEGM.js → chunk-7PU4FYLG.js} +5 -8
  61. package/chunks/{chunk-NQKLOAVJ.js → chunk-7R3CZA3Q.js} +2 -2
  62. package/chunks/{chunk-SAIILPQ3.js → chunk-7X7TEQNX.js} +5 -3
  63. package/chunks/{chunk-OQXDAMKY.js → chunk-A3XKDTSH.js} +8 -8
  64. package/chunks/{chunk-IRJWHZWU.js → chunk-A4UCTEB5.js} +2 -2
  65. package/chunks/{chunk-TSPB77TG.js → chunk-AKIVHSJR.js} +923 -2892
  66. package/chunks/{chunk-FQNC62B2.js → chunk-ATAKVRIF.js} +11 -11
  67. package/chunks/{chunk-GSHQYYIV.js → chunk-AURZZYMD.js} +6 -26
  68. package/chunks/{chunk-CHB5SLZZ.js → chunk-B4G2WOFA.js} +3 -3
  69. package/chunks/{chunk-X3YGAX7V.js → chunk-B5ORX3FG.js} +4 -2
  70. package/chunks/chunk-BAPGSC7U.js +239 -0
  71. package/chunks/{chunk-UX6OTBVD.js → chunk-BMGVRZN4.js} +2 -2
  72. package/chunks/{chunk-ZN5T4BHI.js → chunk-BPZALHVR.js} +2 -2
  73. package/chunks/{chunk-IJRL2S3D.js → chunk-BZIFY5TF.js} +6 -6
  74. package/chunks/{chunk-OFEVLU4C.js → chunk-CARU2RR2.js} +1 -1
  75. package/chunks/chunk-CC2ITGCF.js +176 -0
  76. package/chunks/{chunk-HDOH3QYE.js → chunk-CCDMCZPP.js} +2 -2
  77. package/chunks/{chunk-FV7425LN.js → chunk-CEA3E3JB.js} +18 -5
  78. package/chunks/{chunk-BOBOCR5H.js → chunk-CLFKOZEK.js} +4 -4
  79. package/chunks/{chunk-P3XEEI3X.js → chunk-CNEIYWSA.js} +38 -9
  80. package/chunks/{chunk-GLE2YUPU.js → chunk-COH43MRJ.js} +1 -1
  81. package/chunks/{chunk-E5A7LHNN.js → chunk-CPBF7KYF.js} +1 -1
  82. package/chunks/{chunk-UWCTAVOD.js → chunk-CR3C7WXL.js} +1 -1
  83. package/chunks/{chunk-5P5XGNYH.js → chunk-CU3L64TP.js} +1 -1
  84. package/chunks/{chunk-CD6USWHZ.js → chunk-CUMMSHH6.js} +2 -2
  85. package/chunks/{chunk-IWKSG2AR.js → chunk-CXAOG665.js} +1 -1
  86. package/chunks/{chunk-WQTHLB5T.js → chunk-D2GFWEXB.js} +7 -7
  87. package/chunks/{chunk-I6WGFJJ3.js → chunk-D5SKC4YG.js} +38 -12
  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-QN5NZ3UQ.js → chunk-DMTGGOSA.js} +2 -2
  91. package/chunks/{chunk-A5JOJ77H.js → chunk-E2IY465S.js} +38 -15
  92. package/chunks/{chunk-KHDZHZMH.js → chunk-E5Z2AVNV.js} +1 -1
  93. package/chunks/{chunk-SQ2UATK7.js → chunk-EAXLGSFP.js} +11 -11
  94. package/chunks/{chunk-JCH35JB3.js → chunk-ECNYL4AA.js} +2 -2
  95. package/chunks/{chunk-E7EMCSUG.js → chunk-EGYZ275M.js} +64 -42
  96. package/chunks/{chunk-6VK6FIMQ.js → chunk-EMDMPBJ4.js} +415 -379
  97. package/chunks/{chunk-5IFG2VC4.js → chunk-FCMNKVXT.js} +1 -1
  98. package/chunks/{chunk-V35MD7KN.js → chunk-GRC5HGPI.js} +1 -1
  99. package/chunks/{chunk-WMPVYQ4P.js → chunk-GWHYEOV2.js} +2 -2
  100. package/chunks/{chunk-27BHARDE.js → chunk-GWLSNBG4.js} +2103 -13
  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-DICGXFAI.js → chunk-HKMAENAO.js} +8 -8
  105. package/chunks/{chunk-ZE6E424U.js → chunk-HPDUL5MF.js} +2 -2
  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-ON3JA3RU.js → chunk-HVY356M2.js} +7 -9
  109. package/chunks/{chunk-GGQQA4JW.js → chunk-IAVB3HM3.js} +2 -2
  110. package/chunks/{chunk-FEJ2FZ3U.js → chunk-J3JA76CF.js} +2 -2
  111. package/chunks/{chunk-4CGOWHKM.js → chunk-J4MOFEOK.js} +2 -2
  112. package/chunks/{chunk-22MNNNXE.js → chunk-J4TSQ5EQ.js} +3 -2
  113. package/chunks/{chunk-22IFUCVR.js → chunk-JESCGQM3.js} +1 -1
  114. package/chunks/{chunk-CCGOFQV3.js → chunk-JSG7ZSBC.js} +8 -7
  115. package/chunks/{chunk-WF4FAXDL.js → chunk-JZETEN3Z.js} +62 -62
  116. package/chunks/{chunk-GN3F6PIU.js → chunk-K434OPES.js} +5 -5
  117. package/chunks/{chunk-YKZIAK7C.js → chunk-KHUQZZJ6.js} +66 -16
  118. package/chunks/{chunk-X2XNQQJI.js → chunk-KKNMOEZH.js} +4 -4
  119. package/chunks/{chunk-MRO43B25.js → chunk-KW7NOTN6.js} +1 -1
  120. package/chunks/{chunk-6IAWPIQ6.js → chunk-L36P3NBD.js} +3 -3
  121. package/chunks/{chunk-54YZOP7P.js → chunk-L573Z2QZ.js} +11894 -5190
  122. package/chunks/{chunk-DHOLNQLC.js → chunk-L6P4KPHR.js} +12 -12
  123. package/chunks/{chunk-E7KNELIZ.js → chunk-LK7KUE55.js} +2 -2
  124. package/chunks/{chunk-W7EEOR7D.js → chunk-LUODI7WD.js} +5 -5
  125. package/chunks/{chunk-CSOTA7KX.js → chunk-LXNEJD34.js} +2 -2
  126. package/chunks/{chunk-HLKG6CZN.js → chunk-M5GB774H.js} +1 -1
  127. package/chunks/{chunk-3B26UVCX.js → chunk-MC23XKFE.js} +39 -10
  128. package/chunks/{chunk-PC4ZXHU2.js → chunk-MXL5UP4X.js} +3 -3
  129. package/chunks/{chunk-BVYU3ZTI.js → chunk-N3EMS75V.js} +73 -30
  130. package/chunks/{chunk-332PWN27.js → chunk-NHDKSOEY.js} +1 -1
  131. package/chunks/{chunk-3GNOQZDC.js → chunk-NVYTCB5Z.js} +4797 -2620
  132. package/chunks/{chunk-P7M4GA57.js → chunk-OCPBI7J5.js} +1 -1
  133. package/chunks/chunk-OQ4DRIOA.js +296 -0
  134. package/chunks/{chunk-42XGFRJS.js → chunk-ORBGV7NJ.js} +5 -5
  135. package/chunks/{chunk-AYNCLGTW.js → chunk-P67PFTUT.js} +8 -51
  136. package/chunks/{chunk-VGGOPZUL.js → chunk-PDZ7MBDG.js} +12 -5
  137. package/chunks/{chunk-WHJQ3JUS.js → chunk-PODM4SZ4.js} +12 -12
  138. package/chunks/{chunk-JPALE3PA.js → chunk-PVZM22CK.js} +6 -6
  139. package/chunks/{chunk-AGHU5VRK.js → chunk-PZL23GTM.js} +1 -1
  140. package/chunks/{chunk-L4AMBVQG.js → chunk-Q4YQW46J.js} +31 -17
  141. package/chunks/{chunk-XYR5RUA5.js → chunk-Q6I5QHMH.js} +10 -3
  142. package/chunks/{chunk-FXGLL2HL.js → chunk-Q7YDKOGY.js} +1 -1
  143. package/chunks/{chunk-LGFAYIKX.js → chunk-QHTIBUWB.js} +1 -1
  144. package/chunks/{chunk-P3MYAZBY.js → chunk-QMGX2KO2.js} +1 -1
  145. package/chunks/{chunk-Z3I3QJK3.js → chunk-QRUIS3DN.js} +1 -1
  146. package/chunks/{chunk-ITOGNELQ.js → chunk-QSWDGAIC.js} +2 -2
  147. package/chunks/{chunk-GHOTR7HL.js → chunk-QUPXZXLV.js} +1 -1
  148. package/chunks/{chunk-XOHNA2S6.js → chunk-QYMQSECS.js} +3 -3
  149. package/chunks/{chunk-QYUE6W3T.js → chunk-RDJSGHZE.js} +1 -1
  150. package/chunks/{chunk-TLRYABYP.js → chunk-RKUWKYED.js} +1 -1
  151. package/chunks/{chunk-RNBYOUGV.js → chunk-RXFQM6FQ.js} +1 -1
  152. package/chunks/{chunk-QXPI7FEC.js → chunk-S3TWXGKA.js} +4 -4
  153. package/chunks/{chunk-BOBRZZKD.js → chunk-SCGXUJBT.js} +5 -5
  154. package/chunks/{chunk-44NROQYV.js → chunk-SJHDYNHF.js} +4 -4
  155. package/chunks/{chunk-MLZQVCF3.js → chunk-SZRN75WQ.js} +1 -1
  156. package/chunks/{chunk-7RYW5LQV.js → chunk-T2NCM2ET.js} +2 -2
  157. package/chunks/{chunk-Z2Z3GUXZ.js → chunk-TB6UDU4T.js} +1 -1
  158. package/chunks/{chunk-SUAKHHK7.js → chunk-TDEXZKMT.js} +3 -3
  159. package/chunks/{chunk-EXPMGZZV.js → chunk-TEGEBB2I.js} +1 -1
  160. package/chunks/{chunk-MX2YXRER.js → chunk-TJTKSVIV.js} +1 -1
  161. package/chunks/chunk-TO6QE22P.js +444 -0
  162. package/chunks/{chunk-H6BD2ELD.js → chunk-TYAMGABM.js} +2 -2
  163. package/chunks/chunk-U2NNEY4R.js +38 -0
  164. package/chunks/{chunk-VRDMOSQS.js → chunk-UDXSXLER.js} +2 -2
  165. package/chunks/{chunk-AHNJGLFV.js → chunk-UOZWKTQG.js} +3 -3
  166. package/chunks/{chunk-CJCKDMWO.js → chunk-URK542T4.js} +1 -1
  167. package/chunks/{chunk-AHNXYU4O.js → chunk-V6WHFGST.js} +1 -1
  168. package/chunks/{chunk-DCZWC4PF.js → chunk-VA4KQY6V.js} +5 -5
  169. package/chunks/{chunk-ZTQ26VBE.js → chunk-VDDGDZMA.js} +1 -1
  170. package/chunks/{chunk-OMX7CUOE.js → chunk-VGC4I5JJ.js} +1 -1
  171. package/chunks/{chunk-43W4YSO6.js → chunk-VJNBRRZG.js} +2 -2
  172. package/chunks/{chunk-J7RCB6N5.js → chunk-VTU57BWZ.js} +2 -2
  173. package/chunks/{chunk-LAXNEOBG.js → chunk-VWPVHAMO.js} +15 -3
  174. package/chunks/{chunk-VZ7HY3YU.js → chunk-W2UIXQDL.js} +3 -3
  175. package/chunks/{chunk-JZHIQJOE.js → chunk-W3JHSWRP.js} +6 -6
  176. package/chunks/{chunk-ER3BKOLB.js → chunk-W7FJ3N32.js} +1 -1
  177. package/chunks/{chunk-3HX5LZ6R.js → chunk-WBL3FJEU.js} +2 -2
  178. package/chunks/{chunk-BMFMJINR.js → chunk-WL4L7EZH.js} +1 -1
  179. package/chunks/{chunk-PMFULMLX.js → chunk-WQUQVGTF.js} +2057 -266
  180. package/chunks/chunk-X4NKFGFJ.js +81 -0
  181. package/chunks/{chunk-YHKAK72D.js → chunk-X6TJ3YZV.js} +102 -16
  182. package/chunks/{chunk-3RW4AZJV.js → chunk-XFSFZCM5.js} +1 -1
  183. package/chunks/{chunk-O7FIRWJV.js → chunk-XLA62MYB.js} +3 -3
  184. package/chunks/{chunk-2MPVVENX.js → chunk-XU457J4V.js} +2 -2
  185. package/chunks/{chunk-EEN2TKST.js → chunk-XUOOWUZS.js} +2 -2
  186. package/chunks/{chunk-SIUQ3YYX.js → chunk-XVNQMZ2I.js} +1 -1
  187. package/chunks/{chunk-SYCJMSIJ.js → chunk-XWRJCPHC.js} +1 -1
  188. package/chunks/{chunk-TI3BWD4D.js → chunk-XYQSMABS.js} +39 -19
  189. package/chunks/chunk-YAF2MQZ6.js +19 -0
  190. package/chunks/{chunk-O33TBKDC.js → chunk-YAUFFXDS.js} +154 -19
  191. package/chunks/{chunk-OACJLMLD.js → chunk-YLV3XXFB.js} +2 -2
  192. package/chunks/{chunk-BR4QREVK.js → chunk-YQ3U5MUC.js} +1 -1
  193. package/chunks/{chunk-Y6Z2O3WR.js → chunk-YUZI3WAC.js} +1 -1
  194. package/chunks/{chunk-II73RK2S.js → chunk-YVEDCAUE.js} +1 -1
  195. package/chunks/{chunk-SKAZWEV5.js → chunk-ZJE7NSL3.js} +1 -1
  196. package/chunks/{chunk-7LTB54MK.js → chunk-ZLXQN2QS.js} +2 -2
  197. package/chunks/cli-entry-path-4VJ3Y6T2.js +10 -0
  198. package/chunks/{computer-use-WMY3UOBX.js → computer-use-HM37RX6A.js} +50 -50
  199. package/chunks/config-utils-NV2SVGPB.js +23 -0
  200. package/chunks/contextCommand-OF5SFGH2.js +68 -0
  201. package/chunks/create-sub-session-INX7TXJI.js +207 -0
  202. package/chunks/create-sub-session-KPRT7ECJ.js +375 -0
  203. package/chunks/{cron-create-JSXEIPVS.js → cron-create-AMBKGZX6.js} +9 -9
  204. package/chunks/{cron-delete-OIFVEPGU.js → cron-delete-LRU4OJYG.js} +7 -7
  205. package/chunks/{cron-list-D4ZX4IOE.js → cron-list-WVP2JXER.js} +14 -10
  206. package/chunks/{daemon-WPAENEBW.js → daemon-ARHTSTXP.js} +478 -6
  207. package/chunks/daemon-status-provider-B2UTZUJ2.js +76 -0
  208. package/chunks/{de-ZFPPQVSF.js → de-EBJN3OVN.js} +64 -5
  209. package/chunks/{devtools-FM6GJPYG.js → devtools-4QFYJT6U.js} +3 -3
  210. package/chunks/{dist-Z34QPAQW.js → dist-PNVLTKTL.js} +374 -78
  211. package/chunks/{dist-63IS3ZMI.js → dist-PZV5RKM6.js} +4 -4
  212. package/chunks/{dist-BEXOZXLQ.js → dist-QYCAEZIT.js} +32 -15
  213. package/chunks/{dist-H2AEJCZ5.js → dist-R7LN5AZE.js} +5 -5
  214. package/chunks/{dist-E6UANPIM.js → dist-VHV4EVHG.js} +1011 -197
  215. package/chunks/{dist-O2IRTRPP.js → dist-WKPOYU7O.js} +10 -10
  216. package/chunks/{dist-SCFLLYMK.js → dist-XKU3ABM5.js} +12 -11
  217. package/chunks/earlyInputCapture-3VTHRC4C.js +69 -0
  218. package/chunks/{edit-OTYWAXGP.js → edit-VNI4NJ73.js} +51 -51
  219. package/chunks/{en-N4EPZAQI.js → en-5A6LA7RK.js} +74 -6
  220. package/chunks/{enter-worktree-HNYBDVCX.js → enter-worktree-IYYBKPZT.js} +50 -50
  221. package/chunks/{enterPlanMode-CJCNGT63.js → enterPlanMode-GMC5FD5A.js} +50 -50
  222. package/chunks/environment-5HSSRMJ7.js +88 -0
  223. package/chunks/errors-HMFCJJ7P.js +75 -0
  224. package/chunks/{exit-worktree-DUUKOE3X.js → exit-worktree-6BHCMP5G.js} +50 -50
  225. package/chunks/{exitPlanMode-AMMSXOAJ.js → exitPlanMode-CJW7G74W.js} +51 -51
  226. package/chunks/{fast-path-OV6NIP3K.js → fast-path-FRDMDVHG.js} +8 -8
  227. package/chunks/{fast-path-settings-7RIFI3PK.js → fast-path-settings-5QZE64HR.js} +6 -6
  228. package/chunks/{fileFromPath-IBEHA3CO.js → fileFromPath-UDALK7FM.js} +3 -3
  229. package/chunks/{fr-5WWMY53B.js → fr-YXRABLYZ.js} +64 -5
  230. package/chunks/{gemini-NQ3WWBKB.js → gemini-QYSEGHAX.js} +119 -111
  231. package/chunks/{geminiContentGenerator-CDUNEJQE.js → geminiContentGenerator-J7YNZDYP.js} +9 -9
  232. package/chunks/{getMachineId-bsd-4CASPIU4.js → getMachineId-bsd-EXV7SWPA.js} +3 -3
  233. package/chunks/{getMachineId-darwin-HPQPEMZR.js → getMachineId-darwin-IUOMFXU3.js} +3 -3
  234. package/chunks/{getMachineId-linux-AUARKYHL.js → getMachineId-linux-K7XJYFHL.js} +2 -2
  235. package/chunks/{getMachineId-unsupported-S32ZDA2T.js → getMachineId-unsupported-EA5FDRJ6.js} +2 -2
  236. package/chunks/{getMachineId-win-4EFLHYIJ.js → getMachineId-win-KBA6RAI5.js} +3 -3
  237. package/chunks/{glob-ROV3YS4R.js → glob-BRCSGFOT.js} +95 -74
  238. package/chunks/{grep-NRITKDCD.js → grep-EE2DUOSY.js} +50 -50
  239. package/chunks/handleAutoUpdate-5NWTLR67.js +69 -0
  240. package/chunks/i18n-BASPENNQ.js +84 -0
  241. package/chunks/initializer-UTQ7FB7G.js +71 -0
  242. package/chunks/installationInfo-W6V4DT7W.js +65 -0
  243. package/chunks/{ja-CUHZOLEJ.js → ja-7VQOANVG.js} +64 -5
  244. package/chunks/{keychain-token-storage-UJV5XP4Z.js → keychain-token-storage-VKUBNCP4.js} +4 -4
  245. package/chunks/{kittyProtocolDetector-OJRCJLIU.js → kittyProtocolDetector-HCNNKJTI.js} +2 -2
  246. package/chunks/list-DEICDL65.js +74 -0
  247. package/chunks/loadedSettingsAdapter-NOWEGDYJ.js +68 -0
  248. package/chunks/{loop-wakeup-OHWEMFP5.js → loop-wakeup-WX7BLWWP.js} +10 -10
  249. package/chunks/{lowlight-FYAAUU5J.js → lowlight-J2OZNPCA.js} +1 -1
  250. package/chunks/{ls-7MFU2QYW.js → ls-ZB2BDP6T.js} +6 -6
  251. package/chunks/{lsp-XHDTPTGV.js → lsp-V5DN4YUJ.js} +4 -4
  252. package/chunks/mcp-L5JXEEIA.js +68 -0
  253. package/chunks/{monitor-FL2ZO33W.js → monitor-X5Z3H4FY.js} +50 -50
  254. package/chunks/{multipart-parser-AJ4WASWR.js → multipart-parser-AWZKXSQN.js} +3 -3
  255. package/chunks/nonInteractiveCli-LS2K4X6A.js +124 -0
  256. package/chunks/{notebook-edit-6PULTHL5.js → notebook-edit-TRM2QIJK.js} +51 -51
  257. package/chunks/openaiContentGenerator-AAYONPMF.js +56 -0
  258. package/chunks/pidfile-D4I4FTVA.js +73 -0
  259. package/chunks/{pt-OLYN42DS.js → pt-2C6YCSHL.js} +64 -5
  260. package/chunks/{qwenContentGenerator-NX2E7PB7.js → qwenContentGenerator-K3HZ2H76.js} +52 -52
  261. package/chunks/{qwenOAuth2-ZV7YLMND.js → qwenOAuth2-PJQ27GSO.js} +9 -9
  262. package/chunks/read-file-7SZSIZOL.js +30 -0
  263. package/chunks/{read-mcp-resource-CNQHBSJM.js → read-mcp-resource-SM5DA4RG.js} +5 -5
  264. package/chunks/{read-package-up-ER5OJUGP.js → read-package-up-TCM6I7S2.js} +2 -2
  265. package/chunks/{record-artifact-SQ2OKRJW.js → record-artifact-4XKZX3UW.js} +4 -4
  266. package/chunks/ripGrep-YNPFVCZP.js +60 -0
  267. package/chunks/{ru-LDM3GWVQ.js → ru-6BBHVZZV.js} +64 -5
  268. package/chunks/{run-qwen-serve-JK2DL7PI.js → run-qwen-serve-DMWV5N6L.js} +395 -74
  269. package/chunks/runtime-57WWRVHN.js +94 -0
  270. package/chunks/{scheduler-O4IGVZKK.js → scheduler-YBLGWSIL.js} +51 -51
  271. package/chunks/{send-message-76GT3JMC.js → send-message-F36YD6KQ.js} +7 -7
  272. package/chunks/serve-R63NK3CV.js +74 -0
  273. package/chunks/{server-SGNPKX2O.js → server-Z22GCAXP.js} +3956 -869
  274. package/chunks/{session-5BNM25EZ.js → session-HKAAI7BN.js} +117 -112
  275. package/chunks/settings-3EYN66GK.js +118 -0
  276. package/chunks/shell-6YXINTWJ.js +68 -0
  277. package/chunks/{skill-I22QK5XS.js → skill-OO57P4X6.js} +24 -24
  278. package/chunks/spawnChannel-JD7G4VU6.js +70 -0
  279. package/chunks/{src-BBQSM46M.js → src-G5UPLZSZ.js} +122 -52
  280. package/chunks/{src-7XL4G4DC.js → src-VLI3TKEY.js} +4 -4
  281. package/chunks/standalone-update-OB7F4JPZ.js +79 -0
  282. package/chunks/{startInteractiveUI-JII36UGN.js → startInteractiveUI-73EGLIIH.js} +1775 -1663
  283. package/chunks/stdioHelpers-UMY72QRP.js +14 -0
  284. package/chunks/{syntheticOutput-F7LSDCAR.js → syntheticOutput-34H7LGA7.js} +5 -5
  285. package/chunks/task-create-FRGOEIKY.js +22 -0
  286. package/chunks/{task-list-TGGJX7EA.js → task-list-OW766BXZ.js} +10 -10
  287. package/chunks/{task-stop-W7NGTMYQ.js → task-stop-SIR5SNBN.js} +4 -4
  288. package/chunks/{task-update-XYWMHJGJ.js → task-update-YRA532MT.js} +12 -12
  289. package/chunks/{team-create-A7NUU5AQ.js → team-create-PECAAPXK.js} +50 -50
  290. package/chunks/{team-delete-UHQQ2QQX.js → team-delete-6OSECUZC.js} +10 -10
  291. package/chunks/{team-plan-approval-TOQRJ2KI.js → team-plan-approval-L6F434RY.js} +50 -50
  292. package/chunks/theme-manager-ZG4RY6GU.js +64 -0
  293. package/chunks/{todoWrite-C4ZAMSR3.js → todoWrite-YDMFEV4G.js} +6 -6
  294. package/chunks/{tool-search-SZ4EFS2T.js → tool-search-THVIWAPB.js} +24 -24
  295. package/chunks/total-session-admission-JOKNRIIA.js +72 -0
  296. package/chunks/trustedFolders-EOGT3UVS.js +79 -0
  297. package/chunks/{types-QX5C3CHJ.js → types-JNKGKUJT.js} +2 -2
  298. package/chunks/updateCheck-2KYEYVSB.js +69 -0
  299. package/chunks/updateEventEmitter-5MKC7BLQ.js +10 -0
  300. package/chunks/validateNonInterActiveAuth-EN6ZQSR2.js +190 -0
  301. package/chunks/{version-NO2XSNNP.js → version-3UEA6ZIR.js} +3 -3
  302. package/chunks/{web-fetch-UYGMOHW4.js → web-fetch-PAAEQKBW.js} +8 -8
  303. package/chunks/{workflow-WO2H6NRI.js → workflow-QCCCMYP3.js} +51 -51
  304. package/chunks/workspace-providers-status-4KMGETOJ.js +71 -0
  305. package/chunks/workspace-registry-24QFLQYH.js +76 -0
  306. package/chunks/workspace-service-RVLBMSMZ.js +80 -0
  307. package/chunks/workspace-skills-status-DS4ZVFTO.js +70 -0
  308. package/chunks/{write-file-PX2Z7IBE.js → write-file-I3MUDRIE.js} +51 -51
  309. package/chunks/{yargs-B64EYTCF.js → yargs-6H2AUULL.js} +2 -2
  310. package/chunks/{zh-J4F6QWQV.js → zh-RQE7I22T.js} +74 -6
  311. package/chunks/{zh-TW-XVDWOLSW.js → zh-TW-TBPQGHKH.js} +74 -6
  312. package/cli.js +18 -17
  313. package/locales/ca.js +85 -7
  314. package/locales/de.js +84 -7
  315. package/locales/en.js +101 -7
  316. package/locales/fr.js +85 -7
  317. package/locales/ja.js +86 -6
  318. package/locales/pt.js +84 -7
  319. package/locales/ru.js +84 -7
  320. package/locales/zh-TW.js +97 -6
  321. package/locales/zh.js +97 -6
  322. package/package.json +3 -6
  323. package/web-shell/assets/{arc-BWyoJpY2.js → arc-C15hqnJe.js} +1 -1
  324. package/web-shell/assets/{architectureDiagram-3BPJPVTR-BT0mGrz2.js → architectureDiagram-3BPJPVTR-B-At37J_.js} +1 -1
  325. package/web-shell/assets/{blockDiagram-GPEHLZMM-BLgUWvyf.js → blockDiagram-GPEHLZMM-B51wjcU7.js} +1 -1
  326. package/web-shell/assets/{c4Diagram-AAUBKEIU-DYx9M7oU.js → c4Diagram-AAUBKEIU-xBK7BAwN.js} +1 -1
  327. package/web-shell/assets/channel-CLiQD50f.js +1 -0
  328. package/web-shell/assets/{chunk-2J33WTMH-CfMHWbuZ.js → chunk-2J33WTMH-xKmLg7Yr.js} +1 -1
  329. package/web-shell/assets/{chunk-4BX2VUAB-B1URoqEV.js → chunk-4BX2VUAB-BfkdKZCM.js} +1 -1
  330. package/web-shell/assets/{chunk-55IACEB6-DAIaM4QF.js → chunk-55IACEB6-DT713sZf.js} +1 -1
  331. package/web-shell/assets/{chunk-727SXJPM-B2X34knw.js → chunk-727SXJPM-CYEeAamv.js} +1 -1
  332. package/web-shell/assets/{chunk-AQP2D5EJ-Cif-zLTQ.js → chunk-AQP2D5EJ-DBN-5CFd.js} +1 -1
  333. package/web-shell/assets/{chunk-FMBD7UC4-H18WCXdY.js → chunk-FMBD7UC4-CGZKk4PH.js} +1 -1
  334. package/web-shell/assets/{chunk-ND2GUHAM-YFtetHi0.js → chunk-ND2GUHAM-qQbC2O-y.js} +1 -1
  335. package/web-shell/assets/{chunk-QZHKN3VN-By2hiH3r.js → chunk-QZHKN3VN-B3zeXwsX.js} +1 -1
  336. package/web-shell/assets/classDiagram-4FO5ZUOK-D3sYqow5.js +1 -0
  337. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-D3sYqow5.js +1 -0
  338. package/web-shell/assets/{cose-bilkent-S5V4N54A-VrcnnLZu.js → cose-bilkent-S5V4N54A-BKsl1Wvy.js} +1 -1
  339. package/web-shell/assets/{dagre-BM42HDAG-CGo-IaVV.js → dagre-BM42HDAG-CuuuLiBK.js} +1 -1
  340. package/web-shell/assets/{diagram-2AECGRRQ-Dl4QjzIB.js → diagram-2AECGRRQ-CG4_eKiV.js} +1 -1
  341. package/web-shell/assets/{diagram-5GNKFQAL-qMLmsO0u.js → diagram-5GNKFQAL-DSFTmZB1.js} +1 -1
  342. package/web-shell/assets/{diagram-KO2AKTUF-CHlmuxPI.js → diagram-KO2AKTUF-BxwlEHn8.js} +1 -1
  343. package/web-shell/assets/{diagram-LMA3HP47-BUwU3GwT.js → diagram-LMA3HP47-BUAg-jMG.js} +1 -1
  344. package/web-shell/assets/{diagram-OG6HWLK6-HEhwPDUM.js → diagram-OG6HWLK6-BFM2jlO5.js} +1 -1
  345. package/web-shell/assets/{erDiagram-TEJ5UH35-1n0llBmM.js → erDiagram-TEJ5UH35-CerbFPa2.js} +1 -1
  346. package/web-shell/assets/{flowDiagram-I6XJVG4X-BN74uMnt.js → flowDiagram-I6XJVG4X-BNeZz5Zz.js} +1 -1
  347. package/web-shell/assets/{ganttDiagram-6RSMTGT7-BoOq78i3.js → ganttDiagram-6RSMTGT7-CTOogLaM.js} +1 -1
  348. package/web-shell/assets/{gitGraphDiagram-PVQCEYII-CN8b00c8.js → gitGraphDiagram-PVQCEYII-yxSFkXPj.js} +1 -1
  349. package/web-shell/assets/index-D5IWFVk1.css +5 -0
  350. package/web-shell/assets/index-KFJRDhX7.js +783 -0
  351. package/web-shell/assets/{infoDiagram-5YYISTIA-CCurnIna.js → infoDiagram-5YYISTIA-DYdAUevm.js} +1 -1
  352. package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-AwuRqdUP.js → ishikawaDiagram-YF4QCWOH-CNuijZ90.js} +1 -1
  353. package/web-shell/assets/{journeyDiagram-JHISSGLW-B7IICk-E.js → journeyDiagram-JHISSGLW-BTyiuonf.js} +1 -1
  354. package/web-shell/assets/{kanban-definition-UN3LZRKU-BYF0OKhx.js → kanban-definition-UN3LZRKU-DPJo0Uow.js} +1 -1
  355. package/web-shell/assets/{linear-GWWguGfD.js → linear-C8wKCbIC.js} +1 -1
  356. package/web-shell/assets/{mermaid.core-JqtkxvCl.js → mermaid.core-BV5o7nW5.js} +5 -5
  357. package/web-shell/assets/{mindmap-definition-RKZ34NQL-B0MmHsZ6.js → mindmap-definition-RKZ34NQL-BtLcBmSt.js} +1 -1
  358. package/web-shell/assets/{pieDiagram-4H26LBE5-DhImUZLQ.js → pieDiagram-4H26LBE5-C22J7A9Q.js} +1 -1
  359. package/web-shell/assets/{quadrantDiagram-W4KKPZXB-DGeSC3DT.js → quadrantDiagram-W4KKPZXB-DSL10wiL.js} +1 -1
  360. package/web-shell/assets/{requirementDiagram-4Y6WPE33-DkspY6Re.js → requirementDiagram-4Y6WPE33-feqh0UpX.js} +1 -1
  361. package/web-shell/assets/{sankeyDiagram-5OEKKPKP-CaOofS2x.js → sankeyDiagram-5OEKKPKP-Z4OVaWaZ.js} +1 -1
  362. package/web-shell/assets/{sequenceDiagram-3UESZ5HK-DFQ_kFnN.js → sequenceDiagram-3UESZ5HK-DIluIcb1.js} +1 -1
  363. package/web-shell/assets/{stateDiagram-AJRCARHV-D3snw0Tz.js → stateDiagram-AJRCARHV-hlhnuoYH.js} +1 -1
  364. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-BhQMwgpl.js +1 -0
  365. package/web-shell/assets/{timeline-definition-PNZ67QCA-l4zH8nLM.js → timeline-definition-PNZ67QCA-Dqf_CqgL.js} +1 -1
  366. package/web-shell/assets/{vennDiagram-CIIHVFJN-BnRlA98M.js → vennDiagram-CIIHVFJN-DD2QhaTX.js} +1 -1
  367. package/web-shell/assets/{wardley-L42UT6IY-BS0xUdmd.js → wardley-L42UT6IY-B2uI85U_.js} +1 -1
  368. package/web-shell/assets/{wardleyDiagram-YWT4CUSO-DKGBUWyQ.js → wardleyDiagram-YWT4CUSO-U1aJX71g.js} +1 -1
  369. package/web-shell/assets/{xychartDiagram-2RQKCTM6-DVb2HuOB.js → xychartDiagram-2RQKCTM6-C0ODHUee.js} +1 -1
  370. package/web-shell/index.html +16 -4
  371. package/chunks/MaxSizedBox-5DT2SD7B.js +0 -71
  372. package/chunks/agent-H2DGKS77.js +0 -68
  373. package/chunks/agent-headless-KFMFIT6J.js +0 -62
  374. package/chunks/bridge-NA3ZQQKJ.js +0 -78
  375. package/chunks/chunk-6SHO7WFF.js +0 -79
  376. package/chunks/chunk-IFEWJXFS.js +0 -37
  377. package/chunks/chunk-S6RLAIUR.js +0 -23
  378. package/chunks/chunk-XNXVCNAT.js +0 -88
  379. package/chunks/contextCommand-LXFZRP4L.js +0 -68
  380. package/chunks/daemon-status-provider-GJRXY5EN.js +0 -76
  381. package/chunks/earlyInputCapture-KNOYZ4MJ.js +0 -69
  382. package/chunks/environment-ATFTYMLZ.js +0 -88
  383. package/chunks/errors-A54HAADA.js +0 -75
  384. package/chunks/handleAutoUpdate-CEZJAARW.js +0 -65
  385. package/chunks/initializer-BRFJKRRX.js +0 -71
  386. package/chunks/list-KVHF7O4X.js +0 -73
  387. package/chunks/loadedSettingsAdapter-LL5PSFKX.js +0 -68
  388. package/chunks/mcp-3GCXRTOQ.js +0 -68
  389. package/chunks/nonInteractiveCli-PTQI3DV6.js +0 -119
  390. package/chunks/openaiContentGenerator-YE42PKCY.js +0 -56
  391. package/chunks/pidfile-NZYSCFRK.js +0 -73
  392. package/chunks/read-file-33XGP5HF.js +0 -30
  393. package/chunks/ripGrep-QWZLGTAN.js +0 -60
  394. package/chunks/serve-JTMIGANH.js +0 -74
  395. package/chunks/settings-3MI4XXP6.js +0 -118
  396. package/chunks/shell-YJKBE67T.js +0 -68
  397. package/chunks/spawnChannel-UNV6W4DU.js +0 -70
  398. package/chunks/task-create-WCHTB435.js +0 -22
  399. package/chunks/theme-manager-WZ4RRMMB.js +0 -64
  400. package/chunks/total-session-admission-ULXJWXFV.js +0 -72
  401. package/chunks/trustedFolders-LAZZPZ3D.js +0 -79
  402. package/chunks/updateCheck-KONLM73X.js +0 -65
  403. package/chunks/validateNonInterActiveAuth-MPKSRCXD.js +0 -184
  404. package/chunks/workspace-providers-status-3W7E7DFW.js +0 -71
  405. package/chunks/workspace-registry-SXJZNN3O.js +0 -74
  406. package/chunks/workspace-service-CZRQZ4Q4.js +0 -80
  407. package/chunks/workspace-skills-status-K7WFREPD.js +0 -70
  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-wmPUGHtg.js +0 -1
  430. package/web-shell/assets/classDiagram-4FO5ZUOK-BA2ljEjW.js +0 -1
  431. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-BA2ljEjW.js +0 -1
  432. package/web-shell/assets/index-CNwoO1Cn.js +0 -783
  433. package/web-shell/assets/index-CWQ0z6kZ.css +0 -5
  434. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-BbSm_9d0.js +0 -1
@@ -1,24 +1,8 @@
1
1
  // Force strict mode and setup for ESM
2
2
  "use strict";
3
- import {
4
- LOAD_REPLAY_BULK_MODE,
5
- LOAD_REPLAY_META_KEY,
6
- LOAD_REPLAY_MODE_META_KEY,
7
- LOAD_REPLAY_VERSION,
8
- MID_TURN_QUEUE_DRAIN_METHOD
9
- } from "./chunk-AGHU5VRK.js";
10
- import {
11
- DEFAULT_RING_SIZE,
12
- EVENT_SCHEMA_VERSION,
13
- EventBus,
14
- serializedBridgeEventByteLength
15
- } from "./chunk-3UP777ZS.js";
16
- import {
17
- normalizeCompactedReplayMaxBytes
18
- } from "./chunk-GLE2YUPU.js";
19
3
  import {
20
4
  MCP_RESTART_SERVER_DEADLINE_MS
21
- } from "./chunk-P7M4GA57.js";
5
+ } from "./chunk-OCPBI7J5.js";
22
6
  import {
23
7
  BranchWhilePromptActiveError,
24
8
  CancelSentinelCollisionError,
@@ -38,18 +22,29 @@ import {
38
22
  SessionShellDisabledError,
39
23
  WorkspaceMismatchError,
40
24
  isNotCurrentlyGeneratingCancelError
41
- } from "./chunk-GGQQA4JW.js";
25
+ } from "./chunk-IAVB3HM3.js";
26
+ import {
27
+ LOAD_REPLAY_BULK_MODE,
28
+ LOAD_REPLAY_META_KEY,
29
+ LOAD_REPLAY_MODE_META_KEY,
30
+ LOAD_REPLAY_VERSION,
31
+ MID_TURN_QUEUE_DRAIN_METHOD
32
+ } from "./chunk-PZL23GTM.js";
42
33
  import {
43
34
  canonicalizeWorkspace
44
- } from "./chunk-ER3BKOLB.js";
35
+ } from "./chunk-W7FJ3N32.js";
45
36
  import {
46
- ClientSideConnection,
47
- PROTOCOL_VERSION,
48
- RequestError
49
- } from "./chunk-VRDMOSQS.js";
37
+ DEFAULT_RING_SIZE,
38
+ EVENT_SCHEMA_VERSION,
39
+ EventBus,
40
+ serializedBridgeEventByteLength
41
+ } from "./chunk-6RLROE6G.js";
42
+ import {
43
+ normalizeCompactedReplayMaxBytes
44
+ } from "./chunk-COH43MRJ.js";
50
45
  import {
51
46
  defaultSpawnChannelFactory
52
- } from "./chunk-BOBOCR5H.js";
47
+ } from "./chunk-CLFKOZEK.js";
53
48
  import {
54
49
  BridgeChannelClosedError,
55
50
  BridgeTimeoutError,
@@ -58,18 +53,31 @@ import {
58
53
  STATUS_SCHEMA_VERSION,
59
54
  createIdleWorkspaceExtensionsStatus,
60
55
  createIdleWorkspaceHooksStatus
61
- } from "./chunk-LAXNEOBG.js";
56
+ } from "./chunk-VWPVHAMO.js";
57
+ import {
58
+ ClientSideConnection,
59
+ PROTOCOL_VERSION,
60
+ RequestError
61
+ } from "./chunk-UDXSXLER.js";
62
62
  import {
63
+ SESSION_ARTIFACT_PERSISTENCE_VERSION,
63
64
  ShellExecutionService,
64
- TrustGateError
65
- } from "./chunk-3GNOQZDC.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-NVYTCB5Z.js";
66
74
  import {
67
75
  DAEMON_TRACEPARENT_META_KEY,
68
76
  DAEMON_TRACESTATE_META_KEY
69
- } from "./chunk-BUZ6HGL4.js";
77
+ } from "./chunk-3VAGG5T5.js";
70
78
  import {
71
79
  init_esbuild_shims
72
- } from "./chunk-A4BMJM77.js";
80
+ } from "./chunk-5O2XNYP6.js";
73
81
  import {
74
82
  __name
75
83
  } from "./chunk-J2S4EL5Y.js";
@@ -388,7 +396,7 @@ var TurnBoundaryCompactionEngine = class {
388
396
  retainedEvents,
389
397
  maxBytes: this.maxReplayBytes,
390
398
  ...this.truncatedTurns > 0 ? { truncatedTurns: this.truncatedTurns } : {},
391
- fullTranscriptAvailable: false
399
+ fullTranscriptAvailable: true
392
400
  }
393
401
  };
394
402
  }
@@ -483,6 +491,11 @@ import * as path from "node:path";
483
491
  init_esbuild_shims();
484
492
  var MID_TURN_MESSAGE_INJECTED_EVENT = "mid_turn_message_injected";
485
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
+
486
499
  // packages/acp-bridge/src/permissionMediator.ts
487
500
  init_esbuild_shims();
488
501
 
@@ -1169,6 +1182,60 @@ function isRecord(value) {
1169
1182
  return typeof value === "object" && value !== null && !Array.isArray(value);
1170
1183
  }
1171
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");
1172
1239
  function artifactPayloadFields(artifact) {
1173
1240
  return {
1174
1241
  title: artifact["title"],
@@ -1180,7 +1247,8 @@ function artifactPayloadFields(artifact) {
1180
1247
  url: artifact["url"],
1181
1248
  mimeType: artifact["mimeType"],
1182
1249
  sizeBytes: artifact["sizeBytes"],
1183
- metadata: artifact["metadata"]
1250
+ metadata: artifact["metadata"],
1251
+ retention: artifact["retention"]
1184
1252
  };
1185
1253
  }
1186
1254
  __name(artifactPayloadFields, "artifactPayloadFields");
@@ -1323,7 +1391,7 @@ function sliceLineRange(content, startLine, endLine) {
1323
1391
  }
1324
1392
  __name(sliceLineRange, "sliceLineRange");
1325
1393
  var BridgeClient = class {
1326
- 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) {
1327
1395
  this.resolveEntry = resolveEntry;
1328
1396
  this.resolvePendingRestoreEvents = resolvePendingRestoreEvents;
1329
1397
  this.mediator = mediator;
@@ -1335,6 +1403,7 @@ var BridgeClient = class {
1335
1403
  this.clientMcpSender = clientMcpSender;
1336
1404
  this.ownsSession = ownsSession;
1337
1405
  this.onTokenUsage = onTokenUsage;
1406
+ this.onCreateSubSession = onCreateSubSession;
1338
1407
  }
1339
1408
  static {
1340
1409
  __name(this, "BridgeClient");
@@ -1348,10 +1417,9 @@ var BridgeClient = class {
1348
1417
  );
1349
1418
  return { outcome: { outcome: "cancelled" } };
1350
1419
  }
1420
+ const options = Array.isArray(params.options) ? params.options : [];
1351
1421
  const allowedOptionIds = new Set(
1352
- params.options.map(
1353
- (o) => String(o.optionId ?? "")
1354
- )
1422
+ options.map((o) => String(o.optionId ?? ""))
1355
1423
  );
1356
1424
  allowedOptionIds.delete("");
1357
1425
  const requestId = randomUUID();
@@ -1364,12 +1432,25 @@ var BridgeClient = class {
1364
1432
  requestId,
1365
1433
  sessionId: entry.sessionId,
1366
1434
  toolCall: params.toolCall,
1367
- options: params.options
1435
+ options
1368
1436
  },
1369
1437
  ...entry.activePromptOriginatorClientId ? { originatorClientId: entry.activePromptOriginatorClientId } : {}
1370
1438
  });
1371
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
+ }
1372
1452
  entry.pendingPermissionIds.add(requestId);
1453
+ if (interaction) entry.pendingInteractions.set(requestId, interaction);
1373
1454
  try {
1374
1455
  const record = {
1375
1456
  requestId,
@@ -1385,6 +1466,7 @@ var BridgeClient = class {
1385
1466
  return resolutionToAcpResponse(resolution);
1386
1467
  } finally {
1387
1468
  entry.pendingPermissionIds.delete(requestId);
1469
+ entry.pendingInteractions.delete(requestId);
1388
1470
  }
1389
1471
  }
1390
1472
  async sessionUpdate(params) {
@@ -1458,7 +1540,7 @@ var BridgeClient = class {
1458
1540
  trustedPublisher: isTrustedArtifactToolUpdate(params, updateMeta)
1459
1541
  };
1460
1542
  }
1461
- async seedSessionUpdates(entry, updates) {
1543
+ async seedSessionUpdates(entry, updates, options = {}) {
1462
1544
  const frames = [];
1463
1545
  const artifactBatches = [];
1464
1546
  for (const update of updates) {
@@ -1467,7 +1549,7 @@ var BridgeClient = class {
1467
1549
  entry
1468
1550
  );
1469
1551
  frames.push(...prepared.frames);
1470
- if (prepared.artifacts.length > 0) {
1552
+ if (options.ingestArtifacts !== false && prepared.artifacts.length > 0) {
1471
1553
  artifactBatches.push({
1472
1554
  artifacts: prepared.artifacts,
1473
1555
  trustedPublisher: prepared.trustedPublisher
@@ -1556,9 +1638,12 @@ var BridgeClient = class {
1556
1638
  inFlightRestoreIds = /* @__PURE__ */ new Set();
1557
1639
  /**
1558
1640
  * Handle child->bridge ACP `extMethod` requests (calls that expect a
1559
- * response, unlike `extNotification`). The only method served today is
1560
- * `craft/drainMidTurnQueue`: the ACP child calls it between tool batches to
1561
- * 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
1562
1647
  * queue, return them to the child as the response, and — when non-empty —
1563
1648
  * publish a `mid_turn_message_injected` SSE frame so the browser can move
1564
1649
  * those messages out of its pending queue (a dedupe signal, not a transcript
@@ -1571,6 +1656,9 @@ var BridgeClient = class {
1571
1656
  if (method === SERVE_CONTROL_EXT_METHODS.clientMcpMessage) {
1572
1657
  return this.handleClientMcpMessage(params);
1573
1658
  }
1659
+ if (method === SERVE_CONTROL_EXT_METHODS.createSubSession) {
1660
+ return this.handleCreateSubSession(params);
1661
+ }
1574
1662
  if (method !== MID_TURN_QUEUE_DRAIN_METHOD) {
1575
1663
  throw RequestError.methodNotFound(method);
1576
1664
  }
@@ -1646,6 +1734,68 @@ var BridgeClient = class {
1646
1734
  const response = await send(payload);
1647
1735
  return { payload: response };
1648
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
+ }
1649
1799
  /**
1650
1800
  * Handle child->bridge ACP `extNotification` calls. Recognized methods are
1651
1801
  * `qwen/notify/session/model-update`,
@@ -1655,8 +1805,8 @@ var BridgeClient = class {
1655
1805
  * `qwen/notify/session/artifact-event` (hook artifacts),
1656
1806
  * `qwen/notify/session/terminal-sequence`, and
1657
1807
  * `qwen/notify/session/mcp-budget-event` — each translated into a
1658
- * session-scoped SSE frame. Unknown methods are dropped silently
1659
- * for forward-compat.
1808
+ * session-scoped SSE frame. Unknown methods are dropped silently for
1809
+ * forward-compat.
1660
1810
  */
1661
1811
  async extNotification(method, params) {
1662
1812
  if (method === "qwen/notify/session/model-update") {
@@ -2239,7 +2389,7 @@ __name(extractA2uiToolUpdate, "extractA2uiToolUpdate");
2239
2389
  // packages/acp-bridge/src/sessionArtifacts.ts
2240
2390
  init_esbuild_shims();
2241
2391
  import { createHash } from "node:crypto";
2242
- import { promises as fs2 } from "node:fs";
2392
+ import { constants as fsConstants, promises as fs2 } from "node:fs";
2243
2393
  import path2 from "node:path";
2244
2394
  var SOURCE_RESERVATIONS = {
2245
2395
  tool: 100,
@@ -2248,6 +2398,18 @@ var SOURCE_RESERVATIONS = {
2248
2398
  };
2249
2399
  var WORKSPACE_STATUS_REFRESH_TTL_MS = 5e3;
2250
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");
2251
2413
  var SessionArtifactValidationError = class extends Error {
2252
2414
  constructor(message, field) {
2253
2415
  super(message);
@@ -2259,6 +2421,20 @@ var SessionArtifactValidationError = class extends Error {
2259
2421
  }
2260
2422
  code = "VALIDATION_FAILED";
2261
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
+ };
2262
2438
  var SessionArtifactStore = class {
2263
2439
  static {
2264
2440
  __name(this, "SessionArtifactStore");
@@ -2266,15 +2442,26 @@ var SessionArtifactStore = class {
2266
2442
  sessionId;
2267
2443
  workspaceCwd;
2268
2444
  maxArtifacts;
2445
+ persistence;
2269
2446
  artifacts = /* @__PURE__ */ new Map();
2270
2447
  receivedSeq = 0;
2271
2448
  insertSeq = 0;
2449
+ persistenceSeq = 0;
2450
+ durableEventsSinceSnapshot = 0;
2451
+ consecutiveSnapshotFailures = 0;
2272
2452
  realWorkspaceCwdPromise;
2273
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 = [];
2274
2460
  constructor(options) {
2275
2461
  this.sessionId = options.sessionId;
2276
2462
  this.workspaceCwd = options.workspaceCwd;
2277
2463
  this.maxArtifacts = options.maxArtifacts ?? 200;
2464
+ this.persistence = options.persistence;
2278
2465
  }
2279
2466
  inputBatchLimit() {
2280
2467
  return this.maxArtifacts * 2;
@@ -2287,13 +2474,30 @@ var SessionArtifactStore = class {
2287
2474
  sessionId: this.sessionId,
2288
2475
  artifacts: Array.from(this.artifacts.values()).sort((a, b) => a.insertSeq - b.insertSeq).map(toPublicArtifact),
2289
2476
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2290
- 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] } : {}
2291
2480
  };
2292
2481
  });
2293
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
+ }
2294
2493
  async upsertMany(inputs, options = {}) {
2295
2494
  return this.enqueue(async () => {
2495
+ const validationStrict = options.validationStrict ?? options.strict;
2496
+ const persistenceStrict = options.persistenceStrict ?? options.strict;
2497
+ const before = this.cloneState();
2296
2498
  const normalizedResults = [];
2499
+ const warnings = [];
2500
+ const warningDetails = [];
2297
2501
  for (const input of inputs) {
2298
2502
  try {
2299
2503
  normalizedResults.push(
@@ -2304,7 +2508,7 @@ var SessionArtifactStore = class {
2304
2508
  )
2305
2509
  );
2306
2510
  } catch (error) {
2307
- if (options.strict) {
2511
+ if (validationStrict) {
2308
2512
  throw error;
2309
2513
  }
2310
2514
  const message = error instanceof Error ? error.message : String(error);
@@ -2316,39 +2520,140 @@ var SessionArtifactStore = class {
2316
2520
  }
2317
2521
  }
2318
2522
  const changes = [];
2319
- for (const artifact of coalesceByIdentity(normalizedResults)) {
2320
- const existing = this.artifacts.get(artifact.id) ?? this.findPublishedUpgradeTarget(artifact) ?? this.findPublishedWorkspaceTarget(artifact);
2321
- if (!existing) {
2322
- const stored = {
2323
- ...artifact,
2324
- insertSeq: ++this.insertSeq
2325
- };
2326
- this.artifacts.set(stored.id, stored);
2327
- changes.push({
2328
- action: "created",
2329
- artifactId: stored.id,
2330
- artifact: toPublicArtifact(stored)
2331
- });
2332
- 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
+ }
2333
2589
  }
2334
- const updated = mergeArtifact(existing, artifact);
2335
- if (updated.changed) {
2336
- if (updated.artifact.id !== existing.id) {
2337
- 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
+ };
2338
2628
  }
2339
- this.artifacts.set(updated.artifact.id, updated.artifact);
2340
- changes.push({
2341
- action: "updated",
2342
- artifactId: updated.artifact.id,
2343
- artifact: toPublicArtifact(updated.artifact)
2344
- });
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);
2345
2647
  }
2648
+ throw error;
2346
2649
  }
2347
- const createdIds = new Set(
2348
- changes.filter((change) => change.action === "created").map((change) => change.artifactId)
2349
- );
2350
- changes.push(...await this.evictOverflow(createdIds, changes));
2351
- 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
+ };
2352
2657
  });
2353
2658
  }
2354
2659
  findPublishedUpgradeTarget(artifact) {
@@ -2356,7 +2661,7 @@ var SessionArtifactStore = class {
2356
2661
  return void 0;
2357
2662
  }
2358
2663
  const byUrl = this.artifacts.get(
2359
- stableArtifactId(this.sessionId, `url:${artifact.url}`)
2664
+ stableSessionArtifactId(this.sessionId, `url:${artifact.url}`)
2360
2665
  );
2361
2666
  if (byUrl && (byUrl.storage !== "published" || byUrl.managedId === artifact.managedId)) {
2362
2667
  return byUrl;
@@ -2392,27 +2697,552 @@ var SessionArtifactStore = class {
2392
2697
  if (!existing) {
2393
2698
  return { v: 1, sessionId: this.sessionId, changes: [] };
2394
2699
  }
2395
- if (existing.source === "client" && existing.clientId !== void 0 && existing.clientId !== options?.clientId) {
2396
- writeStderrLine(
2397
- `[artifacts] session=${this.sessionId} action=remove_denied artifactId=${artifactId} owner=${existing.clientId} requester=${options?.clientId ?? "<anonymous>"}`
2398
- );
2399
- 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
+ }
2400
2734
  }
2401
- 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
+ );
2402
2745
  return {
2403
2746
  v: 1,
2404
2747
  sessionId: this.sessionId,
2405
- changes: [
2406
- {
2407
- action: "removed",
2408
- artifactId,
2409
- artifact: toPublicArtifact(existing),
2410
- reason: "explicit"
2411
- }
2412
- ]
2748
+ changes,
2749
+ ...warnings.length > 0 ? { warnings } : {},
2750
+ ...warningDetails.length > 0 ? { warningDetails } : {}
2413
2751
  };
2414
2752
  });
2415
2753
  }
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
+ });
2888
+ }
2889
+ async recordSnapshot() {
2890
+ const persistence = this.persistence;
2891
+ if (!persistence) {
2892
+ return [
2893
+ "artifact persistence unavailable; restored artifacts not snapshotted"
2894
+ ];
2895
+ }
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
+ }
2915
+ });
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)}`
2955
+ );
2956
+ return void 0;
2957
+ }
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
+ }
2416
3246
  enqueue(operation) {
2417
3247
  const result = this.operationQueue.then(operation, operation);
2418
3248
  this.operationQueue = result.then(
@@ -2421,7 +3251,21 @@ var SessionArtifactStore = class {
2421
3251
  );
2422
3252
  return result;
2423
3253
  }
2424
- async normalizeInput(input, receivedSeq, trustedPublisherFromCaller) {
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 = {}) {
2425
3269
  if (!input || typeof input !== "object") {
2426
3270
  throw new SessionArtifactValidationError("Artifact must be an object");
2427
3271
  }
@@ -2440,7 +3284,10 @@ var SessionArtifactStore = class {
2440
3284
  );
2441
3285
  }
2442
3286
  const trustedPublisher = trustedPublisherFromCaller;
2443
- const workspacePath = input.workspacePath ? normalizeWorkspacePath(input.workspacePath, this.workspaceCwd) : void 0;
3287
+ const workspacePath = input.workspacePath ? normalizeWorkspacePath(
3288
+ input.workspacePath,
3289
+ await this.getRealWorkspaceCwdForValidation()
3290
+ ) : void 0;
2444
3291
  const managedId = normalizeManagedId(input.managedId);
2445
3292
  const rawStorage = input.storage;
2446
3293
  const storage = inferStorage(rawStorage, {
@@ -2456,14 +3303,26 @@ var SessionArtifactStore = class {
2456
3303
  url,
2457
3304
  trustedPublisher
2458
3305
  });
2459
- const metadata = normalizeMetadata(input.metadata);
2460
- const workspaceStatus = workspacePath ? await this.getInitialWorkspaceStatus(workspacePath) : void 0;
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;
2461
3314
  if (workspaceStatus?.escaped) {
2462
3315
  throw new SessionArtifactValidationError(
2463
3316
  "workspacePath must stay inside the workspace",
2464
3317
  "workspacePath"
2465
3318
  );
2466
3319
  }
3320
+ const metadata = withWorkspaceContentHashMetadata(
3321
+ normalizeMetadata(input.metadata, {
3322
+ budget: options.metadataBudget ?? "user"
3323
+ }),
3324
+ workspaceStatus
3325
+ );
2467
3326
  const kind = normalizeKind(
2468
3327
  input.kind ?? inferKind({ storage, workspacePath, url })
2469
3328
  );
@@ -2474,11 +3333,12 @@ var SessionArtifactStore = class {
2474
3333
  managedId,
2475
3334
  url
2476
3335
  });
2477
- const id = stableArtifactId(this.sessionId, identityKey);
3336
+ const id = stableSessionArtifactId(this.sessionId, identityKey);
2478
3337
  return {
2479
3338
  id,
2480
3339
  identityKey,
2481
3340
  receivedSeq,
3341
+ retentionExplicit: input.retention !== void 0,
2482
3342
  retentionSource: source,
2483
3343
  trustedPublisher,
2484
3344
  kind,
@@ -2492,9 +3352,12 @@ var SessionArtifactStore = class {
2492
3352
  managedId,
2493
3353
  url,
2494
3354
  mimeType: normalizeString(input.mimeType, "mimeType", 120, false),
2495
- 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,
2496
3356
  metadata,
2497
- 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),
2498
3361
  createdAt: now,
2499
3362
  updatedAt: now,
2500
3363
  toolCallId: normalizeString(input.toolCallId, "toolCallId", 200, false),
@@ -2508,6 +3371,16 @@ var SessionArtifactStore = class {
2508
3371
  clientId: normalizeString(input.clientId, "clientId", 200, false)
2509
3372
  };
2510
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
+ }
2511
3384
  async refreshWorkspaceStatuses() {
2512
3385
  const now = Date.now();
2513
3386
  const staleWorkspaceArtifacts = Array.from(this.artifacts.values()).filter((artifact) => artifact.workspacePath).filter((artifact) => shouldRefreshWorkspaceStatus(artifact, now));
@@ -2520,12 +3393,24 @@ var SessionArtifactStore = class {
2520
3393
  })
2521
3394
  );
2522
3395
  }
2523
- 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 }) {
2524
3408
  try {
2525
3409
  return await getWorkspaceStatus(
2526
- this.workspaceCwd,
2527
3410
  workspacePath,
2528
- this.getRealWorkspaceCwd()
3411
+ this.getRealWorkspaceCwd(),
3412
+ expected,
3413
+ options
2529
3414
  );
2530
3415
  } catch (error) {
2531
3416
  const reason = error instanceof Error ? error.message : String(error);
@@ -2541,16 +3426,23 @@ var SessionArtifactStore = class {
2541
3426
  }
2542
3427
  try {
2543
3428
  const status = await getWorkspaceStatus(
2544
- this.workspaceCwd,
2545
3429
  artifact.workspacePath,
2546
- 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
+ }
2547
3436
  );
2548
- artifact.status = status.status;
2549
- 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
+ }
2550
3442
  if (status.escaped) {
2551
3443
  artifact.status = "missing";
2552
3444
  artifact.sizeBytes = void 0;
2553
- delete artifact.workspacePath;
3445
+ artifact.hideWorkspacePath = true;
2554
3446
  }
2555
3447
  artifact.lastStatAt = options.now ?? Date.now();
2556
3448
  } catch (error) {
@@ -2579,7 +3471,18 @@ var SessionArtifactStore = class {
2579
3471
  }
2580
3472
  return this.realWorkspaceCwdPromise;
2581
3473
  }
2582
- 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) {
2583
3486
  const removed = [];
2584
3487
  if (this.artifacts.size <= this.maxArtifacts) {
2585
3488
  return removed;
@@ -2607,10 +3510,17 @@ var SessionArtifactStore = class {
2607
3510
  action: "removed",
2608
3511
  artifactId: artifact.id,
2609
3512
  artifact: toPublicArtifact(artifact),
3513
+ durableTombstoneRequired: artifact.durableTombstoneRequired,
2610
3514
  reason: "eviction"
2611
3515
  });
2612
3516
  }
2613
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
+ }
2614
3524
  for (const artifact of overflowCreated) {
2615
3525
  if (this.artifacts.size <= this.maxArtifacts) {
2616
3526
  break;
@@ -2659,7 +3569,12 @@ function mergeBatchArtifact(existing, next) {
2659
3569
  trustedPublisher: true,
2660
3570
  createdAt: existing.createdAt,
2661
3571
  receivedSeq: existing.receivedSeq,
3572
+ retentionExplicit: existing.retentionExplicit || next.retentionExplicit,
2662
3573
  retentionSource: existing.retentionSource,
3574
+ retention: mergeRetention(existing, next),
3575
+ restoreState: "live",
3576
+ persistenceWarning: existing.persistenceWarning ?? next.persistenceWarning,
3577
+ persistedAt: existing.persistedAt ?? next.persistedAt,
2663
3578
  clientRetained: existing.clientRetained || next.clientRetained,
2664
3579
  lastStatAt: void 0
2665
3580
  };
@@ -2673,6 +3588,8 @@ function mergeBatchArtifact(existing, next) {
2673
3588
  metadata: mergeMetadata(existing, next),
2674
3589
  clientRetained: existing.clientRetained || next.clientRetained,
2675
3590
  trustedPublisher: existing.trustedPublisher || next.trustedPublisher,
3591
+ retentionExplicit: existing.retentionExplicit || next.retentionExplicit,
3592
+ retention: mergeRetention(existing, next),
2676
3593
  lastStatAt: next.lastStatAt ?? existing.lastStatAt
2677
3594
  };
2678
3595
  }
@@ -2692,9 +3609,14 @@ function mergeArtifact(existing, incoming) {
2692
3609
  managedId: publishedUpdate ? incoming.managedId ?? existing.managedId : existing.managedId,
2693
3610
  url: publishedUpdate ? incoming.url ?? existing.url : existing.url,
2694
3611
  workspacePath: publishedUpdate || existing.storage === "published" ? void 0 : existing.workspacePath ?? incoming.workspacePath,
3612
+ hideWorkspacePath: publishedUpdate || existing.storage === "published" || incoming.workspacePath ? void 0 : existing.hideWorkspacePath,
2695
3613
  mimeType: publishedUpdate ? incoming.mimeType ?? existing.mimeType : existing.mimeType,
2696
3614
  sizeBytes: existing.storage === "published" && !publishedUpdate ? existing.sizeBytes : mergeSizeBytes(existing, incoming),
2697
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,
2698
3620
  source: existing.source,
2699
3621
  retentionSource: existing.retentionSource,
2700
3622
  trustedPublisher: existing.trustedPublisher || incoming.trustedPublisher,
@@ -2706,6 +3628,7 @@ function mergeArtifact(existing, incoming) {
2706
3628
  next.title = incoming.title;
2707
3629
  next.description = incoming.description;
2708
3630
  delete next.workspacePath;
3631
+ delete next.hideWorkspacePath;
2709
3632
  }
2710
3633
  const changed = !publicArtifactsEqual(
2711
3634
  toPublicArtifact(existing),
@@ -2717,8 +3640,12 @@ function mergeArtifact(existing, incoming) {
2717
3640
  return { artifact: changed ? next : existing, changed };
2718
3641
  }
2719
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");
2720
3647
  function publicArtifactsEqual(a, b) {
2721
- 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;
2722
3649
  }
2723
3650
  __name(publicArtifactsEqual, "publicArtifactsEqual");
2724
3651
  function metadataEqual(a, b) {
@@ -2740,6 +3667,24 @@ function mergeSizeBytes(existing, incoming) {
2740
3667
  return existing.sizeBytes;
2741
3668
  }
2742
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");
2743
3688
  function mergeMetadata(existing, incoming) {
2744
3689
  if (!incoming.metadata || incoming.source === "hook" || incoming.source !== existing.source) {
2745
3690
  return existing.metadata;
@@ -2747,7 +3692,10 @@ function mergeMetadata(existing, incoming) {
2747
3692
  const merged = { ...existing.metadata ?? {} };
2748
3693
  let changed = false;
2749
3694
  for (const [key, value] of Object.entries(incoming.metadata)) {
2750
- 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)) {
2751
3699
  merged[key] = value;
2752
3700
  changed = true;
2753
3701
  }
@@ -2765,7 +3713,7 @@ function mergeMetadata(existing, incoming) {
2765
3713
  }
2766
3714
  __name(mergeMetadata, "mergeMetadata");
2767
3715
  function isMetadataWithinLimit(metadata) {
2768
- return Buffer.byteLength(JSON.stringify(metadata), "utf8") <= 4096;
3716
+ return metadataBudgetBytes(metadata, "persisted") <= 4096;
2769
3717
  }
2770
3718
  __name(isMetadataWithinLimit, "isMetadataWithinLimit");
2771
3719
  function countByRetentionSource(artifacts) {
@@ -2821,6 +3769,7 @@ function removePriorChange(changes, artifactId) {
2821
3769
  }
2822
3770
  __name(removePriorChange, "removePriorChange");
2823
3771
  function toPublicArtifact(artifact) {
3772
+ const hideWorkspacePath = "hideWorkspacePath" in artifact && artifact.hideWorkspacePath === true;
2824
3773
  const {
2825
3774
  id,
2826
3775
  kind,
@@ -2835,13 +3784,16 @@ function toPublicArtifact(artifact) {
2835
3784
  mimeType,
2836
3785
  sizeBytes,
2837
3786
  metadata,
3787
+ retention,
3788
+ restoreState,
3789
+ persistenceWarning,
3790
+ persistedAt,
2838
3791
  clientRetained,
2839
3792
  createdAt,
2840
3793
  updatedAt,
2841
3794
  toolCallId,
2842
3795
  toolName,
2843
- hookEventName,
2844
- clientId
3796
+ hookEventName
2845
3797
  } = artifact;
2846
3798
  return {
2847
3799
  id,
@@ -2851,22 +3803,205 @@ function toPublicArtifact(artifact) {
2851
3803
  status,
2852
3804
  title,
2853
3805
  ...description ? { description } : {},
2854
- ...workspacePath ? { workspacePath } : {},
3806
+ ...workspacePath && !hideWorkspacePath ? { workspacePath } : {},
2855
3807
  ...managedId ? { managedId } : {},
2856
3808
  ...url ? { url } : {},
2857
3809
  ...mimeType ? { mimeType } : {},
2858
3810
  ...sizeBytes !== void 0 ? { sizeBytes } : {},
2859
3811
  ...metadata ? { metadata } : {},
3812
+ retention,
3813
+ ...restoreState ? { restoreState } : {},
3814
+ ...persistenceWarning ? { persistenceWarning } : {},
3815
+ ...persistedAt ? { persistedAt } : {},
2860
3816
  clientRetained,
2861
3817
  createdAt,
2862
3818
  updatedAt,
2863
3819
  ...toolCallId ? { toolCallId } : {},
2864
3820
  ...toolName ? { toolName } : {},
2865
- ...hookEventName ? { hookEventName } : {},
2866
- ...clientId ? { clientId } : {}
3821
+ ...hookEventName ? { hookEventName } : {}
2867
3822
  };
2868
3823
  }
2869
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");
2870
4005
  function inferStorage(requested, locators) {
2871
4006
  if (requested !== void 0 && typeof requested !== "string") {
2872
4007
  throw new SessionArtifactValidationError(
@@ -2960,10 +4095,6 @@ function buildIdentityKey(input) {
2960
4095
  );
2961
4096
  }
2962
4097
  __name(buildIdentityKey, "buildIdentityKey");
2963
- function stableArtifactId(sessionId, identityKey) {
2964
- return createHash("sha256").update(`${sessionId}:${identityKey}`).digest("hex").slice(0, 16);
2965
- }
2966
- __name(stableArtifactId, "stableArtifactId");
2967
4098
  function managedIdForWorkspacePath(workspaceCwd, workspacePath) {
2968
4099
  return createHash("sha1").update(path2.resolve(workspaceCwd, workspacePath)).digest("hex").slice(0, 16);
2969
4100
  }
@@ -3077,6 +4208,12 @@ function normalizeArtifactUrl(raw, allowFile) {
3077
4208
  "url"
3078
4209
  );
3079
4210
  }
4211
+ if (hasSecretLikeUrlComponent(parsed)) {
4212
+ throw new SessionArtifactValidationError(
4213
+ "url must not include secret-like components",
4214
+ "url"
4215
+ );
4216
+ }
3080
4217
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:" && !(allowFile && parsed.protocol === "file:")) {
3081
4218
  throw new SessionArtifactValidationError(
3082
4219
  allowFile ? "url must use http, https, or file" : "url must use http or https",
@@ -3086,7 +4223,64 @@ function normalizeArtifactUrl(raw, allowFile) {
3086
4223
  return parsed.href;
3087
4224
  }
3088
4225
  __name(normalizeArtifactUrl, "normalizeArtifactUrl");
3089
- 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 = {}) {
3090
4284
  if (metadata === void 0) {
3091
4285
  return void 0;
3092
4286
  }
@@ -3098,6 +4292,12 @@ function normalizeMetadata(metadata) {
3098
4292
  }
3099
4293
  const normalized = {};
3100
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
+ }
3101
4301
  if (!key) {
3102
4302
  throw new SessionArtifactValidationError(
3103
4303
  "metadata keys must not be empty",
@@ -3116,6 +4316,12 @@ function normalizeMetadata(metadata) {
3116
4316
  "metadata"
3117
4317
  );
3118
4318
  }
4319
+ if (isSecretLikeUrlText(key)) {
4320
+ throw new SessionArtifactValidationError(
4321
+ "metadata keys must not contain secret-like names",
4322
+ "metadata"
4323
+ );
4324
+ }
3119
4325
  if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
3120
4326
  throw new SessionArtifactValidationError(
3121
4327
  "metadata values must be primitive",
@@ -3134,12 +4340,18 @@ function normalizeMetadata(metadata) {
3134
4340
  "metadata"
3135
4341
  );
3136
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
+ }
3137
4349
  normalized[key] = value;
3138
4350
  }
3139
4351
  if (Object.keys(normalized).length === 0) {
3140
4352
  return void 0;
3141
4353
  }
3142
- if (Buffer.byteLength(JSON.stringify(normalized), "utf8") > 4096) {
4354
+ if (metadataBudgetBytes(normalized, options.budget ?? "user") > 4096) {
3143
4355
  throw new SessionArtifactValidationError(
3144
4356
  "metadata must be 4096 bytes or fewer",
3145
4357
  "metadata"
@@ -3148,6 +4360,25 @@ function normalizeMetadata(metadata) {
3148
4360
  return normalized;
3149
4361
  }
3150
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");
3151
4382
  function normalizeSizeBytes(value) {
3152
4383
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
3153
4384
  throw new SessionArtifactValidationError(
@@ -3173,21 +4404,83 @@ function inferKind(input) {
3173
4404
  return input.workspacePath ? "file" : "other";
3174
4405
  }
3175
4406
  __name(inferKind, "inferKind");
3176
- async function getWorkspaceStatus(workspaceCwd, workspacePath, realWorkspaceCwd) {
3177
- const absolutePath = path2.resolve(workspaceCwd, workspacePath);
4407
+ async function getWorkspaceStatus(workspacePath, realWorkspaceCwd, expected, options = { hashContent: true }) {
3178
4408
  const realWorkspace = await realWorkspaceCwd;
4409
+ const absolutePath = path2.resolve(realWorkspace, workspacePath);
3179
4410
  try {
3180
4411
  const realPath = await fs2.realpath(absolutePath);
3181
4412
  const relative = path2.relative(realWorkspace, realPath);
3182
4413
  if (!relative || isOutsidePath(relative)) {
3183
4414
  return { status: "missing", escaped: true };
3184
4415
  }
3185
- const stat = await fs2.stat(realPath);
3186
- return {
3187
- status: "available",
3188
- ...stat.isFile() ? { sizeBytes: stat.size } : {}
3189
- };
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
+ }
3190
4480
  } catch (error) {
4481
+ if (isNoFollowSymlinkError(error)) {
4482
+ return { status: "missing", escaped: true };
4483
+ }
3191
4484
  if (!isNotFoundError(error)) {
3192
4485
  throw error;
3193
4486
  }
@@ -3198,6 +4491,41 @@ async function getWorkspaceStatus(workspaceCwd, workspacePath, realWorkspaceCwd)
3198
4491
  }
3199
4492
  }
3200
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");
3201
4529
  async function danglingSymlinkEscapesWorkspace(absolutePath, realWorkspace) {
3202
4530
  try {
3203
4531
  const stat = await fs2.lstat(absolutePath);
@@ -3230,6 +4558,13 @@ function isNotFoundError(error) {
3230
4558
  return code === "ENOENT" || code === "ENOTDIR";
3231
4559
  }
3232
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");
3233
4568
  function isOutsidePath(relative) {
3234
4569
  return relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative);
3235
4570
  }
@@ -3582,6 +4917,11 @@ function broadcastTurnError(entry, sessionId, err, promptId, originatorClientId)
3582
4917
  );
3583
4918
  }
3584
4919
  entry.retryAllowed = true;
4920
+ entry.turnError = {
4921
+ message,
4922
+ ...code ? { code } : {},
4923
+ ...errorKind ? { errorKind } : {}
4924
+ };
3585
4925
  try {
3586
4926
  entry.events.publish({
3587
4927
  type: "turn_error",
@@ -3624,6 +4964,7 @@ function extractPromptText(prompt) {
3624
4964
  __name(extractPromptText, "extractPromptText");
3625
4965
  var DEFAULT_INIT_TIMEOUT_MS = 1e4;
3626
4966
  var PERSIST_TIMEOUT_MS = 5e3;
4967
+ var MAX_PARENT_PERSIST_ATTEMPTS = 3;
3627
4968
  var MCP_RESTART_TIMEOUT_MS = 3e5;
3628
4969
  var WORKSPACE_MEMORY_REMEMBER_TIMEOUT_MS = 3e5;
3629
4970
  var MCP_OAUTH_TIMEOUT_MS = 6e5;
@@ -3631,6 +4972,7 @@ var DAEMON_RETRY_META_KEY = "qwen.daemon.retry";
3631
4972
  var DAEMON_CONTINUE_META_KEY = "qwen.daemon.continueLastTurn";
3632
4973
  var SESSION_RECAP_TIMEOUT_MS = 6e4;
3633
4974
  var SESSION_BTW_TIMEOUT_MS = 6e4;
4975
+ var SESSION_TRANSCRIPT_TIMEOUT_MS = 6e4;
3634
4976
  var SHELL_COMMAND_TIMEOUT_MS = 12e4;
3635
4977
  var MAX_SHELL_OUTPUT_FOR_HISTORY = 1e4;
3636
4978
  var MAX_MID_TURN_QUEUE_DEPTH = 20;
@@ -3671,6 +5013,15 @@ function createAcpSessionBridge(opts) {
3671
5013
  );
3672
5014
  }
3673
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");
3674
5025
  if (defaultSessionScope !== "single" && defaultSessionScope !== "thread") {
3675
5026
  throw new TypeError(
3676
5027
  `Invalid sessionScope: ${JSON.stringify(defaultSessionScope)}. Expected 'single' or 'thread'.`
@@ -3855,14 +5206,32 @@ function createAcpSessionBridge(opts) {
3855
5206
  const aliveChannels = /* @__PURE__ */ new Set();
3856
5207
  let inFlightChannelSpawn;
3857
5208
  const byId = /* @__PURE__ */ new Map();
3858
- const toSessionSummary = /* @__PURE__ */ __name((entry) => ({
3859
- sessionId: entry.sessionId,
3860
- workspaceCwd: entry.workspaceCwd,
3861
- createdAt: entry.createdAt,
3862
- displayName: entry.displayName,
3863
- clientCount: entry.clientIds.size,
3864
- hasActivePrompt: entry.promptActive
3865
- }), "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");
3866
5235
  const permissionConsensusQuorum = opts.permissionConsensusQuorum;
3867
5236
  if (permissionConsensusQuorum !== void 0 && (!Number.isInteger(permissionConsensusQuorum) || permissionConsensusQuorum < 1)) {
3868
5237
  throw new Error(
@@ -3921,6 +5290,22 @@ function createAcpSessionBridge(opts) {
3921
5290
  entry.clientIds.set(clientId, count - 1);
3922
5291
  }
3923
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");
3924
5309
  const resolveTrustedClientId = /* @__PURE__ */ __name((entry, clientId) => {
3925
5310
  if (clientId === void 0) return void 0;
3926
5311
  if (!entry.clientIds.has(clientId)) {
@@ -4001,7 +5386,11 @@ function createAcpSessionBridge(opts) {
4001
5386
  inputTokens,
4002
5387
  outputTokens,
4003
5388
  durationMs
4004
- )
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
4005
5394
  );
4006
5395
  const connection = new ClientSideConnection(() => client, channel.stream);
4007
5396
  const info = {
@@ -4066,6 +5455,12 @@ function createAcpSessionBridge(opts) {
4066
5455
  }
4067
5456
  byId.delete(sid);
4068
5457
  telemetry.metrics?.sessionLifecycle("die");
5458
+ emitSessionLifecycle({
5459
+ type: "removed",
5460
+ sessionId: sid,
5461
+ workspaceCwd: sessEntry.workspaceCwd,
5462
+ reason: "channel_closed"
5463
+ });
4069
5464
  info.client.markSessionClosed(sid);
4070
5465
  if (defaultEntry === sessEntry) defaultEntry = void 0;
4071
5466
  sessEntry.events.close();
@@ -4114,10 +5509,12 @@ function createAcpSessionBridge(opts) {
4114
5509
  }
4115
5510
  }
4116
5511
  __name(ensureChannel, "ensureChannel");
4117
- async function doSpawn(modelServiceId, effectiveScope, requestedClientId, onSessionRegistered) {
5512
+ async function doSpawn(modelServiceId, effectiveScope, approvalMode, requestedClientId, onSessionRegistered, parentSessionId) {
4118
5513
  const ci = await ensureChannel();
4119
5514
  ci.sessionSpawnsInFlight++;
4120
5515
  let sessionRegistered = false;
5516
+ let sessionRemovedDuringInitialization = false;
5517
+ let initializedSessionId;
4121
5518
  let newSessionResp;
4122
5519
  try {
4123
5520
  try {
@@ -4149,16 +5546,61 @@ function createAcpSessionBridge(opts) {
4149
5546
  if (shuttingDown) {
4150
5547
  throw new Error("AcpSessionBridge is shutting down");
4151
5548
  }
4152
- const entry = createSessionEntry(
4153
- ci,
4154
- newSessionResp.sessionId,
4155
- boundWorkspace
4156
- );
4157
- sessionRegistered = true;
4158
- onSessionRegistered?.();
4159
- seedSnapshotCaches(entry, newSessionResp);
4160
- const clientId = registerClient(entry, requestedClientId);
4161
- if (effectiveScope === "single" && !defaultEntry) defaultEntry = entry;
5549
+ const entry = createSessionEntry(
5550
+ ci,
5551
+ newSessionResp.sessionId,
5552
+ boundWorkspace,
5553
+ void 0,
5554
+ { parentSessionId }
5555
+ );
5556
+ initializedSessionId = entry.sessionId;
5557
+ sessionRegistered = true;
5558
+ onSessionRegistered?.();
5559
+ seedSnapshotCaches(entry, newSessionResp);
5560
+ const clientId = registerClient(entry, requestedClientId);
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
+ }
4162
5604
  if (modelServiceId) {
4163
5605
  await applyModelServiceId(
4164
5606
  entry,
@@ -4168,22 +5610,51 @@ function createAcpSessionBridge(opts) {
4168
5610
  ).catch(() => {
4169
5611
  });
4170
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
+ }
4171
5627
  if (!byId.has(entry.sessionId)) {
4172
5628
  throw new Error(
4173
- `Session ${entry.sessionId} died during model-switch initialization`
5629
+ `Session ${entry.sessionId} died during session initialization`
4174
5630
  );
4175
5631
  }
5632
+ if (effectiveScope === "single" && !defaultEntry) defaultEntry = entry;
4176
5633
  return {
4177
5634
  sessionId: entry.sessionId,
4178
5635
  workspaceCwd: entry.workspaceCwd,
4179
5636
  attached: false,
4180
5637
  clientId,
4181
- createdAt: entry.createdAt
5638
+ createdAt: entry.createdAt,
5639
+ ...entry.parentSessionId ? { parentSessionPersisted: parentSessionPersisted === true } : {}
4182
5640
  };
4183
5641
  } finally {
4184
5642
  ci.sessionSpawnsInFlight = Math.max(0, ci.sessionSpawnsInFlight - 1);
4185
5643
  if (!sessionRegistered) {
4186
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
+ );
4187
5658
  }
4188
5659
  }
4189
5660
  }
@@ -4245,9 +5716,125 @@ function createAcpSessionBridge(opts) {
4245
5716
  return work;
4246
5717
  }
4247
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
+ };
5797
+ } finally {
5798
+ entry.approvalModeRoundtripInFlight = false;
5799
+ if (succeeded) {
5800
+ void reconcileAfterRoundtrip(entry, "approvalMode");
5801
+ } else {
5802
+ writeStderrLine(
5803
+ `[reconcile] session=${entry.sessionId} target=approvalMode action=skipped reason=roundtrip_failed`
5804
+ );
5805
+ }
5806
+ }
5807
+ });
5808
+ entry.approvalModeQueue = approvalWork.then(
5809
+ () => void 0,
5810
+ () => void 0
5811
+ );
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
+ }
5823
+ }
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");
4248
5834
  const cancelPendingForSession = /* @__PURE__ */ __name((sessionId) => {
4249
5835
  permissionMediator.forgetSession(sessionId);
4250
5836
  byId.get(sessionId)?.pendingPermissionIds.clear();
5837
+ byId.get(sessionId)?.pendingInteractions.clear();
4251
5838
  }, "cancelPendingForSession");
4252
5839
  const getTransportClosedReject = /* @__PURE__ */ __name((entry) => {
4253
5840
  if (!entry.transportClosedReject) {
@@ -4529,10 +6116,15 @@ function createAcpSessionBridge(opts) {
4529
6116
  sessionId,
4530
6117
  workspaceCwd,
4531
6118
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6119
+ ...options.parentSessionId ? { parentSessionId: options.parentSessionId } : {},
4532
6120
  channel: ci.channel,
4533
6121
  connection: ci.connection,
4534
6122
  events,
4535
- artifacts: new SessionArtifactStore({ sessionId, workspaceCwd }),
6123
+ artifacts: new SessionArtifactStore({
6124
+ sessionId,
6125
+ workspaceCwd,
6126
+ persistence: createSessionArtifactPersistence(ci.connection, sessionId)
6127
+ }),
4536
6128
  promptQueue: Promise.resolve(),
4537
6129
  pendingPromptCount: 0,
4538
6130
  pendingPromptList: [],
@@ -4542,6 +6134,7 @@ function createAcpSessionBridge(opts) {
4542
6134
  modelPublishGeneration: 0,
4543
6135
  approvalModePublishGeneration: 0,
4544
6136
  pendingPermissionIds: /* @__PURE__ */ new Set(),
6137
+ pendingInteractions: /* @__PURE__ */ new Map(),
4545
6138
  clientIds: /* @__PURE__ */ new Map(),
4546
6139
  clientLastSeenAt: /* @__PURE__ */ new Map(),
4547
6140
  attachCount: 0,
@@ -4553,6 +6146,12 @@ function createAcpSessionBridge(opts) {
4553
6146
  byId.set(entry.sessionId, entry);
4554
6147
  touchActivity();
4555
6148
  telemetry.metrics?.sessionLifecycle("spawn");
6149
+ emitSessionLifecycle({
6150
+ type: "registered",
6151
+ sessionId: entry.sessionId,
6152
+ workspaceCwd: entry.workspaceCwd,
6153
+ reason: options.lifecycleReason ?? "spawn"
6154
+ });
4556
6155
  if (options.drainEarlyEvents !== false) {
4557
6156
  ci.client.drainEarlyEvents(entry.sessionId, entry);
4558
6157
  }
@@ -4567,6 +6166,42 @@ function createAcpSessionBridge(opts) {
4567
6166
  });
4568
6167
  }
4569
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");
4570
6205
  const makeClientArtifactInput = /* @__PURE__ */ __name((artifact, clientId) => {
4571
6206
  const input = {
4572
6207
  title: artifact.title,
@@ -4579,6 +6214,8 @@ function createAcpSessionBridge(opts) {
4579
6214
  mimeType: artifact.mimeType,
4580
6215
  sizeBytes: artifact.sizeBytes,
4581
6216
  metadata: artifact.metadata,
6217
+ retention: artifact.retention,
6218
+ clientRetained: artifact.clientRetained,
4582
6219
  source: "client"
4583
6220
  };
4584
6221
  if (clientId) {
@@ -4586,6 +6223,31 @@ function createAcpSessionBridge(opts) {
4586
6223
  }
4587
6224
  return input;
4588
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");
4589
6251
  const seedSnapshotCaches = /* @__PURE__ */ __name((entry, resp) => {
4590
6252
  const model = resp.models?.currentModelId;
4591
6253
  if (typeof model === "string" && model.length > 0) {
@@ -4633,16 +6295,55 @@ function createAcpSessionBridge(opts) {
4633
6295
  }
4634
6296
  return { lastEventId: snapshot.lastEventId, ...replayStatus };
4635
6297
  }, "replayFieldsFor");
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");
4636
6329
  async function restoreSession(action, req, options = {}) {
4637
6330
  if (shuttingDown) {
4638
6331
  throw new Error("AcpSessionBridge is shutting down");
4639
6332
  }
4640
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
+ }
4641
6339
  const historyReplay = action === "load" ? req.historyReplay ?? "stream" : "stream";
4642
6340
  const existing = byId.get(req.sessionId);
4643
6341
  if (existing) {
4644
6342
  existing.attachCount++;
4645
6343
  const clientId = registerClient(existing, req.clientId);
6344
+ if (req.approvalMode) {
6345
+ await applyApprovalModeForAttach(existing, req.approvalMode, clientId);
6346
+ }
4646
6347
  return {
4647
6348
  sessionId: existing.sessionId,
4648
6349
  workspaceCwd: existing.workspaceCwd,
@@ -4681,10 +6382,14 @@ function createAcpSessionBridge(opts) {
4681
6382
  "the agent child likely crashed during session restore \u2014 retry to restore the session"
4682
6383
  );
4683
6384
  }
6385
+ const clientId = registerClient(entry, req.clientId);
6386
+ if (req.approvalMode) {
6387
+ await applyApprovalModeForAttach(entry, req.approvalMode, clientId);
6388
+ }
4684
6389
  return {
4685
6390
  ...restored,
4686
6391
  attached: true,
4687
- clientId: registerClient(entry, req.clientId),
6392
+ clientId,
4688
6393
  createdAt: entry.createdAt,
4689
6394
  hasActivePrompt: entry.promptActive
4690
6395
  };
@@ -4794,6 +6499,23 @@ function createAcpSessionBridge(opts) {
4794
6499
  restoreEvents.close();
4795
6500
  racedEntry.attachCount += 1 + coalesceState.count;
4796
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
+ }
4797
6519
  return {
4798
6520
  sessionId: racedEntry.sessionId,
4799
6521
  workspaceCwd: racedEntry.workspaceCwd,
@@ -4810,22 +6532,48 @@ function createAcpSessionBridge(opts) {
4810
6532
  req.sessionId,
4811
6533
  workspaceKey,
4812
6534
  restoreEvents,
4813
- { 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
+ }
4814
6542
  );
4815
6543
  releaseAdmissionOnce();
4816
- entry.restoreState = state;
6544
+ const restoredArtifactSnapshot = restoredArtifactSnapshotFromState(state);
6545
+ const publicState = publicRestoreState(state);
6546
+ entry.restoreState = publicState;
4817
6547
  if (replayPartial === true) {
4818
6548
  entry.restoreReplayPartial = true;
4819
6549
  }
4820
6550
  if (replayError !== void 0) {
4821
6551
  entry.restoreReplayError = replayError;
4822
6552
  }
4823
- 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
+ );
4824
6567
  if (replayUpdates.length > 0) {
4825
- await ci.client.seedSessionUpdates(entry, replayUpdates);
6568
+ await ci.client.seedSessionUpdates(entry, replayUpdates, {
6569
+ ingestArtifacts: restoredArtifactSnapshot === void 0 || artifactRestoreFailed
6570
+ });
4826
6571
  ci.client.drainEarlyEvents(entry.sessionId, entry);
4827
6572
  }
4828
6573
  const clientId = registerClient(entry, req.clientId);
6574
+ if (req.approvalMode) {
6575
+ await applyApprovalModeForAttach(entry, req.approvalMode, clientId);
6576
+ }
4829
6577
  entry.attachCount = coalesceState.count;
4830
6578
  registeredEntry = entry;
4831
6579
  return {
@@ -4834,7 +6582,8 @@ function createAcpSessionBridge(opts) {
4834
6582
  attached: false,
4835
6583
  clientId,
4836
6584
  createdAt: entry.createdAt,
4837
- state,
6585
+ state: publicState,
6586
+ ...artifactRestoreWarnings.length > 0 ? { artifactWarnings: artifactRestoreWarnings } : {},
4838
6587
  hasActivePrompt: entry.promptActive,
4839
6588
  ...replayFieldsFor(entry, action)
4840
6589
  };
@@ -4846,9 +6595,16 @@ function createAcpSessionBridge(opts) {
4846
6595
  if (!registeredEntry) {
4847
6596
  restoreEvents.close();
4848
6597
  let removedRestoreEntry = false;
4849
- if (byId.get(req.sessionId)?.events === restoreEvents) {
6598
+ const restoreEntry = byId.get(req.sessionId);
6599
+ if (restoreEntry?.events === restoreEvents) {
4850
6600
  byId.delete(req.sessionId);
4851
6601
  ci?.sessionIds.delete(req.sessionId);
6602
+ emitSessionLifecycle({
6603
+ type: "removed",
6604
+ sessionId: req.sessionId,
6605
+ workspaceCwd: restoreEntry.workspaceCwd,
6606
+ reason: "restore_failed"
6607
+ });
4852
6608
  removedRestoreEntry = true;
4853
6609
  }
4854
6610
  if (removedRestoreEntry && ci && hasNoChannelWork(ci)) {
@@ -4909,6 +6665,7 @@ function createAcpSessionBridge(opts) {
4909
6665
  }
4910
6666
  permissionMediator.forgetSession(sessionId);
4911
6667
  entry.pendingPermissionIds.clear();
6668
+ entry.pendingInteractions.clear();
4912
6669
  if (entry.promptActive) {
4913
6670
  entry.promptActive = false;
4914
6671
  activePromptCounter--;
@@ -4916,6 +6673,12 @@ function createAcpSessionBridge(opts) {
4916
6673
  }
4917
6674
  byId.delete(sessionId);
4918
6675
  telemetry.metrics?.sessionLifecycle("close");
6676
+ emitSessionLifecycle({
6677
+ type: "removed",
6678
+ sessionId,
6679
+ workspaceCwd: entry.workspaceCwd,
6680
+ reason
6681
+ });
4919
6682
  ci?.client.markSessionClosed(sessionId);
4920
6683
  try {
4921
6684
  entry.events.publish({
@@ -5039,6 +6802,11 @@ function createAcpSessionBridge(opts) {
5039
6802
  throw new InvalidSessionScopeError(req.sessionScope);
5040
6803
  }
5041
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
+ }
5042
6810
  if (effectiveScope === "single") {
5043
6811
  const existing = defaultEntry;
5044
6812
  if (existing) {
@@ -5053,6 +6821,13 @@ function createAcpSessionBridge(opts) {
5053
6821
  ).catch(() => {
5054
6822
  });
5055
6823
  }
6824
+ if (req.approvalMode) {
6825
+ await applyApprovalModeForAttach(
6826
+ existing,
6827
+ req.approvalMode,
6828
+ clientId
6829
+ );
6830
+ }
5056
6831
  return {
5057
6832
  sessionId: existing.sessionId,
5058
6833
  workspaceCwd: existing.workspaceCwd,
@@ -5083,6 +6858,13 @@ function createAcpSessionBridge(opts) {
5083
6858
  ).catch(() => {
5084
6859
  });
5085
6860
  }
6861
+ if (req.approvalMode) {
6862
+ await applyApprovalModeForAttach(
6863
+ attachedEntry,
6864
+ req.approvalMode,
6865
+ clientId
6866
+ );
6867
+ }
5086
6868
  return {
5087
6869
  ...session,
5088
6870
  attached: true,
@@ -5107,8 +6889,10 @@ function createAcpSessionBridge(opts) {
5107
6889
  const promise = doSpawn(
5108
6890
  req.modelServiceId,
5109
6891
  effectiveScope,
6892
+ req.approvalMode,
5110
6893
  req.clientId,
5111
- releaseAdmissionOnce
6894
+ releaseAdmissionOnce,
6895
+ req.parentSessionId
5112
6896
  );
5113
6897
  const tracker = effectiveScope === "single" ? workspaceKey : `${workspaceKey}#${randomUUID2()}`;
5114
6898
  inFlightSpawns.set(tracker, promise);
@@ -5249,6 +7033,7 @@ function createAcpSessionBridge(opts) {
5249
7033
  return copy;
5250
7034
  })();
5251
7035
  entry.promptActive = true;
7036
+ delete entry.turnError;
5252
7037
  activePromptCounter++;
5253
7038
  entry.sessionLastSeenAt = Date.now();
5254
7039
  touchActivity();
@@ -5771,9 +7556,10 @@ function createAcpSessionBridge(opts) {
5771
7556
  }
5772
7557
  return { displayName: entry.displayName };
5773
7558
  },
5774
- async getSessionArtifacts(sessionId) {
7559
+ async getSessionArtifacts(sessionId, context) {
5775
7560
  const entry = byId.get(sessionId);
5776
7561
  if (!entry) throw new SessionNotFoundError(sessionId);
7562
+ resolveTrustedClientId(entry, context?.clientId);
5777
7563
  return entry.artifacts.list();
5778
7564
  },
5779
7565
  async addSessionArtifact(sessionId, artifact, context) {
@@ -5781,9 +7567,13 @@ function createAcpSessionBridge(opts) {
5781
7567
  if (!entry) throw new SessionNotFoundError(sessionId);
5782
7568
  const clientId = resolveTrustedClientId(entry, context?.clientId);
5783
7569
  const input = makeClientArtifactInput(artifact, clientId);
5784
- const result = await entry.artifacts.upsertMany([input], { strict: true });
7570
+ const result = await entry.artifacts.upsertMany([input], {
7571
+ validationStrict: true,
7572
+ persistenceStrict: false
7573
+ });
5785
7574
  publishArtifactChanges(entry, result.changes, clientId);
5786
- return result;
7575
+ const warnings = [...result.warnings ?? []];
7576
+ return warnings.length > 0 ? { ...result, warnings } : result;
5787
7577
  },
5788
7578
  async removeSessionArtifact(sessionId, artifactId, context) {
5789
7579
  const entry = byId.get(sessionId);
@@ -5791,7 +7581,8 @@ function createAcpSessionBridge(opts) {
5791
7581
  const clientId = resolveTrustedClientId(entry, context?.clientId);
5792
7582
  const result = await entry.artifacts.remove(artifactId, { clientId });
5793
7583
  publishArtifactChanges(entry, result.changes, clientId);
5794
- return result;
7584
+ const warnings = [...result.warnings ?? []];
7585
+ return warnings.length > 0 ? { ...result, warnings } : result;
5795
7586
  },
5796
7587
  listWorkspaceSessions(workspaceCwd) {
5797
7588
  if (!path3.isAbsolute(workspaceCwd)) return [];
@@ -6076,6 +7867,35 @@ function createAcpSessionBridge(opts) {
6076
7867
  SERVE_STATUS_EXT_METHODS.sessionLspStatus
6077
7868
  );
6078
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
+ },
6079
7899
  async cancelSessionTask(sessionId, taskId, taskKind) {
6080
7900
  return requestSessionStatus(
6081
7901
  sessionId,
@@ -6314,109 +8134,12 @@ function createAcpSessionBridge(opts) {
6314
8134
  entry,
6315
8135
  context?.clientId
6316
8136
  );
6317
- if (opts2.persist && !persistApprovalMode) {
6318
- throw new Error(
6319
- "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`."
6320
- );
6321
- }
6322
- const approvalWork = entry.approvalModeQueue.then(async () => {
6323
- entry.approvalModeRoundtripInFlight = true;
6324
- let succeeded = false;
6325
- try {
6326
- const response = await Promise.race([
6327
- withTimeout(
6328
- entry.connection.extMethod(
6329
- SERVE_CONTROL_EXT_METHODS.sessionApprovalMode,
6330
- { sessionId, mode }
6331
- ),
6332
- initTimeoutMs,
6333
- SERVE_CONTROL_EXT_METHODS.sessionApprovalMode
6334
- ),
6335
- getTransportClosedReject(entry)
6336
- ]);
6337
- if (typeof response.current !== "string" || !KNOWN_APPROVAL_MODES.has(response.current)) {
6338
- throw new Error(
6339
- `Agent returned unknown approval mode: ${JSON.stringify(response.current)}`
6340
- );
6341
- }
6342
- let persisted = false;
6343
- if (opts2.persist) {
6344
- try {
6345
- await withTimeout(
6346
- persistApprovalMode?.(boundWorkspace, mode) ?? Promise.resolve(),
6347
- PERSIST_TIMEOUT_MS,
6348
- "persistApprovalMode"
6349
- );
6350
- persisted = persistApprovalMode !== void 0;
6351
- } catch (err) {
6352
- writeStderrLine(
6353
- `setSessionApprovalMode: persist failed: ${err instanceof Error ? err.message : String(err)}`
6354
- );
6355
- }
6356
- }
6357
- publishApprovalModeChanged(
6358
- entry,
6359
- {
6360
- previous: response.previous,
6361
- next: response.current,
6362
- persisted
6363
- },
6364
- originatorClientId
6365
- );
6366
- if (persisted) {
6367
- broadcastWorkspaceEvent(
6368
- {
6369
- type: "approval_mode_changed",
6370
- data: {
6371
- sessionId: entry.sessionId,
6372
- previous: response.previous,
6373
- next: response.current,
6374
- persisted
6375
- },
6376
- ...originatorClientId ? { originatorClientId } : {}
6377
- },
6378
- entry.sessionId
6379
- );
6380
- for (const peer of byId.values()) {
6381
- if (peer.sessionId === entry.sessionId) {
6382
- continue;
6383
- }
6384
- peer.currentApprovalMode = response.current;
6385
- }
6386
- }
6387
- succeeded = true;
6388
- return {
6389
- sessionId: entry.sessionId,
6390
- mode: response.current,
6391
- previous: response.previous,
6392
- persisted
6393
- };
6394
- } finally {
6395
- entry.approvalModeRoundtripInFlight = false;
6396
- if (succeeded) {
6397
- void reconcileAfterRoundtrip(entry, "approvalMode");
6398
- } else {
6399
- writeStderrLine(
6400
- `[reconcile] session=${entry.sessionId} target=approvalMode action=skipped reason=roundtrip_failed`
6401
- );
6402
- }
6403
- }
6404
- });
6405
- entry.approvalModeQueue = approvalWork.then(
6406
- () => void 0,
6407
- () => void 0
8137
+ return await applyApprovalMode(
8138
+ entry,
8139
+ mode,
8140
+ opts2.persist,
8141
+ originatorClientId
6408
8142
  );
6409
- try {
6410
- return await approvalWork;
6411
- } catch (err) {
6412
- const data = err?.data;
6413
- if (data && typeof data === "object" && "errorKind" in data && data.errorKind === "trust_gate") {
6414
- const rawMessage = err?.message;
6415
- const message = typeof rawMessage === "string" ? rawMessage : "Trust-gate rejection from ACP child";
6416
- throw new TrustGateError(message);
6417
- }
6418
- throw err;
6419
- }
6420
8143
  },
6421
8144
  async generateSessionRecap(sessionId, _context) {
6422
8145
  const entry = byId.get(sessionId);
@@ -6769,6 +8492,48 @@ function createAcpSessionBridge(opts) {
6769
8492
  const targetTurnIndex = response["targetTurnIndex"] ?? 0;
6770
8493
  const filesChanged = response["filesChanged"] ?? [];
6771
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
+ );
6772
8537
  try {
6773
8538
  entry.events.publish({
6774
8539
  type: "session_rewound",
@@ -6777,7 +8542,8 @@ function createAcpSessionBridge(opts) {
6777
8542
  promptId: req.promptId,
6778
8543
  targetTurnIndex,
6779
8544
  filesChanged,
6780
- filesFailed
8545
+ filesFailed,
8546
+ ...artifactWarnings.length > 0 ? { warnings: artifactWarnings } : {}
6781
8547
  },
6782
8548
  ...originatorClientId ? { originatorClientId } : {}
6783
8549
  });
@@ -6787,7 +8553,8 @@ function createAcpSessionBridge(opts) {
6787
8553
  rewound: filesFailed.length === 0,
6788
8554
  targetTurnIndex,
6789
8555
  filesChanged,
6790
- filesFailed
8556
+ filesFailed,
8557
+ ...artifactWarnings.length > 0 ? { warnings: artifactWarnings } : {}
6791
8558
  };
6792
8559
  },
6793
8560
  async manageMcpServer(serverName, action, originatorClientId) {
@@ -6915,6 +8682,7 @@ function createAcpSessionBridge(opts) {
6915
8682
  }
6916
8683
  permissionMediator.forgetSession(sessionId);
6917
8684
  entry.pendingPermissionIds.clear();
8685
+ entry.pendingInteractions.clear();
6918
8686
  if (entry.promptActive) {
6919
8687
  entry.promptActive = false;
6920
8688
  activePromptCounter--;
@@ -6923,6 +8691,12 @@ function createAcpSessionBridge(opts) {
6923
8691
  if (defaultEntry === entry) defaultEntry = void 0;
6924
8692
  byId.delete(sessionId);
6925
8693
  telemetry.metrics?.sessionLifecycle("die");
8694
+ emitSessionLifecycle({
8695
+ type: "removed",
8696
+ sessionId,
8697
+ workspaceCwd: entry.workspaceCwd,
8698
+ reason: "killed"
8699
+ });
6926
8700
  const ci = channelInfoForEntry(entry);
6927
8701
  if (!ci) {
6928
8702
  writeStderrLine(
@@ -6972,8 +8746,17 @@ function createAcpSessionBridge(opts) {
6972
8746
  cancelIdleTimer();
6973
8747
  stopSessionReaper();
6974
8748
  const channels = Array.from(aliveChannels);
8749
+ const entries = Array.from(byId.values());
6975
8750
  defaultEntry = void 0;
6976
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
+ }
6977
8760
  for (const info of channels) {
6978
8761
  try {
6979
8762
  info.channel.killSync();
@@ -6991,11 +8774,18 @@ function createAcpSessionBridge(opts) {
6991
8774
  for (const e of entries) {
6992
8775
  permissionMediator.forgetSession(e.sessionId);
6993
8776
  e.pendingPermissionIds.clear();
8777
+ e.pendingInteractions.clear();
6994
8778
  }
6995
8779
  defaultEntry = void 0;
6996
8780
  byId.clear();
6997
8781
  for (const e of entries) {
6998
8782
  telemetry.metrics?.sessionLifecycle("die");
8783
+ emitSessionLifecycle({
8784
+ type: "removed",
8785
+ sessionId: e.sessionId,
8786
+ workspaceCwd: e.workspaceCwd,
8787
+ reason: "daemon_shutdown"
8788
+ });
6999
8789
  try {
7000
8790
  e.events.publish({
7001
8791
  type: "session_died",
@@ -7057,6 +8847,7 @@ var createHttpAcpBridge = createAcpSessionBridge;
7057
8847
 
7058
8848
  export {
7059
8849
  SessionArtifactValidationError,
8850
+ SessionArtifactAuthorizationError,
7060
8851
  extractErrorMessage,
7061
8852
  extractErrorCode,
7062
8853
  classifyTurnErrorKind,