@qwen-code/qwen-code 0.22.2-preview.1 → 0.22.2
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/goal-draft/SKILL.md +109 -0
- package/bundled/qc-helper/docs/configuration/settings.md +10 -10
- package/bundled/qc-helper/docs/features/_meta.ts +1 -0
- package/bundled/qc-helper/docs/features/commands.md +2 -1
- package/bundled/qc-helper/docs/features/goals.md +58 -0
- package/bundled/qc-helper/docs/features/headless.md +13 -11
- package/bundled/qc-helper/docs/features/status-line.md +3 -3
- package/chunks/MaxSizedBox-X5FZSNAJ.js +112 -0
- package/chunks/{StandaloneSessionPicker-5D2L22SF.js → StandaloneSessionPicker-AVLI6VJP.js} +76 -76
- package/chunks/{acpAgent-4DCKLA32.js → acpAgent-MXEAN54C.js} +969 -255
- package/chunks/{agent-B4AFZL6H.js → agent-PJIPDWHB.js} +41 -41
- package/chunks/agent-headless-B4JVD7UK.js +87 -0
- package/chunks/{anthropicContentGenerator-U76GTVB6.js → anthropicContentGenerator-HGMBACVE.js} +7 -7
- package/chunks/{artifact-tool-XDJCZZCF.js → artifact-tool-5YT4QF7Y.js} +2 -2
- package/chunks/{askUserQuestion-ZXXERLEV.js → askUserQuestion-4SWJY65V.js} +2 -2
- package/chunks/bridge-FKZTVIS3.js +121 -0
- package/chunks/{channel-management-service-HXXS3HOZ.js → channel-management-service-X4AZ6IXX.js} +1 -1
- package/chunks/channel-settings-store-4UFCLBBG.js +117 -0
- package/chunks/{channel-worker-group-EVDC2P4K.js → channel-worker-group-27HMGD5S.js} +7 -6
- package/chunks/{channel-worker-manager-IJJZHGEI.js → channel-worker-manager-QEGX646Z.js} +9 -7
- package/chunks/{channel-worker-supervisor-YSKB5GVB.js → channel-worker-supervisor-UEW2WWCC.js} +5 -4
- package/chunks/{chunk-7QGEJI7M.js → chunk-23RFD54N.js} +1 -1
- package/chunks/{chunk-QR4AHMGQ.js → chunk-2K6ORIHJ.js} +13 -13
- package/chunks/{chunk-3D6PDLNJ.js → chunk-2NYCN6F2.js} +11 -11
- package/chunks/chunk-2TNL5KPM.js +49 -0
- package/chunks/{chunk-ZEADBZLO.js → chunk-3AFMQUTI.js} +3 -3
- package/chunks/{chunk-JSY43GMI.js → chunk-3DXFGHAJ.js} +185 -174
- package/chunks/{chunk-T45CL2NT.js → chunk-3FDFA4MZ.js} +3 -3
- package/chunks/{chunk-WBT3STGU.js → chunk-3I6UTTDX.js} +1 -1
- package/chunks/{chunk-FVDQ5E67.js → chunk-3JGZSIDA.js} +3 -3
- package/chunks/{chunk-J3CBIEDS.js → chunk-3SGB2UHX.js} +15 -10
- package/chunks/{chunk-77TOMRBG.js → chunk-43GGFFLY.js} +3 -3
- package/chunks/{chunk-ZJG323IA.js → chunk-43UYNAWX.js} +6 -4
- package/chunks/{chunk-NEZZHGEI.js → chunk-4CDSK5AZ.js} +6 -6
- package/chunks/chunk-4CMJKUND.js +95 -0
- package/chunks/{chunk-EB4VDXNI.js → chunk-4FTKQNWJ.js} +1 -1
- package/chunks/{chunk-PAJGKZAA.js → chunk-4HV6HH33.js} +10 -8
- package/chunks/{chunk-BQBUZDDH.js → chunk-4J657AJR.js} +3 -3
- package/chunks/{chunk-XMSMTU6H.js → chunk-4JWNNLPT.js} +4 -4
- package/chunks/{chunk-ALTQLFEG.js → chunk-4MQEPP3Y.js} +9 -84
- package/chunks/{chunk-3JKUX4PB.js → chunk-5O72BNDD.js} +8 -8
- package/chunks/{chunk-IKN3IUJP.js → chunk-5XBFCMWD.js} +2 -2
- package/chunks/{chunk-2YDYDDE2.js → chunk-66KDY3LV.js} +3 -3
- package/chunks/{chunk-VOEMVOM4.js → chunk-6CGL7E7G.js} +2 -2
- package/chunks/{chunk-SLEWBQWM.js → chunk-6DIGWMGT.js} +2 -2
- package/chunks/{chunk-SKNVVYIJ.js → chunk-6IUNAPLR.js} +3 -3
- package/chunks/{chunk-BHTJAO36.js → chunk-6LFJEACX.js} +4 -4
- package/chunks/{chunk-AP7WZONS.js → chunk-6LFTG244.js} +8 -8
- package/chunks/{chunk-PVBT5QPD.js → chunk-6PJOTWAN.js} +1 -1
- package/chunks/{chunk-5CMWFBML.js → chunk-77RG5SFQ.js} +1 -1
- package/chunks/{chunk-SDVF6A42.js → chunk-7DJCPZE3.js} +3 -3
- package/chunks/{chunk-ELY4QVIB.js → chunk-7EQQR46F.js} +1 -1
- package/chunks/{chunk-H3HZ4AWW.js → chunk-7UYV53BC.js} +1 -1
- package/chunks/{chunk-FCSWZAS3.js → chunk-A7YP63XN.js} +4 -4
- package/chunks/{chunk-E4I5MVBR.js → chunk-ABS25QER.js} +1 -1
- package/chunks/{chunk-WZ3QKPMQ.js → chunk-AGR5UROZ.js} +5 -5
- package/chunks/{chunk-5T4LWWNX.js → chunk-B7CDU2SL.js} +2 -2
- package/chunks/{chunk-HPECDZ6X.js → chunk-BAJKAAOY.js} +68 -64
- package/chunks/{chunk-WXDZZFH3.js → chunk-BBWV7ONL.js} +3 -3
- package/chunks/{chunk-4TPAG3O7.js → chunk-BFPESSTR.js} +1 -1
- package/chunks/{chunk-DID6IRGC.js → chunk-BNMF3K5X.js} +20 -19
- package/chunks/{chunk-IPQA4SR5.js → chunk-BWORX6FA.js} +1 -1
- package/chunks/{chunk-BKDAF7ES.js → chunk-BXDAPVQU.js} +2 -2
- package/chunks/{chunk-IOGKXPL3.js → chunk-C6K43ZDE.js} +4 -4
- package/chunks/{chunk-7OBH4AM2.js → chunk-CAJTKR6W.js} +5 -5
- package/chunks/{chunk-SUE2ZMUH.js → chunk-CCBZDVUA.js} +30 -25
- package/chunks/{chunk-U45D46O5.js → chunk-CMHFCLBU.js} +1 -1
- package/chunks/{chunk-AWFZDYSD.js → chunk-CPGJJJLU.js} +1 -1
- package/chunks/{chunk-LRKMQPMD.js → chunk-CQH5KTKC.js} +1 -1
- package/chunks/{chunk-3WBMPR3S.js → chunk-CSMI4FP5.js} +1 -1
- package/chunks/{chunk-JECO75XD.js → chunk-D5LUXLUH.js} +2 -2
- package/chunks/{chunk-UWICO5KN.js → chunk-DZWPESIF.js} +3 -3
- package/chunks/{chunk-I6DY753S.js → chunk-EFUM7RVY.js} +2 -2
- package/chunks/{chunk-3UZCLCXR.js → chunk-EKSCLBBF.js} +3 -3
- package/chunks/{chunk-LJP2BBMK.js → chunk-EOGELB3H.js} +4 -4
- package/chunks/{chunk-624NHDGB.js → chunk-ERFDKH32.js} +11 -0
- package/chunks/{chunk-GTUZ3R6I.js → chunk-F5SZP77N.js} +310 -78
- package/chunks/{chunk-SQPOJ4ZI.js → chunk-F6FV3C5K.js} +12 -7
- package/chunks/{chunk-TDQBVEPK.js → chunk-F7TNAPZ4.js} +9 -7
- package/chunks/{chunk-F7M6ZFGU.js → chunk-FBSSIAAQ.js} +6 -6
- package/chunks/{chunk-2FN2HNTI.js → chunk-FDY5RMGH.js} +2 -2
- package/chunks/{chunk-PTQF7WRA.js → chunk-FY76G3G5.js} +5 -5
- package/chunks/{chunk-X3XGCJ32.js → chunk-G34IR3D6.js} +9 -9
- package/chunks/{chunk-ZWKGZB2K.js → chunk-H2WDGQ6D.js} +3 -3
- package/chunks/chunk-H72BQNM3.js +645 -0
- package/chunks/{chunk-ILPXSKGQ.js → chunk-HVEYF6VT.js} +2 -2
- package/chunks/{chunk-5JCZ3UKB.js → chunk-HVQ3B7FF.js} +2 -2
- package/chunks/{chunk-VBF4KZRQ.js → chunk-IJKTMLBE.js} +1 -1
- package/chunks/{chunk-R7G7GKOX.js → chunk-IK5BW3CS.js} +2 -2
- package/chunks/{chunk-UCQOSV6A.js → chunk-IOPCFOTF.js} +3 -3
- package/chunks/{chunk-O3BUXIPM.js → chunk-IZIVM7LZ.js} +3 -3
- package/chunks/{chunk-I6VVOZDK.js → chunk-J2OSJFP3.js} +2 -2
- package/chunks/{chunk-AYVIGUUK.js → chunk-JB4JIVTJ.js} +1 -1
- package/chunks/{chunk-76WGY6LW.js → chunk-JCQD3INC.js} +3 -3
- package/chunks/{chunk-XUX6O2ZT.js → chunk-JHS74YAB.js} +4 -1
- package/chunks/{chunk-KPF2K55D.js → chunk-JSC3H3TD.js} +27 -4
- package/chunks/{chunk-CZFQ62RM.js → chunk-JSHHYW6T.js} +4 -4
- package/chunks/{chunk-MCKJTBZN.js → chunk-JU4EEXC7.js} +2 -2
- package/chunks/{chunk-T6OODF2U.js → chunk-JWALNCLT.js} +1 -1
- package/chunks/{chunk-CDUN63IN.js → chunk-K2NQPH5D.js} +3 -3
- package/chunks/{chunk-7UJTY5V3.js → chunk-KEKZFOL6.js} +1 -1
- package/chunks/{chunk-5URK47RN.js → chunk-KUX5D6DK.js} +1197 -336
- package/chunks/{chunk-OF2DPXUS.js → chunk-L2M46FP5.js} +116 -21
- package/chunks/{chunk-6US7BF3G.js → chunk-L6BZRIUL.js} +2 -2
- package/chunks/{chunk-TR7G7XFQ.js → chunk-L6YT5H34.js} +4 -4
- package/chunks/{chunk-W2WRNJKX.js → chunk-LDP5OD6N.js} +5 -5
- package/chunks/{chunk-OLS3MAXQ.js → chunk-LXIBMVRN.js} +7 -7
- package/chunks/{chunk-GNMJNDZE.js → chunk-M6FOJXW3.js} +1 -1
- package/chunks/{chunk-J5352S3S.js → chunk-MIIVI25Q.js} +3 -3
- package/chunks/{chunk-K3LPAED5.js → chunk-MQM5PJML.js} +24 -4
- package/chunks/{chunk-27ONXAFY.js → chunk-NIFYWDYN.js} +1 -1
- package/chunks/{chunk-IJVDHPKD.js → chunk-NU7RKEZR.js} +2 -2
- package/chunks/{chunk-LWLEPXKK.js → chunk-O2UN57NX.js} +2 -2
- package/chunks/{chunk-5SM2AK65.js → chunk-OIP7C4FE.js} +5 -5
- package/chunks/{chunk-UCSV2IZW.js → chunk-OIVXBW3W.js} +3 -3
- package/chunks/{chunk-TOF4UZHH.js → chunk-P2SU6ZTI.js} +3 -3
- package/chunks/{chunk-JLU3FPQR.js → chunk-P5GGM4KS.js} +5 -5
- package/chunks/{chunk-ZKAZP2ZG.js → chunk-PDMJ3KGS.js} +2 -2
- package/chunks/{chunk-RRKNX553.js → chunk-PDQGMSZK.js} +1 -1
- package/chunks/{chunk-KQYW2GB5.js → chunk-PFCQ62V4.js} +5 -5
- package/chunks/{chunk-JLGFGPZJ.js → chunk-PKAYJDB3.js} +2 -2
- package/chunks/{chunk-GTUKW3I6.js → chunk-PPKZ7JOE.js} +1 -1
- package/chunks/{chunk-HABT2Z7R.js → chunk-Q26AYA62.js} +4 -4
- package/chunks/{chunk-WEGXPP5E.js → chunk-QFCDF5PL.js} +1 -1
- package/chunks/{chunk-R3T33BG5.js → chunk-QP4C6WCE.js} +3 -3
- package/chunks/{chunk-Z6L3C4VF.js → chunk-R4PMVSR5.js} +27 -25
- package/chunks/{chunk-AKQA3WCH.js → chunk-RIEFGNTP.js} +579 -3419
- package/chunks/{chunk-ATLGYSYH.js → chunk-RJC5EIKY.js} +4 -4
- package/chunks/chunk-RKFGE7JH.js +93 -0
- package/chunks/{chunk-CPPYZHBN.js → chunk-RU7BK6B3.js} +1 -1
- package/chunks/{chunk-VYWCFWI5.js → chunk-RUGNCYNO.js} +11 -633
- package/chunks/{chunk-ZLTEQYNC.js → chunk-SFPGAQUL.js} +2 -2
- package/chunks/{chunk-TMGAS5TO.js → chunk-SJ3ZUWFK.js} +26 -4
- package/chunks/{chunk-DEYPMJPD.js → chunk-SMPR7SPO.js} +1 -1
- package/chunks/{chunk-7CWEZQ6T.js → chunk-STUJ7IMZ.js} +0 -77
- package/chunks/{chunk-BPMVPWBT.js → chunk-T32W2JHI.js} +6 -6
- package/chunks/chunk-T3WXYEZ4.js +304 -0
- package/chunks/{chunk-XIRDDFRS.js → chunk-TGWAQ5RB.js} +695 -294
- package/chunks/{chunk-EC5BONC3.js → chunk-TPI7O7HR.js} +1 -1
- package/chunks/{chunk-OI2DR6QB.js → chunk-TRZTNIQ6.js} +2 -2
- package/chunks/{chunk-T2CRBIDJ.js → chunk-TTX2JUE6.js} +1 -1
- package/chunks/{chunk-2NNTE2ED.js → chunk-UHQFIS7N.js} +15 -0
- package/chunks/{chunk-KOJ6MHD5.js → chunk-UTLCH2FK.js} +1 -1
- package/chunks/{chunk-7DPKPBOQ.js → chunk-V2NDP3I4.js} +2 -2
- package/chunks/{chunk-ZGWDHSUM.js → chunk-V4QXXQJ2.js} +3 -3
- package/chunks/{chunk-XR7OIAQF.js → chunk-V5J4J5TP.js} +2 -2
- package/chunks/{chunk-X2UOOG5V.js → chunk-VQUGEVGR.js} +1 -1
- package/chunks/{chunk-XJS54K4A.js → chunk-VTHREBQM.js} +2 -2
- package/chunks/{chunk-V3GINY6K.js → chunk-VVW4ZNFY.js} +3 -3
- package/chunks/{chunk-66GIFJ63.js → chunk-W5MJJ2V7.js} +1 -1
- package/chunks/{chunk-FPH6TXD3.js → chunk-WAOIHTJS.js} +1 -1
- package/chunks/{chunk-FN5C6C7G.js → chunk-WKK5BQNP.js} +151 -60
- package/chunks/chunk-XDFHY4R3.js +1584 -0
- package/chunks/{chunk-L436XFLJ.js → chunk-XDLEZ5HX.js} +1 -1
- package/chunks/{chunk-VL4YX3FH.js → chunk-XF63PKEN.js} +1 -1
- package/chunks/{chunk-3FSBMAYD.js → chunk-XQFT3QUF.js} +1 -1
- package/chunks/{chunk-2MHGD7EE.js → chunk-XRSOI3DL.js} +1 -1
- package/chunks/{chunk-FJWICLG7.js → chunk-Y3QL45LS.js} +1 -1
- package/chunks/{chunk-TFV4RXPU.js → chunk-Y6LSMB6J.js} +1 -1
- package/chunks/{chunk-2SERYG4T.js → chunk-YTDVNSUQ.js} +1 -1
- package/chunks/{chunk-AA7VBRBG.js → chunk-Z7FLBQPR.js} +7 -7
- package/chunks/{chunk-4K44OMLI.js → chunk-Z7TGYN5F.js} +3 -3
- package/chunks/{chunk-FJAITR4L.js → chunk-ZFKOCECU.js} +2 -2
- package/chunks/chunk-ZGXYO7DY.js +3416 -0
- package/chunks/{chunk-OT4YKUUV.js → chunk-ZU4UDIWX.js} +2 -2
- package/chunks/config-utils-BBOCOGZW.js +113 -0
- package/chunks/contextCommand-EET67MQ2.js +108 -0
- package/chunks/{core-runtime-CEJRHCRF.js → core-runtime-677KFX6Q.js} +53 -53
- package/chunks/{create-sub-session-RVHSWVAP.js → create-sub-session-23ITIELI.js} +3 -3
- package/chunks/{create-sub-session-DFZVIG56.js → create-sub-session-JKZAYZX7.js} +160 -79
- package/chunks/{cron-create-IJGVR5R2.js → cron-create-KJ4IY6PP.js} +57 -10
- package/chunks/{cron-delete-4N5BI6GL.js → cron-delete-MBLQCAYY.js} +4 -4
- package/chunks/{cron-list-2CATIPUX.js → cron-list-DWH2PQ47.js} +4 -4
- package/chunks/{daemon-NAYK4XXU.js → daemon-MJQUO7RA.js} +28 -1
- package/chunks/{daemon-git-worktree-guard-L6ZUFKF7.js → daemon-git-worktree-guard-6MVAAZZZ.js} +53 -53
- package/chunks/daemon-status-provider-RFKZOK3S.js +119 -0
- package/chunks/daemon-trust-policy-DRZPFB4Q.js +114 -0
- package/chunks/{daemon-trust-policy-monitor-2G3EKQEU.js → daemon-trust-policy-monitor-B5U6TQX5.js} +59 -59
- package/chunks/deferred-core-runtime-BBVIOP24.js +116 -0
- package/chunks/{display-image-6KHQNEAE.js → display-image-BE7I6ZB6.js} +5 -5
- package/chunks/{dist-FVJZSIO4.js → dist-ILDBUWDB.js} +71 -19
- package/chunks/{dist-6H6RZ5H6.js → dist-QODVOVWI.js} +58 -35
- package/chunks/earlyInputCapture-2JOMMNQ4.js +109 -0
- package/chunks/{edit-ZERGX3RV.js → edit-BSV6YRVW.js} +42 -42
- package/chunks/{en-6MDBWMTG.js → en-YLXBXM4Z.js} +2 -1
- package/chunks/{enter-worktree-UOZ6ZTP7.js → enter-worktree-MQXFFPKM.js} +7 -7
- package/chunks/{enterPlanMode-W2RP3P5B.js → enterPlanMode-PKNF35FE.js} +41 -41
- package/chunks/{environment-ZBJ23YVR.js → environment-MOZMPUHR.js} +55 -55
- package/chunks/errors-DMPQVYJL.js +115 -0
- package/chunks/{exit-worktree-G2QATUCV.js → exit-worktree-L344UY7K.js} +7 -7
- package/chunks/{exitPlanMode-VFJ5R5DD.js → exitPlanMode-UE4FGCY4.js} +41 -41
- package/chunks/{fast-path-4MWBCEUF.js → fast-path-773PV7PU.js} +5 -5
- package/chunks/{gemini-X5GQHQYF.js → gemini-IP4FJAP5.js} +101 -100
- package/chunks/{geminiContentGenerator-UDSWXZXL.js → geminiContentGenerator-PUNEBWJW.js} +8 -8
- package/chunks/{glob-DKTNG32L.js → glob-CZHOWUEA.js} +41 -41
- package/chunks/{goal-tools-CNVXMSZD.js → goal-tools-YZQJXTGH.js} +9 -8
- package/chunks/{grep-3NLSS32D.js → grep-5GPICQBU.js} +6 -6
- package/chunks/handleAutoUpdate-SGXKTQM6.js +111 -0
- package/chunks/{i18n-KX7FEKJJ.js → i18n-B42HKHKH.js} +54 -54
- package/chunks/{image-gen-63NN2H5N.js → image-gen-HAYOEQ4O.js} +9 -9
- package/chunks/initializer-FB66WYR3.js +115 -0
- package/chunks/installationInfo-Y7AULNMX.js +109 -0
- package/chunks/{keychain-token-storage-3MGFRYGM.js → keychain-token-storage-37XPR34V.js} +2 -2
- package/chunks/list-2HWGSVRA.js +118 -0
- package/chunks/{list-agents-EXRSHIDJ.js → list-agents-DI3YFTDT.js} +2 -2
- package/chunks/loadedSettingsAdapter-7Y7IEI6T.js +112 -0
- package/chunks/{loggingContentGenerator-73C7TKDK.js → loggingContentGenerator-2GGZNGLG.js} +35 -22
- package/chunks/{loop-wakeup-XBPB2BRO.js → loop-wakeup-XJFONSQ7.js} +5 -5
- package/chunks/{ls-WFONAND2.js → ls-3VAT2J7A.js} +4 -4
- package/chunks/{lsp-DUKPDUXN.js → lsp-5W7IRUBH.js} +2 -2
- package/chunks/{managed-npm-update-H2KOLQZU.js → managed-npm-update-5TEV2E3J.js} +54 -54
- package/chunks/mcp-QGG5NMJJ.js +112 -0
- package/chunks/{monitor-TGCMNJJH.js → monitor-FI7PY36Z.js} +41 -41
- package/chunks/nonInteractiveCli-4LO2TL72.js +186 -0
- package/chunks/{notebook-edit-YEUNZYDX.js → notebook-edit-TPGMGBST.js} +42 -42
- package/chunks/{openaiContentGenerator-J7LD4VSL.js → openaiContentGenerator-SUCYANUZ.js} +24 -24
- package/chunks/pidfile-JMF2CLMY.js +113 -0
- package/chunks/{processUtils-ZHFC7CSL.js → processUtils-IQZBYWSB.js} +2 -2
- package/chunks/prompt-terminal-ledger-DCZO6S7C.js +107 -0
- package/chunks/{qwenContentGenerator-YEWWK3JQ.js → qwenContentGenerator-J4TXFGJT.js} +46 -46
- package/chunks/{qwenOAuth2-JIYAWXTW.js → qwenOAuth2-RF33DNZW.js} +8 -8
- package/chunks/{read-file-ZQI3WPVK.js → read-file-QE2B5FTX.js} +12 -12
- package/chunks/{read-mcp-resource-7A7NFSFL.js → read-mcp-resource-XD257PGJ.js} +2 -2
- package/chunks/{record-artifact-CPJ4IJSD.js → record-artifact-RZGZBENU.js} +4 -4
- package/chunks/{report-findings-VFM5DENC.js → report-findings-Q3SYKKW5.js} +5 -5
- package/chunks/{request-shutdown-6IW46GD7.js → request-shutdown-BCLA3JV2.js} +7 -7
- package/chunks/resumeHistoryUtils-CZ4JRDVW.js +120 -0
- package/chunks/{ripGrep-46N4FLOS.js → ripGrep-R6B6HWPX.js} +17 -17
- package/chunks/{run-qwen-serve-WHWU7NZ6.js → run-qwen-serve-TPHZMSXJ.js} +148 -319
- package/chunks/{runtime-E7BSPWQP.js → runtime-TTIGSBM4.js} +62 -62
- package/chunks/scheduled-tasks-QB4ZVUQS.js +129 -0
- package/chunks/{scheduler-TJPATFOM.js → scheduler-YRUG22M7.js} +55 -55
- package/chunks/{sdk-exporters-http-XXSKBD2C.js → sdk-exporters-http-KCJ4ZFG7.js} +2 -2
- package/chunks/{sdk-impl-VPNBLV7I.js → sdk-impl-HLSEDAEP.js} +4 -4
- package/chunks/{send-message-OXZK6USO.js → send-message-Y5I6NYAM.js} +7 -7
- package/chunks/serve-MXXCDAQN.js +120 -0
- package/chunks/{server-AXCN7QBJ.js → server-DFUSSSHI.js} +19760 -19444
- package/chunks/{session-HO4US3AG.js → session-3TEHNENZ.js} +100 -99
- package/chunks/{settings-B2PFJJN4.js → settings-VOZYKLTT.js} +58 -58
- package/chunks/{shell-7XMSANI3.js → shell-6RTKZVMI.js} +41 -41
- package/chunks/{skill-NL3VH562.js → skill-FEOKFX26.js} +18 -18
- package/chunks/{skill-settings-SKMN5YXC.js → skill-settings-VA4XVYVL.js} +58 -58
- package/chunks/spawnChannel-F6WVP723.js +114 -0
- package/chunks/{standalone-update-XOCWGXAH.js → standalone-update-RGILUQVK.js} +55 -55
- package/chunks/{startInteractiveUI-G2XKSCQ7.js → startInteractiveUI-WHJPFOGH.js} +2757 -2223
- package/chunks/{syntheticOutput-5AJ444QT.js → syntheticOutput-ML43GEFN.js} +3 -3
- package/chunks/{task-create-TY7P6UHG.js → task-create-TMSV7RD5.js} +11 -11
- package/chunks/{task-list-BELMPA75.js → task-list-W55RBFRX.js} +5 -5
- package/chunks/{task-stop-U3S3KZG2.js → task-stop-CXEH5L2D.js} +2 -2
- package/chunks/{task-update-O4IGDPCK.js → task-update-N536I4GQ.js} +11 -11
- package/chunks/{team-create-IYBUOORE.js → team-create-QYSWCYCL.js} +41 -41
- package/chunks/{team-delete-YWFXX77L.js → team-delete-FJRFX33M.js} +5 -5
- package/chunks/{team-plan-approval-YKU57LBM.js → team-plan-approval-P7DAD644.js} +41 -41
- package/chunks/{terminal-image-renderer-NREKYTAD.js → terminal-image-renderer-SMQE46UJ.js} +55 -55
- package/chunks/theme-manager-RFO5WWFF.js +104 -0
- package/chunks/{todoWrite-435EO2CS.js → todoWrite-WDJNQEMW.js} +43 -25
- package/chunks/{tool-search-4Q3KASIQ.js → tool-search-F2EMIAPV.js} +17 -17
- package/chunks/total-session-admission-HTVTP4BS.js +116 -0
- package/chunks/{trustedFolders-OILZCHRD.js → trustedFolders-7OCNMUQE.js} +54 -54
- package/chunks/{update-relaunch-4DC2KKFS.js → update-relaunch-4GF3OXMB.js} +5 -5
- package/chunks/{updateCheck-AK4COHNK.js → updateCheck-7QBDXZRT.js} +56 -56
- package/chunks/useAutoAcceptIndicator-UI4SNL2O.js +122 -0
- package/chunks/{validateNonInterActiveAuth-YAE4YPO3.js → validateNonInterActiveAuth-BPPA563H.js} +96 -95
- package/chunks/{version-QFP7OGWX.js → version-ANP4TS6O.js} +1 -1
- package/chunks/{web-fetch-6T23IK6Q.js → web-fetch-QJADSAH3.js} +16 -16
- package/chunks/{web-search-WBWTCZK2.js → web-search-7TTQKEKM.js} +9 -9
- package/chunks/{workflow-FMAA3D4R.js → workflow-5XN6XSOT.js} +105 -60
- package/chunks/workspace-providers-status-PDFZWL5Q.js +116 -0
- package/chunks/{workspace-registration-store-CMMX7EPX.js → workspace-registration-store-XKVW2YVH.js} +1 -1
- package/chunks/{workspace-registry-WJLZPRTG.js → workspace-registry-23R7XRB5.js} +61 -59
- package/chunks/workspace-service-MUYO2QIC.js +131 -0
- package/chunks/workspace-skills-status-IKECTOOQ.js +115 -0
- package/chunks/{workspace-trust-reconciler-PJDQUTJL.js → workspace-trust-reconciler-KLGOB53D.js} +67 -65
- package/chunks/write-file-GGRBB5TZ.js +90 -0
- package/chunks/{zh-TW-MV4FWFB6.js → zh-TW-RPTMRTHG.js} +2 -1
- package/chunks/{zh-X3WGU6SI.js → zh-UNY63FR3.js} +2 -1
- package/chunks/{zoom-image-WVYVFRXF.js → zoom-image-6ASG6J4C.js} +12 -12
- package/cli.js +14 -14
- package/locales/en.js +2 -0
- package/locales/zh-TW.js +2 -0
- package/locales/zh.js +2 -0
- package/package.json +3 -3
- package/web-shell/assets/{abnfDiagram-VCTEODGH-d-8xa8uz.js → abnfDiagram-VCTEODGH-uS-vtfs3.js} +1 -1
- package/web-shell/assets/{arc-BhB7CrGK.js → arc-BSTnk5pu.js} +1 -1
- package/web-shell/assets/{architectureDiagram-5GKGNRK7-BDCd_IcJ.js → architectureDiagram-5GKGNRK7-DCMyiqzy.js} +1 -1
- package/web-shell/assets/{blockDiagram-NRAW4CY4-C-AHu6mi.js → blockDiagram-NRAW4CY4-C7_gt0r1.js} +1 -1
- package/web-shell/assets/{c4Diagram-UCG6FXSJ-CFjQt0vl.js → c4Diagram-UCG6FXSJ-BeQg2L8B.js} +1 -1
- package/web-shell/assets/channel-CENAhErJ.js +1 -0
- package/web-shell/assets/{chunk-2Q5K7J3B-DRjwqaDq.js → chunk-2Q5K7J3B-DnV9bKBe.js} +1 -1
- package/web-shell/assets/{chunk-5VM5RSS4-eHM9noJE.js → chunk-5VM5RSS4-rT1GAcT7.js} +1 -1
- package/web-shell/assets/{chunk-F27PBJKO-CcSWUYn1.js → chunk-F27PBJKO-BcgSxqGA.js} +1 -1
- package/web-shell/assets/{chunk-G27WJ6UU-DNr-RF5S.js → chunk-G27WJ6UU-Bw1ZhyZq.js} +1 -1
- package/web-shell/assets/{chunk-JWPE2WC7-rVJHoNF8.js → chunk-JWPE2WC7-D4iDw-96.js} +1 -1
- package/web-shell/assets/{chunk-LCL6LL3I-B2aX0Gzy.js → chunk-LCL6LL3I-D3MB3GTZ.js} +1 -1
- package/web-shell/assets/{chunk-POPQ4Y6H-DVp07n32.js → chunk-POPQ4Y6H-Cep2ndrS.js} +1 -1
- package/web-shell/assets/{chunk-SVP7TREG-C6QB_KUr.js → chunk-SVP7TREG-BuahMHJc.js} +1 -1
- package/web-shell/assets/{chunk-XXDRQBXY-CLVxDNCy.js → chunk-XXDRQBXY-BdujIvJY.js} +1 -1
- package/web-shell/assets/classDiagram-DTDB5LWJ-CM6FArGH.js +1 -0
- package/web-shell/assets/classDiagram-v2-JRS7N3AN-CM6FArGH.js +1 -0
- package/web-shell/assets/{cose-bilkent-JH36ORCC-B_aKX3A3.js → cose-bilkent-JH36ORCC-CGLLUbF8.js} +1 -1
- package/web-shell/assets/{cynefin-OW5HDTMX-BBw2pPUa.js → cynefin-OW5HDTMX-D6kDsRSh.js} +1 -1
- package/web-shell/assets/{cynefinDiagram-5FMLGOSQ-WWPiBTwk.js → cynefinDiagram-5FMLGOSQ-CCy78qZx.js} +1 -1
- package/web-shell/assets/{dagre-3AP2YEHR-CPtIdka-.js → dagre-3AP2YEHR-D5gJuJsf.js} +1 -1
- package/web-shell/assets/{diagram-S7CK7UJ4-Dj5og1Xi.js → diagram-S7CK7UJ4-ZwEZ5W__.js} +1 -1
- package/web-shell/assets/{diagram-UQ7AKVKN-CC_x70YC.js → diagram-UQ7AKVKN-B036APjv.js} +1 -1
- package/web-shell/assets/{diagram-VSXAHHWV-rXMfsO4n.js → diagram-VSXAHHWV-BfDJ57Fq.js} +1 -1
- package/web-shell/assets/{diagram-VX7I27RA-DEmLYQbh.js → diagram-VX7I27RA-Wha0p5rT.js} +1 -1
- package/web-shell/assets/{diagram-Z3DM3KII-B0ys6Vxx.js → diagram-Z3DM3KII-L20bz2K1.js} +1 -1
- package/web-shell/assets/{ebnfDiagram-PWID7BFC-Bq6y6lKH.js → ebnfDiagram-PWID7BFC-D1ZM0Cp7.js} +1 -1
- package/web-shell/assets/{erDiagram-SSCWMZ5O-l69JeBZ4.js → erDiagram-SSCWMZ5O-CMtA19v8.js} +1 -1
- package/web-shell/assets/{flowDiagram-A5DVABFB-Cm-EzGY4.js → flowDiagram-A5DVABFB-D96m326S.js} +1 -1
- package/web-shell/assets/{ganttDiagram-EL5Y4UJY-BRS9ZOp7.js → ganttDiagram-EL5Y4UJY-BuHiYG2o.js} +1 -1
- package/web-shell/assets/{gitGraphDiagram-WWUBYQGX-DAMRKgLk.js → gitGraphDiagram-WWUBYQGX-Ck3NmkSw.js} +1 -1
- package/web-shell/assets/index-8hXIpvo4.js +1865 -0
- package/web-shell/assets/{index-Bncx8E8M.js → index-CBGz4Qjl.js} +1 -1
- package/web-shell/assets/{index-C-XlQn24.css → index-DclEv91i.css} +1 -1
- package/web-shell/assets/{infoDiagram-RXCK75RN-DasffaZp.js → infoDiagram-RXCK75RN-B6DAOyXe.js} +1 -1
- package/web-shell/assets/{ishikawaDiagram-5VMMS53U-CmRUt3C-.js → ishikawaDiagram-5VMMS53U-Bpg-joFo.js} +1 -1
- package/web-shell/assets/{journeyDiagram-EYS64GPL-DzhE_c1Z.js → journeyDiagram-EYS64GPL-BclrHzGa.js} +1 -1
- package/web-shell/assets/{kanban-definition-3QL26DDD-BH5b6nn3.js → kanban-definition-3QL26DDD-5gJzNTYG.js} +1 -1
- package/web-shell/assets/{layout-BxoLK-LY.js → layout-CEz_1L8z.js} +1 -1
- package/web-shell/assets/{linear-CqDPvVI7.js → linear-CBjTq9Hc.js} +1 -1
- package/web-shell/assets/{mermaid.core-DBoN4plo.js → mermaid.core-Dzhbxx_I.js} +6 -6
- package/web-shell/assets/{mindmap-definition-FBJOCRG2-DJJtXPEM.js → mindmap-definition-FBJOCRG2-DQdVqlBF.js} +1 -1
- package/web-shell/assets/{pegDiagram-XKGWAZYB-DmgPXP5l.js → pegDiagram-XKGWAZYB-D1JZlYXL.js} +1 -1
- package/web-shell/assets/{pieDiagram-E7YTZNPT-1ezjzcI5.js → pieDiagram-E7YTZNPT-BmkpYL0w.js} +1 -1
- package/web-shell/assets/{quadrantDiagram-AXDQQJYC-ChKARz3k.js → quadrantDiagram-AXDQQJYC-DRvD6co5.js} +1 -1
- package/web-shell/assets/{railroadDiagram-O6MQD6OU-k7mNB_QT.js → railroadDiagram-O6MQD6OU-CEdjDuoo.js} +1 -1
- package/web-shell/assets/{requirementDiagram-EFPCY7ZU-B0XFsii0.js → requirementDiagram-EFPCY7ZU-BCHG-ie8.js} +1 -1
- package/web-shell/assets/{sankeyDiagram-P5KCCOFB-repJ2Ukk.js → sankeyDiagram-P5KCCOFB-C9cZMnye.js} +1 -1
- package/web-shell/assets/{sequenceDiagram-WJ2MYXX4-D0gUSKGu.js → sequenceDiagram-WJ2MYXX4-DYHRV4zL.js} +1 -1
- package/web-shell/assets/{sizeCapture-X5ZJPWSS-CE463F74.js → sizeCapture-X5ZJPWSS-DlK21aye.js} +1 -1
- package/web-shell/assets/{stateDiagram-HBIQ2CUA-003zYtJa.js → stateDiagram-HBIQ2CUA-BrmZssvk.js} +1 -1
- package/web-shell/assets/stateDiagram-v2-4QOOHH4V-B2Q7XDaE.js +1 -0
- package/web-shell/assets/{swimlanes-XN3QIQJK-DBj8MFpu.js → swimlanes-XN3QIQJK-D9xfcayR.js} +1 -1
- package/web-shell/assets/swimlanesDiagram-VK2B7HYN-DpZPOSbX.js +8 -0
- package/web-shell/assets/{timeline-definition-24CTP7MA-D2aCh4a9.js → timeline-definition-24CTP7MA-D4gdDL_d.js} +1 -1
- package/web-shell/assets/{vennDiagram-4TSXK5OY-BHMfWBpl.js → vennDiagram-4TSXK5OY-DdhIExjI.js} +1 -1
- package/web-shell/assets/{wardleyDiagram-VM6X3IG4-RdHAj66j.js → wardleyDiagram-VM6X3IG4-C9RS_Zof.js} +1 -1
- package/web-shell/assets/{xychartDiagram-S5SC5T6Z-BgJWOvq0.js → xychartDiagram-S5SC5T6Z-orRgmxWk.js} +1 -1
- package/web-shell/index.html +2 -2
- package/chunks/MaxSizedBox-BFVLZP2E.js +0 -112
- package/chunks/agent-headless-UCBCS4V4.js +0 -87
- package/chunks/bridge-FRNYLYY7.js +0 -119
- package/chunks/channel-settings-store-IV4KDL7N.js +0 -117
- package/chunks/chunk-JS73NNWF.js +0 -30
- package/chunks/chunk-VGHZT6JR.js +0 -51
- package/chunks/config-utils-PMX4QZ5Y.js +0 -113
- package/chunks/contextCommand-NZRLVAFC.js +0 -108
- package/chunks/daemon-status-provider-FTTYZEKN.js +0 -118
- package/chunks/daemon-trust-policy-WMGFPA3Z.js +0 -114
- package/chunks/deferred-core-runtime-BUFEFYNA.js +0 -116
- package/chunks/earlyInputCapture-ROPLMXA7.js +0 -109
- package/chunks/errors-U5KNAADB.js +0 -115
- package/chunks/handleAutoUpdate-W4BUYFT7.js +0 -111
- package/chunks/initializer-SNVVLS7Y.js +0 -115
- package/chunks/installationInfo-D4XGQNHJ.js +0 -109
- package/chunks/list-WYECMLXZ.js +0 -118
- package/chunks/loadedSettingsAdapter-5WZBXBCR.js +0 -112
- package/chunks/mcp-CBWAROY3.js +0 -112
- package/chunks/nonInteractiveCli-O232UUV7.js +0 -185
- package/chunks/pidfile-MSPQ255Y.js +0 -113
- package/chunks/prompt-terminal-ledger-YPGMNDXT.js +0 -107
- package/chunks/resumeHistoryUtils-35D4KAVT.js +0 -120
- package/chunks/serve-WQ54UIYG.js +0 -120
- package/chunks/spawnChannel-NE43EEO3.js +0 -113
- package/chunks/theme-manager-IJS6OICX.js +0 -104
- package/chunks/total-session-admission-L5RPEFZ2.js +0 -114
- package/chunks/useAutoAcceptIndicator-O2N2Q6KG.js +0 -122
- package/chunks/workspace-providers-status-OCFEWIPQ.js +0 -116
- package/chunks/workspace-service-5ZOBMMIB.js +0 -131
- package/chunks/workspace-skills-status-B6NJZD4B.js +0 -115
- package/chunks/write-file-B2RNX6SU.js +0 -90
- package/web-shell/assets/channel-CU-Zyv0r.js +0 -1
- package/web-shell/assets/classDiagram-DTDB5LWJ-C0J2_LKs.js +0 -1
- package/web-shell/assets/classDiagram-v2-JRS7N3AN-C0J2_LKs.js +0 -1
- package/web-shell/assets/index-gdvMN4J1.js +0 -1860
- package/web-shell/assets/stateDiagram-v2-4QOOHH4V-2uACDdJN.js +0 -1
- package/web-shell/assets/swimlanesDiagram-VK2B7HYN-C4JJkavU.js +0 -8
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
// Force strict mode and setup for ESM
|
|
2
2
|
"use strict";
|
|
3
|
+
import {
|
|
4
|
+
BridgeClient,
|
|
5
|
+
CANCEL_VOTE_SENTINEL,
|
|
6
|
+
KNOWN_APPROVAL_MODES,
|
|
7
|
+
MultiClientPermissionMediator,
|
|
8
|
+
SessionAttachmentReferenceError,
|
|
9
|
+
SessionAttachmentStore,
|
|
10
|
+
createNoOpPermissionAuditPublisher,
|
|
11
|
+
isSessionAttachmentReference,
|
|
12
|
+
withAttachmentDegradationMarker,
|
|
13
|
+
writeStderrLine
|
|
14
|
+
} from "./chunk-ZGXYO7DY.js";
|
|
3
15
|
import {
|
|
4
16
|
DEFAULT_RING_SIZE,
|
|
5
17
|
EVENT_SCHEMA_VERSION,
|
|
@@ -19,7 +31,7 @@ import {
|
|
|
19
31
|
} from "./chunk-OCPBI7J5.js";
|
|
20
32
|
import {
|
|
21
33
|
defaultSpawnChannelFactory
|
|
22
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-RUGNCYNO.js";
|
|
23
35
|
import {
|
|
24
36
|
BridgeChannelClosedError,
|
|
25
37
|
BridgeTimeoutError,
|
|
@@ -29,7 +41,7 @@ import {
|
|
|
29
41
|
SessionRestoreTimeoutError,
|
|
30
42
|
createIdleWorkspaceExtensionsStatus,
|
|
31
43
|
createIdleWorkspaceHooksStatus
|
|
32
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-JHS74YAB.js";
|
|
33
45
|
import {
|
|
34
46
|
ACTIVE_WORK_CLOSE_IF_UNHELD_PARAM,
|
|
35
47
|
ACTIVE_WORK_CLOSE_TIMEOUT_MS,
|
|
@@ -38,8 +50,6 @@ import {
|
|
|
38
50
|
ACTIVE_WORK_HEARTBEAT_VERSION,
|
|
39
51
|
ACTIVE_WORK_HOLD_CATEGORIES,
|
|
40
52
|
ACTIVE_WORK_MAX_SESSION_HOLDS,
|
|
41
|
-
ACTIVE_WORK_MAX_SNAPSHOT_SESSIONS,
|
|
42
|
-
ACTIVE_WORK_NOTIFICATION_METHOD,
|
|
43
53
|
ACTIVE_WORK_STALE_INTERVALS,
|
|
44
54
|
CHANNEL_LIVENESS_META_KEY,
|
|
45
55
|
CHANNEL_LIVENESS_VERSION,
|
|
@@ -49,7 +59,6 @@ import {
|
|
|
49
59
|
DAEMON_ATTACHMENT_REFERENCES_META_KEY,
|
|
50
60
|
DAEMON_CHANNEL_DELIVERY_META_KEY,
|
|
51
61
|
DAEMON_MODEL_PROMPT_META_KEY,
|
|
52
|
-
DAEMON_PERMISSION_CANCEL_REASON_META_KEY,
|
|
53
62
|
DAEMON_PROMPT_DISPLAY_TEXT_META_KEY,
|
|
54
63
|
DAEMON_RESTORE_ASK_USER_QUESTION_META_KEY,
|
|
55
64
|
DAEMON_SUPPRESS_RESTORE_ASK_USER_QUESTION_META_KEY,
|
|
@@ -60,11 +69,9 @@ import {
|
|
|
60
69
|
LOAD_REPLAY_MODE_META_KEY,
|
|
61
70
|
LOAD_REPLAY_PAGE_SIZE_META_KEY,
|
|
62
71
|
LOAD_REPLAY_VERSION,
|
|
63
|
-
MID_TURN_QUEUE_DRAIN_METHOD,
|
|
64
72
|
MID_TURN_RECONCILIATION_RING_SIZE,
|
|
65
73
|
PROMPT_CANCEL_METHOD,
|
|
66
74
|
REQUESTED_SESSION_ID_META_KEY,
|
|
67
|
-
TODO_STOP_GUARD_CONTINUATION_CLAIM_METHOD,
|
|
68
75
|
TODO_STOP_GUARD_QUEUE_RELEASE_METHOD,
|
|
69
76
|
WORKTREE_MCP_DEFER_META_KEY,
|
|
70
77
|
clampActiveWorkIntervalMs,
|
|
@@ -74,7 +81,6 @@ import {
|
|
|
74
81
|
import {
|
|
75
82
|
BranchWhilePromptActiveError,
|
|
76
83
|
BridgeChannelQuarantinedError,
|
|
77
|
-
CancelSentinelCollisionError,
|
|
78
84
|
CdWhilePromptActiveError,
|
|
79
85
|
InvalidClientIdError,
|
|
80
86
|
InvalidPermissionOptionError,
|
|
@@ -90,33 +96,23 @@ import {
|
|
|
90
96
|
SessionNotFoundError,
|
|
91
97
|
SessionShellClientRequiredError,
|
|
92
98
|
SessionShellDisabledError,
|
|
99
|
+
StandaloneSessionSpawnError,
|
|
93
100
|
WorkspaceMismatchError,
|
|
94
101
|
isNotCurrentlyGeneratingCancelError
|
|
95
|
-
} from "./chunk-
|
|
102
|
+
} from "./chunk-ERFDKH32.js";
|
|
96
103
|
import {
|
|
97
104
|
canonicalizeWorkspace,
|
|
98
105
|
translateAndCheckAbsoluteWorkspacePath
|
|
99
106
|
} from "./chunk-6PLDPT2C.js";
|
|
100
|
-
import {
|
|
101
|
-
CHANNEL_DELIVERY_ERROR_CODES,
|
|
102
|
-
LIVE_TASK_TOOL_NAMES,
|
|
103
|
-
MAX_LIVE_SCREEN_CONTEXT_TEXT_CHARS,
|
|
104
|
-
MAX_LIVE_SPEAK_TO_USER_MESSAGE_CHARS,
|
|
105
|
-
MAX_SUB_SESSION_NAME_CHARS
|
|
106
|
-
} from "./chunk-UDG5EZJI.js";
|
|
107
107
|
import {
|
|
108
108
|
EXTERNAL_TOOL_GUARD_READY_META_KEY,
|
|
109
|
-
EXTERNAL_TOOL_GUARD_REQUIRED_VALUE
|
|
110
|
-
isValidExternalToolGuardDenialReason
|
|
109
|
+
EXTERNAL_TOOL_GUARD_REQUIRED_VALUE
|
|
111
110
|
} from "./chunk-3VUENPWF.js";
|
|
112
111
|
import {
|
|
113
112
|
ClientSideConnection,
|
|
114
113
|
PROTOCOL_VERSION,
|
|
115
114
|
RequestError
|
|
116
115
|
} from "./chunk-IW6RQPQB.js";
|
|
117
|
-
import {
|
|
118
|
-
MAX_SUB_SESSION_PROMPT_CHARS
|
|
119
|
-
} from "./chunk-LJZSMWOH.js";
|
|
120
116
|
import {
|
|
121
117
|
MAX_DIRECTORY_ARTIFACT_DEPTH,
|
|
122
118
|
MAX_DIRECTORY_ARTIFACT_FILES,
|
|
@@ -124,7 +120,7 @@ import {
|
|
|
124
120
|
isOfficeDocumentExtension,
|
|
125
121
|
isRecordableDerivedChild,
|
|
126
122
|
pathHasSkippedDirectoryComponent
|
|
127
|
-
} from "./chunk-
|
|
123
|
+
} from "./chunk-6IUNAPLR.js";
|
|
128
124
|
import {
|
|
129
125
|
SESSION_ARTIFACT_PERSISTENCE_VERSION,
|
|
130
126
|
SESSION_PR_LIST_LIMIT,
|
|
@@ -142,22 +138,16 @@ import {
|
|
|
142
138
|
normalizeSnapshotPayload,
|
|
143
139
|
normalizeTurnResultError,
|
|
144
140
|
stableSessionArtifactId
|
|
145
|
-
} from "./chunk-
|
|
146
|
-
import {
|
|
147
|
-
APPROVAL_MODES
|
|
148
|
-
} from "./chunk-XR7OIAQF.js";
|
|
141
|
+
} from "./chunk-TGWAQ5RB.js";
|
|
149
142
|
import {
|
|
150
143
|
DAEMON_TRACEPARENT_META_KEY,
|
|
151
144
|
DAEMON_TRACESTATE_META_KEY
|
|
152
|
-
} from "./chunk-
|
|
145
|
+
} from "./chunk-HVEYF6VT.js";
|
|
153
146
|
import {
|
|
154
147
|
INVOCATION_CONTEXT_META_KEY,
|
|
155
148
|
PRIVATE_ACP_CAPABILITY_ENV,
|
|
156
149
|
PRIVATE_PARENT_CAPABILITY_META_KEY
|
|
157
|
-
} from "./chunk-
|
|
158
|
-
import {
|
|
159
|
-
getSpecificMimeType
|
|
160
|
-
} from "./chunk-WBT3STGU.js";
|
|
150
|
+
} from "./chunk-L2M46FP5.js";
|
|
161
151
|
import {
|
|
162
152
|
init_esbuild_shims
|
|
163
153
|
} from "./chunk-5O2XNYP6.js";
|
|
@@ -167,8 +157,8 @@ import {
|
|
|
167
157
|
|
|
168
158
|
// packages/acp-bridge/src/bridge.ts
|
|
169
159
|
init_esbuild_shims();
|
|
170
|
-
import { randomBytes, randomUUID
|
|
171
|
-
import * as
|
|
160
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
161
|
+
import * as path2 from "node:path";
|
|
172
162
|
import { inspect } from "node:util";
|
|
173
163
|
|
|
174
164
|
// packages/acp-bridge/src/compactionEngine.ts
|
|
@@ -1185,6 +1175,12 @@ init_esbuild_shims();
|
|
|
1185
1175
|
var SESSION_SOURCE_META_KEY = "qwen.session.source";
|
|
1186
1176
|
var SESSION_SOURCE_TYPE_PATTERN = /^[a-z][a-z0-9_-]{0,63}$/;
|
|
1187
1177
|
var MAX_SESSION_SOURCE_ID_LENGTH = 256;
|
|
1178
|
+
var STANDALONE_SESSION_SOURCE_TYPE = "standalone";
|
|
1179
|
+
var DAEMON_OWNED_STANDALONE_CREATION_KEY = "daemonOwnedStandaloneCreation";
|
|
1180
|
+
function isReservedStandaloneSessionSourceType(sourceType) {
|
|
1181
|
+
return sourceType === STANDALONE_SESSION_SOURCE_TYPE;
|
|
1182
|
+
}
|
|
1183
|
+
__name(isReservedStandaloneSessionSourceType, "isReservedStandaloneSessionSourceType");
|
|
1188
1184
|
function parseSessionSource(sourceType, sourceId) {
|
|
1189
1185
|
if (sourceType === void 0 && sourceId === void 0) return {};
|
|
1190
1186
|
if (typeof sourceType !== "string" || !SESSION_SOURCE_TYPE_PATTERN.test(sourceType)) {
|
|
@@ -1237,8 +1233,8 @@ function startChannelLivenessMonitor(options) {
|
|
|
1237
1233
|
let consecutiveTimeouts = 0;
|
|
1238
1234
|
let nextNonce = 0;
|
|
1239
1235
|
let resolveStopped;
|
|
1240
|
-
const stoppedOutcome = new Promise((
|
|
1241
|
-
resolveStopped =
|
|
1236
|
+
const stoppedOutcome = new Promise((resolve) => {
|
|
1237
|
+
resolveStopped = resolve;
|
|
1242
1238
|
});
|
|
1243
1239
|
const isActive = /* @__PURE__ */ __name(() => !stopped && options.isActive(), "isActive");
|
|
1244
1240
|
const stop = /* @__PURE__ */ __name(() => {
|
|
@@ -1266,10 +1262,10 @@ function startChannelLivenessMonitor(options) {
|
|
|
1266
1262
|
let outcome;
|
|
1267
1263
|
while (true) {
|
|
1268
1264
|
const expectedAt = now() + CHANNEL_LIVENESS_PROBE_TIMEOUT_MS;
|
|
1269
|
-
const timeout = new Promise((
|
|
1265
|
+
const timeout = new Promise((resolve) => {
|
|
1270
1266
|
timer = setTimeout(() => {
|
|
1271
1267
|
timer = void 0;
|
|
1272
|
-
|
|
1268
|
+
resolve(
|
|
1273
1269
|
timerWasLate(expectedAt) ? { kind: "local_delay" } : { kind: "timeout" }
|
|
1274
1270
|
);
|
|
1275
1271
|
}, CHANNEL_LIVENESS_PROBE_TIMEOUT_MS);
|
|
@@ -1329,3273 +1325,104 @@ __name(startChannelLivenessMonitor, "startChannelLivenessMonitor");
|
|
|
1329
1325
|
// packages/acp-bridge/src/channel-startup-profile.ts
|
|
1330
1326
|
init_esbuild_shims();
|
|
1331
1327
|
var MAX_PROFILE_DURATION_MS = 6e5;
|
|
1332
|
-
var ATTRIBUTE_PREFIX = "qwen-code.daemon.acp_startup";
|
|
1333
|
-
function isRecord(value) {
|
|
1334
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1335
|
-
}
|
|
1336
|
-
__name(isRecord, "isRecord");
|
|
1337
|
-
function readDuration(source, key) {
|
|
1338
|
-
const value = source[key];
|
|
1339
|
-
return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= MAX_PROFILE_DURATION_MS ? value : void 0;
|
|
1340
|
-
}
|
|
1341
|
-
__name(readDuration, "readDuration");
|
|
1342
|
-
function readDurations(source, keys) {
|
|
1343
|
-
if (!isRecord(source)) {
|
|
1344
|
-
return { values: {}, complete: false };
|
|
1345
|
-
}
|
|
1346
|
-
const values = {};
|
|
1347
|
-
let complete = true;
|
|
1348
|
-
for (const key of keys) {
|
|
1349
|
-
const value = readDuration(source, key);
|
|
1350
|
-
if (value === void 0) {
|
|
1351
|
-
complete = false;
|
|
1352
|
-
} else {
|
|
1353
|
-
values[key] = value;
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
return { values, complete };
|
|
1357
|
-
}
|
|
1358
|
-
__name(readDurations, "readDurations");
|
|
1359
|
-
function addDurationAttributes(attributes, group, values) {
|
|
1360
|
-
for (const [key, value] of Object.entries(values)) {
|
|
1361
|
-
if (group === "phase" && key === "unattributedMs") continue;
|
|
1362
|
-
const attributeKey = key.replace(
|
|
1363
|
-
/[A-Z]/g,
|
|
1364
|
-
(letter) => `_${letter.toLowerCase()}`
|
|
1365
|
-
);
|
|
1366
|
-
attributes[`${ATTRIBUTE_PREFIX}.${group}.${attributeKey}`] = value;
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
__name(addDurationAttributes, "addDurationAttributes");
|
|
1370
|
-
var PHASE_KEYS = [
|
|
1371
|
-
"processToProfilerReadyMs",
|
|
1372
|
-
"geminiImportMs",
|
|
1373
|
-
"argsParseMs",
|
|
1374
|
-
"settingsLoadMs",
|
|
1375
|
-
"configConstructionMs",
|
|
1376
|
-
"appInitializationMs",
|
|
1377
|
-
"acpImportMs",
|
|
1378
|
-
"bootstrapConfigInitializationMs",
|
|
1379
|
-
"transportSetupMs",
|
|
1380
|
-
"initializeHandlerMs",
|
|
1381
|
-
"unattributedMs"
|
|
1382
|
-
];
|
|
1383
|
-
var CONFIG_KEYS = [
|
|
1384
|
-
"extensionsInitialMs",
|
|
1385
|
-
"hooksMs",
|
|
1386
|
-
"skillsMs",
|
|
1387
|
-
"extensionsFinalMs",
|
|
1388
|
-
"hierarchicalMemoryMs",
|
|
1389
|
-
"toolRegistryMs",
|
|
1390
|
-
"ripgrepProbeMs",
|
|
1391
|
-
"toolWarmupMs",
|
|
1392
|
-
"otherMs"
|
|
1393
|
-
];
|
|
1394
|
-
function getChannelStartupProfileAttributes(response, receivedAtEpochMs, initializeTimeoutMs) {
|
|
1395
|
-
if (!isRecord(response) || !isRecord(response["_meta"])) {
|
|
1396
|
-
return void 0;
|
|
1397
|
-
}
|
|
1398
|
-
const profile = response["_meta"][CHANNEL_STARTUP_PROFILE_META_KEY];
|
|
1399
|
-
if (!isRecord(profile) || profile["v"] !== CHANNEL_STARTUP_PROFILE_VERSION) {
|
|
1400
|
-
return void 0;
|
|
1401
|
-
}
|
|
1402
|
-
const phases = readDurations(profile["phases"], PHASE_KEYS);
|
|
1403
|
-
const config = readDurations(profile["config"], CONFIG_KEYS);
|
|
1404
|
-
const processToResponseMs = readDuration(profile, "processToResponseMs");
|
|
1405
|
-
const responseBuiltAtEpochMs = profile["responseBuiltAtEpochMs"];
|
|
1406
|
-
const childComplete = profile["complete"] === true;
|
|
1407
|
-
const validResponseEpoch = typeof responseBuiltAtEpochMs === "number" && Number.isFinite(responseBuiltAtEpochMs) && responseBuiltAtEpochMs >= 0;
|
|
1408
|
-
const effectiveComplete = childComplete && phases.complete && config.complete && processToResponseMs !== void 0 && validResponseEpoch;
|
|
1409
|
-
const attributes = {
|
|
1410
|
-
[`${ATTRIBUTE_PREFIX}.profile.version`]: CHANNEL_STARTUP_PROFILE_VERSION,
|
|
1411
|
-
[`${ATTRIBUTE_PREFIX}.profile.complete`]: effectiveComplete
|
|
1412
|
-
};
|
|
1413
|
-
if (processToResponseMs !== void 0) {
|
|
1414
|
-
attributes[`${ATTRIBUTE_PREFIX}.child.process_to_response_ms`] = processToResponseMs;
|
|
1415
|
-
}
|
|
1416
|
-
if (phases.values["unattributedMs"] !== void 0) {
|
|
1417
|
-
attributes[`${ATTRIBUTE_PREFIX}.child.unattributed_ms`] = phases.values["unattributedMs"];
|
|
1418
|
-
}
|
|
1419
|
-
addDurationAttributes(attributes, "phase", phases.values);
|
|
1420
|
-
addDurationAttributes(attributes, "config", config.values);
|
|
1421
|
-
if (validResponseEpoch) {
|
|
1422
|
-
const transportMs = receivedAtEpochMs - responseBuiltAtEpochMs;
|
|
1423
|
-
if (Number.isFinite(transportMs) && transportMs >= 0 && transportMs <= initializeTimeoutMs) {
|
|
1424
|
-
attributes[`${ATTRIBUTE_PREFIX}.response_transport_ms`] = transportMs;
|
|
1425
|
-
}
|
|
1426
|
-
}
|
|
1427
|
-
return attributes;
|
|
1428
|
-
}
|
|
1429
|
-
__name(getChannelStartupProfileAttributes, "getChannelStartupProfileAttributes");
|
|
1430
|
-
|
|
1431
|
-
// packages/acp-bridge/src/sessionAttachments.ts
|
|
1432
|
-
init_esbuild_shims();
|
|
1433
|
-
import { randomUUID } from "node:crypto";
|
|
1434
|
-
import { promises as fs, statSync } from "node:fs";
|
|
1435
|
-
import { tmpdir } from "node:os";
|
|
1436
|
-
import * as path from "node:path";
|
|
1437
|
-
var SESSION_ATTACHMENT_MAX_ITEM_BYTES = 8 * 1024 * 1024;
|
|
1438
|
-
var SESSION_ATTACHMENT_MAX_NAME_BYTES = 255;
|
|
1439
|
-
var SUPPORTED_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
1440
|
-
"image/bmp",
|
|
1441
|
-
"image/gif",
|
|
1442
|
-
"image/jpeg",
|
|
1443
|
-
"image/png",
|
|
1444
|
-
"image/webp"
|
|
1445
|
-
]);
|
|
1446
|
-
var SESSION_ATTACHMENT_UNAVAILABLE_TEXT = "[Attachment is no longer available]";
|
|
1447
|
-
var SessionAttachmentReferenceError = class extends Error {
|
|
1448
|
-
constructor(message, code) {
|
|
1449
|
-
super(message);
|
|
1450
|
-
this.code = code;
|
|
1451
|
-
this.name = "SessionAttachmentReferenceError";
|
|
1452
|
-
}
|
|
1453
|
-
static {
|
|
1454
|
-
__name(this, "SessionAttachmentReferenceError");
|
|
1455
|
-
}
|
|
1456
|
-
};
|
|
1457
|
-
function isSessionAttachmentReference(value) {
|
|
1458
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1459
|
-
const record = value;
|
|
1460
|
-
return (record["type"] === "image" || record["type"] === "resource") && typeof record["attachmentId"] === "string" && record["attachmentId"].length > 0 && typeof record["mimeType"] === "string" && record["mimeType"].length > 0 && (record["type"] !== "image" || record["mimeType"].startsWith("image/")) && typeof record["size"] === "number" && Number.isSafeInteger(record["size"]) && record["size"] >= 0 && (record["type"] !== "image" || record["size"] > 0);
|
|
1461
|
-
}
|
|
1462
|
-
__name(isSessionAttachmentReference, "isSessionAttachmentReference");
|
|
1463
|
-
function safeAttachmentName(name) {
|
|
1464
|
-
const safeName = path.basename(name.replaceAll("\\", "/")).trim();
|
|
1465
|
-
const isWindowsReserved = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(safeName);
|
|
1466
|
-
const hasInvalidCharacter = Array.from(safeName).some((character) => {
|
|
1467
|
-
const codePoint = character.codePointAt(0);
|
|
1468
|
-
return codePoint !== void 0 && (codePoint <= 31 || codePoint === 127 || codePoint >= 55296 && codePoint <= 57343);
|
|
1469
|
-
});
|
|
1470
|
-
return !safeName || safeName === "." || safeName === ".." || safeName.endsWith(".") || isWindowsReserved || /[<>:"|?*]/.test(safeName) || hasInvalidCharacter || Buffer.byteLength(safeName) > SESSION_ATTACHMENT_MAX_NAME_BYTES ? void 0 : safeName;
|
|
1471
|
-
}
|
|
1472
|
-
__name(safeAttachmentName, "safeAttachmentName");
|
|
1473
|
-
function truncateUtf8(value, maxBytes) {
|
|
1474
|
-
let bytes = 0;
|
|
1475
|
-
let result = "";
|
|
1476
|
-
for (const character of value) {
|
|
1477
|
-
const characterBytes = Buffer.byteLength(character);
|
|
1478
|
-
if (bytes + characterBytes > maxBytes) break;
|
|
1479
|
-
result += character;
|
|
1480
|
-
bytes += characterBytes;
|
|
1481
|
-
}
|
|
1482
|
-
return result;
|
|
1483
|
-
}
|
|
1484
|
-
__name(truncateUtf8, "truncateUtf8");
|
|
1485
|
-
function deduplicatedName(name, suffix) {
|
|
1486
|
-
if (suffix === 0) return name;
|
|
1487
|
-
const extension = path.extname(name);
|
|
1488
|
-
const suffixText = ` (${suffix})`;
|
|
1489
|
-
const stem = name.slice(0, -extension.length || void 0);
|
|
1490
|
-
const extensionBudget = SESSION_ATTACHMENT_MAX_NAME_BYTES - Buffer.byteLength(suffixText) - 1;
|
|
1491
|
-
const safeExtension = truncateUtf8(extension, extensionBudget).replace(
|
|
1492
|
-
/[. ]+$/u,
|
|
1493
|
-
""
|
|
1494
|
-
);
|
|
1495
|
-
const stemBudget = SESSION_ATTACHMENT_MAX_NAME_BYTES - Buffer.byteLength(suffixText) - Buffer.byteLength(safeExtension);
|
|
1496
|
-
return `${truncateUtf8(stem, stemBudget)}${suffixText}${safeExtension}`;
|
|
1497
|
-
}
|
|
1498
|
-
__name(deduplicatedName, "deduplicatedName");
|
|
1499
|
-
function imageName(mimeType) {
|
|
1500
|
-
const extension = mimeType.slice("image/".length).split(/[;+]/, 1)[0];
|
|
1501
|
-
return `image.${extension === "jpg" ? "jpeg" : extension || "img"}`;
|
|
1502
|
-
}
|
|
1503
|
-
__name(imageName, "imageName");
|
|
1504
|
-
function mimeTypeForName(name) {
|
|
1505
|
-
if ([".ts", ".mts", ".cts", ".tsx"].includes(path.extname(name).toLowerCase())) {
|
|
1506
|
-
return "text/plain";
|
|
1507
|
-
}
|
|
1508
|
-
return getSpecificMimeType(name) ?? "application/octet-stream";
|
|
1509
|
-
}
|
|
1510
|
-
__name(mimeTypeForName, "mimeTypeForName");
|
|
1511
|
-
function isSupportedImageMimeType(mimeType) {
|
|
1512
|
-
return SUPPORTED_IMAGE_MIME_TYPES.has(mimeType);
|
|
1513
|
-
}
|
|
1514
|
-
__name(isSupportedImageMimeType, "isSupportedImageMimeType");
|
|
1515
|
-
function isTextMimeType(mimeType) {
|
|
1516
|
-
return mimeType.startsWith("text/") || mimeType === "application/json" || mimeType.endsWith("+json") || mimeType === "application/xml" || mimeType.endsWith("+xml") || mimeType === "application/javascript" || mimeType === "application/typescript" || mimeType === "application/yaml" || mimeType === "application/x-yaml" || mimeType === "application/toml";
|
|
1517
|
-
}
|
|
1518
|
-
__name(isTextMimeType, "isTextMimeType");
|
|
1519
|
-
function isTextAttachment(data, mimeType) {
|
|
1520
|
-
if (isTextMimeType(mimeType)) return true;
|
|
1521
|
-
if (mimeType.startsWith("image/") || mimeType.startsWith("audio/") || mimeType.startsWith("video/") || mimeType.startsWith("font/") || mimeType === "application/pdf" || data.includes(0)) {
|
|
1522
|
-
return false;
|
|
1523
|
-
}
|
|
1524
|
-
return Buffer.from(data.toString("utf8"), "utf8").equals(data);
|
|
1525
|
-
}
|
|
1526
|
-
__name(isTextAttachment, "isTextAttachment");
|
|
1527
|
-
function withAttachmentDegradationMarker(blocks) {
|
|
1528
|
-
for (let i = blocks.length - 1; i >= 0; i--) {
|
|
1529
|
-
const block = blocks[i];
|
|
1530
|
-
if (block.type === "text") {
|
|
1531
|
-
if (block.text.endsWith(SESSION_ATTACHMENT_UNAVAILABLE_TEXT)) {
|
|
1532
|
-
return [...blocks];
|
|
1533
|
-
}
|
|
1534
|
-
const next = [...blocks];
|
|
1535
|
-
next[i] = {
|
|
1536
|
-
type: "text",
|
|
1537
|
-
text: `${block.text}
|
|
1538
|
-
${SESSION_ATTACHMENT_UNAVAILABLE_TEXT}`
|
|
1539
|
-
};
|
|
1540
|
-
return next;
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
return [
|
|
1544
|
-
...blocks,
|
|
1545
|
-
{ type: "text", text: SESSION_ATTACHMENT_UNAVAILABLE_TEXT }
|
|
1546
|
-
];
|
|
1547
|
-
}
|
|
1548
|
-
__name(withAttachmentDegradationMarker, "withAttachmentDegradationMarker");
|
|
1549
|
-
var SessionAttachmentStore = class {
|
|
1550
|
-
constructor(directoryRoot, sessionId) {
|
|
1551
|
-
this.directoryRoot = directoryRoot;
|
|
1552
|
-
if (!directoryRoot || !sessionId) return;
|
|
1553
|
-
this.persistentDirectory = path.join(
|
|
1554
|
-
directoryRoot,
|
|
1555
|
-
`session-${encodeURIComponent(sessionId)}`
|
|
1556
|
-
);
|
|
1557
|
-
}
|
|
1558
|
-
static {
|
|
1559
|
-
__name(this, "SessionAttachmentStore");
|
|
1560
|
-
}
|
|
1561
|
-
directoryPromise;
|
|
1562
|
-
persistentDirectory;
|
|
1563
|
-
activeDirectory;
|
|
1564
|
-
pendingItems = 0;
|
|
1565
|
-
pendingNames = /* @__PURE__ */ new Map();
|
|
1566
|
-
pendingDrainWaiters = [];
|
|
1567
|
-
copyDrainWaiters = [];
|
|
1568
|
-
copying = false;
|
|
1569
|
-
closing = false;
|
|
1570
|
-
closed = false;
|
|
1571
|
-
async putAttachment(data, mimeType, name) {
|
|
1572
|
-
const isImage = isSupportedImageMimeType(mimeType);
|
|
1573
|
-
const safeName = safeAttachmentName(
|
|
1574
|
-
name ?? (isImage ? imageName(mimeType) : "")
|
|
1575
|
-
);
|
|
1576
|
-
if (!safeName) {
|
|
1577
|
-
throw new TypeError("Session attachment name is invalid");
|
|
1578
|
-
}
|
|
1579
|
-
const storedMimeType = mimeTypeForName(safeName);
|
|
1580
|
-
if (isImage && storedMimeType !== mimeType || isSupportedImageMimeType(storedMimeType) && !isImage) {
|
|
1581
|
-
throw new TypeError("Attachment name and Content-Type do not match");
|
|
1582
|
-
}
|
|
1583
|
-
if (this.closed || this.closing) {
|
|
1584
|
-
throw new Error("Session attachment store is closed");
|
|
1585
|
-
}
|
|
1586
|
-
if (this.copying) throw new Error("Session attachments are being copied");
|
|
1587
|
-
if (isImage && data.byteLength === 0 || data.byteLength > SESSION_ATTACHMENT_MAX_ITEM_BYTES) {
|
|
1588
|
-
throw new RangeError(
|
|
1589
|
-
`Session attachment must be at most ${SESSION_ATTACHMENT_MAX_ITEM_BYTES} bytes and images cannot be empty`
|
|
1590
|
-
);
|
|
1591
|
-
}
|
|
1592
|
-
let filePath;
|
|
1593
|
-
let pendingName = safeName;
|
|
1594
|
-
let removeFileOnFailure = false;
|
|
1595
|
-
this.pendingItems += 1;
|
|
1596
|
-
this.reservePendingName(safeName);
|
|
1597
|
-
try {
|
|
1598
|
-
const directory = await this.directory();
|
|
1599
|
-
let suffix = 0;
|
|
1600
|
-
for (; ; ) {
|
|
1601
|
-
const candidateName = deduplicatedName(safeName, suffix);
|
|
1602
|
-
if (safeAttachmentName(candidateName) !== candidateName) {
|
|
1603
|
-
throw new TypeError("Session attachment name is invalid");
|
|
1604
|
-
}
|
|
1605
|
-
if (pendingName !== candidateName) {
|
|
1606
|
-
if (pendingName) this.releasePendingName(pendingName);
|
|
1607
|
-
this.reservePendingName(candidateName);
|
|
1608
|
-
pendingName = candidateName;
|
|
1609
|
-
}
|
|
1610
|
-
filePath = path.join(directory, candidateName);
|
|
1611
|
-
removeFileOnFailure = true;
|
|
1612
|
-
try {
|
|
1613
|
-
await fs.writeFile(filePath, data, { flag: "wx" });
|
|
1614
|
-
break;
|
|
1615
|
-
} catch (error) {
|
|
1616
|
-
if (error.code !== "EEXIST") throw error;
|
|
1617
|
-
removeFileOnFailure = false;
|
|
1618
|
-
this.releasePendingName(candidateName);
|
|
1619
|
-
pendingName = void 0;
|
|
1620
|
-
filePath = void 0;
|
|
1621
|
-
suffix += 1;
|
|
1622
|
-
}
|
|
1623
|
-
}
|
|
1624
|
-
if (this.closed || this.closing) {
|
|
1625
|
-
throw new Error("Session attachment store is closed");
|
|
1626
|
-
}
|
|
1627
|
-
const name2 = path.basename(filePath);
|
|
1628
|
-
const storedMimeType2 = mimeTypeForName(name2);
|
|
1629
|
-
const reference = {
|
|
1630
|
-
type: isSupportedImageMimeType(storedMimeType2) ? "image" : "resource",
|
|
1631
|
-
attachmentId: name2,
|
|
1632
|
-
mimeType: storedMimeType2,
|
|
1633
|
-
size: data.byteLength
|
|
1634
|
-
};
|
|
1635
|
-
return reference;
|
|
1636
|
-
} catch (error) {
|
|
1637
|
-
if (removeFileOnFailure && filePath) {
|
|
1638
|
-
await fs.rm(filePath, { force: true }).catch(() => {
|
|
1639
|
-
});
|
|
1640
|
-
}
|
|
1641
|
-
throw error;
|
|
1642
|
-
} finally {
|
|
1643
|
-
if (pendingName) this.releasePendingName(pendingName);
|
|
1644
|
-
if (!this.closed) {
|
|
1645
|
-
this.pendingItems -= 1;
|
|
1646
|
-
if (this.pendingItems === 0) {
|
|
1647
|
-
this.resolvePendingDrainWaiters();
|
|
1648
|
-
}
|
|
1649
|
-
}
|
|
1650
|
-
}
|
|
1651
|
-
}
|
|
1652
|
-
// Validate one block against the store. Ordinary ACP content passes through
|
|
1653
|
-
// untouched, matching `assertReferences`.
|
|
1654
|
-
assertReference(block) {
|
|
1655
|
-
if (!block || typeof block !== "object" || Array.isArray(block) || !("attachmentId" in block)) {
|
|
1656
|
-
return;
|
|
1657
|
-
}
|
|
1658
|
-
if (!isSessionAttachmentReference(block)) {
|
|
1659
|
-
throw new SessionAttachmentReferenceError(
|
|
1660
|
-
"Invalid session attachment reference",
|
|
1661
|
-
"invalid_session_attachment_reference"
|
|
1662
|
-
);
|
|
1663
|
-
}
|
|
1664
|
-
this.assertStored(block);
|
|
1665
|
-
}
|
|
1666
|
-
assertReferences(content) {
|
|
1667
|
-
const seenIds = /* @__PURE__ */ new Set();
|
|
1668
|
-
for (const block of content) {
|
|
1669
|
-
if (!block || typeof block !== "object" || Array.isArray(block) || !("attachmentId" in block)) {
|
|
1670
|
-
continue;
|
|
1671
|
-
}
|
|
1672
|
-
if (!isSessionAttachmentReference(block)) {
|
|
1673
|
-
throw new SessionAttachmentReferenceError(
|
|
1674
|
-
"Invalid session attachment reference",
|
|
1675
|
-
"invalid_session_attachment_reference"
|
|
1676
|
-
);
|
|
1677
|
-
}
|
|
1678
|
-
const id = block.attachmentId;
|
|
1679
|
-
if (seenIds.has(id)) {
|
|
1680
|
-
throw new SessionAttachmentReferenceError(
|
|
1681
|
-
`Session attachment referenced more than once: ${id}`,
|
|
1682
|
-
"invalid_session_attachment_reference"
|
|
1683
|
-
);
|
|
1684
|
-
}
|
|
1685
|
-
seenIds.add(id);
|
|
1686
|
-
this.assertStored(block);
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
async resolveContent(content, memo) {
|
|
1690
|
-
const pendingById = memo ?? /* @__PURE__ */ new Map();
|
|
1691
|
-
return await Promise.all(
|
|
1692
|
-
content.map(async (block) => {
|
|
1693
|
-
if (!isSessionAttachmentReference(block)) return block;
|
|
1694
|
-
const id = block.attachmentId;
|
|
1695
|
-
let pending = pendingById.get(id);
|
|
1696
|
-
if (!pending) {
|
|
1697
|
-
const created = this.resolve(block);
|
|
1698
|
-
pendingById.set(id, created);
|
|
1699
|
-
void created.catch(() => {
|
|
1700
|
-
if (pendingById.get(id) === created) {
|
|
1701
|
-
pendingById.delete(id);
|
|
1702
|
-
}
|
|
1703
|
-
});
|
|
1704
|
-
pending = created;
|
|
1705
|
-
}
|
|
1706
|
-
return await pending;
|
|
1707
|
-
})
|
|
1708
|
-
);
|
|
1709
|
-
}
|
|
1710
|
-
// Per-block variant of `resolveContent` for degrade paths: one unresolvable
|
|
1711
|
-
// reference drops only itself, keeping the sibling blocks a wholesale
|
|
1712
|
-
// fallback would discard. Other errors still propagate.
|
|
1713
|
-
async resolveContentDegrading(content, memo) {
|
|
1714
|
-
const retainedBlocks = [];
|
|
1715
|
-
const resolvedBlocks = [];
|
|
1716
|
-
let degraded = 0;
|
|
1717
|
-
for (const block of content) {
|
|
1718
|
-
if (!isSessionAttachmentReference(block)) {
|
|
1719
|
-
retainedBlocks.push(block);
|
|
1720
|
-
resolvedBlocks.push(block);
|
|
1721
|
-
continue;
|
|
1722
|
-
}
|
|
1723
|
-
try {
|
|
1724
|
-
const [resolved] = await this.resolveContent([block], memo);
|
|
1725
|
-
if (resolved) resolvedBlocks.push(resolved);
|
|
1726
|
-
retainedBlocks.push(block);
|
|
1727
|
-
} catch (error) {
|
|
1728
|
-
if (!(error instanceof SessionAttachmentReferenceError)) throw error;
|
|
1729
|
-
degraded += 1;
|
|
1730
|
-
}
|
|
1731
|
-
}
|
|
1732
|
-
return { retainedBlocks, resolvedBlocks, degraded };
|
|
1733
|
-
}
|
|
1734
|
-
async read(attachmentId) {
|
|
1735
|
-
const name = safeAttachmentName(attachmentId);
|
|
1736
|
-
if (!name || name !== attachmentId) return void 0;
|
|
1737
|
-
const filePath = path.join(await this.directory(), name);
|
|
1738
|
-
try {
|
|
1739
|
-
return {
|
|
1740
|
-
data: await fs.readFile(filePath),
|
|
1741
|
-
mimeType: mimeTypeForName(name)
|
|
1742
|
-
};
|
|
1743
|
-
} catch (error) {
|
|
1744
|
-
if (error.code === "ENOENT") {
|
|
1745
|
-
return void 0;
|
|
1746
|
-
}
|
|
1747
|
-
throw error;
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1750
|
-
async copyFrom(source) {
|
|
1751
|
-
if (source === this) return;
|
|
1752
|
-
if (this.closed || this.closing) {
|
|
1753
|
-
throw new Error("Session attachment store is closed");
|
|
1754
|
-
}
|
|
1755
|
-
if (this.copying) throw new Error("Session attachments are being copied");
|
|
1756
|
-
if (source.closed || source.closing) {
|
|
1757
|
-
throw new Error("Session attachment store is closed");
|
|
1758
|
-
}
|
|
1759
|
-
if (source.copying) {
|
|
1760
|
-
throw new Error("Session attachments are being copied");
|
|
1761
|
-
}
|
|
1762
|
-
this.copying = true;
|
|
1763
|
-
source.copying = true;
|
|
1764
|
-
try {
|
|
1765
|
-
if (this.pendingItems > 0) {
|
|
1766
|
-
await new Promise(
|
|
1767
|
-
(resolve2) => this.pendingDrainWaiters.push(resolve2)
|
|
1768
|
-
);
|
|
1769
|
-
}
|
|
1770
|
-
if (source.pendingItems > 0) {
|
|
1771
|
-
await new Promise(
|
|
1772
|
-
(resolve2) => source.pendingDrainWaiters.push(resolve2)
|
|
1773
|
-
);
|
|
1774
|
-
}
|
|
1775
|
-
if (this.closed) throw new Error("Session attachment store is closed");
|
|
1776
|
-
const sourceDirectory = source.persistentDirectory ?? source.activeDirectory;
|
|
1777
|
-
if (!sourceDirectory) return;
|
|
1778
|
-
let entries;
|
|
1779
|
-
try {
|
|
1780
|
-
entries = await fs.readdir(sourceDirectory, { withFileTypes: true });
|
|
1781
|
-
} catch (error) {
|
|
1782
|
-
if (error.code === "ENOENT") return;
|
|
1783
|
-
throw error;
|
|
1784
|
-
}
|
|
1785
|
-
const targetDirectory = await this.directory();
|
|
1786
|
-
await Promise.all(
|
|
1787
|
-
entries.filter(
|
|
1788
|
-
(entry) => entry.isFile() && !source.pendingNames.has(entry.name)
|
|
1789
|
-
).map(async (entry) => {
|
|
1790
|
-
const sourcePath = path.join(sourceDirectory, entry.name);
|
|
1791
|
-
try {
|
|
1792
|
-
await fs.copyFile(
|
|
1793
|
-
sourcePath,
|
|
1794
|
-
path.join(targetDirectory, entry.name)
|
|
1795
|
-
);
|
|
1796
|
-
} catch (error) {
|
|
1797
|
-
if (error.code === "ENOENT") {
|
|
1798
|
-
try {
|
|
1799
|
-
await fs.stat(sourcePath);
|
|
1800
|
-
} catch (sourceError) {
|
|
1801
|
-
if (sourceError.code === "ENOENT") {
|
|
1802
|
-
return;
|
|
1803
|
-
}
|
|
1804
|
-
}
|
|
1805
|
-
}
|
|
1806
|
-
throw error;
|
|
1807
|
-
}
|
|
1808
|
-
})
|
|
1809
|
-
);
|
|
1810
|
-
} finally {
|
|
1811
|
-
source.copying = false;
|
|
1812
|
-
this.copying = false;
|
|
1813
|
-
source.resolveCopyDrainWaiters();
|
|
1814
|
-
this.resolveCopyDrainWaiters();
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
async remove(attachmentId) {
|
|
1818
|
-
const name = safeAttachmentName(attachmentId);
|
|
1819
|
-
if (!name || name !== attachmentId || this.copying || this.pendingNames.has(name)) {
|
|
1820
|
-
return false;
|
|
1821
|
-
}
|
|
1822
|
-
const directory = await this.directory();
|
|
1823
|
-
const filePath = path.join(directory, name);
|
|
1824
|
-
try {
|
|
1825
|
-
await fs.unlink(filePath);
|
|
1826
|
-
return true;
|
|
1827
|
-
} catch (error) {
|
|
1828
|
-
if (error.code === "ENOENT") {
|
|
1829
|
-
return false;
|
|
1830
|
-
}
|
|
1831
|
-
throw error;
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
|
-
async close() {
|
|
1835
|
-
this.closing = true;
|
|
1836
|
-
await this.waitForCopy();
|
|
1837
|
-
if (this.closed) return;
|
|
1838
|
-
this.closed = true;
|
|
1839
|
-
this.pendingItems = 0;
|
|
1840
|
-
this.pendingNames.clear();
|
|
1841
|
-
this.resolvePendingDrainWaiters();
|
|
1842
|
-
if (this.persistentDirectory || !this.directoryPromise) return;
|
|
1843
|
-
const directory = await this.directoryPromise.catch(() => void 0);
|
|
1844
|
-
if (!directory) return;
|
|
1845
|
-
await fs.rm(directory, { recursive: true, force: true });
|
|
1846
|
-
}
|
|
1847
|
-
async delete(options = {}) {
|
|
1848
|
-
options.assertCanCommit?.();
|
|
1849
|
-
this.closing = true;
|
|
1850
|
-
await this.waitForCopy();
|
|
1851
|
-
options.assertCanCommit?.();
|
|
1852
|
-
if (!this.closed) {
|
|
1853
|
-
this.closed = true;
|
|
1854
|
-
this.pendingItems = 0;
|
|
1855
|
-
this.pendingNames.clear();
|
|
1856
|
-
this.resolvePendingDrainWaiters();
|
|
1857
|
-
}
|
|
1858
|
-
const directory = this.persistentDirectory ?? await this.directoryPromise?.catch(() => void 0);
|
|
1859
|
-
if (directory) {
|
|
1860
|
-
options.assertCanCommit?.();
|
|
1861
|
-
const tombstone = path.join(
|
|
1862
|
-
path.dirname(directory),
|
|
1863
|
-
`.${path.basename(directory)}.deleting-${randomUUID()}`
|
|
1864
|
-
);
|
|
1865
|
-
try {
|
|
1866
|
-
await fs.rename(directory, tombstone);
|
|
1867
|
-
} catch (error) {
|
|
1868
|
-
if (error.code === "ENOENT") return;
|
|
1869
|
-
throw error;
|
|
1870
|
-
}
|
|
1871
|
-
await fs.rm(tombstone, { recursive: true, force: true });
|
|
1872
|
-
}
|
|
1873
|
-
}
|
|
1874
|
-
assertStored(reference) {
|
|
1875
|
-
const id = reference.attachmentId;
|
|
1876
|
-
const name = safeAttachmentName(id);
|
|
1877
|
-
let size;
|
|
1878
|
-
const directory = this.persistentDirectory ?? this.activeDirectory;
|
|
1879
|
-
if (name && name === id && directory) {
|
|
1880
|
-
try {
|
|
1881
|
-
size = statSync(path.join(directory, name)).size;
|
|
1882
|
-
} catch {
|
|
1883
|
-
size = void 0;
|
|
1884
|
-
}
|
|
1885
|
-
}
|
|
1886
|
-
const storedMimeType = name ? mimeTypeForName(name) : void 0;
|
|
1887
|
-
const storedType = storedMimeType ? isSupportedImageMimeType(storedMimeType) ? "image" : "resource" : void 0;
|
|
1888
|
-
if (size !== reference.size || storedMimeType !== reference.mimeType || storedType !== reference.type) {
|
|
1889
|
-
throw new SessionAttachmentReferenceError(
|
|
1890
|
-
`Unknown or unavailable session attachment: ${id}`,
|
|
1891
|
-
"session_attachment_gone"
|
|
1892
|
-
);
|
|
1893
|
-
}
|
|
1894
|
-
}
|
|
1895
|
-
releasePendingName(name) {
|
|
1896
|
-
const count = this.pendingNames.get(name) ?? 0;
|
|
1897
|
-
if (count <= 1) this.pendingNames.delete(name);
|
|
1898
|
-
else this.pendingNames.set(name, count - 1);
|
|
1899
|
-
}
|
|
1900
|
-
reservePendingName(name) {
|
|
1901
|
-
this.pendingNames.set(name, (this.pendingNames.get(name) ?? 0) + 1);
|
|
1902
|
-
}
|
|
1903
|
-
async waitForCopy() {
|
|
1904
|
-
while (this.copying) {
|
|
1905
|
-
await new Promise((resolve2) => this.copyDrainWaiters.push(resolve2));
|
|
1906
|
-
}
|
|
1907
|
-
}
|
|
1908
|
-
resolveCopyDrainWaiters() {
|
|
1909
|
-
for (const resolve2 of this.copyDrainWaiters.splice(0)) resolve2();
|
|
1910
|
-
}
|
|
1911
|
-
resolvePendingDrainWaiters() {
|
|
1912
|
-
for (const resolve2 of this.pendingDrainWaiters.splice(0)) resolve2();
|
|
1913
|
-
}
|
|
1914
|
-
async resolve(reference) {
|
|
1915
|
-
const id = reference.attachmentId;
|
|
1916
|
-
const attachment = await this.read(id);
|
|
1917
|
-
if (!attachment) {
|
|
1918
|
-
throw new SessionAttachmentReferenceError(
|
|
1919
|
-
`Unknown or unavailable session attachment: ${id}`,
|
|
1920
|
-
"session_attachment_gone"
|
|
1921
|
-
);
|
|
1922
|
-
}
|
|
1923
|
-
if (reference.type === "resource") {
|
|
1924
|
-
const resource = {
|
|
1925
|
-
uri: `attachment:///${encodeURIComponent(reference.attachmentId)}`,
|
|
1926
|
-
mimeType: attachment.mimeType,
|
|
1927
|
-
...isTextAttachment(attachment.data, attachment.mimeType) ? { text: attachment.data.toString("utf8") } : { blob: attachment.data.toString("base64") }
|
|
1928
|
-
};
|
|
1929
|
-
return {
|
|
1930
|
-
type: "resource",
|
|
1931
|
-
resource
|
|
1932
|
-
};
|
|
1933
|
-
}
|
|
1934
|
-
return {
|
|
1935
|
-
type: "image",
|
|
1936
|
-
data: attachment.data.toString("base64"),
|
|
1937
|
-
mimeType: attachment.mimeType
|
|
1938
|
-
};
|
|
1939
|
-
}
|
|
1940
|
-
async directory() {
|
|
1941
|
-
if (!this.directoryPromise) {
|
|
1942
|
-
const pending = this.persistentDirectory ? fs.mkdir(this.persistentDirectory, {
|
|
1943
|
-
recursive: true,
|
|
1944
|
-
mode: 448
|
|
1945
|
-
}).then(() => this.persistentDirectory) : this.directoryRoot ? fs.mkdir(this.directoryRoot, { recursive: true, mode: 448 }).then(
|
|
1946
|
-
() => fs.mkdtemp(
|
|
1947
|
-
path.join(this.directoryRoot, "session-attachment-")
|
|
1948
|
-
)
|
|
1949
|
-
) : fs.mkdtemp(path.join(tmpdir(), "qwen-session-attachment-"));
|
|
1950
|
-
const directoryPromise = pending.then((directory) => {
|
|
1951
|
-
this.activeDirectory = directory;
|
|
1952
|
-
return directory;
|
|
1953
|
-
});
|
|
1954
|
-
this.directoryPromise = directoryPromise;
|
|
1955
|
-
void directoryPromise.catch(() => {
|
|
1956
|
-
if (this.directoryPromise === directoryPromise)
|
|
1957
|
-
this.directoryPromise = void 0;
|
|
1958
|
-
});
|
|
1959
|
-
}
|
|
1960
|
-
return await this.directoryPromise;
|
|
1961
|
-
}
|
|
1962
|
-
};
|
|
1963
|
-
|
|
1964
|
-
// packages/acp-bridge/src/internal/stderrLine.ts
|
|
1965
|
-
init_esbuild_shims();
|
|
1966
|
-
function writeStderrLine(message) {
|
|
1967
|
-
process.stderr.write(message.endsWith("\n") ? message : `${message}
|
|
1968
|
-
`);
|
|
1969
|
-
}
|
|
1970
|
-
__name(writeStderrLine, "writeStderrLine");
|
|
1971
|
-
|
|
1972
|
-
// packages/acp-bridge/src/bridgeClient.ts
|
|
1973
|
-
init_esbuild_shims();
|
|
1974
|
-
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
1975
|
-
import { promises as fs2 } from "node:fs";
|
|
1976
|
-
import * as path2 from "node:path";
|
|
1977
|
-
|
|
1978
|
-
// packages/acp-bridge/src/daemonEventTypes.ts
|
|
1979
|
-
init_esbuild_shims();
|
|
1980
|
-
var MID_TURN_MESSAGE_INJECTED_EVENT = "mid_turn_message_injected";
|
|
1981
|
-
|
|
1982
|
-
// packages/acp-bridge/src/permissionMediator.ts
|
|
1983
|
-
init_esbuild_shims();
|
|
1984
|
-
|
|
1985
|
-
// packages/acp-bridge/src/permission.ts
|
|
1986
|
-
init_esbuild_shims();
|
|
1987
|
-
|
|
1988
|
-
// packages/acp-bridge/src/permissionMediator.ts
|
|
1989
|
-
var CANCEL_VOTE_SENTINEL = "__cancelled__";
|
|
1990
|
-
var MAX_RESOLVED_PERMISSION_RECORDS = 512;
|
|
1991
|
-
function stringifyError(err) {
|
|
1992
|
-
try {
|
|
1993
|
-
if (err instanceof Error) return `${err.name}: ${err.message}`;
|
|
1994
|
-
return String(err);
|
|
1995
|
-
} catch {
|
|
1996
|
-
return "[unstringifiable error]";
|
|
1997
|
-
}
|
|
1998
|
-
}
|
|
1999
|
-
__name(stringifyError, "stringifyError");
|
|
2000
|
-
function createNoOpPermissionAuditPublisher() {
|
|
2001
|
-
return {
|
|
2002
|
-
recordRequested() {
|
|
2003
|
-
},
|
|
2004
|
-
recordVoted() {
|
|
2005
|
-
},
|
|
2006
|
-
recordForbidden() {
|
|
2007
|
-
},
|
|
2008
|
-
recordResolved() {
|
|
2009
|
-
},
|
|
2010
|
-
recordTimeout() {
|
|
2011
|
-
}
|
|
2012
|
-
};
|
|
2013
|
-
}
|
|
2014
|
-
__name(createNoOpPermissionAuditPublisher, "createNoOpPermissionAuditPublisher");
|
|
2015
|
-
var MultiClientPermissionMediator = class {
|
|
2016
|
-
static {
|
|
2017
|
-
__name(this, "MultiClientPermissionMediator");
|
|
2018
|
-
}
|
|
2019
|
-
policy;
|
|
2020
|
-
deps;
|
|
2021
|
-
pending = /* @__PURE__ */ new Map();
|
|
2022
|
-
resolved = /* @__PURE__ */ new Map();
|
|
2023
|
-
resolvedOrder = [];
|
|
2024
|
-
/**
|
|
2025
|
-
* Dedup flag for the
|
|
2026
|
-
* unanimity-required stderr breadcrumb. Without this, every
|
|
2027
|
-
* permission request on a 2-client consensus session would emit
|
|
2028
|
-
* an identical line (the unanimity condition is the NORMAL
|
|
2029
|
-
* operating mode for M=2, not a rare edge); a busy session with
|
|
2030
|
-
* many tool calls would produce dozens of duplicate stderr lines
|
|
2031
|
-
* within seconds. One emit per mediator (= per bridge/runtime lifetime
|
|
2032
|
-
* since each bridge constructs one) is enough to make the
|
|
2033
|
-
* configuration visible without spam.
|
|
2034
|
-
*/
|
|
2035
|
-
unanimityBreadcrumbEmitted = false;
|
|
2036
|
-
constructor(policy, deps) {
|
|
2037
|
-
this.policy = policy;
|
|
2038
|
-
this.deps = deps;
|
|
2039
|
-
}
|
|
2040
|
-
/**
|
|
2041
|
-
* Register a fresh permission request from the agent.
|
|
2042
|
-
*
|
|
2043
|
-
* **Promise contract — once the Promise is returned, it never
|
|
2044
|
-
* rejects.** All runtime failure modes (timeout, session closure,
|
|
2045
|
-
* voter cancel, emit/audit publisher exceptions) are encoded as
|
|
2046
|
-
* `PermissionResolution { kind:'cancelled', reason:... }`.
|
|
2047
|
-
* Consumers can `await` the returned Promise and forward the
|
|
2048
|
-
* result without a `.catch()` block.
|
|
2049
|
-
*
|
|
2050
|
-
* **Synchronous-throw exception**:
|
|
2051
|
-
* when the agent's `allowedOptionIds` contains the
|
|
2052
|
-
* cancel-vote sentinel string, this method throws
|
|
2053
|
-
* `CancelSentinelCollisionError` synchronously BEFORE constructing
|
|
2054
|
-
* the Promise. The synchronous shape is intentional — a
|
|
2055
|
-
* never-settling Promise alongside a thrown error would be worse
|
|
2056
|
-
* than a clean fail-fast — but callers must wrap this method
|
|
2057
|
-
* itself in `try/catch` (or call it from an `async` function so
|
|
2058
|
-
* the throw bubbles via the function's own Promise machinery).
|
|
2059
|
-
* `bridgeClient.ts` currently has its own pre-check at the bridge
|
|
2060
|
-
* layer; embedded callers must do the same. See `@throws` below.
|
|
2061
|
-
*
|
|
2062
|
-
* **Synchronous-register invariant**: pending entry, audit
|
|
2063
|
-
* record, and timer setup all happen inside the Promise executor
|
|
2064
|
-
* without `await`. The bridge's `publish → mediator.request → await`
|
|
2065
|
-
* sequence relies on this — a `forgetSession` between publish and
|
|
2066
|
-
* await would otherwise miss the new pending and leak it until
|
|
2067
|
-
* timeout.
|
|
2068
|
-
*
|
|
2069
|
-
* @throws `CancelSentinelCollisionError` SYNCHRONOUSLY (not as a
|
|
2070
|
-
* Promise rejection) if `record.allowedOptionIds` contains the
|
|
2071
|
-
* cancel-vote sentinel string. This is a contract violation
|
|
2072
|
-
* between agent and daemon and fails loudly at issue time
|
|
2073
|
-
* rather than silently miscounting votes downstream. Callers
|
|
2074
|
-
* inside an `async` function get the thrown error through the
|
|
2075
|
-
* function's own Promise; synchronous callers must use
|
|
2076
|
-
* `try/catch`.
|
|
2077
|
-
*/
|
|
2078
|
-
request(record, timeoutMs) {
|
|
2079
|
-
if (record.allowedOptionIds.has(CANCEL_VOTE_SENTINEL)) {
|
|
2080
|
-
throw new CancelSentinelCollisionError(
|
|
2081
|
-
record.requestId,
|
|
2082
|
-
CANCEL_VOTE_SENTINEL
|
|
2083
|
-
);
|
|
2084
|
-
}
|
|
2085
|
-
return new Promise((resolve2) => {
|
|
2086
|
-
const policy = this.policy;
|
|
2087
|
-
const votersAtIssue = this.deps.votersForSession(record.sessionId);
|
|
2088
|
-
const pending = {
|
|
2089
|
-
requestId: record.requestId,
|
|
2090
|
-
sessionId: record.sessionId,
|
|
2091
|
-
promptId: record.promptId,
|
|
2092
|
-
policy,
|
|
2093
|
-
originatorClientId: record.originatorClientId,
|
|
2094
|
-
allowedOptionIds: record.allowedOptionIds,
|
|
2095
|
-
issuedAtMs: record.issuedAtMs,
|
|
2096
|
-
timeoutMs,
|
|
2097
|
-
resolve: resolve2,
|
|
2098
|
-
tallies: /* @__PURE__ */ new Map(),
|
|
2099
|
-
votersAtIssue,
|
|
2100
|
-
timer: void 0,
|
|
2101
|
-
consensusQuorumCapNoted: false
|
|
2102
|
-
};
|
|
2103
|
-
this.pending.set(record.requestId, pending);
|
|
2104
|
-
this.safeAudit(
|
|
2105
|
-
() => this.deps.audit.recordRequested(record, policy, votersAtIssue)
|
|
2106
|
-
);
|
|
2107
|
-
const unresolvedWithoutDecision = timeoutMs > 0 ? `voter cancellation, session cancellation, or permissionTimeoutMs (${timeoutMs}ms)` : "voter or session cancellation because permissionTimeoutMs is disabled";
|
|
2108
|
-
if (policy === "consensus" && votersAtIssue.size === 0) {
|
|
2109
|
-
try {
|
|
2110
|
-
process.stderr.write(
|
|
2111
|
-
`permissionMediator: consensus request ${record.requestId} for session ${record.sessionId} issued with empty votersAtIssue; can only resolve via ${unresolvedWithoutDecision}
|
|
2112
|
-
`
|
|
2113
|
-
);
|
|
2114
|
-
} catch {
|
|
2115
|
-
}
|
|
2116
|
-
}
|
|
2117
|
-
if (policy === "consensus" && this.deps.consensusQuorum === void 0 && votersAtIssue.size >= 2 && Math.floor(votersAtIssue.size / 2) + 1 === votersAtIssue.size && !this.unanimityBreadcrumbEmitted) {
|
|
2118
|
-
this.unanimityBreadcrumbEmitted = true;
|
|
2119
|
-
try {
|
|
2120
|
-
process.stderr.write(
|
|
2121
|
-
`permissionMediator: consensus request ${record.requestId} for session ${record.sessionId} requires unanimity (votersAtIssue.size=${votersAtIssue.size}, default quorum=floor(M/2)+1=${votersAtIssue.size}); split votes will only resolve via ${unresolvedWithoutDecision}. This breadcrumb fires once per mediator lifetime; subsequent unanimity-required requests are silent.
|
|
2122
|
-
`
|
|
2123
|
-
);
|
|
2124
|
-
} catch {
|
|
2125
|
-
}
|
|
2126
|
-
}
|
|
2127
|
-
if (timeoutMs > 0) {
|
|
2128
|
-
pending.timer = setTimeout(() => {
|
|
2129
|
-
if (this.pending.get(record.requestId) !== pending) return;
|
|
2130
|
-
const firedAtMs = this.deps.now();
|
|
2131
|
-
try {
|
|
2132
|
-
process.stderr.write(
|
|
2133
|
-
`qwen serve: permission ${record.requestId} (session ${record.sessionId}) timed out after ${timeoutMs}ms
|
|
2134
|
-
`
|
|
2135
|
-
);
|
|
2136
|
-
} catch {
|
|
2137
|
-
}
|
|
2138
|
-
this.safeAudit(() => this.deps.audit.recordTimeout(record));
|
|
2139
|
-
this.resolveEntry(
|
|
2140
|
-
pending,
|
|
2141
|
-
{ kind: "cancelled", reason: "timeout" },
|
|
2142
|
-
{
|
|
2143
|
-
type: "timeout",
|
|
2144
|
-
issuedAtMs: pending.issuedAtMs,
|
|
2145
|
-
timeoutMs: pending.timeoutMs,
|
|
2146
|
-
firedAtMs
|
|
2147
|
-
},
|
|
2148
|
-
void 0
|
|
2149
|
-
);
|
|
2150
|
-
}, timeoutMs);
|
|
2151
|
-
const t = pending.timer;
|
|
2152
|
-
if (t && typeof t === "object" && "unref" in t) {
|
|
2153
|
-
t.unref();
|
|
2154
|
-
}
|
|
2155
|
-
}
|
|
2156
|
-
});
|
|
2157
|
-
}
|
|
2158
|
-
vote(vote) {
|
|
2159
|
-
const pending = this.pending.get(vote.requestId);
|
|
2160
|
-
if (!pending) {
|
|
2161
|
-
const prior = this.resolved.get(vote.requestId);
|
|
2162
|
-
if (prior && prior.sessionId === vote.sessionId) {
|
|
2163
|
-
const optionId = prior.resolution.kind === "option" ? prior.resolution.optionId : CANCEL_VOTE_SENTINEL;
|
|
2164
|
-
this.safeEmit(prior.sessionId, {
|
|
2165
|
-
type: "permission_already_resolved",
|
|
2166
|
-
...prior.promptId ? { promptId: prior.promptId } : {},
|
|
2167
|
-
data: {
|
|
2168
|
-
requestId: prior.requestId,
|
|
2169
|
-
sessionId: prior.sessionId,
|
|
2170
|
-
outcome: this.toAcpOutcome(prior.resolution)
|
|
2171
|
-
}
|
|
2172
|
-
});
|
|
2173
|
-
return { kind: "already_resolved", resolvedOptionId: optionId };
|
|
2174
|
-
}
|
|
2175
|
-
return { kind: "unknown_request" };
|
|
2176
|
-
}
|
|
2177
|
-
if (pending.sessionId !== vote.sessionId) {
|
|
2178
|
-
return { kind: "unknown_request" };
|
|
2179
|
-
}
|
|
2180
|
-
if (vote.optionId === CANCEL_VOTE_SENTINEL) {
|
|
2181
|
-
const outcome = {
|
|
2182
|
-
kind: "resolved",
|
|
2183
|
-
resolvedOptionId: CANCEL_VOTE_SENTINEL
|
|
2184
|
-
};
|
|
2185
|
-
this.safeAudit(
|
|
2186
|
-
() => this.deps.audit.recordVoted(this.toRecord(pending), vote, outcome)
|
|
2187
|
-
);
|
|
2188
|
-
this.resolveEntry(
|
|
2189
|
-
pending,
|
|
2190
|
-
{ kind: "cancelled", reason: "agent_cancelled" },
|
|
2191
|
-
{
|
|
2192
|
-
type: "voter-cancelled",
|
|
2193
|
-
resolverClientId: vote.clientId
|
|
2194
|
-
},
|
|
2195
|
-
vote.clientId
|
|
2196
|
-
);
|
|
2197
|
-
return outcome;
|
|
2198
|
-
}
|
|
2199
|
-
if (!pending.allowedOptionIds.has(vote.optionId)) {
|
|
2200
|
-
throw new InvalidPermissionOptionError(vote.requestId, vote.optionId);
|
|
2201
|
-
}
|
|
2202
|
-
switch (pending.policy) {
|
|
2203
|
-
case "first-responder":
|
|
2204
|
-
return this.voteFirstResponder(pending, vote);
|
|
2205
|
-
case "designated":
|
|
2206
|
-
return this.voteDesignated(pending, vote);
|
|
2207
|
-
case "consensus":
|
|
2208
|
-
return this.voteConsensus(pending, vote);
|
|
2209
|
-
case "local-only":
|
|
2210
|
-
return this.voteLocalOnly(pending, vote);
|
|
2211
|
-
default: {
|
|
2212
|
-
const _exhaustive = pending.policy;
|
|
2213
|
-
void _exhaustive;
|
|
2214
|
-
throw new Error(`Unknown permission policy "${pending.policy}"`);
|
|
2215
|
-
}
|
|
2216
|
-
}
|
|
2217
|
-
}
|
|
2218
|
-
forgetSession(sessionId) {
|
|
2219
|
-
const requestIds = [];
|
|
2220
|
-
for (const [id, pending] of this.pending) {
|
|
2221
|
-
if (pending.sessionId === sessionId) requestIds.push(id);
|
|
2222
|
-
}
|
|
2223
|
-
for (const id of requestIds) {
|
|
2224
|
-
const pending = this.pending.get(id);
|
|
2225
|
-
if (!pending) continue;
|
|
2226
|
-
this.resolveEntry(
|
|
2227
|
-
pending,
|
|
2228
|
-
{ kind: "cancelled", reason: "session_closed" },
|
|
2229
|
-
{ type: "session-closed" },
|
|
2230
|
-
void 0
|
|
2231
|
-
);
|
|
2232
|
-
}
|
|
2233
|
-
}
|
|
2234
|
-
/**
|
|
2235
|
-
* Lookup the sessionId for a given requestId. Used by the legacy
|
|
2236
|
-
* `bridge.respondToPermission(requestId, ...)` route which doesn't
|
|
2237
|
-
* carry a sessionId in the URL. NOT part of the
|
|
2238
|
-
* `PermissionMediator` interface contract — bridge holds the
|
|
2239
|
-
* concrete class reference and calls this directly.
|
|
2240
|
-
*/
|
|
2241
|
-
peekSessionFor(requestId) {
|
|
2242
|
-
const pending = this.pending.get(requestId);
|
|
2243
|
-
if (pending) return pending.sessionId;
|
|
2244
|
-
const prior = this.resolved.get(requestId);
|
|
2245
|
-
return prior?.sessionId;
|
|
2246
|
-
}
|
|
2247
|
-
/**
|
|
2248
|
-
* Daemon-wide in-flight pending count for diagnostics. The bridge
|
|
2249
|
-
* exposes this through its `pendingPermissionCount` getter so
|
|
2250
|
-
* operators can spot stuck FIFOs without reaching into mediator
|
|
2251
|
-
* internals. NOT part of the `PermissionMediator` interface
|
|
2252
|
-
* contract.
|
|
2253
|
-
*/
|
|
2254
|
-
get pendingCount() {
|
|
2255
|
-
return this.pending.size;
|
|
2256
|
-
}
|
|
2257
|
-
// ===========================================================
|
|
2258
|
-
// Per-policy vote handlers
|
|
2259
|
-
// ===========================================================
|
|
2260
|
-
voteFirstResponder(pending, vote) {
|
|
2261
|
-
return this.resolveWithVote(pending, vote, {
|
|
2262
|
-
type: "first-responder",
|
|
2263
|
-
resolverClientId: vote.clientId
|
|
2264
|
-
});
|
|
2265
|
-
}
|
|
2266
|
-
voteDesignated(pending, vote) {
|
|
2267
|
-
if (pending.originatorClientId === void 0) {
|
|
2268
|
-
return this.voteFirstResponder(pending, vote);
|
|
2269
|
-
}
|
|
2270
|
-
if (vote.clientId !== pending.originatorClientId) {
|
|
2271
|
-
return this.rejectForbidden(
|
|
2272
|
-
pending,
|
|
2273
|
-
vote,
|
|
2274
|
-
"designated_mismatch",
|
|
2275
|
-
"designated_mismatch (voter is not the prompt originator)"
|
|
2276
|
-
);
|
|
2277
|
-
}
|
|
2278
|
-
return this.resolveWithVote(pending, vote, {
|
|
2279
|
-
type: "designated-originator",
|
|
2280
|
-
originatorClientId: pending.originatorClientId
|
|
2281
|
-
});
|
|
2282
|
-
}
|
|
2283
|
-
voteConsensus(pending, vote) {
|
|
2284
|
-
if (vote.clientId === void 0 || !pending.votersAtIssue.has(vote.clientId)) {
|
|
2285
|
-
return this.rejectForbidden(
|
|
2286
|
-
pending,
|
|
2287
|
-
vote,
|
|
2288
|
-
"designated_mismatch",
|
|
2289
|
-
"designated_mismatch (voter not in consensus votersAtIssue snapshot)"
|
|
2290
|
-
);
|
|
2291
|
-
}
|
|
2292
|
-
for (const [originalOptionId, set] of pending.tallies.entries()) {
|
|
2293
|
-
if (vote.clientId !== void 0 && set.has(vote.clientId)) {
|
|
2294
|
-
const outcome2 = {
|
|
2295
|
-
kind: "recorded",
|
|
2296
|
-
votesNeeded: this.votesNeededFor(pending)
|
|
2297
|
-
};
|
|
2298
|
-
this.safeAudit(
|
|
2299
|
-
() => this.deps.audit.recordVoted(
|
|
2300
|
-
this.toRecord(pending),
|
|
2301
|
-
{ ...vote, optionId: originalOptionId },
|
|
2302
|
-
outcome2
|
|
2303
|
-
)
|
|
2304
|
-
);
|
|
2305
|
-
return outcome2;
|
|
2306
|
-
}
|
|
2307
|
-
}
|
|
2308
|
-
let bucket = pending.tallies.get(vote.optionId);
|
|
2309
|
-
if (!bucket) {
|
|
2310
|
-
bucket = /* @__PURE__ */ new Set();
|
|
2311
|
-
pending.tallies.set(vote.optionId, bucket);
|
|
2312
|
-
}
|
|
2313
|
-
bucket.add(vote.clientId);
|
|
2314
|
-
const quorum = this.consensusQuorumFor(pending);
|
|
2315
|
-
if (bucket.size >= quorum) {
|
|
2316
|
-
return this.resolveWithVote(pending, vote, {
|
|
2317
|
-
type: "consensus-quorum",
|
|
2318
|
-
resolvedOptionId: vote.optionId,
|
|
2319
|
-
quorum,
|
|
2320
|
-
tally: bucket.size
|
|
2321
|
-
});
|
|
2322
|
-
}
|
|
2323
|
-
const outcome = {
|
|
2324
|
-
kind: "recorded",
|
|
2325
|
-
votesNeeded: this.votesNeededFor(pending)
|
|
2326
|
-
};
|
|
2327
|
-
this.safeAudit(
|
|
2328
|
-
() => this.deps.audit.recordVoted(this.toRecord(pending), vote, outcome)
|
|
2329
|
-
);
|
|
2330
|
-
this.safeEmit(pending.sessionId, {
|
|
2331
|
-
type: "permission_partial_vote",
|
|
2332
|
-
...pending.promptId ? { promptId: pending.promptId } : {},
|
|
2333
|
-
data: {
|
|
2334
|
-
requestId: pending.requestId,
|
|
2335
|
-
sessionId: pending.sessionId,
|
|
2336
|
-
votesReceived: this.totalTalliedFor(pending),
|
|
2337
|
-
votesNeeded: outcome.votesNeeded,
|
|
2338
|
-
quorum,
|
|
2339
|
-
optionTallies: this.optionTalliesFor(pending)
|
|
2340
|
-
},
|
|
2341
|
-
...pending.originatorClientId !== void 0 ? { originatorClientId: pending.originatorClientId } : {}
|
|
2342
|
-
});
|
|
2343
|
-
return outcome;
|
|
2344
|
-
}
|
|
2345
|
-
/**
|
|
2346
|
-
* Vote dispatch for `local-only` policy: only `fromLoopback: true`
|
|
2347
|
-
* voters can resolve a permission.
|
|
2348
|
-
*
|
|
2349
|
-
* **Cancel-sentinel asymmetry** (cancel-sentinel note).
|
|
2350
|
-
* `vote()` recognizes the cancel sentinel BEFORE calling this
|
|
2351
|
-
* method (cross-policy escape hatch — see the
|
|
2352
|
-
* `CANCEL_VOTE_SENTINEL` JSDoc for the rationale), so a remote
|
|
2353
|
-
* voter under `local-only` CAN abort a pending permission via
|
|
2354
|
-
* `{outcome:'cancelled'}` even though they cannot RESOLVE one. The
|
|
2355
|
-
* settings-side description for `local-only` and the design doc call
|
|
2356
|
-
* out this gap explicitly. Operators who want strict-cancel-too
|
|
2357
|
-
* semantics must (a) deploy a dedicated daemon process at
|
|
2358
|
-
* loopback bind, OR (b) wait for the follow-up PR that lifts
|
|
2359
|
-
* cancel into per-policy gating; This version keeps the current
|
|
2360
|
-
* cross-policy cancel for consistency with first-responder /
|
|
2361
|
-
* designated / consensus.
|
|
2362
|
-
*/
|
|
2363
|
-
voteLocalOnly(pending, vote) {
|
|
2364
|
-
if (!vote.fromLoopback) {
|
|
2365
|
-
return this.rejectForbidden(
|
|
2366
|
-
pending,
|
|
2367
|
-
vote,
|
|
2368
|
-
"remote_not_allowed",
|
|
2369
|
-
"remote_not_allowed (local-only policy; vote not from loopback)"
|
|
2370
|
-
);
|
|
2371
|
-
}
|
|
2372
|
-
return this.resolveWithVote(pending, vote, {
|
|
2373
|
-
type: "local-only-loopback",
|
|
2374
|
-
resolverClientId: vote.clientId
|
|
2375
|
-
});
|
|
2376
|
-
}
|
|
2377
|
-
// ===========================================================
|
|
2378
|
-
// Shared vote-resolution helpers
|
|
2379
|
-
// ===========================================================
|
|
2380
|
-
resolveWithVote(pending, vote, decisionReason) {
|
|
2381
|
-
const outcome = {
|
|
2382
|
-
kind: "resolved",
|
|
2383
|
-
resolvedOptionId: vote.optionId
|
|
2384
|
-
};
|
|
2385
|
-
this.safeAudit(
|
|
2386
|
-
() => this.deps.audit.recordVoted(this.toRecord(pending), vote, outcome)
|
|
2387
|
-
);
|
|
2388
|
-
this.resolveEntry(
|
|
2389
|
-
pending,
|
|
2390
|
-
{
|
|
2391
|
-
kind: "option",
|
|
2392
|
-
optionId: vote.optionId,
|
|
2393
|
-
...vote.metadata ? { metadata: vote.metadata } : {}
|
|
2394
|
-
},
|
|
2395
|
-
decisionReason,
|
|
2396
|
-
vote.clientId
|
|
2397
|
-
);
|
|
2398
|
-
return outcome;
|
|
2399
|
-
}
|
|
2400
|
-
rejectForbidden(pending, vote, reason, stderrDetail) {
|
|
2401
|
-
this.safeAudit(
|
|
2402
|
-
() => this.deps.audit.recordForbidden(this.toRecord(pending), vote, reason)
|
|
2403
|
-
);
|
|
2404
|
-
this.safeEmit(pending.sessionId, {
|
|
2405
|
-
type: "permission_forbidden",
|
|
2406
|
-
...pending.promptId ? { promptId: pending.promptId } : {},
|
|
2407
|
-
data: {
|
|
2408
|
-
requestId: pending.requestId,
|
|
2409
|
-
sessionId: pending.sessionId,
|
|
2410
|
-
...vote.clientId !== void 0 ? { clientId: vote.clientId } : {},
|
|
2411
|
-
reason
|
|
2412
|
-
},
|
|
2413
|
-
...pending.originatorClientId !== void 0 ? { originatorClientId: pending.originatorClientId } : {}
|
|
2414
|
-
});
|
|
2415
|
-
this.writeForbiddenStderr(pending, vote, stderrDetail);
|
|
2416
|
-
return { kind: "forbidden", reason };
|
|
2417
|
-
}
|
|
2418
|
-
// ===========================================================
|
|
2419
|
-
// Consensus tally helpers
|
|
2420
|
-
// ===========================================================
|
|
2421
|
-
/**
|
|
2422
|
-
* Compute the quorum size for a `consensus` request. Default
|
|
2423
|
-
* `floor(M/2) + 1` of `votersAtIssue.size`; overridden by
|
|
2424
|
-
* `deps.consensusQuorum` when set, capped to `M` so an operator
|
|
2425
|
-
* misconfig (N > M) can't deadlock.
|
|
2426
|
-
*
|
|
2427
|
-
* When the cap fires, write
|
|
2428
|
-
* a one-time stderr breadcrumb per request so operators don't
|
|
2429
|
-
* have to diff their `policy.consensusQuorum` against
|
|
2430
|
-
* `votersAtIssue.size` manually to understand why a quorum
|
|
2431
|
-
* resolved sooner than configured. Tracked on `MediatorPending`
|
|
2432
|
-
* so the breadcrumb fires once even though `consensusQuorumFor`
|
|
2433
|
-
* may be called multiple times per request (vote tally + final
|
|
2434
|
-
* resolution).
|
|
2435
|
-
*/
|
|
2436
|
-
consensusQuorumFor(pending) {
|
|
2437
|
-
const m = pending.votersAtIssue.size;
|
|
2438
|
-
const override = this.deps.consensusQuorum;
|
|
2439
|
-
if (override !== void 0) {
|
|
2440
|
-
const capped = Math.min(override, Math.max(m, 1));
|
|
2441
|
-
if (capped < override && !pending.consensusQuorumCapNoted) {
|
|
2442
|
-
pending.consensusQuorumCapNoted = true;
|
|
2443
|
-
try {
|
|
2444
|
-
process.stderr.write(
|
|
2445
|
-
`permissionMediator: consensusQuorum override ${override} capped to ${capped} (votersAtIssue.size=${m}) for request ${pending.requestId} session ${pending.sessionId}
|
|
2446
|
-
`
|
|
2447
|
-
);
|
|
2448
|
-
} catch {
|
|
2449
|
-
}
|
|
2450
|
-
}
|
|
2451
|
-
return capped;
|
|
2452
|
-
}
|
|
2453
|
-
return Math.max(1, Math.floor(m / 2) + 1);
|
|
2454
|
-
}
|
|
2455
|
-
totalTalliedFor(pending) {
|
|
2456
|
-
let total = 0;
|
|
2457
|
-
for (const set of pending.tallies.values()) total += set.size;
|
|
2458
|
-
return total;
|
|
2459
|
-
}
|
|
2460
|
-
/**
|
|
2461
|
-
* `votesNeeded` = `quorum - max(tally per option)`. When no
|
|
2462
|
-
* option has any votes (degenerate; `permission_partial_vote`
|
|
2463
|
-
* is only emitted AFTER the first vote, so this should never
|
|
2464
|
-
* appear on the wire), returns `quorum` itself. Always ≥ 1
|
|
2465
|
-
* because the resolved-on-quorum path returns before this
|
|
2466
|
-
* helper runs.
|
|
2467
|
-
*/
|
|
2468
|
-
votesNeededFor(pending) {
|
|
2469
|
-
const quorum = this.consensusQuorumFor(pending);
|
|
2470
|
-
let max = 0;
|
|
2471
|
-
for (const set of pending.tallies.values()) {
|
|
2472
|
-
if (set.size > max) max = set.size;
|
|
2473
|
-
}
|
|
2474
|
-
return Math.max(quorum - max, 1);
|
|
2475
|
-
}
|
|
2476
|
-
optionTalliesFor(pending) {
|
|
2477
|
-
const out = {};
|
|
2478
|
-
for (const [optionId, set] of pending.tallies) {
|
|
2479
|
-
out[optionId] = set.size;
|
|
2480
|
-
}
|
|
2481
|
-
return out;
|
|
2482
|
-
}
|
|
2483
|
-
// ===========================================================
|
|
2484
|
-
// Resolution + cleanup
|
|
2485
|
-
// ===========================================================
|
|
2486
|
-
/**
|
|
2487
|
-
* Settle a pending entry. Cleanup order is hardened (cleanup-order invariant):
|
|
2488
|
-
* 1. clearTimeout (so a timer can never fire on a half-cleaned entry).
|
|
2489
|
-
* 2. Delete from `pending` (state-first half — entry no longer
|
|
2490
|
-
* reachable for new votes).
|
|
2491
|
-
* 3. emit wire `permission_resolved` (best-effort — emit failures
|
|
2492
|
-
* do not block the Promise settle). MUST come before step 4
|
|
2493
|
-
* so a re-entrant subscriber synchronously casting another
|
|
2494
|
-
* vote during emit sees `pending === undefined && resolved
|
|
2495
|
-
* === undefined` (silent false), matching the previous ordering.
|
|
2496
|
-
*
|
|
2497
|
-
* 4. write to `resolved` (the second half of state move — late
|
|
2498
|
-
* voters arriving after this see `permission_already_resolved`).
|
|
2499
|
-
* 5. audit.recordResolved (best-effort, same).
|
|
2500
|
-
* 6. Settle the Promise (LAST — callbacks running re-entrantly
|
|
2501
|
-
* see consistent state).
|
|
2502
|
-
*
|
|
2503
|
-
* Previously the spec bundled
|
|
2504
|
-
* "delete pending + write resolved" into step 2 ahead of emit,
|
|
2505
|
-
* which contradicted the code. The fix
|
|
2506
|
-
* splits the two halves of the state move around the emit so
|
|
2507
|
-
* the spec faithfully describes the ordering invariant.
|
|
2508
|
-
*
|
|
2509
|
-
* @param resolverClientId wire compat: the
|
|
2510
|
-
* `permission_resolved` SSE frame stamps this as
|
|
2511
|
-
* `originatorClientId`. The previous `resolvePending` in
|
|
2512
|
-
* `httpAcpBridge.ts:1518-1523` filled it from the voter's
|
|
2513
|
-
* trusted clientId. We preserve byte-for-byte; vote-driven
|
|
2514
|
-
* paths pass `vote.clientId` (which may be undefined for
|
|
2515
|
-
* loopback no-header voters); timer + session-closed paths
|
|
2516
|
-
* pass undefined (no voter).
|
|
2517
|
-
*/
|
|
2518
|
-
resolveEntry(pending, resolution, decisionReason, resolverClientId) {
|
|
2519
|
-
if (this.pending.get(pending.requestId) !== pending) {
|
|
2520
|
-
return;
|
|
2521
|
-
}
|
|
2522
|
-
if (pending.timer !== void 0) {
|
|
2523
|
-
clearTimeout(pending.timer);
|
|
2524
|
-
pending.timer = void 0;
|
|
2525
|
-
}
|
|
2526
|
-
this.pending.delete(pending.requestId);
|
|
2527
|
-
this.safeEmit(pending.sessionId, {
|
|
2528
|
-
type: "permission_resolved",
|
|
2529
|
-
...pending.promptId ? { promptId: pending.promptId } : {},
|
|
2530
|
-
data: {
|
|
2531
|
-
requestId: pending.requestId,
|
|
2532
|
-
outcome: this.toAcpOutcome(resolution),
|
|
2533
|
-
// Note: `voterClientId` is the canonical,
|
|
2534
|
-
// unambiguous name for "who cast the resolving vote". The envelope
|
|
2535
|
-
// `originatorClientId` below carries the SAME value for wire
|
|
2536
|
-
// compat (it is semantically the voter on `permission_resolved`,
|
|
2537
|
-
// unlike on `permission_request` where it is the prompt originator).
|
|
2538
|
-
// Both are optional and omitted together for no-voter resolutions
|
|
2539
|
-
// (timer expiry / session-closed / loopback voter with no clientId).
|
|
2540
|
-
...resolverClientId !== void 0 ? { voterClientId: resolverClientId } : {}
|
|
2541
|
-
},
|
|
2542
|
-
// Preserve pre-extraction behavior: voter's clientId is stamped
|
|
2543
|
-
// here (not the prompt originator's). Documented inconsistency
|
|
2544
|
-
// with `permission_request.originatorClientId` (which IS the
|
|
2545
|
-
// prompt originator); we do not fix the inconsistency to
|
|
2546
|
-
// avoid breaking the wire shape. A4 keeps it as a deprecated
|
|
2547
|
-
// alias of `data.voterClientId`.
|
|
2548
|
-
...resolverClientId !== void 0 ? { originatorClientId: resolverClientId } : {}
|
|
2549
|
-
});
|
|
2550
|
-
this.rememberResolved({
|
|
2551
|
-
requestId: pending.requestId,
|
|
2552
|
-
sessionId: pending.sessionId,
|
|
2553
|
-
promptId: pending.promptId,
|
|
2554
|
-
resolution,
|
|
2555
|
-
resolverClientId
|
|
2556
|
-
});
|
|
2557
|
-
if (decisionReason !== void 0) {
|
|
2558
|
-
this.safeAudit(
|
|
2559
|
-
() => this.deps.audit.recordResolved(
|
|
2560
|
-
this.toRecord(pending),
|
|
2561
|
-
resolution,
|
|
2562
|
-
decisionReason
|
|
2563
|
-
)
|
|
2564
|
-
);
|
|
2565
|
-
}
|
|
2566
|
-
pending.resolve(resolution);
|
|
2567
|
-
}
|
|
2568
|
-
rememberResolved(record) {
|
|
2569
|
-
if (!this.resolved.has(record.requestId)) {
|
|
2570
|
-
this.resolvedOrder.push(record.requestId);
|
|
2571
|
-
}
|
|
2572
|
-
this.resolved.set(record.requestId, record);
|
|
2573
|
-
while (this.resolvedOrder.length > MAX_RESOLVED_PERMISSION_RECORDS) {
|
|
2574
|
-
const oldest2 = this.resolvedOrder.shift();
|
|
2575
|
-
if (oldest2 !== void 0) this.resolved.delete(oldest2);
|
|
2576
|
-
}
|
|
2577
|
-
}
|
|
2578
|
-
safeEmit(sessionId, event) {
|
|
2579
|
-
try {
|
|
2580
|
-
this.deps.emit(sessionId, event);
|
|
2581
|
-
} catch (err) {
|
|
2582
|
-
try {
|
|
2583
|
-
process.stderr.write(
|
|
2584
|
-
`permissionMediator: emit failed for session=${JSON.stringify(sessionId)} type=${JSON.stringify(event.type)}: ${stringifyError(err)}
|
|
2585
|
-
`
|
|
2586
|
-
);
|
|
2587
|
-
} catch {
|
|
2588
|
-
}
|
|
2589
|
-
}
|
|
2590
|
-
}
|
|
2591
|
-
/**
|
|
2592
|
-
* Emit a stderr breadcrumb
|
|
2593
|
-
* for every vote rejection (the three forbidden paths in
|
|
2594
|
-
* voteDesignated / voteConsensus / voteLocalOnly). Mirrors the
|
|
2595
|
-
* timeout breadcrumb pattern: audit ring + SSE event are
|
|
2596
|
-
* transient observability surfaces (no v1 query route, SSE drops
|
|
2597
|
-
* on disconnect), so an operator tailing daemon stderr would see
|
|
2598
|
-
* zero indication of permission rejections without this.
|
|
2599
|
-
*
|
|
2600
|
-
* Wrapped in `try/catch` because `process.stderr.write` can
|
|
2601
|
-
* synchronously throw on EPIPE during shutdown — a stderr
|
|
2602
|
-
* unavailability must not propagate up through `safeEmit` /
|
|
2603
|
-
* `safeAudit` and break the resolveEntry cleanup ladder. Mirrors
|
|
2604
|
-
* the safeEmit/safeAudit defensive posture (see the
|
|
2605
|
-
* matching hang scenario in safeEmit).
|
|
2606
|
-
*/
|
|
2607
|
-
writeForbiddenStderr(pending, vote, reasonDetail) {
|
|
2608
|
-
try {
|
|
2609
|
-
const voterDescriptor = vote.clientId === void 0 ? "<anonymous>" : JSON.stringify(vote.clientId);
|
|
2610
|
-
process.stderr.write(
|
|
2611
|
-
`qwen serve: permission ${pending.requestId} (session ${pending.sessionId}): vote rejected (${reasonDetail}) by client ${voterDescriptor}
|
|
2612
|
-
`
|
|
2613
|
-
);
|
|
2614
|
-
} catch {
|
|
2615
|
-
}
|
|
2616
|
-
}
|
|
2617
|
-
/**
|
|
2618
|
-
* Run an audit-publisher call defensively. The audit ring is
|
|
2619
|
-
* best-effort observability — a publisher exception (ring full,
|
|
2620
|
-
* host bug, transient I/O) MUST NOT throw out of `request()`,
|
|
2621
|
-
* `vote()`, or the timer callback. Without this guard, the
|
|
2622
|
-
* Promise the agent is awaiting would be left unsettled and the
|
|
2623
|
-
* pending entry would leak.
|
|
2624
|
-
*
|
|
2625
|
-
* Single helper used at all five audit call sites so the
|
|
2626
|
-
* "audit is best-effort" invariant is uniformly enforced (the
|
|
2627
|
-
* pre-fix asymmetric `try/catch` at 2 of 5 sites was a real
|
|
2628
|
-
* silent-failure hole.
|
|
2629
|
-
*
|
|
2630
|
-
* doc placement — JSDoc was previously
|
|
2631
|
-
* stacked above `writeForbiddenStderr` so IDE hover and API
|
|
2632
|
-
* doc generation showed the wrong attribution. Moved adjacent
|
|
2633
|
-
* to its actual definition.
|
|
2634
|
-
*/
|
|
2635
|
-
safeAudit(fn) {
|
|
2636
|
-
try {
|
|
2637
|
-
fn();
|
|
2638
|
-
} catch (err) {
|
|
2639
|
-
try {
|
|
2640
|
-
process.stderr.write(
|
|
2641
|
-
`permissionMediator: audit publisher threw: ${stringifyError(err)}
|
|
2642
|
-
`
|
|
2643
|
-
);
|
|
2644
|
-
} catch {
|
|
2645
|
-
}
|
|
2646
|
-
}
|
|
2647
|
-
}
|
|
2648
|
-
toRecord(pending) {
|
|
2649
|
-
return {
|
|
2650
|
-
requestId: pending.requestId,
|
|
2651
|
-
sessionId: pending.sessionId,
|
|
2652
|
-
...pending.promptId ? { promptId: pending.promptId } : {},
|
|
2653
|
-
originatorClientId: pending.originatorClientId,
|
|
2654
|
-
allowedOptionIds: pending.allowedOptionIds,
|
|
2655
|
-
issuedAtMs: pending.issuedAtMs
|
|
2656
|
-
};
|
|
2657
|
-
}
|
|
2658
|
-
toAcpOutcome(resolution) {
|
|
2659
|
-
if (resolution.kind === "option") {
|
|
2660
|
-
return { outcome: "selected", optionId: resolution.optionId };
|
|
2661
|
-
}
|
|
2662
|
-
return { outcome: "cancelled" };
|
|
2663
|
-
}
|
|
2664
|
-
};
|
|
2665
|
-
|
|
2666
|
-
// packages/acp-bridge/src/bridgeClient.ts
|
|
2667
|
-
function parseActiveWorkSnapshot(params) {
|
|
2668
|
-
const seq = params["seq"];
|
|
2669
|
-
const sessions = params["sessions"];
|
|
2670
|
-
if (params["v"] !== ACTIVE_WORK_HEARTBEAT_VERSION || typeof seq !== "number" || !Number.isSafeInteger(seq) || seq <= 0 || !Array.isArray(sessions) || sessions.length > ACTIVE_WORK_MAX_SNAPSHOT_SESSIONS) {
|
|
2671
|
-
return void 0;
|
|
2672
|
-
}
|
|
2673
|
-
const parsed = [];
|
|
2674
|
-
for (const raw of sessions) {
|
|
2675
|
-
if (typeof raw !== "object" || raw === null) return void 0;
|
|
2676
|
-
const entry = raw;
|
|
2677
|
-
const sessionId = entry["sessionId"];
|
|
2678
|
-
const holds = entry["holds"];
|
|
2679
|
-
if (typeof sessionId !== "string" || !Array.isArray(holds) || holds.length > ACTIVE_WORK_MAX_SESSION_HOLDS) {
|
|
2680
|
-
return void 0;
|
|
2681
|
-
}
|
|
2682
|
-
const parsedHolds = [];
|
|
2683
|
-
for (const rawHold of holds) {
|
|
2684
|
-
if (typeof rawHold !== "object" || rawHold === null) return void 0;
|
|
2685
|
-
const hold = rawHold;
|
|
2686
|
-
const category = hold["category"];
|
|
2687
|
-
const id = hold["id"];
|
|
2688
|
-
if (typeof id !== "string" || typeof category !== "string" || !ACTIVE_WORK_HOLD_CATEGORIES.includes(
|
|
2689
|
-
category
|
|
2690
|
-
)) {
|
|
2691
|
-
return void 0;
|
|
2692
|
-
}
|
|
2693
|
-
parsedHolds.push({
|
|
2694
|
-
category,
|
|
2695
|
-
id
|
|
2696
|
-
});
|
|
2697
|
-
}
|
|
2698
|
-
parsed.push({ sessionId, holds: parsedHolds });
|
|
2699
|
-
}
|
|
2700
|
-
return { v: ACTIVE_WORK_HEARTBEAT_VERSION, seq, sessions: parsed };
|
|
2701
|
-
}
|
|
2702
|
-
__name(parseActiveWorkSnapshot, "parseActiveWorkSnapshot");
|
|
2703
|
-
var PUBLISH_ARTIFACT_TOOL_NAME = "artifact";
|
|
2704
|
-
var MAX_CHANNEL_DELIVERY_TEXT_CHARS = 1e5;
|
|
2705
|
-
var MAX_CHANNEL_DELIVERY_FIELD_CHARS = 2048;
|
|
2706
|
-
var MAX_CHANNEL_DELIVERY_ERROR_CHARS = 500;
|
|
2707
|
-
function isFsErrorShape(err) {
|
|
2708
|
-
return err instanceof Error && err.name === "FsError" && typeof err.kind === "string";
|
|
2709
|
-
}
|
|
2710
|
-
__name(isFsErrorShape, "isFsErrorShape");
|
|
2711
|
-
function isRecord2(value) {
|
|
2712
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2713
|
-
}
|
|
2714
|
-
__name(isRecord2, "isRecord");
|
|
2715
|
-
function normalizeExternalToolGuardResult(value) {
|
|
2716
|
-
if (!isRecord2(value)) {
|
|
2717
|
-
throw new Error("External tool guard handler returned an invalid result.");
|
|
2718
|
-
}
|
|
2719
|
-
const keys = Object.keys(value);
|
|
2720
|
-
if (value["allowed"] === true && keys.length === 1 && keys[0] === "allowed") {
|
|
2721
|
-
return { allowed: true };
|
|
2722
|
-
}
|
|
2723
|
-
if (value["allowed"] !== false || keys.some((key) => key !== "allowed" && key !== "reason")) {
|
|
2724
|
-
throw new Error("External tool guard handler returned an invalid result.");
|
|
2725
|
-
}
|
|
2726
|
-
if (!Object.hasOwn(value, "reason")) return { allowed: false };
|
|
2727
|
-
const reason = value["reason"];
|
|
2728
|
-
if (!isValidExternalToolGuardDenialReason(reason)) {
|
|
2729
|
-
throw new Error("External tool guard handler returned an invalid result.");
|
|
2730
|
-
}
|
|
2731
|
-
return { allowed: false, reason };
|
|
2732
|
-
}
|
|
2733
|
-
__name(normalizeExternalToolGuardResult, "normalizeExternalToolGuardResult");
|
|
2734
|
-
function isBoundedChannelDeliveryString(value) {
|
|
2735
|
-
return typeof value === "string" && value.trim().length > 0 && value.length <= MAX_CHANNEL_DELIVERY_FIELD_CHARS;
|
|
2736
|
-
}
|
|
2737
|
-
__name(isBoundedChannelDeliveryString, "isBoundedChannelDeliveryString");
|
|
2738
|
-
function isChannelDeliveryTarget(value) {
|
|
2739
|
-
if (!isRecord2(value)) return false;
|
|
2740
|
-
return isBoundedChannelDeliveryString(value["channelName"]) && (value["type"] === "user" || value["type"] === "chat") && isBoundedChannelDeliveryString(value["id"]) && Object.keys(value).every(
|
|
2741
|
-
(key) => key === "channelName" || key === "type" || key === "id"
|
|
2742
|
-
);
|
|
2743
|
-
}
|
|
2744
|
-
__name(isChannelDeliveryTarget, "isChannelDeliveryTarget");
|
|
2745
|
-
function normalizeChannelDeliveryHostResult(value) {
|
|
2746
|
-
if (value.status === "delivered") return { status: "delivered" };
|
|
2747
|
-
if (value.status === "skipped") return { status: "skipped" };
|
|
2748
|
-
const code = CHANNEL_DELIVERY_ERROR_CODES.has(value.code) ? value.code : "channel_delivery_failed";
|
|
2749
|
-
const error = typeof value.error === "string" && value.error.length > 0 ? value.error.slice(0, MAX_CHANNEL_DELIVERY_ERROR_CHARS) : "Channel delivery failed.";
|
|
2750
|
-
return { status: "failed", code, error };
|
|
2751
|
-
}
|
|
2752
|
-
__name(normalizeChannelDeliveryHostResult, "normalizeChannelDeliveryHostResult");
|
|
2753
|
-
function pendingInteractionOptions(options) {
|
|
2754
|
-
return options.map((option) => ({
|
|
2755
|
-
optionId: String(option.optionId ?? ""),
|
|
2756
|
-
...typeof option.name === "string" ? { label: option.name } : {},
|
|
2757
|
-
...typeof option.kind === "string" ? { kind: option.kind } : {}
|
|
2758
|
-
}));
|
|
2759
|
-
}
|
|
2760
|
-
__name(pendingInteractionOptions, "pendingInteractionOptions");
|
|
2761
|
-
function pendingInteractionFromRequest(requestId, params) {
|
|
2762
|
-
const toolCall = params.toolCall;
|
|
2763
|
-
const meta = isRecord2(toolCall["_meta"]) ? toolCall["_meta"] : void 0;
|
|
2764
|
-
const rawInput = toolCall["rawInput"];
|
|
2765
|
-
const options = pendingInteractionOptions(
|
|
2766
|
-
Array.isArray(params.options) ? params.options : []
|
|
2767
|
-
);
|
|
2768
|
-
const isUserQuestion = meta?.["qwenInteractionKind"] === "user_question";
|
|
2769
|
-
if (isUserQuestion) {
|
|
2770
|
-
const rawQuestions = Array.isArray(meta?.["qwenQuestions"]) ? meta["qwenQuestions"] : isRecord2(rawInput) && Array.isArray(rawInput["questions"]) ? rawInput["questions"] : [];
|
|
2771
|
-
return {
|
|
2772
|
-
requestId,
|
|
2773
|
-
kind: "user_question",
|
|
2774
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2775
|
-
...typeof toolCall["title"] === "string" ? { title: toolCall["title"] } : {},
|
|
2776
|
-
questions: rawQuestions.flatMap(
|
|
2777
|
-
(question, index) => isRecord2(question) ? [{ ...question, answerKey: String(index) }] : []
|
|
2778
|
-
),
|
|
2779
|
-
options
|
|
2780
|
-
};
|
|
2781
|
-
}
|
|
2782
|
-
return {
|
|
2783
|
-
requestId,
|
|
2784
|
-
kind: "permission",
|
|
2785
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2786
|
-
action: {
|
|
2787
|
-
...typeof toolCall["kind"] === "string" ? { type: toolCall["kind"] } : {},
|
|
2788
|
-
...typeof toolCall["title"] === "string" ? { title: toolCall["title"] } : {},
|
|
2789
|
-
...toolCall["content"] !== void 0 ? { content: toolCall["content"] } : {},
|
|
2790
|
-
...toolCall["locations"] !== void 0 ? { locations: toolCall["locations"] } : {},
|
|
2791
|
-
...rawInput !== void 0 ? { input: rawInput } : {}
|
|
2792
|
-
},
|
|
2793
|
-
options
|
|
2794
|
-
};
|
|
2795
|
-
}
|
|
2796
|
-
__name(pendingInteractionFromRequest, "pendingInteractionFromRequest");
|
|
2797
|
-
function fallbackPendingPermissionInteraction(requestId, options) {
|
|
2798
|
-
return {
|
|
2799
|
-
requestId,
|
|
2800
|
-
kind: "permission",
|
|
2801
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2802
|
-
action: {},
|
|
2803
|
-
options: pendingInteractionOptions(options)
|
|
2804
|
-
};
|
|
2805
|
-
}
|
|
2806
|
-
__name(fallbackPendingPermissionInteraction, "fallbackPendingPermissionInteraction");
|
|
2807
|
-
function artifactPayloadFields(artifact) {
|
|
2808
|
-
return {
|
|
2809
|
-
title: artifact["title"],
|
|
2810
|
-
kind: artifact["kind"],
|
|
2811
|
-
storage: artifact["storage"],
|
|
2812
|
-
description: artifact["description"],
|
|
2813
|
-
workspacePath: artifact["workspacePath"],
|
|
2814
|
-
managedId: artifact["managedId"],
|
|
2815
|
-
url: artifact["url"],
|
|
2816
|
-
mimeType: artifact["mimeType"],
|
|
2817
|
-
sizeBytes: artifact["sizeBytes"],
|
|
2818
|
-
metadata: artifact["metadata"],
|
|
2819
|
-
retention: artifact["retention"]
|
|
2820
|
-
};
|
|
2821
|
-
}
|
|
2822
|
-
__name(artifactPayloadFields, "artifactPayloadFields");
|
|
2823
|
-
function extractCappedArtifactInputs(rawArtifacts, limit, sessionId, source, toInput) {
|
|
2824
|
-
const artifacts = [];
|
|
2825
|
-
for (let index = 0; index < rawArtifacts.length; index++) {
|
|
2826
|
-
const artifact = rawArtifacts[index];
|
|
2827
|
-
if (!isRecord2(artifact)) {
|
|
2828
|
-
writeStderrLine(
|
|
2829
|
-
`[artifacts] session=${sessionId} action=dropped reason=malformed source=${source} index=${index}`
|
|
2830
|
-
);
|
|
2831
|
-
continue;
|
|
2832
|
-
}
|
|
2833
|
-
if (artifacts.length >= limit) {
|
|
2834
|
-
writeStderrLine(
|
|
2835
|
-
`[artifacts] session=${sessionId} action=dropped reason="artifact batch limit exceeded" source=${source} dropped=${rawArtifacts.length - index}`
|
|
2836
|
-
);
|
|
2837
|
-
break;
|
|
2838
|
-
}
|
|
2839
|
-
artifacts.push(toInput(artifact));
|
|
2840
|
-
}
|
|
2841
|
-
return artifacts;
|
|
2842
|
-
}
|
|
2843
|
-
__name(extractCappedArtifactInputs, "extractCappedArtifactInputs");
|
|
2844
|
-
function artifactIngestionErrorReason(error) {
|
|
2845
|
-
if (!(error instanceof Error)) {
|
|
2846
|
-
return String(error);
|
|
2847
|
-
}
|
|
2848
|
-
return {
|
|
2849
|
-
name: error.name,
|
|
2850
|
-
message: error.message,
|
|
2851
|
-
stack: error.stack?.split("\n").slice(0, 4).join("\n")
|
|
2852
|
-
};
|
|
2853
|
-
}
|
|
2854
|
-
__name(artifactIngestionErrorReason, "artifactIngestionErrorReason");
|
|
2855
|
-
function extractSessionUpdateArtifacts(params, updateMeta, limit, sessionId) {
|
|
2856
|
-
const rawArtifacts = updateMeta?.["artifacts"];
|
|
2857
|
-
if (!Array.isArray(rawArtifacts)) {
|
|
2858
|
-
return [];
|
|
2859
|
-
}
|
|
2860
|
-
const update = params.update;
|
|
2861
|
-
if (update.sessionUpdate !== "tool_call_update" || update.status !== "completed" && update.status !== "failed" && update.status !== "cancelled") {
|
|
2862
|
-
return [];
|
|
2863
|
-
}
|
|
2864
|
-
const toolCallId = typeof update.toolCallId === "string" ? update.toolCallId : void 0;
|
|
2865
|
-
const toolName = typeof updateMeta?.["toolName"] === "string" ? updateMeta["toolName"] : void 0;
|
|
2866
|
-
return extractCappedArtifactInputs(
|
|
2867
|
-
rawArtifacts,
|
|
2868
|
-
limit,
|
|
2869
|
-
sessionId,
|
|
2870
|
-
"tool",
|
|
2871
|
-
(artifact) => ({
|
|
2872
|
-
...artifactPayloadFields(artifact),
|
|
2873
|
-
source: "tool",
|
|
2874
|
-
toolCallId,
|
|
2875
|
-
toolName
|
|
2876
|
-
})
|
|
2877
|
-
);
|
|
2878
|
-
}
|
|
2879
|
-
__name(extractSessionUpdateArtifacts, "extractSessionUpdateArtifacts");
|
|
2880
|
-
function sanitizeSessionUpdateArtifacts(params, updateMeta) {
|
|
2881
|
-
if (!Array.isArray(updateMeta?.["artifacts"])) {
|
|
2882
|
-
return params;
|
|
2883
|
-
}
|
|
2884
|
-
const sanitizedMeta = { ...updateMeta };
|
|
2885
|
-
delete sanitizedMeta["artifacts"];
|
|
2886
|
-
const update = {
|
|
2887
|
-
...params.update,
|
|
2888
|
-
_meta: sanitizedMeta
|
|
2889
|
-
};
|
|
2890
|
-
return {
|
|
2891
|
-
...params,
|
|
2892
|
-
update
|
|
2893
|
-
};
|
|
2894
|
-
}
|
|
2895
|
-
__name(sanitizeSessionUpdateArtifacts, "sanitizeSessionUpdateArtifacts");
|
|
2896
|
-
function isTrustedArtifactToolUpdate(params, updateMeta) {
|
|
2897
|
-
const update = params.update;
|
|
2898
|
-
return update.sessionUpdate === "tool_call_update" && update.status === "completed" && updateMeta?.["toolName"] === PUBLISH_ARTIFACT_TOOL_NAME;
|
|
2899
|
-
}
|
|
2900
|
-
__name(isTrustedArtifactToolUpdate, "isTrustedArtifactToolUpdate");
|
|
2901
|
-
function preserveFsErrorOverAcp(err) {
|
|
2902
|
-
if (isFsErrorShape(err)) {
|
|
2903
|
-
const code = err.kind === "parse_error" ? -32602 : -32603;
|
|
2904
|
-
throw new RequestError(code, err.message, {
|
|
2905
|
-
errorKind: err.kind,
|
|
2906
|
-
...err.hint !== void 0 ? { hint: err.hint } : {},
|
|
2907
|
-
...err.status !== void 0 ? { status: err.status } : {}
|
|
2908
|
-
});
|
|
2909
|
-
}
|
|
2910
|
-
throw err;
|
|
2911
|
-
}
|
|
2912
|
-
__name(preserveFsErrorOverAcp, "preserveFsErrorOverAcp");
|
|
2913
|
-
function resolutionToAcpResponse(resolution) {
|
|
2914
|
-
if (resolution.kind === "option") {
|
|
2915
|
-
return {
|
|
2916
|
-
outcome: { outcome: "selected", optionId: resolution.optionId },
|
|
2917
|
-
...resolution.metadata ?? {}
|
|
2918
|
-
};
|
|
2919
|
-
}
|
|
2920
|
-
return {
|
|
2921
|
-
outcome: { outcome: "cancelled" },
|
|
2922
|
-
_meta: {
|
|
2923
|
-
[DAEMON_PERMISSION_CANCEL_REASON_META_KEY]: resolution.reason
|
|
2924
|
-
}
|
|
2925
|
-
};
|
|
2926
|
-
}
|
|
2927
|
-
__name(resolutionToAcpResponse, "resolutionToAcpResponse");
|
|
2928
|
-
var MAX_EARLY_EVENT_SESSIONS = 64;
|
|
2929
|
-
var MAX_EARLY_EVENTS_PER_SESSION = 32;
|
|
2930
|
-
var MAX_SUGGESTION_LENGTH = 500;
|
|
2931
|
-
var EARLY_EVENT_TTL_MS = 6e4;
|
|
2932
|
-
var KNOWN_APPROVAL_MODES = new Set(
|
|
2933
|
-
APPROVAL_MODES
|
|
2934
|
-
);
|
|
2935
|
-
function describeStatKind(stats) {
|
|
2936
|
-
if (stats.isDirectory()) return "directory";
|
|
2937
|
-
if (stats.isSymbolicLink()) return "symlink";
|
|
2938
|
-
if (stats.isCharacterDevice()) return "character device";
|
|
2939
|
-
if (stats.isBlockDevice()) return "block device";
|
|
2940
|
-
if (stats.isFIFO()) return "named pipe (FIFO)";
|
|
2941
|
-
if (stats.isSocket()) return "socket";
|
|
2942
|
-
return "non-regular file";
|
|
2943
|
-
}
|
|
2944
|
-
__name(describeStatKind, "describeStatKind");
|
|
2945
|
-
function sliceLineRange(content, startLine, endLine) {
|
|
2946
|
-
let offset = 0;
|
|
2947
|
-
for (let i = 0; i < startLine; i++) {
|
|
2948
|
-
const nl = content.indexOf("\n", offset);
|
|
2949
|
-
if (nl === -1) return "";
|
|
2950
|
-
offset = nl + 1;
|
|
2951
|
-
}
|
|
2952
|
-
if (endLine === void 0) return content.slice(offset);
|
|
2953
|
-
let end = offset;
|
|
2954
|
-
const want = endLine - startLine;
|
|
2955
|
-
for (let i = 0; i < want; i++) {
|
|
2956
|
-
const nl = content.indexOf("\n", end);
|
|
2957
|
-
if (nl === -1) return content.slice(offset);
|
|
2958
|
-
end = nl + 1;
|
|
2959
|
-
}
|
|
2960
|
-
return content.slice(offset, end > offset ? end - 1 : end);
|
|
2961
|
-
}
|
|
2962
|
-
__name(sliceLineRange, "sliceLineRange");
|
|
2963
|
-
var BridgeClient = class {
|
|
2964
|
-
constructor(resolveEntry, resolvePendingRestoreEvents, mediator, permissionTimeoutMs, maxPendingPerSession, fileSystem, onModelPromoted, onModePromoted, clientMcpSender, ownsSession = () => true, onTokenUsage, onCreateSubSession, onGenerationEvent, onWorkspaceGenerationEvent, onChannelDelivery, hasSessionSpawnInFlight = () => false, getLiveScreenContextCaptureHandler = () => void 0, getLiveTaskToolRequestHandler = () => void 0, getLiveSpeakToUserHandler = () => void 0, externalToolGuard, onActiveWork, onSessionCatalogChanged, onGoalTurnEnded) {
|
|
2965
|
-
this.resolveEntry = resolveEntry;
|
|
2966
|
-
this.resolvePendingRestoreEvents = resolvePendingRestoreEvents;
|
|
2967
|
-
this.mediator = mediator;
|
|
2968
|
-
this.permissionTimeoutMs = permissionTimeoutMs;
|
|
2969
|
-
this.maxPendingPerSession = maxPendingPerSession;
|
|
2970
|
-
this.fileSystem = fileSystem;
|
|
2971
|
-
this.onModelPromoted = onModelPromoted;
|
|
2972
|
-
this.onModePromoted = onModePromoted;
|
|
2973
|
-
this.clientMcpSender = clientMcpSender;
|
|
2974
|
-
this.ownsSession = ownsSession;
|
|
2975
|
-
this.onTokenUsage = onTokenUsage;
|
|
2976
|
-
this.onCreateSubSession = onCreateSubSession;
|
|
2977
|
-
this.onGenerationEvent = onGenerationEvent;
|
|
2978
|
-
this.onWorkspaceGenerationEvent = onWorkspaceGenerationEvent;
|
|
2979
|
-
this.onChannelDelivery = onChannelDelivery;
|
|
2980
|
-
this.hasSessionSpawnInFlight = hasSessionSpawnInFlight;
|
|
2981
|
-
this.getLiveScreenContextCaptureHandler = getLiveScreenContextCaptureHandler;
|
|
2982
|
-
this.getLiveTaskToolRequestHandler = getLiveTaskToolRequestHandler;
|
|
2983
|
-
this.getLiveSpeakToUserHandler = getLiveSpeakToUserHandler;
|
|
2984
|
-
this.externalToolGuard = externalToolGuard;
|
|
2985
|
-
this.onActiveWork = onActiveWork;
|
|
2986
|
-
this.onSessionCatalogChanged = onSessionCatalogChanged;
|
|
2987
|
-
this.onGoalTurnEnded = onGoalTurnEnded;
|
|
2988
|
-
}
|
|
2989
|
-
static {
|
|
2990
|
-
__name(this, "BridgeClient");
|
|
2991
|
-
}
|
|
2992
|
-
async requestPermission(params) {
|
|
2993
|
-
const entry = this.resolveEntry(params.sessionId);
|
|
2994
|
-
if (!entry) return { outcome: { outcome: "cancelled" } };
|
|
2995
|
-
if (entry.pendingPermissionIds.size >= this.maxPendingPerSession) {
|
|
2996
|
-
writeStderrLine(
|
|
2997
|
-
`qwen serve: session ${entry.sessionId} exceeded maxPendingPermissionsPerSession (${this.maxPendingPerSession}) \u2014 resolving new permission as cancelled.`
|
|
2998
|
-
);
|
|
2999
|
-
return { outcome: { outcome: "cancelled" } };
|
|
3000
|
-
}
|
|
3001
|
-
const options = Array.isArray(params.options) ? params.options : [];
|
|
3002
|
-
const allowedOptionIds = new Set(
|
|
3003
|
-
options.map((o) => String(o.optionId ?? ""))
|
|
3004
|
-
);
|
|
3005
|
-
allowedOptionIds.delete("");
|
|
3006
|
-
const requestId = randomUUID2();
|
|
3007
|
-
if (allowedOptionIds.has(CANCEL_VOTE_SENTINEL)) {
|
|
3008
|
-
throw new CancelSentinelCollisionError(requestId, CANCEL_VOTE_SENTINEL);
|
|
3009
|
-
}
|
|
3010
|
-
const published = entry.events.publish({
|
|
3011
|
-
type: "permission_request",
|
|
3012
|
-
...entry.activePromptId ? { promptId: entry.activePromptId } : {},
|
|
3013
|
-
data: {
|
|
3014
|
-
requestId,
|
|
3015
|
-
sessionId: entry.sessionId,
|
|
3016
|
-
toolCall: params.toolCall,
|
|
3017
|
-
options
|
|
3018
|
-
},
|
|
3019
|
-
...entry.activePromptOriginatorClientId ? { originatorClientId: entry.activePromptOriginatorClientId } : {}
|
|
3020
|
-
});
|
|
3021
|
-
if (!published) return { outcome: { outcome: "cancelled" } };
|
|
3022
|
-
let interaction;
|
|
3023
|
-
try {
|
|
3024
|
-
interaction = pendingInteractionFromRequest(requestId, {
|
|
3025
|
-
...params,
|
|
3026
|
-
options
|
|
3027
|
-
});
|
|
3028
|
-
} catch (error) {
|
|
3029
|
-
writeStderrLine(
|
|
3030
|
-
`qwen serve: failed to snapshot pending interaction ${requestId}: ${error instanceof Error ? error.message : String(error)}`
|
|
3031
|
-
);
|
|
3032
|
-
interaction = fallbackPendingPermissionInteraction(requestId, options);
|
|
3033
|
-
}
|
|
3034
|
-
entry.pendingPermissionIds.add(requestId);
|
|
3035
|
-
if (interaction) entry.pendingInteractions.set(requestId, interaction);
|
|
3036
|
-
try {
|
|
3037
|
-
const record = {
|
|
3038
|
-
requestId,
|
|
3039
|
-
sessionId: entry.sessionId,
|
|
3040
|
-
promptId: entry.activePromptId,
|
|
3041
|
-
originatorClientId: entry.activePromptOriginatorClientId,
|
|
3042
|
-
allowedOptionIds,
|
|
3043
|
-
issuedAtMs: Date.now()
|
|
3044
|
-
};
|
|
3045
|
-
const resolution = await this.mediator.request(
|
|
3046
|
-
record,
|
|
3047
|
-
this.permissionTimeoutMs
|
|
3048
|
-
);
|
|
3049
|
-
return resolutionToAcpResponse(resolution);
|
|
3050
|
-
} finally {
|
|
3051
|
-
entry.pendingPermissionIds.delete(requestId);
|
|
3052
|
-
entry.pendingInteractions.delete(requestId);
|
|
3053
|
-
}
|
|
3054
|
-
}
|
|
3055
|
-
async sessionUpdate(params) {
|
|
3056
|
-
if (this.abandonedRestoreIds.has(params.sessionId)) {
|
|
3057
|
-
return;
|
|
3058
|
-
}
|
|
3059
|
-
if (!this.ownsSession(params.sessionId) && !this.inFlightRestoreIds.has(params.sessionId)) {
|
|
3060
|
-
writeStderrLine(
|
|
3061
|
-
`[demux] session=${params.sessionId} type=session_update action=dropped reason=session_not_owned`
|
|
3062
|
-
);
|
|
3063
|
-
return;
|
|
3064
|
-
}
|
|
3065
|
-
const entry = this.resolveEntry(params.sessionId);
|
|
3066
|
-
const events = entry?.events ?? this.resolvePendingRestoreEvents(params.sessionId);
|
|
3067
|
-
if (!events) return;
|
|
3068
|
-
const prepared = this.prepareSessionUpdateFrames(params, entry);
|
|
3069
|
-
for (const frame of prepared.frames) {
|
|
3070
|
-
events.publish(frame);
|
|
3071
|
-
}
|
|
3072
|
-
try {
|
|
3073
|
-
this.recordLiveTokenUsage(params, entry);
|
|
3074
|
-
} catch {
|
|
3075
|
-
}
|
|
3076
|
-
if (entry && prepared.artifacts.length > 0) {
|
|
3077
|
-
await this.upsertAndPublishArtifacts(
|
|
3078
|
-
entry,
|
|
3079
|
-
prepared.artifacts,
|
|
3080
|
-
{
|
|
3081
|
-
trustedPublisher: prepared.trustedPublisher
|
|
3082
|
-
},
|
|
3083
|
-
prepared.turn
|
|
3084
|
-
);
|
|
3085
|
-
}
|
|
3086
|
-
}
|
|
3087
|
-
prepareSessionUpdateFrames(params, entry) {
|
|
3088
|
-
const turn = {
|
|
3089
|
-
...entry?.activePromptId ? { promptId: entry.activePromptId } : {},
|
|
3090
|
-
...entry?.activePromptOriginatorClientId ? { originatorClientId: entry.activePromptOriginatorClientId } : {}
|
|
3091
|
-
};
|
|
3092
|
-
const frames = [];
|
|
3093
|
-
const a2ui = extractA2uiToolUpdate(params);
|
|
3094
|
-
if (a2ui) {
|
|
3095
|
-
for (const surface of a2ui.surfaces) {
|
|
3096
|
-
frames.push({
|
|
3097
|
-
type: "session_update",
|
|
3098
|
-
data: {
|
|
3099
|
-
sessionId: params.sessionId,
|
|
3100
|
-
update: {
|
|
3101
|
-
sessionUpdate: "a2ui",
|
|
3102
|
-
a2ui: {
|
|
3103
|
-
surfaceId: surface.surfaceId,
|
|
3104
|
-
callId: a2ui.callId,
|
|
3105
|
-
commands: surface.commands
|
|
3106
|
-
},
|
|
3107
|
-
_meta: { serverTimestamp: Date.now(), source: "a2ui-bridge" }
|
|
3108
|
-
}
|
|
3109
|
-
},
|
|
3110
|
-
...turn
|
|
3111
|
-
});
|
|
3112
|
-
}
|
|
3113
|
-
params = a2ui.sanitizedParams;
|
|
3114
|
-
}
|
|
3115
|
-
const updateMeta = params.update._meta;
|
|
3116
|
-
const originalTs = updateMeta?.["serverTimestamp"] ?? updateMeta?.["timestamp"];
|
|
3117
|
-
const serverTimestamp = typeof originalTs === "number" && Number.isFinite(originalTs) ? originalTs : void 0;
|
|
3118
|
-
const artifacts = entry?.artifacts ? extractSessionUpdateArtifacts(
|
|
3119
|
-
params,
|
|
3120
|
-
updateMeta,
|
|
3121
|
-
entry.artifacts.inputBatchLimit(),
|
|
3122
|
-
entry.sessionId
|
|
3123
|
-
) : [];
|
|
3124
|
-
const publishParams = sanitizeSessionUpdateArtifacts(params, updateMeta);
|
|
3125
|
-
frames.push({
|
|
3126
|
-
type: "session_update",
|
|
3127
|
-
data: publishParams,
|
|
3128
|
-
...turn,
|
|
3129
|
-
...serverTimestamp !== void 0 ? { _meta: { serverTimestamp } } : {}
|
|
3130
|
-
});
|
|
3131
|
-
return {
|
|
3132
|
-
frames,
|
|
3133
|
-
artifacts,
|
|
3134
|
-
trustedPublisher: isTrustedArtifactToolUpdate(params, updateMeta),
|
|
3135
|
-
turn
|
|
3136
|
-
};
|
|
3137
|
-
}
|
|
3138
|
-
async seedSessionUpdates(entry, updates, options = {}) {
|
|
3139
|
-
const frames = [];
|
|
3140
|
-
const artifactBatches = [];
|
|
3141
|
-
for (const update of updates) {
|
|
3142
|
-
const prepared = this.prepareSessionUpdateFrames(
|
|
3143
|
-
{ sessionId: entry.sessionId, update },
|
|
3144
|
-
entry
|
|
3145
|
-
);
|
|
3146
|
-
frames.push(...prepared.frames);
|
|
3147
|
-
if (options.ingestArtifacts !== false && prepared.artifacts.length > 0) {
|
|
3148
|
-
artifactBatches.push({
|
|
3149
|
-
artifacts: prepared.artifacts,
|
|
3150
|
-
trustedPublisher: prepared.trustedPublisher,
|
|
3151
|
-
turn: prepared.turn
|
|
3152
|
-
});
|
|
3153
|
-
}
|
|
3154
|
-
}
|
|
3155
|
-
entry.events.seedReplayEvents(frames);
|
|
3156
|
-
for (const batch of artifactBatches) {
|
|
3157
|
-
await this.upsertAndPublishArtifacts(
|
|
3158
|
-
entry,
|
|
3159
|
-
batch.artifacts,
|
|
3160
|
-
{
|
|
3161
|
-
trustedPublisher: batch.trustedPublisher
|
|
3162
|
-
},
|
|
3163
|
-
batch.turn
|
|
3164
|
-
);
|
|
3165
|
-
}
|
|
3166
|
-
}
|
|
3167
|
-
/**
|
|
3168
|
-
* Daemon token-burn accounting for LIVE model turns. Called only from
|
|
3169
|
-
* `sessionUpdate` (the live session/update fan-in), never from
|
|
3170
|
-
* `seedSessionUpdates` — so batch load-replay never lands historical usage in
|
|
3171
|
-
* the current metrics window. Additionally guarded on a live `entry`: a stray
|
|
3172
|
-
* pending-restore frame (entry not yet registered) is skipped too, so replayed
|
|
3173
|
-
* history can't post a phantom burn spike with no model call.
|
|
3174
|
-
*
|
|
3175
|
-
* Usage rides an otherwise-empty `agent_message_chunk` as `update._meta.usage`
|
|
3176
|
-
* with per-round camelCase increments; subagent frames carry their own usage
|
|
3177
|
-
* (tagged `parentToolCallId`) and are independent turns, so counting each
|
|
3178
|
-
* frame once is the correct total. `_meta`/`usage` are optional and untyped.
|
|
3179
|
-
*/
|
|
3180
|
-
recordLiveTokenUsage(params, entry) {
|
|
3181
|
-
if (!this.onTokenUsage || !entry) return;
|
|
3182
|
-
const updateMeta = params.update._meta;
|
|
3183
|
-
const usage = updateMeta?.["usage"];
|
|
3184
|
-
if (usage === null || typeof usage !== "object") return;
|
|
3185
|
-
const inputTokens = usage.inputTokens;
|
|
3186
|
-
const outputTokens = usage.outputTokens;
|
|
3187
|
-
if (typeof inputTokens !== "number" && typeof outputTokens !== "number") {
|
|
3188
|
-
return;
|
|
3189
|
-
}
|
|
3190
|
-
const durationMs = updateMeta?.["durationMs"];
|
|
3191
|
-
const apiErrors = updateMeta?.["apiErrors"];
|
|
3192
|
-
const apiRetries = updateMeta?.["apiRetries"];
|
|
3193
|
-
this.onTokenUsage(
|
|
3194
|
-
typeof inputTokens === "number" ? inputTokens : 0,
|
|
3195
|
-
typeof outputTokens === "number" ? outputTokens : 0,
|
|
3196
|
-
typeof durationMs === "number" ? durationMs : void 0,
|
|
3197
|
-
typeof apiErrors === "number" ? apiErrors : 0,
|
|
3198
|
-
typeof apiRetries === "number" ? apiRetries : 0
|
|
3199
|
-
);
|
|
3200
|
-
}
|
|
3201
|
-
/**
|
|
3202
|
-
* Bounded early-event buffer. The map is scoped to this BridgeClient and
|
|
3203
|
-
* therefore to one channel; a stale channel cannot seed a fresh channel's
|
|
3204
|
-
* future session. Frames are keyed by sessionId; each entry tracks its
|
|
3205
|
-
* `expiresAt` for lazy TTL-based eviction in `bufferEarlyEvent`. Drained by
|
|
3206
|
-
* `drainEarlyEvents` whenever the bridge registers a session with a matching
|
|
3207
|
-
* id. See MAX_EARLY_EVENT_* constants for capacity bounds.
|
|
3208
|
-
*/
|
|
3209
|
-
earlyEvents = /* @__PURE__ */ new Map();
|
|
3210
|
-
/**
|
|
3211
|
-
* Tombstone for closed/killed session ids. Prevents late
|
|
3212
|
-
* `extNotification` from a dying child from leaking into the
|
|
3213
|
-
* early-event buffer and being replayed onto a future session
|
|
3214
|
-
* that reuses the same id via `session/load` or `session/resume`.
|
|
3215
|
-
*
|
|
3216
|
-
* Tombstone semantics:
|
|
3217
|
-
* - Marked when the bridge removes a sessionId from `byId` (kill
|
|
3218
|
-
* path, channel.exited handler, closeSession).
|
|
3219
|
-
* - Concurrently purges any in-flight `earlyEvents[id]`.
|
|
3220
|
-
* - `bufferEarlyEvent` rejects tombstoned ids.
|
|
3221
|
-
* - `drainEarlyEvents` clears the tombstone — a fresh
|
|
3222
|
-
* `createSessionEntry` for the same id is a legitimate
|
|
3223
|
-
* "load/resume of a persisted session id" case.
|
|
3224
|
-
* - TTL = `EARLY_EVENT_TTL_MS` (60s) — same as the early-event
|
|
3225
|
-
* buffer, so by the time a tombstone expires there can be no
|
|
3226
|
-
* stale frame for that id anywhere in the system.
|
|
3227
|
-
*/
|
|
3228
|
-
tombstonedSessionIds = /* @__PURE__ */ new Map();
|
|
3229
|
-
/** Restore ownership for `sessionUpdate` and artifact demultiplexing. */
|
|
3230
|
-
inFlightRestoreIds = /* @__PURE__ */ new Set();
|
|
3231
|
-
/**
|
|
3232
|
-
* Registrations allowed to buffer early extended notifications through an
|
|
3233
|
-
* ordinary close tombstone. This includes restores and caller-supplied-id
|
|
3234
|
-
* spawns, and lasts only until their ACP registration attempt settles.
|
|
3235
|
-
*/
|
|
3236
|
-
inFlightSessionRegistrationIds = /* @__PURE__ */ new Set();
|
|
3237
|
-
/**
|
|
3238
|
-
* Restore ids whose caller timed out while the non-cancellable ACP request
|
|
3239
|
-
* continues.
|
|
3240
|
-
*/
|
|
3241
|
-
abandonedRestoreIds = /* @__PURE__ */ new Set();
|
|
3242
|
-
/**
|
|
3243
|
-
* Handle child->bridge ACP `extMethod` requests (calls that expect a
|
|
3244
|
-
* response, unlike `extNotification`). Served methods:
|
|
3245
|
-
* `qwen/control/client_mcp/message` (reverse tool channel),
|
|
3246
|
-
* `qwen/control/create-sub-session` (the `create_sub_session` tool → daemon
|
|
3247
|
-
* spawns a sub-session and, for `'first-turn'`, returns its first-turn
|
|
3248
|
-
* result), and `craft/drainMidTurnQueue`: the ACP child calls the last one
|
|
3249
|
-
* between tool batches to pull any messages the browser queued mid-turn. We splice the per-session
|
|
3250
|
-
* queue, return them to the child as the response, and — when non-empty —
|
|
3251
|
-
* publish a `mid_turn_message_injected` SSE frame so the browser can move
|
|
3252
|
-
* those messages out of its pending queue and render the immediate echo.
|
|
3253
|
-
* Unknown methods reject with ACP `methodNotFound` (-32601), matching
|
|
3254
|
-
* the SDK's
|
|
3255
|
-
* default for an unimplemented client surface; the child's drain caller
|
|
3256
|
-
* treats that as "drain unsupported" and stops asking.
|
|
3257
|
-
*/
|
|
3258
|
-
async extMethod(method, params) {
|
|
3259
|
-
if (method === SERVE_CONTROL_EXT_METHODS.clientMcpMessage) {
|
|
3260
|
-
return this.handleClientMcpMessage(params);
|
|
3261
|
-
}
|
|
3262
|
-
if (method === SERVE_CONTROL_EXT_METHODS.createSubSession) {
|
|
3263
|
-
return this.handleCreateSubSession(params);
|
|
3264
|
-
}
|
|
3265
|
-
if (method === SERVE_CONTROL_EXT_METHODS.liveCaptureScreenContext) {
|
|
3266
|
-
return this.handleLiveScreenContextCapture(params);
|
|
3267
|
-
}
|
|
3268
|
-
if (method === SERVE_CONTROL_EXT_METHODS.liveTaskTool) {
|
|
3269
|
-
return this.handleLiveTaskTool(params);
|
|
3270
|
-
}
|
|
3271
|
-
if (method === SERVE_CONTROL_EXT_METHODS.liveSpeakToUser) {
|
|
3272
|
-
return this.handleLiveSpeakToUser(params);
|
|
3273
|
-
}
|
|
3274
|
-
if (method === SERVE_CONTROL_EXT_METHODS.channelDelivery) {
|
|
3275
|
-
return this.handleChannelDelivery(params);
|
|
3276
|
-
}
|
|
3277
|
-
if (method === SERVE_CONTROL_EXT_METHODS.externalToolGuardPrepare) {
|
|
3278
|
-
return this.handleExternalToolGuardPrepare(params);
|
|
3279
|
-
}
|
|
3280
|
-
if (method === TODO_STOP_GUARD_CONTINUATION_CLAIM_METHOD) {
|
|
3281
|
-
return this.handleTodoStopGuardContinuationClaim(params);
|
|
3282
|
-
}
|
|
3283
|
-
if (method !== MID_TURN_QUEUE_DRAIN_METHOD) {
|
|
3284
|
-
throw RequestError.methodNotFound(method);
|
|
3285
|
-
}
|
|
3286
|
-
const sessionId = typeof params["sessionId"] === "string" ? params["sessionId"] : void 0;
|
|
3287
|
-
if (!sessionId) return { messages: [], items: [], hasQueuedPrompt: false };
|
|
3288
|
-
if (!this.ownsSession(sessionId)) {
|
|
3289
|
-
return { messages: [], items: [], hasQueuedPrompt: false };
|
|
3290
|
-
}
|
|
3291
|
-
const entry = this.resolveEntry(sessionId);
|
|
3292
|
-
if (!entry) return { messages: [], items: [], hasQueuedPrompt: false };
|
|
3293
|
-
const drained = entry.midTurnMessageQueue.splice(0);
|
|
3294
|
-
if (drained.length > 0) {
|
|
3295
|
-
for (const item of drained) {
|
|
3296
|
-
entry.settledMidTurnMessageIds.push(item.messageId);
|
|
3297
|
-
}
|
|
3298
|
-
if (entry.settledMidTurnMessageIds.length > MID_TURN_RECONCILIATION_RING_SIZE) {
|
|
3299
|
-
entry.settledMidTurnMessageIds.splice(
|
|
3300
|
-
0,
|
|
3301
|
-
entry.settledMidTurnMessageIds.length - MID_TURN_RECONCILIATION_RING_SIZE
|
|
3302
|
-
);
|
|
3303
|
-
}
|
|
3304
|
-
}
|
|
3305
|
-
const attachmentMemo = /* @__PURE__ */ new Map();
|
|
3306
|
-
const serializedAttachmentIds = /* @__PURE__ */ new Set();
|
|
3307
|
-
const items = [];
|
|
3308
|
-
try {
|
|
3309
|
-
for (const item of drained) {
|
|
3310
|
-
let degraded = 0;
|
|
3311
|
-
const planned = (item.content ?? []).filter((block) => {
|
|
3312
|
-
if (!isSessionAttachmentReference(block)) return true;
|
|
3313
|
-
if (serializedAttachmentIds.has(block.attachmentId)) {
|
|
3314
|
-
degraded += 1;
|
|
3315
|
-
return false;
|
|
3316
|
-
}
|
|
3317
|
-
serializedAttachmentIds.add(block.attachmentId);
|
|
3318
|
-
return true;
|
|
3319
|
-
});
|
|
3320
|
-
let resolvedBlocks;
|
|
3321
|
-
let attachmentReferences;
|
|
3322
|
-
try {
|
|
3323
|
-
resolvedBlocks = await entry.attachments.resolveContent(
|
|
3324
|
-
planned,
|
|
3325
|
-
attachmentMemo
|
|
3326
|
-
);
|
|
3327
|
-
attachmentReferences = planned.filter(isSessionAttachmentReference);
|
|
3328
|
-
} catch (error) {
|
|
3329
|
-
if (!(error instanceof SessionAttachmentReferenceError)) throw error;
|
|
3330
|
-
writeStderrLine(
|
|
3331
|
-
`[mid-turn] session=${JSON.stringify(entry.sessionId)} degraded attachment for message ${JSON.stringify(item.messageId)}: ${JSON.stringify(error instanceof Error ? error.message : String(error))}`
|
|
3332
|
-
);
|
|
3333
|
-
const perBlock = await entry.attachments.resolveContentDegrading(
|
|
3334
|
-
planned,
|
|
3335
|
-
attachmentMemo
|
|
3336
|
-
);
|
|
3337
|
-
resolvedBlocks = perBlock.resolvedBlocks;
|
|
3338
|
-
attachmentReferences = perBlock.retainedBlocks.filter(
|
|
3339
|
-
isSessionAttachmentReference
|
|
3340
|
-
);
|
|
3341
|
-
degraded += perBlock.degraded;
|
|
3342
|
-
}
|
|
3343
|
-
let content = [
|
|
3344
|
-
...item.text ? [{ type: "text", text: item.text }] : [],
|
|
3345
|
-
...resolvedBlocks
|
|
3346
|
-
];
|
|
3347
|
-
if (degraded > 0) content = withAttachmentDegradationMarker(content);
|
|
3348
|
-
items.push({
|
|
3349
|
-
messageId: item.messageId,
|
|
3350
|
-
displayText: item.text,
|
|
3351
|
-
content,
|
|
3352
|
-
...attachmentReferences.length > 0 ? { attachmentReferences } : {}
|
|
3353
|
-
});
|
|
3354
|
-
}
|
|
3355
|
-
} catch (error) {
|
|
3356
|
-
const requeued = new Set(drained.map((queued) => queued.messageId));
|
|
3357
|
-
const ring = entry.settledMidTurnMessageIds;
|
|
3358
|
-
const kept = ring.filter((id) => !requeued.has(id));
|
|
3359
|
-
ring.splice(0, ring.length, ...kept);
|
|
3360
|
-
entry.midTurnMessageQueue.unshift(...drained);
|
|
3361
|
-
writeStderrLine(
|
|
3362
|
-
`[mid-turn] session=${JSON.stringify(entry.sessionId)} drain failed, requeued ${drained.length} message(s): ${JSON.stringify(error instanceof Error ? error.message : String(error))}`
|
|
3363
|
-
);
|
|
3364
|
-
throw error;
|
|
3365
|
-
}
|
|
3366
|
-
const echoed = drained.filter((item) => !item.queueOnly);
|
|
3367
|
-
const messages = drained.map((item) => item.text);
|
|
3368
|
-
const hasQueuedPrompt = entry.pendingPromptList.some(
|
|
3369
|
-
(prompt) => prompt.state === "queued" && !prompt.abortController.signal.aborted
|
|
3370
|
-
);
|
|
3371
|
-
if (echoed.length > 0) {
|
|
3372
|
-
const published = entry.events.publish({
|
|
3373
|
-
type: MID_TURN_MESSAGE_INJECTED_EVENT,
|
|
3374
|
-
...entry.activePromptId ? { promptId: entry.activePromptId } : {},
|
|
3375
|
-
data: {
|
|
3376
|
-
sessionId: entry.sessionId,
|
|
3377
|
-
messages: echoed.map((item) => item.text),
|
|
3378
|
-
messageIds: echoed.map((item) => item.messageId),
|
|
3379
|
-
// Carry the structured `items` twin (content blocks per message) so
|
|
3380
|
-
// the browser-side echo renderer can show attached images alongside
|
|
3381
|
-
// the message text. Older consumers that don't read this field keep
|
|
3382
|
-
// working unchanged.
|
|
3383
|
-
items: echoed.map((item) => ({
|
|
3384
|
-
...item.content && item.content.length > 0 ? { content: item.content } : {}
|
|
3385
|
-
}))
|
|
3386
|
-
}
|
|
3387
|
-
});
|
|
3388
|
-
writeStderrLine(
|
|
3389
|
-
published ? `[mid-turn] session=${entry.sessionId} drained=${messages.length} echoed=${echoed.length} injected into running turn` : `[mid-turn] session=${entry.sessionId} drained=${messages.length} echoed=${echoed.length} echo frame dropped (bus closed); reconciliation required`
|
|
3390
|
-
);
|
|
3391
|
-
}
|
|
3392
|
-
return { messages, items, hasQueuedPrompt };
|
|
3393
|
-
}
|
|
3394
|
-
async handleExternalToolGuardPrepare(params) {
|
|
3395
|
-
if (!this.externalToolGuard) {
|
|
3396
|
-
throw RequestError.methodNotFound(
|
|
3397
|
-
SERVE_CONTROL_EXT_METHODS.externalToolGuardPrepare
|
|
3398
|
-
);
|
|
3399
|
-
}
|
|
3400
|
-
const sessionId = params["sessionId"];
|
|
3401
|
-
const promptId = params["promptId"];
|
|
3402
|
-
const toolCallId = params["toolCallId"];
|
|
3403
|
-
const toolName = params["toolName"];
|
|
3404
|
-
const args = params["arguments"];
|
|
3405
|
-
if (typeof sessionId !== "string" || sessionId.length === 0 || promptId !== void 0 && (typeof promptId !== "string" || promptId.length === 0) || typeof toolCallId !== "string" || toolCallId.length === 0 || typeof toolName !== "string" || toolName.length === 0 || !isRecord2(args)) {
|
|
3406
|
-
throw RequestError.invalidParams(
|
|
3407
|
-
void 0,
|
|
3408
|
-
"Invalid external tool guard request"
|
|
3409
|
-
);
|
|
3410
|
-
}
|
|
3411
|
-
const promptScoped = promptId !== void 0;
|
|
3412
|
-
if (!this.ownsSession(sessionId)) {
|
|
3413
|
-
throw RequestError.invalidParams(
|
|
3414
|
-
void 0,
|
|
3415
|
-
"External tool guard session is not owned by this connection"
|
|
3416
|
-
);
|
|
3417
|
-
}
|
|
3418
|
-
const entry = this.resolveEntry(sessionId);
|
|
3419
|
-
if (!entry || promptScoped && (!entry.promptActive || entry.activePromptId !== promptId)) {
|
|
3420
|
-
throw RequestError.invalidParams(
|
|
3421
|
-
void 0,
|
|
3422
|
-
"External tool guard prompt is not the active prompt"
|
|
3423
|
-
);
|
|
3424
|
-
}
|
|
3425
|
-
const invocationCwd = params["invocationCwd"];
|
|
3426
|
-
const decision = await this.externalToolGuard({
|
|
3427
|
-
sessionId: entry.sessionId,
|
|
3428
|
-
...promptScoped ? { promptId } : {},
|
|
3429
|
-
toolCallId,
|
|
3430
|
-
toolName,
|
|
3431
|
-
arguments: args,
|
|
3432
|
-
effectiveCwd: entry.effectiveCwd,
|
|
3433
|
-
// Forwarded verbatim and explicitly untrusted: the host policy decides
|
|
3434
|
-
// whether it can establish this scope from state it owns.
|
|
3435
|
-
...typeof invocationCwd === "string" && invocationCwd.length > 0 ? { invocationCwd } : {}
|
|
3436
|
-
});
|
|
3437
|
-
const currentEntry = this.resolveEntry(sessionId);
|
|
3438
|
-
if (!this.ownsSession(sessionId) || currentEntry !== entry || promptScoped && (!currentEntry.promptActive || currentEntry.activePromptId !== promptId)) {
|
|
3439
|
-
throw RequestError.invalidParams(
|
|
3440
|
-
void 0,
|
|
3441
|
-
"External tool guard prompt is no longer active"
|
|
3442
|
-
);
|
|
3443
|
-
}
|
|
3444
|
-
return normalizeExternalToolGuardResult(decision);
|
|
3445
|
-
}
|
|
3446
|
-
handleTodoStopGuardContinuationClaim(params) {
|
|
3447
|
-
const sessionId = typeof params["sessionId"] === "string" && params["sessionId"].length > 0 ? params["sessionId"] : void 0;
|
|
3448
|
-
if (!sessionId) return { claimed: false, hasQueuedPrompt: false };
|
|
3449
|
-
if (!this.ownsSession(sessionId)) {
|
|
3450
|
-
return { claimed: false, hasQueuedPrompt: false };
|
|
3451
|
-
}
|
|
3452
|
-
const entry = this.resolveEntry(sessionId);
|
|
3453
|
-
if (!entry) return { claimed: false, hasQueuedPrompt: false };
|
|
3454
|
-
const livePrompts = entry.pendingPromptList.filter(
|
|
3455
|
-
(prompt) => !prompt.abortController.signal.aborted
|
|
3456
|
-
);
|
|
3457
|
-
const promptId = typeof params["promptId"] === "string" && params["promptId"].length > 0 ? params["promptId"] : void 0;
|
|
3458
|
-
if (promptId) {
|
|
3459
|
-
const ownsRunningPrompt = entry.activePromptId === promptId && livePrompts.some(
|
|
3460
|
-
(prompt) => prompt.promptId === promptId && prompt.state === "running"
|
|
3461
|
-
);
|
|
3462
|
-
const hasCompetingRunningPrompt = livePrompts.some(
|
|
3463
|
-
(prompt) => prompt.promptId !== promptId && prompt.state === "running"
|
|
3464
|
-
);
|
|
3465
|
-
if (!ownsRunningPrompt || hasCompetingRunningPrompt) {
|
|
3466
|
-
return { claimed: false, hasQueuedPrompt: false };
|
|
3467
|
-
}
|
|
3468
|
-
const hasQueuedPrompt = livePrompts.some(
|
|
3469
|
-
(prompt) => prompt.state === "queued"
|
|
3470
|
-
);
|
|
3471
|
-
if (hasQueuedPrompt) {
|
|
3472
|
-
entry.todoStopGuardAwaitingQueuedPromptOwnerPromptId = promptId;
|
|
3473
|
-
return { claimed: false, hasQueuedPrompt: true };
|
|
3474
|
-
}
|
|
3475
|
-
if (entry.todoStopGuardAwaitingQueuedPromptOwnerPromptId === promptId) {
|
|
3476
|
-
delete entry.todoStopGuardAwaitingQueuedPromptOwnerPromptId;
|
|
3477
|
-
}
|
|
3478
|
-
return { claimed: true, hasQueuedPrompt: false };
|
|
3479
|
-
}
|
|
3480
|
-
if (entry.promptActive || livePrompts.length > 0) {
|
|
3481
|
-
return { claimed: false, hasQueuedPrompt: false };
|
|
3482
|
-
}
|
|
3483
|
-
return { claimed: true, hasQueuedPrompt: false };
|
|
3484
|
-
}
|
|
3485
|
-
async handleChannelDelivery(params) {
|
|
3486
|
-
if (!this.onChannelDelivery) {
|
|
3487
|
-
throw RequestError.methodNotFound(
|
|
3488
|
-
SERVE_CONTROL_EXT_METHODS.channelDelivery
|
|
3489
|
-
);
|
|
3490
|
-
}
|
|
3491
|
-
const sessionId = params["sessionId"];
|
|
3492
|
-
if (typeof sessionId !== "string" || sessionId.length === 0 || !this.ownsSession(sessionId)) {
|
|
3493
|
-
throw RequestError.invalidParams(
|
|
3494
|
-
void 0,
|
|
3495
|
-
"`sessionId` must name a session owned by this connection"
|
|
3496
|
-
);
|
|
3497
|
-
}
|
|
3498
|
-
const entry = this.resolveEntry(sessionId);
|
|
3499
|
-
if (!entry) {
|
|
3500
|
-
throw RequestError.invalidParams(void 0, "Unknown `sessionId`");
|
|
3501
|
-
}
|
|
3502
|
-
const deliveryId = params["deliveryId"];
|
|
3503
|
-
const source = params["source"];
|
|
3504
|
-
const text = params["text"];
|
|
3505
|
-
const rawTarget = params["target"];
|
|
3506
|
-
if (!isBoundedChannelDeliveryString(deliveryId) || source !== "prompt" && source !== "scheduled" || typeof text !== "string" || text.length > MAX_CHANNEL_DELIVERY_TEXT_CHARS || !isChannelDeliveryTarget(rawTarget)) {
|
|
3507
|
-
throw RequestError.invalidParams(
|
|
3508
|
-
void 0,
|
|
3509
|
-
"Invalid channel delivery request"
|
|
3510
|
-
);
|
|
3511
|
-
}
|
|
3512
|
-
const promptId = params["promptId"];
|
|
3513
|
-
const taskId = params["taskId"];
|
|
3514
|
-
const firedAt = params["firedAt"];
|
|
3515
|
-
const allowedKeys = source === "prompt" ? /* @__PURE__ */ new Set([
|
|
3516
|
-
"sessionId",
|
|
3517
|
-
"deliveryId",
|
|
3518
|
-
"source",
|
|
3519
|
-
"target",
|
|
3520
|
-
"text",
|
|
3521
|
-
"promptId"
|
|
3522
|
-
]) : /* @__PURE__ */ new Set([
|
|
3523
|
-
"sessionId",
|
|
3524
|
-
"deliveryId",
|
|
3525
|
-
"source",
|
|
3526
|
-
"target",
|
|
3527
|
-
"text",
|
|
3528
|
-
"taskId",
|
|
3529
|
-
"firedAt"
|
|
3530
|
-
]);
|
|
3531
|
-
const correlationValid = source === "prompt" ? isBoundedChannelDeliveryString(promptId) && promptId === deliveryId : isBoundedChannelDeliveryString(taskId) && typeof firedAt === "number" && Number.isFinite(firedAt) && deliveryId === `${taskId}:${firedAt}`;
|
|
3532
|
-
if (!correlationValid || !Object.keys(params).every((key) => allowedKeys.has(key))) {
|
|
3533
|
-
throw RequestError.invalidParams(
|
|
3534
|
-
void 0,
|
|
3535
|
-
"Invalid channel delivery correlation"
|
|
3536
|
-
);
|
|
3537
|
-
}
|
|
3538
|
-
const info = {
|
|
3539
|
-
sessionId,
|
|
3540
|
-
deliveryId,
|
|
3541
|
-
source,
|
|
3542
|
-
target: rawTarget,
|
|
3543
|
-
text,
|
|
3544
|
-
...source === "prompt" ? { promptId } : {},
|
|
3545
|
-
...source === "scheduled" ? { taskId, firedAt } : {}
|
|
3546
|
-
};
|
|
3547
|
-
let result;
|
|
3548
|
-
try {
|
|
3549
|
-
result = normalizeChannelDeliveryHostResult(
|
|
3550
|
-
await this.onChannelDelivery(info)
|
|
3551
|
-
);
|
|
3552
|
-
} catch {
|
|
3553
|
-
result = {
|
|
3554
|
-
status: "failed",
|
|
3555
|
-
code: "channel_delivery_failed",
|
|
3556
|
-
error: "Channel delivery failed."
|
|
3557
|
-
};
|
|
3558
|
-
}
|
|
3559
|
-
try {
|
|
3560
|
-
entry.events.publish({
|
|
3561
|
-
type: "channel_delivery_result",
|
|
3562
|
-
...source === "prompt" ? { promptId } : {},
|
|
3563
|
-
data: {
|
|
3564
|
-
sessionId,
|
|
3565
|
-
deliveryId,
|
|
3566
|
-
source,
|
|
3567
|
-
status: result.status,
|
|
3568
|
-
...source === "prompt" ? { promptId } : {},
|
|
3569
|
-
...source === "scheduled" ? { taskId, firedAt } : {},
|
|
3570
|
-
...result.status === "failed" ? { code: result.code, error: result.error } : {}
|
|
3571
|
-
}
|
|
3572
|
-
});
|
|
3573
|
-
} catch {
|
|
3574
|
-
}
|
|
3575
|
-
return result;
|
|
3576
|
-
}
|
|
3577
|
-
/**
|
|
3578
|
-
* Reverse tool channel (issue #5626, Phase 2) — answer the child's
|
|
3579
|
-
* `qwen/control/client_mcp/message` ext-method. The child's session
|
|
3580
|
-
* `McpClientManager` calls this when its agent drives a client-hosted
|
|
3581
|
-
* (extension) MCP server: `params` carries the advertised `server` name and
|
|
3582
|
-
* the JSON-RPC `payload` (initialize / tools/list / tools/call / a
|
|
3583
|
-
* notification). We resolve the per-WS-connection sender via the injected
|
|
3584
|
-
* `clientMcpSender` lookup, deliver the payload over the daemon WS, and
|
|
3585
|
-
* return the correlated response as `{ payload }`.
|
|
3586
|
-
*
|
|
3587
|
-
* Rejects with ACP `methodNotFound` when no `clientMcpSender` is wired (Mode
|
|
3588
|
-
* A / tests can't host a client MCP server), and `invalidParams` when the
|
|
3589
|
-
* frame is malformed or the named server is no longer hosted (e.g. the
|
|
3590
|
-
* extension disconnected mid-turn) — the agent's `SdkControlClientTransport`
|
|
3591
|
-
* surfaces that as a transport error rather than hanging.
|
|
3592
|
-
*/
|
|
3593
|
-
async handleClientMcpMessage(params) {
|
|
3594
|
-
if (!this.clientMcpSender) {
|
|
3595
|
-
throw RequestError.methodNotFound(
|
|
3596
|
-
SERVE_CONTROL_EXT_METHODS.clientMcpMessage
|
|
3597
|
-
);
|
|
3598
|
-
}
|
|
3599
|
-
const server = params["server"];
|
|
3600
|
-
if (typeof server !== "string" || server.length === 0) {
|
|
3601
|
-
throw RequestError.invalidParams(
|
|
3602
|
-
void 0,
|
|
3603
|
-
"`server` must be a non-empty string"
|
|
3604
|
-
);
|
|
3605
|
-
}
|
|
3606
|
-
const payload = params["payload"];
|
|
3607
|
-
if (payload === null || typeof payload !== "object") {
|
|
3608
|
-
throw RequestError.invalidParams(
|
|
3609
|
-
void 0,
|
|
3610
|
-
"`payload` must be a JSON-RPC message object"
|
|
3611
|
-
);
|
|
3612
|
-
}
|
|
3613
|
-
const send = this.clientMcpSender(server);
|
|
3614
|
-
if (!send) {
|
|
3615
|
-
throw RequestError.invalidParams(
|
|
3616
|
-
void 0,
|
|
3617
|
-
`client-hosted MCP server '${server}' is not currently connected`
|
|
3618
|
-
);
|
|
3619
|
-
}
|
|
3620
|
-
const sessionId = params["sessionId"];
|
|
3621
|
-
if (sessionId !== void 0 && (typeof sessionId !== "string" || sessionId.length === 0)) {
|
|
3622
|
-
throw RequestError.invalidParams(
|
|
3623
|
-
void 0,
|
|
3624
|
-
"`sessionId` must be a non-empty string when provided"
|
|
3625
|
-
);
|
|
3626
|
-
}
|
|
3627
|
-
const ownsSession = typeof sessionId === "string" && this.ownsSession(sessionId);
|
|
3628
|
-
if (typeof sessionId === "string" && !ownsSession && !this.hasSessionSpawnInFlight()) {
|
|
3629
|
-
throw RequestError.invalidParams(
|
|
3630
|
-
void 0,
|
|
3631
|
-
`Session not owned by this channel: ${sessionId}`
|
|
3632
|
-
);
|
|
3633
|
-
}
|
|
3634
|
-
if (typeof sessionId === "string" && !ownsSession) {
|
|
3635
|
-
writeStderrLine(
|
|
3636
|
-
`[demux] session=${sessionId} type=client_mcp_message action=forwarded_without_session reason=session_registration_pending`
|
|
3637
|
-
);
|
|
3638
|
-
}
|
|
3639
|
-
const response = await send(
|
|
3640
|
-
payload,
|
|
3641
|
-
typeof sessionId === "string" && ownsSession ? { sessionId } : void 0
|
|
3642
|
-
);
|
|
3643
|
-
return { payload: response };
|
|
3644
|
-
}
|
|
3645
|
-
/**
|
|
3646
|
-
* Handle the `create_sub_session` tool's request: validate, then forward to
|
|
3647
|
-
* the daemon-host `onCreateSubSession` callback (which spawns a fresh
|
|
3648
|
-
* top-level sub-session and, for `'first-turn'`, waits for its first turn and
|
|
3649
|
-
* returns the result). No host wired → `methodNotFound`, which the tool
|
|
3650
|
-
* surfaces as "daemon-only".
|
|
3651
|
-
*/
|
|
3652
|
-
async handleCreateSubSession(params) {
|
|
3653
|
-
if (!this.onCreateSubSession) {
|
|
3654
|
-
throw RequestError.methodNotFound(
|
|
3655
|
-
SERVE_CONTROL_EXT_METHODS.createSubSession
|
|
3656
|
-
);
|
|
3657
|
-
}
|
|
3658
|
-
const prompt = params["prompt"];
|
|
3659
|
-
if (typeof prompt !== "string" || prompt.length === 0) {
|
|
3660
|
-
throw RequestError.invalidParams(
|
|
3661
|
-
void 0,
|
|
3662
|
-
"`prompt` must be a non-empty string"
|
|
3663
|
-
);
|
|
3664
|
-
}
|
|
3665
|
-
if (prompt.length > MAX_SUB_SESSION_PROMPT_CHARS) {
|
|
3666
|
-
throw RequestError.invalidParams(
|
|
3667
|
-
void 0,
|
|
3668
|
-
`\`prompt\` exceeds the ${MAX_SUB_SESSION_PROMPT_CHARS}-character limit`
|
|
3669
|
-
);
|
|
3670
|
-
}
|
|
3671
|
-
const completion = params["completion"];
|
|
3672
|
-
if (completion !== "sent" && completion !== "first-turn") {
|
|
3673
|
-
throw RequestError.invalidParams(
|
|
3674
|
-
void 0,
|
|
3675
|
-
"`completion` must be 'sent' or 'first-turn'"
|
|
3676
|
-
);
|
|
3677
|
-
}
|
|
3678
|
-
const name = params["name"];
|
|
3679
|
-
if (typeof name === "string" && name.length > MAX_SUB_SESSION_NAME_CHARS) {
|
|
3680
|
-
throw RequestError.invalidParams(
|
|
3681
|
-
void 0,
|
|
3682
|
-
`\`name\` exceeds the ${MAX_SUB_SESSION_NAME_CHARS}-character limit`
|
|
3683
|
-
);
|
|
3684
|
-
}
|
|
3685
|
-
const callerSessionId = params["callerSessionId"];
|
|
3686
|
-
if (typeof callerSessionId !== "string" || callerSessionId.length === 0 || !this.ownsSession(callerSessionId)) {
|
|
3687
|
-
throw RequestError.invalidParams(
|
|
3688
|
-
void 0,
|
|
3689
|
-
"`callerSessionId` is required and must name a session owned by this connection"
|
|
3690
|
-
);
|
|
3691
|
-
}
|
|
3692
|
-
const model = params["model"];
|
|
3693
|
-
const result = await this.onCreateSubSession({
|
|
3694
|
-
prompt,
|
|
3695
|
-
completion,
|
|
3696
|
-
...typeof model === "string" && model.length > 0 && model.length <= 128 ? { model } : {},
|
|
3697
|
-
...typeof name === "string" && name.length > 0 ? { name } : {},
|
|
3698
|
-
callerSessionId
|
|
3699
|
-
});
|
|
3700
|
-
return {
|
|
3701
|
-
sessionId: result.sessionId,
|
|
3702
|
-
...result.result !== void 0 ? { result: result.result } : {},
|
|
3703
|
-
...result.stopReason !== void 0 ? { stopReason: result.stopReason } : {},
|
|
3704
|
-
...result.parentSessionPersisted !== void 0 ? { parentSessionPersisted: result.parentSessionPersisted } : {}
|
|
3705
|
-
};
|
|
3706
|
-
}
|
|
3707
|
-
async handleLiveScreenContextCapture(params) {
|
|
3708
|
-
const handler = this.getLiveScreenContextCaptureHandler();
|
|
3709
|
-
if (!handler) {
|
|
3710
|
-
throw RequestError.methodNotFound(
|
|
3711
|
-
SERVE_CONTROL_EXT_METHODS.liveCaptureScreenContext
|
|
3712
|
-
);
|
|
3713
|
-
}
|
|
3714
|
-
const callerSessionId = params["callerSessionId"];
|
|
3715
|
-
if (typeof callerSessionId !== "string" || callerSessionId.length === 0 || !this.ownsSession(callerSessionId)) {
|
|
3716
|
-
throw RequestError.invalidParams(
|
|
3717
|
-
void 0,
|
|
3718
|
-
"`callerSessionId` is required and must name a session owned by this connection"
|
|
3719
|
-
);
|
|
3720
|
-
}
|
|
3721
|
-
const result = await handler({ callerSessionId });
|
|
3722
|
-
if (result.appName.length === 0 || result.appName.length > 512 || result.windowTitle !== void 0 && result.windowTitle.length > 2048 || result.accessibilityText.length > MAX_LIVE_SCREEN_CONTEXT_TEXT_CHARS || result.screenshotPath.length === 0 || result.screenshotPath.length > 4096) {
|
|
3723
|
-
throw RequestError.internalError(void 0, "Invalid Appshot result.");
|
|
3724
|
-
}
|
|
3725
|
-
return {
|
|
3726
|
-
appName: result.appName,
|
|
3727
|
-
...result.windowTitle ? { windowTitle: result.windowTitle } : {},
|
|
3728
|
-
accessibilityText: result.accessibilityText,
|
|
3729
|
-
screenshotPath: result.screenshotPath
|
|
3730
|
-
};
|
|
3731
|
-
}
|
|
3732
|
-
async handleLiveTaskTool(params) {
|
|
3733
|
-
const handler = this.getLiveTaskToolRequestHandler();
|
|
3734
|
-
if (!handler) {
|
|
3735
|
-
throw RequestError.methodNotFound(SERVE_CONTROL_EXT_METHODS.liveTaskTool);
|
|
3736
|
-
}
|
|
3737
|
-
const callerSessionId = params["callerSessionId"];
|
|
3738
|
-
const name = params["name"];
|
|
3739
|
-
const args = params["arguments"];
|
|
3740
|
-
if (typeof callerSessionId !== "string" || callerSessionId.length === 0 || !this.ownsSession(callerSessionId) || typeof name !== "string" || !LIVE_TASK_TOOL_NAMES.includes(
|
|
3741
|
-
name
|
|
3742
|
-
) || typeof args !== "object" || args === null || Array.isArray(args)) {
|
|
3743
|
-
throw RequestError.invalidParams(
|
|
3744
|
-
void 0,
|
|
3745
|
-
"Invalid Live task-tool request."
|
|
3746
|
-
);
|
|
3747
|
-
}
|
|
3748
|
-
return handler({
|
|
3749
|
-
callerSessionId,
|
|
3750
|
-
name,
|
|
3751
|
-
arguments: args
|
|
3752
|
-
});
|
|
3753
|
-
}
|
|
3754
|
-
async handleLiveSpeakToUser(params) {
|
|
3755
|
-
const handler = this.getLiveSpeakToUserHandler();
|
|
3756
|
-
if (!handler) {
|
|
3757
|
-
throw RequestError.methodNotFound(
|
|
3758
|
-
SERVE_CONTROL_EXT_METHODS.liveSpeakToUser
|
|
3759
|
-
);
|
|
3760
|
-
}
|
|
3761
|
-
const callerSessionId = params["callerSessionId"];
|
|
3762
|
-
const message = params["message"];
|
|
3763
|
-
if (typeof callerSessionId !== "string" || callerSessionId.length === 0 || !this.ownsSession(callerSessionId) || typeof message !== "string" || message.trim().length === 0 || message.length > MAX_LIVE_SPEAK_TO_USER_MESSAGE_CHARS) {
|
|
3764
|
-
throw RequestError.invalidParams(
|
|
3765
|
-
void 0,
|
|
3766
|
-
"Invalid Live speak-to-user request."
|
|
3767
|
-
);
|
|
3768
|
-
}
|
|
3769
|
-
await handler({ callerSessionId, message });
|
|
3770
|
-
return { accepted: true };
|
|
3771
|
-
}
|
|
3772
|
-
/**
|
|
3773
|
-
* Handle child->bridge ACP `extNotification` calls. Recognized methods are
|
|
3774
|
-
* `qwen/notify/session/model-update`,
|
|
3775
|
-
* `qwen/notify/session/mode-update`,
|
|
3776
|
-
* `qwen/notify/session/title-update` (auto/in-process session titles),
|
|
3777
|
-
* `qwen/notify/session/recording-degraded`,
|
|
3778
|
-
* `qwen/notify/session/prompt-suggestion` (followup assist),
|
|
3779
|
-
* `qwen/notify/session/artifact-event` (hook artifacts),
|
|
3780
|
-
* `qwen/notify/session/terminal-sequence`, and
|
|
3781
|
-
* `_qwencode/end_turn` (background-notification and goal turns), and
|
|
3782
|
-
* `qwen/notify/session/mcp-budget-event` — each translated into a
|
|
3783
|
-
* session-scoped SSE frame. Unknown methods are dropped silently for
|
|
3784
|
-
* forward-compat.
|
|
3785
|
-
*/
|
|
3786
|
-
async extNotification(method, params) {
|
|
3787
|
-
const notificationSessionId = params["sessionId"];
|
|
3788
|
-
if (typeof notificationSessionId === "string" && this.abandonedRestoreIds.has(notificationSessionId)) {
|
|
3789
|
-
return;
|
|
3790
|
-
}
|
|
3791
|
-
if (method === ACTIVE_WORK_NOTIFICATION_METHOD) {
|
|
3792
|
-
const snapshot = parseActiveWorkSnapshot(params);
|
|
3793
|
-
if (snapshot) {
|
|
3794
|
-
this.onActiveWork?.({
|
|
3795
|
-
v: ACTIVE_WORK_HEARTBEAT_VERSION,
|
|
3796
|
-
seq: snapshot.seq,
|
|
3797
|
-
sessions: snapshot.sessions.filter(
|
|
3798
|
-
(session) => this.ownsSession(session.sessionId)
|
|
3799
|
-
)
|
|
3800
|
-
});
|
|
3801
|
-
}
|
|
3802
|
-
return;
|
|
3803
|
-
}
|
|
3804
|
-
if (method === "_qwencode/start_turn") {
|
|
3805
|
-
const sessionId2 = params["sessionId"];
|
|
3806
|
-
if (typeof sessionId2 !== "string" || sessionId2.length === 0 || params["source"] !== "goal") {
|
|
3807
|
-
return;
|
|
3808
|
-
}
|
|
3809
|
-
const entry = this.resolveEntry(sessionId2);
|
|
3810
|
-
if (!entry || !this.ownsSession(sessionId2)) return;
|
|
3811
|
-
entry.goalTurnActive = true;
|
|
3812
|
-
return;
|
|
3813
|
-
}
|
|
3814
|
-
if (method === "_qwencode/end_turn") {
|
|
3815
|
-
const sessionId2 = params["sessionId"];
|
|
3816
|
-
const reason = params["reason"];
|
|
3817
|
-
const source = params["source"];
|
|
3818
|
-
if (typeof sessionId2 !== "string" || sessionId2.length === 0 || typeof reason !== "string" || reason.length === 0 || reason.length > 128 || source !== "background_notification" && source !== "goal") {
|
|
3819
|
-
return;
|
|
3820
|
-
}
|
|
3821
|
-
const entry = this.resolveEntry(sessionId2);
|
|
3822
|
-
if (!entry || !this.ownsSession(sessionId2)) return;
|
|
3823
|
-
if (source === "goal") {
|
|
3824
|
-
entry.goalTurnActive = false;
|
|
3825
|
-
this.onGoalTurnEnded?.(sessionId2);
|
|
3826
|
-
const promptId = params["promptId"];
|
|
3827
|
-
if (typeof promptId !== "string" || promptId.length === 0 || promptId.length > 256) {
|
|
3828
|
-
return;
|
|
3829
|
-
}
|
|
3830
|
-
entry.events.publish({
|
|
3831
|
-
type: "turn_complete",
|
|
3832
|
-
promptId,
|
|
3833
|
-
data: { sessionId: sessionId2, stopReason: reason, promptId }
|
|
3834
|
-
});
|
|
3835
|
-
return;
|
|
3836
|
-
}
|
|
3837
|
-
entry.events.publish({
|
|
3838
|
-
type: "background_notification_turn_complete",
|
|
3839
|
-
data: { sessionId: sessionId2, reason }
|
|
3840
|
-
});
|
|
3841
|
-
return;
|
|
3842
|
-
}
|
|
3843
|
-
if (method === "qwen/notify/session/generation/event") {
|
|
3844
|
-
const sessionId2 = params["sessionId"];
|
|
3845
|
-
const requestId = params["requestId"];
|
|
3846
|
-
const event = params["event"];
|
|
3847
|
-
if (params["v"] !== 1 || typeof sessionId2 !== "string" || typeof requestId !== "string" || !event || typeof event !== "object" || Array.isArray(event)) {
|
|
3848
|
-
return;
|
|
3849
|
-
}
|
|
3850
|
-
const record = event;
|
|
3851
|
-
if (record["type"] === "started") {
|
|
3852
|
-
const model = record["model"];
|
|
3853
|
-
const modelSource = record["modelSource"];
|
|
3854
|
-
if (typeof model !== "string" || modelSource !== "fast" && modelSource !== "main") {
|
|
3855
|
-
return;
|
|
3856
|
-
}
|
|
3857
|
-
this.onGenerationEvent?.(sessionId2, {
|
|
3858
|
-
type: "started",
|
|
3859
|
-
requestId,
|
|
3860
|
-
model,
|
|
3861
|
-
modelSource
|
|
3862
|
-
});
|
|
3863
|
-
return;
|
|
3864
|
-
}
|
|
3865
|
-
if (record["type"] === "thinking") {
|
|
3866
|
-
this.onGenerationEvent?.(sessionId2, {
|
|
3867
|
-
type: "thinking",
|
|
3868
|
-
requestId
|
|
3869
|
-
});
|
|
3870
|
-
return;
|
|
3871
|
-
}
|
|
3872
|
-
if (record["type"] === "delta") {
|
|
3873
|
-
const seq = record["seq"];
|
|
3874
|
-
const text = record["text"];
|
|
3875
|
-
if (typeof seq !== "number" || !Number.isSafeInteger(seq) || seq < 0 || typeof text !== "string" || text.length === 0) {
|
|
3876
|
-
return;
|
|
3877
|
-
}
|
|
3878
|
-
this.onGenerationEvent?.(sessionId2, {
|
|
3879
|
-
type: "delta",
|
|
3880
|
-
requestId,
|
|
3881
|
-
seq,
|
|
3882
|
-
text
|
|
3883
|
-
});
|
|
3884
|
-
return;
|
|
3885
|
-
}
|
|
3886
|
-
return;
|
|
3887
|
-
}
|
|
3888
|
-
if (method === "qwen/notify/workspace/generation/event") {
|
|
3889
|
-
const requestId = params["requestId"];
|
|
3890
|
-
const event = params["event"];
|
|
3891
|
-
if (params["v"] !== 1 || typeof requestId !== "string" || !event || typeof event !== "object" || Array.isArray(event)) {
|
|
3892
|
-
return;
|
|
3893
|
-
}
|
|
3894
|
-
const record = event;
|
|
3895
|
-
if (record["type"] === "started") {
|
|
3896
|
-
const model = record["model"];
|
|
3897
|
-
const modelSource = record["modelSource"];
|
|
3898
|
-
if (typeof model !== "string" || modelSource !== "fast" && modelSource !== "main") {
|
|
3899
|
-
return;
|
|
3900
|
-
}
|
|
3901
|
-
this.onWorkspaceGenerationEvent?.({
|
|
3902
|
-
type: "started",
|
|
3903
|
-
requestId,
|
|
3904
|
-
model,
|
|
3905
|
-
modelSource
|
|
3906
|
-
});
|
|
3907
|
-
return;
|
|
3908
|
-
}
|
|
3909
|
-
if (record["type"] === "thinking") {
|
|
3910
|
-
this.onWorkspaceGenerationEvent?.({ type: "thinking", requestId });
|
|
3911
|
-
return;
|
|
3912
|
-
}
|
|
3913
|
-
if (record["type"] === "delta") {
|
|
3914
|
-
const seq = record["seq"];
|
|
3915
|
-
const text = record["text"];
|
|
3916
|
-
if (typeof seq !== "number" || !Number.isSafeInteger(seq) || seq < 0 || typeof text !== "string" || text.length === 0) {
|
|
3917
|
-
return;
|
|
3918
|
-
}
|
|
3919
|
-
this.onWorkspaceGenerationEvent?.({
|
|
3920
|
-
type: "delta",
|
|
3921
|
-
requestId,
|
|
3922
|
-
seq,
|
|
3923
|
-
text
|
|
3924
|
-
});
|
|
3925
|
-
return;
|
|
3926
|
-
}
|
|
3927
|
-
return;
|
|
3928
|
-
}
|
|
3929
|
-
if (method === "qwen/notify/session/model-update") {
|
|
3930
|
-
this.handleInSessionModelUpdate(params);
|
|
3931
|
-
return;
|
|
3932
|
-
}
|
|
3933
|
-
if (method === "qwen/notify/session/mode-update") {
|
|
3934
|
-
this.handleInSessionModeUpdate(params);
|
|
3935
|
-
return;
|
|
3936
|
-
}
|
|
3937
|
-
if (method === "qwen/notify/session/title-update") {
|
|
3938
|
-
const sessionId2 = params["sessionId"];
|
|
3939
|
-
const title = params["title"];
|
|
3940
|
-
if (typeof sessionId2 !== "string" || typeof title !== "string" || !title)
|
|
3941
|
-
return;
|
|
3942
|
-
const entry = this.resolveEntry(sessionId2);
|
|
3943
|
-
if (!entry) return;
|
|
3944
|
-
this.onSessionCatalogChanged?.();
|
|
3945
|
-
try {
|
|
3946
|
-
entry.events.publish({
|
|
3947
|
-
type: "session_metadata_updated",
|
|
3948
|
-
data: {
|
|
3949
|
-
sessionId: sessionId2,
|
|
3950
|
-
displayName: title,
|
|
3951
|
-
...typeof params["titleSource"] === "string" ? { titleSource: params["titleSource"] } : {}
|
|
3952
|
-
}
|
|
3953
|
-
});
|
|
3954
|
-
} catch {
|
|
3955
|
-
}
|
|
3956
|
-
return;
|
|
3957
|
-
}
|
|
3958
|
-
if (method === "qwen/notify/session/recording-degraded") {
|
|
3959
|
-
const sessionId2 = params["sessionId"];
|
|
3960
|
-
if (params["v"] !== 1 || typeof sessionId2 !== "string" || sessionId2.length === 0 || params["reason"] !== "write_failed") {
|
|
3961
|
-
writeStderrLine(
|
|
3962
|
-
`[demux] session=${typeof sessionId2 === "string" ? sessionId2 : "<missing>"} type=session_recording_degraded action=dropped reason=malformed`
|
|
3963
|
-
);
|
|
3964
|
-
return;
|
|
3965
|
-
}
|
|
3966
|
-
const entry = this.resolveEntry(sessionId2);
|
|
3967
|
-
if (entry && !this.ownsSession(sessionId2)) {
|
|
3968
|
-
writeStderrLine(
|
|
3969
|
-
`[demux] session=${sessionId2} type=session_recording_degraded action=dropped reason=session_not_owned`
|
|
3970
|
-
);
|
|
3971
|
-
return;
|
|
3972
|
-
}
|
|
3973
|
-
if (entry) entry.recordingDegraded = true;
|
|
3974
|
-
this.publishExtNotification(sessionId2, "session_recording_degraded", {
|
|
3975
|
-
sessionId: sessionId2,
|
|
3976
|
-
reason: "write_failed"
|
|
3977
|
-
});
|
|
3978
|
-
return;
|
|
3979
|
-
}
|
|
3980
|
-
if (method === "qwen/notify/session/prompt-suggestion") {
|
|
3981
|
-
const sessionId2 = params["sessionId"];
|
|
3982
|
-
const suggestion = params["suggestion"];
|
|
3983
|
-
const promptId = params["promptId"];
|
|
3984
|
-
if (typeof sessionId2 !== "string" || typeof suggestion !== "string" || suggestion.length === 0 || suggestion.length > MAX_SUGGESTION_LENGTH || typeof promptId !== "string") {
|
|
3985
|
-
writeStderrLine(
|
|
3986
|
-
`[demux] session=${typeof sessionId2 === "string" ? sessionId2 : "<missing>"} type=prompt_suggestion action=dropped reason=malformed`
|
|
3987
|
-
);
|
|
3988
|
-
return;
|
|
3989
|
-
}
|
|
3990
|
-
const entry = this.resolveEntry(sessionId2);
|
|
3991
|
-
if (!entry) return;
|
|
3992
|
-
entry.events.publish({
|
|
3993
|
-
type: "followup_suggestion",
|
|
3994
|
-
data: { sessionId: sessionId2, suggestion, promptId }
|
|
3995
|
-
});
|
|
3996
|
-
return;
|
|
3997
|
-
}
|
|
3998
|
-
if (method === "qwen/notify/session/terminal-sequence") {
|
|
3999
|
-
const sessionId2 = params["sessionId"];
|
|
4000
|
-
if (typeof sessionId2 !== "string") return;
|
|
4001
|
-
const { v: _v2, sessionId: _sid2, ...rest2 } = params;
|
|
4002
|
-
void _v2;
|
|
4003
|
-
void _sid2;
|
|
4004
|
-
this.publishExtNotification(sessionId2, "terminal_sequence", rest2, true);
|
|
4005
|
-
return;
|
|
4006
|
-
}
|
|
4007
|
-
if (method === "qwen/notify/session/artifact-event") {
|
|
4008
|
-
await this.handleArtifactEvent(params);
|
|
4009
|
-
return;
|
|
4010
|
-
}
|
|
4011
|
-
if (method !== "qwen/notify/session/mcp-budget-event") return;
|
|
4012
|
-
const sessionId = params["sessionId"];
|
|
4013
|
-
if (typeof sessionId !== "string") return;
|
|
4014
|
-
const kind = params["kind"];
|
|
4015
|
-
let type;
|
|
4016
|
-
if (kind === "budget_warning") {
|
|
4017
|
-
type = "mcp_budget_warning";
|
|
4018
|
-
} else if (kind === "refused_batch") {
|
|
4019
|
-
type = "mcp_child_refused_batch";
|
|
4020
|
-
} else {
|
|
4021
|
-
return;
|
|
4022
|
-
}
|
|
4023
|
-
const { v: _v, sessionId: _sid, kind: _kind, ...rest } = params;
|
|
4024
|
-
void _v;
|
|
4025
|
-
void _sid;
|
|
4026
|
-
void _kind;
|
|
4027
|
-
this.publishExtNotification(sessionId, type, rest);
|
|
4028
|
-
}
|
|
4029
|
-
async handleArtifactEvent(params) {
|
|
4030
|
-
const sessionId = params["sessionId"];
|
|
4031
|
-
const rawArtifacts = params["artifacts"];
|
|
4032
|
-
if (typeof sessionId !== "string" || !Array.isArray(rawArtifacts)) {
|
|
4033
|
-
writeStderrLine(
|
|
4034
|
-
`[demux] session=${typeof sessionId === "string" ? sessionId : "<missing>"} type=artifact_event action=dropped reason=malformed`
|
|
4035
|
-
);
|
|
4036
|
-
return;
|
|
4037
|
-
}
|
|
4038
|
-
if (!this.ownsSession(sessionId) && !this.inFlightRestoreIds.has(sessionId)) {
|
|
4039
|
-
writeStderrLine(
|
|
4040
|
-
`[demux] session=${sessionId} type=artifact_event action=dropped reason=session_not_owned`
|
|
4041
|
-
);
|
|
4042
|
-
return;
|
|
4043
|
-
}
|
|
4044
|
-
const entry = this.resolveEntry(sessionId);
|
|
4045
|
-
if (!entry) {
|
|
4046
|
-
writeStderrLine(
|
|
4047
|
-
`[demux] session=${sessionId} type=artifact_event action=dropped reason=session_not_found`
|
|
4048
|
-
);
|
|
4049
|
-
return;
|
|
4050
|
-
}
|
|
4051
|
-
const hookEventName = typeof params["hookEventName"] === "string" ? params["hookEventName"] : void 0;
|
|
4052
|
-
const toolName = typeof params["toolName"] === "string" ? params["toolName"] : void 0;
|
|
4053
|
-
const toolCallId = typeof params["toolCallId"] === "string" ? params["toolCallId"] : void 0;
|
|
4054
|
-
const artifacts = extractCappedArtifactInputs(
|
|
4055
|
-
rawArtifacts,
|
|
4056
|
-
entry.artifacts.inputBatchLimit(),
|
|
4057
|
-
entry.sessionId,
|
|
4058
|
-
"hook",
|
|
4059
|
-
(artifact) => ({
|
|
4060
|
-
...artifactPayloadFields(artifact),
|
|
4061
|
-
source: "hook",
|
|
4062
|
-
hookEventName,
|
|
4063
|
-
toolName,
|
|
4064
|
-
toolCallId
|
|
4065
|
-
})
|
|
4066
|
-
);
|
|
4067
|
-
const turn = {
|
|
4068
|
-
...entry.activePromptId ? { promptId: entry.activePromptId } : {},
|
|
4069
|
-
...entry.activePromptOriginatorClientId ? { originatorClientId: entry.activePromptOriginatorClientId } : {}
|
|
4070
|
-
};
|
|
4071
|
-
await this.upsertAndPublishArtifacts(entry, artifacts, void 0, turn);
|
|
4072
|
-
}
|
|
4073
|
-
async upsertAndPublishArtifacts(entry, artifacts, options, turn = {}) {
|
|
4074
|
-
try {
|
|
4075
|
-
const result = await entry.artifacts.upsertMany(artifacts, options);
|
|
4076
|
-
for (const warning of result.warnings ?? []) {
|
|
4077
|
-
writeStderrLine(
|
|
4078
|
-
`[artifacts] session=${entry.sessionId} action=warning reason=${JSON.stringify(
|
|
4079
|
-
warning
|
|
4080
|
-
)}`
|
|
4081
|
-
);
|
|
4082
|
-
}
|
|
4083
|
-
this.publishArtifactChanges(entry, result.changes, turn);
|
|
4084
|
-
} catch (error) {
|
|
4085
|
-
writeStderrLine(
|
|
4086
|
-
`[artifacts] session=${entry.sessionId} action=dropped reason=${JSON.stringify(
|
|
4087
|
-
artifactIngestionErrorReason(error)
|
|
4088
|
-
)}`
|
|
4089
|
-
);
|
|
4090
|
-
}
|
|
4091
|
-
}
|
|
4092
|
-
publishArtifactChanges(entry, changes, turn) {
|
|
4093
|
-
for (const change of changes) {
|
|
4094
|
-
entry.events.publish({
|
|
4095
|
-
type: "artifact_changed",
|
|
4096
|
-
data: { sessionId: entry.sessionId, change },
|
|
4097
|
-
...turn
|
|
4098
|
-
});
|
|
4099
|
-
}
|
|
4100
|
-
}
|
|
4101
|
-
publishExtNotification(sessionId, type, data, turnScoped = false) {
|
|
4102
|
-
if (this.abandonedRestoreIds.has(sessionId)) {
|
|
4103
|
-
return;
|
|
4104
|
-
}
|
|
4105
|
-
const entry = this.resolveEntry(sessionId);
|
|
4106
|
-
const frame = {
|
|
4107
|
-
type,
|
|
4108
|
-
data,
|
|
4109
|
-
...turnScoped && entry?.activePromptId ? { promptId: entry.activePromptId } : {},
|
|
4110
|
-
...entry?.activePromptOriginatorClientId ? { originatorClientId: entry.activePromptOriginatorClientId } : {}
|
|
4111
|
-
};
|
|
4112
|
-
if (entry) {
|
|
4113
|
-
entry.events.publish(frame);
|
|
4114
|
-
return;
|
|
4115
|
-
}
|
|
4116
|
-
this.bufferEarlyEvent(sessionId, frame);
|
|
4117
|
-
}
|
|
4118
|
-
/**
|
|
4119
|
-
* Promote an in-session `current_model_update` extNotification to a
|
|
4120
|
-
* `model_switched` bus event. Suppressed while the bridge is driving
|
|
4121
|
-
* its own model roundtrip (`entry.modelRoundtripInFlight`) — there the
|
|
4122
|
-
* bridge publishes the authoritative `model_switched`, so promoting
|
|
4123
|
-
* here too would double-publish. A structured log records the decision
|
|
4124
|
-
* so the `dropped` case is observable.
|
|
4125
|
-
*/
|
|
4126
|
-
handleInSessionModelUpdate(params) {
|
|
4127
|
-
const sessionId = params["sessionId"];
|
|
4128
|
-
const currentModelId = params["currentModelId"];
|
|
4129
|
-
if (typeof sessionId !== "string" || typeof currentModelId !== "string") {
|
|
4130
|
-
return;
|
|
4131
|
-
}
|
|
4132
|
-
const entry = this.resolveEntry(sessionId);
|
|
4133
|
-
if (!entry) {
|
|
4134
|
-
writeStderrLine(
|
|
4135
|
-
`[demux] session=${sessionId} type=current_model_update action=dropped reason=no_entry`
|
|
4136
|
-
);
|
|
4137
|
-
return;
|
|
4138
|
-
}
|
|
4139
|
-
if (entry.modelRoundtripInFlight) {
|
|
4140
|
-
writeStderrLine(
|
|
4141
|
-
`[demux] session=${sessionId} type=current_model_update action=suppressed reason=bridge_roundtrip_in_flight`
|
|
4142
|
-
);
|
|
4143
|
-
return;
|
|
4144
|
-
}
|
|
4145
|
-
if (this.onModelPromoted) {
|
|
4146
|
-
this.onModelPromoted(
|
|
4147
|
-
entry,
|
|
4148
|
-
currentModelId,
|
|
4149
|
-
entry.activePromptOriginatorClientId
|
|
4150
|
-
);
|
|
4151
|
-
} else {
|
|
4152
|
-
entry.events.publish({
|
|
4153
|
-
type: "model_switched",
|
|
4154
|
-
...entry.activePromptId ? { promptId: entry.activePromptId } : {},
|
|
4155
|
-
data: { sessionId, modelId: currentModelId },
|
|
4156
|
-
...entry.activePromptOriginatorClientId ? { originatorClientId: entry.activePromptOriginatorClientId } : {}
|
|
4157
|
-
});
|
|
4158
|
-
}
|
|
4159
|
-
writeStderrLine(
|
|
4160
|
-
`[demux] session=${sessionId} type=current_model_update action=promoted model=${currentModelId}`
|
|
4161
|
-
);
|
|
4162
|
-
}
|
|
4163
|
-
/**
|
|
4164
|
-
* A2: promote an in-session `current_mode_update` extNotification to
|
|
4165
|
-
* `approval_mode_changed`. Uses the same suppression pattern as
|
|
4166
|
-
* `handleInSessionModelUpdate` — suppressed while the bridge is driving
|
|
4167
|
-
* its own approval-mode roundtrip (`entry.approvalModeRoundtripInFlight`)
|
|
4168
|
-
* — but diverges with two additions the model handler lacks: enum
|
|
4169
|
-
* validation against `KNOWN_APPROVAL_MODES`, and a legacy
|
|
4170
|
-
* `session_update{current_mode_update}` dual-emit for IDE companion
|
|
4171
|
-
* compat (transition — see §6 of the design doc), itself deduped via the
|
|
4172
|
-
* `legacyFrameSent` flag.
|
|
4173
|
-
*/
|
|
4174
|
-
handleInSessionModeUpdate(params) {
|
|
4175
|
-
const sessionId = params["sessionId"];
|
|
4176
|
-
const currentModeId = params["currentModeId"];
|
|
4177
|
-
if (typeof sessionId !== "string" || typeof currentModeId !== "string") {
|
|
4178
|
-
return;
|
|
4179
|
-
}
|
|
4180
|
-
if (!KNOWN_APPROVAL_MODES.has(currentModeId)) {
|
|
4181
|
-
writeStderrLine(
|
|
4182
|
-
`[demux] session=${sessionId} type=current_mode_update action=dropped reason=unknown_mode mode=${currentModeId}`
|
|
4183
|
-
);
|
|
4184
|
-
return;
|
|
4185
|
-
}
|
|
4186
|
-
const entry = this.resolveEntry(sessionId);
|
|
4187
|
-
if (!entry) {
|
|
4188
|
-
writeStderrLine(
|
|
4189
|
-
`[demux] session=${sessionId} type=current_mode_update action=dropped reason=no_entry`
|
|
4190
|
-
);
|
|
4191
|
-
return;
|
|
4192
|
-
}
|
|
4193
|
-
if (entry.approvalModeRoundtripInFlight) {
|
|
4194
|
-
writeStderrLine(
|
|
4195
|
-
`[demux] session=${sessionId} type=current_mode_update action=suppressed reason=bridge_roundtrip_in_flight`
|
|
4196
|
-
);
|
|
4197
|
-
return;
|
|
4198
|
-
}
|
|
4199
|
-
if (this.onModePromoted) {
|
|
4200
|
-
this.onModePromoted(
|
|
4201
|
-
entry,
|
|
4202
|
-
currentModeId,
|
|
4203
|
-
entry.activePromptOriginatorClientId
|
|
4204
|
-
);
|
|
4205
|
-
} else {
|
|
4206
|
-
entry.events.publish({
|
|
4207
|
-
type: "approval_mode_changed",
|
|
4208
|
-
...entry.activePromptId ? { promptId: entry.activePromptId } : {},
|
|
4209
|
-
data: {
|
|
4210
|
-
sessionId,
|
|
4211
|
-
previous: "default",
|
|
4212
|
-
next: currentModeId,
|
|
4213
|
-
persisted: false
|
|
4214
|
-
},
|
|
4215
|
-
...entry.activePromptOriginatorClientId ? { originatorClientId: entry.activePromptOriginatorClientId } : {}
|
|
4216
|
-
});
|
|
4217
|
-
}
|
|
4218
|
-
if (params["legacyFrameSent"] === true) {
|
|
4219
|
-
writeStderrLine(
|
|
4220
|
-
`[demux] session=${sessionId} type=current_mode_update action=promoted mode=${currentModeId} legacy_frame=skipped`
|
|
4221
|
-
);
|
|
4222
|
-
return;
|
|
4223
|
-
}
|
|
4224
|
-
entry.events.publish({
|
|
4225
|
-
type: "session_update",
|
|
4226
|
-
...entry.activePromptId ? { promptId: entry.activePromptId } : {},
|
|
4227
|
-
data: {
|
|
4228
|
-
sessionId,
|
|
4229
|
-
update: {
|
|
4230
|
-
sessionUpdate: "current_mode_update",
|
|
4231
|
-
currentModeId
|
|
4232
|
-
}
|
|
4233
|
-
},
|
|
4234
|
-
...entry.activePromptOriginatorClientId ? { originatorClientId: entry.activePromptOriginatorClientId } : {}
|
|
4235
|
-
});
|
|
4236
|
-
writeStderrLine(
|
|
4237
|
-
`[demux] session=${sessionId} type=current_mode_update action=promoted mode=${currentModeId}`
|
|
4238
|
-
);
|
|
4239
|
-
}
|
|
4240
|
-
/**
|
|
4241
|
-
* Enqueue `frame` for `sessionId`. Lazy TTL sweep runs first so
|
|
4242
|
-
* caller doesn't pay for stale entries before deciding whether
|
|
4243
|
-
* the session-cap is reached. New sessionIds past
|
|
4244
|
-
* `MAX_EARLY_EVENT_SESSIONS` are dropped (defense against a
|
|
4245
|
-
* malicious / buggy child fanning out fake sessionIds); same-
|
|
4246
|
-
* sessionId frames past `MAX_EARLY_EVENTS_PER_SESSION` are
|
|
4247
|
-
* dropped to bound per-session memory.
|
|
4248
|
-
*/
|
|
4249
|
-
bufferEarlyEvent(sessionId, frame) {
|
|
4250
|
-
if (this.abandonedRestoreIds.has(sessionId)) {
|
|
4251
|
-
return;
|
|
4252
|
-
}
|
|
4253
|
-
const now = Date.now();
|
|
4254
|
-
this.sweepExpiredTombstones(now);
|
|
4255
|
-
if (this.tombstonedSessionIds.has(sessionId) && !this.inFlightSessionRegistrationIds.has(sessionId)) {
|
|
4256
|
-
writeStderrLine(
|
|
4257
|
-
`qwen serve: dropping early extNotification for tombstoned session ${JSON.stringify(sessionId)} (post-close stale event)`
|
|
4258
|
-
);
|
|
4259
|
-
return;
|
|
4260
|
-
}
|
|
4261
|
-
this.sweepExpiredEarlyEvents(now);
|
|
4262
|
-
let buf = this.earlyEvents.get(sessionId);
|
|
4263
|
-
if (!buf) {
|
|
4264
|
-
if (this.earlyEvents.size >= MAX_EARLY_EVENT_SESSIONS) {
|
|
4265
|
-
writeStderrLine(
|
|
4266
|
-
`qwen serve: dropping early extNotification \u2014 early-event buffer at MAX_EARLY_EVENT_SESSIONS (${MAX_EARLY_EVENT_SESSIONS}); possible session-id fanout abuse`
|
|
4267
|
-
);
|
|
4268
|
-
return;
|
|
4269
|
-
}
|
|
4270
|
-
buf = { frames: [], expiresAt: now + EARLY_EVENT_TTL_MS };
|
|
4271
|
-
this.earlyEvents.set(sessionId, buf);
|
|
4272
|
-
}
|
|
4273
|
-
if (buf.frames.length >= MAX_EARLY_EVENTS_PER_SESSION) {
|
|
4274
|
-
writeStderrLine(
|
|
4275
|
-
`qwen serve: dropping early extNotification for session ${JSON.stringify(sessionId)} \u2014 per-session cap (${MAX_EARLY_EVENTS_PER_SESSION}) reached`
|
|
4276
|
-
);
|
|
4277
|
-
return;
|
|
4278
|
-
}
|
|
4279
|
-
buf.frames.push(frame);
|
|
4280
|
-
}
|
|
4281
|
-
sweepExpiredEarlyEvents(now) {
|
|
4282
|
-
for (const [sid, buf] of this.earlyEvents) {
|
|
4283
|
-
if (buf.expiresAt <= now) this.earlyEvents.delete(sid);
|
|
4284
|
-
}
|
|
4285
|
-
}
|
|
4286
|
-
sweepExpiredTombstones(now) {
|
|
4287
|
-
for (const [sid, expiresAt] of this.tombstonedSessionIds) {
|
|
4288
|
-
if (expiresAt <= now) this.tombstonedSessionIds.delete(sid);
|
|
4289
|
-
}
|
|
4290
|
-
}
|
|
4291
|
-
/**
|
|
4292
|
-
* Mark a sessionId as closed so a late `extNotification` from the
|
|
4293
|
-
* dying child can't leak into the early-event buffer. Bridge factory
|
|
4294
|
-
* calls this from every `byId.delete(sid)` site (kill path,
|
|
4295
|
-
* channel.exited handler, closeSession). Idempotent on already-
|
|
4296
|
-
* tombstoned ids — refreshes the TTL so a recently-killed id stays
|
|
4297
|
-
* dead long enough for any in-flight stale frames to expire.
|
|
4298
|
-
*/
|
|
4299
|
-
markSessionClosed(sessionId) {
|
|
4300
|
-
const now = Date.now();
|
|
4301
|
-
this.sweepExpiredTombstones(now);
|
|
4302
|
-
this.tombstonedSessionIds.set(sessionId, now + EARLY_EVENT_TTL_MS);
|
|
4303
|
-
this.earlyEvents.delete(sessionId);
|
|
4304
|
-
}
|
|
4305
|
-
/**
|
|
4306
|
-
* Mark a sessionId as currently being restored via `session/load` /
|
|
4307
|
-
* `session/resume`. While in this set, `bufferEarlyEvent` accepts
|
|
4308
|
-
* frames for the id even if it's tombstoned — so restore-time
|
|
4309
|
-
* early events from the freshly-restored child reach
|
|
4310
|
-
* `drainEarlyEvents` instead of being rejected by the tombstone.
|
|
4311
|
-
*
|
|
4312
|
-
* Bridge factory calls this BEFORE awaiting the ACP restore call.
|
|
4313
|
-
* `clearRestoreInFlight` is paired in the matching `finally` so a
|
|
4314
|
-
* failed restore doesn't leave a dangling allow-list entry.
|
|
4315
|
-
*/
|
|
4316
|
-
markRestoreInFlight(sessionId) {
|
|
4317
|
-
this.markSessionRegistrationInFlight(sessionId);
|
|
4318
|
-
this.inFlightRestoreIds.add(sessionId);
|
|
4319
|
-
}
|
|
4320
|
-
/**
|
|
4321
|
-
* Transfer an id from closed/abandoned ownership to a new registration
|
|
4322
|
-
* attempt before its ACP call starts. The in-flight allow-list is what lets
|
|
4323
|
-
* legitimate early notifications bypass the ordinary close tombstone until
|
|
4324
|
-
* `createSessionEntry` can drain them.
|
|
4325
|
-
*/
|
|
4326
|
-
markSessionRegistrationInFlight(sessionId) {
|
|
4327
|
-
this.clearAbandonedRestoreFence(sessionId);
|
|
4328
|
-
this.inFlightSessionRegistrationIds.add(sessionId);
|
|
4329
|
-
}
|
|
4330
|
-
/**
|
|
4331
|
-
* Drop the abandoned-restore fence for `sessionId`.
|
|
4332
|
-
*
|
|
4333
|
-
* The fence has no TTL and suppresses session updates, guardrail events,
|
|
4334
|
-
* and child notifications, so it must not outlive the abandoned attempt it
|
|
4335
|
-
* was raised for. The bridge clears it whenever a legitimate owner takes
|
|
4336
|
-
* the id — a new restore, or `createSessionEntry` registering a session
|
|
4337
|
-
* from any other route.
|
|
4338
|
-
*/
|
|
4339
|
-
clearAbandonedRestoreFence(sessionId) {
|
|
4340
|
-
this.abandonedRestoreIds.delete(sessionId);
|
|
4341
|
-
}
|
|
4342
|
-
/**
|
|
4343
|
-
* Companion to `markRestoreInFlight`. Bridge factory calls this when
|
|
4344
|
-
* the restore IIFE settles — after `createSessionEntry` runs
|
|
4345
|
-
* (success) or after the ACP restore call fails (error). Cleared to
|
|
4346
|
-
* prevent the Set from growing forever under high restore churn.
|
|
4347
|
-
*/
|
|
4348
|
-
clearRestoreInFlight(sessionId) {
|
|
4349
|
-
this.inFlightRestoreIds.delete(sessionId);
|
|
4350
|
-
this.clearSessionRegistrationInFlight(sessionId);
|
|
4351
|
-
}
|
|
4352
|
-
clearSessionRegistrationInFlight(sessionId) {
|
|
4353
|
-
this.inFlightSessionRegistrationIds.delete(sessionId);
|
|
4354
|
-
}
|
|
4355
|
-
markRestoreAbandoned(sessionId) {
|
|
4356
|
-
this.inFlightRestoreIds.delete(sessionId);
|
|
4357
|
-
this.inFlightSessionRegistrationIds.delete(sessionId);
|
|
4358
|
-
this.abandonedRestoreIds.add(sessionId);
|
|
4359
|
-
this.earlyEvents.delete(sessionId);
|
|
4360
|
-
}
|
|
4361
|
-
/**
|
|
4362
|
-
* Drain any frames buffered for `sessionId` onto `entry.events`.
|
|
4363
|
-
* Bridge calls this immediately after `byId.set(sessionId, entry)`
|
|
4364
|
-
* in `createSessionEntry`. The frames were captured before the
|
|
4365
|
-
* entry existed (e.g. MCP discovery during the child's `newSession`
|
|
4366
|
-
* handler), so draining them now lands them in the replay ring as
|
|
4367
|
-
* the FIRST events of this session.
|
|
4368
|
-
*
|
|
4369
|
-
* Public so the bridge factory can call it directly. Idempotent on
|
|
4370
|
-
* unknown sessionIds.
|
|
4371
|
-
*/
|
|
4372
|
-
drainEarlyEvents(sessionId, entry) {
|
|
4373
|
-
this.tombstonedSessionIds.delete(sessionId);
|
|
4374
|
-
const buf = this.earlyEvents.get(sessionId);
|
|
4375
|
-
if (!buf) return;
|
|
4376
|
-
for (const frame of buf.frames) {
|
|
4377
|
-
if (frame.type === "session_recording_degraded") {
|
|
4378
|
-
entry.recordingDegraded = true;
|
|
4379
|
-
}
|
|
4380
|
-
entry.events.publish(frame);
|
|
4381
|
-
}
|
|
4382
|
-
this.earlyEvents.delete(sessionId);
|
|
4383
|
-
}
|
|
4384
|
-
async writeTextFile(params) {
|
|
4385
|
-
if (this.fileSystem) {
|
|
4386
|
-
try {
|
|
4387
|
-
return await this.fileSystem.writeText(params);
|
|
4388
|
-
} catch (err) {
|
|
4389
|
-
preserveFsErrorOverAcp(err);
|
|
4390
|
-
}
|
|
4391
|
-
}
|
|
4392
|
-
let realTarget = params.path;
|
|
4393
|
-
try {
|
|
4394
|
-
realTarget = await fs2.realpath(params.path);
|
|
4395
|
-
} catch (err) {
|
|
4396
|
-
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
4397
|
-
if (code !== "ENOENT") throw err;
|
|
4398
|
-
try {
|
|
4399
|
-
const linkTarget = await fs2.readlink(params.path);
|
|
4400
|
-
realTarget = path2.resolve(path2.dirname(params.path), linkTarget);
|
|
4401
|
-
} catch {
|
|
4402
|
-
}
|
|
4403
|
-
}
|
|
4404
|
-
const tmp = `${realTarget}.${process.pid}.${Date.now()}.${randomUUID2()}.tmp`;
|
|
4405
|
-
let preserveMode;
|
|
4406
|
-
try {
|
|
4407
|
-
const targetStat = await fs2.stat(realTarget);
|
|
4408
|
-
preserveMode = {
|
|
4409
|
-
mode: targetStat.mode & 4095,
|
|
4410
|
-
uid: targetStat.uid,
|
|
4411
|
-
gid: targetStat.gid
|
|
4412
|
-
};
|
|
4413
|
-
} catch (err) {
|
|
4414
|
-
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
4415
|
-
if (code !== "ENOENT") throw err;
|
|
4416
|
-
}
|
|
4417
|
-
try {
|
|
4418
|
-
await fs2.writeFile(tmp, params.content, {
|
|
4419
|
-
encoding: "utf8",
|
|
4420
|
-
flag: "wx",
|
|
4421
|
-
mode: preserveMode?.mode ?? 384
|
|
4422
|
-
});
|
|
4423
|
-
if (preserveMode) {
|
|
4424
|
-
await fs2.chmod(tmp, preserveMode.mode).catch(() => {
|
|
4425
|
-
});
|
|
4426
|
-
await fs2.chown(tmp, preserveMode.uid, preserveMode.gid).catch(() => {
|
|
4427
|
-
});
|
|
4428
|
-
}
|
|
4429
|
-
await fs2.rename(tmp, realTarget);
|
|
4430
|
-
} catch (err) {
|
|
4431
|
-
await fs2.unlink(tmp).catch(() => {
|
|
4432
|
-
});
|
|
4433
|
-
throw err;
|
|
4434
|
-
}
|
|
4435
|
-
return {};
|
|
4436
|
-
}
|
|
4437
|
-
async readTextFile(params) {
|
|
4438
|
-
if (this.fileSystem) {
|
|
4439
|
-
try {
|
|
4440
|
-
return await this.fileSystem.readText(params);
|
|
4441
|
-
} catch (err) {
|
|
4442
|
-
preserveFsErrorOverAcp(err);
|
|
4443
|
-
}
|
|
4444
|
-
}
|
|
4445
|
-
if (typeof params.limit === "number" && params.limit <= 0) {
|
|
4446
|
-
return { content: "" };
|
|
4447
|
-
}
|
|
4448
|
-
if (typeof params.limit === "number" && params.limit > 0 && !Number.isSafeInteger(params.limit)) {
|
|
4449
|
-
throw RequestError.invalidParams(
|
|
4450
|
-
void 0,
|
|
4451
|
-
`\`limit\` must be a positive integer, got ${params.limit}`
|
|
4452
|
-
);
|
|
4453
|
-
}
|
|
4454
|
-
if (typeof params.line === "number" && params.line > 0 && !Number.isSafeInteger(params.line)) {
|
|
4455
|
-
throw RequestError.invalidParams(
|
|
4456
|
-
void 0,
|
|
4457
|
-
`\`line\` must be a positive integer, got ${params.line}`
|
|
4458
|
-
);
|
|
4459
|
-
}
|
|
4460
|
-
const READ_FILE_SIZE_CAP = 100 * 1024 * 1024;
|
|
4461
|
-
const stats = await fs2.stat(params.path);
|
|
4462
|
-
if (!stats.isFile()) {
|
|
4463
|
-
throw new Error(
|
|
4464
|
-
`readTextFile: ${params.path} is not a regular file (reported as ${describeStatKind(stats)}). Pipe / device / proc-like inputs can produce unbounded data and aren't supported by the bridge fs proxy.`
|
|
4465
|
-
);
|
|
4466
|
-
}
|
|
4467
|
-
if (stats.size > READ_FILE_SIZE_CAP) {
|
|
4468
|
-
throw new Error(
|
|
4469
|
-
`readTextFile: ${params.path} is ${stats.size} bytes, exceeds the ${READ_FILE_SIZE_CAP}-byte daemon cap. Tail/grep externally and feed the relevant slice instead.`
|
|
4470
|
-
);
|
|
4471
|
-
}
|
|
4472
|
-
const content = await fs2.readFile(params.path, "utf8");
|
|
4473
|
-
if (typeof params.line === "number" || typeof params.limit === "number") {
|
|
4474
|
-
const startLine = params.line ?? 1;
|
|
4475
|
-
const start = startLine > 0 ? startLine - 1 : 0;
|
|
4476
|
-
const end = params.limit != null ? start + params.limit : void 0;
|
|
4477
|
-
return { content: sliceLineRange(content, start, end) };
|
|
4478
|
-
}
|
|
4479
|
-
return { content };
|
|
1328
|
+
var ATTRIBUTE_PREFIX = "qwen-code.daemon.acp_startup";
|
|
1329
|
+
function isRecord(value) {
|
|
1330
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1331
|
+
}
|
|
1332
|
+
__name(isRecord, "isRecord");
|
|
1333
|
+
function readDuration(source, key) {
|
|
1334
|
+
const value = source[key];
|
|
1335
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= MAX_PROFILE_DURATION_MS ? value : void 0;
|
|
1336
|
+
}
|
|
1337
|
+
__name(readDuration, "readDuration");
|
|
1338
|
+
function readDurations(source, keys) {
|
|
1339
|
+
if (!isRecord(source)) {
|
|
1340
|
+
return { values: {}, complete: false };
|
|
4480
1341
|
}
|
|
4481
|
-
};
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
__name(isA2uiToolMeta, "isA2uiToolMeta");
|
|
4490
|
-
function splitA2uiText(raw) {
|
|
4491
|
-
const s = raw.replace(/^\s+/, "");
|
|
4492
|
-
if (s[0] !== "[") return null;
|
|
4493
|
-
let depth = 0;
|
|
4494
|
-
let inStr = false;
|
|
4495
|
-
let esc = false;
|
|
4496
|
-
let end = -1;
|
|
4497
|
-
for (let i = 0; i < s.length; i++) {
|
|
4498
|
-
const c = s[i];
|
|
4499
|
-
if (inStr) {
|
|
4500
|
-
if (esc) esc = false;
|
|
4501
|
-
else if (c === "\\") esc = true;
|
|
4502
|
-
else if (c === '"') inStr = false;
|
|
4503
|
-
continue;
|
|
4504
|
-
}
|
|
4505
|
-
if (c === '"') inStr = true;
|
|
4506
|
-
else if (c === "[") depth++;
|
|
4507
|
-
else if (c === "]") {
|
|
4508
|
-
depth--;
|
|
4509
|
-
if (depth === 0) {
|
|
4510
|
-
end = i + 1;
|
|
4511
|
-
break;
|
|
4512
|
-
}
|
|
1342
|
+
const values = {};
|
|
1343
|
+
let complete = true;
|
|
1344
|
+
for (const key of keys) {
|
|
1345
|
+
const value = readDuration(source, key);
|
|
1346
|
+
if (value === void 0) {
|
|
1347
|
+
complete = false;
|
|
1348
|
+
} else {
|
|
1349
|
+
values[key] = value;
|
|
4513
1350
|
}
|
|
4514
1351
|
}
|
|
4515
|
-
|
|
4516
|
-
try {
|
|
4517
|
-
const arr = JSON.parse(s.slice(0, end));
|
|
4518
|
-
if (!Array.isArray(arr) || arr.length === 0) return null;
|
|
4519
|
-
return [arr, s.slice(end).trim()];
|
|
4520
|
-
} catch {
|
|
4521
|
-
return null;
|
|
4522
|
-
}
|
|
1352
|
+
return { values, complete };
|
|
4523
1353
|
}
|
|
4524
|
-
__name(
|
|
4525
|
-
function
|
|
4526
|
-
const
|
|
4527
|
-
|
|
1354
|
+
__name(readDurations, "readDurations");
|
|
1355
|
+
function addDurationAttributes(attributes, group, values) {
|
|
1356
|
+
for (const [key, value] of Object.entries(values)) {
|
|
1357
|
+
if (group === "phase" && key === "unattributedMs") continue;
|
|
1358
|
+
const attributeKey = key.replace(
|
|
1359
|
+
/[A-Z]/g,
|
|
1360
|
+
(letter) => `_${letter.toLowerCase()}`
|
|
1361
|
+
);
|
|
1362
|
+
attributes[`${ATTRIBUTE_PREFIX}.${group}.${attributeKey}`] = value;
|
|
1363
|
+
}
|
|
4528
1364
|
}
|
|
4529
|
-
__name(
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
1365
|
+
__name(addDurationAttributes, "addDurationAttributes");
|
|
1366
|
+
var PHASE_KEYS = [
|
|
1367
|
+
"processToProfilerReadyMs",
|
|
1368
|
+
"geminiImportMs",
|
|
1369
|
+
"argsParseMs",
|
|
1370
|
+
"settingsLoadMs",
|
|
1371
|
+
"configConstructionMs",
|
|
1372
|
+
"appInitializationMs",
|
|
1373
|
+
"acpImportMs",
|
|
1374
|
+
"bootstrapConfigInitializationMs",
|
|
1375
|
+
"transportSetupMs",
|
|
1376
|
+
"initializeHandlerMs",
|
|
1377
|
+
"unattributedMs"
|
|
1378
|
+
];
|
|
1379
|
+
var CONFIG_KEYS = [
|
|
1380
|
+
"extensionsInitialMs",
|
|
1381
|
+
"hooksMs",
|
|
1382
|
+
"skillsMs",
|
|
1383
|
+
"extensionsFinalMs",
|
|
1384
|
+
"hierarchicalMemoryMs",
|
|
1385
|
+
"toolRegistryMs",
|
|
1386
|
+
"ripgrepProbeMs",
|
|
1387
|
+
"toolWarmupMs",
|
|
1388
|
+
"otherMs"
|
|
1389
|
+
];
|
|
1390
|
+
function getChannelStartupProfileAttributes(response, receivedAtEpochMs, initializeTimeoutMs) {
|
|
1391
|
+
if (!isRecord(response) || !isRecord(response["_meta"])) {
|
|
1392
|
+
return void 0;
|
|
4548
1393
|
}
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
}
|
|
4562
|
-
|
|
4563
|
-
grouped.set(sid, []);
|
|
4564
|
-
order.push(sid);
|
|
4565
|
-
}
|
|
4566
|
-
grouped.get(sid).push(c);
|
|
4567
|
-
}
|
|
4568
|
-
const surfaces = order.map((sid) => ({
|
|
4569
|
-
surfaceId: sid,
|
|
4570
|
-
commands: grouped.get(sid)
|
|
4571
|
-
}));
|
|
4572
|
-
const sanitizedText = fallback || "[A2UI surface rendered]";
|
|
4573
|
-
const sanitizedContent = content.map(
|
|
4574
|
-
(block, i) => i === hitIndex ? {
|
|
4575
|
-
...block,
|
|
4576
|
-
content: {
|
|
4577
|
-
...block.content ?? {},
|
|
4578
|
-
text: sanitizedText
|
|
4579
|
-
}
|
|
4580
|
-
} : block
|
|
4581
|
-
);
|
|
4582
|
-
const sanitizedUpdate = {
|
|
4583
|
-
...update,
|
|
4584
|
-
content: sanitizedContent
|
|
1394
|
+
const profile = response["_meta"][CHANNEL_STARTUP_PROFILE_META_KEY];
|
|
1395
|
+
if (!isRecord(profile) || profile["v"] !== CHANNEL_STARTUP_PROFILE_VERSION) {
|
|
1396
|
+
return void 0;
|
|
1397
|
+
}
|
|
1398
|
+
const phases = readDurations(profile["phases"], PHASE_KEYS);
|
|
1399
|
+
const config = readDurations(profile["config"], CONFIG_KEYS);
|
|
1400
|
+
const processToResponseMs = readDuration(profile, "processToResponseMs");
|
|
1401
|
+
const responseBuiltAtEpochMs = profile["responseBuiltAtEpochMs"];
|
|
1402
|
+
const childComplete = profile["complete"] === true;
|
|
1403
|
+
const validResponseEpoch = typeof responseBuiltAtEpochMs === "number" && Number.isFinite(responseBuiltAtEpochMs) && responseBuiltAtEpochMs >= 0;
|
|
1404
|
+
const effectiveComplete = childComplete && phases.complete && config.complete && processToResponseMs !== void 0 && validResponseEpoch;
|
|
1405
|
+
const attributes = {
|
|
1406
|
+
[`${ATTRIBUTE_PREFIX}.profile.version`]: CHANNEL_STARTUP_PROFILE_VERSION,
|
|
1407
|
+
[`${ATTRIBUTE_PREFIX}.profile.complete`]: effectiveComplete
|
|
4585
1408
|
};
|
|
4586
|
-
if (
|
|
4587
|
-
|
|
1409
|
+
if (processToResponseMs !== void 0) {
|
|
1410
|
+
attributes[`${ATTRIBUTE_PREFIX}.child.process_to_response_ms`] = processToResponseMs;
|
|
4588
1411
|
}
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
1412
|
+
if (phases.values["unattributedMs"] !== void 0) {
|
|
1413
|
+
attributes[`${ATTRIBUTE_PREFIX}.child.unattributed_ms`] = phases.values["unattributedMs"];
|
|
1414
|
+
}
|
|
1415
|
+
addDurationAttributes(attributes, "phase", phases.values);
|
|
1416
|
+
addDurationAttributes(attributes, "config", config.values);
|
|
1417
|
+
if (validResponseEpoch) {
|
|
1418
|
+
const transportMs = receivedAtEpochMs - responseBuiltAtEpochMs;
|
|
1419
|
+
if (Number.isFinite(transportMs) && transportMs >= 0 && transportMs <= initializeTimeoutMs) {
|
|
1420
|
+
attributes[`${ATTRIBUTE_PREFIX}.response_transport_ms`] = transportMs;
|
|
4595
1421
|
}
|
|
4596
|
-
}
|
|
1422
|
+
}
|
|
1423
|
+
return attributes;
|
|
4597
1424
|
}
|
|
4598
|
-
__name(
|
|
1425
|
+
__name(getChannelStartupProfileAttributes, "getChannelStartupProfileAttributes");
|
|
4599
1426
|
|
|
4600
1427
|
// packages/acp-bridge/src/generation-stream.ts
|
|
4601
1428
|
init_esbuild_shims();
|
|
@@ -4653,8 +1480,8 @@ var GenerationStreamQueue = class {
|
|
|
4653
1480
|
new Error("GenerationStreamQueue supports only one pending reader")
|
|
4654
1481
|
);
|
|
4655
1482
|
}
|
|
4656
|
-
return new Promise((
|
|
4657
|
-
this.waiter = { resolve
|
|
1483
|
+
return new Promise((resolve, reject) => {
|
|
1484
|
+
this.waiter = { resolve, reject };
|
|
4658
1485
|
});
|
|
4659
1486
|
}
|
|
4660
1487
|
[Symbol.asyncIterator]() {
|
|
@@ -4671,8 +1498,8 @@ var GenerationStreamQueue = class {
|
|
|
4671
1498
|
// packages/acp-bridge/src/sessionArtifacts.ts
|
|
4672
1499
|
init_esbuild_shims();
|
|
4673
1500
|
import { createHash } from "node:crypto";
|
|
4674
|
-
import { constants as fsConstants, promises as
|
|
4675
|
-
import
|
|
1501
|
+
import { constants as fsConstants, promises as fs } from "node:fs";
|
|
1502
|
+
import path from "node:path";
|
|
4676
1503
|
var SOURCE_RESERVATIONS = {
|
|
4677
1504
|
tool: 100,
|
|
4678
1505
|
client: 50,
|
|
@@ -4748,6 +1575,9 @@ var SessionArtifactStore = class {
|
|
|
4748
1575
|
inputBatchLimit() {
|
|
4749
1576
|
return this.maxArtifacts * 2;
|
|
4750
1577
|
}
|
|
1578
|
+
resetWorkspaceResolutionCache() {
|
|
1579
|
+
this.realWorkspaceCwdPromise = void 0;
|
|
1580
|
+
}
|
|
4751
1581
|
async list() {
|
|
4752
1582
|
return this.enqueue(async () => {
|
|
4753
1583
|
await this.refreshWorkspaceStatuses();
|
|
@@ -5093,7 +1923,8 @@ var SessionArtifactStore = class {
|
|
|
5093
1923
|
if (!markerIds.has(artifact.id)) continue;
|
|
5094
1924
|
const markerArtifact = await this.normalizeRestoredMarkerArtifact(
|
|
5095
1925
|
artifact,
|
|
5096
|
-
warnings
|
|
1926
|
+
warnings,
|
|
1927
|
+
options.workspaceAccess === "metadata-only"
|
|
5097
1928
|
);
|
|
5098
1929
|
if (markerArtifact)
|
|
5099
1930
|
this.markerArtifacts.set(artifact.id, markerArtifact);
|
|
@@ -5115,7 +1946,11 @@ var SessionArtifactStore = class {
|
|
|
5115
1946
|
{
|
|
5116
1947
|
metadataBudget: "persisted",
|
|
5117
1948
|
workspaceExpected: workspaceExpectedFromArtifact(artifact),
|
|
5118
|
-
hashWorkspaceContent: false
|
|
1949
|
+
hashWorkspaceContent: false,
|
|
1950
|
+
...options.workspaceAccess === "metadata-only" ? {
|
|
1951
|
+
workspaceAccess: "metadata-only",
|
|
1952
|
+
workspaceStatus: artifact.status
|
|
1953
|
+
} : {}
|
|
5119
1954
|
}
|
|
5120
1955
|
);
|
|
5121
1956
|
if (this.stickyEphemeralIds.has(normalized.id) && normalized.retention !== "ephemeral") {
|
|
@@ -5222,7 +2057,7 @@ var SessionArtifactStore = class {
|
|
|
5222
2057
|
}
|
|
5223
2058
|
});
|
|
5224
2059
|
}
|
|
5225
|
-
async normalizeRestoredMarkerArtifact(artifact, warnings) {
|
|
2060
|
+
async normalizeRestoredMarkerArtifact(artifact, warnings, metadataOnly = false) {
|
|
5226
2061
|
try {
|
|
5227
2062
|
const input = persistedArtifactToInput(artifact);
|
|
5228
2063
|
if (input.retention === "pinned") {
|
|
@@ -5238,7 +2073,11 @@ var SessionArtifactStore = class {
|
|
|
5238
2073
|
{
|
|
5239
2074
|
metadataBudget: "persisted",
|
|
5240
2075
|
workspaceExpected: workspaceExpectedFromArtifact(artifact),
|
|
5241
|
-
hashWorkspaceContent: false
|
|
2076
|
+
hashWorkspaceContent: false,
|
|
2077
|
+
...metadataOnly ? {
|
|
2078
|
+
workspaceAccess: "metadata-only",
|
|
2079
|
+
workspaceStatus: artifact.status
|
|
2080
|
+
} : {}
|
|
5242
2081
|
}
|
|
5243
2082
|
);
|
|
5244
2083
|
if (normalized.id !== artifact.id) {
|
|
@@ -5580,10 +2419,10 @@ var SessionArtifactStore = class {
|
|
|
5580
2419
|
}
|
|
5581
2420
|
const realWorkspace = await this.getRealWorkspaceCwdForValidation();
|
|
5582
2421
|
const normalizedPath = normalizeWorkspacePath(workspacePath, realWorkspace);
|
|
5583
|
-
const absolutePath =
|
|
2422
|
+
const absolutePath = path.resolve(realWorkspace, normalizedPath);
|
|
5584
2423
|
let stat;
|
|
5585
2424
|
try {
|
|
5586
|
-
stat = await
|
|
2425
|
+
stat = await fs.lstat(absolutePath);
|
|
5587
2426
|
} catch (error) {
|
|
5588
2427
|
if (isNotFoundError(error)) {
|
|
5589
2428
|
return { inputs: [input] };
|
|
@@ -5599,13 +2438,13 @@ var SessionArtifactStore = class {
|
|
|
5599
2438
|
if (stat.isSymbolicLink()) {
|
|
5600
2439
|
let realPath;
|
|
5601
2440
|
try {
|
|
5602
|
-
realPath = await
|
|
2441
|
+
realPath = await fs.realpath(absolutePath);
|
|
5603
2442
|
} catch {
|
|
5604
2443
|
return { inputs: [input] };
|
|
5605
2444
|
}
|
|
5606
2445
|
let realStat;
|
|
5607
2446
|
try {
|
|
5608
|
-
realStat = await
|
|
2447
|
+
realStat = await fs.lstat(realPath);
|
|
5609
2448
|
} catch (error) {
|
|
5610
2449
|
if (isNotFoundError(error)) {
|
|
5611
2450
|
return { inputs: [input] };
|
|
@@ -5625,7 +2464,7 @@ var SessionArtifactStore = class {
|
|
|
5625
2464
|
return { inputs: [input] };
|
|
5626
2465
|
} else {
|
|
5627
2466
|
try {
|
|
5628
|
-
walkDir = await
|
|
2467
|
+
walkDir = await fs.realpath(absolutePath);
|
|
5629
2468
|
} catch (error) {
|
|
5630
2469
|
if (isNotFoundError(error)) {
|
|
5631
2470
|
return { inputs: [input] };
|
|
@@ -5637,7 +2476,7 @@ var SessionArtifactStore = class {
|
|
|
5637
2476
|
);
|
|
5638
2477
|
}
|
|
5639
2478
|
}
|
|
5640
|
-
const resolvedRelative =
|
|
2479
|
+
const resolvedRelative = path.relative(realWorkspace, walkDir);
|
|
5641
2480
|
if (!resolvedRelative || isOutsidePath(resolvedRelative)) {
|
|
5642
2481
|
throw new SessionArtifactValidationError(
|
|
5643
2482
|
"workspacePath must stay inside the workspace",
|
|
@@ -5645,7 +2484,7 @@ var SessionArtifactStore = class {
|
|
|
5645
2484
|
);
|
|
5646
2485
|
}
|
|
5647
2486
|
if (pathHasSkippedDirectoryComponent(
|
|
5648
|
-
resolvedRelative.split(
|
|
2487
|
+
resolvedRelative.split(path.sep).join("/")
|
|
5649
2488
|
) || walkRelative && pathHasSkippedDirectoryComponent(walkRelative)) {
|
|
5650
2489
|
throw new SessionArtifactValidationError(
|
|
5651
2490
|
"workspacePath is a skipped directory and cannot be recorded",
|
|
@@ -5681,7 +2520,7 @@ var SessionArtifactStore = class {
|
|
|
5681
2520
|
walkDir,
|
|
5682
2521
|
walkRelative,
|
|
5683
2522
|
realWorkspace,
|
|
5684
|
-
(filePath) => isRecordableDerivedChild(
|
|
2523
|
+
(filePath) => isRecordableDerivedChild(path.posix.basename(filePath), filePath)
|
|
5685
2524
|
);
|
|
5686
2525
|
} catch (error) {
|
|
5687
2526
|
if (isNotFoundError(error)) {
|
|
@@ -5722,7 +2561,7 @@ var SessionArtifactStore = class {
|
|
|
5722
2561
|
}
|
|
5723
2562
|
return {
|
|
5724
2563
|
inputs: collected.files.map((filePath) => {
|
|
5725
|
-
const title =
|
|
2564
|
+
const title = path.posix.basename(filePath).trim();
|
|
5726
2565
|
const description = parentDescription || (parentTitle && parentTitle !== title ? parentTitle : void 0);
|
|
5727
2566
|
return {
|
|
5728
2567
|
...input,
|
|
@@ -5759,7 +2598,7 @@ var SessionArtifactStore = class {
|
|
|
5759
2598
|
const trustedPublisher = trustedPublisherFromCaller;
|
|
5760
2599
|
const workspacePath = input.workspacePath ? normalizeWorkspacePath(
|
|
5761
2600
|
input.workspacePath,
|
|
5762
|
-
await this.getRealWorkspaceCwdForValidation()
|
|
2601
|
+
options.workspaceAccess === "metadata-only" ? this.workspaceCwd : await this.getRealWorkspaceCwdForValidation()
|
|
5763
2602
|
) : void 0;
|
|
5764
2603
|
const managedId = normalizeManagedId(input.managedId);
|
|
5765
2604
|
const rawStorage = input.storage;
|
|
@@ -5779,7 +2618,10 @@ var SessionArtifactStore = class {
|
|
|
5779
2618
|
const retention = normalizeRetention(input.retention, {
|
|
5780
2619
|
persistenceAvailable: this.persistence !== void 0
|
|
5781
2620
|
});
|
|
5782
|
-
const workspaceStatus = workspacePath ?
|
|
2621
|
+
const workspaceStatus = workspacePath ? options.workspaceAccess === "metadata-only" ? {
|
|
2622
|
+
status: options.workspaceStatus ?? "missing",
|
|
2623
|
+
...options.workspaceExpected?.sizeBytes !== void 0 ? { sizeBytes: options.workspaceExpected.sizeBytes } : {}
|
|
2624
|
+
} : await this.getInitialWorkspaceStatus(
|
|
5783
2625
|
workspacePath,
|
|
5784
2626
|
options.workspaceExpected,
|
|
5785
2627
|
{ hashContent: options.hashWorkspaceContent !== false }
|
|
@@ -5818,7 +2660,7 @@ var SessionArtifactStore = class {
|
|
|
5818
2660
|
storage,
|
|
5819
2661
|
source,
|
|
5820
2662
|
status: workspaceStatus?.status ?? "available",
|
|
5821
|
-
...workspacePath ? { lastStatAt: Date.now() } : {},
|
|
2663
|
+
...workspacePath && options.workspaceAccess !== "metadata-only" ? { lastStatAt: Date.now() } : {},
|
|
5822
2664
|
title,
|
|
5823
2665
|
description,
|
|
5824
2666
|
workspacePath,
|
|
@@ -5934,7 +2776,7 @@ var SessionArtifactStore = class {
|
|
|
5934
2776
|
}
|
|
5935
2777
|
getRealWorkspaceCwd() {
|
|
5936
2778
|
if (!this.realWorkspaceCwdPromise) {
|
|
5937
|
-
const promise =
|
|
2779
|
+
const promise = fs.realpath(this.workspaceCwd).catch((error) => {
|
|
5938
2780
|
if (this.realWorkspaceCwdPromise === promise) {
|
|
5939
2781
|
this.realWorkspaceCwdPromise = void 0;
|
|
5940
2782
|
}
|
|
@@ -6608,7 +3450,7 @@ function buildIdentityKey(input) {
|
|
|
6608
3450
|
}
|
|
6609
3451
|
__name(buildIdentityKey, "buildIdentityKey");
|
|
6610
3452
|
function managedIdForWorkspacePath(workspaceCwd, workspacePath) {
|
|
6611
|
-
return createHash("sha1").update(
|
|
3453
|
+
return createHash("sha1").update(path.resolve(workspaceCwd, workspacePath)).digest("hex").slice(0, 16);
|
|
6612
3454
|
}
|
|
6613
3455
|
__name(managedIdForWorkspacePath, "managedIdForWorkspacePath");
|
|
6614
3456
|
function normalizeString(value, field, maxLength, required) {
|
|
@@ -6655,7 +3497,7 @@ __name(normalizeString, "normalizeString");
|
|
|
6655
3497
|
function normalizeManagedId(value) {
|
|
6656
3498
|
const managedId = normalizeString(value, "managedId", 200, false);
|
|
6657
3499
|
if (!managedId) return void 0;
|
|
6658
|
-
if (managedId.includes("/") || managedId.includes("\\") || managedId.includes("..") ||
|
|
3500
|
+
if (managedId.includes("/") || managedId.includes("\\") || managedId.includes("..") || path.isAbsolute(managedId) || path.win32.isAbsolute(managedId)) {
|
|
6659
3501
|
throw new SessionArtifactValidationError(
|
|
6660
3502
|
"managedId must be an opaque managed resource id",
|
|
6661
3503
|
"managedId"
|
|
@@ -6689,21 +3531,21 @@ function hasUnsafeDisplayPayload(value) {
|
|
|
6689
3531
|
__name(hasUnsafeDisplayPayload, "hasUnsafeDisplayPayload");
|
|
6690
3532
|
function normalizeWorkspacePath(raw, workspaceCwd) {
|
|
6691
3533
|
const trimmed = normalizeString(raw, "workspacePath", 500, true);
|
|
6692
|
-
if (
|
|
3534
|
+
if (path.isAbsolute(trimmed)) {
|
|
6693
3535
|
throw new SessionArtifactValidationError(
|
|
6694
3536
|
"workspacePath must be relative to the workspace",
|
|
6695
3537
|
"workspacePath"
|
|
6696
3538
|
);
|
|
6697
3539
|
}
|
|
6698
|
-
const absolute =
|
|
6699
|
-
const relative =
|
|
3540
|
+
const absolute = path.resolve(workspaceCwd, trimmed);
|
|
3541
|
+
const relative = path.relative(workspaceCwd, absolute);
|
|
6700
3542
|
if (!relative || isOutsidePath(relative)) {
|
|
6701
3543
|
throw new SessionArtifactValidationError(
|
|
6702
3544
|
"workspacePath must stay inside the workspace",
|
|
6703
3545
|
"workspacePath"
|
|
6704
3546
|
);
|
|
6705
3547
|
}
|
|
6706
|
-
return relative.split(
|
|
3548
|
+
return relative.split(path.sep).join("/");
|
|
6707
3549
|
}
|
|
6708
3550
|
__name(normalizeWorkspacePath, "normalizeWorkspacePath");
|
|
6709
3551
|
function normalizeArtifactUrl(raw, allowFile) {
|
|
@@ -6904,7 +3746,7 @@ __name(normalizeSizeBytes, "normalizeSizeBytes");
|
|
|
6904
3746
|
function inferKind(input) {
|
|
6905
3747
|
if (input.storage === "published") return "html";
|
|
6906
3748
|
if (input.url) return "link";
|
|
6907
|
-
const ext = input.workspacePath ?
|
|
3749
|
+
const ext = input.workspacePath ? path.extname(input.workspacePath).toLowerCase() : "";
|
|
6908
3750
|
if (ext === ".html" || ext === ".htm") return "html";
|
|
6909
3751
|
if ([".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"].includes(ext)) {
|
|
6910
3752
|
return "image";
|
|
@@ -6919,15 +3761,15 @@ function inferKind(input) {
|
|
|
6919
3761
|
__name(inferKind, "inferKind");
|
|
6920
3762
|
async function getWorkspaceStatus(workspacePath, realWorkspaceCwd, expected, options = { hashContent: true }) {
|
|
6921
3763
|
const realWorkspace = await realWorkspaceCwd;
|
|
6922
|
-
const absolutePath =
|
|
3764
|
+
const absolutePath = path.resolve(realWorkspace, workspacePath);
|
|
6923
3765
|
try {
|
|
6924
|
-
const realPath = await
|
|
6925
|
-
const relative =
|
|
3766
|
+
const realPath = await fs.realpath(absolutePath);
|
|
3767
|
+
const relative = path.relative(realWorkspace, realPath);
|
|
6926
3768
|
if (!relative || isOutsidePath(relative)) {
|
|
6927
3769
|
return { status: "missing", escaped: true };
|
|
6928
3770
|
}
|
|
6929
|
-
const preOpenStat = await
|
|
6930
|
-
const handle = await
|
|
3771
|
+
const preOpenStat = await fs.lstat(realPath);
|
|
3772
|
+
const handle = await fs.open(
|
|
6931
3773
|
realPath,
|
|
6932
3774
|
fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW
|
|
6933
3775
|
);
|
|
@@ -7039,14 +3881,14 @@ function isWorkspaceContentChanged(artifact, status) {
|
|
|
7039
3881
|
__name(isWorkspaceContentChanged, "isWorkspaceContentChanged");
|
|
7040
3882
|
async function danglingSymlinkEscapesWorkspace(absolutePath, realWorkspace) {
|
|
7041
3883
|
try {
|
|
7042
|
-
const stat = await
|
|
3884
|
+
const stat = await fs.lstat(absolutePath);
|
|
7043
3885
|
if (!stat.isSymbolicLink()) {
|
|
7044
3886
|
return false;
|
|
7045
3887
|
}
|
|
7046
|
-
const target = await
|
|
7047
|
-
const parent = await
|
|
7048
|
-
const targetPath =
|
|
7049
|
-
return isOutsidePath(
|
|
3888
|
+
const target = await fs.readlink(absolutePath);
|
|
3889
|
+
const parent = await fs.realpath(path.dirname(absolutePath));
|
|
3890
|
+
const targetPath = path.resolve(parent, target);
|
|
3891
|
+
return isOutsidePath(path.relative(realWorkspace, targetPath));
|
|
7050
3892
|
} catch (error) {
|
|
7051
3893
|
if (isNotFoundError(error)) {
|
|
7052
3894
|
return false;
|
|
@@ -7081,7 +3923,7 @@ function isNoFollowSymlinkError(error) {
|
|
|
7081
3923
|
}
|
|
7082
3924
|
__name(isNoFollowSymlinkError, "isNoFollowSymlinkError");
|
|
7083
3925
|
function isOutsidePath(relative) {
|
|
7084
|
-
return relative === ".." || relative.startsWith(`..${
|
|
3926
|
+
return relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative);
|
|
7085
3927
|
}
|
|
7086
3928
|
__name(isOutsidePath, "isOutsidePath");
|
|
7087
3929
|
|
|
@@ -7124,32 +3966,45 @@ var KNOWN_SESSION_UPDATE_TYPES = /* @__PURE__ */ new Set([
|
|
|
7124
3966
|
"session_info_update",
|
|
7125
3967
|
"usage_update"
|
|
7126
3968
|
]);
|
|
7127
|
-
function
|
|
3969
|
+
function isRecord2(value) {
|
|
7128
3970
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7129
3971
|
}
|
|
7130
|
-
__name(
|
|
3972
|
+
__name(isRecord2, "isRecord");
|
|
7131
3973
|
function safeTransportFailureCode(error) {
|
|
7132
|
-
if (!
|
|
3974
|
+
if (!isRecord2(error)) return void 0;
|
|
7133
3975
|
const code = error["code"];
|
|
7134
3976
|
return typeof code === "string" && /^[a-z0-9_.-]{1,64}$/iu.test(code) ? code : void 0;
|
|
7135
3977
|
}
|
|
7136
3978
|
__name(safeTransportFailureCode, "safeTransportFailureCode");
|
|
7137
|
-
function sessionSourceRequestMeta(sourceType, sourceId) {
|
|
3979
|
+
function sessionSourceRequestMeta(sourceType, sourceId, daemonOwnedStandaloneCreation = false) {
|
|
7138
3980
|
return sourceType ? {
|
|
7139
3981
|
[SESSION_SOURCE_META_KEY]: {
|
|
7140
3982
|
sourceType,
|
|
7141
|
-
...sourceId !== void 0 ? { sourceId } : {}
|
|
3983
|
+
...sourceId !== void 0 ? { sourceId } : {},
|
|
3984
|
+
...daemonOwnedStandaloneCreation ? { [DAEMON_OWNED_STANDALONE_CREATION_KEY]: true } : {}
|
|
7142
3985
|
}
|
|
7143
3986
|
} : {};
|
|
7144
3987
|
}
|
|
7145
3988
|
__name(sessionSourceRequestMeta, "sessionSourceRequestMeta");
|
|
3989
|
+
function sameConversationDirectoryExpectation(left, right) {
|
|
3990
|
+
return left.canonicalSessionId === right.canonicalSessionId && left.root.canonicalPath === right.root.canonicalPath && left.root.device === right.root.device && left.root.inode === right.root.inode && left.child.name === right.child.name && left.child.canonicalPath === right.child.canonicalPath && left.child.device === right.child.device && left.child.inode === right.child.inode;
|
|
3991
|
+
}
|
|
3992
|
+
__name(sameConversationDirectoryExpectation, "sameConversationDirectoryExpectation");
|
|
3993
|
+
function standaloneWorkingDirectoryMissingError() {
|
|
3994
|
+
return new RequestError(
|
|
3995
|
+
-32004,
|
|
3996
|
+
"The standalone working directory is missing.",
|
|
3997
|
+
{ errorKind: "working_directory_missing" }
|
|
3998
|
+
);
|
|
3999
|
+
}
|
|
4000
|
+
__name(standaloneWorkingDirectoryMissingError, "standaloneWorkingDirectoryMissingError");
|
|
7146
4001
|
function getChannelPromptDisplayText(entry, displayText) {
|
|
7147
4002
|
return entry.sourceType === "channel" && typeof displayText === "string" ? displayText : void 0;
|
|
7148
4003
|
}
|
|
7149
4004
|
__name(getChannelPromptDisplayText, "getChannelPromptDisplayText");
|
|
7150
4005
|
function isDefinitiveAcpRequestError(error) {
|
|
7151
4006
|
if (error instanceof RequestError) return true;
|
|
7152
|
-
if (!
|
|
4007
|
+
if (!isRecord2(error)) return false;
|
|
7153
4008
|
return typeof error["code"] === "number" && Number.isInteger(error["code"]) && typeof error["message"] === "string";
|
|
7154
4009
|
}
|
|
7155
4010
|
__name(isDefinitiveAcpRequestError, "isDefinitiveAcpRequestError");
|
|
@@ -7180,7 +4035,7 @@ var MAX_LOG_SAFE_ACP_ERROR_DETAILS_CHARS = 1024;
|
|
|
7180
4035
|
var MAX_LOG_SAFE_ACP_ERROR_KIND_CHARS = 128;
|
|
7181
4036
|
var MAX_LOG_SAFE_ACP_ERROR_HINT_CHARS = 512;
|
|
7182
4037
|
function logSafeAcpErrorDetails(error) {
|
|
7183
|
-
const details = error instanceof Error ? error.message :
|
|
4038
|
+
const details = error instanceof Error ? error.message : isRecord2(error) && typeof error["message"] === "string" ? error["message"] : void 0;
|
|
7184
4039
|
if (!details) return void 0;
|
|
7185
4040
|
return {
|
|
7186
4041
|
details: details.length <= MAX_LOG_SAFE_ACP_ERROR_DETAILS_CHARS ? details : `${details.slice(0, MAX_LOG_SAFE_ACP_ERROR_DETAILS_CHARS)}\u2026`
|
|
@@ -7213,7 +4068,7 @@ function logSafeRequestErrorMessage(code) {
|
|
|
7213
4068
|
}
|
|
7214
4069
|
__name(logSafeRequestErrorMessage, "logSafeRequestErrorMessage");
|
|
7215
4070
|
function logSafeRequestErrorData(data) {
|
|
7216
|
-
if (!
|
|
4071
|
+
if (!isRecord2(data) || typeof data["errorKind"] !== "string") {
|
|
7217
4072
|
return void 0;
|
|
7218
4073
|
}
|
|
7219
4074
|
const status = data["status"];
|
|
@@ -7266,7 +4121,7 @@ function estimateAcpHandlerBytes(value, limitBytes) {
|
|
|
7266
4121
|
for (let index = current.length - 1; index >= 0; index--) {
|
|
7267
4122
|
stack.push(current[index]);
|
|
7268
4123
|
}
|
|
7269
|
-
} else if (
|
|
4124
|
+
} else if (isRecord2(current)) {
|
|
7270
4125
|
if (seen.has(current)) return limitBytes + 1;
|
|
7271
4126
|
seen.add(current);
|
|
7272
4127
|
const entries = Object.entries(current);
|
|
@@ -7418,15 +4273,15 @@ function createOutboundGuardedConnection(connection, transportGuard) {
|
|
|
7418
4273
|
}
|
|
7419
4274
|
__name(createOutboundGuardedConnection, "createOutboundGuardedConnection");
|
|
7420
4275
|
function getCanonicalModelId(response, fallback) {
|
|
7421
|
-
if (!
|
|
4276
|
+
if (!isRecord2(response) || !isRecord2(response["_meta"])) return fallback;
|
|
7422
4277
|
const modelSwitch = response["_meta"]["qwenModelSwitch"];
|
|
7423
|
-
if (!
|
|
4278
|
+
if (!isRecord2(modelSwitch)) return fallback;
|
|
7424
4279
|
const modelId = modelSwitch["modelId"];
|
|
7425
4280
|
return typeof modelId === "string" ? modelId : fallback;
|
|
7426
4281
|
}
|
|
7427
4282
|
__name(getCanonicalModelId, "getCanonicalModelId");
|
|
7428
4283
|
function isBulkReplayUpdate(value) {
|
|
7429
|
-
if (!
|
|
4284
|
+
if (!isRecord2(value)) return false;
|
|
7430
4285
|
const updateType = value["sessionUpdate"];
|
|
7431
4286
|
return typeof updateType === "string" && KNOWN_SESSION_UPDATE_TYPES.has(updateType);
|
|
7432
4287
|
}
|
|
@@ -7438,11 +4293,11 @@ function describeLoadReplayValue(value) {
|
|
|
7438
4293
|
}
|
|
7439
4294
|
__name(describeLoadReplayValue, "describeLoadReplayValue");
|
|
7440
4295
|
function extractLoadReplayResponse(state) {
|
|
7441
|
-
const meta =
|
|
4296
|
+
const meta = isRecord2(state._meta) ? state._meta : void 0;
|
|
7442
4297
|
const replay = meta?.[LOAD_REPLAY_META_KEY];
|
|
7443
4298
|
if (replay === void 0) return { state, updates: [] };
|
|
7444
|
-
if (!
|
|
7445
|
-
const version =
|
|
4299
|
+
if (!isRecord2(replay) || replay["v"] !== LOAD_REPLAY_VERSION) {
|
|
4300
|
+
const version = isRecord2(replay) ? replay["v"] : void 0;
|
|
7446
4301
|
throw new Error(
|
|
7447
4302
|
`Invalid qwen.session.loadReplay payload (type=${describeLoadReplayValue(replay)}, version=${JSON.stringify(version)})`
|
|
7448
4303
|
);
|
|
@@ -7487,7 +4342,7 @@ function extractLoadReplayResponse(state) {
|
|
|
7487
4342
|
);
|
|
7488
4343
|
if (invalidUpdateIndex !== -1) {
|
|
7489
4344
|
const invalidUpdate = rawUpdates[invalidUpdateIndex];
|
|
7490
|
-
const discriminator =
|
|
4345
|
+
const discriminator = isRecord2(invalidUpdate) ? invalidUpdate["sessionUpdate"] : void 0;
|
|
7491
4346
|
throw new Error(
|
|
7492
4347
|
`Invalid qwen.session.loadReplay update at index ${invalidUpdateIndex} (version=${LOAD_REPLAY_VERSION}, count=${rawUpdates.length}, sessionUpdate=${JSON.stringify(discriminator)})`
|
|
7493
4348
|
);
|
|
@@ -7511,7 +4366,7 @@ function extractLoadReplayResponse(state) {
|
|
|
7511
4366
|
}
|
|
7512
4367
|
__name(extractLoadReplayResponse, "extractLoadReplayResponse");
|
|
7513
4368
|
function takeRestoreAskUserQuestionHint(state) {
|
|
7514
|
-
const meta =
|
|
4369
|
+
const meta = isRecord2(state._meta) ? state._meta : void 0;
|
|
7515
4370
|
const hint = meta?.[DAEMON_RESTORE_ASK_USER_QUESTION_META_KEY] === true;
|
|
7516
4371
|
if (!hint || !meta) return { hint: false, state };
|
|
7517
4372
|
const nextMeta = { ...meta };
|
|
@@ -7651,8 +4506,8 @@ function echoPromptToSessionBus(entry, req, promptId, originatorClientId, displa
|
|
|
7651
4506
|
let displayTextPublished = false;
|
|
7652
4507
|
const serverTimestamp = Date.now();
|
|
7653
4508
|
const echoPrompt = prompt.slice(0, MAX_ECHO_CONTENT_BLOCKS);
|
|
7654
|
-
if (displayText && !echoPrompt.some((part) =>
|
|
7655
|
-
const textPart = prompt.slice(MAX_ECHO_CONTENT_BLOCKS).find((part) =>
|
|
4509
|
+
if (displayText && !echoPrompt.some((part) => isRecord2(part) && part["type"] === "text")) {
|
|
4510
|
+
const textPart = prompt.slice(MAX_ECHO_CONTENT_BLOCKS).find((part) => isRecord2(part) && part["type"] === "text");
|
|
7656
4511
|
if (textPart) echoPrompt[echoPrompt.length - 1] = textPart;
|
|
7657
4512
|
}
|
|
7658
4513
|
const blockCount = echoPrompt.length;
|
|
@@ -8289,7 +5144,7 @@ function createAcpSessionBridge(opts) {
|
|
|
8289
5144
|
);
|
|
8290
5145
|
}
|
|
8291
5146
|
}, "releaseFreshSessionReservation");
|
|
8292
|
-
const sessionCatalogGeneration =
|
|
5147
|
+
const sessionCatalogGeneration = randomUUID();
|
|
8293
5148
|
let sessionCatalogRevision = 0;
|
|
8294
5149
|
const getSessionCatalogVersion = /* @__PURE__ */ __name(() => ({
|
|
8295
5150
|
generation: sessionCatalogGeneration,
|
|
@@ -8369,7 +5224,7 @@ function createAcpSessionBridge(opts) {
|
|
|
8369
5224
|
} else {
|
|
8370
5225
|
maxPendingPromptsPerSession = maxPendingPromptsRaw;
|
|
8371
5226
|
}
|
|
8372
|
-
if (!
|
|
5227
|
+
if (!path2.isAbsolute(opts.boundWorkspace)) {
|
|
8373
5228
|
throw new TypeError(
|
|
8374
5229
|
`Invalid boundWorkspace: "${opts.boundWorkspace}". Must be an absolute path.`
|
|
8375
5230
|
);
|
|
@@ -8887,7 +5742,7 @@ function createAcpSessionBridge(opts) {
|
|
|
8887
5742
|
return limits;
|
|
8888
5743
|
}, "journalSessionLimits");
|
|
8889
5744
|
const unregisterJournalGrowthSessionLimits = journalGrowthPolicy !== void 0 && opts.registerJournalGrowthSessionLimits !== void 0 ? opts.registerJournalGrowthSessionLimits(journalSessionLimits) : void 0;
|
|
8890
|
-
const createClientId = /* @__PURE__ */ __name(() => `client_${
|
|
5745
|
+
const createClientId = /* @__PURE__ */ __name(() => `client_${randomUUID()}`, "createClientId");
|
|
8891
5746
|
const registerClient = /* @__PURE__ */ __name((entry, requestedClientId) => {
|
|
8892
5747
|
if (requestedClientId && entry.clientIds.has(requestedClientId)) {
|
|
8893
5748
|
entry.clientIds.set(
|
|
@@ -8946,7 +5801,7 @@ function createAcpSessionBridge(opts) {
|
|
|
8946
5801
|
if (inFlightChannelSpawn) return await inFlightChannelSpawn;
|
|
8947
5802
|
const promise = (async () => {
|
|
8948
5803
|
const privateParentCapability = randomBytes(32).toString("base64url");
|
|
8949
|
-
const acpChannelId =
|
|
5804
|
+
const acpChannelId = randomUUID();
|
|
8950
5805
|
const channel = await telemetry.withSpan(
|
|
8951
5806
|
"channel.spawn",
|
|
8952
5807
|
{
|
|
@@ -9073,7 +5928,8 @@ function createAcpSessionBridge(opts) {
|
|
|
9073
5928
|
markSessionCatalogChanged,
|
|
9074
5929
|
// A Goal turn drains the mid-turn queue but owns no prompt slot, so
|
|
9075
5930
|
// nothing else would settle what its last drain missed.
|
|
9076
|
-
settleMidTurnQueueAfterGoalTurn
|
|
5931
|
+
settleMidTurnQueueAfterGoalTurn,
|
|
5932
|
+
opts.onCreateCurrentSessionScheduledTask
|
|
9077
5933
|
);
|
|
9078
5934
|
const rawConnection = new ClientSideConnection(
|
|
9079
5935
|
() => channel.transportGuard ? createLogSafeAcpClient(client, channel.transportGuard) : client,
|
|
@@ -9278,8 +6134,8 @@ function createAcpSessionBridge(opts) {
|
|
|
9278
6134
|
);
|
|
9279
6135
|
}
|
|
9280
6136
|
}
|
|
9281
|
-
const activeWorkCapability =
|
|
9282
|
-
if (
|
|
6137
|
+
const activeWorkCapability = isRecord2(response._meta) ? response._meta[ACTIVE_WORK_HEARTBEAT_META_KEY] : void 0;
|
|
6138
|
+
if (isRecord2(activeWorkCapability) && activeWorkCapability["v"] === ACTIVE_WORK_HEARTBEAT_VERSION) {
|
|
9283
6139
|
const advertised = activeWorkCapability["categories"];
|
|
9284
6140
|
info.activeWork = {
|
|
9285
6141
|
intervalMs: clampActiveWorkIntervalMs(
|
|
@@ -9291,8 +6147,8 @@ function createAcpSessionBridge(opts) {
|
|
|
9291
6147
|
seq: 0
|
|
9292
6148
|
};
|
|
9293
6149
|
}
|
|
9294
|
-
const channelLivenessCapability =
|
|
9295
|
-
channelLivenessNegotiated =
|
|
6150
|
+
const channelLivenessCapability = isRecord2(response._meta) ? response._meta[CHANNEL_LIVENESS_META_KEY] : void 0;
|
|
6151
|
+
channelLivenessNegotiated = isRecord2(channelLivenessCapability) && channelLivenessCapability["v"] === CHANNEL_LIVENESS_VERSION;
|
|
9296
6152
|
try {
|
|
9297
6153
|
const attributes = getChannelStartupProfileAttributes(
|
|
9298
6154
|
response,
|
|
@@ -9364,7 +6220,7 @@ function createAcpSessionBridge(opts) {
|
|
|
9364
6220
|
}
|
|
9365
6221
|
}
|
|
9366
6222
|
__name(ensureChannel, "ensureChannel");
|
|
9367
|
-
async function doSpawn(modelServiceId, effectiveScope, approvalMode, requestedClientId, onSessionRegistered, parentSessionId, sourceType, sourceId, worktree, branch, requestedSessionId) {
|
|
6223
|
+
async function doSpawn(modelServiceId, effectiveScope, approvalMode, requestedClientId, onSessionRegistered, parentSessionId, sourceType, sourceId, worktree, branch, requestedSessionId, daemonOwnedStandaloneCreation = false, onNewSessionDispatch) {
|
|
9368
6224
|
const channelPath = channelInfo && !channelInfo.isDying ? "reused" : inFlightChannelSpawn ? "joined" : "spawned_on_request";
|
|
9369
6225
|
const ci = await telemetry.withSpan(
|
|
9370
6226
|
"channel.wait",
|
|
@@ -9401,24 +6257,28 @@ function createAcpSessionBridge(opts) {
|
|
|
9401
6257
|
mcpServers: [],
|
|
9402
6258
|
...requestedSessionId || sourceType ? {
|
|
9403
6259
|
_meta: {
|
|
9404
|
-
...sessionSourceRequestMeta(
|
|
6260
|
+
...sessionSourceRequestMeta(
|
|
6261
|
+
sourceType,
|
|
6262
|
+
sourceId,
|
|
6263
|
+
daemonOwnedStandaloneCreation
|
|
6264
|
+
),
|
|
9405
6265
|
...requestedSessionId ? {
|
|
9406
6266
|
[REQUESTED_SESSION_ID_META_KEY]: requestedSessionId
|
|
9407
6267
|
} : {}
|
|
9408
6268
|
}
|
|
9409
6269
|
} : {}
|
|
9410
6270
|
});
|
|
6271
|
+
const newSessionRequest = worktree ? {
|
|
6272
|
+
...request,
|
|
6273
|
+
_meta: {
|
|
6274
|
+
...isRecord2(request._meta) ? request._meta : {},
|
|
6275
|
+
[WORKTREE_MCP_DEFER_META_KEY]: true
|
|
6276
|
+
}
|
|
6277
|
+
} : request;
|
|
6278
|
+
onNewSessionDispatch?.();
|
|
9411
6279
|
const response = await withTimeout(
|
|
9412
6280
|
Promise.race([
|
|
9413
|
-
ci.connection.newSession(
|
|
9414
|
-
worktree ? {
|
|
9415
|
-
...request,
|
|
9416
|
-
_meta: {
|
|
9417
|
-
...isRecord3(request._meta) ? request._meta : {},
|
|
9418
|
-
[WORKTREE_MCP_DEFER_META_KEY]: true
|
|
9419
|
-
}
|
|
9420
|
-
} : request
|
|
9421
|
-
),
|
|
6281
|
+
ci.connection.newSession(newSessionRequest),
|
|
9422
6282
|
channelUnavailableReject(ci.channel, "during newSession")
|
|
9423
6283
|
]),
|
|
9424
6284
|
initTimeoutMs,
|
|
@@ -9514,7 +6374,8 @@ function createAcpSessionBridge(opts) {
|
|
|
9514
6374
|
{
|
|
9515
6375
|
sessionId: entry.sessionId,
|
|
9516
6376
|
sourceType: entry.sourceType,
|
|
9517
|
-
...entry.sourceId !== void 0 ? { sourceId: entry.sourceId } : {}
|
|
6377
|
+
...entry.sourceId !== void 0 ? { sourceId: entry.sourceId } : {},
|
|
6378
|
+
...daemonOwnedStandaloneCreation ? { [DAEMON_OWNED_STANDALONE_CREATION_KEY]: true } : {}
|
|
9518
6379
|
}
|
|
9519
6380
|
),
|
|
9520
6381
|
initTimeoutMs,
|
|
@@ -9618,14 +6479,16 @@ function createAcpSessionBridge(opts) {
|
|
|
9618
6479
|
transportClosed
|
|
9619
6480
|
]);
|
|
9620
6481
|
publishModelSwitched(entry, modelId, originatorClientId);
|
|
9621
|
-
|
|
9622
|
-
|
|
9623
|
-
|
|
9624
|
-
|
|
9625
|
-
|
|
9626
|
-
|
|
9627
|
-
|
|
9628
|
-
|
|
6482
|
+
if (!isReservedStandaloneSessionSourceType(entry.sourceType)) {
|
|
6483
|
+
broadcastWorkspaceEvent({
|
|
6484
|
+
type: "settings_changed",
|
|
6485
|
+
data: {
|
|
6486
|
+
key: "model.name",
|
|
6487
|
+
value: getCanonicalModelId(result, modelId)
|
|
6488
|
+
},
|
|
6489
|
+
...originatorClientId ? { originatorClientId } : {}
|
|
6490
|
+
});
|
|
6491
|
+
}
|
|
9629
6492
|
succeeded = true;
|
|
9630
6493
|
} catch (err) {
|
|
9631
6494
|
entry.events.publish({
|
|
@@ -9861,7 +6724,7 @@ function createAcpSessionBridge(opts) {
|
|
|
9861
6724
|
const cacheWorkspaceMcpDetails = /* @__PURE__ */ __name(async (info, status) => {
|
|
9862
6725
|
if (!Array.isArray(status.servers)) return;
|
|
9863
6726
|
const serverNames = status.servers.flatMap(
|
|
9864
|
-
(server) =>
|
|
6727
|
+
(server) => isRecord2(server) && typeof server["name"] === "string" && server["mcpStatus"] === "connected" ? [server["name"]] : []
|
|
9865
6728
|
);
|
|
9866
6729
|
const cacheDetail = /* @__PURE__ */ __name(async (serverName, method, cache) => {
|
|
9867
6730
|
try {
|
|
@@ -10330,6 +7193,12 @@ function createAcpSessionBridge(opts) {
|
|
|
10330
7193
|
workspaceCwd,
|
|
10331
7194
|
persistence: createSessionArtifactPersistence(ci.connection, sessionId)
|
|
10332
7195
|
}),
|
|
7196
|
+
artifactWorkspaceCwd: workspaceCwd,
|
|
7197
|
+
artifactWorkspaceReady: !isReservedStandaloneSessionSourceType(
|
|
7198
|
+
options.sourceType
|
|
7199
|
+
),
|
|
7200
|
+
deferredArtifactBatches: [],
|
|
7201
|
+
deferredArtifactInputCount: 0,
|
|
10333
7202
|
attachments: new SessionAttachmentStore(
|
|
10334
7203
|
opts.sessionAttachmentsRoot,
|
|
10335
7204
|
sessionId
|
|
@@ -10365,6 +7234,9 @@ function createAcpSessionBridge(opts) {
|
|
|
10365
7234
|
activeWorkCloseInFlight: false,
|
|
10366
7235
|
retryAllowed: false
|
|
10367
7236
|
};
|
|
7237
|
+
if (isReservedStandaloneSessionSourceType(options.sourceType)) {
|
|
7238
|
+
entry.prepareArtifactWorkspace = () => prepareStandaloneArtifactWorkspace(entry);
|
|
7239
|
+
}
|
|
10368
7240
|
ci.sessionIds.add(entry.sessionId);
|
|
10369
7241
|
byId.set(entry.sessionId, entry);
|
|
10370
7242
|
ci.client.clearAbandonedRestoreFence(entry.sessionId);
|
|
@@ -10381,6 +7253,40 @@ function createAcpSessionBridge(opts) {
|
|
|
10381
7253
|
}
|
|
10382
7254
|
return entry;
|
|
10383
7255
|
}, "createSessionEntry");
|
|
7256
|
+
const prepareStandaloneArtifactWorkspace = /* @__PURE__ */ __name(async (entry) => {
|
|
7257
|
+
if (!entry.artifactWorkspaceReady) {
|
|
7258
|
+
throw standaloneWorkingDirectoryMissingError();
|
|
7259
|
+
}
|
|
7260
|
+
if (entry.artifactWorkspacePreparation) {
|
|
7261
|
+
return entry.artifactWorkspacePreparation;
|
|
7262
|
+
}
|
|
7263
|
+
const owner = channelInfoForEntry(entry);
|
|
7264
|
+
if (!owner) throw standaloneWorkingDirectoryMissingError();
|
|
7265
|
+
const preparation = (async () => {
|
|
7266
|
+
const pending = entry.pendingArtifactRestore;
|
|
7267
|
+
if (pending) {
|
|
7268
|
+
delete entry.pendingArtifactRestore;
|
|
7269
|
+
try {
|
|
7270
|
+
await owner.client.ingestSessionUpdateArtifactsReady(
|
|
7271
|
+
entry,
|
|
7272
|
+
pending.replayUpdates
|
|
7273
|
+
);
|
|
7274
|
+
} catch (error) {
|
|
7275
|
+
entry.pendingArtifactRestore = pending;
|
|
7276
|
+
throw error;
|
|
7277
|
+
}
|
|
7278
|
+
}
|
|
7279
|
+
await owner.client.drainDeferredSessionArtifacts(entry);
|
|
7280
|
+
})();
|
|
7281
|
+
entry.artifactWorkspacePreparation = preparation;
|
|
7282
|
+
try {
|
|
7283
|
+
await preparation;
|
|
7284
|
+
} finally {
|
|
7285
|
+
if (entry.artifactWorkspacePreparation === preparation) {
|
|
7286
|
+
entry.artifactWorkspacePreparation = void 0;
|
|
7287
|
+
}
|
|
7288
|
+
}
|
|
7289
|
+
}, "prepareStandaloneArtifactWorkspace");
|
|
10384
7290
|
const publishArtifactChanges = /* @__PURE__ */ __name((entry, changes, originatorClientId) => {
|
|
10385
7291
|
for (const change of changes) {
|
|
10386
7292
|
entry.events.publish({
|
|
@@ -10540,7 +7446,7 @@ function createAcpSessionBridge(opts) {
|
|
|
10540
7446
|
const warnings = [];
|
|
10541
7447
|
const snapshot = normalizeSnapshotPayload(candidate, warnings);
|
|
10542
7448
|
if (!snapshot) return void 0;
|
|
10543
|
-
const snapshotWarnings =
|
|
7449
|
+
const snapshotWarnings = isRecord2(candidate) && Array.isArray(candidate["warnings"]) ? candidate["warnings"].filter(
|
|
10544
7450
|
(warning) => typeof warning === "string" && warning.length <= 1e3
|
|
10545
7451
|
).slice(-500) : [];
|
|
10546
7452
|
return {
|
|
@@ -10672,7 +7578,7 @@ function createAcpSessionBridge(opts) {
|
|
|
10672
7578
|
const hasMarker = events.some((e) => e.type === "history_truncated");
|
|
10673
7579
|
if (!hasMarker) return void 0;
|
|
10674
7580
|
const hasRecordId = events.some(
|
|
10675
|
-
(e) => transcriptEventRecordId(e) !== void 0 || e.type === "history_truncated" &&
|
|
7581
|
+
(e) => transcriptEventRecordId(e) !== void 0 || e.type === "history_truncated" && isRecord2(e.data) && typeof e.data["recordId"] === "string"
|
|
10676
7582
|
);
|
|
10677
7583
|
if (hasRecordId) return void 0;
|
|
10678
7584
|
try {
|
|
@@ -10726,6 +7632,13 @@ function createAcpSessionBridge(opts) {
|
|
|
10726
7632
|
if (shuttingDown) {
|
|
10727
7633
|
throw new Error("AcpSessionBridge is shutting down");
|
|
10728
7634
|
}
|
|
7635
|
+
const daemonOwnedStandaloneRestore = options.daemonOwnedStandaloneRestore === true;
|
|
7636
|
+
if (isReservedStandaloneSessionSourceType(req.sourceType) && !daemonOwnedStandaloneRestore) {
|
|
7637
|
+
throw new InvalidSessionMetadataError(
|
|
7638
|
+
"sourceType",
|
|
7639
|
+
"`standalone` is reserved for daemon-owned session restore"
|
|
7640
|
+
);
|
|
7641
|
+
}
|
|
10729
7642
|
const workspaceKey = resolveWorkspaceKey(req.workspaceCwd);
|
|
10730
7643
|
if (req.approvalMode !== void 0 && !KNOWN_APPROVAL_MODES.has(req.approvalMode)) {
|
|
10731
7644
|
throw new Error(
|
|
@@ -10896,8 +7809,8 @@ function createAcpSessionBridge(opts) {
|
|
|
10896
7809
|
releaseFreshSessionReservation(admission);
|
|
10897
7810
|
}, "releaseAdmissionOnce");
|
|
10898
7811
|
let resolveSettlement;
|
|
10899
|
-
const settlementPromise = new Promise((
|
|
10900
|
-
resolveSettlement =
|
|
7812
|
+
const settlementPromise = new Promise((resolve) => {
|
|
7813
|
+
resolveSettlement = resolve;
|
|
10901
7814
|
});
|
|
10902
7815
|
const restoreLifecycle = {
|
|
10903
7816
|
phase: "active"
|
|
@@ -11071,7 +7984,11 @@ function createAcpSessionBridge(opts) {
|
|
|
11071
7984
|
// same reason.
|
|
11072
7985
|
mcpServers: [],
|
|
11073
7986
|
_meta: {
|
|
11074
|
-
...sessionSourceRequestMeta(
|
|
7987
|
+
...sessionSourceRequestMeta(
|
|
7988
|
+
req.sourceType,
|
|
7989
|
+
req.sourceId,
|
|
7990
|
+
daemonOwnedStandaloneRestore
|
|
7991
|
+
),
|
|
11075
7992
|
// Decline decisions known before the child RPC: keep the
|
|
11076
7993
|
// child's replay finalize-skip aligned with the re-hang.
|
|
11077
7994
|
...opts.restoreAskUserQuestion === true && (req.clientId === void 0 || options.suppressRestorePrompt === true) ? {
|
|
@@ -11093,7 +8010,11 @@ function createAcpSessionBridge(opts) {
|
|
|
11093
8010
|
cwd: workspaceKey,
|
|
11094
8011
|
mcpServers: [],
|
|
11095
8012
|
_meta: {
|
|
11096
|
-
...sessionSourceRequestMeta(
|
|
8013
|
+
...sessionSourceRequestMeta(
|
|
8014
|
+
req.sourceType,
|
|
8015
|
+
req.sourceId,
|
|
8016
|
+
daemonOwnedStandaloneRestore
|
|
8017
|
+
),
|
|
11097
8018
|
...opts.restoreAskUserQuestion === true && (req.clientId === void 0 || options.suppressRestorePrompt === true) ? {
|
|
11098
8019
|
[DAEMON_SUPPRESS_RESTORE_ASK_USER_QUESTION_META_KEY]: true
|
|
11099
8020
|
} : {}
|
|
@@ -11105,7 +8026,7 @@ function createAcpSessionBridge(opts) {
|
|
|
11105
8026
|
}
|
|
11106
8027
|
);
|
|
11107
8028
|
const observedRestore = Promise.race([rawRestore, transportClosed]);
|
|
11108
|
-
state = await new Promise((
|
|
8029
|
+
state = await new Promise((resolve, reject) => {
|
|
11109
8030
|
const timer = setTimeout(() => {
|
|
11110
8031
|
if (restoreLifecycle.phase !== "active") return;
|
|
11111
8032
|
restoreLifecycle.phase = "abandoned";
|
|
@@ -11147,7 +8068,7 @@ function createAcpSessionBridge(opts) {
|
|
|
11147
8068
|
(value) => {
|
|
11148
8069
|
if (restoreLifecycle.phase === "active") {
|
|
11149
8070
|
clearTimeout(timer);
|
|
11150
|
-
|
|
8071
|
+
resolve(value);
|
|
11151
8072
|
return;
|
|
11152
8073
|
}
|
|
11153
8074
|
void settleAbandonedRestore(restoreChannel, "success");
|
|
@@ -11292,9 +8213,29 @@ function createAcpSessionBridge(opts) {
|
|
|
11292
8213
|
entry.restoreHistoryAnchorRecordId = replayAnchorRecordId;
|
|
11293
8214
|
}
|
|
11294
8215
|
seedSnapshotCaches(entry, publicState);
|
|
11295
|
-
const
|
|
11296
|
-
|
|
11297
|
-
);
|
|
8216
|
+
const deferArtifactWorkspace = isReservedStandaloneSessionSourceType(
|
|
8217
|
+
entry.sourceType
|
|
8218
|
+
);
|
|
8219
|
+
const artifactRestoreWarnings = [];
|
|
8220
|
+
if (deferArtifactWorkspace) {
|
|
8221
|
+
artifactRestoreWarnings.push(
|
|
8222
|
+
...await entry.artifacts.restore(restoredArtifactSnapshot, {
|
|
8223
|
+
workspaceAccess: "metadata-only"
|
|
8224
|
+
})
|
|
8225
|
+
);
|
|
8226
|
+
const artifactRestoreFailed2 = artifactRestoreWarnings.some(
|
|
8227
|
+
(warning) => isArtifactRestoreFailureWarning(warning)
|
|
8228
|
+
);
|
|
8229
|
+
entry.pendingArtifactRestore = {
|
|
8230
|
+
...restoredArtifactSnapshot !== void 0 ? { snapshot: restoredArtifactSnapshot } : {},
|
|
8231
|
+
replayUpdates: restoredArtifactSnapshot === void 0 || artifactRestoreFailed2 ? replayUpdates : [],
|
|
8232
|
+
warnings: artifactRestoreWarnings
|
|
8233
|
+
};
|
|
8234
|
+
} else {
|
|
8235
|
+
artifactRestoreWarnings.push(
|
|
8236
|
+
...await entry.artifacts.restore(restoredArtifactSnapshot)
|
|
8237
|
+
);
|
|
8238
|
+
}
|
|
11298
8239
|
for (const warning of artifactRestoreWarnings) {
|
|
11299
8240
|
writeStderrLine(
|
|
11300
8241
|
`[artifacts] session=${entry.sessionId} action=restore_warning warning=${JSON.stringify(
|
|
@@ -11307,7 +8248,7 @@ function createAcpSessionBridge(opts) {
|
|
|
11307
8248
|
);
|
|
11308
8249
|
if (replayUpdates.length > 0) {
|
|
11309
8250
|
await ci.client.seedSessionUpdates(entry, replayUpdates, {
|
|
11310
|
-
ingestArtifacts: restoredArtifactSnapshot === void 0 || artifactRestoreFailed
|
|
8251
|
+
ingestArtifacts: !deferArtifactWorkspace && (restoredArtifactSnapshot === void 0 || artifactRestoreFailed)
|
|
11311
8252
|
});
|
|
11312
8253
|
if (historyPageSize !== void 0 && entry.events.snapshotReplay()?.compactedTurns.some((event) => event.type === "history_truncated")) {
|
|
11313
8254
|
entry.restoreHistoryHasMore = true;
|
|
@@ -11338,7 +8279,7 @@ function createAcpSessionBridge(opts) {
|
|
|
11338
8279
|
...entry.sourceType ? { sourceType: entry.sourceType } : {},
|
|
11339
8280
|
...entry.sourceId !== void 0 ? { sourceId: entry.sourceId } : {},
|
|
11340
8281
|
state: publicState,
|
|
11341
|
-
...artifactRestoreWarnings.length > 0 ? { artifactWarnings: artifactRestoreWarnings } : {},
|
|
8282
|
+
...deferArtifactWorkspace || artifactRestoreWarnings.length > 0 ? { artifactWarnings: artifactRestoreWarnings } : {},
|
|
11342
8283
|
hasActivePrompt: restorePromptAdmitted || entry.promptActive || entry.goalTurnActive === true,
|
|
11343
8284
|
...replayFieldsFor(entry, action, liveReplayMode)
|
|
11344
8285
|
};
|
|
@@ -11651,6 +8592,7 @@ function createAcpSessionBridge(opts) {
|
|
|
11651
8592
|
if (undrained.length === 0) return;
|
|
11652
8593
|
settleUndrainedMidTurnMessages(entry, undrained);
|
|
11653
8594
|
}, "settleMidTurnQueueAfterGoalTurn");
|
|
8595
|
+
const trustedStandaloneSpawnRequests = /* @__PURE__ */ new WeakMap();
|
|
11654
8596
|
const bridgeApi = {
|
|
11655
8597
|
setLiveScreenContextCaptureHandler(handler) {
|
|
11656
8598
|
liveScreenContextCaptureHandler = handler;
|
|
@@ -11782,11 +8724,42 @@ function createAcpSessionBridge(opts) {
|
|
|
11782
8724
|
async resumeSession(req) {
|
|
11783
8725
|
return restoreSession("resume", req);
|
|
11784
8726
|
},
|
|
8727
|
+
async spawnStandaloneSession(req) {
|
|
8728
|
+
const spawnRequest = {
|
|
8729
|
+
workspaceCwd: req.workspaceCwd,
|
|
8730
|
+
sessionId: req.sessionId,
|
|
8731
|
+
sessionScope: "thread",
|
|
8732
|
+
sourceType: STANDALONE_SESSION_SOURCE_TYPE,
|
|
8733
|
+
...req.parentSessionId !== void 0 ? { parentSessionId: req.parentSessionId } : {},
|
|
8734
|
+
...req.modelServiceId !== void 0 ? { modelServiceId: req.modelServiceId } : {},
|
|
8735
|
+
...req.approvalMode !== void 0 ? { approvalMode: req.approvalMode } : {}
|
|
8736
|
+
};
|
|
8737
|
+
const state = { dispatched: false };
|
|
8738
|
+
trustedStandaloneSpawnRequests.set(spawnRequest, state);
|
|
8739
|
+
try {
|
|
8740
|
+
return await bridgeApi.spawnOrAttach(spawnRequest);
|
|
8741
|
+
} catch (error) {
|
|
8742
|
+
throw new StandaloneSessionSpawnError(state.dispatched, error);
|
|
8743
|
+
}
|
|
8744
|
+
},
|
|
8745
|
+
async restoreStandaloneSession(action, req) {
|
|
8746
|
+
return restoreSession(
|
|
8747
|
+
action,
|
|
8748
|
+
{
|
|
8749
|
+
...req,
|
|
8750
|
+
sourceType: STANDALONE_SESSION_SOURCE_TYPE
|
|
8751
|
+
},
|
|
8752
|
+
{ daemonOwnedStandaloneRestore: true }
|
|
8753
|
+
);
|
|
8754
|
+
},
|
|
11785
8755
|
async spawnOrAttach(req) {
|
|
11786
8756
|
if (shuttingDown) {
|
|
11787
8757
|
throw new Error("AcpSessionBridge is shutting down");
|
|
11788
8758
|
}
|
|
11789
8759
|
const workspaceKey = resolveWorkspaceKey(req.workspaceCwd);
|
|
8760
|
+
const trustedStandaloneSpawn = trustedStandaloneSpawnRequests.get(req);
|
|
8761
|
+
trustedStandaloneSpawnRequests.delete(req);
|
|
8762
|
+
const daemonOwnedStandaloneCreation = trustedStandaloneSpawn !== void 0;
|
|
11790
8763
|
if (req.sessionScope !== void 0 && req.sessionScope !== "single" && req.sessionScope !== "thread") {
|
|
11791
8764
|
throw new InvalidSessionScopeError(req.sessionScope);
|
|
11792
8765
|
}
|
|
@@ -11795,6 +8768,12 @@ function createAcpSessionBridge(opts) {
|
|
|
11795
8768
|
if ("error" in source) {
|
|
11796
8769
|
throw new InvalidSessionMetadataError("sourceType", source.error);
|
|
11797
8770
|
}
|
|
8771
|
+
if (isReservedStandaloneSessionSourceType(source.sourceType) && !daemonOwnedStandaloneCreation) {
|
|
8772
|
+
throw new InvalidSessionMetadataError(
|
|
8773
|
+
"sourceType",
|
|
8774
|
+
"`standalone` is reserved for daemon-owned session creation"
|
|
8775
|
+
);
|
|
8776
|
+
}
|
|
11798
8777
|
if (req.approvalMode !== void 0 && !KNOWN_APPROVAL_MODES.has(req.approvalMode)) {
|
|
11799
8778
|
throw new Error(
|
|
11800
8779
|
`Invalid approvalMode: ${JSON.stringify(req.approvalMode)}`
|
|
@@ -11963,9 +8942,13 @@ function createAcpSessionBridge(opts) {
|
|
|
11963
8942
|
source.sourceId,
|
|
11964
8943
|
req.worktree,
|
|
11965
8944
|
req.branch,
|
|
11966
|
-
req.sessionId
|
|
8945
|
+
req.sessionId,
|
|
8946
|
+
daemonOwnedStandaloneCreation,
|
|
8947
|
+
trustedStandaloneSpawn ? () => {
|
|
8948
|
+
trustedStandaloneSpawn.dispatched = true;
|
|
8949
|
+
} : void 0
|
|
11967
8950
|
);
|
|
11968
|
-
const tracker = effectiveScope === "single" ? workspaceKey : `${workspaceKey}#${
|
|
8951
|
+
const tracker = effectiveScope === "single" ? workspaceKey : `${workspaceKey}#${randomUUID()}`;
|
|
11969
8952
|
inFlightSpawns.set(tracker, promise);
|
|
11970
8953
|
try {
|
|
11971
8954
|
return await promise;
|
|
@@ -11995,6 +8978,9 @@ function createAcpSessionBridge(opts) {
|
|
|
11995
8978
|
)
|
|
11996
8979
|
);
|
|
11997
8980
|
}
|
|
8981
|
+
if (isReservedStandaloneSessionSourceType(entry.sourceType) && entry.managedConversationBinding?.released !== true) {
|
|
8982
|
+
return Promise.reject(standaloneWorkingDirectoryMissingError());
|
|
8983
|
+
}
|
|
11998
8984
|
if (!Array.isArray(req.prompt)) {
|
|
11999
8985
|
return Promise.reject(
|
|
12000
8986
|
RequestError.invalidParams(void 0, "Prompt must be an array")
|
|
@@ -12027,7 +9013,7 @@ function createAcpSessionBridge(opts) {
|
|
|
12027
9013
|
promptSlotReleased = true;
|
|
12028
9014
|
entry.pendingPromptCount = Math.max(0, entry.pendingPromptCount - 1);
|
|
12029
9015
|
}, "releasePromptSlot");
|
|
12030
|
-
const promptId = context?.promptId ??
|
|
9016
|
+
const promptId = context?.promptId ?? randomUUID();
|
|
12031
9017
|
const invocationContext = Object.freeze({
|
|
12032
9018
|
version: 1,
|
|
12033
9019
|
sessionId,
|
|
@@ -12052,7 +9038,7 @@ function createAcpSessionBridge(opts) {
|
|
|
12052
9038
|
context?.promptDisplayText
|
|
12053
9039
|
);
|
|
12054
9040
|
const pendingText = channelDisplayText === void 0 ? extractPromptText(req.prompt) : channelDisplayText || (req.prompt.some(
|
|
12055
|
-
(block) =>
|
|
9041
|
+
(block) => isRecord2(block) && block["type"] === "image"
|
|
12056
9042
|
) ? "[image]" : "");
|
|
12057
9043
|
const pendingEntry = {
|
|
12058
9044
|
promptId,
|
|
@@ -12169,6 +9155,9 @@ function createAcpSessionBridge(opts) {
|
|
|
12169
9155
|
}
|
|
12170
9156
|
throw new DOMException("Prompt aborted", "AbortError");
|
|
12171
9157
|
}
|
|
9158
|
+
if (isReservedStandaloneSessionSourceType(entry.sourceType) && entry.managedConversationBinding?.released !== true) {
|
|
9159
|
+
throw standaloneWorkingDirectoryMissingError();
|
|
9160
|
+
}
|
|
12172
9161
|
pendingEntry.startedAt = Date.now();
|
|
12173
9162
|
if (pendingEntry.state === "queued" || isPromotedMidTurn) {
|
|
12174
9163
|
if (pendingEntry.state === "queued") {
|
|
@@ -12539,6 +9528,11 @@ function createAcpSessionBridge(opts) {
|
|
|
12539
9528
|
if (!entry) throw new SessionNotFoundError(sessionId);
|
|
12540
9529
|
return entry.events.epoch;
|
|
12541
9530
|
},
|
|
9531
|
+
getSessionCurrentCwd(sessionId) {
|
|
9532
|
+
const entry = byId.get(sessionId);
|
|
9533
|
+
if (!entry) throw new SessionNotFoundError(sessionId);
|
|
9534
|
+
return entry.effectiveCwd;
|
|
9535
|
+
},
|
|
12542
9536
|
getSessionReplaySnapshot(sessionId) {
|
|
12543
9537
|
const entry = byId.get(sessionId);
|
|
12544
9538
|
if (!entry) throw new SessionNotFoundError(sessionId);
|
|
@@ -12622,10 +9616,22 @@ function createAcpSessionBridge(opts) {
|
|
|
12622
9616
|
if (isClosingOrAuthorizingClose(entry)) {
|
|
12623
9617
|
throw new SessionNotFoundError(sessionId, "The session is closing");
|
|
12624
9618
|
}
|
|
9619
|
+
if (isReservedStandaloneSessionSourceType(entry.sourceType)) {
|
|
9620
|
+
throw new InvalidSessionMetadataError(
|
|
9621
|
+
"sourceType",
|
|
9622
|
+
"Standalone sessions cannot be branched through the generic session API"
|
|
9623
|
+
);
|
|
9624
|
+
}
|
|
12625
9625
|
const source = parseSessionSource(req.sourceType, req.sourceId);
|
|
12626
9626
|
if ("error" in source) {
|
|
12627
9627
|
throw new InvalidSessionMetadataError("sourceType", source.error);
|
|
12628
9628
|
}
|
|
9629
|
+
if (isReservedStandaloneSessionSourceType(source.sourceType)) {
|
|
9630
|
+
throw new InvalidSessionMetadataError(
|
|
9631
|
+
"sourceType",
|
|
9632
|
+
"`standalone` is reserved for daemon-owned session creation"
|
|
9633
|
+
);
|
|
9634
|
+
}
|
|
12629
9635
|
const isSideTask = source.sourceType === "side_task";
|
|
12630
9636
|
const restoreBranch = isSideTask || req.atRecordId === void 0;
|
|
12631
9637
|
if (context?.clientId !== void 0) {
|
|
@@ -12848,6 +9854,9 @@ function createAcpSessionBridge(opts) {
|
|
|
12848
9854
|
if (shuttingDown) throw new Error("AcpSessionBridge is shutting down");
|
|
12849
9855
|
const entry = byId.get(sessionId);
|
|
12850
9856
|
if (!entry) throw new SessionNotFoundError(sessionId);
|
|
9857
|
+
if (isReservedStandaloneSessionSourceType(entry.sourceType) && (req.managedRelocation !== "live-conversation" || req.conversationDirectoryExpectation === void 0)) {
|
|
9858
|
+
throw standaloneWorkingDirectoryMissingError();
|
|
9859
|
+
}
|
|
12851
9860
|
const originatorClientId = resolveTrustedClientId(
|
|
12852
9861
|
entry,
|
|
12853
9862
|
context?.clientId
|
|
@@ -12862,7 +9871,10 @@ function createAcpSessionBridge(opts) {
|
|
|
12862
9871
|
sessionId,
|
|
12863
9872
|
path: req.path,
|
|
12864
9873
|
...req.allowedRoots ? { allowedRoots: req.allowedRoots } : {},
|
|
12865
|
-
...req.managedRelocation ? { managedRelocation: req.managedRelocation } : {}
|
|
9874
|
+
...req.managedRelocation ? { managedRelocation: req.managedRelocation } : {},
|
|
9875
|
+
...req.conversationDirectoryExpectation ? {
|
|
9876
|
+
conversationDirectoryExpectation: req.conversationDirectoryExpectation
|
|
9877
|
+
} : {}
|
|
12866
9878
|
}),
|
|
12867
9879
|
getTransportClosedReject(entry)
|
|
12868
9880
|
]);
|
|
@@ -12872,7 +9884,17 @@ function createAcpSessionBridge(opts) {
|
|
|
12872
9884
|
`changeSessionCwd: unexpected response shape from agent: ${JSON.stringify(raw)}`
|
|
12873
9885
|
);
|
|
12874
9886
|
}
|
|
9887
|
+
if (isReservedStandaloneSessionSourceType(entry.sourceType) && (req.conversationDirectoryExpectation === void 0 || extResult.newCwd !== req.conversationDirectoryExpectation.child.canonicalPath)) {
|
|
9888
|
+
throw standaloneWorkingDirectoryMissingError();
|
|
9889
|
+
}
|
|
12875
9890
|
entry.effectiveCwd = extResult.newCwd;
|
|
9891
|
+
if (isReservedStandaloneSessionSourceType(entry.sourceType) && req.conversationDirectoryExpectation !== void 0) {
|
|
9892
|
+
entry.artifactWorkspaceReady = false;
|
|
9893
|
+
entry.managedConversationBinding = {
|
|
9894
|
+
expectation: req.conversationDirectoryExpectation,
|
|
9895
|
+
released: false
|
|
9896
|
+
};
|
|
9897
|
+
}
|
|
12876
9898
|
if (extResult.previousCwd !== extResult.newCwd) {
|
|
12877
9899
|
entry.events.publish({
|
|
12878
9900
|
type: "session_cwd_changed",
|
|
@@ -12904,6 +9926,108 @@ function createAcpSessionBridge(opts) {
|
|
|
12904
9926
|
);
|
|
12905
9927
|
return { sessionId, ...result };
|
|
12906
9928
|
},
|
|
9929
|
+
async commitManagedConversationBinding(sessionId, expectation) {
|
|
9930
|
+
let entry = byId.get(sessionId);
|
|
9931
|
+
if (!entry) throw standaloneWorkingDirectoryMissingError();
|
|
9932
|
+
await entry.cwdChangeQueue;
|
|
9933
|
+
if (byId.get(sessionId) !== entry) {
|
|
9934
|
+
throw standaloneWorkingDirectoryMissingError();
|
|
9935
|
+
}
|
|
9936
|
+
const binding = entry.managedConversationBinding;
|
|
9937
|
+
if (!isReservedStandaloneSessionSourceType(entry.sourceType) || !binding || !sameConversationDirectoryExpectation(binding.expectation, expectation)) {
|
|
9938
|
+
throw standaloneWorkingDirectoryMissingError();
|
|
9939
|
+
}
|
|
9940
|
+
const response = await withTimeout(
|
|
9941
|
+
Promise.race([
|
|
9942
|
+
entry.connection.extMethod(
|
|
9943
|
+
SERVE_CONTROL_EXT_METHODS.sessionManagedConversationBindingCommit,
|
|
9944
|
+
{
|
|
9945
|
+
sessionId,
|
|
9946
|
+
conversationDirectoryExpectation: expectation
|
|
9947
|
+
}
|
|
9948
|
+
),
|
|
9949
|
+
getTransportClosedReject(entry)
|
|
9950
|
+
]),
|
|
9951
|
+
initTimeoutMs,
|
|
9952
|
+
"commitManagedConversationBinding"
|
|
9953
|
+
);
|
|
9954
|
+
if (response?.committed !== true) {
|
|
9955
|
+
throw new Error(
|
|
9956
|
+
"commitManagedConversationBinding returned an invalid acknowledgement"
|
|
9957
|
+
);
|
|
9958
|
+
}
|
|
9959
|
+
entry = byId.get(sessionId);
|
|
9960
|
+
if (!entry || entry.managedConversationBinding !== binding || !sameConversationDirectoryExpectation(binding.expectation, expectation)) {
|
|
9961
|
+
throw standaloneWorkingDirectoryMissingError();
|
|
9962
|
+
}
|
|
9963
|
+
if (entry.artifactWorkspaceCwd !== expectation.child.canonicalPath) {
|
|
9964
|
+
const artifacts = new SessionArtifactStore({
|
|
9965
|
+
sessionId,
|
|
9966
|
+
workspaceCwd: expectation.child.canonicalPath,
|
|
9967
|
+
persistence: createSessionArtifactPersistence(
|
|
9968
|
+
entry.connection,
|
|
9969
|
+
sessionId
|
|
9970
|
+
)
|
|
9971
|
+
});
|
|
9972
|
+
const pending = entry.pendingArtifactRestore;
|
|
9973
|
+
if (pending) {
|
|
9974
|
+
const warnings = await artifacts.restore(pending.snapshot, {
|
|
9975
|
+
workspaceAccess: "metadata-only"
|
|
9976
|
+
});
|
|
9977
|
+
for (const warning of warnings) {
|
|
9978
|
+
if (!pending.warnings.includes(warning)) {
|
|
9979
|
+
pending.warnings.push(warning);
|
|
9980
|
+
writeStderrLine(
|
|
9981
|
+
`[artifacts] session=${entry.sessionId} action=restore_warning warning=${JSON.stringify(
|
|
9982
|
+
warning
|
|
9983
|
+
)}`
|
|
9984
|
+
);
|
|
9985
|
+
}
|
|
9986
|
+
}
|
|
9987
|
+
}
|
|
9988
|
+
entry.artifacts = artifacts;
|
|
9989
|
+
entry.artifactWorkspaceCwd = expectation.child.canonicalPath;
|
|
9990
|
+
} else {
|
|
9991
|
+
entry.artifacts.resetWorkspaceResolutionCache();
|
|
9992
|
+
}
|
|
9993
|
+
},
|
|
9994
|
+
async releaseManagedConversationBinding(sessionId, expectation) {
|
|
9995
|
+
let entry = byId.get(sessionId);
|
|
9996
|
+
if (!entry) throw standaloneWorkingDirectoryMissingError();
|
|
9997
|
+
await entry.cwdChangeQueue;
|
|
9998
|
+
if (byId.get(sessionId) !== entry) {
|
|
9999
|
+
throw standaloneWorkingDirectoryMissingError();
|
|
10000
|
+
}
|
|
10001
|
+
const binding = entry.managedConversationBinding;
|
|
10002
|
+
if (!isReservedStandaloneSessionSourceType(entry.sourceType) || !binding || !sameConversationDirectoryExpectation(binding.expectation, expectation)) {
|
|
10003
|
+
throw standaloneWorkingDirectoryMissingError();
|
|
10004
|
+
}
|
|
10005
|
+
const response = await withTimeout(
|
|
10006
|
+
Promise.race([
|
|
10007
|
+
entry.connection.extMethod(
|
|
10008
|
+
SERVE_CONTROL_EXT_METHODS.sessionManagedConversationBindingRelease,
|
|
10009
|
+
{
|
|
10010
|
+
sessionId,
|
|
10011
|
+
conversationDirectoryExpectation: expectation
|
|
10012
|
+
}
|
|
10013
|
+
),
|
|
10014
|
+
getTransportClosedReject(entry)
|
|
10015
|
+
]),
|
|
10016
|
+
initTimeoutMs,
|
|
10017
|
+
"releaseManagedConversationBinding"
|
|
10018
|
+
);
|
|
10019
|
+
if (response?.released !== true) {
|
|
10020
|
+
throw new Error(
|
|
10021
|
+
"releaseManagedConversationBinding returned an invalid acknowledgement"
|
|
10022
|
+
);
|
|
10023
|
+
}
|
|
10024
|
+
entry = byId.get(sessionId);
|
|
10025
|
+
if (!entry || entry.managedConversationBinding !== binding || !sameConversationDirectoryExpectation(binding.expectation, expectation)) {
|
|
10026
|
+
throw standaloneWorkingDirectoryMissingError();
|
|
10027
|
+
}
|
|
10028
|
+
binding.released = true;
|
|
10029
|
+
entry.artifactWorkspaceReady = true;
|
|
10030
|
+
},
|
|
12907
10031
|
setSessionWorktree(sessionId, worktree) {
|
|
12908
10032
|
const entry = byId.get(sessionId);
|
|
12909
10033
|
if (entry) {
|
|
@@ -12914,6 +10038,24 @@ function createAcpSessionBridge(opts) {
|
|
|
12914
10038
|
async closeSession(sessionId, context, closeOpts) {
|
|
12915
10039
|
return closeSessionImpl(sessionId, context, closeOpts);
|
|
12916
10040
|
},
|
|
10041
|
+
async ensureDefaultSessionPersisted(sessionId) {
|
|
10042
|
+
const entry = byId.get(sessionId);
|
|
10043
|
+
if (!entry) throw new SessionNotFoundError(sessionId);
|
|
10044
|
+
const result = await withTimeout(
|
|
10045
|
+
Promise.race([
|
|
10046
|
+
entry.connection.extMethod(SERVE_CONTROL_EXT_METHODS.sessionSource, {
|
|
10047
|
+
sessionId,
|
|
10048
|
+
sourceType: "default"
|
|
10049
|
+
}),
|
|
10050
|
+
getTransportClosedReject(entry)
|
|
10051
|
+
]),
|
|
10052
|
+
initTimeoutMs,
|
|
10053
|
+
"ensureDefaultSessionPersisted"
|
|
10054
|
+
);
|
|
10055
|
+
if (result?.persisted !== true) {
|
|
10056
|
+
throw new Error(`Session '${sessionId}' could not be persisted`);
|
|
10057
|
+
}
|
|
10058
|
+
},
|
|
12917
10059
|
updateSessionMetadata(sessionId, metadata, context) {
|
|
12918
10060
|
const entry = byId.get(sessionId);
|
|
12919
10061
|
if (!entry) throw new SessionNotFoundError(sessionId);
|
|
@@ -13021,13 +10163,21 @@ function createAcpSessionBridge(opts) {
|
|
|
13021
10163
|
async getSessionArtifacts(sessionId, context) {
|
|
13022
10164
|
const entry = byId.get(sessionId);
|
|
13023
10165
|
if (!entry) throw new SessionNotFoundError(sessionId);
|
|
10166
|
+
if (isReservedStandaloneSessionSourceType(entry.sourceType) && entry.managedConversationBinding?.released !== true) {
|
|
10167
|
+
throw standaloneWorkingDirectoryMissingError();
|
|
10168
|
+
}
|
|
13024
10169
|
resolveTrustedClientId(entry, context?.clientId);
|
|
10170
|
+
await entry.prepareArtifactWorkspace?.();
|
|
13025
10171
|
return entry.artifacts.list();
|
|
13026
10172
|
},
|
|
13027
10173
|
async addSessionArtifact(sessionId, artifact, context) {
|
|
13028
10174
|
const entry = byId.get(sessionId);
|
|
13029
10175
|
if (!entry) throw new SessionNotFoundError(sessionId);
|
|
10176
|
+
if (isReservedStandaloneSessionSourceType(entry.sourceType) && entry.managedConversationBinding?.released !== true) {
|
|
10177
|
+
throw standaloneWorkingDirectoryMissingError();
|
|
10178
|
+
}
|
|
13030
10179
|
const clientId = resolveTrustedClientId(entry, context?.clientId);
|
|
10180
|
+
await entry.prepareArtifactWorkspace?.();
|
|
13031
10181
|
const input = makeClientArtifactInput(artifact, clientId);
|
|
13032
10182
|
const result = await entry.artifacts.upsertMany([input], {
|
|
13033
10183
|
validationStrict: true,
|
|
@@ -13040,14 +10190,18 @@ function createAcpSessionBridge(opts) {
|
|
|
13040
10190
|
async removeSessionArtifact(sessionId, artifactId, context) {
|
|
13041
10191
|
const entry = byId.get(sessionId);
|
|
13042
10192
|
if (!entry) throw new SessionNotFoundError(sessionId);
|
|
10193
|
+
if (isReservedStandaloneSessionSourceType(entry.sourceType) && entry.managedConversationBinding?.released !== true) {
|
|
10194
|
+
throw standaloneWorkingDirectoryMissingError();
|
|
10195
|
+
}
|
|
13043
10196
|
const clientId = resolveTrustedClientId(entry, context?.clientId);
|
|
10197
|
+
await entry.prepareArtifactWorkspace?.();
|
|
13044
10198
|
const result = await entry.artifacts.remove(artifactId, { clientId });
|
|
13045
10199
|
publishArtifactChanges(entry, result.changes, clientId);
|
|
13046
10200
|
const warnings = [...result.warnings ?? []];
|
|
13047
10201
|
return warnings.length > 0 ? { ...result, warnings } : result;
|
|
13048
10202
|
},
|
|
13049
10203
|
listWorkspaceSessions(workspaceCwd) {
|
|
13050
|
-
if (!
|
|
10204
|
+
if (!path2.isAbsolute(workspaceCwd)) return [];
|
|
13051
10205
|
const key = workspaceCwd === boundWorkspace ? boundWorkspace : canonicalizeWorkspace(workspaceCwd);
|
|
13052
10206
|
if (key !== boundWorkspace) return [];
|
|
13053
10207
|
const out = [];
|
|
@@ -13589,14 +10743,16 @@ function createAcpSessionBridge(opts) {
|
|
|
13589
10743
|
transportClosed
|
|
13590
10744
|
]);
|
|
13591
10745
|
publishModelSwitched(entry, req.modelId, originatorClientId);
|
|
13592
|
-
|
|
13593
|
-
|
|
13594
|
-
|
|
13595
|
-
|
|
13596
|
-
|
|
13597
|
-
|
|
13598
|
-
|
|
13599
|
-
|
|
10746
|
+
if (!isReservedStandaloneSessionSourceType(entry.sourceType)) {
|
|
10747
|
+
broadcastWorkspaceEvent({
|
|
10748
|
+
type: "settings_changed",
|
|
10749
|
+
data: {
|
|
10750
|
+
key: "model.name",
|
|
10751
|
+
value: getCanonicalModelId(result, req.modelId)
|
|
10752
|
+
},
|
|
10753
|
+
...originatorClientId ? { originatorClientId } : {}
|
|
10754
|
+
});
|
|
10755
|
+
}
|
|
13600
10756
|
succeeded = true;
|
|
13601
10757
|
return result;
|
|
13602
10758
|
} finally {
|
|
@@ -13717,6 +10873,12 @@ function createAcpSessionBridge(opts) {
|
|
|
13717
10873
|
async setSessionApprovalMode(sessionId, mode, opts2, context) {
|
|
13718
10874
|
const entry = byId.get(sessionId);
|
|
13719
10875
|
if (!entry) throw new SessionNotFoundError(sessionId);
|
|
10876
|
+
if (opts2.persist && isReservedStandaloneSessionSourceType(entry.sourceType)) {
|
|
10877
|
+
throw new InvalidSessionMetadataError(
|
|
10878
|
+
"persist",
|
|
10879
|
+
"Standalone approval mode changes are session-scoped"
|
|
10880
|
+
);
|
|
10881
|
+
}
|
|
13720
10882
|
const info = channelInfoForEntry(entry);
|
|
13721
10883
|
if (!info || info.isDying) throw new SessionNotFoundError(sessionId);
|
|
13722
10884
|
const originatorClientId = resolveTrustedClientId(
|
|
@@ -13764,7 +10926,7 @@ function createAcpSessionBridge(opts) {
|
|
|
13764
10926
|
const info = channelInfoForEntry(entry);
|
|
13765
10927
|
if (!info || info.isDying) throw new SessionNotFoundError(sessionId);
|
|
13766
10928
|
resolveTrustedClientId(entry, context?.clientId);
|
|
13767
|
-
const requestId =
|
|
10929
|
+
const requestId = randomUUID();
|
|
13768
10930
|
const queue = new GenerationStreamQueue(
|
|
13769
10931
|
GENERATION_STREAM_QUEUE_CAPACITY
|
|
13770
10932
|
);
|
|
@@ -14030,7 +11192,7 @@ function createAcpSessionBridge(opts) {
|
|
|
14030
11192
|
return { accepted: false };
|
|
14031
11193
|
}
|
|
14032
11194
|
}
|
|
14033
|
-
const messageId = requestedMessageId ??
|
|
11195
|
+
const messageId = requestedMessageId ?? randomUUID();
|
|
14034
11196
|
if (entry.pendingPromptCount === 0 && entry.goalTurnActive !== true) {
|
|
14035
11197
|
if (options?.queueOnly || options?.rejectIfIdle) {
|
|
14036
11198
|
writeStderrLine(
|
|
@@ -14300,9 +11462,9 @@ function createAcpSessionBridge(opts) {
|
|
|
14300
11462
|
try {
|
|
14301
11463
|
await Promise.race([
|
|
14302
11464
|
entry.cwdChangeQueue,
|
|
14303
|
-
new Promise((
|
|
14304
|
-
abortResolve =
|
|
14305
|
-
if (signal?.aborted) return
|
|
11465
|
+
new Promise((resolve) => {
|
|
11466
|
+
abortResolve = resolve;
|
|
11467
|
+
if (signal?.aborted) return resolve();
|
|
14306
11468
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
14307
11469
|
})
|
|
14308
11470
|
]);
|
|
@@ -14677,7 +11839,7 @@ function createAcpSessionBridge(opts) {
|
|
|
14677
11839
|
]);
|
|
14678
11840
|
},
|
|
14679
11841
|
generateWorkspaceContent(prompt, signal, _originatorClientId) {
|
|
14680
|
-
const requestId =
|
|
11842
|
+
const requestId = randomUUID();
|
|
14681
11843
|
const queue = new GenerationStreamQueue(
|
|
14682
11844
|
GENERATION_STREAM_QUEUE_CAPACITY
|
|
14683
11845
|
);
|
|
@@ -14975,8 +12137,8 @@ function createAcpSessionBridge(opts) {
|
|
|
14975
12137
|
const shutdownReason = options?.reason ?? "daemon_shutdown";
|
|
14976
12138
|
let resolveShutdown;
|
|
14977
12139
|
let rejectShutdown;
|
|
14978
|
-
shutdownPromise = new Promise((
|
|
14979
|
-
resolveShutdown =
|
|
12140
|
+
shutdownPromise = new Promise((resolve, reject) => {
|
|
12141
|
+
resolveShutdown = resolve;
|
|
14980
12142
|
rejectShutdown = reject;
|
|
14981
12143
|
});
|
|
14982
12144
|
void (async () => {
|
|
@@ -15091,9 +12253,7 @@ var createHttpAcpBridge = createAcpSessionBridge;
|
|
|
15091
12253
|
export {
|
|
15092
12254
|
SessionArtifactValidationError,
|
|
15093
12255
|
SessionArtifactAuthorizationError,
|
|
15094
|
-
SESSION_SOURCE_META_KEY,
|
|
15095
12256
|
parseSessionSource,
|
|
15096
|
-
isA2uiToolMeta,
|
|
15097
12257
|
extractErrorMessage,
|
|
15098
12258
|
extractErrorCode,
|
|
15099
12259
|
classifyTurnErrorKind,
|