@qwen-code/qwen-code 0.19.8 → 0.19.9-nightly.20260714.9dd8389eb

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 (449) 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 +61 -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/tool-use-summaries.md +19 -23
  14. package/bundled/qc-helper/docs/integration-vscode.md +2 -2
  15. package/bundled/qc-helper/docs/qwen-serve.md +60 -22
  16. package/bundled/qc-helper/docs/reference/keyboard-shortcuts.md +2 -2
  17. package/bundled/review/DESIGN.md +303 -46
  18. package/bundled/review/SKILL.md +658 -165
  19. package/chunks/MaxSizedBox-QAYHOC4F.js +71 -0
  20. package/chunks/{StandaloneSessionPicker-EYRGH6PJ.js → StandaloneSessionPicker-D3R6YYX3.js} +82 -79
  21. package/chunks/{acpAgent-GRFTTRHW.js → acpAgent-ENHBW72A.js} +1178 -421
  22. package/chunks/agent-57LX4KMG.js +69 -0
  23. package/chunks/agent-headless-AC6EVNAP.js +63 -0
  24. package/chunks/{anthropicContentGenerator-CPF22MA7.js → anthropicContentGenerator-56U3YCP2.js} +187 -31
  25. package/chunks/{artifact-tool-D233YJFH.js → artifact-tool-BWZZJ42H.js} +5 -5
  26. package/chunks/{askUserQuestion-MKL5ZTMC.js → askUserQuestion-WU3S5A5T.js} +6 -6
  27. package/chunks/bridge-4OZH44MK.js +79 -0
  28. package/chunks/{ca-MKD32FQL.js → ca-C2JTT2N5.js} +68 -6
  29. package/chunks/channel-worker-group-K7QKH6IG.js +13 -0
  30. package/chunks/channel-worker-manager-5RWKSGEU.js +418 -0
  31. package/chunks/channel-worker-supervisor-PCDAZN4D.js +20 -0
  32. package/chunks/{chunk-CSOTA7KX.js → chunk-2FO5GFKZ.js} +2 -2
  33. package/chunks/{chunk-QQDPRDVW.js → chunk-2HRYPZT5.js} +1 -1
  34. package/chunks/{chunk-I6WGFJJ3.js → chunk-2MF6ICOL.js} +38 -12
  35. package/chunks/{chunk-X3YGAX7V.js → chunk-2UUAIFQO.js} +4 -2
  36. package/chunks/{chunk-2PSWA5ID.js → chunk-2X56YUSZ.js} +1 -1
  37. package/chunks/{chunk-NQKLOAVJ.js → chunk-2Z5UX4KI.js} +2 -2
  38. package/chunks/chunk-3MX7D6QN.js +516 -0
  39. package/chunks/{chunk-YDOXPLYL.js → chunk-4DYYRLIE.js} +3890 -975
  40. package/chunks/{chunk-OQXDAMKY.js → chunk-4FPUAYTT.js} +8 -8
  41. package/chunks/{chunk-55ZMG67I.js → chunk-4LD5APFL.js} +4 -4
  42. package/chunks/{chunk-YKZIAK7C.js → chunk-4LJNI7MU.js} +166 -37
  43. package/chunks/{chunk-BOBOCR5H.js → chunk-4QOPEEL2.js} +4 -4
  44. package/chunks/{chunk-UIDXQNMV.js → chunk-4WGODMVR.js} +1 -1
  45. package/chunks/{chunk-E6US47KI.js → chunk-4WKAA4KS.js} +1 -1
  46. package/chunks/{chunk-3GONHQOA.js → chunk-57OAHC2Y.js} +1 -1
  47. package/chunks/{chunk-JZSY4WP3.js → chunk-5DTTYNB7.js} +1 -1
  48. package/chunks/chunk-5HBA2Z7V.js +28 -0
  49. package/chunks/{chunk-SKAZWEV5.js → chunk-5M6IDOMF.js} +35 -98
  50. package/chunks/{chunk-A4BMJM77.js → chunk-5O2XNYP6.js} +1 -0
  51. package/chunks/{chunk-GO6LNQXT.js → chunk-6AM5D4ME.js} +1 -1
  52. package/chunks/{chunk-BJBWRCSK.js → chunk-6H67XLET.js} +1 -1
  53. package/chunks/{chunk-AYNCLGTW.js → chunk-6HHAADP2.js} +8 -51
  54. package/chunks/{chunk-VZ7HY3YU.js → chunk-6OATMEYN.js} +3 -3
  55. package/chunks/{chunk-3UP777ZS.js → chunk-6RLROE6G.js} +1 -1
  56. package/chunks/{chunk-ZERZSAZL.js → chunk-75DOP5OR.js} +2 -2
  57. package/chunks/{chunk-RMLZAUWH.js → chunk-7BIZARI2.js} +10 -12
  58. package/chunks/{chunk-GN3F6PIU.js → chunk-7NAWTFVY.js} +5 -5
  59. package/chunks/{chunk-22MNNNXE.js → chunk-7NFB7LBQ.js} +11 -3
  60. package/chunks/{chunk-ZL5HN75Q.js → chunk-7R4DPSRA.js} +37 -3
  61. package/chunks/{chunk-BVYU3ZTI.js → chunk-7U7UIO5W.js} +1005 -942
  62. package/chunks/{chunk-SAIILPQ3.js → chunk-7X7TEQNX.js} +5 -3
  63. package/chunks/{chunk-IRJWHZWU.js → chunk-A4UCTEB5.js} +2 -2
  64. package/chunks/{chunk-WF4FAXDL.js → chunk-AAS2Q2TV.js} +62 -62
  65. package/chunks/{chunk-O7FIRWJV.js → chunk-AAWKZ5R7.js} +3 -3
  66. package/chunks/{chunk-JCH35JB3.js → chunk-AGBFWLA5.js} +2 -2
  67. package/chunks/{chunk-QYUE6W3T.js → chunk-ALGGS7UH.js} +2 -39
  68. package/chunks/{chunk-WMPVYQ4P.js → chunk-ARKANCNX.js} +3 -2
  69. package/chunks/{chunk-HDOH3QYE.js → chunk-ASVORVFT.js} +2 -2
  70. package/chunks/{chunk-CHB5SLZZ.js → chunk-AUGSSJX3.js} +3 -3
  71. package/chunks/{chunk-PUVCVELY.js → chunk-B6ABUBYY.js} +4 -4
  72. package/chunks/{chunk-L4AMBVQG.js → chunk-B6KHOCU7.js} +49 -21
  73. package/chunks/{chunk-HW4N45EE.js → chunk-B7NUUEXC.js} +38 -11
  74. package/chunks/{chunk-CD6USWHZ.js → chunk-BBNVFRGD.js} +2 -2
  75. package/chunks/{chunk-ZN5T4BHI.js → chunk-BPZALHVR.js} +2 -2
  76. package/chunks/{chunk-VGGOPZUL.js → chunk-C4CXFRKY.js} +12 -5
  77. package/chunks/{chunk-OFEVLU4C.js → chunk-CARU2RR2.js} +1 -1
  78. package/chunks/{chunk-SQ2UATK7.js → chunk-CLTWL6HD.js} +11 -11
  79. package/chunks/{chunk-GLE2YUPU.js → chunk-COH43MRJ.js} +1 -1
  80. package/chunks/{chunk-E5A7LHNN.js → chunk-CPBF7KYF.js} +1 -1
  81. package/chunks/{chunk-UWCTAVOD.js → chunk-CR3C7WXL.js} +1 -1
  82. package/chunks/{chunk-DIWVZ3VM.js → chunk-CSACSGAW.js} +5 -5
  83. package/chunks/{chunk-5P5XGNYH.js → chunk-CU3L64TP.js} +1 -1
  84. package/chunks/{chunk-IWKSG2AR.js → chunk-CXAOG665.js} +1 -1
  85. package/chunks/{chunk-64WXLC72.js → chunk-DJ2GSRLV.js} +1 -1
  86. package/chunks/chunk-DKXW66XA.js +38 -0
  87. package/chunks/{chunk-QN5NZ3UQ.js → chunk-DMTGGOSA.js} +2 -2
  88. package/chunks/{chunk-JZHIQJOE.js → chunk-DMTQY5GY.js} +6 -6
  89. package/chunks/{chunk-GSHQYYIV.js → chunk-DNKX35GS.js} +6 -26
  90. package/chunks/{chunk-6IAWPIQ6.js → chunk-DPSWJWZI.js} +4 -3
  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-3B26UVCX.js → chunk-E67NFMF4.js} +39 -10
  94. package/chunks/{chunk-ON3JA3RU.js → chunk-EU3U7USF.js} +7 -9
  95. package/chunks/{chunk-L46XKEGM.js → chunk-EUNRLYJG.js} +5 -8
  96. package/chunks/{chunk-OACJLMLD.js → chunk-FBU7WRZI.js} +18 -2
  97. package/chunks/{chunk-5IFG2VC4.js → chunk-FCMNKVXT.js} +1 -1
  98. package/chunks/{chunk-A5JOJ77H.js → chunk-FDL4PB5H.js} +38 -15
  99. package/chunks/{chunk-E7KNELIZ.js → chunk-FSA7ERJ2.js} +2 -2
  100. package/chunks/{chunk-TI3BWD4D.js → chunk-G6QTKQN4.js} +39 -19
  101. package/chunks/{chunk-44NROQYV.js → chunk-GDQQRV43.js} +4 -4
  102. package/chunks/{chunk-O2YT7VB4.js → chunk-GOXKNSDZ.js} +2 -65
  103. package/chunks/{chunk-V35MD7KN.js → chunk-GRC5HGPI.js} +1 -1
  104. package/chunks/chunk-GYGGUR4Q.js +253 -0
  105. package/chunks/{chunk-S6RLAIUR.js → chunk-GYTKQYDB.js} +1 -1
  106. package/chunks/{chunk-A2X3KLC3.js → chunk-GZXCZHCT.js} +3 -3
  107. package/chunks/{chunk-K5PGHDBN.js → chunk-H6XPXXMH.js} +1 -1
  108. package/chunks/{chunk-E7EMCSUG.js → chunk-HE54PGVI.js} +64 -42
  109. package/chunks/{chunk-KYMBIKIW.js → chunk-HTHUX2T5.js} +1 -1
  110. package/chunks/{chunk-LQ7TMOCE.js → chunk-HTO4JFDZ.js} +1 -1
  111. package/chunks/chunk-HZYUNZDQ.js +296 -0
  112. package/chunks/chunk-IDS7MSUP.js +74 -0
  113. package/chunks/chunk-J2TDDOSM.js +493 -0
  114. package/chunks/{chunk-4CGOWHKM.js → chunk-J4MOFEOK.js} +2 -2
  115. package/chunks/{chunk-22IFUCVR.js → chunk-JESCGQM3.js} +1 -1
  116. package/chunks/chunk-JR63TOV4.js +91 -0
  117. package/chunks/chunk-K3PPUB46.js +445 -0
  118. package/chunks/{chunk-42XGFRJS.js → chunk-KKRTYONM.js} +8 -6
  119. package/chunks/{chunk-GGQQA4JW.js → chunk-KSO42X3Z.js} +15 -2
  120. package/chunks/{chunk-MRO43B25.js → chunk-KW7NOTN6.js} +1 -1
  121. package/chunks/{chunk-54YZOP7P.js → chunk-L573Z2QZ.js} +11894 -5190
  122. package/chunks/{chunk-TLWA7TPR.js → chunk-L5U3ELZX.js} +2 -2
  123. package/chunks/{chunk-WHJQ3JUS.js → chunk-LAM6CGP5.js} +10 -10
  124. package/chunks/{chunk-H6EMK6QK.js → chunk-LQIDP6K5.js} +11913 -5207
  125. package/chunks/{chunk-MZ7BABX3.js → chunk-LXLTBIDP.js} +44 -7
  126. package/chunks/{chunk-HLKG6CZN.js → chunk-M5GB774H.js} +1 -1
  127. package/chunks/{chunk-EEN2TKST.js → chunk-MBJ3D6SF.js} +2 -2
  128. package/chunks/{chunk-PMFULMLX.js → chunk-MBP3K324.js} +2234 -377
  129. package/chunks/chunk-MR3PXB6E.js +48 -0
  130. package/chunks/{chunk-ZE6E424U.js → chunk-N7FCSERB.js} +2 -2
  131. package/chunks/{chunk-332PWN27.js → chunk-NHDKSOEY.js} +1 -1
  132. package/chunks/chunk-OATTJGPJ.js +448 -0
  133. package/chunks/{chunk-UX6OTBVD.js → chunk-OCC3UTWA.js} +2 -2
  134. package/chunks/{chunk-P7M4GA57.js → chunk-OCPBI7J5.js} +1 -1
  135. package/chunks/chunk-OJAMDVF5.js +66 -0
  136. package/chunks/{chunk-IJRL2S3D.js → chunk-OKAIGAYW.js} +6 -6
  137. package/chunks/{chunk-WQTHLB5T.js → chunk-P3S67A5R.js} +7 -7
  138. package/chunks/{chunk-FBET5XZ2.js → chunk-PEJWA3QQ.js} +29 -10
  139. package/chunks/{chunk-J7RCB6N5.js → chunk-PHYCWSDG.js} +2 -2
  140. package/chunks/{chunk-LAXNEOBG.js → chunk-PMRH4YYW.js} +18 -3
  141. package/chunks/{chunk-AGHU5VRK.js → chunk-PZL23GTM.js} +1 -1
  142. package/chunks/{chunk-PC4ZXHU2.js → chunk-Q6TUALBE.js} +3 -3
  143. package/chunks/{chunk-FXGLL2HL.js → chunk-Q7YDKOGY.js} +1 -1
  144. package/chunks/{chunk-P3XEEI3X.js → chunk-QABSSDQH.js} +36 -7
  145. package/chunks/{chunk-XOHNA2S6.js → chunk-QAW7PIHT.js} +3 -3
  146. package/chunks/{chunk-LGFAYIKX.js → chunk-QHTIBUWB.js} +1 -1
  147. package/chunks/{chunk-P3MYAZBY.js → chunk-QMGX2KO2.js} +1 -1
  148. package/chunks/{chunk-Z3I3QJK3.js → chunk-QRUIS3DN.js} +1 -1
  149. package/chunks/{chunk-ITOGNELQ.js → chunk-QSWDGAIC.js} +2 -2
  150. package/chunks/{chunk-GHOTR7HL.js → chunk-QUPXZXLV.js} +1 -1
  151. package/chunks/{chunk-W7EEOR7D.js → chunk-RCEEXIOO.js} +8 -6
  152. package/chunks/{chunk-2EM7ECZD.js → chunk-RJL5T4WJ.js} +281 -65
  153. package/chunks/{chunk-TLRYABYP.js → chunk-RKUWKYED.js} +1 -1
  154. package/chunks/{chunk-RNBYOUGV.js → chunk-RXFQM6FQ.js} +1 -1
  155. package/chunks/{chunk-JRQ7EVGE.js → chunk-S2QXYLUB.js} +2 -2
  156. package/chunks/{chunk-QXPI7FEC.js → chunk-S3TWXGKA.js} +4 -4
  157. package/chunks/{chunk-O33TBKDC.js → chunk-SE74CIZD.js} +155 -19
  158. package/chunks/{chunk-FV7425LN.js → chunk-SPJQO2CE.js} +18 -5
  159. package/chunks/{chunk-3GNOQZDC.js → chunk-SSFDPGFN.js} +11866 -11542
  160. package/chunks/{chunk-DCZWC4PF.js → chunk-SUWGUGAD.js} +5 -5
  161. package/chunks/{chunk-IBY3Q3HG.js → chunk-SXU3KKNI.js} +963 -181
  162. package/chunks/{chunk-MLZQVCF3.js → chunk-SZRN75WQ.js} +1 -1
  163. package/chunks/{chunk-M7S4GK5Z.js → chunk-T2L6SGQY.js} +115 -29
  164. package/chunks/{chunk-7RYW5LQV.js → chunk-T2NCM2ET.js} +2 -2
  165. package/chunks/{chunk-JPALE3PA.js → chunk-T5C2OUCI.js} +6 -6
  166. package/chunks/{chunk-BUZ6HGL4.js → chunk-T7FSRWQ5.js} +248 -55
  167. package/chunks/{chunk-Z2Z3GUXZ.js → chunk-TB6UDU4T.js} +1 -1
  168. package/chunks/{chunk-EXPMGZZV.js → chunk-TEGEBB2I.js} +1 -1
  169. package/chunks/{chunk-MX2YXRER.js → chunk-TJTKSVIV.js} +1 -1
  170. package/chunks/{chunk-CCGOFQV3.js → chunk-TQBODCFD.js} +8 -7
  171. package/chunks/chunk-TV6K2FB4.js +224 -0
  172. package/chunks/{chunk-5MBO76SL.js → chunk-TXY2C3D2.js} +3 -3
  173. package/chunks/{chunk-H6BD2ELD.js → chunk-TYAMGABM.js} +2 -2
  174. package/chunks/{chunk-QYNNN7BK.js → chunk-TYLOFD2U.js} +2 -2
  175. package/chunks/{chunk-VRDMOSQS.js → chunk-UDXSXLER.js} +2 -2
  176. package/chunks/{chunk-UXEW557D.js → chunk-UI3W56WF.js} +3 -3
  177. package/chunks/{chunk-CJCKDMWO.js → chunk-URK542T4.js} +1 -1
  178. package/chunks/{channel-worker-supervisor-NZQUOEFJ.js → chunk-URU5UEK6.js} +190 -34
  179. package/chunks/{chunk-AHNXYU4O.js → chunk-V6WHFGST.js} +1 -1
  180. package/chunks/chunk-V75IOTWF.js +71 -0
  181. package/chunks/{chunk-ZTQ26VBE.js → chunk-VDDGDZMA.js} +1 -1
  182. package/chunks/{chunk-OMX7CUOE.js → chunk-VGC4I5JJ.js} +1 -1
  183. package/chunks/{chunk-43W4YSO6.js → chunk-VJNBRRZG.js} +2 -2
  184. package/chunks/{chunk-ER3BKOLB.js → chunk-W7FJ3N32.js} +1 -1
  185. package/chunks/{chunk-3HX5LZ6R.js → chunk-WBL3FJEU.js} +2 -2
  186. package/chunks/{chunk-7LTB54MK.js → chunk-WIEO4CWB.js} +2 -2
  187. package/chunks/{chunk-BMFMJINR.js → chunk-WL4L7EZH.js} +1 -1
  188. package/chunks/{chunk-6VK6FIMQ.js → chunk-WM5HY72H.js} +415 -379
  189. package/chunks/{chunk-BOBRZZKD.js → chunk-WN4D7YCI.js} +5 -5
  190. package/chunks/{chunk-DHOLNQLC.js → chunk-WWQ3QIJS.js} +60 -40
  191. package/chunks/{chunk-FEJ2FZ3U.js → chunk-WX6SD4IQ.js} +2 -2
  192. package/chunks/{chunk-DICGXFAI.js → chunk-X7EBAABW.js} +11 -9
  193. package/chunks/{chunk-3RW4AZJV.js → chunk-XFSFZCM5.js} +1 -1
  194. package/chunks/{chunk-YHKAK72D.js → chunk-XHW5VUIQ.js} +102 -16
  195. package/chunks/{chunk-2MPVVENX.js → chunk-XU457J4V.js} +2 -2
  196. package/chunks/{chunk-SIUQ3YYX.js → chunk-XVNQMZ2I.js} +1 -1
  197. package/chunks/{chunk-SYCJMSIJ.js → chunk-XWRJCPHC.js} +1 -1
  198. package/chunks/{chunk-SUAKHHK7.js → chunk-Y66VOBRP.js} +3 -3
  199. package/chunks/chunk-YAF2MQZ6.js +19 -0
  200. package/chunks/{chunk-FQNC62B2.js → chunk-YH4J5XI3.js} +11 -11
  201. package/chunks/chunk-YHN5SUIJ.js +219 -0
  202. package/chunks/{chunk-BR4QREVK.js → chunk-YQ3U5MUC.js} +1 -1
  203. package/chunks/{chunk-Y6Z2O3WR.js → chunk-YUZI3WAC.js} +1 -1
  204. package/chunks/{chunk-II73RK2S.js → chunk-YVEDCAUE.js} +1 -1
  205. package/chunks/{chunk-XYR5RUA5.js → chunk-YWUK7OS7.js} +10 -3
  206. package/chunks/{chunk-27BHARDE.js → chunk-Z56JO4JZ.js} +2124 -30
  207. package/chunks/{chunk-TSPB77TG.js → chunk-ZLM3KXBT.js} +1015 -2967
  208. package/chunks/{chunk-AHNJGLFV.js → chunk-ZPAHLFZU.js} +3 -3
  209. package/chunks/chunk-ZRKBI3DP.js +34 -0
  210. package/chunks/cli-entry-path-4VJ3Y6T2.js +10 -0
  211. package/chunks/{computer-use-WMY3UOBX.js → computer-use-DIP5BWXP.js} +51 -50
  212. package/chunks/config-utils-CFT33LTP.js +24 -0
  213. package/chunks/contextCommand-QU4GJWWD.js +69 -0
  214. package/chunks/create-sub-session-TCLIYVGR.js +376 -0
  215. package/chunks/create-sub-session-WNX2OQVX.js +207 -0
  216. package/chunks/{cron-create-JSXEIPVS.js → cron-create-36TIFKRT.js} +9 -9
  217. package/chunks/{cron-delete-OIFVEPGU.js → cron-delete-OQD3WULU.js} +7 -7
  218. package/chunks/{cron-list-D4ZX4IOE.js → cron-list-4533T2FH.js} +14 -10
  219. package/chunks/{daemon-WPAENEBW.js → daemon-CEZ27OH4.js} +734 -24
  220. package/chunks/daemon-status-provider-SIBFMLBQ.js +77 -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-E6UANPIM.js → dist-3V3ZV6ML.js} +1011 -197
  224. package/chunks/{dist-SCFLLYMK.js → dist-ERCPDVA6.js} +12 -11
  225. package/chunks/{dist-Z34QPAQW.js → dist-HNN7DR2L.js} +529 -79
  226. package/chunks/{dist-O2IRTRPP.js → dist-JZE7F6YR.js} +10 -10
  227. package/chunks/{dist-BEXOZXLQ.js → dist-MOEC47TR.js} +36 -15
  228. package/chunks/{dist-H2AEJCZ5.js → dist-PHEPJLXD.js} +5 -5
  229. package/chunks/{dist-63IS3ZMI.js → dist-PZV5RKM6.js} +4 -4
  230. package/chunks/earlyInputCapture-L66BEG2C.js +70 -0
  231. package/chunks/{edit-OTYWAXGP.js → edit-Y5LAJSST.js} +52 -51
  232. package/chunks/{en-N4EPZAQI.js → en-NJCA3TAA.js} +78 -6
  233. package/chunks/{enter-worktree-HNYBDVCX.js → enter-worktree-X5LML7T4.js} +51 -50
  234. package/chunks/{enterPlanMode-CJCNGT63.js → enterPlanMode-XWF32QOW.js} +67 -51
  235. package/chunks/environment-YVMA5F4I.js +90 -0
  236. package/chunks/errors-4Y5SUAOC.js +76 -0
  237. package/chunks/{exit-worktree-DUUKOE3X.js → exit-worktree-SFHU7XYC.js} +51 -50
  238. package/chunks/{exitPlanMode-AMMSXOAJ.js → exitPlanMode-FP3NAN6E.js} +52 -51
  239. package/chunks/{fast-path-OV6NIP3K.js → fast-path-ZJNFFCBM.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-5CAB22BI.js} +122 -109
  244. package/chunks/{geminiContentGenerator-CDUNEJQE.js → geminiContentGenerator-GE3HPIFB.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-YPACZAEL.js} +98 -75
  251. package/chunks/{grep-NRITKDCD.js → grep-IPR4XW37.js} +51 -50
  252. package/chunks/handleAutoUpdate-P2KEP56V.js +70 -0
  253. package/chunks/i18n-BRCT66AA.js +85 -0
  254. package/chunks/initializer-34HXSK3A.js +74 -0
  255. package/chunks/installationInfo-WOTDQMSC.js +66 -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-XOGP7UPP.js +77 -0
  260. package/chunks/loadedSettingsAdapter-L4YKGU6F.js +71 -0
  261. package/chunks/{loop-wakeup-OHWEMFP5.js → loop-wakeup-MVUKDVCP.js} +10 -10
  262. package/chunks/{lowlight-FYAAUU5J.js → lowlight-J2OZNPCA.js} +1 -1
  263. package/chunks/{ls-7MFU2QYW.js → ls-RXNIAQDE.js} +6 -6
  264. package/chunks/{lsp-XHDTPTGV.js → lsp-HB5LM5IL.js} +4 -4
  265. package/chunks/mcp-HH7DIP7Y.js +71 -0
  266. package/chunks/{monitor-FL2ZO33W.js → monitor-25DEBT6U.js} +51 -50
  267. package/chunks/{multipart-parser-AJ4WASWR.js → multipart-parser-AWZKXSQN.js} +3 -3
  268. package/chunks/nonInteractiveCli-CT3VSSLK.js +130 -0
  269. package/chunks/{notebook-edit-6PULTHL5.js → notebook-edit-TAVXSWQ6.js} +52 -51
  270. package/chunks/openaiContentGenerator-6I7DKUAN.js +56 -0
  271. package/chunks/pidfile-SIQGCFLZ.js +74 -0
  272. package/chunks/{pt-OLYN42DS.js → pt-TFZO5Y6T.js} +68 -6
  273. package/chunks/{qwenContentGenerator-NX2E7PB7.js → qwenContentGenerator-5DDIXI2N.js} +53 -52
  274. package/chunks/{qwenOAuth2-ZV7YLMND.js → qwenOAuth2-LEYOU7RU.js} +9 -9
  275. package/chunks/read-file-WDOAYRFU.js +30 -0
  276. package/chunks/{read-mcp-resource-CNQHBSJM.js → read-mcp-resource-6LRTZKXI.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-PMDTY4OS.js} +4 -4
  279. package/chunks/ripGrep-MI7PHN3Q.js +61 -0
  280. package/chunks/{ru-LDM3GWVQ.js → ru-4L4LFHIT.js} +68 -6
  281. package/chunks/{run-qwen-serve-JK2DL7PI.js → run-qwen-serve-PNTYF3RE.js} +1289 -256
  282. package/chunks/runtime-V3FBMUTG.js +100 -0
  283. package/chunks/{scheduler-O4IGVZKK.js → scheduler-4TAUIJBM.js} +52 -51
  284. package/chunks/{send-message-76GT3JMC.js → send-message-SR4WSXP2.js} +7 -7
  285. package/chunks/serve-WB2TR7XM.js +77 -0
  286. package/chunks/{server-SGNPKX2O.js → server-AF6LNFPK.js} +8094 -2800
  287. package/chunks/{session-5BNM25EZ.js → session-BIGK4NRW.js} +341 -135
  288. package/chunks/settings-IXF5FIQO.js +121 -0
  289. package/chunks/shell-5VWJ2PPG.js +69 -0
  290. package/chunks/{skill-I22QK5XS.js → skill-SI667Y3B.js} +24 -24
  291. package/chunks/spawnChannel-IKX6DD7J.js +71 -0
  292. package/chunks/{src-BBQSM46M.js → src-55DZWDAK.js} +151 -52
  293. package/chunks/{src-7XL4G4DC.js → src-VLI3TKEY.js} +4 -4
  294. package/chunks/standalone-update-MHLSFHE6.js +80 -0
  295. package/chunks/{startInteractiveUI-JII36UGN.js → startInteractiveUI-V2EDWLAX.js} +2239 -1846
  296. package/chunks/stdioHelpers-UMY72QRP.js +14 -0
  297. package/chunks/{syntheticOutput-F7LSDCAR.js → syntheticOutput-YKFNUQZD.js} +5 -5
  298. package/chunks/task-create-NUFOHU53.js +23 -0
  299. package/chunks/{task-list-TGGJX7EA.js → task-list-Z2IKQY3S.js} +11 -10
  300. package/chunks/{task-stop-W7NGTMYQ.js → task-stop-KQFIVQTJ.js} +4 -4
  301. package/chunks/{task-update-XYWMHJGJ.js → task-update-HKADXSTD.js} +13 -12
  302. package/chunks/{team-create-A7NUU5AQ.js → team-create-IZAMLBJS.js} +51 -50
  303. package/chunks/{team-delete-UHQQ2QQX.js → team-delete-SDNTGZLG.js} +11 -10
  304. package/chunks/{team-plan-approval-TOQRJ2KI.js → team-plan-approval-3R4RCKCP.js} +51 -50
  305. package/chunks/theme-manager-LCOXZ5DO.js +65 -0
  306. package/chunks/{todoWrite-C4ZAMSR3.js → todoWrite-YNXB27GB.js} +6 -6
  307. package/chunks/{tool-search-SZ4EFS2T.js → tool-search-NYNCD5LF.js} +24 -24
  308. package/chunks/total-session-admission-PJWGOMQE.js +73 -0
  309. package/chunks/tree-sitter-YJVE2ZUK.js +2980 -0
  310. package/chunks/trustedFolders-DOJDHNYP.js +81 -0
  311. package/chunks/{types-QX5C3CHJ.js → types-JNKGKUJT.js} +2 -2
  312. package/chunks/updateCheck-ZXL66KIG.js +70 -0
  313. package/chunks/updateEventEmitter-5MKC7BLQ.js +10 -0
  314. package/chunks/validateNonInterActiveAuth-JSNJZCYY.js +195 -0
  315. package/chunks/{version-NO2XSNNP.js → version-3NLBENG5.js} +3 -3
  316. package/chunks/{web-fetch-UYGMOHW4.js → web-fetch-ZSPR3PRR.js} +8 -8
  317. package/chunks/{workflow-WO2H6NRI.js → workflow-BQYLF7XP.js} +52 -51
  318. package/chunks/workspace-providers-status-FA2DGEZW.js +74 -0
  319. package/chunks/workspace-registration-store-TARTAGMI.js +27 -0
  320. package/chunks/workspace-registry-AGE7WAXX.js +77 -0
  321. package/chunks/workspace-service-ERC5VR2B.js +83 -0
  322. package/chunks/workspace-skills-status-FKYWGNCN.js +73 -0
  323. package/chunks/{write-file-PX2Z7IBE.js → write-file-S47KSK7Z.js} +52 -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-Zj4EkBUY.js} +1 -1
  339. package/web-shell/assets/{architectureDiagram-3BPJPVTR-BT0mGrz2.js → architectureDiagram-3BPJPVTR-DAk1Ua3Y.js} +1 -1
  340. package/web-shell/assets/{blockDiagram-GPEHLZMM-BLgUWvyf.js → blockDiagram-GPEHLZMM-DBNqUthM.js} +1 -1
  341. package/web-shell/assets/{c4Diagram-AAUBKEIU-DYx9M7oU.js → c4Diagram-AAUBKEIU-Dq5QGJ53.js} +1 -1
  342. package/web-shell/assets/channel-DWfYijFS.js +1 -0
  343. package/web-shell/assets/{chunk-2J33WTMH-CfMHWbuZ.js → chunk-2J33WTMH-DY0AeGru.js} +1 -1
  344. package/web-shell/assets/{chunk-4BX2VUAB-B1URoqEV.js → chunk-4BX2VUAB-COX3OyIi.js} +1 -1
  345. package/web-shell/assets/{chunk-55IACEB6-DAIaM4QF.js → chunk-55IACEB6-Csoyddbh.js} +1 -1
  346. package/web-shell/assets/{chunk-727SXJPM-B2X34knw.js → chunk-727SXJPM-BJPONdxX.js} +1 -1
  347. package/web-shell/assets/{chunk-AQP2D5EJ-Cif-zLTQ.js → chunk-AQP2D5EJ-syLVH6fg.js} +1 -1
  348. package/web-shell/assets/{chunk-FMBD7UC4-H18WCXdY.js → chunk-FMBD7UC4-Cfm59Nhw.js} +1 -1
  349. package/web-shell/assets/{chunk-ND2GUHAM-YFtetHi0.js → chunk-ND2GUHAM-DZf6tAqS.js} +1 -1
  350. package/web-shell/assets/{chunk-QZHKN3VN-By2hiH3r.js → chunk-QZHKN3VN-DKBp_v5q.js} +1 -1
  351. package/web-shell/assets/classDiagram-4FO5ZUOK-DDht-78J.js +1 -0
  352. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-DDht-78J.js +1 -0
  353. package/web-shell/assets/{cose-bilkent-S5V4N54A-VrcnnLZu.js → cose-bilkent-S5V4N54A-DUU3mMQ5.js} +1 -1
  354. package/web-shell/assets/{dagre-BM42HDAG-CGo-IaVV.js → dagre-BM42HDAG-BUgNfbh-.js} +1 -1
  355. package/web-shell/assets/{diagram-2AECGRRQ-Dl4QjzIB.js → diagram-2AECGRRQ-DT7MVfAg.js} +1 -1
  356. package/web-shell/assets/{diagram-5GNKFQAL-qMLmsO0u.js → diagram-5GNKFQAL-Cv4YG-ec.js} +1 -1
  357. package/web-shell/assets/{diagram-KO2AKTUF-CHlmuxPI.js → diagram-KO2AKTUF-BAe9LLkn.js} +1 -1
  358. package/web-shell/assets/{diagram-LMA3HP47-BUwU3GwT.js → diagram-LMA3HP47-DW8wqTIP.js} +1 -1
  359. package/web-shell/assets/{diagram-OG6HWLK6-HEhwPDUM.js → diagram-OG6HWLK6-CmkuWbaK.js} +1 -1
  360. package/web-shell/assets/{erDiagram-TEJ5UH35-1n0llBmM.js → erDiagram-TEJ5UH35-DwvdHjZ9.js} +1 -1
  361. package/web-shell/assets/{flowDiagram-I6XJVG4X-BN74uMnt.js → flowDiagram-I6XJVG4X-CSsXrhW_.js} +1 -1
  362. package/web-shell/assets/{ganttDiagram-6RSMTGT7-BoOq78i3.js → ganttDiagram-6RSMTGT7-BtuznCdi.js} +3 -3
  363. package/web-shell/assets/{gitGraphDiagram-PVQCEYII-CN8b00c8.js → gitGraphDiagram-PVQCEYII-DGmAm06g.js} +1 -1
  364. package/web-shell/assets/index-B9Xwf4OS.css +5 -0
  365. package/web-shell/assets/index-BEtz1cvN.js +3 -0
  366. package/web-shell/assets/index-BUkM4ZXX.js +1088 -0
  367. package/web-shell/assets/{infoDiagram-5YYISTIA-CCurnIna.js → infoDiagram-5YYISTIA-OAiZ1q0K.js} +1 -1
  368. package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-AwuRqdUP.js → ishikawaDiagram-YF4QCWOH-DyK0KTIT.js} +1 -1
  369. package/web-shell/assets/{journeyDiagram-JHISSGLW-B7IICk-E.js → journeyDiagram-JHISSGLW-DzYJlwfO.js} +1 -1
  370. package/web-shell/assets/{kanban-definition-UN3LZRKU-BYF0OKhx.js → kanban-definition-UN3LZRKU-CU_tcDwi.js} +1 -1
  371. package/web-shell/assets/{linear-GWWguGfD.js → linear-DvZXjF17.js} +1 -1
  372. package/web-shell/assets/{mermaid.core-JqtkxvCl.js → mermaid.core-_ZFEQlOl.js} +5 -5
  373. package/web-shell/assets/{mindmap-definition-RKZ34NQL-B0MmHsZ6.js → mindmap-definition-RKZ34NQL-Or4p64HB.js} +1 -1
  374. package/web-shell/assets/{pieDiagram-4H26LBE5-DhImUZLQ.js → pieDiagram-4H26LBE5-lXE-zZ6b.js} +1 -1
  375. package/web-shell/assets/{quadrantDiagram-W4KKPZXB-DGeSC3DT.js → quadrantDiagram-W4KKPZXB-DUXb9mUV.js} +1 -1
  376. package/web-shell/assets/{requirementDiagram-4Y6WPE33-DkspY6Re.js → requirementDiagram-4Y6WPE33-D_10uHfo.js} +1 -1
  377. package/web-shell/assets/{sankeyDiagram-5OEKKPKP-CaOofS2x.js → sankeyDiagram-5OEKKPKP-E8c0fPqe.js} +1 -1
  378. package/web-shell/assets/{sequenceDiagram-3UESZ5HK-DFQ_kFnN.js → sequenceDiagram-3UESZ5HK-CJMtFttK.js} +1 -1
  379. package/web-shell/assets/{stateDiagram-AJRCARHV-D3snw0Tz.js → stateDiagram-AJRCARHV-DhbviRbB.js} +1 -1
  380. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-CntljfwW.js +1 -0
  381. package/web-shell/assets/{timeline-definition-PNZ67QCA-l4zH8nLM.js → timeline-definition-PNZ67QCA-CM5yMkFK.js} +1 -1
  382. package/web-shell/assets/{vennDiagram-CIIHVFJN-BnRlA98M.js → vennDiagram-CIIHVFJN-BCi-zWNN.js} +1 -1
  383. package/web-shell/assets/{wardley-L42UT6IY-BS0xUdmd.js → wardley-L42UT6IY-BJ_yJNjJ.js} +1 -1
  384. package/web-shell/assets/{wardleyDiagram-YWT4CUSO-DKGBUWyQ.js → wardleyDiagram-YWT4CUSO-TjtLk-qN.js} +1 -1
  385. package/web-shell/assets/{xychartDiagram-2RQKCTM6-DVb2HuOB.js → xychartDiagram-2RQKCTM6-D1TjyoQ3.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-6SHO7WFF.js +0 -79
  392. package/chunks/chunk-IFEWJXFS.js +0 -37
  393. package/chunks/chunk-XNXVCNAT.js +0 -88
  394. package/chunks/contextCommand-LXFZRP4L.js +0 -68
  395. package/chunks/daemon-status-provider-GJRXY5EN.js +0 -76
  396. package/chunks/earlyInputCapture-KNOYZ4MJ.js +0 -69
  397. package/chunks/environment-ATFTYMLZ.js +0 -88
  398. package/chunks/errors-A54HAADA.js +0 -75
  399. package/chunks/handleAutoUpdate-CEZJAARW.js +0 -65
  400. package/chunks/initializer-BRFJKRRX.js +0 -71
  401. package/chunks/list-KVHF7O4X.js +0 -73
  402. package/chunks/loadedSettingsAdapter-LL5PSFKX.js +0 -68
  403. package/chunks/mcp-3GCXRTOQ.js +0 -68
  404. package/chunks/nonInteractiveCli-PTQI3DV6.js +0 -119
  405. package/chunks/openaiContentGenerator-YE42PKCY.js +0 -56
  406. package/chunks/pidfile-NZYSCFRK.js +0 -73
  407. package/chunks/read-file-33XGP5HF.js +0 -30
  408. package/chunks/ripGrep-QWZLGTAN.js +0 -60
  409. package/chunks/serve-JTMIGANH.js +0 -74
  410. package/chunks/settings-3MI4XXP6.js +0 -118
  411. package/chunks/shell-YJKBE67T.js +0 -68
  412. package/chunks/spawnChannel-UNV6W4DU.js +0 -70
  413. package/chunks/task-create-WCHTB435.js +0 -22
  414. package/chunks/theme-manager-WZ4RRMMB.js +0 -64
  415. package/chunks/total-session-admission-ULXJWXFV.js +0 -72
  416. package/chunks/trustedFolders-LAZZPZ3D.js +0 -79
  417. package/chunks/updateCheck-KONLM73X.js +0 -65
  418. package/chunks/validateNonInterActiveAuth-MPKSRCXD.js +0 -184
  419. package/chunks/workspace-providers-status-3W7E7DFW.js +0 -71
  420. package/chunks/workspace-registry-SXJZNN3O.js +0 -74
  421. package/chunks/workspace-service-CZRQZ4Q4.js +0 -80
  422. package/chunks/workspace-skills-status-K7WFREPD.js +0 -70
  423. package/node_modules/@qwen-code/audio-capture/dist/index.d.ts +0 -35
  424. package/node_modules/@qwen-code/audio-capture/dist/index.js +0 -48
  425. package/node_modules/@qwen-code/audio-capture/dist/index.js.map +0 -1
  426. package/node_modules/@qwen-code/audio-capture/dist/platform.d.ts +0 -7
  427. package/node_modules/@qwen-code/audio-capture/dist/platform.js +0 -18
  428. package/node_modules/@qwen-code/audio-capture/dist/platform.js.map +0 -1
  429. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/LICENSE +0 -21
  430. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/README.md +0 -58
  431. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/SECURITY.md +0 -5
  432. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/bin.js +0 -84
  433. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/build-test.js +0 -19
  434. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/index.js +0 -6
  435. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/node-gyp-build.js +0 -207
  436. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/optional.js +0 -7
  437. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/package.json +0 -43
  438. package/node_modules/@qwen-code/audio-capture/package.json +0 -30
  439. package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-arm64/@qwen-code+audio-capture.node +0 -0
  440. package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-x64/@qwen-code+audio-capture.node +0 -0
  441. package/node_modules/@qwen-code/audio-capture/prebuilds/linux-arm64/@qwen-code+audio-capture.node +0 -0
  442. package/node_modules/@qwen-code/audio-capture/prebuilds/linux-x64/@qwen-code+audio-capture.node +0 -0
  443. package/node_modules/@qwen-code/audio-capture/prebuilds/win32-x64/@qwen-code+audio-capture.node +0 -0
  444. package/web-shell/assets/channel-wmPUGHtg.js +0 -1
  445. package/web-shell/assets/classDiagram-4FO5ZUOK-BA2ljEjW.js +0 -1
  446. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-BA2ljEjW.js +0 -1
  447. package/web-shell/assets/index-CNwoO1Cn.js +0 -783
  448. package/web-shell/assets/index-CWQ0z6kZ.css +0 -5
  449. 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-4QOPEEL2.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-PMRH4YYW.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-SSFDPGFN.js";
66
74
  import {
67
75
  DAEMON_TRACEPARENT_META_KEY,
68
76
  DAEMON_TRACESTATE_META_KEY
69
- } from "./chunk-BUZ6HGL4.js";
77
+ } from "./chunk-T7FSRWQ5.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
@@ -1512,11 +1595,12 @@ var BridgeClient = class {
1512
1595
  );
