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