@qwen-code/qwen-code 0.19.8 → 0.19.9-preview.0

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 (451) 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 +68 -51
  5. package/bundled/qc-helper/docs/features/_meta.ts +1 -0
  6. package/bundled/qc-helper/docs/features/channels/dingtalk.md +12 -2
  7. package/bundled/qc-helper/docs/features/channels/overview.md +82 -7
  8. package/bundled/qc-helper/docs/features/channels/wecom.md +6 -0
  9. package/bundled/qc-helper/docs/features/code-review.md +109 -41
  10. package/bundled/qc-helper/docs/features/commands.md +45 -44
  11. package/bundled/qc-helper/docs/features/computer-use.md +77 -0
  12. package/bundled/qc-helper/docs/features/hooks.md +29 -0
  13. package/bundled/qc-helper/docs/features/sub-agents.md +21 -0
  14. package/bundled/qc-helper/docs/features/tool-use-summaries.md +19 -23
  15. package/bundled/qc-helper/docs/integration-vscode.md +2 -2
  16. package/bundled/qc-helper/docs/qwen-serve.md +66 -23
  17. package/bundled/qc-helper/docs/reference/keyboard-shortcuts.md +2 -2
  18. package/bundled/review/DESIGN.md +303 -46
  19. package/bundled/review/SKILL.md +670 -165
  20. package/chunks/MaxSizedBox-BEVSAWZ6.js +70 -0
  21. package/chunks/{StandaloneSessionPicker-EYRGH6PJ.js → StandaloneSessionPicker-2DA2MDDE.js} +81 -79
  22. package/chunks/{acpAgent-GRFTTRHW.js → acpAgent-G3WJU3PS.js} +1318 -465
  23. package/chunks/agent-DD3WSHXE.js +68 -0
  24. package/chunks/agent-headless-MHI7U2CU.js +62 -0
  25. package/chunks/{anthropicContentGenerator-CPF22MA7.js → anthropicContentGenerator-56U3YCP2.js} +187 -31
  26. package/chunks/{artifact-tool-D233YJFH.js → artifact-tool-MIZLXBLF.js} +5 -5
  27. package/chunks/{askUserQuestion-MKL5ZTMC.js → askUserQuestion-XMJVPNWM.js} +6 -6
  28. package/chunks/bridge-HKEWPNB5.js +78 -0
  29. package/chunks/{ca-MKD32FQL.js → ca-C2JTT2N5.js} +68 -6
  30. package/chunks/channel-worker-group-K7QKH6IG.js +13 -0
  31. package/chunks/channel-worker-manager-YSFB4HUU.js +418 -0
  32. package/chunks/channel-worker-supervisor-QCKIOYZD.js +20 -0
  33. package/chunks/chunk-2FMQQXMM.js +38 -0
  34. package/chunks/{chunk-CSOTA7KX.js → chunk-2FO5GFKZ.js} +2 -2
  35. package/chunks/{chunk-QQDPRDVW.js → chunk-2HRYPZT5.js} +1 -1
  36. package/chunks/{chunk-UXEW557D.js → chunk-2J4ZWME4.js} +3 -3
  37. package/chunks/{chunk-P3XEEI3X.js → chunk-2TQXXQMI.js} +36 -7
  38. package/chunks/{chunk-GSHQYYIV.js → chunk-2UW5RFRP.js} +6 -26
  39. package/chunks/{chunk-6IAWPIQ6.js → chunk-2W3OOD4W.js} +5 -3
  40. package/chunks/{chunk-2PSWA5ID.js → chunk-2X56YUSZ.js} +1 -1
  41. package/chunks/{chunk-NQKLOAVJ.js → chunk-2Z5UX4KI.js} +2 -2
  42. package/chunks/chunk-3MX7D6QN.js +516 -0
  43. package/chunks/{chunk-EEN2TKST.js → chunk-43RXPU5Q.js} +2 -2
  44. package/chunks/{chunk-55ZMG67I.js → chunk-4LD5APFL.js} +4 -4
  45. package/chunks/{chunk-WF4FAXDL.js → chunk-4QZ3XHWE.js} +62 -62
  46. package/chunks/chunk-4QZADKJS.js +34 -0
  47. package/chunks/{chunk-UIDXQNMV.js → chunk-4WGODMVR.js} +1 -1
  48. package/chunks/{chunk-E6US47KI.js → chunk-4WKAA4KS.js} +1 -1
  49. package/chunks/{chunk-TI3BWD4D.js → chunk-54UZQ57N.js} +39 -19
  50. package/chunks/{chunk-3GONHQOA.js → chunk-57OAHC2Y.js} +1 -1
  51. package/chunks/{chunk-JZSY4WP3.js → chunk-5DTTYNB7.js} +1 -1
  52. package/chunks/chunk-5HBA2Z7V.js +28 -0
  53. package/chunks/{chunk-SKAZWEV5.js → chunk-5M6IDOMF.js} +35 -98
  54. package/chunks/{chunk-A4BMJM77.js → chunk-5O2XNYP6.js} +1 -0
  55. package/chunks/{chunk-FQNC62B2.js → chunk-5VHASII5.js} +11 -11
  56. package/chunks/{chunk-GO6LNQXT.js → chunk-6AM5D4ME.js} +1 -1
  57. package/chunks/{chunk-BJBWRCSK.js → chunk-6H67XLET.js} +1 -1
  58. package/chunks/{chunk-O7FIRWJV.js → chunk-6JPDCEDB.js} +3 -3
  59. package/chunks/{chunk-3UP777ZS.js → chunk-6RLROE6G.js} +1 -1
  60. package/chunks/{chunk-GN3F6PIU.js → chunk-6WPJRYLZ.js} +5 -5
  61. package/chunks/{chunk-ZERZSAZL.js → chunk-75DOP5OR.js} +2 -2
  62. package/chunks/chunk-7DKX4A5Z.js +91 -0
  63. package/chunks/{chunk-M7S4GK5Z.js → chunk-7IDCQ35Q.js} +147 -30
  64. package/chunks/{chunk-22MNNNXE.js → chunk-7NFB7LBQ.js} +11 -3
  65. package/chunks/{chunk-ZL5HN75Q.js → chunk-7R4DPSRA.js} +37 -3
  66. package/chunks/{chunk-VZ7HY3YU.js → chunk-7STBROEV.js} +3 -3
  67. package/chunks/{chunk-SAIILPQ3.js → chunk-7X7TEQNX.js} +5 -3
  68. package/chunks/{chunk-RMLZAUWH.js → chunk-A3U2TQUL.js} +10 -12
  69. package/chunks/{chunk-IRJWHZWU.js → chunk-A4UCTEB5.js} +2 -2
  70. package/chunks/{chunk-BOBRZZKD.js → chunk-A7W4GQRW.js} +5 -5
  71. package/chunks/{chunk-O33TBKDC.js → chunk-AAUZBE5Y.js} +155 -19
  72. package/chunks/{chunk-SQ2UATK7.js → chunk-ADLYZJYZ.js} +11 -11
  73. package/chunks/{chunk-QYUE6W3T.js → chunk-ALGGS7UH.js} +2 -39
  74. package/chunks/{chunk-WMPVYQ4P.js → chunk-ARKANCNX.js} +3 -2
  75. package/chunks/{chunk-IBY3Q3HG.js → chunk-AU4R7ACM.js} +967 -181
  76. package/chunks/{channel-worker-supervisor-NZQUOEFJ.js → chunk-B7MFJBKE.js} +190 -34
  77. package/chunks/{chunk-HW4N45EE.js → chunk-B7NUUEXC.js} +38 -11
  78. package/chunks/{chunk-3B26UVCX.js → chunk-BLPMRN4B.js} +39 -10
  79. package/chunks/{chunk-ZN5T4BHI.js → chunk-BPZALHVR.js} +2 -2
  80. package/chunks/{chunk-BVYU3ZTI.js → chunk-BXT6PQ73.js} +1020 -942
  81. package/chunks/{chunk-OFEVLU4C.js → chunk-CARU2RR2.js} +1 -1
  82. package/chunks/{chunk-HDOH3QYE.js → chunk-CEGKYZDB.js} +2 -2
  83. package/chunks/{chunk-GLE2YUPU.js → chunk-COH43MRJ.js} +1 -1
  84. package/chunks/{chunk-E5A7LHNN.js → chunk-CPBF7KYF.js} +1 -1
  85. package/chunks/{chunk-UWCTAVOD.js → chunk-CR3C7WXL.js} +1 -1
  86. package/chunks/{chunk-IWKSG2AR.js → chunk-CXAOG665.js} +1 -1
  87. package/chunks/{chunk-64WXLC72.js → chunk-DJ2GSRLV.js} +1 -1
  88. package/chunks/{chunk-QN5NZ3UQ.js → chunk-DMTGGOSA.js} +2 -2
  89. package/chunks/{chunk-27BHARDE.js → chunk-DMVA55W5.js} +2124 -30
  90. package/chunks/{chunk-JPALE3PA.js → chunk-DQCCLVQK.js} +6 -6
  91. package/chunks/{chunk-X2XNQQJI.js → chunk-E2UVIFI2.js} +186 -399
  92. package/chunks/{chunk-KHDZHZMH.js → chunk-E5Z2AVNV.js} +1 -1
  93. package/chunks/{chunk-E7EMCSUG.js → chunk-EAHMOXLL.js} +64 -42
  94. package/chunks/{chunk-VGGOPZUL.js → chunk-EJ3KGYWL.js} +36 -8
  95. package/chunks/{chunk-DIWVZ3VM.js → chunk-ERTHKDKN.js} +5 -5
  96. package/chunks/{chunk-UX6OTBVD.js → chunk-EX5OULP3.js} +2 -2
  97. package/chunks/{chunk-5MBO76SL.js → chunk-F333XEAU.js} +3 -3
  98. package/chunks/{chunk-OACJLMLD.js → chunk-FBU7WRZI.js} +18 -2
  99. package/chunks/{chunk-5IFG2VC4.js → chunk-FCMNKVXT.js} +1 -1
  100. package/chunks/chunk-FEOBU3FA.js +253 -0
  101. package/chunks/{chunk-LAXNEOBG.js → chunk-FI2WSOWX.js} +19 -3
  102. package/chunks/{chunk-E7KNELIZ.js → chunk-FSA7ERJ2.js} +2 -2
  103. package/chunks/{chunk-44NROQYV.js → chunk-GDQQRV43.js} +4 -4
  104. package/chunks/{chunk-ZE6E424U.js → chunk-GN5HJJBO.js} +2 -2
  105. package/chunks/{chunk-O2YT7VB4.js → chunk-GOXKNSDZ.js} +2 -65
  106. package/chunks/{chunk-V35MD7KN.js → chunk-GRC5HGPI.js} +1 -1
  107. package/chunks/{chunk-ON3JA3RU.js → chunk-GTENQOEJ.js} +7 -9
  108. package/chunks/{chunk-S6RLAIUR.js → chunk-GYTKQYDB.js} +1 -1
  109. package/chunks/{chunk-A2X3KLC3.js → chunk-GZXCZHCT.js} +3 -3
  110. package/chunks/{chunk-K5PGHDBN.js → chunk-H6XPXXMH.js} +1 -1
  111. package/chunks/{chunk-PMFULMLX.js → chunk-HCJTURZD.js} +2262 -384
  112. package/chunks/chunk-HGG2RRXD.js +445 -0
  113. package/chunks/{chunk-II73RK2S.js → chunk-HGT6JR3U.js} +20 -1
  114. package/chunks/{chunk-DICGXFAI.js → chunk-HJCVNZLQ.js} +11 -9
  115. package/chunks/{chunk-3GNOQZDC.js → chunk-HJUDVNCO.js} +17663 -14877
  116. package/chunks/{chunk-AYNCLGTW.js → chunk-HRUGYOJN.js} +8 -51
  117. package/chunks/{chunk-KYMBIKIW.js → chunk-HTHUX2T5.js} +1 -1
  118. package/chunks/{chunk-LQ7TMOCE.js → chunk-HTO4JFDZ.js} +1 -1
  119. package/chunks/{chunk-BUZ6HGL4.js → chunk-HWTL6MPR.js} +357 -59
  120. package/chunks/chunk-IDS7MSUP.js +74 -0
  121. package/chunks/{chunk-4CGOWHKM.js → chunk-J4MOFEOK.js} +2 -2
  122. package/chunks/chunk-JCD4FM7K.js +296 -0
  123. package/chunks/{chunk-22IFUCVR.js → chunk-JESCGQM3.js} +1 -1
  124. package/chunks/{chunk-JZHIQJOE.js → chunk-JGO6RFI2.js} +6 -6
  125. package/chunks/{chunk-X3YGAX7V.js → chunk-JGUT3LWZ.js} +9 -2
  126. package/chunks/{chunk-JRQ7EVGE.js → chunk-JW7OOJC6.js} +2 -2
  127. package/chunks/{chunk-OQXDAMKY.js → chunk-KF5MMGG4.js} +8 -8
  128. package/chunks/{chunk-42XGFRJS.js → chunk-KKRTYONM.js} +8 -6
  129. package/chunks/{chunk-GGQQA4JW.js → chunk-KSO42X3Z.js} +15 -2
  130. package/chunks/{chunk-MRO43B25.js → chunk-KW7NOTN6.js} +1 -1
  131. package/chunks/{chunk-54YZOP7P.js → chunk-L573Z2QZ.js} +11894 -5190
  132. package/chunks/{chunk-TLWA7TPR.js → chunk-L5U3ELZX.js} +2 -2
  133. package/chunks/{chunk-YHKAK72D.js → chunk-LHEOLOXU.js} +251 -63
  134. package/chunks/{chunk-H6EMK6QK.js → chunk-LQIDP6K5.js} +11913 -5207
  135. package/chunks/{chunk-I6WGFJJ3.js → chunk-LSEJG6AM.js} +73 -19
  136. package/chunks/{chunk-MZ7BABX3.js → chunk-LXLTBIDP.js} +44 -7
  137. package/chunks/{chunk-HLKG6CZN.js → chunk-M5GB774H.js} +1 -1
  138. package/chunks/{chunk-JCH35JB3.js → chunk-MKK4XGLF.js} +2 -2
  139. package/chunks/{chunk-YKZIAK7C.js → chunk-MO7O5722.js} +424 -40
  140. package/chunks/chunk-MR3PXB6E.js +48 -0
  141. package/chunks/{chunk-A5JOJ77H.js → chunk-MRYWYBQ4.js} +38 -15
  142. package/chunks/{chunk-332PWN27.js → chunk-NHDKSOEY.js} +1 -1
  143. package/chunks/{chunk-TSPB77TG.js → chunk-NRAFASYX.js} +1016 -2967
  144. package/chunks/{chunk-BMFMJINR.js → chunk-OA4JVLSA.js} +28 -2
  145. package/chunks/{chunk-P7M4GA57.js → chunk-OCPBI7J5.js} +1 -1
  146. package/chunks/chunk-OJAMDVF5.js +66 -0
  147. package/chunks/{chunk-IJRL2S3D.js → chunk-OKAIGAYW.js} +6 -6
  148. package/chunks/{chunk-FBET5XZ2.js → chunk-PEJWA3QQ.js} +29 -10
  149. package/chunks/{chunk-L4AMBVQG.js → chunk-PMKW35AT.js} +54 -22
  150. package/chunks/{chunk-AGHU5VRK.js → chunk-PZL23GTM.js} +1 -1
  151. package/chunks/{chunk-PC4ZXHU2.js → chunk-Q6TUALBE.js} +3 -3
  152. package/chunks/{chunk-FXGLL2HL.js → chunk-Q7YDKOGY.js} +1 -1
  153. package/chunks/{chunk-XOHNA2S6.js → chunk-QAW7PIHT.js} +3 -3
  154. package/chunks/{chunk-XYR5RUA5.js → chunk-QE2WJ7FS.js} +10 -3
  155. package/chunks/{chunk-LGFAYIKX.js → chunk-QHTIBUWB.js} +1 -1
  156. package/chunks/{chunk-P3MYAZBY.js → chunk-QMGX2KO2.js} +1 -1
  157. package/chunks/{chunk-Z3I3QJK3.js → chunk-QRUIS3DN.js} +1 -1
  158. package/chunks/{chunk-ITOGNELQ.js → chunk-QSWDGAIC.js} +2 -2
  159. package/chunks/{chunk-PUVCVELY.js → chunk-QUJOI7SR.js} +70 -8
  160. package/chunks/{chunk-GHOTR7HL.js → chunk-QUPXZXLV.js} +1 -1
  161. package/chunks/{chunk-6VK6FIMQ.js → chunk-R5D5JOAM.js} +415 -379
  162. package/chunks/{chunk-CD6USWHZ.js → chunk-R5XLNDE2.js} +2 -2
  163. package/chunks/{chunk-W7EEOR7D.js → chunk-RCEEXIOO.js} +8 -6
  164. package/chunks/{chunk-TLRYABYP.js → chunk-RKUWKYED.js} +1 -1
  165. package/chunks/{chunk-RNBYOUGV.js → chunk-RXFQM6FQ.js} +1 -1
  166. package/chunks/{chunk-QXPI7FEC.js → chunk-S3TWXGKA.js} +4 -4
  167. package/chunks/{chunk-2EM7ECZD.js → chunk-SMSFWUFY.js} +309 -80
  168. package/chunks/{chunk-FV7425LN.js → chunk-SPJQO2CE.js} +18 -5
  169. package/chunks/{chunk-MLZQVCF3.js → chunk-SZRN75WQ.js} +1 -1
  170. package/chunks/{chunk-7RYW5LQV.js → chunk-T2NCM2ET.js} +2 -2
  171. package/chunks/{chunk-Z2Z3GUXZ.js → chunk-TB6UDU4T.js} +1 -1
  172. package/chunks/{chunk-EXPMGZZV.js → chunk-TEGEBB2I.js} +1 -1
  173. package/chunks/{chunk-MX2YXRER.js → chunk-TJTKSVIV.js} +1 -1
  174. package/chunks/{chunk-CCGOFQV3.js → chunk-TQBODCFD.js} +8 -7
  175. package/chunks/chunk-TV6K2FB4.js +224 -0
  176. package/chunks/{chunk-H6BD2ELD.js → chunk-TYAMGABM.js} +2 -2
  177. package/chunks/{chunk-QYNNN7BK.js → chunk-TYLOFD2U.js} +2 -2
  178. package/chunks/{chunk-VRDMOSQS.js → chunk-UDXSXLER.js} +2 -2
  179. package/chunks/{chunk-DHOLNQLC.js → chunk-UFDX27PL.js} +60 -40
  180. package/chunks/{chunk-WHJQ3JUS.js → chunk-UGN6CTNZ.js} +10 -10
  181. package/chunks/{chunk-CHB5SLZZ.js → chunk-UNLRKDMW.js} +4 -4
  182. package/chunks/{chunk-CJCKDMWO.js → chunk-URK542T4.js} +1 -1
  183. package/chunks/{chunk-AHNXYU4O.js → chunk-V6WHFGST.js} +1 -1
  184. package/chunks/{chunk-ZTQ26VBE.js → chunk-VDDGDZMA.js} +1 -1
  185. package/chunks/{chunk-BOBOCR5H.js → chunk-VEETSXYX.js} +4 -4
  186. package/chunks/{chunk-OMX7CUOE.js → chunk-VGC4I5JJ.js} +1 -1
  187. package/chunks/{chunk-43W4YSO6.js → chunk-VJNBRRZG.js} +2 -2
  188. package/chunks/{chunk-WQTHLB5T.js → chunk-VKS7EIOA.js} +7 -7
  189. package/chunks/{chunk-J7RCB6N5.js → chunk-VVDNGM6X.js} +2 -2
  190. package/chunks/{chunk-ER3BKOLB.js → chunk-W7FJ3N32.js} +1 -1
  191. package/chunks/{chunk-3HX5LZ6R.js → chunk-WBL3FJEU.js} +2 -2
  192. package/chunks/{chunk-7LTB54MK.js → chunk-WIEO4CWB.js} +2 -2
  193. package/chunks/chunk-WMC6HH7Y.js +493 -0
  194. package/chunks/{chunk-FEJ2FZ3U.js → chunk-WTNZ6DXB.js} +2 -2
  195. package/chunks/{chunk-L46XKEGM.js → chunk-X6ODDESK.js} +5 -8
  196. package/chunks/chunk-XDPYM5DE.js +71 -0
  197. package/chunks/{chunk-3RW4AZJV.js → chunk-XFSFZCM5.js} +1 -1
  198. package/chunks/{chunk-2MPVVENX.js → chunk-XU457J4V.js} +2 -2
  199. package/chunks/{chunk-SIUQ3YYX.js → chunk-XVNQMZ2I.js} +1 -1
  200. package/chunks/{chunk-SYCJMSIJ.js → chunk-XWRJCPHC.js} +1 -1
  201. package/chunks/{chunk-SUAKHHK7.js → chunk-Y66VOBRP.js} +3 -3
  202. package/chunks/chunk-YAF2MQZ6.js +19 -0
  203. package/chunks/chunk-YHN5SUIJ.js +219 -0
  204. package/chunks/{chunk-BR4QREVK.js → chunk-YQ3U5MUC.js} +1 -1
  205. package/chunks/{chunk-Y6Z2O3WR.js → chunk-YUZI3WAC.js} +1 -1
  206. package/chunks/{chunk-YDOXPLYL.js → chunk-ZA74ZTQU.js} +4564 -1326
  207. package/chunks/{chunk-DCZWC4PF.js → chunk-ZGE3222J.js} +5 -5
  208. package/chunks/chunk-ZP4ZZJJG.js +448 -0
  209. package/chunks/{chunk-AHNJGLFV.js → chunk-ZPAHLFZU.js} +3 -3
  210. package/chunks/cli-entry-path-4VJ3Y6T2.js +10 -0
  211. package/chunks/{computer-use-WMY3UOBX.js → computer-use-SHA6C3ES.js} +50 -50
  212. package/chunks/config-utils-XSHOMMK4.js +24 -0
  213. package/chunks/contextCommand-HOAF5LUC.js +68 -0
  214. package/chunks/create-sub-session-EAB2U5XW.js +207 -0
  215. package/chunks/create-sub-session-WJOWBFEP.js +375 -0
  216. package/chunks/{cron-create-JSXEIPVS.js → cron-create-GMVKSXZT.js} +9 -9
  217. package/chunks/{cron-delete-OIFVEPGU.js → cron-delete-VDZKUAVK.js} +7 -7
  218. package/chunks/{cron-list-D4ZX4IOE.js → cron-list-UG7C7RAR.js} +14 -10
  219. package/chunks/{daemon-WPAENEBW.js → daemon-MI5HF26N.js} +1074 -61
  220. package/chunks/daemon-status-provider-KFL3LRTP.js +76 -0
  221. package/chunks/{de-ZFPPQVSF.js → de-2VCDRDGU.js} +68 -6
  222. package/chunks/{devtools-FM6GJPYG.js → devtools-4QFYJT6U.js} +3 -3
  223. package/chunks/{dist-O2IRTRPP.js → dist-IEXZC5ZR.js} +10 -10
  224. package/chunks/{dist-SCFLLYMK.js → dist-PQK4GCKA.js} +12 -11
  225. package/chunks/{dist-63IS3ZMI.js → dist-PZV5RKM6.js} +4 -4
  226. package/chunks/{dist-E6UANPIM.js → dist-U75JK3PF.js} +1011 -197
  227. package/chunks/{dist-H2AEJCZ5.js → dist-VXO7QBON.js} +5 -5
  228. package/chunks/{dist-Z34QPAQW.js → dist-WH4TZSH3.js} +529 -79
  229. package/chunks/{dist-BEXOZXLQ.js → dist-X2ABIW5R.js} +36 -15
  230. package/chunks/earlyInputCapture-PKAQJMIP.js +69 -0
  231. package/chunks/{edit-OTYWAXGP.js → edit-6TXLYHJ2.js} +51 -51
  232. package/chunks/{en-N4EPZAQI.js → en-NJCA3TAA.js} +78 -6
  233. package/chunks/{enter-worktree-HNYBDVCX.js → enter-worktree-ZTILGPJX.js} +50 -50
  234. package/chunks/{enterPlanMode-CJCNGT63.js → enterPlanMode-62HRAMCH.js} +66 -51
  235. package/chunks/environment-OYA3ZVEL.js +89 -0
  236. package/chunks/errors-BFXOUUUR.js +75 -0
  237. package/chunks/{exit-worktree-DUUKOE3X.js → exit-worktree-HHJ5M6MV.js} +50 -50
  238. package/chunks/{exitPlanMode-AMMSXOAJ.js → exitPlanMode-M3SWHCL7.js} +51 -51
  239. package/chunks/{fast-path-OV6NIP3K.js → fast-path-3VMPLM74.js} +8 -8
  240. package/chunks/{fast-path-settings-7RIFI3PK.js → fast-path-settings-DAOVFADC.js} +8 -6
  241. package/chunks/{fileFromPath-IBEHA3CO.js → fileFromPath-UDALK7FM.js} +3 -3
  242. package/chunks/{fr-5WWMY53B.js → fr-5F3E7WKD.js} +68 -6
  243. package/chunks/{gemini-NQ3WWBKB.js → gemini-ZCGVYOJL.js} +181 -110
  244. package/chunks/{geminiContentGenerator-CDUNEJQE.js → geminiContentGenerator-XBYBAIDQ.js} +9 -9
  245. package/chunks/{getMachineId-bsd-4CASPIU4.js → getMachineId-bsd-EXV7SWPA.js} +3 -3
  246. package/chunks/{getMachineId-darwin-HPQPEMZR.js → getMachineId-darwin-IUOMFXU3.js} +3 -3
  247. package/chunks/{getMachineId-linux-AUARKYHL.js → getMachineId-linux-K7XJYFHL.js} +2 -2
  248. package/chunks/{getMachineId-unsupported-S32ZDA2T.js → getMachineId-unsupported-EA5FDRJ6.js} +2 -2
  249. package/chunks/{getMachineId-win-4EFLHYIJ.js → getMachineId-win-KBA6RAI5.js} +3 -3
  250. package/chunks/{glob-ROV3YS4R.js → glob-UOEYQNHN.js} +97 -75
  251. package/chunks/{grep-NRITKDCD.js → grep-OQM3CH7W.js} +50 -50
  252. package/chunks/handleAutoUpdate-ORBYVJHZ.js +69 -0
  253. package/chunks/i18n-NBP574CO.js +84 -0
  254. package/chunks/initializer-CWD7TCCD.js +73 -0
  255. package/chunks/installationInfo-CHPDUPWC.js +65 -0
  256. package/chunks/{ja-CUHZOLEJ.js → ja-66J6B53L.js} +69 -7
  257. package/chunks/{keychain-token-storage-UJV5XP4Z.js → keychain-token-storage-AU22CQI6.js} +4 -4
  258. package/chunks/{kittyProtocolDetector-OJRCJLIU.js → kittyProtocolDetector-HCNNKJTI.js} +2 -2
  259. package/chunks/list-ZTTO362W.js +76 -0
  260. package/chunks/loadedSettingsAdapter-V4ZTRKUO.js +70 -0
  261. package/chunks/{loop-wakeup-OHWEMFP5.js → loop-wakeup-C4EXLYVA.js} +10 -10
  262. package/chunks/{lowlight-FYAAUU5J.js → lowlight-J2OZNPCA.js} +1 -1
  263. package/chunks/{ls-7MFU2QYW.js → ls-2OUTG3IZ.js} +6 -6
  264. package/chunks/{lsp-XHDTPTGV.js → lsp-4VMNXZW6.js} +4 -4
  265. package/chunks/mcp-UH5PSGIK.js +70 -0
  266. package/chunks/{monitor-FL2ZO33W.js → monitor-F4NYEU5J.js} +50 -50
  267. package/chunks/{multipart-parser-AJ4WASWR.js → multipart-parser-AWZKXSQN.js} +3 -3
  268. package/chunks/nonInteractiveCli-7IHPXVNH.js +129 -0
  269. package/chunks/{notebook-edit-6PULTHL5.js → notebook-edit-HPDXNGQH.js} +51 -51
  270. package/chunks/openaiContentGenerator-OBDNNOE2.js +56 -0
  271. package/chunks/pidfile-AUMGYIOK.js +73 -0
  272. package/chunks/{pt-OLYN42DS.js → pt-TFZO5Y6T.js} +68 -6
  273. package/chunks/{qwenContentGenerator-NX2E7PB7.js → qwenContentGenerator-LH2U2YRG.js} +52 -52
  274. package/chunks/{qwenOAuth2-ZV7YLMND.js → qwenOAuth2-LEYOU7RU.js} +9 -9
  275. package/chunks/read-file-HUUFBLEO.js +30 -0
  276. package/chunks/{read-mcp-resource-CNQHBSJM.js → read-mcp-resource-JUFHCKSC.js} +5 -5
  277. package/chunks/{read-package-up-ER5OJUGP.js → read-package-up-TCM6I7S2.js} +2 -2
  278. package/chunks/{record-artifact-SQ2OKRJW.js → record-artifact-DWSMBXYY.js} +4 -4
  279. package/chunks/ripGrep-C7X33LU6.js +60 -0
  280. package/chunks/{ru-LDM3GWVQ.js → ru-4L4LFHIT.js} +68 -6
  281. package/chunks/{run-qwen-serve-JK2DL7PI.js → run-qwen-serve-DGW5AFDJ.js} +1465 -283
  282. package/chunks/runtime-NYUSLBEZ.js +99 -0
  283. package/chunks/{scheduler-O4IGVZKK.js → scheduler-5VKDXULT.js} +51 -51
  284. package/chunks/{send-message-76GT3JMC.js → send-message-JZ752NWD.js} +7 -7
  285. package/chunks/serve-UTAHSFXH.js +76 -0
  286. package/chunks/{server-SGNPKX2O.js → server-VUHSYSUV.js} +9088 -1817
  287. package/chunks/{session-5BNM25EZ.js → session-QSGSSNFB.js} +340 -135
  288. package/chunks/settings-X24RYEED.js +120 -0
  289. package/chunks/shell-4MSI2R4O.js +70 -0
  290. package/chunks/{skill-I22QK5XS.js → skill-JR3GTNVZ.js} +24 -24
  291. package/chunks/spawnChannel-RFAAO44J.js +70 -0
  292. package/chunks/{src-7XL4G4DC.js → src-VLI3TKEY.js} +4 -4
  293. package/chunks/{src-BBQSM46M.js → src-YDBNMIIM.js} +183 -53
  294. package/chunks/standalone-update-4CY4HKGC.js +79 -0
  295. package/chunks/{startInteractiveUI-JII36UGN.js → startInteractiveUI-FOD7MMVB.js} +2368 -1923
  296. package/chunks/stdioHelpers-UMY72QRP.js +14 -0
  297. package/chunks/{syntheticOutput-F7LSDCAR.js → syntheticOutput-LELY6HAI.js} +5 -5
  298. package/chunks/task-create-QY3BIMNM.js +23 -0
  299. package/chunks/{task-list-TGGJX7EA.js → task-list-D2U47WBJ.js} +11 -10
  300. package/chunks/{task-stop-W7NGTMYQ.js → task-stop-N6E5IYLH.js} +4 -4
  301. package/chunks/{task-update-XYWMHJGJ.js → task-update-HJCQILLB.js} +13 -12
  302. package/chunks/{team-create-A7NUU5AQ.js → team-create-ZLQSU2E3.js} +50 -50
  303. package/chunks/{team-delete-UHQQ2QQX.js → team-delete-RCWYDRF4.js} +11 -10
  304. package/chunks/{team-plan-approval-TOQRJ2KI.js → team-plan-approval-YUAYBYLS.js} +50 -50
  305. package/chunks/theme-manager-SKNEBMOG.js +64 -0
  306. package/chunks/{todoWrite-C4ZAMSR3.js → todoWrite-FKCGATPJ.js} +6 -6
  307. package/chunks/{tool-search-SZ4EFS2T.js → tool-search-V5J3TGWY.js} +24 -24
  308. package/chunks/total-session-admission-2WIH72I3.js +72 -0
  309. package/chunks/tree-sitter-YJVE2ZUK.js +2980 -0
  310. package/chunks/trustedFolders-JF265CRI.js +80 -0
  311. package/chunks/types-ML3TRJQ5.js +16 -0
  312. package/chunks/updateCheck-AMXNPBP6.js +69 -0
  313. package/chunks/updateEventEmitter-5MKC7BLQ.js +10 -0
  314. package/chunks/validateNonInterActiveAuth-6YHQO7PD.js +194 -0
  315. package/chunks/{version-NO2XSNNP.js → version-UEJJK7PE.js} +3 -3
  316. package/chunks/{web-fetch-UYGMOHW4.js → web-fetch-QGODBB6S.js} +8 -8
  317. package/chunks/{workflow-WO2H6NRI.js → workflow-5YFA5V4M.js} +51 -51
  318. package/chunks/workspace-providers-status-LMBLBNR7.js +73 -0
  319. package/chunks/workspace-registration-store-VV2S4MSV.js +27 -0
  320. package/chunks/workspace-registry-BVMPYRLN.js +76 -0
  321. package/chunks/workspace-service-PUYIZ3J4.js +86 -0
  322. package/chunks/workspace-skills-status-CJ7J2KPP.js +72 -0
  323. package/chunks/{write-file-PX2Z7IBE.js → write-file-OV77M6X7.js} +51 -51
  324. package/chunks/{yargs-B64EYTCF.js → yargs-6H2AUULL.js} +2 -2
  325. package/chunks/{zh-J4F6QWQV.js → zh-FSAH32JB.js} +79 -7
  326. package/chunks/{zh-TW-XVDWOLSW.js → zh-TW-D2YIE2LO.js} +79 -7
  327. package/cli.js +18 -17
  328. package/locales/ca.js +91 -7
  329. package/locales/de.js +90 -7
  330. package/locales/en.js +109 -7
  331. package/locales/fr.js +91 -7
  332. package/locales/ja.js +93 -7
  333. package/locales/pt.js +90 -7
  334. package/locales/ru.js +90 -7
  335. package/locales/zh-TW.js +106 -7
  336. package/locales/zh.js +106 -7
  337. package/package.json +3 -6
  338. package/web-shell/assets/{arc-BWyoJpY2.js → arc-CObt_TK9.js} +1 -1
  339. package/web-shell/assets/{architectureDiagram-3BPJPVTR-BT0mGrz2.js → architectureDiagram-3BPJPVTR-B7aiJ6CK.js} +1 -1
  340. package/web-shell/assets/{blockDiagram-GPEHLZMM-BLgUWvyf.js → blockDiagram-GPEHLZMM-BSK2CrID.js} +1 -1
  341. package/web-shell/assets/{c4Diagram-AAUBKEIU-DYx9M7oU.js → c4Diagram-AAUBKEIU-Bhx2p7T_.js} +1 -1
  342. package/web-shell/assets/channel-BBaLq8MV.js +1 -0
  343. package/web-shell/assets/{chunk-2J33WTMH-CfMHWbuZ.js → chunk-2J33WTMH-c3Hzv0Ni.js} +1 -1
  344. package/web-shell/assets/{chunk-4BX2VUAB-B1URoqEV.js → chunk-4BX2VUAB-DNXaK7ZF.js} +1 -1
  345. package/web-shell/assets/{chunk-55IACEB6-DAIaM4QF.js → chunk-55IACEB6-BXjvDj_i.js} +1 -1
  346. package/web-shell/assets/{chunk-727SXJPM-B2X34knw.js → chunk-727SXJPM-B6CaKFp-.js} +1 -1
  347. package/web-shell/assets/{chunk-AQP2D5EJ-Cif-zLTQ.js → chunk-AQP2D5EJ-tij6Mz9B.js} +1 -1
  348. package/web-shell/assets/{chunk-FMBD7UC4-H18WCXdY.js → chunk-FMBD7UC4-DdlsGk2h.js} +1 -1
  349. package/web-shell/assets/{chunk-ND2GUHAM-YFtetHi0.js → chunk-ND2GUHAM-CdgGZSHE.js} +1 -1
  350. package/web-shell/assets/{chunk-QZHKN3VN-By2hiH3r.js → chunk-QZHKN3VN-D8oJn1C0.js} +1 -1
  351. package/web-shell/assets/classDiagram-4FO5ZUOK-B2ZK6sWE.js +1 -0
  352. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-B2ZK6sWE.js +1 -0
  353. package/web-shell/assets/{cose-bilkent-S5V4N54A-VrcnnLZu.js → cose-bilkent-S5V4N54A-BUVmNp1Y.js} +1 -1
  354. package/web-shell/assets/{dagre-BM42HDAG-CGo-IaVV.js → dagre-BM42HDAG-CO4q310O.js} +1 -1
  355. package/web-shell/assets/{diagram-2AECGRRQ-Dl4QjzIB.js → diagram-2AECGRRQ-C6gFvWkg.js} +1 -1
  356. package/web-shell/assets/{diagram-5GNKFQAL-qMLmsO0u.js → diagram-5GNKFQAL-NG2I_c0h.js} +1 -1
  357. package/web-shell/assets/{diagram-KO2AKTUF-CHlmuxPI.js → diagram-KO2AKTUF-DFKMS6I9.js} +1 -1
  358. package/web-shell/assets/{diagram-LMA3HP47-BUwU3GwT.js → diagram-LMA3HP47-BiElfsfP.js} +1 -1
  359. package/web-shell/assets/{diagram-OG6HWLK6-HEhwPDUM.js → diagram-OG6HWLK6-zLWDYUqu.js} +1 -1
  360. package/web-shell/assets/{erDiagram-TEJ5UH35-1n0llBmM.js → erDiagram-TEJ5UH35-DoVaazhK.js} +1 -1
  361. package/web-shell/assets/{flowDiagram-I6XJVG4X-BN74uMnt.js → flowDiagram-I6XJVG4X-0RLz-mXg.js} +1 -1
  362. package/web-shell/assets/{ganttDiagram-6RSMTGT7-BoOq78i3.js → ganttDiagram-6RSMTGT7-C-eX1A39.js} +3 -3
  363. package/web-shell/assets/{gitGraphDiagram-PVQCEYII-CN8b00c8.js → gitGraphDiagram-PVQCEYII-BauQu1Bu.js} +1 -1
  364. package/web-shell/assets/index-3uAT3ehO.css +5 -0
  365. package/web-shell/assets/index-CVHa2QH7.js +3 -0
  366. package/web-shell/assets/index-D1KigWat.js +1128 -0
  367. package/web-shell/assets/{infoDiagram-5YYISTIA-CCurnIna.js → infoDiagram-5YYISTIA-C800cFvZ.js} +1 -1
  368. package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-AwuRqdUP.js → ishikawaDiagram-YF4QCWOH-CFK1Jk6e.js} +1 -1
  369. package/web-shell/assets/{journeyDiagram-JHISSGLW-B7IICk-E.js → journeyDiagram-JHISSGLW-Cjx0WAC4.js} +1 -1
  370. package/web-shell/assets/{kanban-definition-UN3LZRKU-BYF0OKhx.js → kanban-definition-UN3LZRKU-DWMDHbiv.js} +1 -1
  371. package/web-shell/assets/{linear-GWWguGfD.js → linear-vpW4FrZs.js} +1 -1
  372. package/web-shell/assets/{mermaid.core-JqtkxvCl.js → mermaid.core-DsHRUnDd.js} +5 -5
  373. package/web-shell/assets/{mindmap-definition-RKZ34NQL-B0MmHsZ6.js → mindmap-definition-RKZ34NQL-D3Eb1cAK.js} +1 -1
  374. package/web-shell/assets/{pieDiagram-4H26LBE5-DhImUZLQ.js → pieDiagram-4H26LBE5-Cr2-6sWt.js} +1 -1
  375. package/web-shell/assets/{quadrantDiagram-W4KKPZXB-DGeSC3DT.js → quadrantDiagram-W4KKPZXB-Yd7yUgVp.js} +1 -1
  376. package/web-shell/assets/{requirementDiagram-4Y6WPE33-DkspY6Re.js → requirementDiagram-4Y6WPE33-BE6SprL8.js} +1 -1
  377. package/web-shell/assets/{sankeyDiagram-5OEKKPKP-CaOofS2x.js → sankeyDiagram-5OEKKPKP-DzkuMK8d.js} +1 -1
  378. package/web-shell/assets/{sequenceDiagram-3UESZ5HK-DFQ_kFnN.js → sequenceDiagram-3UESZ5HK-wD9A3Dhi.js} +1 -1
  379. package/web-shell/assets/{stateDiagram-AJRCARHV-D3snw0Tz.js → stateDiagram-AJRCARHV-BOp5q1Nm.js} +1 -1
  380. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-BDsWUmPG.js +1 -0
  381. package/web-shell/assets/{timeline-definition-PNZ67QCA-l4zH8nLM.js → timeline-definition-PNZ67QCA-Btc08sUA.js} +1 -1
  382. package/web-shell/assets/{vennDiagram-CIIHVFJN-BnRlA98M.js → vennDiagram-CIIHVFJN-Bv6upsOA.js} +1 -1
  383. package/web-shell/assets/{wardley-L42UT6IY-BS0xUdmd.js → wardley-L42UT6IY-D-XamnCK.js} +1 -1
  384. package/web-shell/assets/{wardleyDiagram-YWT4CUSO-DKGBUWyQ.js → wardleyDiagram-YWT4CUSO-BbP_5Wfp.js} +1 -1
  385. package/web-shell/assets/{xychartDiagram-2RQKCTM6-DVb2HuOB.js → xychartDiagram-2RQKCTM6-CO5Hh28b.js} +1 -1
  386. package/web-shell/index.html +16 -4
  387. package/chunks/MaxSizedBox-5DT2SD7B.js +0 -71
  388. package/chunks/agent-H2DGKS77.js +0 -68
  389. package/chunks/agent-headless-KFMFIT6J.js +0 -62
  390. package/chunks/bridge-NA3ZQQKJ.js +0 -78
  391. package/chunks/chunk-5P5XGNYH.js +0 -93
  392. package/chunks/chunk-6SHO7WFF.js +0 -79
  393. package/chunks/chunk-IFEWJXFS.js +0 -37
  394. package/chunks/chunk-XNXVCNAT.js +0 -88
  395. package/chunks/contextCommand-LXFZRP4L.js +0 -68
  396. package/chunks/daemon-status-provider-GJRXY5EN.js +0 -76
  397. package/chunks/earlyInputCapture-KNOYZ4MJ.js +0 -69
  398. package/chunks/environment-ATFTYMLZ.js +0 -88
  399. package/chunks/errors-A54HAADA.js +0 -75
  400. package/chunks/handleAutoUpdate-CEZJAARW.js +0 -65
  401. package/chunks/initializer-BRFJKRRX.js +0 -71
  402. package/chunks/list-KVHF7O4X.js +0 -73
  403. package/chunks/loadedSettingsAdapter-LL5PSFKX.js +0 -68
  404. package/chunks/mcp-3GCXRTOQ.js +0 -68
  405. package/chunks/nonInteractiveCli-PTQI3DV6.js +0 -119
  406. package/chunks/openaiContentGenerator-YE42PKCY.js +0 -56
  407. package/chunks/pidfile-NZYSCFRK.js +0 -73
  408. package/chunks/read-file-33XGP5HF.js +0 -30
  409. package/chunks/ripGrep-QWZLGTAN.js +0 -60
  410. package/chunks/serve-JTMIGANH.js +0 -74
  411. package/chunks/settings-3MI4XXP6.js +0 -118
  412. package/chunks/shell-YJKBE67T.js +0 -68
  413. package/chunks/spawnChannel-UNV6W4DU.js +0 -70
  414. package/chunks/task-create-WCHTB435.js +0 -22
  415. package/chunks/theme-manager-WZ4RRMMB.js +0 -64
  416. package/chunks/total-session-admission-ULXJWXFV.js +0 -72
  417. package/chunks/trustedFolders-LAZZPZ3D.js +0 -79
  418. package/chunks/types-QX5C3CHJ.js +0 -12
  419. package/chunks/updateCheck-KONLM73X.js +0 -65
  420. package/chunks/validateNonInterActiveAuth-MPKSRCXD.js +0 -184
  421. package/chunks/workspace-providers-status-3W7E7DFW.js +0 -71
  422. package/chunks/workspace-registry-SXJZNN3O.js +0 -74
  423. package/chunks/workspace-service-CZRQZ4Q4.js +0 -80
  424. package/chunks/workspace-skills-status-K7WFREPD.js +0 -70
  425. package/node_modules/@qwen-code/audio-capture/dist/index.d.ts +0 -35
  426. package/node_modules/@qwen-code/audio-capture/dist/index.js +0 -48
  427. package/node_modules/@qwen-code/audio-capture/dist/index.js.map +0 -1
  428. package/node_modules/@qwen-code/audio-capture/dist/platform.d.ts +0 -7
  429. package/node_modules/@qwen-code/audio-capture/dist/platform.js +0 -18
  430. package/node_modules/@qwen-code/audio-capture/dist/platform.js.map +0 -1
  431. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/LICENSE +0 -21
  432. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/README.md +0 -58
  433. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/SECURITY.md +0 -5
  434. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/bin.js +0 -84
  435. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/build-test.js +0 -19
  436. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/index.js +0 -6
  437. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/node-gyp-build.js +0 -207
  438. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/optional.js +0 -7
  439. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/package.json +0 -43
  440. package/node_modules/@qwen-code/audio-capture/package.json +0 -30
  441. package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-arm64/@qwen-code+audio-capture.node +0 -0
  442. package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-x64/@qwen-code+audio-capture.node +0 -0
  443. package/node_modules/@qwen-code/audio-capture/prebuilds/linux-arm64/@qwen-code+audio-capture.node +0 -0
  444. package/node_modules/@qwen-code/audio-capture/prebuilds/linux-x64/@qwen-code+audio-capture.node +0 -0
  445. package/node_modules/@qwen-code/audio-capture/prebuilds/win32-x64/@qwen-code+audio-capture.node +0 -0
  446. package/web-shell/assets/channel-wmPUGHtg.js +0 -1
  447. package/web-shell/assets/classDiagram-4FO5ZUOK-BA2ljEjW.js +0 -1
  448. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-BA2ljEjW.js +0 -1
  449. package/web-shell/assets/index-CNwoO1Cn.js +0 -783
  450. package/web-shell/assets/index-CWQ0z6kZ.css +0 -5
  451. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-BbSm_9d0.js +0 -1
