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