1513
1596
  }
1514
1597
  /**
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.
1598
+ * Bounded early-event buffer. The map is scoped to this BridgeClient and
1599
+ * therefore to one channel; a stale channel cannot seed a fresh channel's
1600
+ * future session. Frames are keyed by sessionId; each entry tracks its
1601
+ * `expiresAt` for lazy TTL-based eviction in `bufferEarlyEvent`. Drained by
1602
+ * `drainEarlyEvents` whenever the bridge registers a session with a matching
1603
+ * id. See MAX_EARLY_EVENT_* constants for capacity bounds.
1520
1604
  */
1521
1605
  earlyEvents = /* @__PURE__ */ new Map();
1522
1606
  /**
@@ -1541,7 +1625,7 @@ var BridgeClient = class {
1541
1625
  /**
1542
1626
  * Allow-list of sessionIds currently being restored via
1543
1627
  * `session/load` / `session/resume`. Bypasses the tombstone check
1544
- * in `bufferEarlyEvent` so restore-time guardrail events for a
1628
+ * in `bufferEarlyEvent` so restore-time events for a
1545
1629
  * previously-closed id flow through to the future
1546
1630
  * `createSessionEntry -> drainEarlyEvents` call.
1547
1631
  *
@@ -1556,9 +1640,12 @@ var BridgeClient = class {
1556
1640
  inFlightRestoreIds = /* @__PURE__ */ new Set();
1557
1641
  /**
1558
1642
  * 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
1643
+ * response, unlike `extNotification`). Served methods:
1644
+ * `qwen/control/client_mcp/message` (reverse tool channel),
1645
+ * `qwen/control/create-sub-session` (the `create_sub_session` tool daemon
1646
+ * spawns a sub-session and, for `'first-turn'`, returns its first-turn
1647
+ * result), and `craft/drainMidTurnQueue`: the ACP child calls the last one
1648
+ * between tool batches to pull any messages the browser queued mid-turn. We splice the per-session
1562
1649
  * queue, return them to the child as the response, and — when non-empty —
1563
1650
  * publish a `mid_turn_message_injected` SSE frame so the browser can move
1564
1651
  * those messages out of its pending queue (a dedupe signal, not a transcript
@@ -1571,6 +1658,9 @@ var BridgeClient = class {
1571
1658
  if (method === SERVE_CONTROL_EXT_METHODS.clientMcpMessage) {
1572
1659
  return this.handleClientMcpMessage(params);
1573
1660
  }
1661
+ if (method === SERVE_CONTROL_EXT_METHODS.createSubSession) {
1662
+ return this.handleCreateSubSession(params);
1663
+ }
1574
1664
  if (method !== MID_TURN_QUEUE_DRAIN_METHOD) {
1575
1665
  throw RequestError.methodNotFound(method);
1576
1666
  }
@@ -1646,17 +1736,80 @@ var BridgeClient = class {
1646
1736
  const response = await send(payload);
1647
1737
  return { payload: response };
1648
1738
  }
1739
+ /**
1740
+ * Handle the `create_sub_session` tool's request: validate, then forward to
1741
+ * the daemon-host `onCreateSubSession` callback (which spawns a fresh
1742
+ * top-level sub-session and, for `'first-turn'`, waits for its first turn and
1743
+ * returns the result). No host wired → `methodNotFound`, which the tool
1744
+ * surfaces as "daemon-only".
1745
+ */
1746
+ async handleCreateSubSession(params) {
1747
+ if (!this.onCreateSubSession) {
1748
+ throw RequestError.methodNotFound(
1749
+ SERVE_CONTROL_EXT_METHODS.createSubSession
1750
+ );
1751
+ }
1752
+ const prompt = params["prompt"];
1753
+ if (typeof prompt !== "string" || prompt.length === 0) {
1754
+ throw RequestError.invalidParams(
1755
+ void 0,
1756
+ "`prompt` must be a non-empty string"
1757
+ );
1758
+ }
1759
+ if (prompt.length > MAX_SUB_SESSION_PROMPT_CHARS) {
1760
+ throw RequestError.invalidParams(
1761
+ void 0,
1762
+ `\`prompt\` exceeds the ${MAX_SUB_SESSION_PROMPT_CHARS}-character limit`
1763
+ );
1764
+ }
1765
+ const completion = params["completion"];
1766
+ if (completion !== "sent" && completion !== "first-turn") {
1767
+ throw RequestError.invalidParams(
1768
+ void 0,
1769
+ "`completion` must be 'sent' or 'first-turn'"
1770
+ );
1771
+ }
1772
+ const name = params["name"];
1773
+ if (typeof name === "string" && name.length > MAX_SUB_SESSION_NAME_CHARS) {
1774
+ throw RequestError.invalidParams(
1775
+ void 0,
1776
+ `\`name\` exceeds the ${MAX_SUB_SESSION_NAME_CHARS}-character limit`
1777
+ );
1778
+ }
1779
+ const callerSessionId = params["callerSessionId"];
1780
+ if (typeof callerSessionId !== "string" || callerSessionId.length === 0 || !this.ownsSession(callerSessionId)) {
1781
+ throw RequestError.invalidParams(
1782
+ void 0,
1783
+ "`callerSessionId` is required and must name a session owned by this connection"
1784
+ );
1785
+ }
1786
+ const model = params["model"];
1787
+ const result = await this.onCreateSubSession({
1788
+ prompt,
1789
+ completion,
1790
+ ...typeof model === "string" && model.length > 0 && model.length <= 128 ? { model } : {},
1791
+ ...typeof name === "string" && name.length > 0 ? { name } : {},
1792
+ callerSessionId
1793
+ });
1794
+ return {
1795
+ sessionId: result.sessionId,
1796
+ ...result.result !== void 0 ? { result: result.result } : {},
1797
+ ...result.stopReason !== void 0 ? { stopReason: result.stopReason } : {},
1798
+ ...result.parentSessionPersisted !== void 0 ? { parentSessionPersisted: result.parentSessionPersisted } : {}
1799
+ };
1800
+ }
1649
1801
  /**
1650
1802
  * Handle child->bridge ACP `extNotification` calls. Recognized methods are
1651
1803
  * `qwen/notify/session/model-update`,
1652
1804
  * `qwen/notify/session/mode-update`,
1653
1805
  * `qwen/notify/session/title-update` (auto/in-process session titles),
1806
+ * `qwen/notify/session/recording-degraded`,
1654
1807
  * `qwen/notify/session/prompt-suggestion` (followup assist),
1655
1808
  * `qwen/notify/session/artifact-event` (hook artifacts),
1656
1809
  * `qwen/notify/session/terminal-sequence`, and
1657
1810
  * `qwen/notify/session/mcp-budget-event` — each translated into a
1658
- * session-scoped SSE frame. Unknown methods are dropped silently
1659
- * for forward-compat.
1811
+ * session-scoped SSE frame. Unknown methods are dropped silently for
1812
+ * forward-compat.
1660
1813
  */