@@ -6,19 +6,19 @@ import {
6
6
  LOAD_REPLAY_MODE_META_KEY,
7
7
  LOAD_REPLAY_VERSION,
8
8
  MID_TURN_QUEUE_DRAIN_METHOD
9
- } from "./chunk-AGHU5VRK.js";
9
+ } from "./chunk-PZL23GTM.js";
10
10
  import {
11
11
  DEFAULT_RING_SIZE,
12
12
  EVENT_SCHEMA_VERSION,
13
13
  EventBus,
14
14
  serializedBridgeEventByteLength
15
- } from "./chunk-3UP777ZS.js";
15
+ } from "./chunk-6RLROE6G.js";
16
16
  import {
17
17
  normalizeCompactedReplayMaxBytes
18
- } from "./chunk-GLE2YUPU.js";
18
+ } from "./chunk-COH43MRJ.js";
19
19
  import {
20
20
  MCP_RESTART_SERVER_DEADLINE_MS
21
- } from "./chunk-P7M4GA57.js";
21
+ } from "./chunk-OCPBI7J5.js";
22
22
  import {
23
23
  BranchWhilePromptActiveError,
24
24
  CancelSentinelCollisionError,
@@ -38,18 +38,10 @@ import {
38
38
  SessionShellDisabledError,
39
39
  WorkspaceMismatchError,
40
40
  isNotCurrentlyGeneratingCancelError
41
- } from "./chunk-GGQQA4JW.js";
42
- import {
43
- canonicalizeWorkspace
44
- } from "./chunk-ER3BKOLB.js";
45
- import {
46
- ClientSideConnection,
47
- PROTOCOL_VERSION,
48
- RequestError
49
- } from "./chunk-VRDMOSQS.js";
41
+ } from "./chunk-KSO42X3Z.js";
50
42
  import {
51
43
  defaultSpawnChannelFactory
52
- } from "./chunk-BOBOCR5H.js";
44
+ } from "./chunk-VEETSXYX.js";
53
45
  import {
54
46
  BridgeChannelClosedError,
55
47
  BridgeTimeoutError,
@@ -58,18 +50,34 @@ import {
58
50
  STATUS_SCHEMA_VERSION,
59
51
  createIdleWorkspaceExtensionsStatus,
60
52
  createIdleWorkspaceHooksStatus
61
- } from "./chunk-LAXNEOBG.js";
53
+ } from "./chunk-FI2WSOWX.js";
54
+ import {
55
+ canonicalizeWorkspace
56
+ } from "./chunk-W7FJ3N32.js";
62
57
  import {
58
+ ClientSideConnection,
59
+ PROTOCOL_VERSION,
60
+ RequestError
61
+ } from "./chunk-UDXSXLER.js";
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-HJUDVNCO.js";
66
74
  import {
67
75
  DAEMON_TRACEPARENT_META_KEY,
68
76
  DAEMON_TRACESTATE_META_KEY
69
- } from "./chunk-BUZ6HGL4.js";
77
+ } from "./chunk-HWTL6MPR.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");
@@ -1264,7 +1332,8 @@ function isTrustedArtifactToolUpdate(params, updateMeta) {
1264
1332
  __name(isTrustedArtifactToolUpdate, "isTrustedArtifactToolUpdate");
1265
1333
  function preserveFsErrorOverAcp(err) {
1266
1334
  if (isFsErrorShape(err)) {
1267
- throw new RequestError(-32603, err.message, {
1335
+ const code = err.kind === "parse_error" ? -32602 : -32603;
1336
+ throw new RequestError(code, err.message, {
1268
1337
  errorKind: err.kind,
1269
1338
  ...err.hint !== void 0 ? { hint: err.hint } : {},
1270
1339
  ...err.status !== void 0 ? { status: err.status } : {}
@@ -1323,7 +1392,7 @@ function sliceLineRange(content, startLine, endLine) {
1323
1392
  }
1324
1393
  __name(sliceLineRange, "sliceLineRange");
1325
1394
  var BridgeClient = class {
1326
- constructor(resolveEntry, resolvePendingRestoreEvents, mediator, permissionTimeoutMs, maxPendingPerSession, fileSystem, onModelPromoted, onModePromoted, clientMcpSender, ownsSession = () => true, onTokenUsage) {
1395
+ constructor(resolveEntry, resolvePendingRestoreEvents, mediator, permissionTimeoutMs, maxPendingPerSession, fileSystem, onModelPromoted, onModePromoted, clientMcpSender, ownsSession = () => true, onTokenUsage, onCreateSubSession) {
1327
1396
  this.resolveEntry = resolveEntry;
1328
1397
  this.resolvePendingRestoreEvents = resolvePendingRestoreEvents;
1329
1398
  this.mediator = mediator;
@@ -1335,6 +1404,7 @@ var BridgeClient = class {
1335
1404
  this.clientMcpSender = clientMcpSender;
1336
1405
  this.ownsSession = ownsSession;
1337
1406
  this.onTokenUsage = onTokenUsage;
1407
+ this.onCreateSubSession = onCreateSubSession;
1338
1408
  }
1339
1409
  static {
1340
1410
  __name(this, "BridgeClient");
@@ -1348,10 +1418,9 @@ var BridgeClient = class {
1348
1418
  );
1349
1419
  return { outcome: { outcome: "cancelled" } };
1350
1420
  }
1421
+ const options = Array.isArray(params.options) ? params.options : [];
1351
1422
  const allowedOptionIds = new Set(
1352
- params.options.map(
1353
- (o) => String(o.optionId ?? "")
1354
- )
1423
+ options.map((o) => String(o.optionId ?? ""))
1355
1424
  );
1356
1425
  allowedOptionIds.delete("");
1357
1426
  const requestId = randomUUID();
@@ -1364,12 +1433,25 @@ var BridgeClient = class {
1364
1433
  requestId,
1365
1434
  sessionId: entry.sessionId,
1366
1435
  toolCall: params.toolCall,
1367
- options: params.options
1436
+ options
1368
1437
  },
1369
1438
  ...entry.activePromptOriginatorClientId ? { originatorClientId: entry.activePromptOriginatorClientId } : {}
1370
1439
  });
1371
1440
  if (!published) return { outcome: { outcome: "cancelled" } };
1441
+ let interaction;
1442
+ try {
1443
+ interaction = pendingInteractionFromRequest(requestId, {
1444
+ ...params,
1445
+ options
1446
+ });
1447
+ } catch (error) {
1448
+ writeStderrLine(
1449
+ `qwen serve: failed to snapshot pending interaction ${requestId}: ${error instanceof Error ? error.message : String(error)}`
1450
+ );
1451
+ interaction = fallbackPendingPermissionInteraction(requestId, options);
1452
+ }
1372
1453
  entry.pendingPermissionIds.add(requestId);
1454
+ if (interaction) entry.pendingInteractions.set(requestId, interaction);
1373
1455
  try {
1374
1456
  const record = {
1375
1457
  requestId,
@@ -1385,6 +1467,7 @@ var BridgeClient = class {
1385
1467
  return resolutionToAcpResponse(resolution);
1386
1468
  } finally {
1387
1469
  entry.pendingPermissionIds.delete(requestId);
1470
+ entry.pendingInteractions.delete(requestId);
1388
1471
  }
1389
1472
  }
1390
1473
  async sessionUpdate(params) {
@@ -1458,7 +1541,7 @@ var BridgeClient = class {
1458
1541
  trustedPublisher: isTrustedArtifactToolUpdate(params, updateMeta)
1459
1542
  };
1460
1543
  }
1461
- async seedSessionUpdates(entry, updates) {
1544
+ async seedSessionUpdates(entry, updates, options = {}) {
1462
1545
  const frames = [];
1463
1546
  const artifactBatches = [];
1464
1547
  for (const update of updates) {
@@ -1467,7 +1550,7 @@ var BridgeClient = class {
1467
1550
  entry
1468
1551
  );
1469
1552
  frames.push(...prepared.frames);
1470
- if (prepared.artifacts.length > 0) {
1553
+ if (options.ingestArtifacts !== false && prepared.artifacts.length > 0) {
1471
1554
  artifactBatches.push({
1472
1555
  artifacts: prepared.artifacts,
1473
1556
  trustedPublisher: prepared.trustedPublisher
@@ -1505,18 +1588,23 @@ var BridgeClient = class {
1505
1588
  return;
1506
1589
  }
1507
1590
  const durationMs = updateMeta?.["durationMs"];
1591
+ const apiErrors = updateMeta?.["apiErrors"];
1592
+ const apiRetries = updateMeta?.["apiRetries"];
1508
1593
  this.onTokenUsage(
1509
1594
  typeof inputTokens === "number" ? inputTokens : 0,
1510
1595
  typeof outputTokens === "number" ? outputTokens : 0,
1511
- typeof durationMs === "number" ? durationMs : void 0
1596
+ typeof durationMs === "number" ? durationMs : void 0,
1597
+ typeof apiErrors === "number" ? apiErrors : 0,
1598
+ typeof apiRetries === "number" ? apiRetries : 0
1512
1599
  );
1513
1600
  }
1514
1601
  /**
1515
- * Bounded early-event buffer. Frames are keyed by sessionId; each
1516
- * entry tracks its `expiresAt` for lazy TTL-based eviction in
1517
- * `bufferEarlyEvent`. Drained by `drainEarlyEvents` whenever the
1518
- * bridge registers a session with a matching id. See
1519
- * MAX_EARLY_EVENT_* constants for capacity bounds.
1602
+ * Bounded early-event buffer. The map is scoped to this BridgeClient and
1603
+ * therefore to one channel; a stale channel cannot seed a fresh channel's
1604
+ * future session. Frames are keyed by sessionId; each entry tracks its
1605
+ * `expiresAt` for lazy TTL-based eviction in `bufferEarlyEvent`. Drained by
1606
+ * `drainEarlyEvents` whenever the bridge registers a session with a matching
1607
+ * id. See MAX_EARLY_EVENT_* constants for capacity bounds.
1520
1608
  */
1521
1609
  earlyEvents = /* @__PURE__ */ new Map();
1522
1610
  /**
@@ -1541,7 +1629,7 @@ var BridgeClient = class {
1541
1629
  /**
1542
1630
  * Allow-list of sessionIds currently being restored via
1543
1631
  * `session/load` / `session/resume`. Bypasses the tombstone check
1544
- * in `bufferEarlyEvent` so restore-time guardrail events for a
1632
+ * in `bufferEarlyEvent` so restore-time events for a
1545
1633
  * previously-closed id flow through to the future
1546
1634
  * `createSessionEntry -> drainEarlyEvents` call.
1547
1635
  *
@@ -1556,9 +1644,12 @@ var BridgeClient = class {
1556
1644
  inFlightRestoreIds = /* @__PURE__ */ new Set();
1557
1645
  /**
1558
1646
  * 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
1647
+ * response, unlike `extNotification`). Served methods:
1648
+ * `qwen/control/client_mcp/message` (reverse tool channel),
1649
+ * `qwen/control/create-sub-session` (the `create_sub_session` tool daemon
1650
+ * spawns a sub-session and, for `'first-turn'`, returns its first-turn
1651
+ * result), and `craft/drainMidTurnQueue`: the ACP child calls the last one
1652
+ * between tool batches to pull any messages the browser queued mid-turn. We splice the per-session
1562
1653
  * queue, return them to the child as the response, and — when non-empty —
1563
1654
  * publish a `mid_turn_message_injected` SSE frame so the browser can move
1564
1655
  * those messages out of its pending queue (a dedupe signal, not a transcript
@@ -1571,6 +1662,9 @@ var BridgeClient = class {
1571
1662
  if (method === SERVE_CONTROL_EXT_METHODS.clientMcpMessage) {
1572
1663
  return this.handleClientMcpMessage(params);
1573
1664
  }
1665
+ if (method === SERVE_CONTROL_EXT_METHODS.createSubSession) {
1666
+ return this.handleCreateSubSession(params);
1667
+ }
1574
1668
  if (method !== MID_TURN_QUEUE_DRAIN_METHOD) {
1575
1669
  throw RequestError.methodNotFound(method);
1576
1670
  }
@@ -1646,17 +1740,80 @@ var BridgeClient = class {
1646
1740
  const response = await send(payload);
1647
1741
  return { payload: response };
1648
1742
  }
1743
+ /**
1744
+ * Handle the `create_sub_session` tool's request: validate, then forward to
1745
+ * the daemon-host `onCreateSubSession` callback (which spawns a fresh
1746
+ * top-level sub-session and, for `'first-turn'`, waits for its first turn and
1747
+ * returns the result). No host wired → `methodNotFound`, which the tool
1748
+ * surfaces as "daemon-only".
1749
+ */
1750
+ async handleCreateSubSession(params) {
1751
+ if (!this.onCreateSubSession) {
1752
+ throw RequestError.methodNotFound(
1753
+ SERVE_CONTROL_EXT_METHODS.createSubSession
1754
+ );
1755
+ }
1756
+ const prompt = params["prompt"];
1757
+ if (typeof prompt !== "string" || prompt.length === 0) {
1758
+ throw RequestError.invalidParams(
1759
+ void 0,
1760
+ "`prompt` must be a non-empty string"
1761
+ );
1762
+ }
1763
+ if (prompt.length > MAX_SUB_SESSION_PROMPT_CHARS) {
1764
+ throw RequestError.invalidParams(
1765
+ void 0,
1766
+ `\`prompt\` exceeds the ${MAX_SUB_SESSION_PROMPT_CHARS}-character limit`
1767
+ );
1768
+ }
1769
+ const completion = params["completion"];
1770
+ if (completion !== "sent" && completion !== "first-turn") {
1771
+ throw RequestError.invalidParams(
1772
+ void 0,
1773
+ "`completion` must be 'sent' or 'first-turn'"
1774
+ );
1775
+ }
1776
+ const name = params["name"];
1777
+ if (typeof name === "string" && name.length > MAX_SUB_SESSION_NAME_CHARS) {
1778
+ throw RequestError.invalidParams(
1779
+ void 0,
1780
+ `\`name\` exceeds the ${MAX_SUB_SESSION_NAME_CHARS}-character limit`
1781
+ );
1782
+ }
1783
+ const callerSessionId = params["callerSessionId"];
1784
+ if (typeof callerSessionId !== "string" || callerSessionId.length === 0 || !this.ownsSession(callerSessionId)) {
1785
+ throw RequestError.invalidParams(
1786
+ void 0,
1787
+ "`callerSessionId` is required and must name a session owned by this connection"
1788
+ );
1789
+ }
1790
+ const model = params["model"];
1791
+ const result = await this.onCreateSubSession({
1792
+ prompt,
1793
+ completion,
1794
+ ...typeof model === "string" && model.length > 0 && model.length <= 128 ? { model } : {},
1795
+ ...typeof name === "string" && name.length > 0 ? { name } : {},
1796
+ callerSessionId
1797
+ });
1798
+ return {
1799
+ sessionId: result.sessionId,
1800
+ ...result.result !== void 0 ? { result: result.result } : {},
1801
+ ...result.stopReason !== void 0 ? { stopReason: result.stopReason } : {},
1802
+ ...result.parentSessionPersisted !== void 0 ? { parentSessionPersisted: result.parentSessionPersisted } : {}
1803
+ };
1804
+ }
1649
1805
  /**
1650
1806
  * Handle child->bridge ACP `extNotification` calls. Recognized methods are
1651
1807
  * `qwen/notify/session/model-update`,
1652
1808
  * `qwen/notify/session/mode-update`,
1653
1809
  * `qwen/notify/session/title-update` (auto/in-process session titles),
1810
+ * `qwen/notify/session/recording-degraded`,
1654
1811
  * `qwen/notify/session/prompt-suggestion` (followup assist),
1655
1812
  * `qwen/notify/session/artifact-event` (hook artifacts),
1656
1813
  * `qwen/notify/session/terminal-sequence`, and
1657
1814
  * `qwen/notify/session/mcp-budget-event` — each translated into a
1658
- * session-scoped SSE frame. Unknown methods are dropped silently
1659
- * for forward-compat.
1815
+ * session-scoped SSE frame. Unknown methods are dropped silently for
1816
+ * forward-compat.
1660
1817
  */
1661
1818
  async extNotification(method, params) {
1662
1819
  if (method === "qwen/notify/session/model-update") {
@@ -1687,6 +1844,28 @@ var BridgeClient = class {
1687
1844
  }
1688
1845
  return;
1689
1846
  }
1847
+ if (method === "qwen/notify/session/recording-degraded") {
1848
+ const sessionId2 = params["sessionId"];
1849
+ if (params["v"] !== 1 || typeof sessionId2 !== "string" || sessionId2.length === 0 || params["reason"] !== "write_failed") {
1850
+ writeStderrLine(
1851
+ `[demux] session=${typeof sessionId2 === "string" ? sessionId2 : "<missing>"} type=session_recording_degraded action=dropped reason=malformed`
1852
+ );
1853
+ return;
1854
+ }
1855
+ const entry = this.resolveEntry(sessionId2);
1856
+ if (entry && !this.ownsSession(sessionId2)) {
1857
+ writeStderrLine(
1858
+ `[demux] session=${sessionId2} type=session_recording_degraded action=dropped reason=session_not_owned`
1859
+ );
1860
+ return;
1861
+ }
1862
+ if (entry) entry.recordingDegraded = true;
1863
+ this.publishExtNotification(sessionId2, "session_recording_degraded", {
1864
+ sessionId: sessionId2,
1865
+ reason: "write_failed"
1866
+ });
1867
+ return;
1868
+ }
1690
1869
  if (method === "qwen/notify/session/prompt-suggestion") {
1691
1870
  const sessionId2 = params["sessionId"];
1692
1871
  const suggestion = params["suggestion"];
@@ -1943,7 +2122,7 @@ var BridgeClient = class {
1943
2122
  this.sweepExpiredTombstones(now);
1944
2123
  if (this.tombstonedSessionIds.has(sessionId) && !this.inFlightRestoreIds.has(sessionId)) {
1945
2124
  writeStderrLine(
1946
- `qwen serve: dropping mcp guardrail extNotification for tombstoned session ${JSON.stringify(sessionId)} (post-close stale event)`
2125
+ `qwen serve: dropping early extNotification for tombstoned session ${JSON.stringify(sessionId)} (post-close stale event)`
1947
2126
  );
1948
2127
  return;
1949
2128
  }
@@ -1952,7 +2131,7 @@ var BridgeClient = class {
1952
2131
  if (!buf) {
1953
2132
  if (this.earlyEvents.size >= MAX_EARLY_EVENT_SESSIONS) {
1954
2133
  writeStderrLine(
1955
- `qwen serve: dropping mcp guardrail extNotification \u2014 early-event buffer at MAX_EARLY_EVENT_SESSIONS (${MAX_EARLY_EVENT_SESSIONS}); possible session-id fanout abuse`
2134
+ `qwen serve: dropping early extNotification \u2014 early-event buffer at MAX_EARLY_EVENT_SESSIONS (${MAX_EARLY_EVENT_SESSIONS}); possible session-id fanout abuse`
1956
2135
  );
1957
2136
  return;
1958
2137
  }
@@ -1961,7 +2140,7 @@ var BridgeClient = class {
1961
2140
  }
1962
2141
  if (buf.frames.length >= MAX_EARLY_EVENTS_PER_SESSION) {
1963
2142
  writeStderrLine(
1964
- `qwen serve: dropping mcp guardrail extNotification for session ${JSON.stringify(sessionId)} \u2014 per-session cap (${MAX_EARLY_EVENTS_PER_SESSION}) reached`
2143
+ `qwen serve: dropping early extNotification for session ${JSON.stringify(sessionId)} \u2014 per-session cap (${MAX_EARLY_EVENTS_PER_SESSION}) reached`
1965
2144
  );
1966
2145
  return;
1967
2146
  }
@@ -1995,7 +2174,7 @@ var BridgeClient = class {
1995
2174
  * Mark a sessionId as currently being restored via `session/load` /
1996
2175
  * `session/resume`. While in this set, `bufferEarlyEvent` accepts
1997
2176
  * frames for the id even if it's tombstoned — so restore-time
1998
- * guardrail events from the freshly-restored child reach
2177
+ * early events from the freshly-restored child reach
1999
2178
  * `drainEarlyEvents` instead of being rejected by the tombstone.
2000
2179
  *
2001
2180
  * Bridge factory calls this BEFORE awaiting the ACP restore call.
@@ -2029,7 +2208,12 @@ var BridgeClient = class {
2029
2208
  this.tombstonedSessionIds.delete(sessionId);
2030
2209
  const buf = this.earlyEvents.get(sessionId);
2031
2210
  if (!buf) return;
2032
- for (const frame of buf.frames) entry.events.publish(frame);
2211
+ for (const frame of buf.frames) {
2212
+ if (frame.type === "session_recording_degraded") {
2213
+ entry.recordingDegraded = true;
2214
+ }
2215
+ entry.events.publish(frame);
2216
+ }
2033
2217
  this.earlyEvents.delete(sessionId);
2034
2218
  }
2035
2219
  async writeTextFile(params) {
@@ -2096,6 +2280,18 @@ var BridgeClient = class {
2096
2280
  if (typeof params.limit === "number" && params.limit <= 0) {
2097
2281
  return { content: "" };
2098
2282
  }
2283
+ if (typeof params.limit === "number" && params.limit > 0 && !Number.isSafeInteger(params.limit)) {
2284
+ throw RequestError.invalidParams(
2285
+ void 0,
2286
+ `\`limit\` must be a positive integer, got ${params.limit}`
2287
+ );
2288
+ }
2289
+ if (typeof params.line === "number" && params.line > 0 && !Number.isSafeInteger(params.line)) {
2290
+ throw RequestError.invalidParams(
2291
+ void 0,
2292
+ `\`line\` must be a positive integer, got ${params.line}`
2293
+ );
2294
+ }
2099
2295
  const READ_FILE_SIZE_CAP = 100 * 1024 * 1024;
2100
2296
  const stats = await fs.stat(params.path);
2101
2297
  if (!stats.isFile()) {
@@ -2239,7 +2435,7 @@ __name(extractA2uiToolUpdate, "extractA2uiToolUpdate");
2239
2435
  // packages/acp-bridge/src/sessionArtifacts.ts
2240
2436
  init_esbuild_shims();
2241
2437
  import { createHash } from "node:crypto";
2242
- import { promises as fs2 } from "node:fs";
2438
+ import { constants as fsConstants, promises as fs2 } from "node:fs";
2243
2439
  import path2 from "node:path";
2244
2440
  var SOURCE_RESERVATIONS = {
2245
2441
  tool: 100,
@@ -2248,6 +2444,18 @@ var SOURCE_RESERVATIONS = {
2248
2444
  };
2249
2445
  var WORKSPACE_STATUS_REFRESH_TTL_MS = 5e3;
2250
2446
  var WORKSPACE_STATUS_REFRESH_BATCH_SIZE = 20;
2447
+ var SNAPSHOT_AFTER_DURABLE_EVENTS = 50;
2448
+ var MAX_SNAPSHOT_BACKOFF_MULTIPLIER = 4;
2449
+ var MAX_TOMBSTONED_IDS = 500;
2450
+ var MAX_STICKY_EPHEMERAL_IDS = 500;
2451
+ var MAX_WORKSPACE_HASH_BYTES = 100 * 1024 * 1024;
2452
+ 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;
2453
+ var RESTORE_FAILED_WARNING_PREFIX = "artifact snapshot restore failed";
2454
+ var RESTORE_PARTIAL_FAILED_WARNING_PREFIX = "artifact snapshot restore partially failed";
2455
+ function isArtifactRestoreFailureWarning(warning) {
2456
+ return warning.startsWith(RESTORE_FAILED_WARNING_PREFIX) || warning.startsWith(RESTORE_PARTIAL_FAILED_WARNING_PREFIX);
2457
+ }
2458
+ __name(isArtifactRestoreFailureWarning, "isArtifactRestoreFailureWarning");
2251
2459
  var SessionArtifactValidationError = class extends Error {
2252
2460
  constructor(message, field) {
2253
2461
  super(message);
@@ -2259,6 +2467,20 @@ var SessionArtifactValidationError = class extends Error {
2259
2467
  }
2260
2468
  code = "VALIDATION_FAILED";
2261
2469
  };
2470
+ var SessionArtifactAuthorizationError = class extends Error {
2471
+ constructor(sessionId, artifactId, ownerClientId, requesterClientId) {
2472
+ super(`artifact ${artifactId} is owned by a different client`);
2473
+ this.sessionId = sessionId;
2474
+ this.artifactId = artifactId;
2475
+ this.ownerClientId = ownerClientId;
2476
+ this.requesterClientId = requesterClientId;
2477
+ this.name = "SessionArtifactAuthorizationError";
2478
+ }
2479
+ static {
2480
+ __name(this, "SessionArtifactAuthorizationError");
2481
+ }
2482
+ code = "SESSION_ARTIFACT_FORBIDDEN";
2483
+ };
2262
2484
  var SessionArtifactStore = class {
2263
2485
  static {
2264
2486
  __name(this, "SessionArtifactStore");
@@ -2266,15 +2488,26 @@ var SessionArtifactStore = class {
2266
2488
  sessionId;
2267
2489
  workspaceCwd;
2268
2490
  maxArtifacts;
2491
+ persistence;
2269
2492
  artifacts = /* @__PURE__ */ new Map();
2270
2493
  receivedSeq = 0;
2271
2494
  insertSeq = 0;
2495
+ persistenceSeq = 0;
2496
+ durableEventsSinceSnapshot = 0;
2497
+ consecutiveSnapshotFailures = 0;
2272
2498
  realWorkspaceCwdPromise;
2273
2499
  operationQueue = Promise.resolve();
2500
+ tombstonedIds = /* @__PURE__ */ new Set();
2501
+ tombstonedClientIds = /* @__PURE__ */ new Map();
2502
+ stickyEphemeralIds = /* @__PURE__ */ new Set();
2503
+ markerArtifacts = /* @__PURE__ */ new Map();
2504
+ lastRestoreWarnings = [];
2505
+ lastRestoreWarningDetails = [];
2274
2506
  constructor(options) {
2275
2507
  this.sessionId = options.sessionId;
2276
2508
  this.workspaceCwd = options.workspaceCwd;
2277
2509
  this.maxArtifacts = options.maxArtifacts ?? 200;
2510
+ this.persistence = options.persistence;
2278
2511
  }
2279
2512
  inputBatchLimit() {
2280
2513
  return this.maxArtifacts * 2;
@@ -2287,13 +2520,30 @@ var SessionArtifactStore = class {
2287
2520
  sessionId: this.sessionId,
2288
2521
  artifacts: Array.from(this.artifacts.values()).sort((a, b) => a.insertSeq - b.insertSeq).map(toPublicArtifact),
2289
2522
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2290
- limits: { maxArtifacts: this.maxArtifacts }
2523
+ limits: { maxArtifacts: this.maxArtifacts },
2524
+ ...this.lastRestoreWarnings.length > 0 ? { warnings: [...this.lastRestoreWarnings] } : {},
2525
+ ...this.lastRestoreWarningDetails.length > 0 ? { warningDetails: [...this.lastRestoreWarningDetails] } : {}
2291
2526
  };
2292
2527
  });
2293
2528
  }
2529
+ async get(artifactId) {
2530
+ return this.enqueue(async () => {
2531
+ const artifact = this.artifacts.get(artifactId);
2532
+ if (!artifact) return void 0;
2533
+ if (artifact.workspacePath && shouldRefreshWorkspaceStatus(artifact, Date.now())) {
2534
+ await this.refreshWorkspaceStatus(artifact, { onError: "missing" });
2535
+ }
2536
+ return toPublicArtifact(artifact);
2537
+ });
2538
+ }
2294
2539
  async upsertMany(inputs, options = {}) {
2295
2540
  return this.enqueue(async () => {
2541
+ const validationStrict = options.validationStrict ?? options.strict;
2542
+ const persistenceStrict = options.persistenceStrict ?? options.strict;
2543
+ const before = this.cloneState();
2296
2544
  const normalizedResults = [];
2545
+ const warnings = [];
2546
+ const warningDetails = [];
2297
2547
  for (const input of inputs) {
2298
2548
  try {
2299
2549
  normalizedResults.push(
@@ -2304,7 +2554,7 @@ var SessionArtifactStore = class {
2304
2554
  )
2305
2555
  );
2306
2556
  } catch (error) {
2307
- if (options.strict) {
2557
+ if (validationStrict) {
2308
2558
  throw error;
2309
2559
  }
2310
2560
  const message = error instanceof Error ? error.message : String(error);
@@ -2316,39 +2566,140 @@ var SessionArtifactStore = class {
2316
2566
  }
2317
2567
  }
2318
2568
  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;
2569
+ try {
2570
+ for (const normalized of coalesceByIdentity(normalizedResults)) {
2571
+ const artifact = this.applyStickyEphemeralOverride(normalized);
2572
+ if (this.shouldSuppressTombstonedUpsert(artifact)) {
2573
+ writeStderrLine(
2574
+ `[artifacts] session=${this.sessionId} action=tombstone_replay_suppressed artifactId=${artifact.id}`
2575
+ );
2576
+ continue;
2577
+ }
2578
+ const existing = this.artifacts.get(artifact.id) ?? this.findPublishedUpgradeTarget(artifact) ?? this.findPublishedWorkspaceTarget(artifact);
2579
+ if (!existing) {
2580
+ const stored = {
2581
+ ...artifact,
2582
+ insertSeq: ++this.insertSeq
2583
+ };
2584
+ this.artifacts.set(stored.id, stored);
2585
+ changes.push({
2586
+ action: "created",
2587
+ artifactId: stored.id,
2588
+ artifact: toPublicArtifact(stored)
2589
+ });
2590
+ continue;
2591
+ }
2592
+ try {
2593
+ this.denyCrossClientMutation("upsert", existing.id, existing, {
2594
+ clientId: artifact.clientId
2595
+ });
2596
+ } catch (error) {
2597
+ if (validationStrict) {
2598
+ throw error;
2599
+ }
2600
+ if (error instanceof SessionArtifactAuthorizationError) {
2601
+ warnings.push(error.message);
2602
+ continue;
2603
+ }
2604
+ throw error;
2605
+ }
2606
+ const updated = mergeArtifact(existing, artifact);
2607
+ if (updated.changed) {
2608
+ if (updated.artifact.id !== existing.id) {
2609
+ const removeChange = {
2610
+ action: "removed",
2611
+ artifactId: existing.id,
2612
+ artifact: toPublicArtifact(existing),
2613
+ reason: "explicit",
2614
+ durableTombstoneRequired: existing.durableTombstoneRequired || existing.persistedAt !== void 0,
2615
+ removedClientId: existing.clientId
2616
+ };
2617
+ changes.push(removeChange);
2618
+ this.artifacts.delete(existing.id);
2619
+ } else if (shouldRecordEphemeralUnpin(existing, artifact)) {
2620
+ changes.push({
2621
+ action: "removed",
2622
+ artifactId: existing.id,
2623
+ artifact: toPublicArtifact(existing),
2624
+ reason: "unpin_to_ephemeral",
2625
+ durableTombstoneRequired: true
2626
+ });
2627
+ }
2628
+ this.artifacts.set(updated.artifact.id, updated.artifact);
2629
+ changes.push({
2630
+ action: "updated",
2631
+ artifactId: updated.artifact.id,
2632
+ artifact: toPublicArtifact(updated.artifact)
2633
+ });
2634
+ }
2333
2635
  }
2334
- const updated = mergeArtifact(existing, artifact);
2335
- if (updated.changed) {
2336
- if (updated.artifact.id !== existing.id) {
2337
- this.artifacts.delete(existing.id);
2636
+ const createdIds = new Set(
2637
+ changes.filter((change) => change.action === "created").map((change) => change.artifactId)
2638
+ );
2639
+ changes.push(
2640
+ ...await this.evictOverflow(createdIds, changes, persistenceStrict)
2641
+ );
2642
+ const hasStrictDurableTransition = changes.some(
2643
+ shouldCommitBeforeDurablePersistence
2644
+ );
2645
+ let persistenceWarnings;
2646
+ if (hasStrictDurableTransition && !persistenceStrict) {
2647
+ try {
2648
+ persistenceWarnings = await this.persistChanges(changes, true);
2649
+ } catch (error) {
2650
+ this.restoreState(before);
2651
+ const artifactIds = changes.filter(shouldCommitBeforeDurablePersistence).map((change) => change.artifactId);
2652
+ const warning = "artifact durable removal not persisted; live changes rolled back";
2653
+ writeStderrLine(
2654
+ `[artifacts] session=${this.sessionId} action=upsert_rollback warning=${JSON.stringify(
2655
+ warning
2656
+ )} artifactIds=${JSON.stringify(artifactIds)} error=${JSON.stringify(
2657
+ error instanceof Error ? error.message : String(error)
2658
+ )}`
2659
+ );
2660
+ return {
2661
+ v: 1,
2662
+ sessionId: this.sessionId,
2663
+ changes: [],
2664
+ warnings: [warning],
2665
+ warningDetails: [
2666
+ persistenceFailureDetail({
2667
+ message: warning,
2668
+ operation: "upsert",
2669
+ artifactIds,
2670
+ error
2671
+ })
2672
+ ]
2673
+ };
2338
2674
  }
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
- });
2675
+ } else {
2676
+ persistenceWarnings = await this.persistChanges(
2677
+ changes,
2678
+ persistenceStrict
2679
+ );
2680
+ }
2681
+ warnings.push(...persistenceWarnings);
2682
+ warningDetails.push(
2683
+ ...detailsForPersistenceWarnings(
2684
+ persistenceWarnings,
2685
+ changes,
2686
+ "upsert"
2687
+ )
2688
+ );
2689
+ stripDurableTombstoneMarkers(changes);
2690
+ } catch (error) {
2691
+ if (validationStrict || persistenceStrict || error instanceof SessionArtifactAuthorizationError) {
2692
+ this.restoreState(before);
2345
2693
  }
2694
+ throw error;
2346
2695
  }
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 };
2696
+ return {
2697
+ v: 1,
2698
+ sessionId: this.sessionId,
2699
+ changes,
2700
+ ...warnings.length > 0 ? { warnings } : {},
2701
+ ...warningDetails.length > 0 ? { warningDetails } : {}
2702
+ };
2352
2703
  });
2353
2704
  }
2354
2705
  findPublishedUpgradeTarget(artifact) {
@@ -2356,7 +2707,7 @@ var SessionArtifactStore = class {
2356
2707
  return void 0;
2357
2708
  }
2358
2709
  const byUrl = this.artifacts.get(
2359
- stableArtifactId(this.sessionId, `url:${artifact.url}`)
2710
+ stableSessionArtifactId(this.sessionId, `url:${artifact.url}`)
2360
2711
  );
2361
2712
  if (byUrl && (byUrl.storage !== "published" || byUrl.managedId === artifact.managedId)) {
2362
2713
  return byUrl;
@@ -2392,93 +2743,648 @@ var SessionArtifactStore = class {
2392
2743
  if (!existing) {
2393
2744
  return { v: 1, sessionId: this.sessionId, changes: [] };
2394
2745
  }
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: [] };
2746
+ this.denyCrossClientMutation("remove", artifactId, existing, options);
2747
+ const removeChange = {
2748
+ action: "removed",
2749
+ artifactId,
2750
+ artifact: toPublicArtifact(existing),
2751
+ reason: "explicit",
2752
+ durableTombstoneRequired: existing.durableTombstoneRequired || existing.retention !== "ephemeral" ? true : void 0,
2753
+ removedClientId: existing.clientId
2754
+ };
2755
+ const changes = [removeChange];
2756
+ const needsDurableTombstone = removeChange.durableTombstoneRequired === true;
2757
+ if (needsDurableTombstone) {
2758
+ const before = this.cloneState();
2759
+ this.artifacts.delete(artifactId);
2760
+ try {
2761
+ await this.persistChanges(changes, true);
2762
+ } catch (error) {
2763
+ this.restoreState(before);
2764
+ const warning = "artifact removal not persisted; live artifact kept";
2765
+ return {
2766
+ v: 1,
2767
+ sessionId: this.sessionId,
2768
+ changes: [],
2769
+ warnings: [warning],
2770
+ warningDetails: [
2771
+ persistenceFailureDetail({
2772
+ message: warning,
2773
+ operation: "remove",
2774
+ artifactIds: [artifactId],
2775
+ error
2776
+ })
2777
+ ]
2778
+ };
2779
+ }
2780
+ }
2781
+ if (!needsDurableTombstone) {
2782
+ this.artifacts.delete(artifactId);
2400
2783
  }
2401
- this.artifacts.delete(artifactId);
2784
+ const warnings = needsDurableTombstone ? [] : await this.persistChanges(changes, false);
2785
+ stripDurableTombstoneMarkers(changes);
2786
+ const warningDetails = detailsForPersistenceWarnings(
2787
+ warnings,
2788
+ changes,
2789
+ "remove"
2790
+ );
2402
2791
  return {
2403
2792
  v: 1,
2404
2793
  sessionId: this.sessionId,
2405
- changes: [
2406
- {
2407
- action: "removed",
2408
- artifactId,
2409
- artifact: toPublicArtifact(existing),
2410
- reason: "explicit"
2411
- }
2412
- ]
2794
+ changes,
2795
+ ...warnings.length > 0 ? { warnings } : {},
2796
+ ...warningDetails.length > 0 ? { warningDetails } : {}
2413
2797
  };
2414
2798
  });
2415
2799
  }
2416
- enqueue(operation) {
2417
- const result = this.operationQueue.then(operation, operation);
2418
- this.operationQueue = result.then(
2419
- () => void 0,
2420
- () => void 0
2421
- );
2422
- return result;
2800
+ async restore(snapshot, options = {}) {
2801
+ if (!snapshot) return [];
2802
+ return this.enqueue(async () => {
2803
+ const warnings = [...snapshot?.warnings ?? []];
2804
+ const baselineWarnings = [...warnings];
2805
+ const previousState = this.cloneState();
2806
+ const preservedLiveEphemeralArtifacts = options.preserveLiveEphemeral ? Array.from(this.artifacts.values()).filter((artifact) => artifact.retention === "ephemeral").map(cloneStoredArtifact) : [];
2807
+ let restoredCount = 0;
2808
+ this.artifacts.clear();
2809
+ this.tombstonedIds.clear();
2810
+ this.tombstonedClientIds.clear();
2811
+ this.stickyEphemeralIds.clear();
2812
+ this.markerArtifacts.clear();
2813
+ if (snapshot) {
2814
+ this.insertSeq = 0;
2815
+ this.persistenceSeq = snapshot.sequence;
2816
+ this.durableEventsSinceSnapshot = 0;
2817
+ this.consecutiveSnapshotFailures = 0;
2818
+ for (const id of snapshot.tombstonedIds) {
2819
+ this.tombstonedIds.add(id);
2820
+ }
2821
+ for (const id of snapshot.stickyEphemeralIds.slice(
2822
+ -MAX_STICKY_EPHEMERAL_IDS
2823
+ )) {
2824
+ this.stickyEphemeralIds.add(id);
2825
+ }
2826
+ const markerIds = /* @__PURE__ */ new Set([
2827
+ ...this.tombstonedIds,
2828
+ ...this.stickyEphemeralIds
2829
+ ]);
2830
+ for (const artifact of snapshot.markerArtifacts ?? []) {
2831
+ if (!markerIds.has(artifact.id)) continue;
2832
+ const markerArtifact = await this.normalizeRestoredMarkerArtifact(
2833
+ artifact,
2834
+ warnings
2835
+ );
2836
+ if (markerArtifact)
2837
+ this.markerArtifacts.set(artifact.id, markerArtifact);
2838
+ }
2839
+ }
2840
+ for (const artifact of snapshot?.artifacts ?? []) {
2841
+ try {
2842
+ const input = persistedArtifactToInput(artifact);
2843
+ if (input.retention === "pinned") {
2844
+ input.retention = "restorable";
2845
+ warnings.push(
2846
+ `pinned artifact ${artifact.id} downgraded to restorable; runtime does not support pinned retention`
2847
+ );
2848
+ }
2849
+ let normalized = await this.normalizeInput(
2850
+ input,
2851
+ ++this.receivedSeq,
2852
+ artifact.storage === "published" && !isFileArtifactUrl(artifact.url),
2853
+ {
2854
+ metadataBudget: "persisted",
2855
+ workspaceExpected: workspaceExpectedFromArtifact(artifact),
2856
+ hashWorkspaceContent: false
2857
+ }
2858
+ );
2859
+ if (this.stickyEphemeralIds.has(normalized.id) && normalized.retention !== "ephemeral") {
2860
+ normalized = {
2861
+ ...normalized,
2862
+ retention: "ephemeral",
2863
+ persistenceWarning: "sticky_override_active"
2864
+ };
2865
+ }
2866
+ const stickyApplied = normalized.persistenceWarning === "sticky_override_active";
2867
+ if (normalized.id !== artifact.id) {
2868
+ warnings.push(`skipped artifact with mismatched id ${artifact.id}`);
2869
+ continue;
2870
+ }
2871
+ const retention = normalized.retention;
2872
+ let persistenceWarning;
2873
+ if (stickyApplied) {
2874
+ persistenceWarning = "sticky_override_active";
2875
+ } else if (normalized.status !== "available") {
2876
+ persistenceWarning = "metadata_only_restore";
2877
+ }
2878
+ const stored = {
2879
+ ...normalized,
2880
+ retention,
2881
+ clientRetained: artifact.clientRetained,
2882
+ createdAt: artifact.createdAt,
2883
+ updatedAt: artifact.updatedAt,
2884
+ persistedAt: artifact.persistedAt,
2885
+ status: normalized.status,
2886
+ restoreState: "restored",
2887
+ persistenceWarning,
2888
+ durableTombstoneRequired: retention !== "ephemeral" ? true : void 0,
2889
+ insertSeq: ++this.insertSeq
2890
+ };
2891
+ this.artifacts.set(stored.id, stored);
2892
+ restoredCount++;
2893
+ } catch (error) {
2894
+ warnings.push(
2895
+ `skipped artifact restore: ${error instanceof Error ? error.message : String(error)}`
2896
+ );
2897
+ }
2898
+ }
2899
+ if (snapshot.artifacts.length > 0 && restoredCount === 0) {
2900
+ this.restoreState(previousState);
2901
+ const rollbackWarnings = [
2902
+ ...baselineWarnings,
2903
+ `${RESTORE_FAILED_WARNING_PREFIX}; kept existing live artifacts`
2904
+ ];
2905
+ this.setLastRestoreWarnings(rollbackWarnings);
2906
+ return rollbackWarnings;
2907
+ }
2908
+ if (previousState.artifacts.size > 0 && baselineWarnings.some(isArtifactSnapshotCompletenessWarning)) {
2909
+ this.restoreState(previousState);
2910
+ const rollbackWarnings = [
2911
+ ...baselineWarnings,
2912
+ restoredCount === 0 ? `${RESTORE_FAILED_WARNING_PREFIX}; kept existing live artifacts` : `${RESTORE_PARTIAL_FAILED_WARNING_PREFIX}; kept existing live artifacts`
2913
+ ];
2914
+ this.setLastRestoreWarnings(rollbackWarnings);
2915
+ return rollbackWarnings;
2916
+ }
2917
+ for (const artifact of preservedLiveEphemeralArtifacts) {
2918
+ if (this.artifacts.has(artifact.id) || this.tombstonedIds.has(artifact.id)) {
2919
+ continue;
2920
+ }
2921
+ this.artifacts.set(artifact.id, {
2922
+ ...artifact,
2923
+ insertSeq: ++this.insertSeq
2924
+ });
2925
+ }
2926
+ const evicted = await this.evictOverflow(/* @__PURE__ */ new Set(), []);
2927
+ if (evicted.length > 0) {
2928
+ warnings.push("restored artifact list pruned to live limit");
2929
+ warnings.push(...await this.persistChanges(evicted, false));
2930
+ }
2931
+ this.setLastRestoreWarnings(warnings);
2932
+ return warnings;
2933
+ });
2423
2934
  }
2424
- async normalizeInput(input, receivedSeq, trustedPublisherFromCaller) {
2425
- if (!input || typeof input !== "object") {
2426
- throw new SessionArtifactValidationError("Artifact must be an object");
2427
- }
2428
- const title = normalizeString(input.title, "title", 200, true);
2429
- const description = normalizeString(
2430
- input.description,
2431
- "description",
2432
- 1e3,
2433
- false
2434
- );
2435
- const source = input.source ?? "tool";
2436
- if (source !== "tool" && source !== "hook" && source !== "client") {
2437
- throw new SessionArtifactValidationError(
2438
- "source must be tool, hook, or client",
2439
- "source"
2440
- );
2935
+ async recordSnapshot() {
2936
+ const persistence = this.persistence;
2937
+ if (!persistence) {
2938
+ return [
2939
+ "artifact persistence unavailable; restored artifacts not snapshotted"
2940
+ ];
2441
2941
  }
2442
- const trustedPublisher = trustedPublisherFromCaller;
2443
- const workspacePath = input.workspacePath ? normalizeWorkspacePath(input.workspacePath, this.workspaceCwd) : void 0;
2444
- const managedId = normalizeManagedId(input.managedId);
2445
- const rawStorage = input.storage;
2446
- const storage = inferStorage(rawStorage, {
2447
- workspacePath,
2448
- managedId,
2449
- url: input.url,
2450
- trustedPublisher
2451
- });
2452
- const url = input.url ? normalizeArtifactUrl(input.url, trustedPublisher) : void 0;
2453
- validateLocator(storage, {
2454
- workspacePath,
2455
- managedId,
2456
- url,
2457
- trustedPublisher
2942
+ return this.enqueue(async () => {
2943
+ const recordedAt = (/* @__PURE__ */ new Date()).toISOString();
2944
+ const sequence = this.persistenceSeq + 1;
2945
+ try {
2946
+ await persistence.recordSnapshot(
2947
+ this.buildSnapshotPayload(recordedAt, sequence)
2948
+ );
2949
+ this.persistenceSeq = sequence;
2950
+ this.durableEventsSinceSnapshot = 0;
2951
+ this.consecutiveSnapshotFailures = 0;
2952
+ return [];
2953
+ } catch (error) {
2954
+ writeStderrLine(
2955
+ `[artifacts] session=${this.sessionId} action=snapshot_failed reason=${JSON.stringify(
2956
+ error instanceof Error ? error.message : String(error)
2957
+ )}`
2958
+ );
2959
+ return ["artifact snapshot not persisted"];
2960
+ }
2458
2961
  });
2459
- const metadata = normalizeMetadata(input.metadata);
2460
- const workspaceStatus = workspacePath ? await this.getInitialWorkspaceStatus(workspacePath) : void 0;
2461
- if (workspaceStatus?.escaped) {
2462
- throw new SessionArtifactValidationError(
2463
- "workspacePath must stay inside the workspace",
2464
- "workspacePath"
2962
+ }
2963
+ async normalizeRestoredMarkerArtifact(artifact, warnings) {
2964
+ try {
2965
+ const input = persistedArtifactToInput(artifact);
2966
+ if (input.retention === "pinned") {
2967
+ input.retention = "restorable";
2968
+ warnings.push(
2969
+ `pinned marker artifact ${artifact.id} downgraded to restorable; runtime does not support pinned retention`
2970
+ );
2971
+ }
2972
+ const normalized = await this.normalizeInput(
2973
+ input,
2974
+ ++this.receivedSeq,
2975
+ artifact.storage === "published" && !isFileArtifactUrl(artifact.url),
2976
+ {
2977
+ metadataBudget: "persisted",
2978
+ workspaceExpected: workspaceExpectedFromArtifact(artifact),
2979
+ hashWorkspaceContent: false
2980
+ }
2981
+ );
2982
+ if (normalized.id !== artifact.id) {
2983
+ warnings.push(
2984
+ `skipped marker artifact with mismatched id ${artifact.id}`
2985
+ );
2986
+ return void 0;
2987
+ }
2988
+ return toPersistedArtifact(
2989
+ {
2990
+ ...normalized,
2991
+ clientRetained: artifact.clientRetained,
2992
+ createdAt: artifact.createdAt,
2993
+ updatedAt: artifact.updatedAt,
2994
+ persistedAt: artifact.persistedAt
2995
+ },
2996
+ artifact.persistedAt ?? artifact.updatedAt
2997
+ );
2998
+ } catch (error) {
2999
+ warnings.push(
3000
+ `skipped marker artifact ${artifact.id}: ${error instanceof Error ? error.message : String(error)}`
2465
3001
  );
3002
+ return void 0;
2466
3003
  }
2467
- const kind = normalizeKind(
2468
- input.kind ?? inferKind({ storage, workspacePath, url })
2469
- );
2470
- const now = (/* @__PURE__ */ new Date()).toISOString();
2471
- const identityKey = buildIdentityKey({
2472
- storage,
2473
- workspacePath,
2474
- managedId,
2475
- url
2476
- });
2477
- const id = stableArtifactId(this.sessionId, identityKey);
3004
+ }
3005
+ cloneState() {
3006
+ return {
3007
+ artifacts: new Map(
3008
+ Array.from(this.artifacts.entries()).map(([id, artifact]) => [
3009
+ id,
3010
+ cloneStoredArtifact(artifact)
3011
+ ])
3012
+ ),
3013
+ receivedSeq: this.receivedSeq,
3014
+ insertSeq: this.insertSeq,
3015
+ persistenceSeq: this.persistenceSeq,
3016
+ durableEventsSinceSnapshot: this.durableEventsSinceSnapshot,
3017
+ consecutiveSnapshotFailures: this.consecutiveSnapshotFailures,
3018
+ tombstonedIds: new Set(this.tombstonedIds),
3019
+ tombstonedClientIds: new Map(this.tombstonedClientIds),
3020
+ stickyEphemeralIds: new Set(this.stickyEphemeralIds),
3021
+ markerArtifacts: new Map(this.markerArtifacts),
3022
+ lastRestoreWarnings: [...this.lastRestoreWarnings],
3023
+ lastRestoreWarningDetails: [...this.lastRestoreWarningDetails]
3024
+ };
3025
+ }
3026
+ restoreState(state) {
3027
+ this.artifacts.clear();
3028
+ for (const [id, artifact] of state.artifacts) {
3029
+ this.artifacts.set(id, cloneStoredArtifact(artifact));
3030
+ }
3031
+ this.receivedSeq = state.receivedSeq;
3032
+ this.insertSeq = state.insertSeq;
3033
+ this.persistenceSeq = state.persistenceSeq;
3034
+ this.durableEventsSinceSnapshot = state.durableEventsSinceSnapshot;
3035
+ this.consecutiveSnapshotFailures = state.consecutiveSnapshotFailures;
3036
+ this.tombstonedIds.clear();
3037
+ for (const id of state.tombstonedIds) {
3038
+ this.tombstonedIds.add(id);
3039
+ }
3040
+ this.tombstonedClientIds.clear();
3041
+ for (const [id, clientId] of state.tombstonedClientIds) {
3042
+ this.tombstonedClientIds.set(id, clientId);
3043
+ }
3044
+ this.stickyEphemeralIds.clear();
3045
+ for (const id of state.stickyEphemeralIds) {
3046
+ this.stickyEphemeralIds.add(id);
3047
+ }
3048
+ this.markerArtifacts.clear();
3049
+ for (const [id, artifact] of state.markerArtifacts) {
3050
+ this.markerArtifacts.set(id, artifact);
3051
+ }
3052
+ this.setLastRestoreWarnings(state.lastRestoreWarnings);
3053
+ this.lastRestoreWarningDetails = [...state.lastRestoreWarningDetails];
3054
+ }
3055
+ async persistChanges(changes, strict = false) {
3056
+ const durableChanges = changes.filter(isDurablePersistenceChange);
3057
+ if (durableChanges.length === 0) {
3058
+ return [];
3059
+ }
3060
+ if (!this.persistence) {
3061
+ if (strict) {
3062
+ throw new SessionArtifactValidationError(
3063
+ "artifact persistence is unavailable",
3064
+ "retention"
3065
+ );
3066
+ }
3067
+ const warnings = this.downgradeDurableChanges(durableChanges);
3068
+ this.applyDurableMarkers(durableChanges);
3069
+ return warnings;
3070
+ }
3071
+ const recordedAt = (/* @__PURE__ */ new Date()).toISOString();
3072
+ for (const change of durableChanges) {
3073
+ if (change.action === "removed") continue;
3074
+ const stored = this.artifacts.get(change.artifactId);
3075
+ if (!stored) continue;
3076
+ const artifact = {
3077
+ ...toPublicArtifact(stored),
3078
+ persistedAt: recordedAt
3079
+ };
3080
+ change.artifact = artifact;
3081
+ }
3082
+ const sequence = this.persistenceSeq + 1;
3083
+ const payload = {
3084
+ v: SESSION_ARTIFACT_PERSISTENCE_VERSION,
3085
+ sessionId: this.sessionId,
3086
+ sequence,
3087
+ recordedAt,
3088
+ changes: durableChanges.map(
3089
+ (change) => toPersistedChange(change, recordedAt)
3090
+ )
3091
+ };
3092
+ try {
3093
+ await this.persistence.recordEvent(payload);
3094
+ this.persistenceSeq = sequence;
3095
+ for (const change of durableChanges) {
3096
+ if (change.action === "removed") continue;
3097
+ const stored = this.artifacts.get(change.artifactId);
3098
+ if (!stored) continue;
3099
+ stored.persistedAt = recordedAt;
3100
+ stored.durableTombstoneRequired = true;
3101
+ change.artifact = toPublicArtifact(stored);
3102
+ }
3103
+ this.applyDurableMarkers(durableChanges);
3104
+ await this.maybeRecordSnapshot(recordedAt);
3105
+ return [];
3106
+ } catch (error) {
3107
+ if (strict) {
3108
+ throw error;
3109
+ }
3110
+ const reason = error instanceof Error ? error.message : String(error);
3111
+ const artifactIds = durableChanges.map((change) => change.artifactId);
3112
+ writeStderrLine(
3113
+ `[artifacts] session=${this.sessionId} action=persist_failed sequence=${payload.sequence} artifactIds=${JSON.stringify(
3114
+ artifactIds
3115
+ )} reason=${JSON.stringify(reason)}`
3116
+ );
3117
+ const warnings = this.downgradeDurableChanges(durableChanges);
3118
+ this.applyDurableMarkers(durableChanges);
3119
+ return warnings;
3120
+ }
3121
+ }
3122
+ async maybeRecordSnapshot(recordedAt) {
3123
+ if (!this.persistence) return;
3124
+ this.durableEventsSinceSnapshot++;
3125
+ const snapshotThreshold = SNAPSHOT_AFTER_DURABLE_EVENTS * Math.min(
3126
+ MAX_SNAPSHOT_BACKOFF_MULTIPLIER,
3127
+ 2 ** this.consecutiveSnapshotFailures
3128
+ );
3129
+ if (this.durableEventsSinceSnapshot < snapshotThreshold) {
3130
+ return;
3131
+ }
3132
+ const sequence = this.persistenceSeq + 1;
3133
+ try {
3134
+ await this.persistence.recordSnapshot(
3135
+ this.buildSnapshotPayload(recordedAt, sequence)
3136
+ );
3137
+ this.persistenceSeq = sequence;
3138
+ this.durableEventsSinceSnapshot = 0;
3139
+ this.consecutiveSnapshotFailures = 0;
3140
+ } catch (error) {
3141
+ this.consecutiveSnapshotFailures = Math.min(
3142
+ this.consecutiveSnapshotFailures + 1,
3143
+ Math.log2(MAX_SNAPSHOT_BACKOFF_MULTIPLIER)
3144
+ );
3145
+ writeStderrLine(
3146
+ `[artifacts] session=${this.sessionId} action=snapshot_failed reason=${JSON.stringify(
3147
+ error instanceof Error ? error.message : String(error)
3148
+ )}`
3149
+ );
3150
+ }
3151
+ }
3152
+ buildSnapshotPayload(recordedAt, sequence) {
3153
+ const artifacts = Array.from(this.artifacts.values()).filter((artifact) => artifact.retention !== "ephemeral").sort((a, b) => a.insertSeq - b.insertSeq).map((artifact) => toPersistedArtifact(artifact, recordedAt));
3154
+ const stickyEphemeralIds = Array.from(this.stickyEphemeralIds).filter(
3155
+ (id) => this.artifacts.has(id) || this.markerArtifacts.has(id)
3156
+ );
3157
+ const markerArtifacts = this.buildMarkerArtifacts(
3158
+ recordedAt,
3159
+ stickyEphemeralIds
3160
+ );
3161
+ return {
3162
+ v: SESSION_ARTIFACT_PERSISTENCE_VERSION,
3163
+ sessionId: this.sessionId,
3164
+ sequence,
3165
+ recordedAt,
3166
+ artifacts,
3167
+ tombstonedIds: Array.from(this.tombstonedIds),
3168
+ stickyEphemeralIds,
3169
+ ...markerArtifacts.length > 0 ? { markerArtifacts } : {}
3170
+ };
3171
+ }
3172
+ buildMarkerArtifacts(recordedAt, stickyEphemeralIds) {
3173
+ const markerIds = [...this.tombstonedIds, ...stickyEphemeralIds];
3174
+ const artifacts = [];
3175
+ const seen = /* @__PURE__ */ new Set();
3176
+ for (const id of markerIds) {
3177
+ if (seen.has(id)) continue;
3178
+ seen.add(id);
3179
+ const live = this.artifacts.get(id);
3180
+ if (live) {
3181
+ artifacts.push(toPersistedArtifact(toPublicArtifact(live), recordedAt));
3182
+ continue;
3183
+ }
3184
+ const markerArtifact = this.markerArtifacts.get(id);
3185
+ if (markerArtifact) {
3186
+ artifacts.push(markerArtifact);
3187
+ }
3188
+ }
3189
+ return artifacts;
3190
+ }
3191
+ downgradeDurableChanges(changes) {
3192
+ let downgraded = false;
3193
+ let removalNotPersisted = false;
3194
+ for (const change of changes) {
3195
+ if (change.action === "removed") {
3196
+ removalNotPersisted = true;
3197
+ if (change.reason === "explicit") {
3198
+ this.rememberTombstone(change);
3199
+ this.stickyEphemeralIds.delete(change.artifactId);
3200
+ }
3201
+ continue;
3202
+ }
3203
+ const stored = this.artifacts.get(change.artifactId);
3204
+ if (!stored) continue;
3205
+ stored.retention = "ephemeral";
3206
+ stored.persistenceWarning = "persistence_unavailable";
3207
+ stored.durableTombstoneRequired = stored.durableTombstoneRequired || stored.persistedAt !== void 0;
3208
+ delete stored.persistedAt;
3209
+ change.artifact = toPublicArtifact(stored);
3210
+ downgraded = true;
3211
+ }
3212
+ const warnings = [];
3213
+ if (downgraded) {
3214
+ warnings.push(
3215
+ "artifact persistence unavailable; durable artifacts kept ephemeral"
3216
+ );
3217
+ }
3218
+ if (removalNotPersisted) {
3219
+ warnings.push("artifact removal not persisted; live removal kept");
3220
+ }
3221
+ return warnings;
3222
+ }
3223
+ applyDurableMarkers(changes) {
3224
+ for (const change of changes) {
3225
+ if (change.action === "removed") {
3226
+ if (change.reason === "explicit") {
3227
+ this.rememberTombstone(change);
3228
+ this.stickyEphemeralIds.delete(change.artifactId);
3229
+ } else if (change.reason === "eviction") {
3230
+ this.stickyEphemeralIds.delete(change.artifactId);
3231
+ this.markerArtifacts.delete(change.artifactId);
3232
+ } else if (change.reason === "unpin_to_ephemeral") {
3233
+ this.rememberStickyEphemeral(change);
3234
+ }
3235
+ continue;
3236
+ }
3237
+ if (change.artifact && change.artifact.retention !== "ephemeral") {
3238
+ this.tombstonedIds.delete(change.artifactId);
3239
+ this.tombstonedClientIds.delete(change.artifactId);
3240
+ this.stickyEphemeralIds.delete(change.artifactId);
3241
+ this.markerArtifacts.delete(change.artifactId);
3242
+ }
3243
+ }
3244
+ }
3245
+ rememberStickyEphemeral(change) {
3246
+ const artifactId = change.artifactId;
3247
+ this.stickyEphemeralIds.delete(artifactId);
3248
+ this.stickyEphemeralIds.add(artifactId);
3249
+ if (change.artifact) {
3250
+ this.markerArtifacts.set(
3251
+ artifactId,
3252
+ toPersistedArtifact(change.artifact, change.artifact.updatedAt)
3253
+ );
3254
+ }
3255
+ while (this.stickyEphemeralIds.size > MAX_STICKY_EPHEMERAL_IDS) {
3256
+ const oldest2 = this.stickyEphemeralIds.values().next().value;
3257
+ if (oldest2 === void 0) break;
3258
+ this.stickyEphemeralIds.delete(oldest2);
3259
+ this.markerArtifacts.delete(oldest2);
3260
+ }
3261
+ }
3262
+ rememberTombstone(change) {
3263
+ this.tombstonedIds.delete(change.artifactId);
3264
+ this.tombstonedIds.add(change.artifactId);
3265
+ this.tombstonedClientIds.set(
3266
+ change.artifactId,
3267
+ change.removedClientId ?? this.artifacts.get(change.artifactId)?.clientId
3268
+ );
3269
+ if (change.artifact) {
3270
+ this.markerArtifacts.set(
3271
+ change.artifactId,
3272
+ toPersistedArtifact(change.artifact, change.artifact.updatedAt)
3273
+ );
3274
+ }
3275
+ while (this.tombstonedIds.size > MAX_TOMBSTONED_IDS) {
3276
+ const oldest2 = this.tombstonedIds.values().next().value;
3277
+ if (oldest2 === void 0) break;
3278
+ writeStderrLine(
3279
+ `[artifacts] session=${this.sessionId} action=tombstone_evicted artifactId=${JSON.stringify(
3280
+ oldest2
3281
+ )} limit=${MAX_TOMBSTONED_IDS}`
3282
+ );
3283
+ this.tombstonedIds.delete(oldest2);
3284
+ this.tombstonedClientIds.delete(oldest2);
3285
+ this.markerArtifacts.delete(oldest2);
3286
+ }
3287
+ }
3288
+ setLastRestoreWarnings(warnings) {
3289
+ this.lastRestoreWarnings = [...warnings];
3290
+ this.lastRestoreWarningDetails = detailsForRestoreWarnings(warnings);
3291
+ }
3292
+ enqueue(operation) {
3293
+ const result = this.operationQueue.then(operation, operation);
3294
+ this.operationQueue = result.then(
3295
+ () => void 0,
3296
+ () => void 0
3297
+ );
3298
+ return result;
3299
+ }
3300
+ denyCrossClientMutation(action, artifactId, existing, options) {
3301
+ if (existing.source !== "client" || existing.clientId === void 0 || existing.clientId === options?.clientId) {
3302
+ return;
3303
+ }
3304
+ writeStderrLine(
3305
+ `[artifacts] session=${this.sessionId} action=${action}_denied artifactId=${artifactId} owner=${existing.clientId} requester=${options?.clientId ?? "<anonymous>"}`
3306
+ );
3307
+ throw new SessionArtifactAuthorizationError(
3308
+ this.sessionId,
3309
+ artifactId,
3310
+ existing.clientId,
3311
+ options?.clientId
3312
+ );
3313
+ }
3314
+ async normalizeInput(input, receivedSeq, trustedPublisherFromCaller, options = {}) {
3315
+ if (!input || typeof input !== "object") {
3316
+ throw new SessionArtifactValidationError("Artifact must be an object");
3317
+ }
3318
+ const title = normalizeString(input.title, "title", 200, true);
3319
+ const description = normalizeString(
3320
+ input.description,
3321
+ "description",
3322
+ 1e3,
3323
+ false
3324
+ );
3325
+ const source = input.source ?? "tool";
3326
+ if (source !== "tool" && source !== "hook" && source !== "client") {
3327
+ throw new SessionArtifactValidationError(
3328
+ "source must be tool, hook, or client",
3329
+ "source"
3330
+ );
3331
+ }
3332
+ const trustedPublisher = trustedPublisherFromCaller;
3333
+ const workspacePath = input.workspacePath ? normalizeWorkspacePath(
3334
+ input.workspacePath,
3335
+ await this.getRealWorkspaceCwdForValidation()
3336
+ ) : void 0;
3337
+ const managedId = normalizeManagedId(input.managedId);
3338
+ const rawStorage = input.storage;
3339
+ const storage = inferStorage(rawStorage, {
3340
+ workspacePath,
3341
+ managedId,
3342
+ url: input.url,
3343
+ trustedPublisher
3344
+ });
3345
+ const url = input.url ? normalizeArtifactUrl(input.url, trustedPublisher) : void 0;
3346
+ validateLocator(storage, {
3347
+ workspacePath,
3348
+ managedId,
3349
+ url,
3350
+ trustedPublisher
3351
+ });
3352
+ const retention = normalizeRetention(input.retention, {
3353
+ persistenceAvailable: this.persistence !== void 0
3354
+ });
3355
+ const workspaceStatus = workspacePath ? await this.getInitialWorkspaceStatus(
3356
+ workspacePath,
3357
+ options.workspaceExpected,
3358
+ { hashContent: options.hashWorkspaceContent !== false }
3359
+ ) : void 0;
3360
+ if (workspaceStatus?.escaped) {
3361
+ throw new SessionArtifactValidationError(
3362
+ "workspacePath must stay inside the workspace",
3363
+ "workspacePath"
3364
+ );
3365
+ }
3366
+ const metadata = withWorkspaceContentHashMetadata(
3367
+ normalizeMetadata(input.metadata, {
3368
+ budget: options.metadataBudget ?? "user"
3369
+ }),
3370
+ workspaceStatus
3371
+ );
3372
+ const kind = normalizeKind(
3373
+ input.kind ?? inferKind({ storage, workspacePath, url })
3374
+ );
3375
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3376
+ const identityKey = buildIdentityKey({
3377
+ storage,
3378
+ workspacePath,
3379
+ managedId,
3380
+ url
3381
+ });
3382
+ const id = stableSessionArtifactId(this.sessionId, identityKey);
2478
3383
  return {
2479
3384
  id,
2480
3385
  identityKey,
2481
3386
  receivedSeq,
3387
+ retentionExplicit: input.retention !== void 0,
2482
3388
  retentionSource: source,
2483
3389
  trustedPublisher,
2484
3390
  kind,
@@ -2492,9 +3398,12 @@ var SessionArtifactStore = class {
2492
3398
  managedId,
2493
3399
  url,
2494
3400
  mimeType: normalizeString(input.mimeType, "mimeType", 120, false),
2495
- sizeBytes: input.sizeBytes !== void 0 ? normalizeSizeBytes(input.sizeBytes) : workspaceStatus?.sizeBytes,
3401
+ sizeBytes: workspaceStatus?.sizeBytes !== void 0 ? workspaceStatus.sizeBytes : input.sizeBytes !== void 0 ? normalizeSizeBytes(input.sizeBytes) : void 0,
2496
3402
  metadata,
2497
- clientRetained: source === "client",
3403
+ retention,
3404
+ restoreState: "live",
3405
+ ...this.persistence === void 0 && retention !== "ephemeral" ? { persistenceWarning: "persistence_unavailable" } : {},
3406
+ clientRetained: source === "client" && (input.clientRetained !== void 0 ? input.clientRetained === true : true),
2498
3407
  createdAt: now,
2499
3408
  updatedAt: now,
2500
3409
  toolCallId: normalizeString(input.toolCallId, "toolCallId", 200, false),
@@ -2508,6 +3417,16 @@ var SessionArtifactStore = class {
2508
3417
  clientId: normalizeString(input.clientId, "clientId", 200, false)
2509
3418
  };
2510
3419
  }
3420
+ shouldSuppressTombstonedUpsert(artifact) {
3421
+ if (!this.tombstonedIds.has(artifact.id)) {
3422
+ return false;
3423
+ }
3424
+ const tombstonedClientId = this.tombstonedClientIds.get(artifact.id);
3425
+ if (artifact.source !== "client") {
3426
+ return false;
3427
+ }
3428
+ return !(artifact.retentionExplicit && artifact.clientId !== void 0 && artifact.clientId === tombstonedClientId);
3429
+ }
2511
3430
  async refreshWorkspaceStatuses() {
2512
3431
  const now = Date.now();
2513
3432
  const staleWorkspaceArtifacts = Array.from(this.artifacts.values()).filter((artifact) => artifact.workspacePath).filter((artifact) => shouldRefreshWorkspaceStatus(artifact, now));
@@ -2520,12 +3439,24 @@ var SessionArtifactStore = class {
2520
3439
  })
2521
3440
  );
2522
3441
  }
2523
- async getInitialWorkspaceStatus(workspacePath) {
3442
+ applyStickyEphemeralOverride(artifact) {
3443
+ if (!this.stickyEphemeralIds.has(artifact.id) || artifact.retentionExplicit || artifact.retention === "ephemeral") {
3444
+ return artifact;
3445
+ }
3446
+ return {
3447
+ ...artifact,
3448
+ retention: "ephemeral",
3449
+ retentionExplicit: true,
3450
+ persistenceWarning: "sticky_override_active"
3451
+ };
3452
+ }
3453
+ async getInitialWorkspaceStatus(workspacePath, expected, options = { hashContent: true }) {
2524
3454
  try {
2525
3455
  return await getWorkspaceStatus(
2526
- this.workspaceCwd,
2527
3456
  workspacePath,
2528
- this.getRealWorkspaceCwd()
3457
+ this.getRealWorkspaceCwd(),
3458
+ expected,
3459
+ options
2529
3460
  );
2530
3461
  } catch (error) {
2531
3462
  const reason = error instanceof Error ? error.message : String(error);
@@ -2541,16 +3472,23 @@ var SessionArtifactStore = class {
2541
3472
  }
2542
3473
  try {
2543
3474
  const status = await getWorkspaceStatus(
2544
- this.workspaceCwd,
2545
3475
  artifact.workspacePath,
2546
- this.getRealWorkspaceCwd()
3476
+ this.getRealWorkspaceCwd(),
3477
+ {
3478
+ sizeBytes: artifact.sizeBytes,
3479
+ mtimeMs: artifact.metadata?.[WORKSPACE_CONTENT_MTIME_MS_METADATA_KEY],
3480
+ sha256: artifact.metadata?.[WORKSPACE_CONTENT_SHA256_METADATA_KEY]
3481
+ }
2547
3482
  );
2548
- artifact.status = status.status;
2549
- artifact.sizeBytes = status.sizeBytes;
3483
+ const changed = isWorkspaceContentChanged(artifact, status);
3484
+ artifact.status = changed ? "changed" : status.status;
3485
+ if (!changed) {
3486
+ artifact.sizeBytes = status.sizeBytes;
3487
+ }
2550
3488
  if (status.escaped) {
2551
3489
  artifact.status = "missing";
2552
3490
  artifact.sizeBytes = void 0;
2553
- delete artifact.workspacePath;
3491
+ artifact.hideWorkspacePath = true;
2554
3492
  }
2555
3493
  artifact.lastStatAt = options.now ?? Date.now();
2556
3494
  } catch (error) {
@@ -2579,7 +3517,18 @@ var SessionArtifactStore = class {
2579
3517
  }
2580
3518
  return this.realWorkspaceCwdPromise;
2581
3519
  }
2582
- async evictOverflow(createdIds, changes) {
3520
+ async getRealWorkspaceCwdForValidation() {
3521
+ try {
3522
+ return await this.getRealWorkspaceCwd();
3523
+ } catch (error) {
3524
+ const reason = error instanceof Error ? error.message : String(error);
3525
+ throw new SessionArtifactValidationError(
3526
+ `workspacePath could not be inspected: ${reason}`,
3527
+ "workspacePath"
3528
+ );
3529
+ }
3530
+ }
3531
+ async evictOverflow(createdIds, changes, strict = false) {
2583
3532
  const removed = [];
2584
3533
  if (this.artifacts.size <= this.maxArtifacts) {
2585
3534
  return removed;
@@ -2607,10 +3556,17 @@ var SessionArtifactStore = class {
2607
3556
  action: "removed",
2608
3557
  artifactId: artifact.id,
2609
3558
  artifact: toPublicArtifact(artifact),
3559
+ durableTombstoneRequired: artifact.durableTombstoneRequired,
2610
3560
  reason: "eviction"
2611
3561
  });
2612
3562
  }
2613
3563
  const overflowCreated = Array.from(this.artifacts.values()).filter((artifact) => createdInThisBatch.has(artifact.id)).sort((a, b) => b.receivedSeq - a.receivedSeq);
3564
+ if (strict && overflowCreated.length > 0 && this.artifacts.size > this.maxArtifacts) {
3565
+ throw new SessionArtifactValidationError(
3566
+ "artifact store is full; no eviction candidate is available",
3567
+ "artifactId"
3568
+ );
3569
+ }
2614
3570
  for (const artifact of overflowCreated) {
2615
3571
  if (this.artifacts.size <= this.maxArtifacts) {
2616
3572
  break;
@@ -2659,7 +3615,12 @@ function mergeBatchArtifact(existing, next) {
2659
3615
  trustedPublisher: true,
2660
3616
  createdAt: existing.createdAt,
2661
3617
  receivedSeq: existing.receivedSeq,
3618
+ retentionExplicit: existing.retentionExplicit || next.retentionExplicit,
2662
3619
  retentionSource: existing.retentionSource,
3620
+ retention: mergeRetention(existing, next),
3621
+ restoreState: "live",
3622
+ persistenceWarning: existing.persistenceWarning ?? next.persistenceWarning,
3623
+ persistedAt: existing.persistedAt ?? next.persistedAt,
2663
3624
  clientRetained: existing.clientRetained || next.clientRetained,
2664
3625
  lastStatAt: void 0
2665
3626
  };
@@ -2673,6 +3634,8 @@ function mergeBatchArtifact(existing, next) {
2673
3634
  metadata: mergeMetadata(existing, next),
2674
3635
  clientRetained: existing.clientRetained || next.clientRetained,
2675
3636
  trustedPublisher: existing.trustedPublisher || next.trustedPublisher,
3637
+ retentionExplicit: existing.retentionExplicit || next.retentionExplicit,
3638
+ retention: mergeRetention(existing, next),
2676
3639
  lastStatAt: next.lastStatAt ?? existing.lastStatAt
2677
3640
  };
2678
3641
  }
@@ -2692,9 +3655,14 @@ function mergeArtifact(existing, incoming) {
2692
3655
  managedId: publishedUpdate ? incoming.managedId ?? existing.managedId : existing.managedId,
2693
3656
  url: publishedUpdate ? incoming.url ?? existing.url : existing.url,
2694
3657
  workspacePath: publishedUpdate || existing.storage === "published" ? void 0 : existing.workspacePath ?? incoming.workspacePath,
3658
+ hideWorkspacePath: publishedUpdate || existing.storage === "published" || incoming.workspacePath ? void 0 : existing.hideWorkspacePath,
2695
3659
  mimeType: publishedUpdate ? incoming.mimeType ?? existing.mimeType : existing.mimeType,
2696
3660
  sizeBytes: existing.storage === "published" && !publishedUpdate ? existing.sizeBytes : mergeSizeBytes(existing, incoming),
2697
3661
  metadata: existing.storage === "published" && !publishedUpdate ? existing.metadata : mergeMetadata(existing, incoming),
3662
+ retention: mergeRetention(existing, incoming),
3663
+ restoreState: "live",
3664
+ persistenceWarning: incoming.retentionExplicit && incoming.retention !== "ephemeral" ? incoming.persistenceWarning : existing.persistenceWarning ?? incoming.persistenceWarning,
3665
+ persistedAt: incoming.retentionExplicit && incoming.retention === "ephemeral" ? void 0 : existing.persistedAt ?? incoming.persistedAt,
2698
3666
  source: existing.source,
2699
3667
  retentionSource: existing.retentionSource,
2700
3668
  trustedPublisher: existing.trustedPublisher || incoming.trustedPublisher,
@@ -2706,6 +3674,7 @@ function mergeArtifact(existing, incoming) {
2706
3674
  next.title = incoming.title;
2707
3675
  next.description = incoming.description;
2708
3676
  delete next.workspacePath;
3677
+ delete next.hideWorkspacePath;
2709
3678
  }
2710
3679
  const changed = !publicArtifactsEqual(
2711
3680
  toPublicArtifact(existing),
@@ -2717,8 +3686,12 @@ function mergeArtifact(existing, incoming) {
2717
3686
  return { artifact: changed ? next : existing, changed };
2718
3687
  }
2719
3688
  __name(mergeArtifact, "mergeArtifact");
3689
+ function shouldRecordEphemeralUnpin(existing, incoming) {
3690
+ return incoming.retentionExplicit && incoming.retention === "ephemeral" && (existing.retention !== "ephemeral" || existing.persistedAt !== void 0 || existing.durableTombstoneRequired === true);
3691
+ }
3692
+ __name(shouldRecordEphemeralUnpin, "shouldRecordEphemeralUnpin");
2720
3693
  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;
3694
+ 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
3695
  }
2723
3696
  __name(publicArtifactsEqual, "publicArtifactsEqual");
2724
3697
  function metadataEqual(a, b) {
@@ -2740,6 +3713,24 @@ function mergeSizeBytes(existing, incoming) {
2740
3713
  return existing.sizeBytes;
2741
3714
  }
2742
3715
  __name(mergeSizeBytes, "mergeSizeBytes");
3716
+ function strongestRetention(a, b) {
3717
+ const rank = {
3718
+ ephemeral: 0,
3719
+ restorable: 1
3720
+ };
3721
+ return rank[b] > rank[a] ? b : a;
3722
+ }
3723
+ __name(strongestRetention, "strongestRetention");
3724
+ function mergeRetention(existing, incoming) {
3725
+ if (incoming.retentionExplicit && incoming.retention === "ephemeral") {
3726
+ return "ephemeral";
3727
+ }
3728
+ if (existing.retentionExplicit && existing.retention === "ephemeral" && !incoming.retentionExplicit) {
3729
+ return "ephemeral";
3730
+ }
3731
+ return strongestRetention(existing.retention, incoming.retention);
3732
+ }
3733
+ __name(mergeRetention, "mergeRetention");
2743
3734
  function mergeMetadata(existing, incoming) {
2744
3735
  if (!incoming.metadata || incoming.source === "hook" || incoming.source !== existing.source) {
2745
3736
  return existing.metadata;
@@ -2747,7 +3738,10 @@ function mergeMetadata(existing, incoming) {
2747
3738
  const merged = { ...existing.metadata ?? {} };
2748
3739
  let changed = false;
2749
3740
  for (const [key, value] of Object.entries(incoming.metadata)) {
2750
- if (!Object.hasOwn(merged, key)) {
3741
+ if ((key === WORKSPACE_CONTENT_SHA256_METADATA_KEY || key === WORKSPACE_CONTENT_MTIME_MS_METADATA_KEY) && merged[key] !== value) {
3742
+ merged[key] = value;
3743
+ changed = true;
3744
+ } else if (!Object.hasOwn(merged, key)) {
2751
3745
  merged[key] = value;
2752
3746
  changed = true;
2753
3747
  }
@@ -2765,7 +3759,7 @@ function mergeMetadata(existing, incoming) {
2765
3759
  }
2766
3760
  __name(mergeMetadata, "mergeMetadata");
2767
3761
  function isMetadataWithinLimit(metadata) {
2768
- return Buffer.byteLength(JSON.stringify(metadata), "utf8") <= 4096;
3762
+ return metadataBudgetBytes(metadata, "persisted") <= 4096;
2769
3763
  }
2770
3764
  __name(isMetadataWithinLimit, "isMetadataWithinLimit");
2771
3765
  function countByRetentionSource(artifacts) {
@@ -2821,6 +3815,7 @@ function removePriorChange(changes, artifactId) {
2821
3815
  }
2822
3816
  __name(removePriorChange, "removePriorChange");
2823
3817
  function toPublicArtifact(artifact) {
3818
+ const hideWorkspacePath = "hideWorkspacePath" in artifact && artifact.hideWorkspacePath === true;
2824
3819
  const {
2825
3820
  id,
2826
3821
  kind,
@@ -2835,13 +3830,16 @@ function toPublicArtifact(artifact) {
2835
3830
  mimeType,
2836
3831
  sizeBytes,
2837
3832
  metadata,
3833
+ retention,
3834
+ restoreState,
3835
+ persistenceWarning,
3836
+ persistedAt,
2838
3837
  clientRetained,
2839
3838
  createdAt,
2840
3839
  updatedAt,
2841
3840
  toolCallId,
2842
3841
  toolName,
2843
- hookEventName,
2844
- clientId
3842
+ hookEventName
2845
3843
  } = artifact;
2846
3844
  return {
2847
3845
  id,
@@ -2851,22 +3849,205 @@ function toPublicArtifact(artifact) {
2851
3849
  status,
2852
3850
  title,
2853
3851
  ...description ? { description } : {},
2854
- ...workspacePath ? { workspacePath } : {},
3852
+ ...workspacePath && !hideWorkspacePath ? { workspacePath } : {},
2855
3853
  ...managedId ? { managedId } : {},
2856
3854
  ...url ? { url } : {},
2857
3855
  ...mimeType ? { mimeType } : {},
2858
3856
  ...sizeBytes !== void 0 ? { sizeBytes } : {},
2859
3857
  ...metadata ? { metadata } : {},
3858
+ retention,
3859
+ ...restoreState ? { restoreState } : {},
3860
+ ...persistenceWarning ? { persistenceWarning } : {},
3861
+ ...persistedAt ? { persistedAt } : {},
2860
3862
  clientRetained,
2861
3863
  createdAt,
2862
3864
  updatedAt,
2863
3865
  ...toolCallId ? { toolCallId } : {},
2864
3866
  ...toolName ? { toolName } : {},
2865
- ...hookEventName ? { hookEventName } : {},
2866
- ...clientId ? { clientId } : {}
3867
+ ...hookEventName ? { hookEventName } : {}
2867
3868
  };
2868
3869
  }
2869
3870
  __name(toPublicArtifact, "toPublicArtifact");
3871
+ function persistedArtifactToInput(artifact) {
3872
+ return {
3873
+ title: artifact.title,
3874
+ kind: artifact.kind,
3875
+ storage: artifact.storage,
3876
+ description: artifact.description,
3877
+ workspacePath: artifact.workspacePath,
3878
+ managedId: artifact.managedId,
3879
+ url: artifact.url,
3880
+ mimeType: artifact.mimeType,
3881
+ sizeBytes: artifact.sizeBytes,
3882
+ metadata: artifact.metadata,
3883
+ source: artifact.source,
3884
+ retention: artifact.retention,
3885
+ clientRetained: artifact.clientRetained,
3886
+ toolCallId: artifact.toolCallId,
3887
+ toolName: artifact.toolName,
3888
+ hookEventName: artifact.hookEventName
3889
+ };
3890
+ }
3891
+ __name(persistedArtifactToInput, "persistedArtifactToInput");
3892
+ function workspaceExpectedFromArtifact(artifact) {
3893
+ if (!artifact.workspacePath) {
3894
+ return void 0;
3895
+ }
3896
+ return {
3897
+ sizeBytes: artifact.sizeBytes,
3898
+ mtimeMs: artifact.metadata?.[WORKSPACE_CONTENT_MTIME_MS_METADATA_KEY],
3899
+ sha256: artifact.metadata?.[WORKSPACE_CONTENT_SHA256_METADATA_KEY]
3900
+ };
3901
+ }
3902
+ __name(workspaceExpectedFromArtifact, "workspaceExpectedFromArtifact");
3903
+ function toPersistedChange(change, recordedAt) {
3904
+ return {
3905
+ action: change.action,
3906
+ artifactId: change.artifactId,
3907
+ ...change.artifact ? { artifact: toPersistedArtifact(change.artifact, recordedAt) } : {},
3908
+ ...change.reason ? { reason: change.reason } : {}
3909
+ };
3910
+ }
3911
+ __name(toPersistedChange, "toPersistedChange");
3912
+ function toPersistedArtifact(artifact, recordedAt) {
3913
+ return {
3914
+ id: artifact.id,
3915
+ kind: artifact.kind,
3916
+ storage: artifact.storage,
3917
+ source: artifact.source,
3918
+ status: artifact.status === "changed" ? "available" : artifact.status,
3919
+ title: artifact.title,
3920
+ ...artifact.description ? { description: artifact.description } : {},
3921
+ ...artifact.workspacePath ? { workspacePath: artifact.workspacePath } : {},
3922
+ ...artifact.managedId ? { managedId: artifact.managedId } : {},
3923
+ ...artifact.url ? { url: artifact.url } : {},
3924
+ ...artifact.mimeType ? { mimeType: artifact.mimeType } : {},
3925
+ ...artifact.sizeBytes !== void 0 ? { sizeBytes: artifact.sizeBytes } : {},
3926
+ ...artifact.metadata ? { metadata: artifact.metadata } : {},
3927
+ retention: artifact.retention,
3928
+ clientRetained: artifact.clientRetained,
3929
+ createdAt: artifact.createdAt,
3930
+ updatedAt: artifact.updatedAt,
3931
+ persistedAt: artifact.persistedAt ?? recordedAt,
3932
+ ...artifact.toolCallId ? { toolCallId: artifact.toolCallId } : {},
3933
+ ...artifact.toolName ? { toolName: artifact.toolName } : {},
3934
+ ...artifact.hookEventName ? { hookEventName: artifact.hookEventName } : {}
3935
+ };
3936
+ }
3937
+ __name(toPersistedArtifact, "toPersistedArtifact");
3938
+ function isFileArtifactUrl(raw) {
3939
+ if (typeof raw !== "string") return false;
3940
+ try {
3941
+ return new URL(raw).protocol === "file:";
3942
+ } catch {
3943
+ return false;
3944
+ }
3945
+ }
3946
+ __name(isFileArtifactUrl, "isFileArtifactUrl");
3947
+ function isArtifactSnapshotCompletenessWarning(warning) {
3948
+ if (warning.startsWith("skipped stale event sequence ")) return false;
3949
+ return warning.startsWith("skipped ") || warning.includes(" list truncated to ");
3950
+ }
3951
+ __name(isArtifactSnapshotCompletenessWarning, "isArtifactSnapshotCompletenessWarning");
3952
+ function shouldCommitBeforeDurablePersistence(change) {
3953
+ return change.action === "removed" && change.durableTombstoneRequired === true;
3954
+ }
3955
+ __name(shouldCommitBeforeDurablePersistence, "shouldCommitBeforeDurablePersistence");
3956
+ function isDurablePersistenceChange(change) {
3957
+ if (change.action === "removed" && change.durableTombstoneRequired === true) {
3958
+ return true;
3959
+ }
3960
+ if (!change.artifact) return false;
3961
+ return change.artifact.retention !== "ephemeral" || change.artifact.persistenceWarning === "persistence_unavailable";
3962
+ }
3963
+ __name(isDurablePersistenceChange, "isDurablePersistenceChange");
3964
+ function stripDurableTombstoneMarkers(changes) {
3965
+ for (const change of changes) {
3966
+ delete change.durableTombstoneRequired;
3967
+ delete change.removedClientId;
3968
+ }
3969
+ }
3970
+ __name(stripDurableTombstoneMarkers, "stripDurableTombstoneMarkers");
3971
+ function detailsForRestoreWarnings(warnings) {
3972
+ return warnings.map((warning) => {
3973
+ if (warning.startsWith(RESTORE_FAILED_WARNING_PREFIX)) {
3974
+ return {
3975
+ code: "ARTIFACT_RESTORE_FAILED",
3976
+ operation: "restore",
3977
+ durability: "unavailable",
3978
+ retryable: true,
3979
+ message: warning
3980
+ };
3981
+ }
3982
+ if (warning.startsWith(RESTORE_PARTIAL_FAILED_WARNING_PREFIX)) {
3983
+ return {
3984
+ code: "ARTIFACT_RESTORE_PARTIAL_FAILED",
3985
+ operation: "restore",
3986
+ durability: "live_only",
3987
+ retryable: true,
3988
+ message: warning
3989
+ };
3990
+ }
3991
+ return {
3992
+ code: "ARTIFACT_WARNING",
3993
+ operation: "restore",
3994
+ message: warning
3995
+ };
3996
+ });
3997
+ }
3998
+ __name(detailsForRestoreWarnings, "detailsForRestoreWarnings");
3999
+ function detailsForPersistenceWarnings(warnings, changes, operation) {
4000
+ if (warnings.length === 0) return [];
4001
+ const artifactIds = changes.filter(isDurablePersistenceChange).map((change) => change.artifactId);
4002
+ return warnings.map((warning) => {
4003
+ if (warning === "artifact persistence unavailable; durable artifacts kept ephemeral") {
4004
+ return {
4005
+ code: "ARTIFACT_PERSISTENCE_UNAVAILABLE",
4006
+ operation,
4007
+ artifactIds,
4008
+ durability: "live_only",
4009
+ retryable: false,
4010
+ message: warning
4011
+ };
4012
+ }
4013
+ if (warning === "artifact removal not persisted; live removal kept") {
4014
+ return {
4015
+ code: "ARTIFACT_REMOVAL_NOT_PERSISTED",
4016
+ operation,
4017
+ artifactIds,
4018
+ durability: "live_only",
4019
+ retryable: true,
4020
+ message: warning
4021
+ };
4022
+ }
4023
+ return {
4024
+ code: "ARTIFACT_WARNING",
4025
+ operation,
4026
+ artifactIds,
4027
+ message: warning
4028
+ };
4029
+ });
4030
+ }
4031
+ __name(detailsForPersistenceWarnings, "detailsForPersistenceWarnings");
4032
+ function persistenceFailureDetail(options) {
4033
+ const unavailable = options.error instanceof SessionArtifactValidationError && options.error.message === "artifact persistence is unavailable";
4034
+ return {
4035
+ code: unavailable ? "ARTIFACT_PERSISTENCE_UNAVAILABLE" : "ARTIFACT_PERSISTENCE_WRITE_FAILED",
4036
+ operation: options.operation,
4037
+ artifactIds: options.artifactIds,
4038
+ durability: "unavailable",
4039
+ retryable: !unavailable,
4040
+ message: options.message
4041
+ };
4042
+ }
4043
+ __name(persistenceFailureDetail, "persistenceFailureDetail");
4044
+ function cloneStoredArtifact(artifact) {
4045
+ return {
4046
+ ...artifact,
4047
+ ...artifact.metadata ? { metadata: { ...artifact.metadata } } : {}
4048
+ };
4049
+ }
4050
+ __name(cloneStoredArtifact, "cloneStoredArtifact");
2870
4051
  function inferStorage(requested, locators) {
2871
4052
  if (requested !== void 0 && typeof requested !== "string") {
2872
4053
  throw new SessionArtifactValidationError(
@@ -2960,10 +4141,6 @@ function buildIdentityKey(input) {
2960
4141
  );
2961
4142
  }
2962
4143
  __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
4144
  function managedIdForWorkspacePath(workspaceCwd, workspacePath) {
2968
4145
  return createHash("sha1").update(path2.resolve(workspaceCwd, workspacePath)).digest("hex").slice(0, 16);
2969
4146
  }
@@ -3077,6 +4254,12 @@ function normalizeArtifactUrl(raw, allowFile) {
3077
4254
  "url"
3078
4255
  );
3079
4256
  }
4257
+ if (hasSecretLikeUrlComponent(parsed)) {
4258
+ throw new SessionArtifactValidationError(
4259
+ "url must not include secret-like components",
4260
+ "url"
4261
+ );
4262
+ }
3080
4263
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:" && !(allowFile && parsed.protocol === "file:")) {
3081
4264
  throw new SessionArtifactValidationError(
3082
4265
  allowFile ? "url must use http, https, or file" : "url must use http or https",
@@ -3086,7 +4269,64 @@ function normalizeArtifactUrl(raw, allowFile) {
3086
4269
  return parsed.href;
3087
4270
  }
3088
4271
  __name(normalizeArtifactUrl, "normalizeArtifactUrl");
3089
- function normalizeMetadata(metadata) {
4272
+ function hasSecretLikeUrlComponent(parsed) {
4273
+ for (const [key, value] of parsed.searchParams) {
4274
+ if (isSecretLikeUrlText(key) || isSecretLikeUrlValue(value)) {
4275
+ return true;
4276
+ }
4277
+ }
4278
+ for (const segment of parsed.pathname.split("/").filter(Boolean)) {
4279
+ let decodedSegment = segment;
4280
+ try {
4281
+ decodedSegment = decodeURIComponent(segment);
4282
+ } catch {
4283
+ }
4284
+ if (isSecretLikeUrlValue(decodedSegment)) {
4285
+ return true;
4286
+ }
4287
+ }
4288
+ const fragment = parsed.hash.slice(1);
4289
+ return hasSecretLikeUrlFragment(fragment);
4290
+ }
4291
+ __name(hasSecretLikeUrlComponent, "hasSecretLikeUrlComponent");
4292
+ function hasSecretLikeUrlFragment(fragment) {
4293
+ if (!fragment) return false;
4294
+ const candidates = /* @__PURE__ */ new Set([fragment]);
4295
+ try {
4296
+ candidates.add(decodeURIComponent(fragment));
4297
+ } catch {
4298
+ }
4299
+ for (const candidate of candidates) {
4300
+ if (isSecretLikeUrlText(candidate) || isSecretLikeUrlValue(candidate)) {
4301
+ return true;
4302
+ }
4303
+ for (const [key, value] of new URLSearchParams(candidate)) {
4304
+ if (isSecretLikeUrlText(key) || isSecretLikeUrlValue(value)) {
4305
+ return true;
4306
+ }
4307
+ }
4308
+ }
4309
+ return false;
4310
+ }
4311
+ __name(hasSecretLikeUrlFragment, "hasSecretLikeUrlFragment");
4312
+ function isSecretLikeUrlText(value) {
4313
+ const normalized = value.replace(/([a-z])([A-Z])/g, "$1-$2");
4314
+ return /(?:^|[-_.])(token|secret|password|passwd|pwd|cookie|authorization|credential|signature|sig|api[-_]?key|access[-_]?key)(?:$|[-_.=&#])/i.test(
4315
+ normalized
4316
+ );
4317
+ }
4318
+ __name(isSecretLikeUrlText, "isSecretLikeUrlText");
4319
+ function isSecretLikeUrlValue(value) {
4320
+ return SECRET_TOKEN_VALUE_PATTERN.test(value.trim());
4321
+ }
4322
+ __name(isSecretLikeUrlValue, "isSecretLikeUrlValue");
4323
+ function isSecretLikeMetadataValue(value) {
4324
+ return /^(?:bearer\s+\S{8,}|sk-[A-Za-z0-9_-]{12,}|(?:gh[pousr]|github_pat)_[A-Za-z0-9_/-]{12,})$/i.test(
4325
+ value.trim()
4326
+ );
4327
+ }
4328
+ __name(isSecretLikeMetadataValue, "isSecretLikeMetadataValue");
4329
+ function normalizeMetadata(metadata, options = {}) {
3090
4330
  if (metadata === void 0) {
3091
4331
  return void 0;
3092
4332
  }
@@ -3098,6 +4338,12 @@ function normalizeMetadata(metadata) {
3098
4338
  }
3099
4339
  const normalized = {};
3100
4340
  for (const [key, value] of Object.entries(metadata)) {
4341
+ if (isPrototypeMetadataKey(key)) {
4342
+ continue;
4343
+ }
4344
+ if (options.budget !== "persisted" && isReservedWorkspaceMetadataKey(key)) {
4345
+ continue;
4346
+ }
3101
4347
  if (!key) {
3102
4348
  throw new SessionArtifactValidationError(
3103
4349
  "metadata keys must not be empty",
@@ -3116,6 +4362,12 @@ function normalizeMetadata(metadata) {
3116
4362
  "metadata"
3117
4363
  );
3118
4364
  }
4365
+ if (isSecretLikeUrlText(key)) {
4366
+ throw new SessionArtifactValidationError(
4367
+ "metadata keys must not contain secret-like names",
4368
+ "metadata"
4369
+ );
4370
+ }
3119
4371
  if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
3120
4372
  throw new SessionArtifactValidationError(
3121
4373
  "metadata values must be primitive",
@@ -3134,12 +4386,18 @@ function normalizeMetadata(metadata) {
3134
4386
  "metadata"
3135
4387
  );
3136
4388
  }
4389
+ if (typeof value === "string" && !isReservedWorkspaceMetadataKey(key) && isSecretLikeMetadataValue(value)) {
4390
+ throw new SessionArtifactValidationError(
4391
+ "metadata string values must not contain secret-like tokens",
4392
+ "metadata"
4393
+ );
4394
+ }
3137
4395
  normalized[key] = value;
3138
4396
  }
3139
4397
  if (Object.keys(normalized).length === 0) {
3140
4398
  return void 0;
3141
4399
  }
3142
- if (Buffer.byteLength(JSON.stringify(normalized), "utf8") > 4096) {
4400
+ if (metadataBudgetBytes(normalized, options.budget ?? "user") > 4096) {
3143
4401
  throw new SessionArtifactValidationError(
3144
4402
  "metadata must be 4096 bytes or fewer",
3145
4403
  "metadata"
@@ -3148,6 +4406,25 @@ function normalizeMetadata(metadata) {
3148
4406
  return normalized;
3149
4407
  }
3150
4408
  __name(normalizeMetadata, "normalizeMetadata");
4409
+ function normalizeRetention(value, options) {
4410
+ if (value === void 0) {
4411
+ return options.persistenceAvailable ? "restorable" : "ephemeral";
4412
+ }
4413
+ if (value === "ephemeral" || value === "restorable") {
4414
+ return value;
4415
+ }
4416
+ if (value === "pinned") {
4417
+ throw new SessionArtifactValidationError(
4418
+ "pinned retention is not supported by session_artifacts_persistence",
4419
+ "retention"
4420
+ );
4421
+ }
4422
+ throw new SessionArtifactValidationError(
4423
+ "retention must be ephemeral or restorable",
4424
+ "retention"
4425
+ );
4426
+ }
4427
+ __name(normalizeRetention, "normalizeRetention");
3151
4428
  function normalizeSizeBytes(value) {
3152
4429
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
3153
4430
  throw new SessionArtifactValidationError(
@@ -3173,21 +4450,83 @@ function inferKind(input) {
3173
4450
  return input.workspacePath ? "file" : "other";
3174
4451
  }
3175
4452
  __name(inferKind, "inferKind");
3176
- async function getWorkspaceStatus(workspaceCwd, workspacePath, realWorkspaceCwd) {
3177
- const absolutePath = path2.resolve(workspaceCwd, workspacePath);
4453
+ async function getWorkspaceStatus(workspacePath, realWorkspaceCwd, expected, options = { hashContent: true }) {
3178
4454
  const realWorkspace = await realWorkspaceCwd;
4455
+ const absolutePath = path2.resolve(realWorkspace, workspacePath);
3179
4456
  try {
3180
4457
  const realPath = await fs2.realpath(absolutePath);
3181
4458
  const relative = path2.relative(realWorkspace, realPath);
3182
4459
  if (!relative || isOutsidePath(relative)) {
3183
4460
  return { status: "missing", escaped: true };
3184
4461
  }
3185
- const stat = await fs2.stat(realPath);
3186
- return {
3187
- status: "available",
3188
- ...stat.isFile() ? { sizeBytes: stat.size } : {}
3189
- };
4462
+ const preOpenStat = await fs2.lstat(realPath);
4463
+ const handle = await fs2.open(
4464
+ realPath,
4465
+ fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW
4466
+ );
4467
+ try {
4468
+ const stat = await handle.stat();
4469
+ if (!isSameFile(preOpenStat, stat)) {
4470
+ return { status: "missing", escaped: true };
4471
+ }
4472
+ if (stat.isFile()) {
4473
+ const expectedMtimeMs = typeof expected?.mtimeMs === "number" ? expected.mtimeMs : void 0;
4474
+ const expectedSha256 = typeof expected?.sha256 === "string" ? expected.sha256 : void 0;
4475
+ const unchanged = expected?.sizeBytes === stat.size && expectedMtimeMs === stat.mtimeMs;
4476
+ const sizeChanged = expected?.sizeBytes !== void 0 && expected.sizeBytes !== stat.size;
4477
+ if (sizeChanged) {
4478
+ return {
4479
+ status: "changed",
4480
+ sizeBytes: stat.size,
4481
+ mtimeMs: stat.mtimeMs
4482
+ };
4483
+ }
4484
+ if (unchanged) {
4485
+ return {
4486
+ status: "available",
4487
+ sizeBytes: stat.size,
4488
+ mtimeMs: stat.mtimeMs
4489
+ };
4490
+ }
4491
+ if (stat.size > MAX_WORKSPACE_HASH_BYTES) {
4492
+ return {
4493
+ status: expectedSha256 ? "changed" : "available",
4494
+ sizeBytes: stat.size,
4495
+ mtimeMs: stat.mtimeMs
4496
+ };
4497
+ }
4498
+ if (!options.hashContent) {
4499
+ return {
4500
+ status: expectedSha256 ? "changed" : "available",
4501
+ sizeBytes: stat.size,
4502
+ mtimeMs: stat.mtimeMs
4503
+ };
4504
+ }
4505
+ const sha256 = await hashFile(handle);
4506
+ if (expectedSha256 && sha256 !== expectedSha256) {
4507
+ return {
4508
+ status: "changed",
4509
+ sizeBytes: stat.size,
4510
+ mtimeMs: stat.mtimeMs
4511
+ };
4512
+ }
4513
+ return {
4514
+ status: "available",
4515
+ sizeBytes: stat.size,
4516
+ mtimeMs: stat.mtimeMs,
4517
+ sha256
4518
+ };
4519
+ }
4520
+ return {
4521
+ status: "available"
4522
+ };
4523
+ } finally {
4524
+ await handle.close();
4525
+ }
3190
4526
  } catch (error) {
4527
+ if (isNoFollowSymlinkError(error)) {
4528
+ return { status: "missing", escaped: true };
4529
+ }
3191
4530
  if (!isNotFoundError(error)) {
3192
4531
  throw error;
3193
4532
  }
@@ -3198,6 +4537,41 @@ async function getWorkspaceStatus(workspaceCwd, workspacePath, realWorkspaceCwd)
3198
4537
  }
3199
4538
  }
3200
4539
  __name(getWorkspaceStatus, "getWorkspaceStatus");
4540
+ function isSameFile(before, after) {
4541
+ return before.dev === after.dev && before.ino === after.ino;
4542
+ }
4543
+ __name(isSameFile, "isSameFile");
4544
+ async function hashFile(handle) {
4545
+ const hash = createHash("sha256");
4546
+ for await (const chunk of handle.createReadStream({ start: 0 })) {
4547
+ hash.update(chunk);
4548
+ }
4549
+ return hash.digest("hex");
4550
+ }
4551
+ __name(hashFile, "hashFile");
4552
+ function withWorkspaceContentHashMetadata(metadata, workspaceStatus) {
4553
+ if (workspaceStatus?.status !== "available" || !workspaceStatus.sha256) {
4554
+ return metadata;
4555
+ }
4556
+ const next = {
4557
+ ...metadata ?? {},
4558
+ [WORKSPACE_CONTENT_SHA256_METADATA_KEY]: workspaceStatus.sha256,
4559
+ ...workspaceStatus.mtimeMs !== void 0 ? { [WORKSPACE_CONTENT_MTIME_MS_METADATA_KEY]: workspaceStatus.mtimeMs } : {}
4560
+ };
4561
+ return next;
4562
+ }
4563
+ __name(withWorkspaceContentHashMetadata, "withWorkspaceContentHashMetadata");
4564
+ function isWorkspaceContentChanged(artifact, status) {
4565
+ if (status.status !== "available") {
4566
+ return false;
4567
+ }
4568
+ const expectedSha256 = artifact.metadata?.[WORKSPACE_CONTENT_SHA256_METADATA_KEY];
4569
+ if (artifact.sizeBytes !== void 0 && status.sizeBytes !== void 0 && status.sizeBytes !== artifact.sizeBytes) {
4570
+ return true;
4571
+ }
4572
+ return typeof expectedSha256 === "string" && status.sha256 !== void 0 && status.sha256 !== expectedSha256;
4573
+ }
4574
+ __name(isWorkspaceContentChanged, "isWorkspaceContentChanged");
3201
4575
  async function danglingSymlinkEscapesWorkspace(absolutePath, realWorkspace) {
3202
4576
  try {
3203
4577
  const stat = await fs2.lstat(absolutePath);
@@ -3230,6 +4604,13 @@ function isNotFoundError(error) {
3230
4604
  return code === "ENOENT" || code === "ENOTDIR";
3231
4605
  }
3232
4606
  __name(isNotFoundError, "isNotFoundError");
4607
+ function isNoFollowSymlinkError(error) {
4608
+ if (typeof error !== "object" || error === null || !("code" in error)) {
4609
+ return false;
4610
+ }
4611
+ return error.code === "ELOOP";
4612
+ }
4613
+ __name(isNoFollowSymlinkError, "isNoFollowSymlinkError");
3233
4614
  function isOutsidePath(relative) {
3234
4615
  return relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative);
3235
4616
  }
@@ -3464,6 +4845,12 @@ function parseWorkspaceMemoryDreamResult(response) {
3464
4845
  };
3465
4846
  }
3466
4847
  __name(parseWorkspaceMemoryDreamResult, "parseWorkspaceMemoryDreamResult");
4848
+ function pickUserInputEchoMeta(meta) {
4849
+ if (!meta || typeof meta !== "object" || Array.isArray(meta)) return {};
4850
+ const inputAnnotations = meta["inputAnnotations"];
4851
+ return Array.isArray(inputAnnotations) ? { inputAnnotations } : {};
4852
+ }
4853
+ __name(pickUserInputEchoMeta, "pickUserInputEchoMeta");
3467
4854
  function echoPromptToSessionBus(entry, req, originatorClientId) {
3468
4855
  const prompt = req.prompt;
3469
4856
  if (!Array.isArray(prompt) || prompt.length === 0) return;
@@ -3485,7 +4872,11 @@ function echoPromptToSessionBus(entry, req, originatorClientId) {
3485
4872
  // field permitted alongside spec fields, the SDK normalizer
3486
4873
  // reads it from `update._meta`/`data._meta`, and every other
3487
4874
  // agent-emitted session_update carries `_meta` the same way.
3488
- _meta: { serverTimestamp, source: "bridge-echo" }
4875
+ _meta: {
4876
+ ...pickUserInputEchoMeta(req._meta),
4877
+ serverTimestamp,
4878
+ source: "bridge-echo"
4879
+ }
3489
4880
  }
3490
4881
  },
3491
4882
  ...originatorClientId ? { originatorClientId } : {}
@@ -3582,6 +4973,11 @@ function broadcastTurnError(entry, sessionId, err, promptId, originatorClientId)
3582
4973
  );
3583
4974
  }
3584
4975
  entry.retryAllowed = true;
4976
+ entry.turnError = {
4977
+ message,
4978
+ ...code ? { code } : {},
4979
+ ...errorKind ? { errorKind } : {}
4980
+ };
3585
4981
  try {
3586
4982
  entry.events.publish({
3587
4983
  type: "turn_error",
@@ -3624,6 +5020,7 @@ function extractPromptText(prompt) {
3624
5020
  __name(extractPromptText, "extractPromptText");
3625
5021
  var DEFAULT_INIT_TIMEOUT_MS = 1e4;
3626
5022
  var PERSIST_TIMEOUT_MS = 5e3;
5023
+ var MAX_PARENT_PERSIST_ATTEMPTS = 3;
3627
5024
  var MCP_RESTART_TIMEOUT_MS = 3e5;
3628
5025
  var WORKSPACE_MEMORY_REMEMBER_TIMEOUT_MS = 3e5;
3629
5026
  var MCP_OAUTH_TIMEOUT_MS = 6e5;
@@ -3631,6 +5028,7 @@ var DAEMON_RETRY_META_KEY = "qwen.daemon.retry";
3631
5028
  var DAEMON_CONTINUE_META_KEY = "qwen.daemon.continueLastTurn";
3632
5029
  var SESSION_RECAP_TIMEOUT_MS = 6e4;
3633
5030
  var SESSION_BTW_TIMEOUT_MS = 6e4;
5031
+ var SESSION_TRANSCRIPT_TIMEOUT_MS = 6e4;
3634
5032
  var SHELL_COMMAND_TIMEOUT_MS = 12e4;
3635
5033
  var MAX_SHELL_OUTPUT_FOR_HISTORY = 1e4;
3636
5034
  var MAX_MID_TURN_QUEUE_DEPTH = 20;
@@ -3671,6 +5069,15 @@ function createAcpSessionBridge(opts) {
3671
5069
  );
3672
5070
  }
3673
5071
  }, "releaseFreshSessionReservation");
5072
+ const emitSessionLifecycle = /* @__PURE__ */ __name((event) => {
5073
+ try {
5074
+ opts.sessionLifecycle?.(event);
5075
+ } catch (err) {
5076
+ const message = `qwen serve: session lifecycle callback failed: ${err instanceof Error ? err.message : String(err)}`;
5077
+ opts.onDiagnosticLine?.(message, "warn");
5078
+ writeStderrLine(message);
5079
+ }
5080
+ }, "emitSessionLifecycle");
3674
5081
  if (defaultSessionScope !== "single" && defaultSessionScope !== "thread") {
3675
5082
  throw new TypeError(
3676
5083
  `Invalid sessionScope: ${JSON.stringify(defaultSessionScope)}. Expected 'single' or 'thread'.`
@@ -3855,14 +5262,33 @@ function createAcpSessionBridge(opts) {
3855
5262
  const aliveChannels = /* @__PURE__ */ new Set();
3856
5263
  let inFlightChannelSpawn;
3857
5264
  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");
5265
+ const inFlightExtensionRefreshes = /* @__PURE__ */ new Map();
5266
+ const toSessionSummary = /* @__PURE__ */ __name((entry) => {
5267
+ let isWaitingForPermission = false;
5268
+ let isWaitingForUserQuestion = false;
5269
+ for (const interaction of entry.pendingInteractions.values()) {
5270
+ if (interaction.kind === "user_question") {
5271
+ isWaitingForUserQuestion = true;
5272
+ } else {
5273
+ isWaitingForPermission = true;
5274
+ }
5275
+ }
5276
+ return {
5277
+ sessionId: entry.sessionId,
5278
+ workspaceCwd: entry.workspaceCwd,
5279
+ createdAt: entry.createdAt,
5280
+ displayName: entry.displayName,
5281
+ ...entry.parentSessionId ? { parentSessionId: entry.parentSessionId } : {},
5282
+ clientCount: entry.clientIds.size,
5283
+ hasActivePrompt: entry.promptActive,
5284
+ isWaitingForPermission,
5285
+ isWaitingForUserQuestion,
5286
+ pendingInteractionCount: entry.pendingInteractions.size,
5287
+ hasTurnError: entry.turnError !== void 0,
5288
+ ...entry.turnError !== void 0 ? { turnError: entry.turnError } : {},
5289
+ pendingInteractions: [...entry.pendingInteractions.values()]
5290
+ };
5291
+ }, "toSessionSummary");
3866
5292
  const permissionConsensusQuorum = opts.permissionConsensusQuorum;
3867
5293
  if (permissionConsensusQuorum !== void 0 && (!Number.isInteger(permissionConsensusQuorum) || permissionConsensusQuorum < 1)) {
3868
5294
  throw new Error(
@@ -3888,6 +5314,7 @@ function createAcpSessionBridge(opts) {
3888
5314
  }
3889
5315
  );
3890
5316
  let shuttingDown = false;
5317
+ let shutdownPromise;
3891
5318
  const teeServeDebugLine = /* @__PURE__ */ __name((message) => {
3892
5319
  writeServeDebugLine(message);
3893
5320
  if (opts.onDiagnosticLine && isServeDebugLoggingEnabled()) {
@@ -3921,6 +5348,22 @@ function createAcpSessionBridge(opts) {
3921
5348
  entry.clientIds.set(clientId, count - 1);
3922
5349
  }
3923
5350
  }, "unregisterClient");
5351
+ const rollbackAttachRegistration = /* @__PURE__ */ __name(async (entry, clientId, attachCountDelta = 1) => {
5352
+ entry.attachCount = Math.max(0, entry.attachCount - attachCountDelta);
5353
+ unregisterClient(entry, clientId);
5354
+ if (entry.spawnOwnerWantedKill && entry.attachCount === 0 && entry.events.subscriberCount === 0) {
5355
+ await bridgeApi.killSession(entry.sessionId).catch(() => {
5356
+ });
5357
+ } else if (entry.clientIds.size === 0 && entry.events.subscriberCount === 0 && !entry.promptActive) {
5358
+ await closeSessionImpl(entry.sessionId, void 0, {
5359
+ reason: "last_client_detached"
5360
+ }).catch((err) => {
5361
+ writeStderrLine(
5362
+ `qwen serve: close-on-attach-rollback failed for ${JSON.stringify(entry.sessionId)}: ${err instanceof Error ? err.stack ?? err.message : String(err)}`
5363
+ );
5364
+ });
5365
+ }
5366
+ }, "rollbackAttachRegistration");
3924
5367
  const resolveTrustedClientId = /* @__PURE__ */ __name((entry, clientId) => {
3925
5368
  if (clientId === void 0) return void 0;
3926
5369
  if (!entry.clientIds.has(clientId)) {
@@ -3997,11 +5440,17 @@ function createAcpSessionBridge(opts) {
3997
5440
  // at the session/update fan-in to the daemon host's metrics ring via
3998
5441
  // the telemetry seam. Optional-chained so non-daemon callers (tests,
3999
5442
  // Mode A) that wire no `tokenUsage` metric are a silent no-op.
4000
- (inputTokens, outputTokens, durationMs) => telemetry.metrics?.tokenUsage?.(
5443
+ (inputTokens, outputTokens, durationMs, apiErrors, apiRetries) => telemetry.metrics?.tokenUsage?.(
4001
5444
  inputTokens,
4002
5445
  outputTokens,
4003
- durationMs
4004
- )
5446
+ durationMs,
5447
+ apiErrors,
5448
+ apiRetries
5449
+ ),
5450
+ // `create_sub_session` tool: forward the request/response hook so a child
5451
+ // tool can ask the daemon to spawn a sub-session and (for 'first-turn')
5452
+ // return its result. Omitted → the method reports daemon-only.
5453
+ opts.onCreateSubSession
4005
5454
  );
4006
5455
  const connection = new ClientSideConnection(() => client, channel.stream);
4007
5456
  const info = {
@@ -4066,6 +5515,12 @@ function createAcpSessionBridge(opts) {
4066
5515
  }
4067
5516
  byId.delete(sid);
4068
5517
  telemetry.metrics?.sessionLifecycle("die");
5518
+ emitSessionLifecycle({
5519
+ type: "removed",
5520
+ sessionId: sid,
5521
+ workspaceCwd: sessEntry.workspaceCwd,
5522
+ reason: "channel_closed"
5523
+ });
4069
5524
  info.client.markSessionClosed(sid);
4070
5525
  if (defaultEntry === sessEntry) defaultEntry = void 0;
4071
5526
  sessEntry.events.close();
@@ -4114,10 +5569,12 @@ function createAcpSessionBridge(opts) {
4114
5569
  }
4115
5570
  }
4116
5571
  __name(ensureChannel, "ensureChannel");
4117
- async function doSpawn(modelServiceId, effectiveScope, requestedClientId, onSessionRegistered) {
5572
+ async function doSpawn(modelServiceId, effectiveScope, approvalMode, requestedClientId, onSessionRegistered, parentSessionId) {
4118
5573
  const ci = await ensureChannel();
4119
5574
  ci.sessionSpawnsInFlight++;
4120
5575
  let sessionRegistered = false;
5576
+ let sessionRemovedDuringInitialization = false;
5577
+ let initializedSessionId;
4121
5578
  let newSessionResp;
4122
5579
  try {
4123
5580
  try {
@@ -4152,13 +5609,58 @@ function createAcpSessionBridge(opts) {
4152
5609
  const entry = createSessionEntry(
4153
5610
  ci,
4154
5611
  newSessionResp.sessionId,
4155
- boundWorkspace
5612
+ boundWorkspace,
5613
+ void 0,
5614
+ { parentSessionId }
4156
5615
  );
5616
+ initializedSessionId = entry.sessionId;
4157
5617
  sessionRegistered = true;
4158
5618
  onSessionRegistered?.();
4159
5619
  seedSnapshotCaches(entry, newSessionResp);
4160
5620
  const clientId = registerClient(entry, requestedClientId);
4161
- if (effectiveScope === "single" && !defaultEntry) defaultEntry = entry;
5621
+ let parentSessionPersisted;
5622
+ if (entry.parentSessionId) {
5623
+ const parentDeadline = Date.now() + initTimeoutMs;
5624
+ let lastParentErr;
5625
+ for (let attempt = 1; attempt <= MAX_PARENT_PERSIST_ATTEMPTS; attempt++) {
5626
+ const remaining = parentDeadline - Date.now();
5627
+ if (remaining <= 0) {
5628
+ parentSessionPersisted = false;
5629
+ lastParentErr = "deadline exceeded";
5630
+ break;
5631
+ }
5632
+ try {
5633
+ const parentResult = await Promise.race([
5634
+ withTimeout(
5635
+ entry.connection.extMethod(
5636
+ SERVE_CONTROL_EXT_METHODS.sessionParent,
5637
+ {
5638
+ sessionId: entry.sessionId,
5639
+ parentSessionId: entry.parentSessionId
5640
+ }
5641
+ ),
5642
+ remaining,
5643
+ "sessionParent"
5644
+ ),
5645
+ getTransportClosedReject(entry)
5646
+ ]);
5647
+ parentSessionPersisted = parentResult?.persisted === true;
5648
+ break;
5649
+ } catch (err) {
5650
+ lastParentErr = err instanceof Error ? err.message : String(err);
5651
+ const terminal = err instanceof BridgeTimeoutError || err instanceof BridgeChannelClosedError || attempt === MAX_PARENT_PERSIST_ATTEMPTS;
5652
+ if (terminal) {
5653
+ parentSessionPersisted = false;
5654
+ break;
5655
+ }
5656
+ }
5657
+ }
5658
+ if (parentSessionPersisted === false) {
5659
+ writeStderrLine(
5660
+ `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)`
5661
+ );
5662
+ }
5663
+ }
4162
5664
  if (modelServiceId) {
4163
5665
  await applyModelServiceId(
4164
5666
  entry,
@@ -4168,22 +5670,51 @@ function createAcpSessionBridge(opts) {
4168
5670
  ).catch(() => {
4169
5671
  });
4170
5672
  }
5673
+ if (approvalMode) {
5674
+ try {
5675
+ await applyApprovalMode(entry, approvalMode, false, clientId);
5676
+ } catch (err) {
5677
+ try {
5678
+ await closeSessionImpl(entry.sessionId, void 0, {
5679
+ reason: "approval_mode_initialization_failed"
5680
+ });
5681
+ sessionRemovedDuringInitialization = true;
5682
+ } catch {
5683
+ }
5684
+ throw err;
5685
+ }
5686
+ }
4171
5687
  if (!byId.has(entry.sessionId)) {
4172
5688
  throw new Error(
4173
- `Session ${entry.sessionId} died during model-switch initialization`
5689
+ `Session ${entry.sessionId} died during session initialization`
4174
5690
  );
4175
5691
  }
5692
+ if (effectiveScope === "single" && !defaultEntry) defaultEntry = entry;
4176
5693
  return {
4177
5694
  sessionId: entry.sessionId,
4178
5695
  workspaceCwd: entry.workspaceCwd,
4179
5696
  attached: false,
4180
5697
  clientId,
4181
- createdAt: entry.createdAt
5698
+ createdAt: entry.createdAt,
5699
+ ...entry.parentSessionId ? { parentSessionPersisted: parentSessionPersisted === true } : {}
4182
5700
  };
4183
5701
  } finally {
4184
5702
  ci.sessionSpawnsInFlight = Math.max(0, ci.sessionSpawnsInFlight - 1);
4185
5703
  if (!sessionRegistered) {
4186
5704
  await reapPendingEmptyChannel(ci);
5705
+ } else if (sessionRemovedDuringInitialization && hasNoChannelWork(ci)) {
5706
+ await reapPendingEmptyChannel(ci);
5707
+ if (!ci.isDying) {
5708
+ await startIdleTimer(
5709
+ ci,
5710
+ `approval-mode initialization failure "${initializedSessionId}"`
5711
+ );
5712
+ }
5713
+ } else if (sessionRegistered && hasNoChannelWork(ci) && !ci.isDying) {
5714
+ await startIdleTimer(
5715
+ ci,
5716
+ `session orphaned during initialization "${initializedSessionId}"`
5717
+ );
4187
5718
  }
4188
5719
  }
4189
5720
  }
@@ -4213,41 +5744,157 @@ function createAcpSessionBridge(opts) {
4213
5744
  key: "model.name",
4214
5745
  value: modelId
4215
5746
  },
4216
- ...originatorClientId ? { originatorClientId } : {}
4217
- });
5747
+ ...originatorClientId ? { originatorClientId } : {}
5748
+ });
5749
+ succeeded = true;
5750
+ } catch (err) {
5751
+ entry.events.publish({
5752
+ type: "model_switch_failed",
5753
+ data: {
5754
+ sessionId: entry.sessionId,
5755
+ requestedModelId: modelId,
5756
+ error: err instanceof Error ? err.message : String(err)
5757
+ },
5758
+ ...originatorClientId ? { originatorClientId } : {}
5759
+ });
5760
+ throw err;
5761
+ } finally {
5762
+ entry.modelRoundtripInFlight = false;
5763
+ if (succeeded) {
5764
+ void reconcileAfterRoundtrip(entry, "model");
5765
+ } else {
5766
+ writeStderrLine(
5767
+ `[reconcile] session=${entry.sessionId} target=model action=skipped reason=roundtrip_failed`
5768
+ );
5769
+ }
5770
+ }
5771
+ });
5772
+ entry.modelChangeQueue = work.then(
5773
+ () => void 0,
5774
+ () => void 0
5775
+ );
5776
+ return work;
5777
+ }
5778
+ __name(applyModelServiceId, "applyModelServiceId");
5779
+ async function applyApprovalMode(entry, mode, persist, originatorClientId) {
5780
+ if (persist && !persistApprovalMode) {
5781
+ throw new Error(
5782
+ "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`."
5783
+ );
5784
+ }
5785
+ const approvalWork = entry.approvalModeQueue.then(async () => {
5786
+ entry.approvalModeRoundtripInFlight = true;
5787
+ let succeeded = false;
5788
+ try {
5789
+ const response = await Promise.race([
5790
+ withTimeout(
5791
+ entry.connection.extMethod(
5792
+ SERVE_CONTROL_EXT_METHODS.sessionApprovalMode,
5793
+ { sessionId: entry.sessionId, mode }
5794
+ ),
5795
+ initTimeoutMs,
5796
+ SERVE_CONTROL_EXT_METHODS.sessionApprovalMode
5797
+ ),
5798
+ getTransportClosedReject(entry)
5799
+ ]);
5800
+ if (typeof response.current !== "string" || !KNOWN_APPROVAL_MODES.has(response.current)) {
5801
+ throw new Error(
5802
+ `Agent returned unknown approval mode: ${JSON.stringify(response.current)}`
5803
+ );
5804
+ }
5805
+ let persisted = false;
5806
+ if (persist) {
5807
+ try {
5808
+ await withTimeout(
5809
+ persistApprovalMode?.(boundWorkspace, mode) ?? Promise.resolve(),
5810
+ PERSIST_TIMEOUT_MS,
5811
+ "persistApprovalMode"
5812
+ );
5813
+ persisted = persistApprovalMode !== void 0;
5814
+ } catch (err) {
5815
+ writeStderrLine(
5816
+ `setSessionApprovalMode: persist failed: ${err instanceof Error ? err.message : String(err)}`
5817
+ );
5818
+ }
5819
+ }
5820
+ publishApprovalModeChanged(
5821
+ entry,
5822
+ {
5823
+ previous: response.previous,
5824
+ next: response.current,
5825
+ persisted
5826
+ },
5827
+ originatorClientId
5828
+ );
5829
+ if (persisted) {
5830
+ broadcastWorkspaceEvent(
5831
+ {
5832
+ type: "approval_mode_changed",
5833
+ data: {
5834
+ sessionId: entry.sessionId,
5835
+ previous: response.previous,
5836
+ next: response.current,
5837
+ persisted
5838
+ },
5839
+ ...originatorClientId ? { originatorClientId } : {}
5840
+ },
5841
+ entry.sessionId
5842
+ );
5843
+ for (const peer of byId.values()) {
5844
+ if (peer.sessionId === entry.sessionId) {
5845
+ continue;
5846
+ }
5847
+ peer.currentApprovalMode = response.current;
5848
+ }
5849
+ }
4218
5850
  succeeded = true;
4219
- } catch (err) {
4220
- entry.events.publish({
4221
- type: "model_switch_failed",
4222
- data: {
4223
- sessionId: entry.sessionId,
4224
- requestedModelId: modelId,
4225
- error: err instanceof Error ? err.message : String(err)
4226
- },
4227
- ...originatorClientId ? { originatorClientId } : {}
4228
- });
4229
- throw err;
5851
+ return {
5852
+ sessionId: entry.sessionId,
5853
+ mode: response.current,
5854
+ previous: response.previous,
5855
+ persisted
5856
+ };
4230
5857
  } finally {
4231
- entry.modelRoundtripInFlight = false;
5858
+ entry.approvalModeRoundtripInFlight = false;
4232
5859
  if (succeeded) {
4233
- void reconcileAfterRoundtrip(entry, "model");
5860
+ void reconcileAfterRoundtrip(entry, "approvalMode");
4234
5861
  } else {
4235
5862
  writeStderrLine(
4236
- `[reconcile] session=${entry.sessionId} target=model action=skipped reason=roundtrip_failed`
5863
+ `[reconcile] session=${entry.sessionId} target=approvalMode action=skipped reason=roundtrip_failed`
4237
5864
  );
4238
5865
  }
4239
5866
  }
4240
5867
  });
4241
- entry.modelChangeQueue = work.then(
5868
+ entry.approvalModeQueue = approvalWork.then(
4242
5869
  () => void 0,
4243
5870
  () => void 0
4244
5871
  );
4245
- return work;
5872
+ try {
5873
+ return await approvalWork;
5874
+ } catch (err) {
5875
+ const data = err?.data;
5876
+ if (data && typeof data === "object" && "errorKind" in data && data.errorKind === "trust_gate") {
5877
+ const rawMessage = err?.message;
5878
+ const message = typeof rawMessage === "string" ? rawMessage : "Trust-gate rejection from ACP child";
5879
+ throw new TrustGateError(message);
5880
+ }
5881
+ throw err;
5882
+ }
4246
5883
  }
4247
- __name(applyModelServiceId, "applyModelServiceId");
5884
+ __name(applyApprovalMode, "applyApprovalMode");
5885
+ async function applyApprovalModeForAttach(entry, mode, clientId) {
5886
+ try {
5887
+ await applyApprovalMode(entry, mode, false, clientId);
5888
+ } catch (err) {
5889
+ await rollbackAttachRegistration(entry, clientId);
5890
+ throw err;
5891
+ }
5892
+ }
5893
+ __name(applyApprovalModeForAttach, "applyApprovalModeForAttach");
4248
5894
  const cancelPendingForSession = /* @__PURE__ */ __name((sessionId) => {
4249
5895
  permissionMediator.forgetSession(sessionId);
4250
5896
  byId.get(sessionId)?.pendingPermissionIds.clear();
5897
+ byId.get(sessionId)?.pendingInteractions.clear();
4251
5898
  }, "cancelPendingForSession");
4252
5899
  const getTransportClosedReject = /* @__PURE__ */ __name((entry) => {
4253
5900
  if (!entry.transportClosedReject) {
@@ -4529,10 +6176,16 @@ function createAcpSessionBridge(opts) {
4529
6176
  sessionId,
4530
6177
  workspaceCwd,
4531
6178
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6179
+ ...options.parentSessionId ? { parentSessionId: options.parentSessionId } : {},
4532
6180
  channel: ci.channel,
4533
6181
  connection: ci.connection,
4534
6182
  events,
4535
- artifacts: new SessionArtifactStore({ sessionId, workspaceCwd }),
6183
+ artifacts: new SessionArtifactStore({
6184
+ sessionId,
6185
+ workspaceCwd,
6186
+ persistence: createSessionArtifactPersistence(ci.connection, sessionId)
6187
+ }),
6188
+ recordingDegraded: false,
4536
6189
  promptQueue: Promise.resolve(),
4537
6190
  pendingPromptCount: 0,
4538
6191
  pendingPromptList: [],
@@ -4542,6 +6195,7 @@ function createAcpSessionBridge(opts) {
4542
6195
  modelPublishGeneration: 0,
4543
6196
  approvalModePublishGeneration: 0,
4544
6197
  pendingPermissionIds: /* @__PURE__ */ new Set(),
6198
+ pendingInteractions: /* @__PURE__ */ new Map(),
4545
6199
  clientIds: /* @__PURE__ */ new Map(),
4546
6200
  clientLastSeenAt: /* @__PURE__ */ new Map(),
4547
6201
  attachCount: 0,
@@ -4553,6 +6207,12 @@ function createAcpSessionBridge(opts) {
4553
6207
  byId.set(entry.sessionId, entry);
4554
6208
  touchActivity();
4555
6209
  telemetry.metrics?.sessionLifecycle("spawn");
6210
+ emitSessionLifecycle({
6211
+ type: "registered",
6212
+ sessionId: entry.sessionId,
6213
+ workspaceCwd: entry.workspaceCwd,
6214
+ reason: options.lifecycleReason ?? "spawn"
6215
+ });
4556
6216
  if (options.drainEarlyEvents !== false) {
4557
6217
  ci.client.drainEarlyEvents(entry.sessionId, entry);
4558
6218
  }
@@ -4567,6 +6227,42 @@ function createAcpSessionBridge(opts) {
4567
6227
  });
4568
6228
  }
4569
6229
  }, "publishArtifactChanges");
6230
+ const artifactReseedChanges = /* @__PURE__ */ __name((before, after) => {
6231
+ const beforeById = new Map(
6232
+ before.map((artifact) => [artifact.id, artifact])
6233
+ );
6234
+ const afterById = new Map(after.map((artifact) => [artifact.id, artifact]));
6235
+ const changes = [];
6236
+ for (const artifact of before) {
6237
+ if (!afterById.has(artifact.id)) {
6238
+ changes.push({
6239
+ action: "removed",
6240
+ artifactId: artifact.id,
6241
+ artifact,
6242
+ reason: "eviction"
6243
+ });
6244
+ }
6245
+ }
6246
+ for (const artifact of after) {
6247
+ const previous = beforeById.get(artifact.id);
6248
+ if (!previous) {
6249
+ changes.push({
6250
+ action: "created",
6251
+ artifactId: artifact.id,
6252
+ artifact
6253
+ });
6254
+ continue;
6255
+ }
6256
+ if (!publicArtifactsEqual(previous, artifact)) {
6257
+ changes.push({
6258
+ action: "updated",
6259
+ artifactId: artifact.id,
6260
+ artifact
6261
+ });
6262
+ }
6263
+ }
6264
+ return changes;
6265
+ }, "artifactReseedChanges");
4570
6266
  const makeClientArtifactInput = /* @__PURE__ */ __name((artifact, clientId) => {
4571
6267
  const input = {
4572
6268
  title: artifact.title,
@@ -4579,6 +6275,8 @@ function createAcpSessionBridge(opts) {
4579
6275
  mimeType: artifact.mimeType,
4580
6276
  sizeBytes: artifact.sizeBytes,
4581
6277
  metadata: artifact.metadata,
6278
+ retention: artifact.retention,
6279
+ clientRetained: artifact.clientRetained,
4582
6280
  source: "client"
4583
6281
  };
4584
6282
  if (clientId) {
@@ -4586,6 +6284,31 @@ function createAcpSessionBridge(opts) {
4586
6284
  }
4587
6285
  return input;
4588
6286
  }, "makeClientArtifactInput");
6287
+ function createSessionArtifactPersistence(connection, sessionId) {
6288
+ return {
6289
+ recordEvent: /* @__PURE__ */ __name(async (payload) => {
6290
+ await connection.extMethod(
6291
+ SERVE_CONTROL_EXT_METHODS.sessionArtifactsPersist,
6292
+ {
6293
+ sessionId,
6294
+ kind: "event",
6295
+ payload
6296
+ }
6297
+ );
6298
+ }, "recordEvent"),
6299
+ recordSnapshot: /* @__PURE__ */ __name(async (payload) => {
6300
+ await connection.extMethod(
6301
+ SERVE_CONTROL_EXT_METHODS.sessionArtifactsPersist,
6302
+ {
6303
+ sessionId,
6304
+ kind: "snapshot",
6305
+ payload
6306
+ }
6307
+ );
6308
+ }, "recordSnapshot")
6309
+ };
6310
+ }
6311
+ __name(createSessionArtifactPersistence, "createSessionArtifactPersistence");
4589
6312
  const seedSnapshotCaches = /* @__PURE__ */ __name((entry, resp) => {
4590
6313
  const model = resp.models?.currentModelId;
4591
6314
  if (typeof model === "string" && model.length > 0) {
@@ -4633,16 +6356,55 @@ function createAcpSessionBridge(opts) {
4633
6356
  }
4634
6357
  return { lastEventId: snapshot.lastEventId, ...replayStatus };
4635
6358
  }, "replayFieldsFor");
6359
+ const restoredArtifactSnapshotFromState = /* @__PURE__ */ __name((state) => {
6360
+ const candidate = state.artifactSnapshot;
6361
+ const warnings = [];
6362
+ const snapshot = normalizeSnapshotPayload(candidate, warnings);
6363
+ if (!snapshot) return void 0;
6364
+ const snapshotWarnings = isRecord2(candidate) && Array.isArray(candidate["warnings"]) ? candidate["warnings"].filter(
6365
+ (warning) => typeof warning === "string" && warning.length <= 1e3
6366
+ ).slice(-500) : [];
6367
+ return {
6368
+ v: SESSION_ARTIFACT_PERSISTENCE_VERSION,
6369
+ sessionId: snapshot.sessionId,
6370
+ sequence: snapshot.sequence,
6371
+ artifacts: snapshot.artifacts,
6372
+ ...snapshot.markerArtifacts ? { markerArtifacts: snapshot.markerArtifacts } : {},
6373
+ tombstonedIds: snapshot.tombstonedIds ?? [],
6374
+ stickyEphemeralIds: snapshot.stickyEphemeralIds ?? [],
6375
+ warnings: [...warnings, ...snapshotWarnings]
6376
+ };
6377
+ }, "restoredArtifactSnapshotFromState");
6378
+ const artifactSnapshotUnavailableReason = /* @__PURE__ */ __name((state) => {
6379
+ const reason = state.artifactSnapshotUnavailable;
6380
+ return typeof reason === "string" && reason ? reason : void 0;
6381
+ }, "artifactSnapshotUnavailableReason");
6382
+ const publicRestoreState = /* @__PURE__ */ __name((state) => {
6383
+ const {
6384
+ artifactSnapshot: _artifactSnapshot,
6385
+ artifactSnapshotUnavailable: _artifactSnapshotUnavailable,
6386
+ ...publicState
6387
+ } = state;
6388
+ return publicState;
6389
+ }, "publicRestoreState");
4636
6390
  async function restoreSession(action, req, options = {}) {
4637
6391
  if (shuttingDown) {
4638
6392
  throw new Error("AcpSessionBridge is shutting down");
4639
6393
  }
4640
6394
  const workspaceKey = resolveWorkspaceKey(req.workspaceCwd);
6395
+ if (req.approvalMode !== void 0 && !KNOWN_APPROVAL_MODES.has(req.approvalMode)) {
6396
+ throw new Error(
6397
+ `Invalid approvalMode: ${JSON.stringify(req.approvalMode)}`
6398
+ );
6399
+ }
4641
6400
  const historyReplay = action === "load" ? req.historyReplay ?? "stream" : "stream";
4642
6401
  const existing = byId.get(req.sessionId);
4643
6402
  if (existing) {
4644
6403
  existing.attachCount++;
4645
6404
  const clientId = registerClient(existing, req.clientId);
6405
+ if (req.approvalMode) {
6406
+ await applyApprovalModeForAttach(existing, req.approvalMode, clientId);
6407
+ }
4646
6408
  return {
4647
6409
  sessionId: existing.sessionId,
4648
6410
  workspaceCwd: existing.workspaceCwd,
@@ -4681,10 +6443,14 @@ function createAcpSessionBridge(opts) {
4681
6443
  "the agent child likely crashed during session restore \u2014 retry to restore the session"
4682
6444
  );
4683
6445
  }
6446
+ const clientId = registerClient(entry, req.clientId);
6447
+ if (req.approvalMode) {
6448
+ await applyApprovalModeForAttach(entry, req.approvalMode, clientId);
6449
+ }
4684
6450
  return {
4685
6451
  ...restored,
4686
6452
  attached: true,
4687
- clientId: registerClient(entry, req.clientId),
6453
+ clientId,
4688
6454
  createdAt: entry.createdAt,
4689
6455
  hasActivePrompt: entry.promptActive
4690
6456
  };
@@ -4794,6 +6560,23 @@ function createAcpSessionBridge(opts) {
4794
6560
  restoreEvents.close();
4795
6561
  racedEntry.attachCount += 1 + coalesceState.count;
4796
6562
  const clientId2 = registerClient(racedEntry, req.clientId);
6563
+ if (req.approvalMode) {
6564
+ try {
6565
+ await applyApprovalMode(
6566
+ racedEntry,
6567
+ req.approvalMode,
6568
+ false,
6569
+ clientId2
6570
+ );
6571
+ } catch (err) {
6572
+ await rollbackAttachRegistration(
6573
+ racedEntry,
6574
+ clientId2,
6575
+ 1 + coalesceState.count
6576
+ );
6577
+ throw err;
6578
+ }
6579
+ }
4797
6580
  return {
4798
6581
  sessionId: racedEntry.sessionId,
4799
6582
  workspaceCwd: racedEntry.workspaceCwd,
@@ -4810,22 +6593,48 @@ function createAcpSessionBridge(opts) {
4810
6593
  req.sessionId,
4811
6594
  workspaceKey,
4812
6595
  restoreEvents,
4813
- { drainEarlyEvents: replayUpdates.length === 0 }
6596
+ {
6597
+ drainEarlyEvents: replayUpdates.length === 0,
6598
+ lifecycleReason: action,
6599
+ // Re-seed the persisted parent lineage the caller recovered from the
6600
+ // transcript, so a restored sub-session's status reports its parent.
6601
+ ...req.parentSessionId ? { parentSessionId: req.parentSessionId } : {}
6602
+ }
4814
6603
  );
4815
6604
  releaseAdmissionOnce();
4816
- entry.restoreState = state;
6605
+ const restoredArtifactSnapshot = restoredArtifactSnapshotFromState(state);
6606
+ const publicState = publicRestoreState(state);
6607
+ entry.restoreState = publicState;
4817
6608
  if (replayPartial === true) {
4818
6609
  entry.restoreReplayPartial = true;
4819
6610
  }
4820
6611
  if (replayError !== void 0) {
4821
6612
  entry.restoreReplayError = replayError;
4822
6613
  }
4823
- seedSnapshotCaches(entry, state);
6614
+ seedSnapshotCaches(entry, publicState);
6615
+ const artifactRestoreWarnings = await entry.artifacts.restore(
6616
+ restoredArtifactSnapshot
6617
+ );
6618
+ for (const warning of artifactRestoreWarnings) {
6619
+ writeStderrLine(
6620
+ `[artifacts] session=${entry.sessionId} action=restore_warning warning=${JSON.stringify(
6621
+ warning
6622
+ )}`
6623
+ );
6624
+ }
6625
+ const artifactRestoreFailed = artifactRestoreWarnings.some(
6626
+ (warning) => isArtifactRestoreFailureWarning(warning)
6627
+ );
4824
6628
  if (replayUpdates.length > 0) {
4825
- await ci.client.seedSessionUpdates(entry, replayUpdates);
6629
+ await ci.client.seedSessionUpdates(entry, replayUpdates, {
6630
+ ingestArtifacts: restoredArtifactSnapshot === void 0 || artifactRestoreFailed
6631
+ });
4826
6632
  ci.client.drainEarlyEvents(entry.sessionId, entry);
4827
6633
  }
4828
6634
  const clientId = registerClient(entry, req.clientId);
6635
+ if (req.approvalMode) {
6636
+ await applyApprovalModeForAttach(entry, req.approvalMode, clientId);
6637
+ }
4829
6638
  entry.attachCount = coalesceState.count;
4830
6639
  registeredEntry = entry;
4831
6640
  return {
@@ -4834,7 +6643,8 @@ function createAcpSessionBridge(opts) {
4834
6643
  attached: false,
4835
6644
  clientId,
4836
6645
  createdAt: entry.createdAt,
4837
- state,
6646
+ state: publicState,
6647
+ ...artifactRestoreWarnings.length > 0 ? { artifactWarnings: artifactRestoreWarnings } : {},
4838
6648
  hasActivePrompt: entry.promptActive,
4839
6649
  ...replayFieldsFor(entry, action)
4840
6650
  };
@@ -4846,9 +6656,16 @@ function createAcpSessionBridge(opts) {
4846
6656
  if (!registeredEntry) {
4847
6657
  restoreEvents.close();
4848
6658
  let removedRestoreEntry = false;
4849
- if (byId.get(req.sessionId)?.events === restoreEvents) {
6659
+ const restoreEntry = byId.get(req.sessionId);
6660
+ if (restoreEntry?.events === restoreEvents) {
4850
6661
  byId.delete(req.sessionId);
4851
6662
  ci?.sessionIds.delete(req.sessionId);
6663
+ emitSessionLifecycle({
6664
+ type: "removed",
6665
+ sessionId: req.sessionId,
6666
+ workspaceCwd: restoreEntry.workspaceCwd,
6667
+ reason: "restore_failed"
6668
+ });
4852
6669
  removedRestoreEntry = true;
4853
6670
  }
4854
6671
  if (removedRestoreEntry && ci && hasNoChannelWork(ci)) {
@@ -4909,6 +6726,7 @@ function createAcpSessionBridge(opts) {
4909
6726
  }
4910
6727
  permissionMediator.forgetSession(sessionId);
4911
6728
  entry.pendingPermissionIds.clear();
6729
+ entry.pendingInteractions.clear();
4912
6730
  if (entry.promptActive) {
4913
6731
  entry.promptActive = false;
4914
6732
  activePromptCounter--;
@@ -4916,6 +6734,12 @@ function createAcpSessionBridge(opts) {
4916
6734
  }
4917
6735
  byId.delete(sessionId);
4918
6736
  telemetry.metrics?.sessionLifecycle("close");
6737
+ emitSessionLifecycle({
6738
+ type: "removed",
6739
+ sessionId,
6740
+ workspaceCwd: entry.workspaceCwd,
6741
+ reason
6742
+ });
4919
6743
  ci?.client.markSessionClosed(sessionId);
4920
6744
  try {
4921
6745
  entry.events.publish({
@@ -5039,6 +6863,11 @@ function createAcpSessionBridge(opts) {
5039
6863
  throw new InvalidSessionScopeError(req.sessionScope);
5040
6864
  }
5041
6865
  const effectiveScope = req.sessionScope ?? defaultSessionScope;
6866
+ if (req.approvalMode !== void 0 && !KNOWN_APPROVAL_MODES.has(req.approvalMode)) {
6867
+ throw new Error(
6868
+ `Invalid approvalMode: ${JSON.stringify(req.approvalMode)}`
6869
+ );
6870
+ }
5042
6871
  if (effectiveScope === "single") {
5043
6872
  const existing = defaultEntry;
5044
6873
  if (existing) {
@@ -5053,6 +6882,13 @@ function createAcpSessionBridge(opts) {
5053
6882
  ).catch(() => {
5054
6883
  });
5055
6884
  }
6885
+ if (req.approvalMode) {
6886
+ await applyApprovalModeForAttach(
6887
+ existing,
6888
+ req.approvalMode,
6889
+ clientId
6890
+ );
6891
+ }
5056
6892
  return {
5057
6893
  sessionId: existing.sessionId,
5058
6894
  workspaceCwd: existing.workspaceCwd,
@@ -5083,6 +6919,13 @@ function createAcpSessionBridge(opts) {
5083
6919
  ).catch(() => {
5084
6920
  });
5085
6921
  }
6922
+ if (req.approvalMode) {
6923
+ await applyApprovalModeForAttach(
6924
+ attachedEntry,
6925
+ req.approvalMode,
6926
+ clientId
6927
+ );
6928
+ }
5086
6929
  return {
5087
6930
  ...session,
5088
6931
  attached: true,
@@ -5107,8 +6950,10 @@ function createAcpSessionBridge(opts) {
5107
6950
  const promise = doSpawn(
5108
6951
  req.modelServiceId,
5109
6952
  effectiveScope,
6953
+ req.approvalMode,
5110
6954
  req.clientId,
5111
- releaseAdmissionOnce
6955
+ releaseAdmissionOnce,
6956
+ req.parentSessionId
5112
6957
  );
5113
6958
  const tracker = effectiveScope === "single" ? workspaceKey : `${workspaceKey}#${randomUUID2()}`;
5114
6959
  inFlightSpawns.set(tracker, promise);
@@ -5249,6 +7094,7 @@ function createAcpSessionBridge(opts) {
5249
7094
  return copy;
5250
7095
  })();
5251
7096
  entry.promptActive = true;
7097
+ delete entry.turnError;
5252
7098
  activePromptCounter++;
5253
7099
  entry.sessionLastSeenAt = Date.now();
5254
7100
  touchActivity();
@@ -5456,7 +7302,8 @@ function createAcpSessionBridge(opts) {
5456
7302
  data: {
5457
7303
  sessionId: entry.sessionId,
5458
7304
  currentModelId: entry.currentModelId ?? null,
5459
- currentApprovalMode: entry.currentApprovalMode ?? null
7305
+ currentApprovalMode: entry.currentApprovalMode ?? null,
7306
+ recordingDegraded: entry.recordingDegraded
5460
7307
  }
5461
7308
  }), "snapshotFrame");
5462
7309
  async function* withSnapshot() {
@@ -5750,7 +7597,7 @@ function createAcpSessionBridge(opts) {
5750
7597
  const r = res;
5751
7598
  if (r && r.persisted === false) {
5752
7599
  writeStderrLine(
5753
- `qwen serve: displayName for ${sessionId} was not persisted (recording service unavailable)`
7600
+ `qwen serve: displayName for ${sessionId} was not persisted`
5754
7601
  );
5755
7602
  }
5756
7603
  }).catch((err) => {
@@ -5771,9 +7618,10 @@ function createAcpSessionBridge(opts) {
5771
7618
  }
5772
7619
  return { displayName: entry.displayName };
5773
7620
  },
5774
- async getSessionArtifacts(sessionId) {
7621
+ async getSessionArtifacts(sessionId, context) {
5775
7622
  const entry = byId.get(sessionId);
5776
7623
  if (!entry) throw new SessionNotFoundError(sessionId);
7624
+ resolveTrustedClientId(entry, context?.clientId);
5777
7625
  return entry.artifacts.list();
5778
7626
  },
5779
7627
  async addSessionArtifact(sessionId, artifact, context) {
@@ -5781,9 +7629,13 @@ function createAcpSessionBridge(opts) {
5781
7629
  if (!entry) throw new SessionNotFoundError(sessionId);
5782
7630
  const clientId = resolveTrustedClientId(entry, context?.clientId);
5783
7631
  const input = makeClientArtifactInput(artifact, clientId);
5784
- const result = await entry.artifacts.upsertMany([input], { strict: true });
7632
+ const result = await entry.artifacts.upsertMany([input], {
7633
+ validationStrict: true,
7634
+ persistenceStrict: false
7635
+ });
5785
7636
  publishArtifactChanges(entry, result.changes, clientId);
5786
- return result;
7637
+ const warnings = [...result.warnings ?? []];
7638
+ return warnings.length > 0 ? { ...result, warnings } : result;
5787
7639
  },
5788
7640
  async removeSessionArtifact(sessionId, artifactId, context) {
5789
7641
  const entry = byId.get(sessionId);
@@ -5791,7 +7643,8 @@ function createAcpSessionBridge(opts) {
5791
7643
  const clientId = resolveTrustedClientId(entry, context?.clientId);
5792
7644
  const result = await entry.artifacts.remove(artifactId, { clientId });
5793
7645
  publishArtifactChanges(entry, result.changes, clientId);
5794
- return result;
7646
+ const warnings = [...result.warnings ?? []];
7647
+ return warnings.length > 0 ? { ...result, warnings } : result;
5795
7648
  },
5796
7649
  listWorkspaceSessions(workspaceCwd) {
5797
7650
  if (!path3.isAbsolute(workspaceCwd)) return [];
@@ -6076,6 +7929,35 @@ function createAcpSessionBridge(opts) {
6076
7929
  SERVE_STATUS_EXT_METHODS.sessionLspStatus
6077
7930
  );
6078
7931
  },
7932
+ async getSessionTranscriptPage(req) {
7933
+ const info = await ensureChannel();
7934
+ try {
7935
+ const response = await withWorkspaceControl(
7936
+ info,
7937
+ () => withTimeout(
7938
+ Promise.race([
7939
+ info.connection.extMethod(
7940
+ SERVE_STATUS_EXT_METHODS.sessionTranscript,
7941
+ { ...req, cwd: boundWorkspace }
7942
+ ),
7943
+ getChannelClosedReject(info)
7944
+ ]),
7945
+ Math.max(initTimeoutMs, SESSION_TRANSCRIPT_TIMEOUT_MS),
7946
+ SERVE_STATUS_EXT_METHODS.sessionTranscript
7947
+ )
7948
+ );
7949
+ return response;
7950
+ } catch (err) {
7951
+ if (isAcpSessionResourceNotFound(err, req.sessionId)) {
7952
+ throw new SessionNotFoundError(req.sessionId);
7953
+ }
7954
+ throw err;
7955
+ } finally {
7956
+ if (hasNoChannelWork(info)) {
7957
+ await startIdleTimer(info, "session transcript");
7958
+ }
7959
+ }
7960
+ },
6079
7961
  async cancelSessionTask(sessionId, taskId, taskKind) {
6080
7962
  return requestSessionStatus(
6081
7963
  sessionId,
@@ -6155,12 +8037,26 @@ function createAcpSessionBridge(opts) {
6155
8037
  return { refreshed: 0, failed: 0 };
6156
8038
  }
6157
8039
  try {
6158
- await Promise.race([
6159
- withTimeout(
6160
- entry.connection.extMethod(
8040
+ let inFlight = inFlightExtensionRefreshes.get(entry.sessionId);
8041
+ if (!inFlight || inFlight.connection !== entry.connection) {
8042
+ const promise = (async () => {
8043
+ await entry.connection.extMethod(
6161
8044
  SERVE_CONTROL_EXT_METHODS.workspaceExtensionsRefresh,
6162
8045
  { sessionId: entry.sessionId }
6163
- ),
8046
+ );
8047
+ })();
8048
+ inFlight = { connection: entry.connection, promise };
8049
+ inFlightExtensionRefreshes.set(entry.sessionId, inFlight);
8050
+ const clear = /* @__PURE__ */ __name(() => {
8051
+ if (inFlightExtensionRefreshes.get(entry.sessionId) === inFlight) {
8052
+ inFlightExtensionRefreshes.delete(entry.sessionId);
8053
+ }
8054
+ }, "clear");
8055
+ void promise.then(clear, clear);
8056
+ }
8057
+ await Promise.race([
8058
+ withTimeout(
8059
+ inFlight.promise,
6164
8060
  3e4,
6165
8061
  SERVE_CONTROL_EXT_METHODS.workspaceExtensionsRefresh
6166
8062
  ),
@@ -6314,109 +8210,12 @@ function createAcpSessionBridge(opts) {
6314
8210
  entry,
6315
8211
  context?.clientId
6316
8212
  );
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
8213
+ return await applyApprovalMode(
8214
+ entry,
8215
+ mode,
8216
+ opts2.persist,
8217
+ originatorClientId
6408
8218
  );
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
8219
  },
6421
8220
  async generateSessionRecap(sessionId, _context) {
6422
8221
  const entry = byId.get(sessionId);
@@ -6769,6 +8568,48 @@ function createAcpSessionBridge(opts) {
6769
8568
  const targetTurnIndex = response["targetTurnIndex"] ?? 0;
6770
8569
  const filesChanged = response["filesChanged"] ?? [];
6771
8570
  const filesFailed = response["filesFailed"] ?? [];
8571
+ const artifactSnapshot = restoredArtifactSnapshotFromState(
8572
+ response
8573
+ );
8574
+ const artifactSnapshotUnavailable = artifactSnapshotUnavailableReason(
8575
+ response
8576
+ );
8577
+ const beforeArtifacts = (await entry.artifacts.list()).artifacts;
8578
+ const shouldRestoreArtifactSnapshot = artifactSnapshot !== void 0 && artifactSnapshotUnavailable === void 0;
8579
+ const artifactRestoreWarnings = artifactSnapshotUnavailable !== void 0 ? [
8580
+ `artifact snapshot rebuild unavailable during rewind: ${artifactSnapshotUnavailable}`
8581
+ ] : shouldRestoreArtifactSnapshot ? await entry.artifacts.restore(artifactSnapshot, {
8582
+ preserveLiveEphemeral: true
8583
+ }) : [];
8584
+ const artifactRestoreFailed = artifactRestoreWarnings.some(
8585
+ isArtifactRestoreFailureWarning
8586
+ );
8587
+ const shouldRecordArtifactSnapshot = shouldRestoreArtifactSnapshot && !artifactRestoreFailed;
8588
+ const artifactSnapshotWarnings = shouldRecordArtifactSnapshot ? await entry.artifacts.recordSnapshot() : [];
8589
+ const artifactWarnings = [
8590
+ ...artifactRestoreWarnings,
8591
+ ...artifactSnapshotWarnings
8592
+ ];
8593
+ for (const warning of artifactRestoreWarnings) {
8594
+ writeStderrLine(
8595
+ `[artifacts] session=${entry.sessionId} action=rewind_restore_warning warning=${JSON.stringify(
8596
+ warning
8597
+ )}`
8598
+ );
8599
+ }
8600
+ for (const warning of artifactSnapshotWarnings) {
8601
+ writeStderrLine(
8602
+ `[artifacts] session=${entry.sessionId} action=rewind_snapshot_warning warning=${JSON.stringify(
8603
+ warning
8604
+ )}`
8605
+ );
8606
+ }
8607
+ const afterArtifacts = (await entry.artifacts.list()).artifacts;
8608
+ publishArtifactChanges(
8609
+ entry,
8610
+ artifactReseedChanges(beforeArtifacts, afterArtifacts),
8611
+ originatorClientId
8612
+ );
6772
8613
  try {
6773
8614
  entry.events.publish({
6774
8615
  type: "session_rewound",
@@ -6777,7 +8618,8 @@ function createAcpSessionBridge(opts) {
6777
8618
  promptId: req.promptId,
6778
8619
  targetTurnIndex,
6779
8620
  filesChanged,
6780
- filesFailed
8621
+ filesFailed,
8622
+ ...artifactWarnings.length > 0 ? { warnings: artifactWarnings } : {}
6781
8623
  },
6782
8624
  ...originatorClientId ? { originatorClientId } : {}
6783
8625
  });
@@ -6787,7 +8629,8 @@ function createAcpSessionBridge(opts) {
6787
8629
  rewound: filesFailed.length === 0,
6788
8630
  targetTurnIndex,
6789
8631
  filesChanged,
6790
- filesFailed
8632
+ filesFailed,
8633
+ ...artifactWarnings.length > 0 ? { warnings: artifactWarnings } : {}
6791
8634
  };
6792
8635
  },
6793
8636
  async manageMcpServer(serverName, action, originatorClientId) {
@@ -6915,6 +8758,7 @@ function createAcpSessionBridge(opts) {
6915
8758
  }
6916
8759
  permissionMediator.forgetSession(sessionId);
6917
8760
  entry.pendingPermissionIds.clear();
8761
+ entry.pendingInteractions.clear();
6918
8762
  if (entry.promptActive) {
6919
8763
  entry.promptActive = false;
6920
8764
  activePromptCounter--;
@@ -6923,6 +8767,12 @@ function createAcpSessionBridge(opts) {
6923
8767
  if (defaultEntry === entry) defaultEntry = void 0;
6924
8768
  byId.delete(sessionId);
6925
8769
  telemetry.metrics?.sessionLifecycle("die");
8770
+ emitSessionLifecycle({
8771
+ type: "removed",
8772
+ sessionId,
8773
+ workspaceCwd: entry.workspaceCwd,
8774
+ reason: "killed"
8775
+ });
6926
8776
  const ci = channelInfoForEntry(entry);
6927
8777
  if (!ci) {
6928
8778
  writeStderrLine(
@@ -6972,8 +8822,17 @@ function createAcpSessionBridge(opts) {
6972
8822
  cancelIdleTimer();
6973
8823
  stopSessionReaper();
6974
8824
  const channels = Array.from(aliveChannels);
8825
+ const entries = Array.from(byId.values());
6975
8826
  defaultEntry = void 0;
6976
8827
  byId.clear();
8828
+ for (const entry of entries) {
8829
+ emitSessionLifecycle({
8830
+ type: "removed",
8831
+ sessionId: entry.sessionId,
8832
+ workspaceCwd: entry.workspaceCwd,
8833
+ reason: "kill_all"
8834
+ });
8835
+ }
6977
8836
  for (const info of channels) {
6978
8837
  try {
6979
8838
  info.channel.killSync();
@@ -6981,53 +8840,71 @@ function createAcpSessionBridge(opts) {
6981
8840
  }
6982
8841
  }
6983
8842
  },
6984
- async shutdown() {
6985
- shuttingDown = true;
6986
- cancelIdleTimer();
6987
- stopSessionReaper();
6988
- const entries = Array.from(byId.values());
6989
- const channels = Array.from(aliveChannels);
6990
- for (const ci of channels) ci.isDying = true;
6991
- for (const e of entries) {
6992
- permissionMediator.forgetSession(e.sessionId);
6993
- e.pendingPermissionIds.clear();
6994
- }
6995
- defaultEntry = void 0;
6996
- byId.clear();
6997
- for (const e of entries) {
6998
- telemetry.metrics?.sessionLifecycle("die");
6999
- try {
7000
- e.events.publish({
7001
- type: "session_died",
7002
- data: { sessionId: e.sessionId, reason: "daemon_shutdown" }
8843
+ shutdown(options) {
8844
+ if (shutdownPromise) return shutdownPromise;
8845
+ const shutdownReason = options?.reason ?? "daemon_shutdown";
8846
+ let resolveShutdown;
8847
+ let rejectShutdown;
8848
+ shutdownPromise = new Promise((resolve2, reject) => {
8849
+ resolveShutdown = resolve2;
8850
+ rejectShutdown = reject;
8851
+ });
8852
+ void (async () => {
8853
+ shuttingDown = true;
8854
+ cancelIdleTimer();
8855
+ stopSessionReaper();
8856
+ const entries = Array.from(byId.values());
8857
+ const channels = Array.from(aliveChannels);
8858
+ for (const ci of channels) ci.isDying = true;
8859
+ for (const e of entries) {
8860
+ permissionMediator.forgetSession(e.sessionId);
8861
+ e.pendingPermissionIds.clear();
8862
+ e.pendingInteractions.clear();
8863
+ }
8864
+ defaultEntry = void 0;
8865
+ byId.clear();
8866
+ for (const e of entries) {
8867
+ telemetry.metrics?.sessionLifecycle("die");
8868
+ emitSessionLifecycle({
8869
+ type: "removed",
8870
+ sessionId: e.sessionId,
8871
+ workspaceCwd: e.workspaceCwd,
8872
+ reason: shutdownReason
7003
8873
  });
7004
- } catch {
8874
+ try {
8875
+ e.events.publish({
8876
+ type: "session_died",
8877
+ data: { sessionId: e.sessionId, reason: shutdownReason }
8878
+ });
8879
+ } catch {
8880
+ }
8881
+ e.events.close();
7005
8882
  }
7006
- e.events.close();
7007
- }
7008
- const inFlightSessionAwaits = Array.from(inFlightSpawns.values()).map(
7009
- (p) => p.then(
7010
- () => void 0,
7011
- () => void 0
7012
- )
7013
- );
7014
- const inFlightRestoreAwaits = Array.from(inFlightRestores.values()).map(
7015
- (restore) => restore.promise.then(
8883
+ const inFlightSessionAwaits = Array.from(inFlightSpawns.values()).map(
8884
+ (p) => p.then(
8885
+ () => void 0,
8886
+ () => void 0
8887
+ )
8888
+ );
8889
+ const inFlightRestoreAwaits = Array.from(inFlightRestores.values()).map(
8890
+ (restore) => restore.promise.then(
8891
+ () => void 0,
8892
+ () => void 0
8893
+ )
8894
+ );
8895
+ const inFlightChannelAwait = inFlightChannelSpawn ? inFlightChannelSpawn.then(
7016
8896
  () => void 0,
7017
8897
  () => void 0
7018
- )
7019
- );
7020
- const inFlightChannelAwait = inFlightChannelSpawn ? inFlightChannelSpawn.then(
7021
- () => void 0,
7022
- () => void 0
7023
- ) : Promise.resolve();
7024
- await Promise.all([
7025
- ...channels.map((ci) => ci.channel.kill().catch(() => {
7026
- })),
7027
- ...inFlightSessionAwaits,
7028
- ...inFlightRestoreAwaits,
7029
- inFlightChannelAwait
7030
- ]);
8898
+ ) : Promise.resolve();
8899
+ await Promise.all([
8900
+ ...channels.map((ci) => ci.channel.kill().catch(() => {
8901
+ })),
8902
+ ...inFlightSessionAwaits,
8903
+ ...inFlightRestoreAwaits,
8904
+ inFlightChannelAwait
8905
+ ]);
8906
+ })().then(resolveShutdown, rejectShutdown);
8907
+ return shutdownPromise;
7031
8908
  },
7032
8909
  async preheat() {
7033
8910
  if (shuttingDown) return;
@@ -7057,6 +8934,7 @@ var createHttpAcpBridge = createAcpSessionBridge;
7057
8934
 
7058
8935
  export {
7059
8936
  SessionArtifactValidationError,
8937
+ SessionArtifactAuthorizationError,
7060
8938
  extractErrorMessage,
7061
8939
  extractErrorCode,
7062
8940
  classifyTurnErrorKind,