1661
1814
  async extNotification(method, params) {
1662
1815
  if (method === "qwen/notify/session/model-update") {
@@ -1687,6 +1840,28 @@ var BridgeClient = class {
1687
1840
  }
1688
1841
  return;
1689
1842
  }
1843
+ if (method === "qwen/notify/session/recording-degraded") {
1844
+ const sessionId2 = params["sessionId"];
1845
+ if (params["v"] !== 1 || typeof sessionId2 !== "string" || sessionId2.length === 0 || params["reason"] !== "write_failed") {
1846
+ writeStderrLine(
1847
+ `[demux] session=${typeof sessionId2 === "string" ? sessionId2 : "<missing>"} type=session_recording_degraded action=dropped reason=malformed`
1848
+ );
1849
+ return;
1850
+ }
1851
+ const entry = this.resolveEntry(sessionId2);
1852
+ if (entry && !this.ownsSession(sessionId2)) {
1853
+ writeStderrLine(
1854
+ `[demux] session=${sessionId2} type=session_recording_degraded action=dropped reason=session_not_owned`
1855
+ );
1856
+ return;
1857
+ }
1858
+ if (entry) entry.recordingDegraded = true;
1859
+ this.publishExtNotification(sessionId2, "session_recording_degraded", {
1860
+ sessionId: sessionId2,
1861
+ reason: "write_failed"
1862
+ });
1863
+ return;
1864
+ }
1690
1865
  if (method === "qwen/notify/session/prompt-suggestion") {
1691
1866
  const sessionId2 = params["sessionId"];
1692
1867
  const suggestion = params["suggestion"];
@@ -1943,7 +2118,7 @@ var BridgeClient = class {
1943
2118
  this.sweepExpiredTombstones(now);
1944
2119
  if (this.tombstonedSessionIds.has(sessionId) && !this.inFlightRestoreIds.has(sessionId)) {
1945
2120
  writeStderrLine(
1946
- `qwen serve: dropping mcp guardrail extNotification for tombstoned session ${JSON.stringify(sessionId)} (post-close stale event)`
2121
+ `qwen serve: dropping early extNotification for tombstoned session ${JSON.stringify(sessionId)} (post-close stale event)`
1947
2122
  );
1948
2123
  return;
1949
2124
  }
@@ -1952,7 +2127,7 @@ var BridgeClient = class {
1952
2127
  if (!buf) {
1953
2128
  if (this.earlyEvents.size >= MAX_EARLY_EVENT_SESSIONS) {
1954
2129
  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`
2130
+ `qwen serve: dropping early extNotification \u2014 early-event buffer at MAX_EARLY_EVENT_SESSIONS (${MAX_EARLY_EVENT_SESSIONS}); possible session-id fanout abuse`
1956
2131
  );
1957
2132
  return;
1958
2133
  }
@@ -1961,7 +2136,7 @@ var BridgeClient = class {
1961
2136
  }
1962
2137
  if (buf.frames.length >= MAX_EARLY_EVENTS_PER_SESSION) {
1963
2138
  writeStderrLine(
1964
- `qwen serve: dropping mcp guardrail extNotification for session ${JSON.stringify(sessionId)} \u2014 per-session cap (${MAX_EARLY_EVENTS_PER_SESSION}) reached`
2139
+ `qwen serve: dropping early extNotification for session ${JSON.stringify(sessionId)} \u2014 per-session cap (${MAX_EARLY_EVENTS_PER_SESSION}) reached`
1965
2140
  );
1966
2141
  return;
1967
2142
  }
@@ -1995,7 +2170,7 @@ var BridgeClient = class {
1995
2170
  * Mark a sessionId as currently being restored via `session/load` /
1996
2171
  * `session/resume`. While in this set, `bufferEarlyEvent` accepts
1997
2172
  * frames for the id even if it's tombstoned — so restore-time
1998
- * guardrail events from the freshly-restored child reach
2173
+ * early events from the freshly-restored child reach
1999
2174
  * `drainEarlyEvents` instead of being rejected by the tombstone.
2000
2175
  *
2001
2176
  * Bridge factory calls this BEFORE awaiting the ACP restore call.
@@ -2029,7 +2204,12 @@ var BridgeClient = class {
2029
2204
  this.tombstonedSessionIds.delete(sessionId);
2030
2205
  const buf = this.earlyEvents.get(sessionId);
2031
2206
  if (!buf) return;
2032
- for (const frame of buf.frames) entry.events.publish(frame);
2207
+ for (const frame of buf.frames) {
2208
+ if (frame.type === "session_recording_degraded") {
2209
+ entry.recordingDegraded = true;
2210
+ }
2211
+ entry.events.publish(frame);
2212
+ }
2033
2213
  this.earlyEvents.delete(sessionId);
2034
2214
  }
2035
2215
  async writeTextFile(params) {
@@ -2096,6 +2276,18 @@ var BridgeClient = class {
2096
2276
  if (typeof params.limit === "number" && params.limit <= 0) {
2097
2277
  return { content: "" };
2098
2278
  }
2279
+ if (typeof params.limit === "number" && params.limit > 0 && !Number.isSafeInteger(params.limit)) {
2280
+ throw RequestError.invalidParams(
2281
+ void 0,
2282
+ `\`limit\` must be a positive integer, got ${params.limit}`
2283
+ );
2284
+ }
2285
+ if (typeof params.line === "number" && params.line > 0 && !Number.isSafeInteger(params.line)) {
2286
+ throw RequestError.invalidParams(
2287
+ void 0,
2288
+ `\`line\` must be a positive integer, got ${params.line}`
2289
+ );
2290
+ }
2099
2291
  const READ_FILE_SIZE_CAP = 100 * 1024 * 1024;
2100
2292
  const stats = await fs.stat(params.path);
2101
2293
  if (!stats.isFile()) {
@@ -2239,7 +2431,7 @@ __name(extractA2uiToolUpdate, "extractA2uiToolUpdate");
2239
2431
  // packages/acp-bridge/src/sessionArtifacts.ts
2240
2432
  init_esbuild_shims();
2241
2433
  import { createHash } from "node:crypto";
2242
- import { promises as fs2 } from "node:fs";
2434
+ import { constants as fsConstants, promises as fs2 } from "node:fs";
2243
2435
  import path2 from "node:path";
2244
2436
  var SOURCE_RESERVATIONS = {
2245
2437
  tool: 100,
@@ -2248,6 +2440,18 @@ var SOURCE_RESERVATIONS = {
2248
2440
  };
2249
2441
  var WORKSPACE_STATUS_REFRESH_TTL_MS = 5e3;
2250
2442
  var WORKSPACE_STATUS_REFRESH_BATCH_SIZE = 20;
2443
+ var SNAPSHOT_AFTER_DURABLE_EVENTS = 50;
2444
+ var MAX_SNAPSHOT_BACKOFF_MULTIPLIER = 4;
2445
+ var MAX_TOMBSTONED_IDS = 500;
2446
+ var MAX_STICKY_EPHEMERAL_IDS = 500;
2447
+ var MAX_WORKSPACE_HASH_BYTES = 100 * 1024 * 1024;
2448
+ 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;
2449
+ var RESTORE_FAILED_WARNING_PREFIX = "artifact snapshot restore failed";
2450
+ var RESTORE_PARTIAL_FAILED_WARNING_PREFIX = "artifact snapshot restore partially failed";
2451
+ function isArtifactRestoreFailureWarning(warning) {
2452
+ return warning.startsWith(RESTORE_FAILED_WARNING_PREFIX) || warning.startsWith(RESTORE_PARTIAL_FAILED_WARNING_PREFIX);
2453
+ }
2454
+ __name(isArtifactRestoreFailureWarning, "isArtifactRestoreFailureWarning");
2251
2455
  var SessionArtifactValidationError = class extends Error {
2252
2456
  constructor(message, field) {
2253
2457
  super(message);
@@ -2259,6 +2463,20 @@ var SessionArtifactValidationError = class extends Error {
2259
2463
  }
2260
2464
  code = "VALIDATION_FAILED";
2261
2465
  };
2466
+ var SessionArtifactAuthorizationError = class extends Error {
2467
+ constructor(sessionId, artifactId, ownerClientId, requesterClientId) {
2468
+ super(`artifact ${artifactId} is owned by a different client`);
2469
+ this.sessionId = sessionId;
2470
+ this.artifactId = artifactId;
2471
+ this.ownerClientId = ownerClientId;
2472
+ this.requesterClientId = requesterClientId;
2473
+ this.name = "SessionArtifactAuthorizationError";
2474
+ }
2475
+ static {
2476
+ __name(this, "SessionArtifactAuthorizationError");
2477
+ }
2478
+ code = "SESSION_ARTIFACT_FORBIDDEN";
2479
+ };
2262
2480
  var SessionArtifactStore = class {
2263
2481
  static {
2264
2482
  __name(this, "SessionArtifactStore");
@@ -2266,15 +2484,26 @@ var SessionArtifactStore = class {
2266
2484
  sessionId;
2267
2485
  workspaceCwd;
2268
2486
  maxArtifacts;
2487
+ persistence;
2269
2488
  artifacts = /* @__PURE__ */ new Map();
2270
2489
  receivedSeq = 0;
2271
2490
  insertSeq = 0;
2491
+ persistenceSeq = 0;
2492
+ durableEventsSinceSnapshot = 0;
2493
+ consecutiveSnapshotFailures = 0;
2272
2494
  realWorkspaceCwdPromise;
2273
2495
  operationQueue = Promise.resolve();
2496
+ tombstonedIds = /* @__PURE__ */ new Set();
2497
+ tombstonedClientIds = /* @__PURE__ */ new Map();
2498
+ stickyEphemeralIds = /* @__PURE__ */ new Set();
2499
+ markerArtifacts = /* @__PURE__ */ new Map();
2500
+ lastRestoreWarnings = [];
2501
+ lastRestoreWarningDetails = [];
2274
2502
  constructor(options) {
2275
2503
  this.sessionId = options.sessionId;
2276
2504
  this.workspaceCwd = options.workspaceCwd;
2277
2505
  this.maxArtifacts = options.maxArtifacts ?? 200;
2506
+ this.persistence = options.persistence;
2278
2507
  }
2279
2508
  inputBatchLimit() {
2280
2509
  return this.maxArtifacts * 2;
@@ -2287,13 +2516,30 @@ var SessionArtifactStore = class {
2287
2516
  sessionId: this.sessionId,
2288
2517
  artifacts: Array.from(this.artifacts.values()).sort((a, b) => a.insertSeq - b.insertSeq).map(toPublicArtifact),
2289
2518
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2290
- limits: { maxArtifacts: this.maxArtifacts }
2519
+ limits: { maxArtifacts: this.maxArtifacts },
2520
+ ...this.lastRestoreWarnings.length > 0 ? { warnings: [...this.lastRestoreWarnings] } : {},
2521
+ ...this.lastRestoreWarningDetails.length > 0 ? { warningDetails: [...this.lastRestoreWarningDetails] } : {}
2291
2522
  };
2292
2523
  });
2293
2524
  }
2525
+ async get(artifactId) {
2526
+ return this.enqueue(async () => {
2527
+ const artifact = this.artifacts.get(artifactId);
2528
+ if (!artifact) return void 0;
2529
+ if (artifact.workspacePath && shouldRefreshWorkspaceStatus(artifact, Date.now())) {
2530
+ await this.refreshWorkspaceStatus(artifact, { onError: "missing" });
2531
+ }
2532
+ return toPublicArtifact(artifact);
2533
+ });
2534
+ }
2294
2535
  async upsertMany(inputs, options = {}) {
2295
2536
  return this.enqueue(async () => {
2537
+ const validationStrict = options.validationStrict ?? options.strict;
2538
+ const persistenceStrict = options.persistenceStrict ?? options.strict;
2539
+ const before = this.cloneState();
2296
2540
  const normalizedResults = [];
2541
+ const warnings = [];
2542
+ const warningDetails = [];
2297
2543
  for (const input of inputs) {
2298
2544
  try {
2299
2545
  normalizedResults.push(
@@ -2304,7 +2550,7 @@ var SessionArtifactStore = class {
2304
2550
  )
2305
2551
  );
2306
2552
  } catch (error) {
2307
- if (options.strict) {
2553
+ if (validationStrict) {
2308
2554
  throw error;
2309
2555
  }
2310
2556
  const message = error instanceof Error ? error.message : String(error);
@@ -2316,39 +2562,140 @@ var SessionArtifactStore = class {
2316
2562
  }
2317
2563
  }
2318
2564
  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;
2565
+ try {
2566
+ for (const normalized of coalesceByIdentity(normalizedResults)) {
2567
+ const artifact = this.applyStickyEphemeralOverride(normalized);
2568
+ if (this.shouldSuppressTombstonedUpsert(artifact)) {
2569
+ writeStderrLine(
2570
+ `[artifacts] session=${this.sessionId} action=tombstone_replay_suppressed artifactId=${artifact.id}`
2571
+ );
2572
+ continue;
2573
+ }
2574
+ const existing = this.artifacts.get(artifact.id) ?? this.findPublishedUpgradeTarget(artifact) ?? this.findPublishedWorkspaceTarget(artifact);
2575
+ if (!existing) {
2576
+ const stored = {
2577
+ ...artifact,
2578
+ insertSeq: ++this.insertSeq
2579
+ };
2580
+ this.artifacts.set(stored.id, stored);
2581
+ changes.push({
2582
+ action: "created",
2583
+ artifactId: stored.id,
2584
+ artifact: toPublicArtifact(stored)
2585
+ });
2586
+ continue;
2587
+ }
2588
+ try {
2589
+ this.denyCrossClientMutation("upsert", existing.id, existing, {
2590
+ clientId: artifact.clientId
2591
+ });
2592
+ } catch (error) {
2593
+ if (validationStrict) {
2594
+ throw error;
2595
+ }
2596
+ if (error instanceof SessionArtifactAuthorizationError) {
2597
+ warnings.push(error.message);
2598
+ continue;
2599
+ }
2600
+ throw error;
2601
+ }
2602
+ const updated = mergeArtifact(existing, artifact);
2603
+ if (updated.changed) {
2604
+ if (updated.artifact.id !== existing.id) {
2605
+ const removeChange = {
2606
+ action: "removed",
2607
+ artifactId: existing.id,
2608
+ artifact: toPublicArtifact(existing),
2609
+ reason: "explicit",
2610
+ durableTombstoneRequired: existing.durableTombstoneRequired || existing.persistedAt !== void 0,
2611
+ removedClientId: existing.clientId
2612
+ };
2613
+ changes.push(removeChange);
2614
+ this.artifacts.delete(existing.id);
2615
+ } else if (shouldRecordEphemeralUnpin(existing, artifact)) {
2616
+ changes.push({
2617
+ action: "removed",
2618
+ artifactId: existing.id,
2619
+ artifact: toPublicArtifact(existing),
2620
+ reason: "unpin_to_ephemeral",
2621
+ durableTombstoneRequired: true
2622
+ });
2623
+ }
2624
+ this.artifacts.set(updated.artifact.id, updated.artifact);
2625
+ changes.push({
2626
+ action: "updated",
2627
+ artifactId: updated.artifact.id,
2628
+ artifact: toPublicArtifact(updated.artifact)
2629
+ });
2630
+ }
2333
2631
  }
2334
- const updated = mergeArtifact(existing, artifact);
2335
- if (updated.changed) {
2336
- if (updated.artifact.id !== existing.id) {
2337
- this.artifacts.delete(existing.id);
2632
+ const createdIds = new Set(
2633
+ changes.filter((change) => change.action === "created").map((change) => change.artifactId)
2634
+ );
2635
+ changes.push(
2636
+ ...await this.evictOverflow(createdIds, changes, persistenceStrict)
2637
+ );
2638
+ const hasStrictDurableTransition = changes.some(
2639
+ shouldCommitBeforeDurablePersistence
2640
+ );
2641
+ let persistenceWarnings;
2642
+ if (hasStrictDurableTransition && !persistenceStrict) {
2643
+ try {
2644
+ persistenceWarnings = await this.persistChanges(changes, true);
2645
+ } catch (error) {
2646
+ this.restoreState(before);
2647
+ const artifactIds = changes.filter(shouldCommitBeforeDurablePersistence).map((change) => change.artifactId);
2648
+ const warning = "artifact durable removal not persisted; live changes rolled back";
2649
+ writeStderrLine(
2650
+ `[artifacts] session=${this.sessionId} action=upsert_rollback warning=${JSON.stringify(
2651
+ warning
2652
+ )} artifactIds=${JSON.stringify(artifactIds)} error=${JSON.stringify(
2653
+ error instanceof Error ? error.message : String(error)
2654
+ )}`
2655
+ );
2656
+ return {
2657
+ v: 1,
2658
+ sessionId: this.sessionId,
2659
+ changes: [],
2660
+ warnings: [warning],
2661
+ warningDetails: [
2662
+ persistenceFailureDetail({
2663
+ message: warning,
2664
+ operation: "upsert",
2665
+ artifactIds,
2666
+ error
2667
+ })
2668
+ ]
2669
+ };
2338
2670
  }
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
- });
2671
+ } else {
2672
+ persistenceWarnings = await this.persistChanges(
2673
+ changes,
2674
+ persistenceStrict
2675
+ );
2676
+ }
2677
+ warnings.push(...persistenceWarnings);
2678
+ warningDetails.push(
2679
+ ...detailsForPersistenceWarnings(
2680
+ persistenceWarnings,
2681
+ changes,
2682
+ "upsert"
2683
+ )
2684
+ );
2685
+ stripDurableTombstoneMarkers(changes);
2686
+ } catch (error) {
2687
+ if (validationStrict || persistenceStrict || error instanceof SessionArtifactAuthorizationError) {
2688
+ this.restoreState(before);
2345
2689
  }
2690
+ throw error;
2346
2691
  }
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 };
2692
+ return {
2693
+ v: 1,
2694
+ sessionId: this.sessionId,
2695
+ changes,
2696
+ ...warnings.length > 0 ? { warnings } : {},
2697
+ ...warningDetails.length > 0 ? { warningDetails } : {}
2698
+ };
2352
2699
  });
2353
2700
  }
2354
2701
  findPublishedUpgradeTarget(artifact) {
@@ -2356,7 +2703,7 @@ var SessionArtifactStore = class {
2356
2703
  return void 0;
2357
2704
  }
2358
2705
  const byUrl = this.artifacts.get(
2359
- stableArtifactId(this.sessionId, `url:${artifact.url}`)
2706
+ stableSessionArtifactId(this.sessionId, `url:${artifact.url}`)
2360
2707
  );
2361
2708
  if (byUrl && (byUrl.storage !== "published" || byUrl.managedId === artifact.managedId)) {
2362
2709
  return byUrl;
@@ -2392,93 +2739,648 @@ var SessionArtifactStore = class {
2392
2739
  if (!existing) {
2393
2740
  return { v: 1, sessionId: this.sessionId, changes: [] };
2394
2741
  }
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: [] };
2742
+ this.denyCrossClientMutation("remove", artifactId, existing, options);
2743
+ const removeChange = {
2744
+ action: "removed",
2745
+ artifactId,
2746
+ artifact: toPublicArtifact(existing),
2747
+ reason: "explicit",
2748
+ durableTombstoneRequired: existing.durableTombstoneRequired || existing.retention !== "ephemeral" ? true : void 0,
2749
+ removedClientId: existing.clientId
2750
+ };
2751
+ const changes = [removeChange];
2752
+ const needsDurableTombstone = removeChange.durableTombstoneRequired === true;
2753
+ if (needsDurableTombstone) {
2754
+ const before = this.cloneState();
2755
+ this.artifacts.delete(artifactId);
2756
+ try {
2757
+ await this.persistChanges(changes, true);
2758
+ } catch (error) {
2759
+ this.restoreState(before);
2760
+ const warning = "artifact removal not persisted; live artifact kept";
2761
+ return {
2762
+ v: 1,
2763
+ sessionId: this.sessionId,
2764
+ changes: [],
2765
+ warnings: [warning],
2766
+ warningDetails: [
2767
+ persistenceFailureDetail({
2768
+ message: warning,
2769
+ operation: "remove",
2770
+ artifactIds: [artifactId],
2771
+ error
2772
+ })
2773
+ ]
2774
+ };
2775
+ }
2776
+ }
2777
+ if (!needsDurableTombstone) {
2778
+ this.artifacts.delete(artifactId);
2400
2779
  }
2401
- this.artifacts.delete(artifactId);
2780
+ const warnings = needsDurableTombstone ? [] : await this.persistChanges(changes, false);
2781
+ stripDurableTombstoneMarkers(changes);
2782
+ const warningDetails = detailsForPersistenceWarnings(
2783
+ warnings,
2784
+ changes,
2785
+ "remove"
2786
+ );
2402
2787
  return {
2403
2788
  v: 1,
2404
2789
  sessionId: this.sessionId,
2405
- changes: [
2406
- {
2407
- action: "removed",
2408
- artifactId,
2409
- artifact: toPublicArtifact(existing),
2410
- reason: "explicit"
2411
- }
2412
- ]
2790
+ changes,
2791
+ ...warnings.length > 0 ? { warnings } : {},
2792
+ ...warningDetails.length > 0 ? { warningDetails } : {}
2413
2793
  };
2414
2794
  });
2415
2795
  }
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;
2796
+ async restore(snapshot, options = {}) {
2797
+ if (!snapshot) return [];
2798
+ return this.enqueue(async () => {
2799
+ const warnings = [...snapshot?.warnings ?? []];
2800
+ const baselineWarnings = [...warnings];
2801
+ const previousState = this.cloneState();
2802
+ const preservedLiveEphemeralArtifacts = options.preserveLiveEphemeral ? Array.from(this.artifacts.values()).filter((artifact) => artifact.retention === "ephemeral").map(cloneStoredArtifact) : [];
2803
+ let restoredCount = 0;
2804
+ this.artifacts.clear();
2805
+ this.tombstonedIds.clear();
2806
+ this.tombstonedClientIds.clear();
2807
+ this.stickyEphemeralIds.clear();
2808
+ this.markerArtifacts.clear();
2809
+ if (snapshot) {
2810
+ this.insertSeq = 0;
2811
+ this.persistenceSeq = snapshot.sequence;
2812
+ this.durableEventsSinceSnapshot = 0;
2813
+ this.consecutiveSnapshotFailures = 0;
2814
+ for (const id of snapshot.tombstonedIds) {
2815
+ this.tombstonedIds.add(id);
2816
+ }
2817
+ for (const id of snapshot.stickyEphemeralIds.slice(
2818
+ -MAX_STICKY_EPHEMERAL_IDS
2819
+ )) {
2820
+ this.stickyEphemeralIds.add(id);
2821
+ }
2822
+ const markerIds = /* @__PURE__ */ new Set([
2823
+ ...this.tombstonedIds,
2824
+ ...this.stickyEphemeralIds
2825
+ ]);
2826
+ for (const artifact of snapshot.markerArtifacts ?? []) {
2827
+ if (!markerIds.has(artifact.id)) continue;
2828
+ const markerArtifact = await this.normalizeRestoredMarkerArtifact(
2829
+ artifact,
2830
+ warnings
2831
+ );
2832
+ if (markerArtifact)
2833
+ this.markerArtifacts.set(artifact.id, markerArtifact);
2834
+ }
2835
+ }
2836
+ for (const artifact of snapshot?.artifacts ?? []) {
2837
+ try {
2838
+ const input = persistedArtifactToInput(artifact);
2839
+ if (input.retention === "pinned") {
2840
+ input.retention = "restorable";
2841
+ warnings.push(
2842
+ `pinned artifact ${artifact.id} downgraded to restorable; runtime does not support pinned retention`
2843
+ );
2844
+ }
2845
+ let normalized = await this.normalizeInput(
2846
+ input,
2847
+ ++this.receivedSeq,
2848
+ artifact.storage === "published" && !isFileArtifactUrl(artifact.url),
2849
+ {
2850
+ metadataBudget: "persisted",
2851
+ workspaceExpected: workspaceExpectedFromArtifact(artifact),
2852
+ hashWorkspaceContent: false
2853
+ }
2854
+ );
2855
+ if (this.stickyEphemeralIds.has(normalized.id) && normalized.retention !== "ephemeral") {
2856
+ normalized = {
2857
+ ...normalized,
2858
+ retention: "ephemeral",
2859
+ persistenceWarning: "sticky_override_active"
2860
+ };
2861
+ }
2862
+ const stickyApplied = normalized.persistenceWarning === "sticky_override_active";
2863
+ if (normalized.id !== artifact.id) {
2864
+ warnings.push(`skipped artifact with mismatched id ${artifact.id}`);
2865
+ continue;
2866
+ }
2867
+ const retention = normalized.retention;
2868
+ let persistenceWarning;
2869
+ if (stickyApplied) {
2870
+ persistenceWarning = "sticky_override_active";
2871
+ } else if (normalized.status !== "available") {
2872
+ persistenceWarning = "metadata_only_restore";
2873
+ }
2874
+ const stored = {
2875
+ ...normalized,
2876
+ retention,
2877
+ clientRetained: artifact.clientRetained,
2878
+ createdAt: artifact.createdAt,
2879
+ updatedAt: artifact.updatedAt,
2880
+ persistedAt: artifact.persistedAt,
2881
+ status: normalized.status,
2882
+ restoreState: "restored",
2883
+ persistenceWarning,
2884
+ durableTombstoneRequired: retention !== "ephemeral" ? true : void 0,
2885
+ insertSeq: ++this.insertSeq
2886
+ };
2887
+ this.artifacts.set(stored.id, stored);
2888
+ restoredCount++;
2889
+ } catch (error) {
2890
+ warnings.push(
2891
+ `skipped artifact restore: ${error instanceof Error ? error.message : String(error)}`
2892
+ );
2893
+ }
2894
+ }
2895
+ if (snapshot.artifacts.length > 0 && restoredCount === 0) {
2896
+ this.restoreState(previousState);
2897
+ const rollbackWarnings = [
2898
+ ...baselineWarnings,
2899
+ `${RESTORE_FAILED_WARNING_PREFIX}; kept existing live artifacts`
2900
+ ];
2901
+ this.setLastRestoreWarnings(rollbackWarnings);
2902
+ return rollbackWarnings;
2903
+ }
2904
+ if (previousState.artifacts.size > 0 && baselineWarnings.some(isArtifactSnapshotCompletenessWarning)) {
2905
+ this.restoreState(previousState);
2906
+ const rollbackWarnings = [
2907
+ ...baselineWarnings,
2908
+ restoredCount === 0 ? `${RESTORE_FAILED_WARNING_PREFIX}; kept existing live artifacts` : `${RESTORE_PARTIAL_FAILED_WARNING_PREFIX}; kept existing live artifacts`
2909
+ ];
2910
+ this.setLastRestoreWarnings(rollbackWarnings);
2911
+ return rollbackWarnings;
2912
+ }
2913
+ for (const artifact of preservedLiveEphemeralArtifacts) {
2914
+ if (this.artifacts.has(artifact.id) || this.tombstonedIds.has(artifact.id)) {
2915
+ continue;
2916
+ }
2917
+ this.artifacts.set(artifact.id, {
2918
+ ...artifact,
2919
+ insertSeq: ++this.insertSeq
2920
+ });
2921
+ }
2922
+ const evicted = await this.evictOverflow(/* @__PURE__ */ new Set(), []);
2923
+ if (evicted.length > 0) {
2924
+ warnings.push("restored artifact list pruned to live limit");
2925
+ warnings.push(...await this.persistChanges(evicted, false));
2926
+ }
2927
+ this.setLastRestoreWarnings(warnings);
2928
+ return warnings;
2929
+ });
2423
2930
  }
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
- );
2931
+ async recordSnapshot() {
2932
+ const persistence = this.persistence;
2933
+ if (!persistence) {
2934
+ return [
2935
+ "artifact persistence unavailable; restored artifacts not snapshotted"
2936
+ ];
2441
2937
  }
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
2938
+ return this.enqueue(async () => {
2939
+ const recordedAt = (/* @__PURE__ */ new Date()).toISOString();
2940
+ const sequence = this.persistenceSeq + 1;
2941
+ try {
2942
+ await persistence.recordSnapshot(
2943
+ this.buildSnapshotPayload(recordedAt, sequence)
2944
+ );
2945
+ this.persistenceSeq = sequence;
2946
+ this.durableEventsSinceSnapshot = 0;
2947
+ this.consecutiveSnapshotFailures = 0;
2948
+ return [];
2949
+ } catch (error) {
2950
+ writeStderrLine(
2951
+ `[artifacts] session=${this.sessionId} action=snapshot_failed reason=${JSON.stringify(
2952
+ error instanceof Error ? error.message : String(error)
2953
+ )}`
2954
+ );
2955
+ return ["artifact snapshot not persisted"];
2956
+ }
2458
2957
  });
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"
2958
+ }
2959
+ async normalizeRestoredMarkerArtifact(artifact, warnings) {
2960
+ try {
2961
+ const input = persistedArtifactToInput(artifact);
2962
+ if (input.retention === "pinned") {
2963
+ input.retention = "restorable";
2964
+ warnings.push(
2965
+ `pinned marker artifact ${artifact.id} downgraded to restorable; runtime does not support pinned retention`
2966
+ );
2967
+ }
2968
+ const normalized = await this.normalizeInput(
2969
+ input,
2970
+ ++this.receivedSeq,
2971
+ artifact.storage === "published" && !isFileArtifactUrl(artifact.url),
2972
+ {
2973
+ metadataBudget: "persisted",
2974
+ workspaceExpected: workspaceExpectedFromArtifact(artifact),
2975
+ hashWorkspaceContent: false
2976
+ }
2977
+ );
2978
+ if (normalized.id !== artifact.id) {
2979
+ warnings.push(
2980
+ `skipped marker artifact with mismatched id ${artifact.id}`
2981
+ );
2982
+ return void 0;
2983
+ }
2984
+ return toPersistedArtifact(
2985
+ {
2986
+ ...normalized,
2987
+ clientRetained: artifact.clientRetained,
2988
+ createdAt: artifact.createdAt,
2989
+ updatedAt: artifact.updatedAt,
2990
+ persistedAt: artifact.persistedAt
2991
+ },
2992
+ artifact.persistedAt ?? artifact.updatedAt
2993
+ );
2994
+ } catch (error) {
2995
+ warnings.push(
2996
+ `skipped marker artifact ${artifact.id}: ${error instanceof Error ? error.message : String(error)}`
2465
2997
  );
2998
+ return void 0;
2466
2999
  }
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);
3000
+ }
3001
+ cloneState() {
3002
+ return {
3003
+ artifacts: new Map(
3004
+ Array.from(this.artifacts.entries()).map(([id, artifact]) => [
3005
+ id,
3006
+ cloneStoredArtifact(artifact)
3007
+ ])
3008
+ ),
3009
+ receivedSeq: this.receivedSeq,
3010
+ insertSeq: this.insertSeq,
3011
+ persistenceSeq: this.persistenceSeq,
3012
+ durableEventsSinceSnapshot: this.durableEventsSinceSnapshot,
3013
+ consecutiveSnapshotFailures: this.consecutiveSnapshotFailures,
3014
+ tombstonedIds: new Set(this.tombstonedIds),
3015
+ tombstonedClientIds: new Map(this.tombstonedClientIds),
3016
+ stickyEphemeralIds: new Set(this.stickyEphemeralIds),
3017
+ markerArtifacts: new Map(this.markerArtifacts),
3018
+ lastRestoreWarnings: [...this.lastRestoreWarnings],
3019
+ lastRestoreWarningDetails: [...this.lastRestoreWarningDetails]
3020
+ };
3021
+ }
3022
+ restoreState(state) {
3023
+ this.artifacts.clear();
3024
+ for (const [id, artifact] of state.artifacts) {
3025
+ this.artifacts.set(id, cloneStoredArtifact(artifact));
3026
+ }
3027
+ this.receivedSeq = state.receivedSeq;
3028
+ this.insertSeq = state.insertSeq;
3029
+ this.persistenceSeq = state.persistenceSeq;
3030
+ this.durableEventsSinceSnapshot = state.durableEventsSinceSnapshot;
3031
+ this.consecutiveSnapshotFailures = state.consecutiveSnapshotFailures;
3032
+ this.tombstonedIds.clear();
3033
+ for (const id of state.tombstonedIds) {
3034
+ this.tombstonedIds.add(id);
3035
+ }
3036
+ this.tombstonedClientIds.clear();
3037
+ for (const [id, clientId] of state.tombstonedClientIds) {
3038
+ this.tombstonedClientIds.set(id, clientId);
3039
+ }
3040
+ this.stickyEphemeralIds.clear();
3041
+ for (const id of state.stickyEphemeralIds) {
3042
+ this.stickyEphemeralIds.add(id);
3043
+ }
3044
+ this.markerArtifacts.clear();
3045
+ for (const [id, artifact] of state.markerArtifacts) {
3046
+ this.markerArtifacts.set(id, artifact);
3047
+ }
3048
+ this.setLastRestoreWarnings(state.lastRestoreWarnings);
3049
+ this.lastRestoreWarningDetails = [...state.lastRestoreWarningDetails];
3050
+ }
3051
+ async persistChanges(changes, strict = false) {
3052
+ const durableChanges = changes.filter(isDurablePersistenceChange);
3053
+ if (durableChanges.length === 0) {
3054
+ return [];
3055
+ }
3056
+ if (!this.persistence) {
3057
+ if (strict) {
3058
+ throw new SessionArtifactValidationError(
3059
+ "artifact persistence is unavailable",
3060
+ "retention"
3061
+ );
3062
+ }
3063
+ const warnings = this.downgradeDurableChanges(durableChanges);
3064
+ this.applyDurableMarkers(durableChanges);
3065
+ return warnings;
3066
+ }
3067
+ const recordedAt = (/* @__PURE__ */ new Date()).toISOString();
3068
+ for (const change of durableChanges) {
3069
+ if (change.action === "removed") continue;
3070
+ const stored = this.artifacts.get(change.artifactId);
3071
+ if (!stored) continue;
3072
+ const artifact = {
3073
+ ...toPublicArtifact(stored),
3074
+ persistedAt: recordedAt
3075
+ };
3076
+ change.artifact = artifact;
3077
+ }
3078
+ const sequence = this.persistenceSeq + 1;
3079
+ const payload = {
3080
+ v: SESSION_ARTIFACT_PERSISTENCE_VERSION,
3081
+ sessionId: this.sessionId,
3082
+ sequence,
3083
+ recordedAt,
3084
+ changes: durableChanges.map(
3085
+ (change) => toPersistedChange(change, recordedAt)
3086
+ )
3087
+ };
3088
+ try {
3089
+ await this.persistence.recordEvent(payload);
3090
+ this.persistenceSeq = sequence;
3091
+ for (const change of durableChanges) {
3092
+ if (change.action === "removed") continue;
3093
+ const stored = this.artifacts.get(change.artifactId);
3094
+ if (!stored) continue;
3095
+ stored.persistedAt = recordedAt;
3096
+ stored.durableTombstoneRequired = true;
3097
+ change.artifact = toPublicArtifact(stored);
3098
+ }
3099
+ this.applyDurableMarkers(durableChanges);
3100
+ await this.maybeRecordSnapshot(recordedAt);
3101
+ return [];
3102
+ } catch (error) {
3103
+ if (strict) {
3104
+ throw error;
3105
+ }
3106
+ const reason = error instanceof Error ? error.message : String(error);
3107
+ const artifactIds = durableChanges.map((change) => change.artifactId);
3108
+ writeStderrLine(
3109
+ `[artifacts] session=${this.sessionId} action=persist_failed sequence=${payload.sequence} artifactIds=${JSON.stringify(
3110
+ artifactIds
3111
+ )} reason=${JSON.stringify(reason)}`
3112
+ );
3113
+ const warnings = this.downgradeDurableChanges(durableChanges);
3114
+ this.applyDurableMarkers(durableChanges);
3115
+ return warnings;
3116
+ }
3117
+ }
3118
+ async maybeRecordSnapshot(recordedAt) {
3119
+ if (!this.persistence) return;
3120
+ this.durableEventsSinceSnapshot++;
3121
+ const snapshotThreshold = SNAPSHOT_AFTER_DURABLE_EVENTS * Math.min(
3122
+ MAX_SNAPSHOT_BACKOFF_MULTIPLIER,
3123
+ 2 ** this.consecutiveSnapshotFailures
3124
+ );
3125
+ if (this.durableEventsSinceSnapshot < snapshotThreshold) {
3126
+ return;
3127
+ }
3128
+ const sequence = this.persistenceSeq + 1;
3129
+ try {
3130
+ await this.persistence.recordSnapshot(
3131
+ this.buildSnapshotPayload(recordedAt, sequence)
3132
+ );
3133
+ this.persistenceSeq = sequence;
3134
+ this.durableEventsSinceSnapshot = 0;
3135
+ this.consecutiveSnapshotFailures = 0;
3136
+ } catch (error) {
3137
+ this.consecutiveSnapshotFailures = Math.min(
3138
+ this.consecutiveSnapshotFailures + 1,
3139
+ Math.log2(MAX_SNAPSHOT_BACKOFF_MULTIPLIER)
3140
+ );
3141
+ writeStderrLine(
3142
+ `[artifacts] session=${this.sessionId} action=snapshot_failed reason=${JSON.stringify(
3143
+ error instanceof Error ? error.message : String(error)
3144
+ )}`
3145
+ );
3146
+ }
3147
+ }
3148
+ buildSnapshotPayload(recordedAt, sequence) {
3149
+ const artifacts = Array.from(this.artifacts.values()).filter((artifact) => artifact.retention !== "ephemeral").sort((a, b) => a.insertSeq - b.insertSeq).map((artifact) => toPersistedArtifact(artifact, recordedAt));
3150
+ const stickyEphemeralIds = Array.from(this.stickyEphemeralIds).filter(
3151
+ (id) => this.artifacts.has(id) || this.markerArtifacts.has(id)
3152
+ );
3153
+ const markerArtifacts = this.buildMarkerArtifacts(
3154
+ recordedAt,
3155
+ stickyEphemeralIds
3156
+ );
3157
+ return {
3158
+ v: SESSION_ARTIFACT_PERSISTENCE_VERSION,
3159
+ sessionId: this.sessionId,
3160
+ sequence,
3161
+ recordedAt,
3162
+ artifacts,
3163
+ tombstonedIds: Array.from(this.tombstonedIds),
3164
+ stickyEphemeralIds,
3165
+ ...markerArtifacts.length > 0 ? { markerArtifacts } : {}
3166
+ };
3167
+ }
3168
+ buildMarkerArtifacts(recordedAt, stickyEphemeralIds) {
3169
+ const markerIds = [...this.tombstonedIds, ...stickyEphemeralIds];
3170
+ const artifacts = [];
3171
+ const seen = /* @__PURE__ */ new Set();
3172
+ for (const id of markerIds) {
3173
+ if (seen.has(id)) continue;
3174
+ seen.add(id);
3175
+ const live = this.artifacts.get(id);
3176
+ if (live) {
3177
+ artifacts.push(toPersistedArtifact(toPublicArtifact(live), recordedAt));
3178
+ continue;
3179
+ }
3180
+ const markerArtifact = this.markerArtifacts.get(id);
3181
+ if (markerArtifact) {
3182
+ artifacts.push(markerArtifact);
3183
+ }
3184
+ }
3185
+ return artifacts;
3186
+ }
3187
+ downgradeDurableChanges(changes) {
3188
+ let downgraded = false;
3189
+ let removalNotPersisted = false;
3190
+ for (const change of changes) {
3191
+ if (change.action === "removed") {
3192
+ removalNotPersisted = true;
3193
+ if (change.reason === "explicit") {
3194
+ this.rememberTombstone(change);
3195
+ this.stickyEphemeralIds.delete(change.artifactId);
3196
+ }
3197
+ continue;
3198
+ }
3199
+ const stored = this.artifacts.get(change.artifactId);
3200
+ if (!stored) continue;
3201
+ stored.retention = "ephemeral";
3202
+ stored.persistenceWarning = "persistence_unavailable";
3203
+ stored.durableTombstoneRequired = stored.durableTombstoneRequired || stored.persistedAt !== void 0;
3204
+ delete stored.persistedAt;
3205
+ change.artifact = toPublicArtifact(stored);
3206
+ downgraded = true;
3207
+ }
3208
+ const warnings = [];
3209
+ if (downgraded) {
3210
+ warnings.push(
3211
+ "artifact persistence unavailable; durable artifacts kept ephemeral"
3212
+ );
3213
+ }
3214
+ if (removalNotPersisted) {
3215
+ warnings.push("artifact removal not persisted; live removal kept");
3216
+ }
3217
+ return warnings;
3218
+ }
3219
+ applyDurableMarkers(changes) {
3220
+ for (const change of changes) {
3221
+ if (change.action === "removed") {
3222
+ if (change.reason === "explicit") {
3223
+ this.rememberTombstone(change);
3224
+ this.stickyEphemeralIds.delete(change.artifactId);
3225
+ } else if (change.reason === "eviction") {
3226
+ this.stickyEphemeralIds.delete(change.artifactId);
3227
+ this.markerArtifacts.delete(change.artifactId);
3228
+ } else if (change.reason === "unpin_to_ephemeral") {
3229
+ this.rememberStickyEphemeral(change);
3230
+ }
3231
+ continue;
3232
+ }
3233
+ if (change.artifact && change.artifact.retention !== "ephemeral") {
3234
+ this.tombstonedIds.delete(change.artifactId);
3235
+ this.tombstonedClientIds.delete(change.artifactId);
3236
+ this.stickyEphemeralIds.delete(change.artifactId);
3237
+ this.markerArtifacts.delete(change.artifactId);
3238
+ }
3239
+ }
3240
+ }
3241
+ rememberStickyEphemeral(change) {
3242
+ const artifactId = change.artifactId;
3243
+ this.stickyEphemeralIds.delete(artifactId);
3244
+ this.stickyEphemeralIds.add(artifactId);
3245
+ if (change.artifact) {
3246
+ this.markerArtifacts.set(
3247
+ artifactId,
3248
+ toPersistedArtifact(change.artifact, change.artifact.updatedAt)
3249
+ );
3250
+ }
3251
+ while (this.stickyEphemeralIds.size > MAX_STICKY_EPHEMERAL_IDS) {
3252
+ const oldest2 = this.stickyEphemeralIds.values().next().value;
3253
+ if (oldest2 === void 0) break;
3254
+ this.stickyEphemeralIds.delete(oldest2);
3255
+ this.markerArtifacts.delete(oldest2);
3256
+ }
3257
+ }
3258
+ rememberTombstone(change) {
3259
+ this.tombstonedIds.delete(change.artifactId);
3260
+ this.tombstonedIds.add(change.artifactId);
3261
+ this.tombstonedClientIds.set(
3262
+ change.artifactId,
3263
+ change.removedClientId ?? this.artifacts.get(change.artifactId)?.clientId
3264
+ );
3265
+ if (change.artifact) {
3266
+ this.markerArtifacts.set(
3267
+ change.artifactId,
3268
+ toPersistedArtifact(change.artifact, change.artifact.updatedAt)
3269
+ );
3270
+ }
3271
+ while (this.tombstonedIds.size > MAX_TOMBSTONED_IDS) {
3272
+ const oldest2 = this.tombstonedIds.values().next().value;
3273
+ if (oldest2 === void 0) break;
3274
+ writeStderrLine(
3275
+ `[artifacts] session=${this.sessionId} action=tombstone_evicted artifactId=${JSON.stringify(
3276
+ oldest2
3277
+ )} limit=${MAX_TOMBSTONED_IDS}`
3278
+ );
3279
+ this.tombstonedIds.delete(oldest2);
3280
+ this.tombstonedClientIds.delete(oldest2);
3281
+ this.markerArtifacts.delete(oldest2);
3282
+ }
3283
+ }
3284
+ setLastRestoreWarnings(warnings) {
3285
+ this.lastRestoreWarnings = [...warnings];
3286
+ this.lastRestoreWarningDetails = detailsForRestoreWarnings(warnings);
3287
+ }
3288
+ enqueue(operation) {
3289
+ const result = this.operationQueue.then(operation, operation);
3290
+ this.operationQueue = result.then(
3291
+ () => void 0,
3292
+ () => void 0
3293
+ );
3294
+ return result;
3295
+ }
3296
+ denyCrossClientMutation(action, artifactId, existing, options) {
3297
+ if (existing.source !== "client" || existing.clientId === void 0 || existing.clientId === options?.clientId) {
3298
+ return;
3299
+ }
3300
+ writeStderrLine(
3301
+ `[artifacts] session=${this.sessionId} action=${action}_denied artifactId=${artifactId} owner=${existing.clientId} requester=${options?.clientId ?? "<anonymous>"}`
3302
+ );
3303
+ throw new SessionArtifactAuthorizationError(
3304
+ this.sessionId,
3305
+ artifactId,
3306
+ existing.clientId,
3307
+ options?.clientId
3308
+ );
3309
+ }
3310
+ async normalizeInput(input, receivedSeq, trustedPublisherFromCaller, options = {}) {
3311
+ if (!input || typeof input !== "object") {
3312
+ throw new SessionArtifactValidationError("Artifact must be an object");
3313
+ }
3314
+ const title = normalizeString(input.title, "title", 200, true);
3315
+ const description = normalizeString(
3316
+ input.description,
3317
+ "description",
3318
+ 1e3,
3319
+ false
3320
+ );
3321
+ const source = input.source ?? "tool";
3322
+ if (source !== "tool" && source !== "hook" && source !== "client") {
3323
+ throw new SessionArtifactValidationError(
3324
+ "source must be tool, hook, or client",
3325
+ "source"
3326
+ );
3327
+ }
3328
+ const trustedPublisher = trustedPublisherFromCaller;
3329
+ const workspacePath = input.workspacePath ? normalizeWorkspacePath(
3330
+ input.workspacePath,
3331
+ await this.getRealWorkspaceCwdForValidation()
3332
+ ) : void 0;
3333
+ const managedId = normalizeManagedId(input.managedId);
3334
+ const rawStorage = input.storage;
3335
+ const storage = inferStorage(rawStorage, {
3336
+ workspacePath,
3337
+ managedId,
3338
+ url: input.url,
3339
+ trustedPublisher
3340
+ });
3341
+ const url = input.url ? normalizeArtifactUrl(input.url, trustedPublisher) : void 0;
3342
+ validateLocator(storage, {
3343
+ workspacePath,
3344
+ managedId,
3345
+ url,
3346
+ trustedPublisher
3347
+ });
3348
+ const retention = normalizeRetention(input.retention, {
3349
+ persistenceAvailable: this.persistence !== void 0
3350
+ });
3351
+ const workspaceStatus = workspacePath ? await this.getInitialWorkspaceStatus(
3352
+ workspacePath,
3353
+ options.workspaceExpected,
3354
+ { hashContent: options.hashWorkspaceContent !== false }
3355
+ ) : void 0;
3356
+ if (workspaceStatus?.escaped) {
3357
+ throw new SessionArtifactValidationError(
3358
+ "workspacePath must stay inside the workspace",
3359
+ "workspacePath"
3360
+ );
3361
+ }
3362
+ const metadata = withWorkspaceContentHashMetadata(
3363
+ normalizeMetadata(input.metadata, {
3364
+ budget: options.metadataBudget ?? "user"
3365
+ }),
3366
+ workspaceStatus
3367
+ );
3368
+ const kind = normalizeKind(
3369
+ input.kind ?? inferKind({ storage, workspacePath, url })
3370
+ );
3371
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3372
+ const identityKey = buildIdentityKey({
3373
+ storage,
3374
+ workspacePath,
3375
+ managedId,
3376
+ url
3377
+ });
3378
+ const id = stableSessionArtifactId(this.sessionId, identityKey);
2478
3379
  return {
2479
3380
  id,
2480
3381
  identityKey,
2481
3382
  receivedSeq,
3383
+ retentionExplicit: input.retention !== void 0,
2482
3384
  retentionSource: source,
2483
3385
  trustedPublisher,
2484
3386
  kind,
@@ -2492,9 +3394,12 @@ var SessionArtifactStore = class {
2492
3394
  managedId,
2493
3395
  url,
2494
3396
  mimeType: normalizeString(input.mimeType, "mimeType", 120, false),
2495
- sizeBytes: input.sizeBytes !== void 0 ? normalizeSizeBytes(input.sizeBytes) : workspaceStatus?.sizeBytes,
3397
+ sizeBytes: workspaceStatus?.sizeBytes !== void 0 ? workspaceStatus.sizeBytes : input.sizeBytes !== void 0 ? normalizeSizeBytes(input.sizeBytes) : void 0,
2496
3398
  metadata,
2497
- clientRetained: source === "client",
3399
+ retention,
3400
+ restoreState: "live",
3401
+ ...this.persistence === void 0 && retention !== "ephemeral" ? { persistenceWarning: "persistence_unavailable" } : {},
3402
+ clientRetained: source === "client" && (input.clientRetained !== void 0 ? input.clientRetained === true : true),
2498
3403
  createdAt: now,
2499
3404
  updatedAt: now,
2500
3405
  toolCallId: normalizeString(input.toolCallId, "toolCallId", 200, false),
@@ -2508,6 +3413,16 @@ var SessionArtifactStore = class {
2508
3413
  clientId: normalizeString(input.clientId, "clientId", 200, false)
2509
3414
  };
2510
3415
  }
3416
+ shouldSuppressTombstonedUpsert(artifact) {
3417
+ if (!this.tombstonedIds.has(artifact.id)) {
3418
+ return false;
3419
+ }
3420
+ const tombstonedClientId = this.tombstonedClientIds.get(artifact.id);
3421
+ if (artifact.source !== "client") {
3422
+ return false;
3423
+ }
3424
+ return !(artifact.retentionExplicit && artifact.clientId !== void 0 && artifact.clientId === tombstonedClientId);
3425
+ }
2511
3426
  async refreshWorkspaceStatuses() {
2512
3427
  const now = Date.now();
2513
3428
  const staleWorkspaceArtifacts = Array.from(this.artifacts.values()).filter((artifact) => artifact.workspacePath).filter((artifact) => shouldRefreshWorkspaceStatus(artifact, now));
@@ -2520,12 +3435,24 @@ var SessionArtifactStore = class {
2520
3435
  })
2521
3436
  );
2522
3437
  }
2523
- async getInitialWorkspaceStatus(workspacePath) {
3438
+ applyStickyEphemeralOverride(artifact) {
3439
+ if (!this.stickyEphemeralIds.has(artifact.id) || artifact.retentionExplicit || artifact.retention === "ephemeral") {
3440
+ return artifact;
3441
+ }
3442
+ return {
3443
+ ...artifact,
3444
+ retention: "ephemeral",
3445
+ retentionExplicit: true,
3446
+ persistenceWarning: "sticky_override_active"
3447
+ };
3448
+ }
3449
+ async getInitialWorkspaceStatus(workspacePath, expected, options = { hashContent: true }) {
2524
3450
  try {
2525
3451
  return await getWorkspaceStatus(
2526
- this.workspaceCwd,
2527
3452
  workspacePath,
2528
- this.getRealWorkspaceCwd()
3453
+ this.getRealWorkspaceCwd(),
3454
+ expected,
3455
+ options
2529
3456
  );
2530
3457
  } catch (error) {
2531
3458
  const reason = error instanceof Error ? error.message : String(error);
@@ -2541,16 +3468,23 @@ var SessionArtifactStore = class {
2541
3468
  }
2542
3469
  try {
2543
3470
  const status = await getWorkspaceStatus(
2544
- this.workspaceCwd,
2545
3471
  artifact.workspacePath,
2546
- this.getRealWorkspaceCwd()
3472
+ this.getRealWorkspaceCwd(),
3473
+ {
3474
+ sizeBytes: artifact.sizeBytes,
3475
+ mtimeMs: artifact.metadata?.[WORKSPACE_CONTENT_MTIME_MS_METADATA_KEY],
3476
+ sha256: artifact.metadata?.[WORKSPACE_CONTENT_SHA256_METADATA_KEY]
3477
+ }
2547
3478
  );
2548
- artifact.status = status.status;
2549
- artifact.sizeBytes = status.sizeBytes;
3479
+ const changed = isWorkspaceContentChanged(artifact, status);
3480
+ artifact.status = changed ? "changed" : status.status;
3481
+ if (!changed) {
3482
+ artifact.sizeBytes = status.sizeBytes;
3483
+ }
2550
3484
  if (status.escaped) {
2551
3485
  artifact.status = "missing";
2552
3486
  artifact.sizeBytes = void 0;
2553
- delete artifact.workspacePath;
3487
+ artifact.hideWorkspacePath = true;
2554
3488
  }
2555
3489
  artifact.lastStatAt = options.now ?? Date.now();
2556
3490
  } catch (error) {
@@ -2579,7 +3513,18 @@ var SessionArtifactStore = class {
2579
3513
  }
2580
3514
  return this.realWorkspaceCwdPromise;
2581
3515
  }
2582
- async evictOverflow(createdIds, changes) {
3516
+ async getRealWorkspaceCwdForValidation() {
3517
+ try {
3518
+ return await this.getRealWorkspaceCwd();
3519
+ } catch (error) {
3520
+ const reason = error instanceof Error ? error.message : String(error);
3521
+ throw new SessionArtifactValidationError(
3522
+ `workspacePath could not be inspected: ${reason}`,
3523
+ "workspacePath"
3524
+ );
3525
+ }
3526
+ }
3527
+ async evictOverflow(createdIds, changes, strict = false) {
2583
3528
  const removed = [];
2584
3529
  if (this.artifacts.size <= this.maxArtifacts) {
2585
3530
  return removed;
@@ -2607,10 +3552,17 @@ var SessionArtifactStore = class {
2607
3552
  action: "removed",
2608
3553
  artifactId: artifact.id,
2609
3554
  artifact: toPublicArtifact(artifact),
3555
+ durableTombstoneRequired: artifact.durableTombstoneRequired,
2610
3556
  reason: "eviction"
2611
3557
  });
2612
3558
  }
2613
3559
  const overflowCreated = Array.from(this.artifacts.values()).filter((artifact) => createdInThisBatch.has(artifact.id)).sort((a, b) => b.receivedSeq - a.receivedSeq);
3560
+ if (strict && overflowCreated.length > 0 && this.artifacts.size > this.maxArtifacts) {
3561
+ throw new SessionArtifactValidationError(
3562
+ "artifact store is full; no eviction candidate is available",
3563
+ "artifactId"
3564
+ );
3565
+ }
2614
3566
  for (const artifact of overflowCreated) {
2615
3567
  if (this.artifacts.size <= this.maxArtifacts) {
2616
3568
  break;
@@ -2659,7 +3611,12 @@ function mergeBatchArtifact(existing, next) {
2659
3611
  trustedPublisher: true,
2660
3612
  createdAt: existing.createdAt,
2661
3613
  receivedSeq: existing.receivedSeq,
3614
+ retentionExplicit: existing.retentionExplicit || next.retentionExplicit,
2662
3615
  retentionSource: existing.retentionSource,
3616
+ retention: mergeRetention(existing, next),
3617
+ restoreState: "live",
3618
+ persistenceWarning: existing.persistenceWarning ?? next.persistenceWarning,
3619
+ persistedAt: existing.persistedAt ?? next.persistedAt,
2663
3620
  clientRetained: existing.clientRetained || next.clientRetained,
2664
3621
  lastStatAt: void 0
2665
3622
  };
@@ -2673,6 +3630,8 @@ function mergeBatchArtifact(existing, next) {
2673
3630
  metadata: mergeMetadata(existing, next),
2674
3631
  clientRetained: existing.clientRetained || next.clientRetained,
2675
3632
  trustedPublisher: existing.trustedPublisher || next.trustedPublisher,
3633
+ retentionExplicit: existing.retentionExplicit || next.retentionExplicit,
3634
+ retention: mergeRetention(existing, next),
2676
3635
  lastStatAt: next.lastStatAt ?? existing.lastStatAt
2677
3636
  };
2678
3637
  }
@@ -2692,9 +3651,14 @@ function mergeArtifact(existing, incoming) {
2692
3651
  managedId: publishedUpdate ? incoming.managedId ?? existing.managedId : existing.managedId,
2693
3652
  url: publishedUpdate ? incoming.url ?? existing.url : existing.url,
2694
3653
  workspacePath: publishedUpdate || existing.storage === "published" ? void 0 : existing.workspacePath ?? incoming.workspacePath,
3654
+ hideWorkspacePath: publishedUpdate || existing.storage === "published" || incoming.workspacePath ? void 0 : existing.hideWorkspacePath,
2695
3655
  mimeType: publishedUpdate ? incoming.mimeType ?? existing.mimeType : existing.mimeType,
2696
3656
  sizeBytes: existing.storage === "published" && !publishedUpdate ? existing.sizeBytes : mergeSizeBytes(existing, incoming),
2697
3657
  metadata: existing.storage === "published" && !publishedUpdate ? existing.metadata : mergeMetadata(existing, incoming),
3658
+ retention: mergeRetention(existing, incoming),
3659
+ restoreState: "live",
3660
+ persistenceWarning: incoming.retentionExplicit && incoming.retention !== "ephemeral" ? incoming.persistenceWarning : existing.persistenceWarning ?? incoming.persistenceWarning,
3661
+ persistedAt: incoming.retentionExplicit && incoming.retention === "ephemeral" ? void 0 : existing.persistedAt ?? incoming.persistedAt,
2698
3662
  source: existing.source,
2699
3663
  retentionSource: existing.retentionSource,
2700
3664
  trustedPublisher: existing.trustedPublisher || incoming.trustedPublisher,
@@ -2706,6 +3670,7 @@ function mergeArtifact(existing, incoming) {
2706
3670
  next.title = incoming.title;
2707
3671
  next.description = incoming.description;
2708
3672
  delete next.workspacePath;
3673
+ delete next.hideWorkspacePath;
2709
3674
  }
2710
3675
  const changed = !publicArtifactsEqual(
2711
3676
  toPublicArtifact(existing),
@@ -2717,8 +3682,12 @@ function mergeArtifact(existing, incoming) {
2717
3682
  return { artifact: changed ? next : existing, changed };
2718
3683
  }
2719
3684
  __name(mergeArtifact, "mergeArtifact");
3685
+ function shouldRecordEphemeralUnpin(existing, incoming) {
3686
+ return incoming.retentionExplicit && incoming.retention === "ephemeral" && (existing.retention !== "ephemeral" || existing.persistedAt !== void 0 || existing.durableTombstoneRequired === true);
3687
+ }
3688
+ __name(shouldRecordEphemeralUnpin, "shouldRecordEphemeralUnpin");
2720
3689
  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;
3690
+ 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
3691
  }
2723
3692
  __name(publicArtifactsEqual, "publicArtifactsEqual");
2724
3693
  function metadataEqual(a, b) {
@@ -2740,6 +3709,24 @@ function mergeSizeBytes(existing, incoming) {
2740
3709
  return existing.sizeBytes;
2741
3710
  }
2742
3711
  __name(mergeSizeBytes, "mergeSizeBytes");
3712
+ function strongestRetention(a, b) {
3713
+ const rank = {
3714
+ ephemeral: 0,
3715
+ restorable: 1
3716
+ };
3717
+ return rank[b] > rank[a] ? b : a;
3718
+ }
3719
+ __name(strongestRetention, "strongestRetention");
3720
+ function mergeRetention(existing, incoming) {
3721
+ if (incoming.retentionExplicit && incoming.retention === "ephemeral") {
3722
+ return "ephemeral";
3723
+ }
3724
+ if (existing.retentionExplicit && existing.retention === "ephemeral" && !incoming.retentionExplicit) {
3725
+ return "ephemeral";
3726
+ }
3727
+ return strongestRetention(existing.retention, incoming.retention);
3728
+ }
3729
+ __name(mergeRetention, "mergeRetention");
2743
3730
  function mergeMetadata(existing, incoming) {
2744
3731
  if (!incoming.metadata || incoming.source === "hook" || incoming.source !== existing.source) {
2745
3732
  return existing.metadata;
@@ -2747,7 +3734,10 @@ function mergeMetadata(existing, incoming) {
2747
3734
  const merged = { ...existing.metadata ?? {} };
2748
3735
  let changed = false;
2749
3736
  for (const [key, value] of Object.entries(incoming.metadata)) {
2750
- if (!Object.hasOwn(merged, key)) {
3737
+ if ((key === WORKSPACE_CONTENT_SHA256_METADATA_KEY || key === WORKSPACE_CONTENT_MTIME_MS_METADATA_KEY) && merged[key] !== value) {
3738
+ merged[key] = value;
3739
+ changed = true;
3740
+ } else if (!Object.hasOwn(merged, key)) {
2751
3741
  merged[key] = value;
2752
3742
  changed = true;
2753
3743
  }
@@ -2765,7 +3755,7 @@ function mergeMetadata(existing, incoming) {
2765
3755
  }
2766
3756
  __name(mergeMetadata, "mergeMetadata");
2767
3757
  function isMetadataWithinLimit(metadata) {
2768
- return Buffer.byteLength(JSON.stringify(metadata), "utf8") <= 4096;
3758
+ return metadataBudgetBytes(metadata, "persisted") <= 4096;
2769
3759
  }
2770
3760
  __name(isMetadataWithinLimit, "isMetadataWithinLimit");
2771
3761
  function countByRetentionSource(artifacts) {
@@ -2821,6 +3811,7 @@ function removePriorChange(changes, artifactId) {
2821
3811
  }
2822
3812
  __name(removePriorChange, "removePriorChange");
2823
3813
  function toPublicArtifact(artifact) {
3814
+ const hideWorkspacePath = "hideWorkspacePath" in artifact && artifact.hideWorkspacePath === true;
2824
3815
  const {
2825
3816
  id,
2826
3817
  kind,
@@ -2835,13 +3826,16 @@ function toPublicArtifact(artifact) {
2835
3826
  mimeType,
2836
3827
  sizeBytes,
2837
3828
  metadata,
3829
+ retention,
3830
+ restoreState,
3831
+ persistenceWarning,
3832
+ persistedAt,
2838
3833
  clientRetained,
2839
3834
  createdAt,
2840
3835
  updatedAt,
2841
3836
  toolCallId,
2842
3837
  toolName,
2843
- hookEventName,
2844
- clientId
3838
+ hookEventName
2845
3839
  } = artifact;
2846
3840
  return {
2847
3841
  id,
@@ -2851,22 +3845,205 @@ function toPublicArtifact(artifact) {
2851
3845
  status,
2852
3846
  title,
2853
3847
  ...description ? { description } : {},
2854
- ...workspacePath ? { workspacePath } : {},
3848
+ ...workspacePath && !hideWorkspacePath ? { workspacePath } : {},
2855
3849
  ...managedId ? { managedId } : {},
2856
3850
  ...url ? { url } : {},
2857
3851
  ...mimeType ? { mimeType } : {},
2858
3852
  ...sizeBytes !== void 0 ? { sizeBytes } : {},
2859
3853
  ...metadata ? { metadata } : {},
3854
+ retention,
3855
+ ...restoreState ? { restoreState } : {},
3856
+ ...persistenceWarning ? { persistenceWarning } : {},
3857
+ ...persistedAt ? { persistedAt } : {},
2860
3858
  clientRetained,
2861
3859
  createdAt,
2862
3860
  updatedAt,
2863
3861
  ...toolCallId ? { toolCallId } : {},
2864
3862
  ...toolName ? { toolName } : {},
2865
- ...hookEventName ? { hookEventName } : {},
2866
- ...clientId ? { clientId } : {}
3863
+ ...hookEventName ? { hookEventName } : {}
2867
3864
  };
2868
3865
  }
2869
3866
  __name(toPublicArtifact, "toPublicArtifact");
3867
+ function persistedArtifactToInput(artifact) {
3868
+ return {
3869
+ title: artifact.title,
3870
+ kind: artifact.kind,
3871
+ storage: artifact.storage,
3872
+ description: artifact.description,
3873
+ workspacePath: artifact.workspacePath,
3874
+ managedId: artifact.managedId,
3875
+ url: artifact.url,
3876
+ mimeType: artifact.mimeType,
3877
+ sizeBytes: artifact.sizeBytes,
3878
+ metadata: artifact.metadata,
3879
+ source: artifact.source,
3880
+ retention: artifact.retention,
3881
+ clientRetained: artifact.clientRetained,
3882
+ toolCallId: artifact.toolCallId,
3883
+ toolName: artifact.toolName,
3884
+ hookEventName: artifact.hookEventName
3885
+ };
3886
+ }
3887
+ __name(persistedArtifactToInput, "persistedArtifactToInput");
3888
+ function workspaceExpectedFromArtifact(artifact) {
3889
+ if (!artifact.workspacePath) {
3890
+ return void 0;
3891
+ }
3892
+ return {
3893
+ sizeBytes: artifact.sizeBytes,
3894
+ mtimeMs: artifact.metadata?.[WORKSPACE_CONTENT_MTIME_MS_METADATA_KEY],
3895
+ sha256: artifact.metadata?.[WORKSPACE_CONTENT_SHA256_METADATA_KEY]
3896
+ };
3897
+ }
3898
+ __name(workspaceExpectedFromArtifact, "workspaceExpectedFromArtifact");
3899
+ function toPersistedChange(change, recordedAt) {
3900
+ return {
3901
+ action: change.action,
3902
+ artifactId: change.artifactId,
3903
+ ...change.artifact ? { artifact: toPersistedArtifact(change.artifact, recordedAt) } : {},
3904
+ ...change.reason ? { reason: change.reason } : {}
3905
+ };
3906
+ }
3907
+ __name(toPersistedChange, "toPersistedChange");
3908
+ function toPersistedArtifact(artifact, recordedAt) {
3909
+ return {
3910
+ id: artifact.id,
3911
+ kind: artifact.kind,
3912
+ storage: artifact.storage,
3913
+ source: artifact.source,
3914
+ status: artifact.status === "changed" ? "available" : artifact.status,
3915
+ title: artifact.title,
3916
+ ...artifact.description ? { description: artifact.description } : {},
3917
+ ...artifact.workspacePath ? { workspacePath: artifact.workspacePath } : {},
3918
+ ...artifact.managedId ? { managedId: artifact.managedId } : {},
3919
+ ...artifact.url ? { url: artifact.url } : {},
3920
+ ...artifact.mimeType ? { mimeType: artifact.mimeType } : {},
3921
+ ...artifact.sizeBytes !== void 0 ? { sizeBytes: artifact.sizeBytes } : {},
3922
+ ...artifact.metadata ? { metadata: artifact.metadata } : {},
3923
+ retention: artifact.retention,
3924
+ clientRetained: artifact.clientRetained,
3925
+ createdAt: artifact.createdAt,
3926
+ updatedAt: artifact.updatedAt,
3927
+ persistedAt: artifact.persistedAt ?? recordedAt,
3928
+ ...artifact.toolCallId ? { toolCallId: artifact.toolCallId } : {},
3929
+ ...artifact.toolName ? { toolName: artifact.toolName } : {},
3930
+ ...artifact.hookEventName ? { hookEventName: artifact.hookEventName } : {}
3931
+ };
3932
+ }
3933
+ __name(toPersistedArtifact, "toPersistedArtifact");
3934
+ function isFileArtifactUrl(raw) {
3935
+ if (typeof raw !== "string") return false;
3936
+ try {
3937
+ return new URL(raw).protocol === "file:";
3938
+ } catch {
3939
+ return false;
3940
+ }
3941
+ }
3942
+ __name(isFileArtifactUrl, "isFileArtifactUrl");
3943
+ function isArtifactSnapshotCompletenessWarning(warning) {
3944
+ if (warning.startsWith("skipped stale event sequence ")) return false;
3945
+ return warning.startsWith("skipped ") || warning.includes(" list truncated to ");
3946
+ }
3947
+ __name(isArtifactSnapshotCompletenessWarning, "isArtifactSnapshotCompletenessWarning");
3948
+ function shouldCommitBeforeDurablePersistence(change) {
3949
+ return change.action === "removed" && change.durableTombstoneRequired === true;
3950
+ }
3951
+ __name(shouldCommitBeforeDurablePersistence, "shouldCommitBeforeDurablePersistence");
3952
+ function isDurablePersistenceChange(change) {
3953
+ if (change.action === "removed" && change.durableTombstoneRequired === true) {
3954
+ return true;
3955
+ }
3956
+ if (!change.artifact) return false;
3957
+ return change.artifact.retention !== "ephemeral" || change.artifact.persistenceWarning === "persistence_unavailable";
3958
+ }
3959
+ __name(isDurablePersistenceChange, "isDurablePersistenceChange");
3960
+ function stripDurableTombstoneMarkers(changes) {
3961
+ for (const change of changes) {
3962
+ delete change.durableTombstoneRequired;
3963
+ delete change.removedClientId;
3964
+ }
3965
+ }
3966
+ __name(stripDurableTombstoneMarkers, "stripDurableTombstoneMarkers");
3967
+ function detailsForRestoreWarnings(warnings) {
3968
+ return warnings.map((warning) => {
3969
+ if (warning.startsWith(RESTORE_FAILED_WARNING_PREFIX)) {
3970
+ return {
3971
+ code: "ARTIFACT_RESTORE_FAILED",
3972
+ operation: "restore",
3973
+ durability: "unavailable",
3974
+ retryable: true,
3975
+ message: warning
3976
+ };
3977
+ }
3978
+ if (warning.startsWith(RESTORE_PARTIAL_FAILED_WARNING_PREFIX)) {
3979
+ return {
3980
+ code: "ARTIFACT_RESTORE_PARTIAL_FAILED",
3981
+ operation: "restore",
3982
+ durability: "live_only",
3983
+ retryable: true,
3984
+ message: warning
3985
+ };
3986
+ }
3987
+ return {
3988
+ code: "ARTIFACT_WARNING",
3989
+ operation: "restore",
3990
+ message: warning
3991
+ };
3992
+ });
3993
+ }
3994
+ __name(detailsForRestoreWarnings, "detailsForRestoreWarnings");
3995
+ function detailsForPersistenceWarnings(warnings, changes, operation) {
3996
+ if (warnings.length === 0) return [];
3997
+ const artifactIds = changes.filter(isDurablePersistenceChange).map((change) => change.artifactId);
3998
+ return warnings.map((warning) => {
3999
+ if (warning === "artifact persistence unavailable; durable artifacts kept ephemeral") {
4000
+ return {
4001
+ code: "ARTIFACT_PERSISTENCE_UNAVAILABLE",
4002
+ operation,
4003
+ artifactIds,
4004
+ durability: "live_only",
4005
+ retryable: false,
4006
+ message: warning
4007
+ };
4008
+ }
4009
+ if (warning === "artifact removal not persisted; live removal kept") {
4010
+ return {
4011
+ code: "ARTIFACT_REMOVAL_NOT_PERSISTED",
4012
+ operation,
4013
+ artifactIds,
4014
+ durability: "live_only",
4015
+ retryable: true,
4016
+ message: warning
4017
+ };
4018
+ }
4019
+ return {
4020
+ code: "ARTIFACT_WARNING",
4021
+ operation,
4022
+ artifactIds,
4023
+ message: warning
4024
+ };
4025
+ });
4026
+ }
4027
+ __name(detailsForPersistenceWarnings, "detailsForPersistenceWarnings");
4028
+ function persistenceFailureDetail(options) {
4029
+ const unavailable = options.error instanceof SessionArtifactValidationError && options.error.message === "artifact persistence is unavailable";
4030
+ return {
4031
+ code: unavailable ? "ARTIFACT_PERSISTENCE_UNAVAILABLE" : "ARTIFACT_PERSISTENCE_WRITE_FAILED",
4032
+ operation: options.operation,
4033
+ artifactIds: options.artifactIds,
4034
+ durability: "unavailable",
4035
+ retryable: !unavailable,
4036
+ message: options.message
4037
+ };
4038
+ }
4039
+ __name(persistenceFailureDetail, "persistenceFailureDetail");
4040
+ function cloneStoredArtifact(artifact) {
4041
+ return {
4042
+ ...artifact,
4043
+ ...artifact.metadata ? { metadata: { ...artifact.metadata } } : {}
4044
+ };
4045
+ }
4046
+ __name(cloneStoredArtifact, "cloneStoredArtifact");
2870
4047
  function inferStorage(requested, locators) {
2871
4048
  if (requested !== void 0 && typeof requested !== "string") {
2872
4049
  throw new SessionArtifactValidationError(
@@ -2960,10 +4137,6 @@ function buildIdentityKey(input) {
2960
4137
  );
2961
4138
  }
2962
4139
  __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
4140
  function managedIdForWorkspacePath(workspaceCwd, workspacePath) {
2968
4141
  return createHash("sha1").update(path2.resolve(workspaceCwd, workspacePath)).digest("hex").slice(0, 16);
2969
4142
  }
@@ -3077,6 +4250,12 @@ function normalizeArtifactUrl(raw, allowFile) {
3077
4250
  "url"
3078
4251
  );
3079
4252
  }
4253
+ if (hasSecretLikeUrlComponent(parsed)) {
4254
+ throw new SessionArtifactValidationError(
4255
+ "url must not include secret-like components",
4256
+ "url"
4257
+ );
4258
+ }
3080
4259
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:" && !(allowFile && parsed.protocol === "file:")) {
3081
4260
  throw new SessionArtifactValidationError(
3082
4261
  allowFile ? "url must use http, https, or file" : "url must use http or https",
@@ -3086,7 +4265,64 @@ function normalizeArtifactUrl(raw, allowFile) {
3086
4265
  return parsed.href;
3087
4266
  }
3088
4267
  __name(normalizeArtifactUrl, "normalizeArtifactUrl");
3089
- function normalizeMetadata(metadata) {
4268
+ function hasSecretLikeUrlComponent(parsed) {
4269
+ for (const [key, value] of parsed.searchParams) {
4270
+ if (isSecretLikeUrlText(key) || isSecretLikeUrlValue(value)) {
4271
+ return true;
4272
+ }
4273
+ }
4274
+ for (const segment of parsed.pathname.split("/").filter(Boolean)) {
4275
+ let decodedSegment = segment;
4276
+ try {
4277
+ decodedSegment = decodeURIComponent(segment);
4278
+ } catch {
4279
+ }
4280
+ if (isSecretLikeUrlValue(decodedSegment)) {
4281
+ return true;
4282
+ }
4283
+ }
4284
+ const fragment = parsed.hash.slice(1);
4285
+ return hasSecretLikeUrlFragment(fragment);
4286
+ }
4287
+ __name(hasSecretLikeUrlComponent, "hasSecretLikeUrlComponent");
4288
+ function hasSecretLikeUrlFragment(fragment) {
4289
+ if (!fragment) return false;
4290
+ const candidates = /* @__PURE__ */ new Set([fragment]);
4291
+ try {
4292
+ candidates.add(decodeURIComponent(fragment));
4293
+ } catch {
4294
+ }
4295
+ for (const candidate of candidates) {
4296
+ if (isSecretLikeUrlText(candidate) || isSecretLikeUrlValue(candidate)) {
4297
+ return true;
4298
+ }
4299
+ for (const [key, value] of new URLSearchParams(candidate)) {
4300
+ if (isSecretLikeUrlText(key) || isSecretLikeUrlValue(value)) {
4301
+ return true;
4302
+ }
4303
+ }
4304
+ }
4305
+ return false;
4306
+ }
4307
+ __name(hasSecretLikeUrlFragment, "hasSecretLikeUrlFragment");
4308
+ function isSecretLikeUrlText(value) {
4309
+ const normalized = value.replace(/([a-z])([A-Z])/g, "$1-$2");
4310
+ return /(?:^|[-_.])(token|secret|password|passwd|pwd|cookie|authorization|credential|signature|sig|api[-_]?key|access[-_]?key)(?:$|[-_.=&#])/i.test(
4311
+ normalized
4312
+ );
4313
+ }
4314
+ __name(isSecretLikeUrlText, "isSecretLikeUrlText");
4315
+ function isSecretLikeUrlValue(value) {
4316
+ return SECRET_TOKEN_VALUE_PATTERN.test(value.trim());
4317
+ }
4318
+ __name(isSecretLikeUrlValue, "isSecretLikeUrlValue");
4319
+ function isSecretLikeMetadataValue(value) {
4320
+ return /^(?:bearer\s+\S{8,}|sk-[A-Za-z0-9_-]{12,}|(?:gh[pousr]|github_pat)_[A-Za-z0-9_/-]{12,})$/i.test(
4321
+ value.trim()
4322
+ );
4323
+ }
4324
+ __name(isSecretLikeMetadataValue, "isSecretLikeMetadataValue");
4325
+ function normalizeMetadata(metadata, options = {}) {
3090
4326
  if (metadata === void 0) {
3091
4327
  return void 0;
3092
4328
  }
@@ -3098,6 +4334,12 @@ function normalizeMetadata(metadata) {
3098
4334
  }
3099
4335
  const normalized = {};
3100
4336
  for (const [key, value] of Object.entries(metadata)) {
4337
+ if (isPrototypeMetadataKey(key)) {
4338
+ continue;
4339
+ }
4340
+ if (options.budget !== "persisted" && isReservedWorkspaceMetadataKey(key)) {
4341
+ continue;
4342
+ }
3101
4343
  if (!key) {
3102
4344
  throw new SessionArtifactValidationError(
3103
4345
  "metadata keys must not be empty",
@@ -3116,6 +4358,12 @@ function normalizeMetadata(metadata) {
3116
4358
  "metadata"
3117
4359
  );
3118
4360
  }
4361
+ if (isSecretLikeUrlText(key)) {
4362
+ throw new SessionArtifactValidationError(
4363
+ "metadata keys must not contain secret-like names",
4364
+ "metadata"
4365
+ );
4366
+ }
3119
4367
  if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
3120
4368
  throw new SessionArtifactValidationError(
3121
4369
  "metadata values must be primitive",
@@ -3134,12 +4382,18 @@ function normalizeMetadata(metadata) {
3134
4382
  "metadata"
3135
4383
  );
3136
4384
  }
4385
+ if (typeof value === "string" && !isReservedWorkspaceMetadataKey(key) && isSecretLikeMetadataValue(value)) {
4386
+ throw new SessionArtifactValidationError(
4387
+ "metadata string values must not contain secret-like tokens",
4388
+ "metadata"
4389
+ );
4390
+ }
3137
4391
  normalized[key] = value;
3138
4392
  }
3139
4393
  if (Object.keys(normalized).length === 0) {
3140
4394
  return void 0;
3141
4395
  }
3142
- if (Buffer.byteLength(JSON.stringify(normalized), "utf8") > 4096) {
4396
+ if (metadataBudgetBytes(normalized, options.budget ?? "user") > 4096) {
3143
4397
  throw new SessionArtifactValidationError(
3144
4398
  "metadata must be 4096 bytes or fewer",
3145
4399
  "metadata"
@@ -3148,6 +4402,25 @@ function normalizeMetadata(metadata) {
3148
4402
  return normalized;
3149
4403
  }
3150
4404
  __name(normalizeMetadata, "normalizeMetadata");
4405
+ function normalizeRetention(value, options) {
4406
+ if (value === void 0) {
4407
+ return options.persistenceAvailable ? "restorable" : "ephemeral";
4408
+ }
4409
+ if (value === "ephemeral" || value === "restorable") {
4410
+ return value;
4411
+ }
4412
+ if (value === "pinned") {
4413
+ throw new SessionArtifactValidationError(
4414
+ "pinned retention is not supported by session_artifacts_persistence",
4415
+ "retention"
4416
+ );
4417
+ }
4418
+ throw new SessionArtifactValidationError(
4419
+ "retention must be ephemeral or restorable",
4420
+ "retention"
4421
+ );
4422
+ }
4423
+ __name(normalizeRetention, "normalizeRetention");
3151
4424
  function normalizeSizeBytes(value) {
3152
4425
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
3153
4426
  throw new SessionArtifactValidationError(
@@ -3173,21 +4446,83 @@ function inferKind(input) {
3173
4446
  return input.workspacePath ? "file" : "other";
3174
4447
  }
3175
4448
  __name(inferKind, "inferKind");
3176
- async function getWorkspaceStatus(workspaceCwd, workspacePath, realWorkspaceCwd) {
3177
- const absolutePath = path2.resolve(workspaceCwd, workspacePath);
4449
+ async function getWorkspaceStatus(workspacePath, realWorkspaceCwd, expected, options = { hashContent: true }) {
3178
4450
  const realWorkspace = await realWorkspaceCwd;
4451
+ const absolutePath = path2.resolve(realWorkspace, workspacePath);
3179
4452
  try {
3180
4453
  const realPath = await fs2.realpath(absolutePath);
3181
4454
  const relative = path2.relative(realWorkspace, realPath);
3182
4455
  if (!relative || isOutsidePath(relative)) {
3183
4456
  return { status: "missing", escaped: true };
3184
4457
  }
3185
- const stat = await fs2.stat(realPath);
3186
- return {
3187
- status: "available",
3188
- ...stat.isFile() ? { sizeBytes: stat.size } : {}
3189
- };
4458
+ const preOpenStat = await fs2.lstat(realPath);
4459
+ const handle = await fs2.open(
4460
+ realPath,
4461
+ fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW
4462
+ );
4463
+ try {
4464
+ const stat = await handle.stat();
4465
+ if (!isSameFile(preOpenStat, stat)) {
4466
+ return { status: "missing", escaped: true };
4467
+ }
4468
+ if (stat.isFile()) {
4469
+ const expectedMtimeMs = typeof expected?.mtimeMs === "number" ? expected.mtimeMs : void 0;
4470
+ const expectedSha256 = typeof expected?.sha256 === "string" ? expected.sha256 : void 0;
4471
+ const unchanged = expected?.sizeBytes === stat.size && expectedMtimeMs === stat.mtimeMs;
4472
+ const sizeChanged = expected?.sizeBytes !== void 0 && expected.sizeBytes !== stat.size;
4473
+ if (sizeChanged) {
4474
+ return {
4475
+ status: "changed",
4476
+ sizeBytes: stat.size,
4477
+ mtimeMs: stat.mtimeMs
4478
+ };
4479
+ }
4480
+ if (unchanged) {
4481
+ return {
4482
+ status: "available",
4483
+ sizeBytes: stat.size,
4484
+ mtimeMs: stat.mtimeMs
4485
+ };
4486
+ }
4487
+ if (stat.size > MAX_WORKSPACE_HASH_BYTES) {
4488
+ return {
4489
+ status: expectedSha256 ? "changed" : "available",
4490
+ sizeBytes: stat.size,
4491
+ mtimeMs: stat.mtimeMs
4492
+ };
4493
+ }
4494
+ if (!options.hashContent) {
4495
+ return {
4496
+ status: expectedSha256 ? "changed" : "available",
4497
+ sizeBytes: stat.size,
4498
+ mtimeMs: stat.mtimeMs
4499
+ };
4500
+ }
4501
+ const sha256 = await hashFile(handle);
4502
+ if (expectedSha256 && sha256 !== expectedSha256) {
4503
+ return {
4504
+ status: "changed",
4505
+ sizeBytes: stat.size,
4506
+ mtimeMs: stat.mtimeMs
4507
+ };
4508
+ }
4509
+ return {
4510
+ status: "available",
4511
+ sizeBytes: stat.size,
4512
+ mtimeMs: stat.mtimeMs,
4513
+ sha256
4514
+ };
4515
+ }
4516
+ return {
4517
+ status: "available"
4518
+ };
4519
+ } finally {
4520
+ await handle.close();
4521
+ }
3190
4522
  } catch (error) {
4523
+ if (isNoFollowSymlinkError(error)) {
4524
+ return { status: "missing", escaped: true };
4525
+ }
3191
4526
  if (!isNotFoundError(error)) {
3192
4527
  throw error;
3193
4528
  }
@@ -3198,6 +4533,41 @@ async function getWorkspaceStatus(workspaceCwd, workspacePath, realWorkspaceCwd)
3198
4533
  }
3199
4534
  }
3200
4535
  __name(getWorkspaceStatus, "getWorkspaceStatus");
4536
+ function isSameFile(before, after) {
4537
+ return before.dev === after.dev && before.ino === after.ino;
4538
+ }
4539
+ __name(isSameFile, "isSameFile");
4540
+ async function hashFile(handle) {
4541
+ const hash = createHash("sha256");
4542
+ for await (const chunk of handle.createReadStream({ start: 0 })) {
4543
+ hash.update(chunk);
4544
+ }
4545
+ return hash.digest("hex");
4546
+ }
4547
+ __name(hashFile, "hashFile");
4548
+ function withWorkspaceContentHashMetadata(metadata, workspaceStatus) {
4549
+ if (workspaceStatus?.status !== "available" || !workspaceStatus.sha256) {
4550
+ return metadata;
4551
+ }
4552
+ const next = {
4553
+ ...metadata ?? {},
4554
+ [WORKSPACE_CONTENT_SHA256_METADATA_KEY]: workspaceStatus.sha256,
4555
+ ...workspaceStatus.mtimeMs !== void 0 ? { [WORKSPACE_CONTENT_MTIME_MS_METADATA_KEY]: workspaceStatus.mtimeMs } : {}
4556
+ };
4557
+ return next;
4558
+ }
4559
+ __name(withWorkspaceContentHashMetadata, "withWorkspaceContentHashMetadata");
4560
+ function isWorkspaceContentChanged(artifact, status) {
4561
+ if (status.status !== "available") {
4562
+ return false;
4563
+ }
4564
+ const expectedSha256 = artifact.metadata?.[WORKSPACE_CONTENT_SHA256_METADATA_KEY];
4565
+ if (artifact.sizeBytes !== void 0 && status.sizeBytes !== void 0 && status.sizeBytes !== artifact.sizeBytes) {
4566
+ return true;
4567
+ }
4568
+ return typeof expectedSha256 === "string" && status.sha256 !== void 0 && status.sha256 !== expectedSha256;
4569
+ }
4570
+ __name(isWorkspaceContentChanged, "isWorkspaceContentChanged");
3201
4571
  async function danglingSymlinkEscapesWorkspace(absolutePath, realWorkspace) {
3202
4572
  try {
3203
4573
  const stat = await fs2.lstat(absolutePath);
@@ -3230,6 +4600,13 @@ function isNotFoundError(error) {
3230
4600
  return code === "ENOENT" || code === "ENOTDIR";
3231
4601
  }
3232
4602
  __name(isNotFoundError, "isNotFoundError");
4603
+ function isNoFollowSymlinkError(error) {
4604
+ if (typeof error !== "object" || error === null || !("code" in error)) {
4605
+ return false;
4606
+ }
4607
+ return error.code === "ELOOP";
4608
+ }
4609
+ __name(isNoFollowSymlinkError, "isNoFollowSymlinkError");
3233
4610
  function isOutsidePath(relative) {
3234
4611
  return relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative);
3235
4612
  }
@@ -3464,6 +4841,12 @@ function parseWorkspaceMemoryDreamResult(response) {
3464
4841
  };
3465
4842
  }
3466
4843
  __name(parseWorkspaceMemoryDreamResult, "parseWorkspaceMemoryDreamResult");
4844
+ function pickUserInputEchoMeta(meta) {
4845
+ if (!meta || typeof meta !== "object" || Array.isArray(meta)) return {};
4846
+ const inputAnnotations = meta["inputAnnotations"];
4847
+ return Array.isArray(inputAnnotations) ? { inputAnnotations } : {};
4848
+ }
4849
+ __name(pickUserInputEchoMeta, "pickUserInputEchoMeta");
3467
4850
  function echoPromptToSessionBus(entry, req, originatorClientId) {
3468
4851
  const prompt = req.prompt;
3469
4852
  if (!Array.isArray(prompt) || prompt.length === 0) return;
@@ -3485,7 +4868,11 @@ function echoPromptToSessionBus(entry, req, originatorClientId) {
3485
4868
  // field permitted alongside spec fields, the SDK normalizer
3486
4869
  // reads it from `update._meta`/`data._meta`, and every other
3487
4870
  // agent-emitted session_update carries `_meta` the same way.
3488
- _meta: { serverTimestamp, source: "bridge-echo" }
4871
+ _meta: {
4872
+ ...pickUserInputEchoMeta(req._meta),
4873
+ serverTimestamp,
4874
+ source: "bridge-echo"
4875
+ }
3489
4876
  }
3490
4877
  },
3491
4878
  ...originatorClientId ? { originatorClientId } : {}
@@ -3582,6 +4969,11 @@ function broadcastTurnError(entry, sessionId, err, promptId, originatorClientId)
3582
4969
  );
3583
4970
  }
3584
4971
  entry.retryAllowed = true;
4972
+ entry.turnError = {
4973
+ message,
4974
+ ...code ? { code } : {},
4975
+ ...errorKind ? { errorKind } : {}
4976
+ };
3585
4977
  try {
3586
4978
  entry.events.publish({
3587
4979
  type: "turn_error",
@@ -3624,6 +5016,7 @@ function extractPromptText(prompt) {
3624
5016
  __name(extractPromptText, "extractPromptText");
3625
5017
  var DEFAULT_INIT_TIMEOUT_MS = 1e4;
3626
5018
  var PERSIST_TIMEOUT_MS = 5e3;
5019
+ var MAX_PARENT_PERSIST_ATTEMPTS = 3;
3627
5020
  var MCP_RESTART_TIMEOUT_MS = 3e5;
3628
5021
  var WORKSPACE_MEMORY_REMEMBER_TIMEOUT_MS = 3e5;
3629
5022
  var MCP_OAUTH_TIMEOUT_MS = 6e5;
@@ -3631,6 +5024,7 @@ var DAEMON_RETRY_META_KEY = "qwen.daemon.retry";
3631
5024
  var DAEMON_CONTINUE_META_KEY = "qwen.daemon.continueLastTurn";
3632
5025
  var SESSION_RECAP_TIMEOUT_MS = 6e4;
3633
5026
  var SESSION_BTW_TIMEOUT_MS = 6e4;
5027
+ var SESSION_TRANSCRIPT_TIMEOUT_MS = 6e4;
3634
5028
  var SHELL_COMMAND_TIMEOUT_MS = 12e4;
3635
5029
  var MAX_SHELL_OUTPUT_FOR_HISTORY = 1e4;
3636
5030
  var MAX_MID_TURN_QUEUE_DEPTH = 20;
@@ -3671,6 +5065,15 @@ function createAcpSessionBridge(opts) {
3671
5065
  );
3672
5066
  }
3673
5067
  }, "releaseFreshSessionReservation");
5068
+ const emitSessionLifecycle = /* @__PURE__ */ __name((event) => {
5069
+ try {
5070
+ opts.sessionLifecycle?.(event);
5071
+ } catch (err) {
5072
+ const message = `qwen serve: session lifecycle callback failed: ${err instanceof Error ? err.message : String(err)}`;
5073
+ opts.onDiagnosticLine?.(message, "warn");
5074
+ writeStderrLine(message);
5075
+ }
5076
+ }, "emitSessionLifecycle");
3674
5077
  if (defaultSessionScope !== "single" && defaultSessionScope !== "thread") {
3675
5078
  throw new TypeError(
3676
5079
  `Invalid sessionScope: ${JSON.stringify(defaultSessionScope)}. Expected 'single' or 'thread'.`
@@ -3855,14 +5258,32 @@ function createAcpSessionBridge(opts) {
3855
5258
  const aliveChannels = /* @__PURE__ */ new Set();
3856
5259
  let inFlightChannelSpawn;
3857
5260
  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");
5261
+ const toSessionSummary = /* @__PURE__ */ __name((entry) => {
5262
+ let isWaitingForPermission = false;
5263
+ let isWaitingForUserQuestion = false;
5264
+ for (const interaction of entry.pendingInteractions.values()) {
5265
+ if (interaction.kind === "user_question") {
5266
+ isWaitingForUserQuestion = true;
5267
+ } else {
5268
+ isWaitingForPermission = true;
5269
+ }
5270
+ }
5271
+ return {
5272
+ sessionId: entry.sessionId,
5273
+ workspaceCwd: entry.workspaceCwd,
5274
+ createdAt: entry.createdAt,
5275
+ displayName: entry.displayName,
5276
+ ...entry.parentSessionId ? { parentSessionId: entry.parentSessionId } : {},
5277
+ clientCount: entry.clientIds.size,
5278
+ hasActivePrompt: entry.promptActive,
5279
+ isWaitingForPermission,
5280
+ isWaitingForUserQuestion,
5281
+ pendingInteractionCount: entry.pendingInteractions.size,
5282
+ hasTurnError: entry.turnError !== void 0,
5283
+ ...entry.turnError !== void 0 ? { turnError: entry.turnError } : {},
5284
+ pendingInteractions: [...entry.pendingInteractions.values()]
5285
+ };
5286
+ }, "toSessionSummary");
3866
5287
  const permissionConsensusQuorum = opts.permissionConsensusQuorum;
3867
5288
  if (permissionConsensusQuorum !== void 0 && (!Number.isInteger(permissionConsensusQuorum) || permissionConsensusQuorum < 1)) {
3868
5289
  throw new Error(
@@ -3888,6 +5309,7 @@ function createAcpSessionBridge(opts) {
3888
5309
  }
3889
5310
  );
3890
5311
  let shuttingDown = false;
5312
+ let shutdownPromise;
3891
5313
  const teeServeDebugLine = /* @__PURE__ */ __name((message) => {
3892
5314
  writeServeDebugLine(message);
3893
5315
  if (opts.onDiagnosticLine && isServeDebugLoggingEnabled()) {
@@ -3921,6 +5343,22 @@ function createAcpSessionBridge(opts) {
3921
5343
  entry.clientIds.set(clientId, count - 1);
3922
5344
  }
3923
5345
  }, "unregisterClient");
5346
+ const rollbackAttachRegistration = /* @__PURE__ */ __name(async (entry, clientId, attachCountDelta = 1) => {
5347
+ entry.attachCount = Math.max(0, entry.attachCount - attachCountDelta);
5348
+ unregisterClient(entry, clientId);
5349
+ if (entry.spawnOwnerWantedKill && entry.attachCount === 0 && entry.events.subscriberCount === 0) {
5350
+ await bridgeApi.killSession(entry.sessionId).catch(() => {
5351
+ });
5352
+ } else if (entry.clientIds.size === 0 && entry.events.subscriberCount === 0 && !entry.promptActive) {
5353
+ await closeSessionImpl(entry.sessionId, void 0, {
5354
+ reason: "last_client_detached"
5355
+ }).catch((err) => {
5356
+ writeStderrLine(
5357
+ `qwen serve: close-on-attach-rollback failed for ${JSON.stringify(entry.sessionId)}: ${err instanceof Error ? err.stack ?? err.message : String(err)}`
5358
+ );
5359
+ });
5360
+ }
5361
+ }, "rollbackAttachRegistration");
3924
5362
  const resolveTrustedClientId = /* @__PURE__ */ __name((entry, clientId) => {
3925
5363
  if (clientId === void 0) return void 0;
3926
5364
  if (!entry.clientIds.has(clientId)) {
@@ -4001,7 +5439,11 @@ function createAcpSessionBridge(opts) {
4001
5439
  inputTokens,
4002
5440
  outputTokens,
4003
5441
  durationMs
4004
- )
5442
+ ),
5443
+ // `create_sub_session` tool: forward the request/response hook so a child
5444
+ // tool can ask the daemon to spawn a sub-session and (for 'first-turn')
5445
+ // return its result. Omitted → the method reports daemon-only.
5446
+ opts.onCreateSubSession
4005
5447
  );
4006
5448
  const connection = new ClientSideConnection(() => client, channel.stream);
4007
5449
  const info = {
@@ -4066,6 +5508,12 @@ function createAcpSessionBridge(opts) {
4066
5508
  }
4067
5509
  byId.delete(sid);
4068
5510
  telemetry.metrics?.sessionLifecycle("die");
5511
+ emitSessionLifecycle({
5512
+ type: "removed",
5513
+ sessionId: sid,
5514
+ workspaceCwd: sessEntry.workspaceCwd,
5515
+ reason: "channel_closed"
5516
+ });
4069
5517
  info.client.markSessionClosed(sid);
4070
5518
  if (defaultEntry === sessEntry) defaultEntry = void 0;
4071
5519
  sessEntry.events.close();
@@ -4114,10 +5562,12 @@ function createAcpSessionBridge(opts) {
4114
5562
  }
4115
5563
  }
4116
5564
  __name(ensureChannel, "ensureChannel");
4117
- async function doSpawn(modelServiceId, effectiveScope, requestedClientId, onSessionRegistered) {
5565
+ async function doSpawn(modelServiceId, effectiveScope, approvalMode, requestedClientId, onSessionRegistered, parentSessionId) {
4118
5566
  const ci = await ensureChannel();
4119
5567
  ci.sessionSpawnsInFlight++;
4120
5568
  let sessionRegistered = false;
5569
+ let sessionRemovedDuringInitialization = false;
5570
+ let initializedSessionId;
4121
5571
  let newSessionResp;
4122
5572
  try {
4123
5573
  try {
@@ -4152,13 +5602,58 @@ function createAcpSessionBridge(opts) {
4152
5602
  const entry = createSessionEntry(
4153
5603
  ci,
4154
5604
  newSessionResp.sessionId,
4155
- boundWorkspace
5605
+ boundWorkspace,
5606
+ void 0,
5607
+ { parentSessionId }
4156
5608
  );
5609
+ initializedSessionId = entry.sessionId;
4157
5610
  sessionRegistered = true;
4158
5611
  onSessionRegistered?.();
4159
5612
  seedSnapshotCaches(entry, newSessionResp);
4160
5613
  const clientId = registerClient(entry, requestedClientId);
4161
- if (effectiveScope === "single" && !defaultEntry) defaultEntry = entry;
5614
+ let parentSessionPersisted;
5615
+ if (entry.parentSessionId) {
5616
+ const parentDeadline = Date.now() + initTimeoutMs;
5617
+ let lastParentErr;
5618
+ for (let attempt = 1; attempt <= MAX_PARENT_PERSIST_ATTEMPTS; attempt++) {
5619
+ const remaining = parentDeadline - Date.now();
5620
+ if (remaining <= 0) {
5621
+ parentSessionPersisted = false;
5622
+ lastParentErr = "deadline exceeded";
5623
+ break;
5624
+ }
5625
+ try {
5626
+ const parentResult = await Promise.race([
5627
+ withTimeout(
5628
+ entry.connection.extMethod(
5629
+ SERVE_CONTROL_EXT_METHODS.sessionParent,
5630
+ {
5631
+ sessionId: entry.sessionId,
5632
+ parentSessionId: entry.parentSessionId
5633
+ }
5634
+ ),
5635
+ remaining,
5636
+ "sessionParent"
5637
+ ),
5638
+ getTransportClosedReject(entry)
5639
+ ]);
5640
+ parentSessionPersisted = parentResult?.persisted === true;
5641
+ break;
5642
+ } catch (err) {
5643
+ lastParentErr = err instanceof Error ? err.message : String(err);
5644
+ const terminal = err instanceof BridgeTimeoutError || err instanceof BridgeChannelClosedError || attempt === MAX_PARENT_PERSIST_ATTEMPTS;
5645
+ if (terminal) {
5646
+ parentSessionPersisted = false;
5647
+ break;
5648
+ }
5649
+ }
5650
+ }
5651
+ if (parentSessionPersisted === false) {
5652
+ writeStderrLine(
5653
+ `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)`
5654
+ );
5655
+ }
5656
+ }
4162
5657
  if (modelServiceId) {
4163
5658
  await applyModelServiceId(
4164
5659
  entry,
@@ -4168,22 +5663,51 @@ function createAcpSessionBridge(opts) {
4168
5663
  ).catch(() => {
4169
5664
  });
4170
5665
  }
5666
+ if (approvalMode) {
5667
+ try {
5668
+ await applyApprovalMode(entry, approvalMode, false, clientId);
5669
+ } catch (err) {
5670
+ try {
5671
+ await closeSessionImpl(entry.sessionId, void 0, {
5672
+ reason: "approval_mode_initialization_failed"
5673
+ });
5674
+ sessionRemovedDuringInitialization = true;
5675
+ } catch {
5676
+ }
5677
+ throw err;
5678
+ }
5679
+ }
4171
5680
  if (!byId.has(entry.sessionId)) {
4172
5681
  throw new Error(
4173
- `Session ${entry.sessionId} died during model-switch initialization`
5682
+ `Session ${entry.sessionId} died during session initialization`
4174
5683
  );
4175
5684
  }
5685
+ if (effectiveScope === "single" && !defaultEntry) defaultEntry = entry;
4176
5686
  return {
4177
5687
  sessionId: entry.sessionId,
4178
5688
  workspaceCwd: entry.workspaceCwd,
4179
5689
  attached: false,
4180
5690
  clientId,
4181
- createdAt: entry.createdAt
5691
+ createdAt: entry.createdAt,
5692
+ ...entry.parentSessionId ? { parentSessionPersisted: parentSessionPersisted === true } : {}
4182
5693
  };
4183
5694
  } finally {
4184
5695
  ci.sessionSpawnsInFlight = Math.max(0, ci.sessionSpawnsInFlight - 1);
4185
5696
  if (!sessionRegistered) {
4186
5697
  await reapPendingEmptyChannel(ci);
5698
+ } else if (sessionRemovedDuringInitialization && hasNoChannelWork(ci)) {
5699
+ await reapPendingEmptyChannel(ci);
5700
+ if (!ci.isDying) {
5701
+ await startIdleTimer(
5702
+ ci,
5703
+ `approval-mode initialization failure "${initializedSessionId}"`
5704
+ );
5705
+ }
5706
+ } else if (sessionRegistered && hasNoChannelWork(ci) && !ci.isDying) {
5707
+ await startIdleTimer(
5708
+ ci,
5709
+ `session orphaned during initialization "${initializedSessionId}"`
5710
+ );
4187
5711
  }
4188
5712
  }
4189
5713
  }
@@ -4213,41 +5737,157 @@ function createAcpSessionBridge(opts) {
4213
5737
  key: "model.name",
4214
5738
  value: modelId
4215
5739
  },
4216
- ...originatorClientId ? { originatorClientId } : {}
4217
- });
5740
+ ...originatorClientId ? { originatorClientId } : {}
5741
+ });
5742
+ succeeded = true;
5743
+ } catch (err) {
5744
+ entry.events.publish({
5745
+ type: "model_switch_failed",
5746
+ data: {
5747
+ sessionId: entry.sessionId,
5748
+ requestedModelId: modelId,
5749
+ error: err instanceof Error ? err.message : String(err)
5750
+ },
5751
+ ...originatorClientId ? { originatorClientId } : {}
5752
+ });
5753
+ throw err;
5754
+ } finally {
5755
+ entry.modelRoundtripInFlight = false;
5756
+ if (succeeded) {
5757
+ void reconcileAfterRoundtrip(entry, "model");
5758
+ } else {
5759
+ writeStderrLine(
5760
+ `[reconcile] session=${entry.sessionId} target=model action=skipped reason=roundtrip_failed`
5761
+ );
5762
+ }
5763
+ }
5764
+ });
5765
+ entry.modelChangeQueue = work.then(
5766
+ () => void 0,
5767
+ () => void 0
5768
+ );
5769
+ return work;
5770
+ }
5771
+ __name(applyModelServiceId, "applyModelServiceId");
5772
+ async function applyApprovalMode(entry, mode, persist, originatorClientId) {
5773
+ if (persist && !persistApprovalMode) {
5774
+ throw new Error(
5775
+ "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`."
5776
+ );
5777
+ }
5778
+ const approvalWork = entry.approvalModeQueue.then(async () => {
5779
+ entry.approvalModeRoundtripInFlight = true;
5780
+ let succeeded = false;
5781
+ try {
5782
+ const response = await Promise.race([
5783
+ withTimeout(
5784
+ entry.connection.extMethod(
5785
+ SERVE_CONTROL_EXT_METHODS.sessionApprovalMode,
5786
+ { sessionId: entry.sessionId, mode }
5787
+ ),
5788
+ initTimeoutMs,
5789
+ SERVE_CONTROL_EXT_METHODS.sessionApprovalMode
5790
+ ),
5791
+ getTransportClosedReject(entry)
5792
+ ]);
5793
+ if (typeof response.current !== "string" || !KNOWN_APPROVAL_MODES.has(response.current)) {
5794
+ throw new Error(
5795
+ `Agent returned unknown approval mode: ${JSON.stringify(response.current)}`
5796
+ );
5797
+ }
5798
+ let persisted = false;
5799
+ if (persist) {
5800
+ try {
5801
+ await withTimeout(
5802
+ persistApprovalMode?.(boundWorkspace, mode) ?? Promise.resolve(),
5803
+ PERSIST_TIMEOUT_MS,
5804
+ "persistApprovalMode"
5805
+ );
5806
+ persisted = persistApprovalMode !== void 0;
5807
+ } catch (err) {
5808
+ writeStderrLine(
5809
+ `setSessionApprovalMode: persist failed: ${err instanceof Error ? err.message : String(err)}`
5810
+ );
5811
+ }
5812
+ }
5813
+ publishApprovalModeChanged(
5814
+ entry,
5815
+ {
5816
+ previous: response.previous,
5817
+ next: response.current,
5818
+ persisted
5819
+ },
5820
+ originatorClientId
5821
+ );
5822
+ if (persisted) {
5823
+ broadcastWorkspaceEvent(
5824
+ {
5825
+ type: "approval_mode_changed",
5826
+ data: {
5827
+ sessionId: entry.sessionId,
5828
+ previous: response.previous,
5829
+ next: response.current,
5830
+ persisted
5831
+ },
5832
+ ...originatorClientId ? { originatorClientId } : {}
5833
+ },
5834
+ entry.sessionId
5835
+ );
5836
+ for (const peer of byId.values()) {
5837
+ if (peer.sessionId === entry.sessionId) {
5838
+ continue;
5839
+ }
5840
+ peer.currentApprovalMode = response.current;
5841
+ }
5842
+ }
4218
5843
  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;
5844
+ return {
5845
+ sessionId: entry.sessionId,
5846
+ mode: response.current,
5847
+ previous: response.previous,
5848
+ persisted
5849
+ };
4230
5850
  } finally {
4231
- entry.modelRoundtripInFlight = false;
5851
+ entry.approvalModeRoundtripInFlight = false;
4232
5852
  if (succeeded) {
4233
- void reconcileAfterRoundtrip(entry, "model");
5853
+ void reconcileAfterRoundtrip(entry, "approvalMode");
4234
5854
  } else {
4235
5855
  writeStderrLine(
4236
- `[reconcile] session=${entry.sessionId} target=model action=skipped reason=roundtrip_failed`
5856
+ `[reconcile] session=${entry.sessionId} target=approvalMode action=skipped reason=roundtrip_failed`
4237
5857
  );
4238
5858
  }
4239
5859
  }
4240
5860
  });
4241
- entry.modelChangeQueue = work.then(
5861
+ entry.approvalModeQueue = approvalWork.then(
4242
5862
  () => void 0,
4243
5863
  () => void 0
4244
5864
  );
4245
- return work;
5865
+ try {
5866
+ return await approvalWork;
5867
+ } catch (err) {
5868
+ const data = err?.data;
5869
+ if (data && typeof data === "object" && "errorKind" in data && data.errorKind === "trust_gate") {
5870
+ const rawMessage = err?.message;
5871
+ const message = typeof rawMessage === "string" ? rawMessage : "Trust-gate rejection from ACP child";
5872
+ throw new TrustGateError(message);
5873
+ }
5874
+ throw err;
5875
+ }
4246
5876
  }
4247
- __name(applyModelServiceId, "applyModelServiceId");
5877
+ __name(applyApprovalMode, "applyApprovalMode");
5878
+ async function applyApprovalModeForAttach(entry, mode, clientId) {
5879
+ try {
5880
+ await applyApprovalMode(entry, mode, false, clientId);
5881
+ } catch (err) {
5882
+ await rollbackAttachRegistration(entry, clientId);
5883
+ throw err;
5884
+ }
5885
+ }
5886
+ __name(applyApprovalModeForAttach, "applyApprovalModeForAttach");
4248
5887
  const cancelPendingForSession = /* @__PURE__ */ __name((sessionId) => {
4249
5888
  permissionMediator.forgetSession(sessionId);
4250
5889
  byId.get(sessionId)?.pendingPermissionIds.clear();
5890
+ byId.get(sessionId)?.pendingInteractions.clear();
4251
5891
  }, "cancelPendingForSession");
4252
5892
  const getTransportClosedReject = /* @__PURE__ */ __name((entry) => {
4253
5893
  if (!entry.transportClosedReject) {
@@ -4529,10 +6169,16 @@ function createAcpSessionBridge(opts) {
4529
6169
  sessionId,
4530
6170
  workspaceCwd,
4531
6171
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6172
+ ...options.parentSessionId ? { parentSessionId: options.parentSessionId } : {},
4532
6173
  channel: ci.channel,
4533
6174
  connection: ci.connection,
4534
6175
  events,
4535
- artifacts: new SessionArtifactStore({ sessionId, workspaceCwd }),
6176
+ artifacts: new SessionArtifactStore({
6177
+ sessionId,
6178
+ workspaceCwd,
6179
+ persistence: createSessionArtifactPersistence(ci.connection, sessionId)
6180
+ }),
6181
+ recordingDegraded: false,
4536
6182
  promptQueue: Promise.resolve(),
4537
6183
  pendingPromptCount: 0,
4538
6184
  pendingPromptList: [],
@@ -4542,6 +6188,7 @@ function createAcpSessionBridge(opts) {
4542
6188
  modelPublishGeneration: 0,
4543
6189
  approvalModePublishGeneration: 0,
4544
6190
  pendingPermissionIds: /* @__PURE__ */ new Set(),
6191
+ pendingInteractions: /* @__PURE__ */ new Map(),
4545
6192
  clientIds: /* @__PURE__ */ new Map(),
4546
6193
  clientLastSeenAt: /* @__PURE__ */ new Map(),
4547
6194
  attachCount: 0,
@@ -4553,6 +6200,12 @@ function createAcpSessionBridge(opts) {
4553
6200
  byId.set(entry.sessionId, entry);
4554
6201
  touchActivity();
4555
6202
  telemetry.metrics?.sessionLifecycle("spawn");
6203
+ emitSessionLifecycle({
6204
+ type: "registered",
6205
+ sessionId: entry.sessionId,
6206
+ workspaceCwd: entry.workspaceCwd,
6207
+ reason: options.lifecycleReason ?? "spawn"
6208
+ });
4556
6209
  if (options.drainEarlyEvents !== false) {
4557
6210
  ci.client.drainEarlyEvents(entry.sessionId, entry);
4558
6211
  }
@@ -4567,6 +6220,42 @@ function createAcpSessionBridge(opts) {
4567
6220
  });
4568
6221
  }
4569
6222
  }, "publishArtifactChanges");
6223
+ const artifactReseedChanges = /* @__PURE__ */ __name((before, after) => {
6224
+ const beforeById = new Map(
6225
+ before.map((artifact) => [artifact.id, artifact])
6226
+ );
6227
+ const afterById = new Map(after.map((artifact) => [artifact.id, artifact]));
6228
+ const changes = [];
6229
+ for (const artifact of before) {
6230
+ if (!afterById.has(artifact.id)) {
6231
+ changes.push({
6232
+ action: "removed",
6233
+ artifactId: artifact.id,
6234
+ artifact,
6235
+ reason: "eviction"
6236
+ });
6237
+ }
6238
+ }
6239
+ for (const artifact of after) {
6240
+ const previous = beforeById.get(artifact.id);
6241
+ if (!previous) {
6242
+ changes.push({
6243
+ action: "created",
6244
+ artifactId: artifact.id,
6245
+ artifact
6246
+ });
6247
+ continue;
6248
+ }
6249
+ if (!publicArtifactsEqual(previous, artifact)) {
6250
+ changes.push({
6251
+ action: "updated",
6252
+ artifactId: artifact.id,
6253
+ artifact
6254
+ });
6255
+ }
6256
+ }
6257
+ return changes;
6258
+ }, "artifactReseedChanges");
4570
6259
  const makeClientArtifactInput = /* @__PURE__ */ __name((artifact, clientId) => {
4571
6260
  const input = {
4572
6261
  title: artifact.title,
@@ -4579,6 +6268,8 @@ function createAcpSessionBridge(opts) {
4579
6268
  mimeType: artifact.mimeType,
4580
6269
  sizeBytes: artifact.sizeBytes,
4581
6270
  metadata: artifact.metadata,
6271
+ retention: artifact.retention,
6272
+ clientRetained: artifact.clientRetained,
4582
6273
  source: "client"
4583
6274
  };
4584
6275
  if (clientId) {
@@ -4586,6 +6277,31 @@ function createAcpSessionBridge(opts) {
4586
6277
  }
4587
6278
  return input;
4588
6279
  }, "makeClientArtifactInput");
6280
+ function createSessionArtifactPersistence(connection, sessionId) {
6281
+ return {
6282
+ recordEvent: /* @__PURE__ */ __name(async (payload) => {
6283
+ await connection.extMethod(
6284
+ SERVE_CONTROL_EXT_METHODS.sessionArtifactsPersist,
6285
+ {
6286
+ sessionId,
6287
+ kind: "event",
6288
+ payload
6289
+ }
6290
+ );
6291
+ }, "recordEvent"),
6292
+ recordSnapshot: /* @__PURE__ */ __name(async (payload) => {
6293
+ await connection.extMethod(
6294
+ SERVE_CONTROL_EXT_METHODS.sessionArtifactsPersist,
6295
+ {
6296
+ sessionId,
6297
+ kind: "snapshot",
6298
+ payload
6299
+ }
6300
+ );
6301
+ }, "recordSnapshot")
6302
+ };
6303
+ }
6304
+ __name(createSessionArtifactPersistence, "createSessionArtifactPersistence");
4589
6305
  const seedSnapshotCaches = /* @__PURE__ */ __name((entry, resp) => {
4590
6306
  const model = resp.models?.currentModelId;
4591
6307
  if (typeof model === "string" && model.length > 0) {
@@ -4633,16 +6349,55 @@ function createAcpSessionBridge(opts) {
4633
6349
  }
4634
6350
  return { lastEventId: snapshot.lastEventId, ...replayStatus };
4635
6351
  }, "replayFieldsFor");
6352
+ const restoredArtifactSnapshotFromState = /* @__PURE__ */ __name((state) => {
6353
+ const candidate = state.artifactSnapshot;
6354
+ const warnings = [];
6355
+ const snapshot = normalizeSnapshotPayload(candidate, warnings);
6356
+ if (!snapshot) return void 0;
6357
+ const snapshotWarnings = isRecord2(candidate) && Array.isArray(candidate["warnings"]) ? candidate["warnings"].filter(
6358
+ (warning) => typeof warning === "string" && warning.length <= 1e3
6359
+ ).slice(-500) : [];
6360
+ return {
6361
+ v: SESSION_ARTIFACT_PERSISTENCE_VERSION,
6362
+ sessionId: snapshot.sessionId,
6363
+ sequence: snapshot.sequence,
6364
+ artifacts: snapshot.artifacts,
6365
+ ...snapshot.markerArtifacts ? { markerArtifacts: snapshot.markerArtifacts } : {},
6366
+ tombstonedIds: snapshot.tombstonedIds ?? [],
6367
+ stickyEphemeralIds: snapshot.stickyEphemeralIds ?? [],
6368
+ warnings: [...warnings, ...snapshotWarnings]
6369
+ };
6370
+ }, "restoredArtifactSnapshotFromState");
6371
+ const artifactSnapshotUnavailableReason = /* @__PURE__ */ __name((state) => {
6372
+ const reason = state.artifactSnapshotUnavailable;
6373
+ return typeof reason === "string" && reason ? reason : void 0;
6374
+ }, "artifactSnapshotUnavailableReason");
6375
+ const publicRestoreState = /* @__PURE__ */ __name((state) => {
6376
+ const {
6377
+ artifactSnapshot: _artifactSnapshot,
6378
+ artifactSnapshotUnavailable: _artifactSnapshotUnavailable,
6379
+ ...publicState
6380
+ } = state;
6381
+ return publicState;
6382
+ }, "publicRestoreState");
4636
6383
  async function restoreSession(action, req, options = {}) {
4637
6384
  if (shuttingDown) {
4638
6385
  throw new Error("AcpSessionBridge is shutting down");
4639
6386
  }
4640
6387
  const workspaceKey = resolveWorkspaceKey(req.workspaceCwd);
6388
+ if (req.approvalMode !== void 0 && !KNOWN_APPROVAL_MODES.has(req.approvalMode)) {
6389
+ throw new Error(
6390
+ `Invalid approvalMode: ${JSON.stringify(req.approvalMode)}`
6391
+ );
6392
+ }
4641
6393
  const historyReplay = action === "load" ? req.historyReplay ?? "stream" : "stream";
4642
6394
  const existing = byId.get(req.sessionId);
4643
6395
  if (existing) {
4644
6396
  existing.attachCount++;
4645
6397
  const clientId = registerClient(existing, req.clientId);
6398
+ if (req.approvalMode) {
6399
+ await applyApprovalModeForAttach(existing, req.approvalMode, clientId);
6400
+ }
4646
6401
  return {
4647
6402
  sessionId: existing.sessionId,
4648
6403
  workspaceCwd: existing.workspaceCwd,
@@ -4681,10 +6436,14 @@ function createAcpSessionBridge(opts) {
4681
6436
  "the agent child likely crashed during session restore \u2014 retry to restore the session"
4682
6437
  );
4683
6438
  }
6439
+ const clientId = registerClient(entry, req.clientId);
6440
+ if (req.approvalMode) {
6441
+ await applyApprovalModeForAttach(entry, req.approvalMode, clientId);
6442
+ }
4684
6443
  return {
4685
6444
  ...restored,
4686
6445
  attached: true,
4687
- clientId: registerClient(entry, req.clientId),
6446
+ clientId,
4688
6447
  createdAt: entry.createdAt,
4689
6448
  hasActivePrompt: entry.promptActive
4690
6449
  };
@@ -4794,6 +6553,23 @@ function createAcpSessionBridge(opts) {
4794
6553
  restoreEvents.close();
4795
6554
  racedEntry.attachCount += 1 + coalesceState.count;
4796
6555
  const clientId2 = registerClient(racedEntry, req.clientId);
6556
+ if (req.approvalMode) {
6557
+ try {
6558
+ await applyApprovalMode(
6559
+ racedEntry,
6560
+ req.approvalMode,
6561
+ false,
6562
+ clientId2
6563
+ );
6564
+ } catch (err) {
6565
+ await rollbackAttachRegistration(
6566
+ racedEntry,
6567
+ clientId2,
6568
+ 1 + coalesceState.count
6569
+ );
6570
+ throw err;
6571
+ }
6572
+ }
4797
6573
  return {
4798
6574
  sessionId: racedEntry.sessionId,
4799
6575
  workspaceCwd: racedEntry.workspaceCwd,
@@ -4810,22 +6586,48 @@ function createAcpSessionBridge(opts) {
4810
6586
  req.sessionId,
4811
6587
  workspaceKey,
4812
6588
  restoreEvents,
4813
- { drainEarlyEvents: replayUpdates.length === 0 }
6589
+ {
6590
+ drainEarlyEvents: replayUpdates.length === 0,
6591
+ lifecycleReason: action,
6592
+ // Re-seed the persisted parent lineage the caller recovered from the
6593
+ // transcript, so a restored sub-session's status reports its parent.
6594
+ ...req.parentSessionId ? { parentSessionId: req.parentSessionId } : {}
6595
+ }
4814
6596
  );
4815
6597
  releaseAdmissionOnce();
4816
- entry.restoreState = state;
6598
+ const restoredArtifactSnapshot = restoredArtifactSnapshotFromState(state);
6599
+ const publicState = publicRestoreState(state);
6600
+ entry.restoreState = publicState;
4817
6601
  if (replayPartial === true) {
4818
6602
  entry.restoreReplayPartial = true;
4819
6603
  }
4820
6604
  if (replayError !== void 0) {
4821
6605
  entry.restoreReplayError = replayError;
4822
6606
  }
4823
- seedSnapshotCaches(entry, state);
6607
+ seedSnapshotCaches(entry, publicState);
6608
+ const artifactRestoreWarnings = await entry.artifacts.restore(
6609
+ restoredArtifactSnapshot
6610
+ );
6611
+ for (const warning of artifactRestoreWarnings) {
6612
+ writeStderrLine(
6613
+ `[artifacts] session=${entry.sessionId} action=restore_warning warning=${JSON.stringify(
6614
+ warning
6615
+ )}`
6616
+ );
6617
+ }
6618
+ const artifactRestoreFailed = artifactRestoreWarnings.some(
6619
+ (warning) => isArtifactRestoreFailureWarning(warning)
6620
+ );
4824
6621
  if (replayUpdates.length > 0) {
4825
- await ci.client.seedSessionUpdates(entry, replayUpdates);
6622
+ await ci.client.seedSessionUpdates(entry, replayUpdates, {
6623
+ ingestArtifacts: restoredArtifactSnapshot === void 0 || artifactRestoreFailed
6624
+ });
4826
6625
  ci.client.drainEarlyEvents(entry.sessionId, entry);
4827
6626
  }
4828
6627
  const clientId = registerClient(entry, req.clientId);
6628
+ if (req.approvalMode) {
6629
+ await applyApprovalModeForAttach(entry, req.approvalMode, clientId);
6630
+ }
4829
6631
  entry.attachCount = coalesceState.count;
4830
6632
  registeredEntry = entry;
4831
6633
  return {
@@ -4834,7 +6636,8 @@ function createAcpSessionBridge(opts) {
4834
6636
  attached: false,
4835
6637
  clientId,
4836
6638
  createdAt: entry.createdAt,
4837
- state,
6639
+ state: publicState,
6640
+ ...artifactRestoreWarnings.length > 0 ? { artifactWarnings: artifactRestoreWarnings } : {},
4838
6641
  hasActivePrompt: entry.promptActive,
4839
6642
  ...replayFieldsFor(entry, action)
4840
6643
  };
@@ -4846,9 +6649,16 @@ function createAcpSessionBridge(opts) {
4846
6649
  if (!registeredEntry) {
4847
6650
  restoreEvents.close();
4848
6651
  let removedRestoreEntry = false;
4849
- if (byId.get(req.sessionId)?.events === restoreEvents) {
6652
+ const restoreEntry = byId.get(req.sessionId);
6653
+ if (restoreEntry?.events === restoreEvents) {
4850
6654
  byId.delete(req.sessionId);
4851
6655
  ci?.sessionIds.delete(req.sessionId);
6656
+ emitSessionLifecycle({
6657
+ type: "removed",
6658
+ sessionId: req.sessionId,
6659
+ workspaceCwd: restoreEntry.workspaceCwd,
6660
+ reason: "restore_failed"
6661
+ });
4852
6662
  removedRestoreEntry = true;
4853
6663
  }
4854
6664
  if (removedRestoreEntry && ci && hasNoChannelWork(ci)) {
@@ -4909,6 +6719,7 @@ function createAcpSessionBridge(opts) {
4909
6719
  }
4910
6720
  permissionMediator.forgetSession(sessionId);
4911
6721
  entry.pendingPermissionIds.clear();
6722
+ entry.pendingInteractions.clear();
4912
6723
  if (entry.promptActive) {
4913
6724
  entry.promptActive = false;
4914
6725
  activePromptCounter--;
@@ -4916,6 +6727,12 @@ function createAcpSessionBridge(opts) {
4916
6727
  }
4917
6728
  byId.delete(sessionId);
4918
6729
  telemetry.metrics?.sessionLifecycle("close");
6730
+ emitSessionLifecycle({
6731
+ type: "removed",
6732
+ sessionId,
6733
+ workspaceCwd: entry.workspaceCwd,
6734
+ reason
6735
+ });
4919
6736
  ci?.client.markSessionClosed(sessionId);
4920
6737
  try {
4921
6738
  entry.events.publish({
@@ -5039,6 +6856,11 @@ function createAcpSessionBridge(opts) {
5039
6856
  throw new InvalidSessionScopeError(req.sessionScope);
5040
6857
  }
5041
6858
  const effectiveScope = req.sessionScope ?? defaultSessionScope;
6859
+ if (req.approvalMode !== void 0 && !KNOWN_APPROVAL_MODES.has(req.approvalMode)) {
6860
+ throw new Error(
6861
+ `Invalid approvalMode: ${JSON.stringify(req.approvalMode)}`
6862
+ );
6863
+ }
5042
6864
  if (effectiveScope === "single") {
5043
6865
  const existing = defaultEntry;
5044
6866
  if (existing) {
@@ -5053,6 +6875,13 @@ function createAcpSessionBridge(opts) {
5053
6875
  ).catch(() => {
5054
6876
  });
5055
6877
  }
6878
+ if (req.approvalMode) {
6879
+ await applyApprovalModeForAttach(
6880
+ existing,
6881
+ req.approvalMode,
6882
+ clientId
6883
+ );
6884
+ }
5056
6885
  return {
5057
6886
  sessionId: existing.sessionId,
5058
6887
  workspaceCwd: existing.workspaceCwd,
@@ -5083,6 +6912,13 @@ function createAcpSessionBridge(opts) {
5083
6912
  ).catch(() => {
5084
6913
  });
5085
6914
  }
6915
+ if (req.approvalMode) {
6916
+ await applyApprovalModeForAttach(
6917
+ attachedEntry,
6918
+ req.approvalMode,
6919
+ clientId
6920
+ );
6921
+ }
5086
6922
  return {
5087
6923
  ...session,
5088
6924
  attached: true,
@@ -5107,8 +6943,10 @@ function createAcpSessionBridge(opts) {
5107
6943
  const promise = doSpawn(
5108
6944
  req.modelServiceId,
5109
6945
  effectiveScope,
6946
+ req.approvalMode,
5110
6947
  req.clientId,
5111
- releaseAdmissionOnce
6948
+ releaseAdmissionOnce,
6949
+ req.parentSessionId
5112
6950
  );
5113
6951
  const tracker = effectiveScope === "single" ? workspaceKey : `${workspaceKey}#${randomUUID2()}`;
5114
6952
  inFlightSpawns.set(tracker, promise);
@@ -5249,6 +7087,7 @@ function createAcpSessionBridge(opts) {
5249
7087
  return copy;
5250
7088
  })();
5251
7089
  entry.promptActive = true;
7090
+ delete entry.turnError;
5252
7091
  activePromptCounter++;
5253
7092
  entry.sessionLastSeenAt = Date.now();
5254
7093
  touchActivity();
@@ -5456,7 +7295,8 @@ function createAcpSessionBridge(opts) {
5456
7295
  data: {
5457
7296
  sessionId: entry.sessionId,
5458
7297
  currentModelId: entry.currentModelId ?? null,
5459
- currentApprovalMode: entry.currentApprovalMode ?? null
7298
+ currentApprovalMode: entry.currentApprovalMode ?? null,
7299
+ recordingDegraded: entry.recordingDegraded
5460
7300
  }
5461
7301
  }), "snapshotFrame");
5462
7302
  async function* withSnapshot() {
@@ -5750,7 +7590,7 @@ function createAcpSessionBridge(opts) {
5750
7590
  const r = res;
5751
7591
  if (r && r.persisted === false) {
5752
7592
  writeStderrLine(
5753
- `qwen serve: displayName for ${sessionId} was not persisted (recording service unavailable)`
7593
+ `qwen serve: displayName for ${sessionId} was not persisted`
5754
7594
  );
5755
7595
  }
5756
7596
  }).catch((err) => {
@@ -5771,9 +7611,10 @@ function createAcpSessionBridge(opts) {
5771
7611
  }
5772
7612
  return { displayName: entry.displayName };
5773
7613
  },
5774
- async getSessionArtifacts(sessionId) {
7614
+ async getSessionArtifacts(sessionId, context) {
5775
7615
  const entry = byId.get(sessionId);
5776
7616
  if (!entry) throw new SessionNotFoundError(sessionId);
7617
+ resolveTrustedClientId(entry, context?.clientId);
5777
7618
  return entry.artifacts.list();
5778
7619
  },
5779
7620
  async addSessionArtifact(sessionId, artifact, context) {
@@ -5781,9 +7622,13 @@ function createAcpSessionBridge(opts) {
5781
7622
  if (!entry) throw new SessionNotFoundError(sessionId);
5782
7623
  const clientId = resolveTrustedClientId(entry, context?.clientId);
5783
7624
  const input = makeClientArtifactInput(artifact, clientId);
5784
- const result = await entry.artifacts.upsertMany([input], { strict: true });
7625
+ const result = await entry.artifacts.upsertMany([input], {
7626
+ validationStrict: true,
7627
+ persistenceStrict: false
7628
+ });
5785
7629
  publishArtifactChanges(entry, result.changes, clientId);
5786
- return result;
7630
+ const warnings = [...result.warnings ?? []];
7631
+ return warnings.length > 0 ? { ...result, warnings } : result;
5787
7632
  },
5788
7633
  async removeSessionArtifact(sessionId, artifactId, context) {
5789
7634
  const entry = byId.get(sessionId);
@@ -5791,7 +7636,8 @@ function createAcpSessionBridge(opts) {
5791
7636
  const clientId = resolveTrustedClientId(entry, context?.clientId);
5792
7637
  const result = await entry.artifacts.remove(artifactId, { clientId });
5793
7638
  publishArtifactChanges(entry, result.changes, clientId);
5794
- return result;
7639
+ const warnings = [...result.warnings ?? []];
7640
+ return warnings.length > 0 ? { ...result, warnings } : result;
5795
7641
  },
5796
7642
  listWorkspaceSessions(workspaceCwd) {
5797
7643
  if (!path3.isAbsolute(workspaceCwd)) return [];
@@ -6076,6 +7922,35 @@ function createAcpSessionBridge(opts) {
6076
7922
  SERVE_STATUS_EXT_METHODS.sessionLspStatus
6077
7923
  );
6078
7924
  },
7925
+ async getSessionTranscriptPage(req) {
7926
+ const info = await ensureChannel();
7927
+ try {
7928
+ const response = await withWorkspaceControl(
7929
+ info,
7930
+ () => withTimeout(
7931
+ Promise.race([
7932
+ info.connection.extMethod(
7933
+ SERVE_STATUS_EXT_METHODS.sessionTranscript,
7934
+ { ...req, cwd: boundWorkspace }
7935
+ ),
7936
+ getChannelClosedReject(info)
7937
+ ]),
7938
+ Math.max(initTimeoutMs, SESSION_TRANSCRIPT_TIMEOUT_MS),
7939
+ SERVE_STATUS_EXT_METHODS.sessionTranscript
7940
+ )
7941
+ );
7942
+ return response;
7943
+ } catch (err) {
7944
+ if (isAcpSessionResourceNotFound(err, req.sessionId)) {
7945
+ throw new SessionNotFoundError(req.sessionId);
7946
+ }
7947
+ throw err;
7948
+ } finally {
7949
+ if (hasNoChannelWork(info)) {
7950
+ await startIdleTimer(info, "session transcript");
7951
+ }
7952
+ }
7953
+ },
6079
7954
  async cancelSessionTask(sessionId, taskId, taskKind) {
6080
7955
  return requestSessionStatus(
6081
7956
  sessionId,
@@ -6314,109 +8189,12 @@ function createAcpSessionBridge(opts) {
6314
8189
  entry,
6315
8190
  context?.clientId
6316
8191
  );
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
8192
+ return await applyApprovalMode(
8193
+ entry,
8194
+ mode,
8195
+ opts2.persist,
8196
+ originatorClientId
6408
8197
  );
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
8198
  },
6421
8199
  async generateSessionRecap(sessionId, _context) {
6422
8200
  const entry = byId.get(sessionId);
@@ -6769,6 +8547,48 @@ function createAcpSessionBridge(opts) {
6769
8547
  const targetTurnIndex = response["targetTurnIndex"] ?? 0;
6770
8548
  const filesChanged = response["filesChanged"] ?? [];
6771
8549
  const filesFailed = response["filesFailed"] ?? [];
8550
+ const artifactSnapshot = restoredArtifactSnapshotFromState(
8551
+ response
8552
+ );
8553
+ const artifactSnapshotUnavailable = artifactSnapshotUnavailableReason(
8554
+ response
8555
+ );
8556
+ const beforeArtifacts = (await entry.artifacts.list()).artifacts;
8557
+ const shouldRestoreArtifactSnapshot = artifactSnapshot !== void 0 && artifactSnapshotUnavailable === void 0;
8558
+ const artifactRestoreWarnings = artifactSnapshotUnavailable !== void 0 ? [
8559
+ `artifact snapshot rebuild unavailable during rewind: ${artifactSnapshotUnavailable}`
8560
+ ] : shouldRestoreArtifactSnapshot ? await entry.artifacts.restore(artifactSnapshot, {
8561
+ preserveLiveEphemeral: true
8562
+ }) : [];
8563
+ const artifactRestoreFailed = artifactRestoreWarnings.some(
8564
+ isArtifactRestoreFailureWarning
8565
+ );
8566
+ const shouldRecordArtifactSnapshot = shouldRestoreArtifactSnapshot && !artifactRestoreFailed;
8567
+ const artifactSnapshotWarnings = shouldRecordArtifactSnapshot ? await entry.artifacts.recordSnapshot() : [];
8568
+ const artifactWarnings = [
8569
+ ...artifactRestoreWarnings,
8570
+ ...artifactSnapshotWarnings
8571
+ ];
8572
+ for (const warning of artifactRestoreWarnings) {
8573
+ writeStderrLine(
8574
+ `[artifacts] session=${entry.sessionId} action=rewind_restore_warning warning=${JSON.stringify(
8575
+ warning
8576
+ )}`
8577
+ );
8578
+ }
8579
+ for (const warning of artifactSnapshotWarnings) {
8580
+ writeStderrLine(
8581
+ `[artifacts] session=${entry.sessionId} action=rewind_snapshot_warning warning=${JSON.stringify(
8582
+ warning
8583
+ )}`
8584
+ );
8585
+ }
8586
+ const afterArtifacts = (await entry.artifacts.list()).artifacts;
8587
+ publishArtifactChanges(
8588
+ entry,
8589
+ artifactReseedChanges(beforeArtifacts, afterArtifacts),
8590
+ originatorClientId
8591
+ );
6772
8592
  try {
6773
8593
  entry.events.publish({
6774
8594
  type: "session_rewound",
@@ -6777,7 +8597,8 @@ function createAcpSessionBridge(opts) {
6777
8597
  promptId: req.promptId,
6778
8598
  targetTurnIndex,
6779
8599
  filesChanged,
6780
- filesFailed
8600
+ filesFailed,
8601
+ ...artifactWarnings.length > 0 ? { warnings: artifactWarnings } : {}
6781
8602
  },
6782
8603
  ...originatorClientId ? { originatorClientId } : {}
6783
8604
  });
@@ -6787,7 +8608,8 @@ function createAcpSessionBridge(opts) {
6787
8608
  rewound: filesFailed.length === 0,
6788
8609
  targetTurnIndex,
6789
8610
  filesChanged,
6790
- filesFailed
8611
+ filesFailed,
8612
+ ...artifactWarnings.length > 0 ? { warnings: artifactWarnings } : {}
6791
8613
  };
6792
8614
  },
6793
8615
  async manageMcpServer(serverName, action, originatorClientId) {
@@ -6915,6 +8737,7 @@ function createAcpSessionBridge(opts) {
6915
8737
  }
6916
8738
  permissionMediator.forgetSession(sessionId);
6917
8739
  entry.pendingPermissionIds.clear();
8740
+ entry.pendingInteractions.clear();
6918
8741
  if (entry.promptActive) {
6919
8742
  entry.promptActive = false;
6920
8743
  activePromptCounter--;
@@ -6923,6 +8746,12 @@ function createAcpSessionBridge(opts) {
6923
8746
  if (defaultEntry === entry) defaultEntry = void 0;
6924
8747
  byId.delete(sessionId);
6925
8748
  telemetry.metrics?.sessionLifecycle("die");
8749
+ emitSessionLifecycle({
8750
+ type: "removed",
8751
+ sessionId,
8752
+ workspaceCwd: entry.workspaceCwd,
8753
+ reason: "killed"
8754
+ });
6926
8755
  const ci = channelInfoForEntry(entry);
6927
8756
  if (!ci) {
6928
8757
  writeStderrLine(
@@ -6972,8 +8801,17 @@ function createAcpSessionBridge(opts) {
6972
8801
  cancelIdleTimer();
6973
8802
  stopSessionReaper();
6974
8803
  const channels = Array.from(aliveChannels);
8804
+ const entries = Array.from(byId.values());
6975
8805
  defaultEntry = void 0;
6976
8806
  byId.clear();
8807
+ for (const entry of entries) {
8808
+ emitSessionLifecycle({
8809
+ type: "removed",
8810
+ sessionId: entry.sessionId,
8811
+ workspaceCwd: entry.workspaceCwd,
8812
+ reason: "kill_all"
8813
+ });
8814
+ }
6977
8815
  for (const info of channels) {
6978
8816
  try {
6979
8817
  info.channel.killSync();
@@ -6981,53 +8819,71 @@ function createAcpSessionBridge(opts) {
6981
8819
  }
6982
8820
  }
6983
8821
  },
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" }
8822
+ shutdown(options) {
8823
+ if (shutdownPromise) return shutdownPromise;
8824
+ const shutdownReason = options?.reason ?? "daemon_shutdown";
8825
+ let resolveShutdown;
8826
+ let rejectShutdown;
8827
+ shutdownPromise = new Promise((resolve2, reject) => {
8828
+ resolveShutdown = resolve2;
8829
+ rejectShutdown = reject;
8830
+ });
8831
+ void (async () => {
8832
+ shuttingDown = true;
8833
+ cancelIdleTimer();
8834
+ stopSessionReaper();
8835
+ const entries = Array.from(byId.values());
8836
+ const channels = Array.from(aliveChannels);
8837
+ for (const ci of channels) ci.isDying = true;
8838
+ for (const e of entries) {
8839
+ permissionMediator.forgetSession(e.sessionId);
8840
+ e.pendingPermissionIds.clear();
8841
+ e.pendingInteractions.clear();
8842
+ }
8843
+ defaultEntry = void 0;
8844
+ byId.clear();
8845
+ for (const e of entries) {
8846
+ telemetry.metrics?.sessionLifecycle("die");
8847
+ emitSessionLifecycle({
8848
+ type: "removed",
8849
+ sessionId: e.sessionId,
8850
+ workspaceCwd: e.workspaceCwd,
8851
+ reason: shutdownReason
7003
8852
  });
7004
- } catch {
8853
+ try {
8854
+ e.events.publish({
8855
+ type: "session_died",
8856
+ data: { sessionId: e.sessionId, reason: shutdownReason }
8857
+ });
8858
+ } catch {
8859
+ }
8860
+ e.events.close();
7005
8861
  }
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(
8862
+ const inFlightSessionAwaits = Array.from(inFlightSpawns.values()).map(
8863
+ (p) => p.then(
8864
+ () => void 0,
8865
+ () => void 0
8866
+ )
8867
+ );
8868
+ const inFlightRestoreAwaits = Array.from(inFlightRestores.values()).map(
8869
+ (restore) => restore.promise.then(
8870
+ () => void 0,
8871
+ () => void 0
8872
+ )
8873
+ );
8874
+ const inFlightChannelAwait = inFlightChannelSpawn ? inFlightChannelSpawn.then(
7016
8875
  () => void 0,
7017
8876
  () => 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
- ]);
8877
+ ) : Promise.resolve();
8878
+ await Promise.all([
8879
+ ...channels.map((ci) => ci.channel.kill().catch(() => {
8880
+ })),
8881
+ ...inFlightSessionAwaits,
8882
+ ...inFlightRestoreAwaits,
8883
+ inFlightChannelAwait
8884
+ ]);
8885
+ })().then(resolveShutdown, rejectShutdown);
8886
+ return shutdownPromise;
7031
8887
  },
7032
8888
  async preheat() {
7033
8889
  if (shuttingDown) return;
@@ -7057,6 +8913,7 @@ var createHttpAcpBridge = createAcpSessionBridge;
7057
8913
 
7058
8914
  export {
7059
8915
  SessionArtifactValidationError,
8916
+ SessionArtifactAuthorizationError,
7060
8917
  extractErrorMessage,
7061
8918
  extractErrorCode,
7062
8919
  classifyTurnErrorKind,