@qwen-code/qwen-code 0.23.2-preview.0 → 0.23.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/qc-helper/docs/configuration/auth.md +1 -1
- package/bundled/qc-helper/docs/configuration/model-providers.md +19 -15
- package/bundled/qc-helper/docs/features/channels/dingtalk.md +1 -1
- package/bundled/qc-helper/docs/features/channels/github.md +1 -9
- package/bundled/qc-helper/docs/features/channels/overview.md +26 -51
- package/bundled/qc-helper/docs/features/channels/qqbot.md +1 -2
- package/bundled/qc-helper/docs/features/commands.md +48 -9
- package/bundled/qc-helper/docs/features/goals.md +2 -0
- package/bundled/qc-helper/docs/features/sub-agents.md +8 -0
- package/bundled/qc-helper/docs/qwen-serve.md +62 -48
- package/chunks/{MaxSizedBox-ZZJT3PJF.js → MaxSizedBox-4C3BKKKY.js} +17 -16
- package/chunks/{StandaloneSessionPicker-DIOSZTMX.js → StandaloneSessionPicker-TXVF65YW.js} +32 -31
- package/chunks/{acpAgent-62JUP5A7.js → acpAgent-H5XBKGB4.js} +301 -166
- package/chunks/{agent-NLPEQQDZ.js → agent-T5LEFJIU.js} +10 -10
- package/chunks/{agent-headless-Y2GR5F2Q.js → agent-headless-VZKSRH36.js} +10 -10
- package/chunks/{anthropicContentGenerator-I7LQZH26.js → anthropicContentGenerator-PRJWWVYJ.js} +2 -2
- package/chunks/{artifact-tool-P52OKKEF.js → artifact-tool-G5LHNECG.js} +1 -1
- package/chunks/{bridge-Q66BD62H.js → bridge-AOX5IY44.js} +18 -17
- package/chunks/{channel-management-service-TRTLQTSV.js → channel-management-service-MWQGHPAS.js} +4 -4
- package/chunks/{channel-settings-store-R5STFWUW.js → channel-settings-store-XZCGDGHW.js} +26 -25
- package/chunks/{channel-worker-group-PIYPVCTF.js → channel-worker-group-SQ5FRWOH.js} +5 -5
- package/chunks/{channel-worker-manager-FQHEDTD2.js → channel-worker-manager-TU4ZNWOE.js} +5 -5
- package/chunks/{channel-worker-supervisor-XJPZZ3VK.js → channel-worker-supervisor-EMITFMCP.js} +4 -4
- package/chunks/{chunk-3SH53ANB.js → chunk-22GORRNY.js} +12 -0
- package/chunks/{chunk-7L35FKPF.js → chunk-2BMXHB6N.js} +2 -2
- package/chunks/chunk-2WC3Y7YY.js +1033 -0
- package/chunks/{chunk-WZ4YULVF.js → chunk-3OAXF3UK.js} +210 -80
- package/chunks/{chunk-W673V2H3.js → chunk-46MMBZHI.js} +3 -3
- package/chunks/{chunk-6GRDHIVI.js → chunk-4I3WFI3U.js} +4 -4
- package/chunks/{chunk-QXXBW2G4.js → chunk-4JNHNVAZ.js} +3 -3
- package/chunks/{chunk-R2266LQ5.js → chunk-4WMGYG3F.js} +2 -2
- package/chunks/{chunk-Y6ZWXM44.js → chunk-5BH2AIEY.js} +1 -1
- package/chunks/{chunk-EA4NFWVG.js → chunk-5OZUKLL7.js} +3 -3
- package/chunks/{chunk-RX3FYFCN.js → chunk-67XTVRVB.js} +1 -1
- package/chunks/{chunk-MA5OUNN6.js → chunk-6G4V7SE4.js} +1 -1
- package/chunks/{chunk-B54PFQS5.js → chunk-7CLLH5TI.js} +1 -1
- package/chunks/{chunk-7GZCCHJL.js → chunk-7LTCFO6T.js} +3 -3
- package/chunks/{chunk-33MLFMCL.js → chunk-7NDZKE2M.js} +2 -2
- package/chunks/{chunk-5WC2ECUK.js → chunk-7PD7ZMX5.js} +17 -4
- package/chunks/{chunk-ZUNWKOAP.js → chunk-7SVM3RP2.js} +4 -24
- package/chunks/{chunk-5GD7LYOH.js → chunk-A7EFXK7P.js} +1 -1
- package/chunks/{chunk-RDV4NLI3.js → chunk-AO2VMS72.js} +2 -2
- package/chunks/{chunk-XAXTQURR.js → chunk-APXIW2TF.js} +3 -3
- package/chunks/{chunk-3BTHHXXE.js → chunk-AUTUFD5X.js} +1 -1
- package/chunks/{chunk-VH764G6B.js → chunk-B25JYXZE.js} +2 -2
- package/chunks/{chunk-MMMA36MN.js → chunk-BK7D2JR6.js} +3 -3
- package/chunks/{chunk-RPFJJ33Z.js → chunk-CALTQWTZ.js} +1 -1
- package/chunks/{chunk-EUXF6URV.js → chunk-CCBTNJB4.js} +16 -18
- package/chunks/{chunk-BF3RUIVP.js → chunk-CH2YDT6X.js} +36 -38
- package/chunks/{chunk-L6ZME3W7.js → chunk-CMWU6P4D.js} +1 -1
- package/chunks/{chunk-QJAFSFYA.js → chunk-CWVZBIZJ.js} +1 -1
- package/chunks/{chunk-JDPBJIKI.js → chunk-DAI65CMX.js} +1 -1
- package/chunks/{chunk-GM44O6F7.js → chunk-DARWOZY6.js} +1 -1
- package/chunks/{chunk-J4ECGDA3.js → chunk-ELD3OBPU.js} +3 -3
- package/chunks/{chunk-ZELCTN6Y.js → chunk-F23YN37B.js} +10 -0
- package/chunks/{chunk-OIPUQE3S.js → chunk-F2NYVASI.js} +14 -1
- package/chunks/{chunk-ODBNTMZW.js → chunk-FAJPLSYF.js} +36 -29
- package/chunks/{chunk-GXIHBIHC.js → chunk-FGHPZGOP.js} +29 -24
- package/chunks/{chunk-TK4XHPKW.js → chunk-G37O7YO6.js} +2 -2
- package/chunks/{chunk-4JKLPTV6.js → chunk-G3ZUMSFS.js} +12 -5
- package/chunks/{chunk-743XE7N7.js → chunk-G5VOXCRX.js} +1 -1
- package/chunks/{chunk-BL6B2QS2.js → chunk-GOEVQADN.js} +618 -1122
- package/chunks/{chunk-XI7RST4D.js → chunk-GOKAOSCN.js} +1 -1
- package/chunks/{chunk-FDOGGKAA.js → chunk-GSFV5RQW.js} +2 -0
- package/chunks/{chunk-43CI6YWJ.js → chunk-HJ5UJQQL.js} +1384 -1245
- package/chunks/{chunk-LUVXLLMS.js → chunk-IR6JKBAP.js} +1 -1
- package/chunks/{chunk-CPJRFFPH.js → chunk-IRGDIDGJ.js} +558 -158
- package/chunks/{chunk-DCPRCDSR.js → chunk-IWABUX6T.js} +1 -1
- package/chunks/{chunk-DTB5BXRG.js → chunk-JO77FGNZ.js} +16 -15
- package/chunks/{chunk-45ROPFQY.js → chunk-JYGIJA4W.js} +1 -1
- package/chunks/{chunk-M6YLOETP.js → chunk-KCF2436G.js} +1 -1
- package/chunks/{chunk-CABDNUJ4.js → chunk-KEPC5BOL.js} +5 -5
- package/chunks/{chunk-RN6V5HKE.js → chunk-KFECPHLV.js} +3 -3
- package/chunks/{chunk-WIRYFL2L.js → chunk-LEAOJ43M.js} +17 -15
- package/chunks/{chunk-IUJLW6ZI.js → chunk-LUNC5KCL.js} +3 -3
- package/chunks/{chunk-WZS3X663.js → chunk-LWX4WDFF.js} +2 -5
- package/chunks/{chunk-ISPBBKJN.js → chunk-MV5DTLJV.js} +7 -2
- package/chunks/chunk-NFXXBWSE.js +463 -0
- package/chunks/{chunk-PQSCGZT2.js → chunk-NPKCVSXX.js} +26 -2
- package/chunks/{chunk-7RXOEUFF.js → chunk-NX7ZTXFF.js} +1 -1
- package/chunks/{chunk-G5QLLD53.js → chunk-OXR34GCD.js} +14 -2
- package/chunks/{chunk-VHPG7BH6.js → chunk-PDN7FJZV.js} +1 -1
- package/chunks/{chunk-UHUT4IDM.js → chunk-PFKGV6PO.js} +9 -9
- package/chunks/{chunk-NFIDVZND.js → chunk-QB3KS3N3.js} +2 -21
- package/chunks/{chunk-QLM7D7W3.js → chunk-QFJZAY7W.js} +7 -7
- package/chunks/{chunk-NVS4Z6GT.js → chunk-QHLHE2OT.js} +1 -1
- package/chunks/{chunk-B44JBZBB.js → chunk-QQK4L3UU.js} +1 -1
- package/chunks/{chunk-63ACHB2H.js → chunk-QTB4VP4K.js} +50 -18
- package/chunks/{chunk-4BLTSTF6.js → chunk-R3JIDRUL.js} +4 -4
- package/chunks/{chunk-VEQBVJYH.js → chunk-R457KFV7.js} +3 -3
- package/chunks/{chunk-CG2PDIGD.js → chunk-RM244SCQ.js} +1 -1
- package/chunks/{chunk-W7PXLIZJ.js → chunk-RNAJVXUG.js} +5 -5
- package/chunks/{chunk-VF6D6EGV.js → chunk-S5QR6CB5.js} +52 -6
- package/chunks/{chunk-IRKQ4YYX.js → chunk-SVWWF5TZ.js} +29 -1
- package/chunks/{chunk-5HBA2Z7V.js → chunk-TGNNLRC3.js} +2 -3
- package/chunks/{chunk-ORREXWOM.js → chunk-TODNHE76.js} +3 -3
- package/chunks/{chunk-W3ONSAYO.js → chunk-TOOOLHVS.js} +2 -2
- package/chunks/{chunk-EHTKOO6W.js → chunk-TRQNRP2H.js} +2 -2
- package/chunks/{chunk-PQWM4SX2.js → chunk-U6REWGVR.js} +2 -2
- package/chunks/{chunk-UXDVUFKU.js → chunk-ULZECEIP.js} +2 -2
- package/chunks/{chunk-6YEOCHSE.js → chunk-VBLAI2HA.js} +101 -0
- package/chunks/{chunk-M73KHGNR.js → chunk-VJJXG73H.js} +404 -226
- package/chunks/{chunk-Q2F43WTJ.js → chunk-WBU2PIZ5.js} +23 -6
- package/chunks/{chunk-UULHYZ4W.js → chunk-WDN64MVT.js} +74 -15
- package/chunks/{chunk-XVTCSVBK.js → chunk-WDVV3LRM.js} +193 -169
- package/chunks/{chunk-LBKYQ33T.js → chunk-WK7P62DV.js} +16 -10
- package/chunks/{chunk-ULY4ISY2.js → chunk-WXD7INFV.js} +19 -4
- package/chunks/{chunk-ZIPYCXQP.js → chunk-WXPPUPHM.js} +1 -1
- package/chunks/{chunk-5Z4FCEPY.js → chunk-X7LFJF5D.js} +600 -46
- package/chunks/{chunk-YHDGABDC.js → chunk-XKBFYTCD.js} +1 -1
- package/chunks/{chunk-MWEF5MUK.js → chunk-XSOXPG2L.js} +8 -8
- package/chunks/{chunk-5VZFJTL2.js → chunk-YLOHCA6I.js} +2 -2
- package/chunks/{chunk-LN3WBOKU.js → chunk-YQGW3M6Z.js} +2 -2
- package/chunks/{chunk-2OR32P5M.js → chunk-ZN5TKAVR.js} +125 -10
- package/chunks/{chunk-XBLG564H.js → chunk-ZP5XDLSA.js} +10 -10
- package/chunks/{config-utils-37FHWPTP.js → config-utils-GPLIABL2.js} +21 -20
- package/chunks/{contextCommand-R4JUBV5N.js → contextCommand-NCUQUDBY.js} +18 -17
- package/chunks/{core-runtime-6U4OXU5C.js → core-runtime-WKYKWVYU.js} +23 -21
- package/chunks/{create-sub-session-LLUYAGZN.js → create-sub-session-PPLXB3JJ.js} +17 -16
- package/chunks/{daemon-UI6SQSBV.js → daemon-2CKIAO3I.js} +2 -2
- package/chunks/{daemon-git-worktree-guard-O2UTNFJC.js → daemon-git-worktree-guard-73ZWOLKL.js} +17 -16
- package/chunks/{daemon-status-provider-7GMAKSTX.js → daemon-status-provider-OK7VONJG.js} +22 -21
- package/chunks/{daemon-trust-policy-VNP76VMO.js → daemon-trust-policy-CAUCREG2.js} +22 -21
- package/chunks/{daemon-trust-policy-monitor-TD5O3BZA.js → daemon-trust-policy-monitor-3Y36OOEQ.js} +22 -21
- package/chunks/{deferred-core-runtime-NJM63YJS.js → deferred-core-runtime-Q7WFWYD3.js} +17 -16
- package/chunks/{dist-STAGIFNB.js → dist-6YDUH3BB.js} +388 -102
- package/chunks/{dist-XJ25G6OH.js → dist-BNQVF565.js} +2 -8
- package/chunks/{dist-NQ2636VT.js → dist-F34J4FZX.js} +1 -1
- package/chunks/{dist-OR2ZQTXZ.js → dist-F6JLAJLE.js} +1 -1
- package/chunks/{dist-WZTUKIDR.js → dist-NP7QKLVG.js} +2 -2
- package/chunks/{dist-M4CBX53H.js → dist-UFTFGI6I.js} +1 -1
- package/chunks/{dist-5I4HLUTO.js → dist-XLV33CLY.js} +1 -2
- package/chunks/{dist-24TCWK5H.js → dist-Y5KC3IHW.js} +1 -2
- package/chunks/{dist-FDISN4AK.js → dist-ZDNJK74R.js} +3 -7
- package/chunks/{edit-GL7OA6RV.js → edit-YJD6IETK.js} +10 -10
- package/chunks/{enterPlanMode-VC4BCSZE.js → enterPlanMode-EFPVTWIH.js} +10 -10
- package/chunks/{environment-BI6IBAZJ.js → environment-HP3AXFTB.js} +19 -18
- package/chunks/{errors-D6G2ZM2R.js → errors-22ON46G7.js} +19 -18
- package/chunks/{exitPlanMode-DX446CA6.js → exitPlanMode-MOQUNCBH.js} +10 -10
- package/chunks/{fast-path-MAYDLJIP.js → fast-path-2X7BPMXX.js} +3 -3
- package/chunks/{glob-TVKRFY36.js → glob-Y7ACIB3P.js} +10 -10
- package/chunks/{goal-tools-CA6YANRN.js → goal-tools-DABMICUC.js} +10 -10
- package/chunks/{handleAutoUpdate-AW4ZAHON.js → handleAutoUpdate-MCAMHYDI.js} +19 -18
- package/chunks/{i18n-IH5CRAY7.js → i18n-C3GDCALP.js} +17 -16
- package/chunks/{initializer-UBR37ZNM.js → initializer-4URDQL2G.js} +22 -21
- package/chunks/{installationInfo-Y52756EC.js → installationInfo-LBOL6SBV.js} +17 -16
- package/chunks/{list-7V5JDZPO.js → list-JOGBAC2R.js} +25 -24
- package/chunks/{list-agents-I5TRH3J7.js → list-agents-2CXPGJUE.js} +3 -3
- package/chunks/{llm-QDB6JVF5.js → llm-2KPNSAVY.js} +65 -59
- package/chunks/{llm-content-generator-EO6MZCEO.js → llm-content-generator-GR3FOHLB.js} +2 -2
- package/chunks/{loadedSettingsAdapter-I3YQKMC4.js → loadedSettingsAdapter-DVKROEJD.js} +22 -21
- package/chunks/{loggingContentGenerator-MNXYUGDG.js → loggingContentGenerator-P4K6K5W4.js} +12 -12
- package/chunks/{managed-npm-update-EA3EXPTO.js → managed-npm-update-VWBKHNIN.js} +17 -16
- package/chunks/{mcp-QTAJ544Y.js → mcp-6YYNSYLK.js} +22 -21
- package/chunks/{monitor-6FQKQOTP.js → monitor-A255TNZR.js} +10 -10
- package/chunks/{nonInteractiveCli-MRCTZL5X.js → nonInteractiveCli-LXGIKQAT.js} +55 -54
- package/chunks/{notebook-edit-BQQCKI7K.js → notebook-edit-5UYHS2DM.js} +10 -10
- package/chunks/{open-with-auth-D6JD6LDS.js → open-with-auth-6MVIHMXB.js} +1 -1
- package/chunks/{openaiContentGenerator-TTSKXNVH.js → openaiContentGenerator-A6MAMEO5.js} +7 -7
- package/chunks/{pidfile-KPIS6RIT.js → pidfile-B7DZC4TP.js} +20 -17
- package/chunks/{processUtils-ZCASPKK4.js → processUtils-BPZ2MCZS.js} +2 -2
- package/chunks/{prompt-terminal-ledger-D6HM6TV7.js → prompt-terminal-ledger-FYYTAOXS.js} +18 -17
- package/chunks/{qwenContentGenerator-GGZEFLBD.js → qwenContentGenerator-4IOJFD4S.js} +13 -13
- package/chunks/{read-file-VE5GDFLP.js → read-file-A4H4O3VQ.js} +2 -2
- package/chunks/{resumeHistoryUtils-HSSGMYDC.js → resumeHistoryUtils-YQQ43E4N.js} +19 -18
- package/chunks/{ripGrep-SEJE7AA6.js → ripGrep-W32NWFTA.js} +3 -3
- package/chunks/{run-qwen-serve-OJYVH7N6.js → run-qwen-serve-WBSWWIRV.js} +216 -64
- package/chunks/{runtime-KGMQQFJD.js → runtime-VRVBX2E5.js} +29 -26
- package/chunks/{scheduled-tasks-7H5HWHYD.js → scheduled-tasks-D65KJDA3.js} +20 -19
- package/chunks/{scheduler-BE42AKDG.js → scheduler-M2YPN7Z7.js} +19 -18
- package/chunks/{send-message-AWKUDJBH.js → send-message-76BKWKFZ.js} +3 -3
- package/chunks/{serve-KZVKYMSL.js → serve-BE3H2P6P.js} +22 -21
- package/chunks/{server-PJRALPJQ.js → server-FLY5JERG.js} +84 -148
- package/chunks/{session-IHITXU2S.js → session-JVN62R5K.js} +85 -57
- package/chunks/{session-attachments-root-GQ2TUI3Y.js → session-attachments-root-7ACGLHXA.js} +17 -16
- package/chunks/{session-pr-refresh-H43LULTE.js → session-pr-refresh-TQS57SDK.js} +19 -18
- package/chunks/{settings-RKEQQDFC.js → settings-D44YMZFT.js} +21 -20
- package/chunks/{shell-6PJ444U5.js → shell-3VE6I4V5.js} +10 -10
- package/chunks/{skill-Q3UP7B5V.js → skill-FHEGRRZU.js} +2 -2
- package/chunks/{skill-settings-LZR5YRRW.js → skill-settings-VHEJMBFL.js} +21 -20
- package/chunks/{spawnChannel-WM3DT2BF.js → spawnChannel-T6MX2ZDN.js} +17 -16
- package/chunks/{standalone-update-E5474IBI.js → standalone-update-QUGHFMCS.js} +18 -17
- package/chunks/{start-opentui-ui-WLWZNQ2I.js → start-opentui-ui-IHIIZ2LE.js} +438 -203
- package/chunks/{startInteractiveUI-FBQSGWGO.js → startInteractiveUI-LR7K2ARZ.js} +1171 -1234
- package/chunks/{task-create-PMAFST2T.js → task-create-MWXJFRRM.js} +3 -3
- package/chunks/{task-list-R2HSY2NC.js → task-list-EYO3FE57.js} +2 -2
- package/chunks/{task-update-JYC57USF.js → task-update-2JJJRHMT.js} +3 -3
- package/chunks/{team-create-VIGUFUT4.js → team-create-GY3VFY47.js} +10 -10
- package/chunks/{team-delete-CBHVBTPB.js → team-delete-IQZ6XGFI.js} +2 -2
- package/chunks/{team-plan-approval-HQVZA3MD.js → team-plan-approval-BQPGZOT5.js} +10 -10
- package/chunks/{terminal-image-renderer-LUKNCOVM.js → terminal-image-renderer-YNYW5CQG.js} +17 -16
- package/chunks/{theme-manager-LOENKYLY.js → theme-manager-U6CIK3GM.js} +17 -16
- package/chunks/{tool-search-ZIFAQCE2.js → tool-search-XI3QWD5I.js} +2 -2
- package/chunks/{total-session-admission-FUUJH3QH.js → total-session-admission-3SNSJJ32.js} +18 -17
- package/chunks/{trustedFolders-C76KYROP.js → trustedFolders-OVAQKNV5.js} +18 -17
- package/chunks/{update-relaunch-4G2SRXKX.js → update-relaunch-XV5O74E3.js} +5 -5
- package/chunks/{updateCheck-EIZIO7HI.js → updateCheck-CHUYWD6L.js} +17 -16
- package/chunks/{useAutoAcceptIndicator-EAA6KSY4.js → useAutoAcceptIndicator-VKSVIXZC.js} +23 -22
- package/chunks/{validateNonInterActiveAuth-BRX22QCN.js → validateNonInterActiveAuth-ZNUTD3E5.js} +53 -52
- package/chunks/{version-E56RU7PR.js → version-EK7VAGSI.js} +3 -1
- package/chunks/{web-fetch-AAUCD7RM.js → web-fetch-24KZO763.js} +2 -2
- package/chunks/{web-search-HFUQF44Q.js → web-search-CRNXKZ2X.js} +184 -7
- package/chunks/{web-shell-static-POZHGGXE.js → web-shell-static-BFDESDD4.js} +5 -4
- package/chunks/{workflow-3MABM53N.js → workflow-HD5NYMW5.js} +201 -65
- package/chunks/{workspace-providers-status-2FRLBZ5I.js → workspace-providers-status-MAM42IZS.js} +24 -23
- package/chunks/{workspace-registration-store-YL7HLYYI.js → workspace-registration-store-HKLJEDQX.js} +1 -2
- package/chunks/{workspace-registry-BNRP2ELW.js → workspace-registry-7RDDOBLN.js} +18 -17
- package/chunks/{workspace-runtime-coordinator-WKGF7VHS.js → workspace-runtime-coordinator-LEPDPXJO.js} +18 -17
- package/chunks/{workspace-service-4MYO72MM.js → workspace-service-5IFDCNN3.js} +23 -22
- package/chunks/{workspace-skills-status-SU66TJLZ.js → workspace-skills-status-FJ3HNH4H.js} +22 -21
- package/chunks/{workspace-trust-reconciler-D5QT7WQB.js → workspace-trust-reconciler-PQFHIWLR.js} +23 -22
- package/chunks/{write-file-NVPQE2AZ.js → write-file-ERG67ZFX.js} +11 -11
- package/chunks/{zoom-image-RJHC66WG.js → zoom-image-WQYEC3SR.js} +2 -2
- package/cli.js +12 -12
- package/export-transcript-document.js +82 -82
- package/package.json +4 -4
- package/web-shell/assets/{abnfDiagram-VCTEODGH-DHc6plZn.js → abnfDiagram-VCTEODGH-BRLgQbnT.js} +1 -1
- package/web-shell/assets/{arc-BQ9m8TDV.js → arc-qAF9_XsR.js} +1 -1
- package/web-shell/assets/{architectureDiagram-5GKGNRK7-D5ap3oVB.js → architectureDiagram-5GKGNRK7-BorUttEz.js} +1 -1
- package/web-shell/assets/{blockDiagram-NRAW4CY4-BIbnyNph.js → blockDiagram-NRAW4CY4-Bj0qZVxu.js} +1 -1
- package/web-shell/assets/{c4Diagram-UCG6FXSJ-BRsdx_iG.js → c4Diagram-UCG6FXSJ-BTqJis32.js} +1 -1
- package/web-shell/assets/channel-COIuCrVH.js +1 -0
- package/web-shell/assets/{chunk-2Q5K7J3B-3lf1fNlx.js → chunk-2Q5K7J3B-RrQ5X8m-.js} +1 -1
- package/web-shell/assets/{chunk-5VM5RSS4-DP8VNRc4.js → chunk-5VM5RSS4-uUjDl8wO.js} +1 -1
- package/web-shell/assets/{chunk-F27PBJKO-DrohDmZ6.js → chunk-F27PBJKO-C3JhpyzI.js} +1 -1
- package/web-shell/assets/{chunk-G27WJ6UU-UZ2QhSx6.js → chunk-G27WJ6UU-C8ZNFs7F.js} +1 -1
- package/web-shell/assets/{chunk-JWPE2WC7-BIMHVcZK.js → chunk-JWPE2WC7-DNpXtEOs.js} +1 -1
- package/web-shell/assets/{chunk-LCL6LL3I-izRxdSDd.js → chunk-LCL6LL3I-DagA2ZLw.js} +1 -1
- package/web-shell/assets/{chunk-POPQ4Y6H-CXADD4PP.js → chunk-POPQ4Y6H-R23uH7Xm.js} +1 -1
- package/web-shell/assets/{chunk-SVP7TREG-B96d8Y_1.js → chunk-SVP7TREG-Qz9RN_BR.js} +1 -1
- package/web-shell/assets/{chunk-XXDRQBXY-CF2sVQpv.js → chunk-XXDRQBXY-jYb1_hni.js} +1 -1
- package/web-shell/assets/classDiagram-DTDB5LWJ-DoX6OYqu.js +1 -0
- package/web-shell/assets/classDiagram-v2-JRS7N3AN-DoX6OYqu.js +1 -0
- package/web-shell/assets/{cose-bilkent-JH36ORCC-CT6tXVAS.js → cose-bilkent-JH36ORCC-Cv2OunpE.js} +1 -1
- package/web-shell/assets/{cynefin-OW5HDTMX-X1NS4vtV.js → cynefin-OW5HDTMX-LH42KFJx.js} +1 -1
- package/web-shell/assets/{cynefinDiagram-5FMLGOSQ-DNsKNOT4.js → cynefinDiagram-5FMLGOSQ-Dha4_OoR.js} +1 -1
- package/web-shell/assets/{dagre-3AP2YEHR-9-AHKqsb.js → dagre-3AP2YEHR-Br5lnUKD.js} +1 -1
- package/web-shell/assets/{diagram-S7CK7UJ4-Bjgms7j6.js → diagram-S7CK7UJ4-dpKq2bBX.js} +1 -1
- package/web-shell/assets/{diagram-UQ7AKVKN-h6bCprbE.js → diagram-UQ7AKVKN-BAgHWpXr.js} +1 -1
- package/web-shell/assets/{diagram-VSXAHHWV-4SbQXVLt.js → diagram-VSXAHHWV-GRRzpyE2.js} +1 -1
- package/web-shell/assets/{diagram-VX7I27RA-B5gPCzzB.js → diagram-VX7I27RA-BNFiaYpG.js} +1 -1
- package/web-shell/assets/{diagram-Z3DM3KII-DFJXIA3A.js → diagram-Z3DM3KII-BisZzB_h.js} +1 -1
- package/web-shell/assets/{ebnfDiagram-PWID7BFC-FwyaU7VY.js → ebnfDiagram-PWID7BFC-B1xbwycf.js} +1 -1
- package/web-shell/assets/{erDiagram-SSCWMZ5O-8vJQh2te.js → erDiagram-SSCWMZ5O-CQMzbGE5.js} +1 -1
- package/web-shell/assets/{flowDiagram-A5DVABFB-BzA_1gMN.js → flowDiagram-A5DVABFB-CZ_Zondl.js} +1 -1
- package/web-shell/assets/{ganttDiagram-EL5Y4UJY-Co-ABbVX.js → ganttDiagram-EL5Y4UJY-BTh2ShxS.js} +1 -1
- package/web-shell/assets/{gitGraphDiagram-WWUBYQGX-DW_FcyrX.js → gitGraphDiagram-WWUBYQGX-oHOB-Uxt.js} +1 -1
- package/web-shell/assets/{index-Bu-rZoJ-.js → index-CI1ysIEv.js} +478 -474
- package/web-shell/assets/index-CpxXGH-8.css +36 -0
- package/web-shell/assets/{index-BbMXfhNG.js → index-DWoFMu5x.js} +1 -1
- package/web-shell/assets/{infoDiagram-RXCK75RN-BEgfhUFN.js → infoDiagram-RXCK75RN-C0QpudbG.js} +1 -1
- package/web-shell/assets/{ishikawaDiagram-5VMMS53U-DCsMxQmc.js → ishikawaDiagram-5VMMS53U-a6B741uT.js} +1 -1
- package/web-shell/assets/{journeyDiagram-EYS64GPL-DyIEYoHz.js → journeyDiagram-EYS64GPL-Dbht9ICp.js} +1 -1
- package/web-shell/assets/{kanban-definition-3QL26DDD-CkxjqCNr.js → kanban-definition-3QL26DDD-D4OFR8-h.js} +1 -1
- package/web-shell/assets/{layout-BEWc0Al9.js → layout-Dvdh5jRH.js} +1 -1
- package/web-shell/assets/{linear-CNuNxirL.js → linear-Dvc6KIW2.js} +1 -1
- package/web-shell/assets/{mermaid.core-DZBre674.js → mermaid.core-CB36RfIb.js} +6 -6
- package/web-shell/assets/{mindmap-definition-FBJOCRG2-CpafFz_F.js → mindmap-definition-FBJOCRG2-Fmqo1CjP.js} +1 -1
- package/web-shell/assets/{pegDiagram-XKGWAZYB-DfZqk_dq.js → pegDiagram-XKGWAZYB-CCxYBoA4.js} +1 -1
- package/web-shell/assets/{pieDiagram-E7YTZNPT-S0UWXlgC.js → pieDiagram-E7YTZNPT-BVHKQFzt.js} +1 -1
- package/web-shell/assets/{quadrantDiagram-AXDQQJYC-dd37_Ohj.js → quadrantDiagram-AXDQQJYC-l95IznwR.js} +1 -1
- package/web-shell/assets/{railroadDiagram-O6MQD6OU-CH8NJZ7u.js → railroadDiagram-O6MQD6OU-BlLdRdXp.js} +1 -1
- package/web-shell/assets/{requirementDiagram-EFPCY7ZU-CFdqWYih.js → requirementDiagram-EFPCY7ZU-BkDLBR5w.js} +1 -1
- package/web-shell/assets/{sankeyDiagram-P5KCCOFB-CsddcEGD.js → sankeyDiagram-P5KCCOFB-jAIqRp65.js} +1 -1
- package/web-shell/assets/{sequenceDiagram-WJ2MYXX4-BZ270mGw.js → sequenceDiagram-WJ2MYXX4-f-soZYPr.js} +1 -1
- package/web-shell/assets/{sizeCapture-X5ZJPWSS-CGaS4DBV.js → sizeCapture-X5ZJPWSS-BzOuAfB2.js} +1 -1
- package/web-shell/assets/{stateDiagram-HBIQ2CUA-COpIzMu6.js → stateDiagram-HBIQ2CUA-BPLSs12r.js} +1 -1
- package/web-shell/assets/stateDiagram-v2-4QOOHH4V-IRul-LaT.js +1 -0
- package/web-shell/assets/{swimlanes-XN3QIQJK-CMF889m3.js → swimlanes-XN3QIQJK-D-9E_RIR.js} +1 -1
- package/web-shell/assets/swimlanesDiagram-VK2B7HYN-D6lgKepA.js +8 -0
- package/web-shell/assets/{timeline-definition-24CTP7MA-BelnxYz5.js → timeline-definition-24CTP7MA-CT56K-gV.js} +1 -1
- package/web-shell/assets/{vennDiagram-4TSXK5OY-CU-NRqIB.js → vennDiagram-4TSXK5OY-B-vi4IyO.js} +1 -1
- package/web-shell/assets/{wardleyDiagram-VM6X3IG4-CDNKqWh-.js → wardleyDiagram-VM6X3IG4-C9O7Ju98.js} +1 -1
- package/web-shell/assets/{xychartDiagram-S5SC5T6Z-CLqguL5D.js → xychartDiagram-S5SC5T6Z-6d8w3jg2.js} +1 -1
- package/web-shell/index.html +2 -2
- package/chunks/chunk-X35VIWWJ.js +0 -276
- package/web-shell/assets/channel-uMMdTrYk.js +0 -1
- package/web-shell/assets/classDiagram-DTDB5LWJ-DcY-26Ig.js +0 -1
- package/web-shell/assets/classDiagram-v2-JRS7N3AN-DcY-26Ig.js +0 -1
- package/web-shell/assets/index-CsPwZ1ut.css +0 -36
- package/web-shell/assets/stateDiagram-v2-4QOOHH4V-D6qEVAji.js +0 -1
- package/web-shell/assets/swimlanesDiagram-VK2B7HYN-mem5Jj4m.js +0 -8
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
import {
|
|
4
4
|
listLiveSessions,
|
|
5
5
|
readOwnSessionRecord
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-QQK4L3UU.js";
|
|
7
7
|
import {
|
|
8
8
|
LEADER_NAME,
|
|
9
9
|
sanitizeName
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-CH2YDT6X.js";
|
|
11
11
|
import {
|
|
12
12
|
APPROVAL_MODES
|
|
13
13
|
} from "./chunk-JX6XHPTX.js";
|
|
@@ -93,8 +93,10 @@ import * as net from "node:net";
|
|
|
93
93
|
init_esbuild_shims();
|
|
94
94
|
import { randomUUID } from "node:crypto";
|
|
95
95
|
var PEER_FRAME_VERSION = 1;
|
|
96
|
+
var MAX_DROPPED_MSG_IDS = 256;
|
|
96
97
|
var MAX_FRAME_BYTES = 1024 * 1024;
|
|
97
98
|
var MSG_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
99
|
+
var MAX_RETAINED_REPLY_TOKEN_CHARS = 256;
|
|
98
100
|
function canonicalizeMsgId(msgId) {
|
|
99
101
|
return msgId.replace(/-/g, "").toLowerCase();
|
|
100
102
|
}
|
|
@@ -107,6 +109,20 @@ function optionalString(value) {
|
|
|
107
109
|
return typeof value === "string" ? value : void 0;
|
|
108
110
|
}
|
|
109
111
|
__name(optionalString, "optionalString");
|
|
112
|
+
function isAddressableMsgId(value) {
|
|
113
|
+
return typeof value === "string" && MSG_ID_RE.test(value) && canonicalizeMsgId(value) !== "all";
|
|
114
|
+
}
|
|
115
|
+
__name(isAddressableMsgId, "isAddressableMsgId");
|
|
116
|
+
function optionalDropReason(value) {
|
|
117
|
+
return value === "rate-limited" || value === "duplicate" || value === "queue-full" ? value : void 0;
|
|
118
|
+
}
|
|
119
|
+
__name(optionalDropReason, "optionalDropReason");
|
|
120
|
+
function parseDroppedMsgIds(value) {
|
|
121
|
+
if (!Array.isArray(value)) return void 0;
|
|
122
|
+
const ids = value.filter(isAddressableMsgId).slice(0, MAX_DROPPED_MSG_IDS);
|
|
123
|
+
return ids.length > 0 ? ids : void 0;
|
|
124
|
+
}
|
|
125
|
+
__name(parseDroppedMsgIds, "parseDroppedMsgIds");
|
|
110
126
|
function parsePeerFrame(line) {
|
|
111
127
|
let parsed;
|
|
112
128
|
try {
|
|
@@ -118,8 +134,7 @@ function parsePeerFrame(line) {
|
|
|
118
134
|
const msgV = parsed["msgV"];
|
|
119
135
|
if (typeof msgV !== "number" || msgV > PEER_FRAME_VERSION) return null;
|
|
120
136
|
const msgId = parsed["msgId"];
|
|
121
|
-
if (
|
|
122
|
-
if (canonicalizeMsgId(msgId) === "all") return null;
|
|
137
|
+
if (!isAddressableMsgId(msgId)) return null;
|
|
123
138
|
if (parsed["type"] === "user") {
|
|
124
139
|
const message = parsed["message"];
|
|
125
140
|
if (!isRecord(message)) return null;
|
|
@@ -146,11 +161,13 @@ function parsePeerFrame(line) {
|
|
|
146
161
|
if (parsed["type"] === "control") {
|
|
147
162
|
if (parsed["action"] !== "delivery_status") return null;
|
|
148
163
|
const status = parsed["status"];
|
|
149
|
-
if (status !== "held" && status !== "denied" && status !== "refused" && status !== "expired" && status !== "delivered" && status !== "misaddressed") {
|
|
164
|
+
if (status !== "held" && status !== "denied" && status !== "refused" && status !== "expired" && status !== "delivered" && status !== "misaddressed" && status !== "dropped") {
|
|
150
165
|
return null;
|
|
151
166
|
}
|
|
152
167
|
const origMsgId = parsed["origMsgId"];
|
|
153
168
|
if (typeof origMsgId !== "string" || origMsgId.length === 0) return null;
|
|
169
|
+
const dropReason = status === "dropped" ? optionalDropReason(parsed["dropReason"]) : void 0;
|
|
170
|
+
const droppedMsgIds = status === "dropped" ? parseDroppedMsgIds(parsed["droppedMsgIds"]) : void 0;
|
|
154
171
|
return {
|
|
155
172
|
msgV,
|
|
156
173
|
msgId,
|
|
@@ -159,7 +176,9 @@ function parsePeerFrame(line) {
|
|
|
159
176
|
status,
|
|
160
177
|
origMsgId,
|
|
161
178
|
from: optionalString(parsed["from"]),
|
|
162
|
-
reason: optionalString(parsed["reason"])
|
|
179
|
+
reason: optionalString(parsed["reason"]),
|
|
180
|
+
...dropReason !== void 0 ? { dropReason } : {},
|
|
181
|
+
...droppedMsgIds !== void 0 ? { droppedMsgIds } : {}
|
|
163
182
|
};
|
|
164
183
|
}
|
|
165
184
|
return null;
|
|
@@ -199,6 +218,8 @@ function describeDeliveryStatus(status) {
|
|
|
199
218
|
return "Your message was released to the recipient session.";
|
|
200
219
|
case "misaddressed":
|
|
201
220
|
return "That address now belongs to a different session than the one you addressed; it was not delivered. List the agents again before re-sending.";
|
|
221
|
+
case "dropped":
|
|
222
|
+
return "Your message was dropped at the recipient's inbox before anyone saw it: sent too fast, a repeat of your previous message, or the inbox queue was full. Treat it as unsent; fold what still matters into one later message rather than re-sending.";
|
|
202
223
|
default: {
|
|
203
224
|
const exhaustive = status;
|
|
204
225
|
return exhaustive;
|
|
@@ -206,6 +227,21 @@ function describeDeliveryStatus(status) {
|
|
|
206
227
|
}
|
|
207
228
|
}
|
|
208
229
|
__name(describeDeliveryStatus, "describeDeliveryStatus");
|
|
230
|
+
function describeDropReason(reason) {
|
|
231
|
+
switch (reason) {
|
|
232
|
+
case "rate-limited":
|
|
233
|
+
return "you sent faster than that session accepts";
|
|
234
|
+
case "duplicate":
|
|
235
|
+
return "it repeated your previous message";
|
|
236
|
+
case "queue-full":
|
|
237
|
+
return "its queue of undelivered peer messages was full";
|
|
238
|
+
default: {
|
|
239
|
+
const exhaustive = reason;
|
|
240
|
+
return exhaustive;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
__name(describeDropReason, "describeDropReason");
|
|
209
245
|
function buildAuthLine(token) {
|
|
210
246
|
return `${JSON.stringify({ msgV: PEER_FRAME_VERSION, type: "auth", token })}
|
|
211
247
|
`;
|
|
@@ -235,7 +271,11 @@ function buildDeliveryStatusFrame(fields) {
|
|
|
235
271
|
status: fields.status,
|
|
236
272
|
origMsgId: fields.origMsgId,
|
|
237
273
|
...fields.from !== void 0 ? { from: fields.from } : {},
|
|
238
|
-
reason: describeDeliveryStatus(fields.status)
|
|
274
|
+
reason: describeDeliveryStatus(fields.status),
|
|
275
|
+
...fields.dropReason !== void 0 ? { dropReason: fields.dropReason } : {},
|
|
276
|
+
// An empty list is left off rather than sent as `[]`: the field means
|
|
277
|
+
// "these ids too", and there are none.
|
|
278
|
+
...fields.droppedMsgIds !== void 0 && fields.droppedMsgIds.length > 0 ? { droppedMsgIds: fields.droppedMsgIds.slice(0, MAX_DROPPED_MSG_IDS) } : {}
|
|
239
279
|
};
|
|
240
280
|
}
|
|
241
281
|
__name(buildDeliveryStatusFrame, "buildDeliveryStatusFrame");
|
|
@@ -508,7 +548,225 @@ __name(isInProcessRecipient, "isInProcessRecipient");
|
|
|
508
548
|
|
|
509
549
|
// packages/core/src/ipc/inbound-gate.ts
|
|
510
550
|
init_esbuild_shims();
|
|
511
|
-
|
|
551
|
+
|
|
552
|
+
// packages/core/src/ipc/peer-admission.ts
|
|
553
|
+
init_esbuild_shims();
|
|
554
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
555
|
+
var debugLogger2 = createDebugLogger("PEER_ADMISSION");
|
|
556
|
+
var PEER_ADMISSION_LIMITS = {
|
|
557
|
+
bucketCapacity: 30,
|
|
558
|
+
refillPerSecond: 0.5,
|
|
559
|
+
dedupWindowMs: 3e4,
|
|
560
|
+
globalBucketCapacity: 32,
|
|
561
|
+
globalRefillPerSecond: 1,
|
|
562
|
+
maxTrackedSenders: 256
|
|
563
|
+
};
|
|
564
|
+
var PEER_BURST_WINDOW_MS = PEER_ADMISSION_LIMITS.bucketCapacity / PEER_ADMISSION_LIMITS.refillPerSecond * 1e3;
|
|
565
|
+
function refillBucket(tokens, lastRefill, now, capacity, perSecond) {
|
|
566
|
+
const elapsedSeconds = Math.max(0, now - lastRefill) / 1e3;
|
|
567
|
+
return Math.min(capacity, tokens + elapsedSeconds * perSecond);
|
|
568
|
+
}
|
|
569
|
+
__name(refillBucket, "refillBucket");
|
|
570
|
+
function hasToken(tokens) {
|
|
571
|
+
return tokens >= 1;
|
|
572
|
+
}
|
|
573
|
+
__name(hasToken, "hasToken");
|
|
574
|
+
function isBodyWithinWindow(at, atWall, now, wallNow, windowMs) {
|
|
575
|
+
return Math.max(now - at, wallNow - atWall) < windowMs;
|
|
576
|
+
}
|
|
577
|
+
__name(isBodyWithinWindow, "isBodyWithinWindow");
|
|
578
|
+
var PeerAdmission = class {
|
|
579
|
+
static {
|
|
580
|
+
__name(this, "PeerAdmission");
|
|
581
|
+
}
|
|
582
|
+
now;
|
|
583
|
+
wallNow;
|
|
584
|
+
limits;
|
|
585
|
+
senders = /* @__PURE__ */ new Map();
|
|
586
|
+
globalTokens;
|
|
587
|
+
globalRefill;
|
|
588
|
+
constructor(options = {}) {
|
|
589
|
+
this.now = options.now ?? (() => performance.now());
|
|
590
|
+
this.wallNow = options.wallNow ?? (() => Date.now());
|
|
591
|
+
this.limits = { ...PEER_ADMISSION_LIMITS, ...options.limits };
|
|
592
|
+
this.globalTokens = this.limits.globalBucketCapacity;
|
|
593
|
+
this.globalRefill = this.now();
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Whether this message may go on to the gate's policy.
|
|
597
|
+
*
|
|
598
|
+
* The order matters. The global bucket is checked first, so a flood
|
|
599
|
+
* that rotates `from` is stopped before it can mint a meter per name.
|
|
600
|
+
* The duplicate check comes before the sender's bucket is charged: a
|
|
601
|
+
* repeat should not also cost the sender the allowance it would need to
|
|
602
|
+
* say something new. Nothing is charged unless every check passes.
|
|
603
|
+
*/
|
|
604
|
+
admit(request) {
|
|
605
|
+
const now = this.now();
|
|
606
|
+
const wallNow = this.wallNow();
|
|
607
|
+
this.globalTokens = refillBucket(
|
|
608
|
+
this.globalTokens,
|
|
609
|
+
this.globalRefill,
|
|
610
|
+
now,
|
|
611
|
+
this.limits.globalBucketCapacity,
|
|
612
|
+
this.limits.globalRefillPerSecond
|
|
613
|
+
);
|
|
614
|
+
this.globalRefill = now;
|
|
615
|
+
if (!hasToken(this.globalTokens)) {
|
|
616
|
+
debugLogger2.debug(
|
|
617
|
+
`dropping a peer message from ${request.senderKey}: every sender combined is over the rate limit`
|
|
618
|
+
);
|
|
619
|
+
return { admitted: false, reason: "rate-limited" };
|
|
620
|
+
}
|
|
621
|
+
const meter = this.trackSender(request.senderKey, now);
|
|
622
|
+
meter.bodies = meter.bodies.filter(
|
|
623
|
+
(record) => isBodyWithinWindow(
|
|
624
|
+
record.at,
|
|
625
|
+
record.atWall,
|
|
626
|
+
now,
|
|
627
|
+
wallNow,
|
|
628
|
+
this.limits.dedupWindowMs
|
|
629
|
+
)
|
|
630
|
+
);
|
|
631
|
+
const bodyHash = request.exemptFromDedup ? void 0 : hashBody(request.body);
|
|
632
|
+
const lastBody = meter.bodies.at(-1);
|
|
633
|
+
if (bodyHash !== void 0 && lastBody?.hash === bodyHash) {
|
|
634
|
+
debugLogger2.debug(
|
|
635
|
+
`dropping a peer message from ${request.senderKey}: identical to its previous message`
|
|
636
|
+
);
|
|
637
|
+
return { admitted: false, reason: "duplicate" };
|
|
638
|
+
}
|
|
639
|
+
meter.tokens = refillBucket(
|
|
640
|
+
meter.tokens,
|
|
641
|
+
meter.lastRefill,
|
|
642
|
+
now,
|
|
643
|
+
this.limits.bucketCapacity,
|
|
644
|
+
this.limits.refillPerSecond
|
|
645
|
+
);
|
|
646
|
+
meter.lastRefill = now;
|
|
647
|
+
if (!hasToken(meter.tokens)) {
|
|
648
|
+
debugLogger2.debug(
|
|
649
|
+
`dropping a peer message from ${request.senderKey}: over its rate limit`
|
|
650
|
+
);
|
|
651
|
+
return { admitted: false, reason: "rate-limited" };
|
|
652
|
+
}
|
|
653
|
+
this.globalTokens -= 1;
|
|
654
|
+
meter.tokens -= 1;
|
|
655
|
+
if (bodyHash !== void 0) {
|
|
656
|
+
meter.bodies.push({
|
|
657
|
+
messageId: request.messageId,
|
|
658
|
+
hash: bodyHash,
|
|
659
|
+
at: now,
|
|
660
|
+
atWall: wallNow
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
return { admitted: true };
|
|
664
|
+
}
|
|
665
|
+
/** How many senders are metered right now. For tests and diagnostics. */
|
|
666
|
+
trackedSenderCount() {
|
|
667
|
+
return this.senders.size;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Undo the repeat record `body` left when it was admitted, leaving the
|
|
671
|
+
* bucket alone.
|
|
672
|
+
*
|
|
673
|
+
* The gate calls this wherever it settles an admitted message as
|
|
674
|
+
* anything other than delivered or held — a full input queue, a
|
|
675
|
+
* standing refusal, a hold buffer with no room, a shutdown, a session
|
|
676
|
+
* swap. In every one of those the far model never saw the message, so a
|
|
677
|
+
* `duplicate` verdict on the sender's honest retry would assert
|
|
678
|
+
* something false: that identical content is already over there. The
|
|
679
|
+
* retry then meets the repeat check it would have met had this message
|
|
680
|
+
* never arrived.
|
|
681
|
+
*
|
|
682
|
+
* Removes only this message's record. A later admitted message remains
|
|
683
|
+
* the duplicate baseline, and the body admitted before this one keeps
|
|
684
|
+
* the protection it earned if it becomes the latest remaining record.
|
|
685
|
+
*
|
|
686
|
+
* The token stays spent. It is the only bound on how often a peer can
|
|
687
|
+
* make the receiver attempt, and fail, a delivery.
|
|
688
|
+
*/
|
|
689
|
+
forgetBody(senderKey, body, messageId) {
|
|
690
|
+
const meter = this.senders.get(senderKey);
|
|
691
|
+
if (meter === void 0) return;
|
|
692
|
+
const bodyHash = hashBody(body);
|
|
693
|
+
for (let index = meter.bodies.length - 1; index >= 0; index -= 1) {
|
|
694
|
+
const record = meter.bodies[index];
|
|
695
|
+
if (record?.hash === bodyHash && (messageId === void 0 || record.messageId === messageId)) {
|
|
696
|
+
meter.bodies.splice(index, 1);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
/** Undo every repeat record owned by one admitted message. */
|
|
701
|
+
forgetMessage(senderKey, messageId) {
|
|
702
|
+
const meter = this.senders.get(senderKey);
|
|
703
|
+
if (meter === void 0) return;
|
|
704
|
+
const canonicalId = canonicalizeMsgId(messageId);
|
|
705
|
+
meter.bodies = meter.bodies.filter(
|
|
706
|
+
(record) => record.messageId === void 0 || canonicalizeMsgId(record.messageId) !== canonicalId
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
/** Start a fresh per-session conversation without replacing the gate. */
|
|
710
|
+
reset() {
|
|
711
|
+
this.senders.clear();
|
|
712
|
+
this.globalTokens = this.limits.globalBucketCapacity;
|
|
713
|
+
this.globalRefill = this.now();
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* The meter for `key`, creating one and making room if needed.
|
|
717
|
+
*
|
|
718
|
+
* The map is an LRU: a hit is re-inserted so iteration order is
|
|
719
|
+
* least-recently-seen first. When it is full, a meter whose bucket has
|
|
720
|
+
* refilled completely goes first — it is not limiting anyone, so
|
|
721
|
+
* forgetting it changes no verdict except that its last body is
|
|
722
|
+
* forgotten too, which can admit one repeat. Losing a repeat check is
|
|
723
|
+
* the right thing to trade for a bounded map; losing a *bucket* that is
|
|
724
|
+
* currently holding a flood back is not, which is why a full bucket is
|
|
725
|
+
* preferred over a merely old one.
|
|
726
|
+
*/
|
|
727
|
+
trackSender(key, now) {
|
|
728
|
+
const existing = this.senders.get(key);
|
|
729
|
+
if (existing !== void 0) {
|
|
730
|
+
this.senders.delete(key);
|
|
731
|
+
this.senders.set(key, existing);
|
|
732
|
+
return existing;
|
|
733
|
+
}
|
|
734
|
+
const limit = Math.max(1, this.limits.maxTrackedSenders);
|
|
735
|
+
while (this.senders.size >= limit) {
|
|
736
|
+
let victim;
|
|
737
|
+
for (const [candidate, meter] of this.senders) {
|
|
738
|
+
const level = refillBucket(
|
|
739
|
+
meter.tokens,
|
|
740
|
+
meter.lastRefill,
|
|
741
|
+
now,
|
|
742
|
+
this.limits.bucketCapacity,
|
|
743
|
+
this.limits.refillPerSecond
|
|
744
|
+
);
|
|
745
|
+
if (level >= this.limits.bucketCapacity) {
|
|
746
|
+
victim = candidate;
|
|
747
|
+
break;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
victim ??= this.senders.keys().next().value;
|
|
751
|
+
if (victim === void 0) break;
|
|
752
|
+
this.senders.delete(victim);
|
|
753
|
+
}
|
|
754
|
+
const fresh = {
|
|
755
|
+
tokens: this.limits.bucketCapacity,
|
|
756
|
+
lastRefill: now,
|
|
757
|
+
bodies: []
|
|
758
|
+
};
|
|
759
|
+
this.senders.set(key, fresh);
|
|
760
|
+
return fresh;
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
function hashBody(body) {
|
|
764
|
+
return createHash2("sha256").update(body).digest("hex");
|
|
765
|
+
}
|
|
766
|
+
__name(hashBody, "hashBody");
|
|
767
|
+
|
|
768
|
+
// packages/core/src/ipc/inbound-gate.ts
|
|
769
|
+
var debugLogger3 = createDebugLogger("PEER_INBOUND");
|
|
512
770
|
var MAX_HELD_MESSAGES = 50;
|
|
513
771
|
var MAX_SETTLED_IDS = 512;
|
|
514
772
|
function receiverReviewsActions(mode) {
|
|
@@ -523,6 +781,14 @@ function isInboundPolicy(value) {
|
|
|
523
781
|
return value === "accept" || value === "hold" || value === "refuse";
|
|
524
782
|
}
|
|
525
783
|
__name(isInboundPolicy, "isInboundPolicy");
|
|
784
|
+
var MAX_SENDER_KEY_CHARS = 256;
|
|
785
|
+
function peerSenderKey(frame, origin) {
|
|
786
|
+
if (origin.controller) return `controller:${origin.controller.id}`;
|
|
787
|
+
const address = (frame.from ?? "").slice(0, MAX_SENDER_KEY_CHARS);
|
|
788
|
+
if (origin.selfSent) return `own:${address}`;
|
|
789
|
+
return address ? `peer:${address}` : "unknown";
|
|
790
|
+
}
|
|
791
|
+
__name(peerSenderKey, "peerSenderKey");
|
|
526
792
|
var MAX_TIMEOUT_MS = 2 ** 31 - 1;
|
|
527
793
|
var DEFAULT_HELD_EXPIRY_MS = 5 * 60 * 1e3;
|
|
528
794
|
var HELD_EXPIRY_VALUES = {
|
|
@@ -535,7 +801,7 @@ var HELD_EXPIRY_OPTIONS = Object.keys(HELD_EXPIRY_VALUES);
|
|
|
535
801
|
function parseHeldExpiry(value) {
|
|
536
802
|
if (value === void 0) return DEFAULT_HELD_EXPIRY_MS;
|
|
537
803
|
if (typeof value !== "string" || !Object.hasOwn(HELD_EXPIRY_VALUES, value)) {
|
|
538
|
-
|
|
804
|
+
debugLogger3.debug(
|
|
539
805
|
`unrecognized crossSessionHeldExpiry value (using the default): ${String(
|
|
540
806
|
value
|
|
541
807
|
)}`
|
|
@@ -548,6 +814,7 @@ __name(parseHeldExpiry, "parseHeldExpiry");
|
|
|
548
814
|
var InboundGate = class {
|
|
549
815
|
constructor(options) {
|
|
550
816
|
this.options = options;
|
|
817
|
+
this.admission = options.admission ?? new PeerAdmission();
|
|
551
818
|
}
|
|
552
819
|
static {
|
|
553
820
|
__name(this, "InboundGate");
|
|
@@ -568,6 +835,10 @@ var InboundGate = class {
|
|
|
568
835
|
* them, all firing to do the same sweep.
|
|
569
836
|
*/
|
|
570
837
|
expiryTimer = null;
|
|
838
|
+
/** How fast senders may arrive. See `peer-admission.ts`. */
|
|
839
|
+
admission;
|
|
840
|
+
admissionSessionObserved = false;
|
|
841
|
+
admissionSessionId;
|
|
571
842
|
/** Messages currently parked, oldest first. */
|
|
572
843
|
getHeld() {
|
|
573
844
|
this.forgetInvalidControllers();
|
|
@@ -615,7 +886,7 @@ var InboundGate = class {
|
|
|
615
886
|
try {
|
|
616
887
|
return this.options.getHeldExpiryMs();
|
|
617
888
|
} catch (error) {
|
|
618
|
-
|
|
889
|
+
debugLogger3.debug(
|
|
619
890
|
`held-expiry getter threw; falling back to the default: ${error instanceof Error ? error.message : String(error)}`
|
|
620
891
|
);
|
|
621
892
|
return DEFAULT_HELD_EXPIRY_MS;
|
|
@@ -632,7 +903,7 @@ var InboundGate = class {
|
|
|
632
903
|
try {
|
|
633
904
|
const configured = this.options.getPolicySetting();
|
|
634
905
|
if (configured !== void 0 && !isInboundPolicy(configured)) {
|
|
635
|
-
|
|
906
|
+
debugLogger3.debug(
|
|
636
907
|
`unrecognized crossSessionInbound value (failing closed): ${String(
|
|
637
908
|
configured
|
|
638
909
|
)}`
|
|
@@ -641,7 +912,7 @@ var InboundGate = class {
|
|
|
641
912
|
}
|
|
642
913
|
explicit = configured;
|
|
643
914
|
} catch (error) {
|
|
644
|
-
|
|
915
|
+
debugLogger3.debug(
|
|
645
916
|
`policy-setting getter threw (failing closed): ${error instanceof Error ? error.message : String(error)}`
|
|
646
917
|
);
|
|
647
918
|
return { policy: "hold", cause: "policy-unreadable" };
|
|
@@ -662,7 +933,7 @@ var InboundGate = class {
|
|
|
662
933
|
try {
|
|
663
934
|
mode = this.options.getApprovalMode();
|
|
664
935
|
} catch (error) {
|
|
665
|
-
|
|
936
|
+
debugLogger3.debug(
|
|
666
937
|
`approval-mode getter threw (failing closed): ${error instanceof Error ? error.message : String(error)}`
|
|
667
938
|
);
|
|
668
939
|
mode = null;
|
|
@@ -684,7 +955,7 @@ var InboundGate = class {
|
|
|
684
955
|
try {
|
|
685
956
|
return this.options.getPolicyScope?.();
|
|
686
957
|
} catch (error) {
|
|
687
|
-
|
|
958
|
+
debugLogger3.debug(
|
|
688
959
|
`policy-scope getter threw (ignored): ${error instanceof Error ? error.message : String(error)}`
|
|
689
960
|
);
|
|
690
961
|
return void 0;
|
|
@@ -695,52 +966,71 @@ var InboundGate = class {
|
|
|
695
966
|
* an ordinary peer — the transport asserts self-sent, never the frame.
|
|
696
967
|
*/
|
|
697
968
|
admit(frame, origin = { selfSent: false }) {
|
|
969
|
+
const sessionId = this.options.getSessionId?.();
|
|
970
|
+
if (this.admissionSessionObserved && this.admissionSessionId !== sessionId) {
|
|
971
|
+
this.admission.reset();
|
|
972
|
+
}
|
|
973
|
+
this.admissionSessionObserved = true;
|
|
974
|
+
this.admissionSessionId = sessionId;
|
|
698
975
|
this.expireOverdue();
|
|
976
|
+
const verdict = this.admission.admit({
|
|
977
|
+
senderKey: peerSenderKey(frame, origin),
|
|
978
|
+
body: frame.message.content,
|
|
979
|
+
messageId: frame.msgId,
|
|
980
|
+
// A hook reporting the same line twice, or a user repeating
|
|
981
|
+
// themselves to a controller, is not the model-driven repetition
|
|
982
|
+
// the duplicate check exists to stop. Both are still rate limited.
|
|
983
|
+
exemptFromDedup: origin.selfSent || origin.controller !== void 0
|
|
984
|
+
});
|
|
985
|
+
if (!verdict.admitted) {
|
|
986
|
+
return this.drop(frame, origin, verdict.reason);
|
|
987
|
+
}
|
|
699
988
|
const settled = this.settled.get(canonicalizeMsgId(frame.msgId));
|
|
700
989
|
if (settled !== void 0) {
|
|
701
|
-
|
|
990
|
+
debugLogger3.debug(
|
|
702
991
|
`re-sent msgId ${frame.msgId}; repeating earlier verdict ${settled}`
|
|
703
992
|
);
|
|
993
|
+
this.forgetAdmittedBody(frame, origin);
|
|
704
994
|
void this.report(frame, settled);
|
|
705
995
|
return "refused";
|
|
706
996
|
}
|
|
707
997
|
if (this.held.some(
|
|
708
998
|
(entry) => canonicalizeMsgId(entry.frame.msgId) === canonicalizeMsgId(frame.msgId)
|
|
709
999
|
)) {
|
|
710
|
-
|
|
1000
|
+
debugLogger3.debug(`duplicate msgId ${frame.msgId}; already held`);
|
|
1001
|
+
this.forgetAdmittedBody(frame, origin);
|
|
711
1002
|
void this.report(frame, "held");
|
|
712
1003
|
return "held";
|
|
713
1004
|
}
|
|
714
1005
|
const decision = this.resolvePolicy(frame, origin);
|
|
715
1006
|
const { policy } = decision;
|
|
716
1007
|
if (policy === "refuse") {
|
|
717
|
-
|
|
1008
|
+
debugLogger3.debug(`refused peer message ${frame.msgId}`);
|
|
1009
|
+
this.forgetAdmittedBody(frame, origin);
|
|
718
1010
|
this.recordSettled(frame.msgId, "refused");
|
|
719
1011
|
void this.report(frame, "refused");
|
|
720
1012
|
return "refused";
|
|
721
1013
|
}
|
|
722
1014
|
if (this.shuttingDown) {
|
|
723
|
-
|
|
1015
|
+
debugLogger3.debug(
|
|
724
1016
|
`not admitting peer message ${frame.msgId} during shutdown; expiring it`
|
|
725
1017
|
);
|
|
1018
|
+
this.forgetAdmittedBody(frame, origin);
|
|
726
1019
|
void this.report(frame, "expired");
|
|
727
1020
|
return "refused";
|
|
728
1021
|
}
|
|
729
1022
|
if (policy === "accept") {
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
this.
|
|
1023
|
+
if (!this.tryDeliver(frame, origin)) {
|
|
1024
|
+
this.forgetAdmittedBody(frame, origin);
|
|
1025
|
+
return this.drop(frame, origin, "queue-full");
|
|
733
1026
|
}
|
|
734
|
-
|
|
735
|
-
|
|
1027
|
+
this.recordSettled(frame.msgId, "delivered");
|
|
1028
|
+
void this.report(frame, "delivered");
|
|
1029
|
+
return "accept";
|
|
736
1030
|
}
|
|
737
1031
|
if (this.held.length >= MAX_HELD_MESSAGES) {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
debugLogger2.debug(`hold buffer full; expiring ${evicted.frame.msgId}`);
|
|
741
|
-
this.recordSettled(evicted.frame.msgId, "expired");
|
|
742
|
-
void this.report(evicted.frame, "expired");
|
|
743
|
-
}
|
|
1032
|
+
this.forgetAdmittedBody(frame, origin);
|
|
1033
|
+
return this.drop(frame, origin, "queue-full");
|
|
744
1034
|
}
|
|
745
1035
|
const cause = decision.policy === "hold" ? decision.cause : "mode-unknown";
|
|
746
1036
|
const scope = decision.policy === "hold" ? decision.scope : void 0;
|
|
@@ -753,7 +1043,7 @@ var InboundGate = class {
|
|
|
753
1043
|
...origin.selfSent ? { selfSent: true } : {},
|
|
754
1044
|
...origin.controller ? { controller: origin.controller } : {}
|
|
755
1045
|
});
|
|
756
|
-
|
|
1046
|
+
debugLogger3.debug(
|
|
757
1047
|
`held peer message ${frame.msgId} (cause=${cause}, ${this.held.length} held)`
|
|
758
1048
|
);
|
|
759
1049
|
void this.report(frame, "held");
|
|
@@ -782,6 +1072,7 @@ var InboundGate = class {
|
|
|
782
1072
|
if (!entry) return "gone";
|
|
783
1073
|
if (decision === "approve") {
|
|
784
1074
|
if (!this.pinStillValid(entry.frame)) {
|
|
1075
|
+
this.forgetAdmittedBody(entry.frame, originOf(entry));
|
|
785
1076
|
this.recordSettled(entry.frame.msgId, "misaddressed");
|
|
786
1077
|
void this.report(entry.frame, "misaddressed");
|
|
787
1078
|
this.notifyHeldChange();
|
|
@@ -798,6 +1089,7 @@ var InboundGate = class {
|
|
|
798
1089
|
this.recordSettled(entry.frame.msgId, "delivered");
|
|
799
1090
|
void this.report(entry.frame, "delivered");
|
|
800
1091
|
} else {
|
|
1092
|
+
this.forgetAdmittedBody(entry.frame, originOf(entry));
|
|
801
1093
|
this.recordSettled(entry.frame.msgId, "denied");
|
|
802
1094
|
void this.report(entry.frame, "denied");
|
|
803
1095
|
}
|
|
@@ -830,6 +1122,7 @@ var InboundGate = class {
|
|
|
830
1122
|
release.push(entry);
|
|
831
1123
|
} else if (policy === "refuse") {
|
|
832
1124
|
dropped += 1;
|
|
1125
|
+
this.forgetAdmittedBody(entry.frame, originOf(entry));
|
|
833
1126
|
this.recordSettled(entry.frame.msgId, "denied");
|
|
834
1127
|
void this.report(entry.frame, "denied");
|
|
835
1128
|
} else {
|
|
@@ -841,6 +1134,7 @@ var InboundGate = class {
|
|
|
841
1134
|
for (const entry of release) {
|
|
842
1135
|
if (!this.pinStillValid(entry.frame)) {
|
|
843
1136
|
misaddressed += 1;
|
|
1137
|
+
this.forgetAdmittedBody(entry.frame, originOf(entry));
|
|
844
1138
|
this.recordSettled(entry.frame.msgId, "misaddressed");
|
|
845
1139
|
void this.report(entry.frame, "misaddressed");
|
|
846
1140
|
continue;
|
|
@@ -859,7 +1153,7 @@ var InboundGate = class {
|
|
|
859
1153
|
this.held.push(...stillHeld);
|
|
860
1154
|
this.rescheduleExpiry();
|
|
861
1155
|
if (release.length > 0 || dropped > 0) {
|
|
862
|
-
|
|
1156
|
+
debugLogger3.debug(
|
|
863
1157
|
`reevaluate (${reason}): released ${released}, dropped ${dropped}, misaddressed ${misaddressed}, ${this.held.length} still held`
|
|
864
1158
|
);
|
|
865
1159
|
this.notifyHeldChange();
|
|
@@ -880,12 +1174,13 @@ var InboundGate = class {
|
|
|
880
1174
|
}
|
|
881
1175
|
if (this.held.length === 0) return Promise.resolve();
|
|
882
1176
|
const settling = this.held.splice(0, this.held.length);
|
|
883
|
-
|
|
1177
|
+
debugLogger3.debug(
|
|
884
1178
|
`shutdown: expiring ${settling.length} held peer message(s)`
|
|
885
1179
|
);
|
|
886
|
-
const receipts = settling.map(
|
|
887
|
-
|
|
888
|
-
|
|
1180
|
+
const receipts = settling.map((entry) => {
|
|
1181
|
+
this.forgetAdmittedBody(entry.frame, originOf(entry));
|
|
1182
|
+
return this.report(entry.frame, "expired");
|
|
1183
|
+
});
|
|
889
1184
|
this.notifyHeldChange();
|
|
890
1185
|
return Promise.allSettled(receipts).then(() => void 0);
|
|
891
1186
|
}
|
|
@@ -923,19 +1218,70 @@ var InboundGate = class {
|
|
|
923
1218
|
try {
|
|
924
1219
|
return Promise.resolve(this.options.reportStatus?.(frame, status));
|
|
925
1220
|
} catch (error) {
|
|
926
|
-
|
|
1221
|
+
debugLogger3.debug(
|
|
927
1222
|
`reportStatus(${status}) threw: ${error instanceof Error ? error.message : String(error)}`
|
|
928
1223
|
);
|
|
929
1224
|
return Promise.resolve();
|
|
930
1225
|
}
|
|
931
1226
|
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Undo the repeat record an admitted message left, when the gate went on
|
|
1229
|
+
* to settle it as something the far model never saw.
|
|
1230
|
+
*
|
|
1231
|
+
* Admission records a body before the gate decides what to do with it,
|
|
1232
|
+
* so every terminal that is not a delivery or a hold leaves a record for
|
|
1233
|
+
* content that never arrived — and the sender's honest retry then comes
|
|
1234
|
+
* back `duplicate`, a verdict whose whole premise is that the far side
|
|
1235
|
+
* already has it. Keyed exactly as admission keyed it, or it is a
|
|
1236
|
+
* silent no-op.
|
|
1237
|
+
*/
|
|
1238
|
+
forgetAdmittedBody(frame, origin) {
|
|
1239
|
+
this.admission.forgetBody(
|
|
1240
|
+
peerSenderKey(frame, origin),
|
|
1241
|
+
frame.message.content,
|
|
1242
|
+
frame.msgId
|
|
1243
|
+
);
|
|
1244
|
+
}
|
|
1245
|
+
/** Undo an admitted queued message that was invalidated outside the gate. */
|
|
1246
|
+
forgetAdmittedMessage(senderKey, messageId) {
|
|
1247
|
+
this.admission.forgetMessage(senderKey, messageId);
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Turn a message away without the model or the user ever seeing it, and
|
|
1251
|
+
* say so to both audiences.
|
|
1252
|
+
*
|
|
1253
|
+
* Two of the three reasons are decided above policy; `queue-full` is
|
|
1254
|
+
* decided below it, when a buffer the message was already accepted into
|
|
1255
|
+
* had no room. What they share is that nothing was *decided about the
|
|
1256
|
+
* message*, which is why none of them leaves a tombstone: a sender that
|
|
1257
|
+
* waits and retries should find the same gate it would have found if it
|
|
1258
|
+
* had waited in the first place.
|
|
1259
|
+
*
|
|
1260
|
+
* Both reporters are best-effort and neither may take the gate down,
|
|
1261
|
+
* for the same reason `report` is wrapped: this runs on the arrival
|
|
1262
|
+
* path of every message.
|
|
1263
|
+
*/
|
|
1264
|
+
drop(frame, origin, reason) {
|
|
1265
|
+
debugLogger3.debug(`dropped peer message ${frame.msgId} (${reason})`);
|
|
1266
|
+
try {
|
|
1267
|
+
this.options.reportDropped?.(frame, reason, origin);
|
|
1268
|
+
} catch (error) {
|
|
1269
|
+
debugLogger3.debug(`reportDropped(${reason}) threw: ${describe(error)}`);
|
|
1270
|
+
}
|
|
1271
|
+
try {
|
|
1272
|
+
this.options.onDropped?.(frame, origin, reason);
|
|
1273
|
+
} catch (error) {
|
|
1274
|
+
debugLogger3.debug(`onDropped(${reason}) threw: ${describe(error)}`);
|
|
1275
|
+
}
|
|
1276
|
+
return "dropped";
|
|
1277
|
+
}
|
|
932
1278
|
/** Hand a message to the session, reporting whether it landed. */
|
|
933
1279
|
tryDeliver(frame, origin) {
|
|
934
1280
|
try {
|
|
935
1281
|
this.options.deliver(frame, origin);
|
|
936
1282
|
return true;
|
|
937
1283
|
} catch (error) {
|
|
938
|
-
|
|
1284
|
+
debugLogger3.error(
|
|
939
1285
|
`deliver threw for ${frame.msgId}: ${error instanceof Error ? error.message : String(error)}`
|
|
940
1286
|
);
|
|
941
1287
|
return false;
|
|
@@ -985,9 +1331,10 @@ var InboundGate = class {
|
|
|
985
1331
|
this.held.length = 0;
|
|
986
1332
|
this.held.push(...survivors);
|
|
987
1333
|
for (const entry of expired) {
|
|
988
|
-
|
|
1334
|
+
debugLogger3.debug(
|
|
989
1335
|
`held peer message ${entry.frame.msgId} expired after ${expiryMs} ms`
|
|
990
1336
|
);
|
|
1337
|
+
this.forgetAdmittedBody(entry.frame, originOf(entry));
|
|
991
1338
|
this.recordSettled(entry.frame.msgId, "expired");
|
|
992
1339
|
void this.report(entry.frame, "expired");
|
|
993
1340
|
}
|
|
@@ -1028,12 +1375,16 @@ var InboundGate = class {
|
|
|
1028
1375
|
try {
|
|
1029
1376
|
this.options.onHeldChange?.(this.held);
|
|
1030
1377
|
} catch (error) {
|
|
1031
|
-
|
|
1378
|
+
debugLogger3.debug(
|
|
1032
1379
|
`onHeldChange threw: ${error instanceof Error ? error.message : String(error)}`
|
|
1033
1380
|
);
|
|
1034
1381
|
}
|
|
1035
1382
|
}
|
|
1036
1383
|
};
|
|
1384
|
+
function describe(error) {
|
|
1385
|
+
return error instanceof Error ? error.message : String(error);
|
|
1386
|
+
}
|
|
1387
|
+
__name(describe, "describe");
|
|
1037
1388
|
function originOf(entry) {
|
|
1038
1389
|
return {
|
|
1039
1390
|
selfSent: entry.selfSent === true,
|
|
@@ -1103,6 +1454,14 @@ function senderModeClass(mode) {
|
|
|
1103
1454
|
__name(senderModeClass, "senderModeClass");
|
|
1104
1455
|
var MAX_TRACKED_SENDS = 200;
|
|
1105
1456
|
var sentMessages = /* @__PURE__ */ new Map();
|
|
1457
|
+
var NEVER_WRITTEN_SEND_CODES = /* @__PURE__ */ new Set([
|
|
1458
|
+
void 0,
|
|
1459
|
+
"ENOENT",
|
|
1460
|
+
"ECONNREFUSED",
|
|
1461
|
+
"EMSGSIZE",
|
|
1462
|
+
"EAGAIN",
|
|
1463
|
+
"EBUSY"
|
|
1464
|
+
]);
|
|
1106
1465
|
function trackSent(msgId, info) {
|
|
1107
1466
|
const key = canonicalizeMsgId(msgId);
|
|
1108
1467
|
sentMessages.delete(key);
|
|
@@ -1121,17 +1480,25 @@ var RECEIPT_TRANSITIONS = {
|
|
|
1121
1480
|
"denied",
|
|
1122
1481
|
"refused",
|
|
1123
1482
|
"expired",
|
|
1124
|
-
"misaddressed"
|
|
1483
|
+
"misaddressed",
|
|
1484
|
+
"dropped"
|
|
1125
1485
|
]),
|
|
1126
1486
|
// A refusal is decided at admission, so it cannot follow a hold: a
|
|
1127
1487
|
// message already parked was not turned away. Switching the setting to
|
|
1128
1488
|
// `refuse` while it sits there settles it as `denied` — someone chose.
|
|
1489
|
+
//
|
|
1490
|
+
// A drop is decided even earlier, before the message is anything to the
|
|
1491
|
+
// receiver at all, so it can only ever follow `pending`. A `dropped`
|
|
1492
|
+
// receipt naming a message this session already saw held or delivered
|
|
1493
|
+
// is answering a *later* frame that reused the id, and applying it
|
|
1494
|
+
// would tell the user a message that did arrive never did.
|
|
1129
1495
|
held: /* @__PURE__ */ new Set(["delivered", "denied", "expired", "misaddressed"]),
|
|
1130
1496
|
delivered: /* @__PURE__ */ new Set(["expired", "misaddressed"]),
|
|
1131
1497
|
denied: /* @__PURE__ */ new Set(),
|
|
1132
1498
|
refused: /* @__PURE__ */ new Set(),
|
|
1133
1499
|
expired: /* @__PURE__ */ new Set(),
|
|
1134
|
-
misaddressed: /* @__PURE__ */ new Set()
|
|
1500
|
+
misaddressed: /* @__PURE__ */ new Set(),
|
|
1501
|
+
dropped: /* @__PURE__ */ new Set()
|
|
1135
1502
|
};
|
|
1136
1503
|
function settleSentPeerMessage(msgId, status) {
|
|
1137
1504
|
const entry = sentMessages.get(canonicalizeMsgId(msgId));
|
|
@@ -1140,9 +1507,170 @@ function settleSentPeerMessage(msgId, status) {
|
|
|
1140
1507
|
}
|
|
1141
1508
|
const previous = entry.state;
|
|
1142
1509
|
entry.state = status;
|
|
1143
|
-
return {
|
|
1510
|
+
return {
|
|
1511
|
+
address: entry.address,
|
|
1512
|
+
ipcPath: entry.ipcPath,
|
|
1513
|
+
previous,
|
|
1514
|
+
ageMs: Math.max(0, pacerWallNow() - entry.sentAt)
|
|
1515
|
+
};
|
|
1144
1516
|
}
|
|
1145
1517
|
__name(settleSentPeerMessage, "settleSentPeerMessage");
|
|
1518
|
+
var MAX_PACED_TARGETS = 256;
|
|
1519
|
+
var pacedTargets = /* @__PURE__ */ new Map();
|
|
1520
|
+
function pacedTargetFor(ipcPath, sessionId, now) {
|
|
1521
|
+
const existing = pacedTargets.get(ipcPath);
|
|
1522
|
+
if (existing?.sessionId === sessionId) {
|
|
1523
|
+
pacedTargets.delete(ipcPath);
|
|
1524
|
+
pacedTargets.set(ipcPath, existing);
|
|
1525
|
+
return existing;
|
|
1526
|
+
}
|
|
1527
|
+
const generation = (existing?.generation ?? -1) + 1;
|
|
1528
|
+
pacedTargets.delete(ipcPath);
|
|
1529
|
+
while (pacedTargets.size >= MAX_PACED_TARGETS) {
|
|
1530
|
+
let victim;
|
|
1531
|
+
for (const [candidate, target] of pacedTargets) {
|
|
1532
|
+
const level = refillBucket(
|
|
1533
|
+
target.tokens,
|
|
1534
|
+
target.lastRefill,
|
|
1535
|
+
now,
|
|
1536
|
+
PEER_ADMISSION_LIMITS.bucketCapacity,
|
|
1537
|
+
PEER_ADMISSION_LIMITS.refillPerSecond
|
|
1538
|
+
);
|
|
1539
|
+
if (level >= PEER_ADMISSION_LIMITS.bucketCapacity) {
|
|
1540
|
+
victim = candidate;
|
|
1541
|
+
break;
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
victim ??= pacedTargets.keys().next().value;
|
|
1545
|
+
if (victim === void 0) break;
|
|
1546
|
+
pacedTargets.delete(victim);
|
|
1547
|
+
}
|
|
1548
|
+
const fresh = {
|
|
1549
|
+
sessionId,
|
|
1550
|
+
tokens: PEER_ADMISSION_LIMITS.bucketCapacity,
|
|
1551
|
+
lastRefill: now,
|
|
1552
|
+
sentInBurst: 0,
|
|
1553
|
+
burstStartedAt: now,
|
|
1554
|
+
bodies: [],
|
|
1555
|
+
generation
|
|
1556
|
+
};
|
|
1557
|
+
pacedTargets.set(ipcPath, fresh);
|
|
1558
|
+
return fresh;
|
|
1559
|
+
}
|
|
1560
|
+
__name(pacedTargetFor, "pacedTargetFor");
|
|
1561
|
+
var pacerNow = /* @__PURE__ */ __name(() => performance.now(), "pacerNow");
|
|
1562
|
+
var pacerWallNow = /* @__PURE__ */ __name(() => Date.now(), "pacerWallNow");
|
|
1563
|
+
function reservePacerToken(ipcPath, sessionId, body, messageId) {
|
|
1564
|
+
const now = pacerNow();
|
|
1565
|
+
const wallNow = pacerWallNow();
|
|
1566
|
+
const target = pacedTargetFor(ipcPath, sessionId, now);
|
|
1567
|
+
target.tokens = refillBucket(
|
|
1568
|
+
target.tokens,
|
|
1569
|
+
target.lastRefill,
|
|
1570
|
+
now,
|
|
1571
|
+
PEER_ADMISSION_LIMITS.bucketCapacity,
|
|
1572
|
+
PEER_ADMISSION_LIMITS.refillPerSecond
|
|
1573
|
+
);
|
|
1574
|
+
target.lastRefill = now;
|
|
1575
|
+
target.bodies = target.bodies.filter(
|
|
1576
|
+
(record2) => isBodyWithinWindow(
|
|
1577
|
+
record2.at,
|
|
1578
|
+
record2.atWall,
|
|
1579
|
+
now,
|
|
1580
|
+
wallNow,
|
|
1581
|
+
PEER_ADMISSION_LIMITS.dedupWindowMs
|
|
1582
|
+
)
|
|
1583
|
+
);
|
|
1584
|
+
const bodyHash = hashBody(body);
|
|
1585
|
+
const lastBody = target.bodies.at(-1);
|
|
1586
|
+
if (lastBody?.hash === bodyHash) {
|
|
1587
|
+
return { ok: false, repeat: true };
|
|
1588
|
+
}
|
|
1589
|
+
if (!hasToken(target.tokens)) {
|
|
1590
|
+
return { ok: false, sentInBurst: target.sentInBurst };
|
|
1591
|
+
}
|
|
1592
|
+
if (target.tokens >= PEER_ADMISSION_LIMITS.bucketCapacity || now - target.burstStartedAt > PEER_BURST_WINDOW_MS) {
|
|
1593
|
+
target.sentInBurst = 0;
|
|
1594
|
+
target.burstStartedAt = now;
|
|
1595
|
+
}
|
|
1596
|
+
const generation = target.generation;
|
|
1597
|
+
target.tokens -= 1;
|
|
1598
|
+
const record = {
|
|
1599
|
+
messageId: canonicalizeMsgId(messageId),
|
|
1600
|
+
hash: bodyHash,
|
|
1601
|
+
at: now,
|
|
1602
|
+
atWall: wallNow,
|
|
1603
|
+
generation,
|
|
1604
|
+
tokenSpent: true
|
|
1605
|
+
};
|
|
1606
|
+
target.bodies.push(record);
|
|
1607
|
+
target.sentInBurst += 1;
|
|
1608
|
+
let refunded = false;
|
|
1609
|
+
return {
|
|
1610
|
+
ok: true,
|
|
1611
|
+
refund: /* @__PURE__ */ __name(() => {
|
|
1612
|
+
if (refunded) return;
|
|
1613
|
+
refunded = true;
|
|
1614
|
+
refundPacedRecord(target, record, true);
|
|
1615
|
+
forgetPacedBodies(target, [messageId]);
|
|
1616
|
+
}, "refund")
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1619
|
+
__name(reservePacerToken, "reservePacerToken");
|
|
1620
|
+
function drainSendPacer(ipcPath) {
|
|
1621
|
+
const target = pacedTargets.get(ipcPath);
|
|
1622
|
+
if (target === void 0) return;
|
|
1623
|
+
target.tokens = 0;
|
|
1624
|
+
target.lastRefill = pacerNow();
|
|
1625
|
+
target.generation += 1;
|
|
1626
|
+
}
|
|
1627
|
+
__name(drainSendPacer, "drainSendPacer");
|
|
1628
|
+
function forgetSendPacerMessages(ipcPath, messageIds) {
|
|
1629
|
+
const target = pacedTargets.get(ipcPath);
|
|
1630
|
+
if (target === void 0) return;
|
|
1631
|
+
forgetPacedBodies(target, messageIds);
|
|
1632
|
+
}
|
|
1633
|
+
__name(forgetSendPacerMessages, "forgetSendPacerMessages");
|
|
1634
|
+
function refundSendPacerMessage(ipcPath, messageId) {
|
|
1635
|
+
const target = pacedTargets.get(ipcPath);
|
|
1636
|
+
if (target === void 0) return;
|
|
1637
|
+
const canonicalId = canonicalizeMsgId(messageId);
|
|
1638
|
+
const record = target.bodies.find((body) => body.messageId === canonicalId);
|
|
1639
|
+
if (record === void 0) return;
|
|
1640
|
+
refundPacedRecord(target, record);
|
|
1641
|
+
forgetPacedBodies(target, [messageId]);
|
|
1642
|
+
}
|
|
1643
|
+
__name(refundSendPacerMessage, "refundSendPacerMessage");
|
|
1644
|
+
function refundSendPacerToken(ipcPath, messageId) {
|
|
1645
|
+
const target = pacedTargets.get(ipcPath);
|
|
1646
|
+
if (target === void 0) return;
|
|
1647
|
+
const canonicalId = canonicalizeMsgId(messageId);
|
|
1648
|
+
const record = target.bodies.find((body) => body.messageId === canonicalId);
|
|
1649
|
+
if (record === void 0) return;
|
|
1650
|
+
refundPacedRecord(target, record);
|
|
1651
|
+
}
|
|
1652
|
+
__name(refundSendPacerToken, "refundSendPacerToken");
|
|
1653
|
+
function refundPacedRecord(target, record, decrementBurst = false) {
|
|
1654
|
+
if (!record.tokenSpent) return;
|
|
1655
|
+
record.tokenSpent = false;
|
|
1656
|
+
if (decrementBurst) {
|
|
1657
|
+
target.sentInBurst = Math.max(0, target.sentInBurst - 1);
|
|
1658
|
+
}
|
|
1659
|
+
if (record.generation === target.generation) {
|
|
1660
|
+
target.tokens = Math.min(
|
|
1661
|
+
PEER_ADMISSION_LIMITS.bucketCapacity,
|
|
1662
|
+
target.tokens + 1
|
|
1663
|
+
);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
__name(refundPacedRecord, "refundPacedRecord");
|
|
1667
|
+
function forgetPacedBodies(target, messageIds) {
|
|
1668
|
+
const forgotten = new Set(messageIds.map(canonicalizeMsgId));
|
|
1669
|
+
target.bodies = target.bodies.filter(
|
|
1670
|
+
(record) => !forgotten.has(record.messageId)
|
|
1671
|
+
);
|
|
1672
|
+
}
|
|
1673
|
+
__name(forgetPacedBodies, "forgetPacedBodies");
|
|
1146
1674
|
async function sendToPeer(options) {
|
|
1147
1675
|
const own = await readOwnSessionRecord();
|
|
1148
1676
|
const self = own === null ? null : toPeerSessionInfo(own);
|
|
@@ -1214,8 +1742,24 @@ async function sendToPeer(options) {
|
|
|
1214
1742
|
toSessionId: peer.sessionId,
|
|
1215
1743
|
...options.approvalMode !== null ? { fromMode: senderModeClass(options.approvalMode) } : {}
|
|
1216
1744
|
});
|
|
1745
|
+
const reservation = reservePacerToken(
|
|
1746
|
+
peer.ipcPath,
|
|
1747
|
+
peer.sessionId,
|
|
1748
|
+
options.message,
|
|
1749
|
+
frame.msgId
|
|
1750
|
+
);
|
|
1751
|
+
if (!reservation.ok) {
|
|
1752
|
+
return {
|
|
1753
|
+
kind: "failed",
|
|
1754
|
+
peer,
|
|
1755
|
+
address,
|
|
1756
|
+
reason: reservation.repeat ? `that exact message went to that session within the last ${PEER_ADMISSION_LIMITS.dedupWindowMs / 1e3} seconds, and its inbox turns away a repeat before anyone reads it, so this one was not sent. Say something different, or wait for a reply rather than re-sending.` : reservation.sentInBurst === 0 ? "that session inbox is still over its rate limit, so this message was not sent. Wait a little before sending more." : `too many messages to that session just now: ${reservation.sentInBurst} were sent in the last ${PEER_BURST_WINDOW_MS / 1e3} seconds and more would be dropped by its rate limit, so this one was not sent. Batch what remains into one message, or wait a little before sending more.`
|
|
1757
|
+
};
|
|
1758
|
+
}
|
|
1217
1759
|
trackSent(frame.msgId, {
|
|
1218
1760
|
address,
|
|
1761
|
+
ipcPath: peer.ipcPath,
|
|
1762
|
+
sentAt: pacerWallNow(),
|
|
1219
1763
|
state: "pending"
|
|
1220
1764
|
});
|
|
1221
1765
|
try {
|
|
@@ -1224,9 +1768,9 @@ async function sendToPeer(options) {
|
|
|
1224
1768
|
});
|
|
1225
1769
|
return { kind: "sent", peer, address };
|
|
1226
1770
|
} catch (error) {
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1771
|
+
const definitelyUnwritten = error instanceof PeerSendError && NEVER_WRITTEN_SEND_CODES.has(error.code);
|
|
1772
|
+
if (definitelyUnwritten) {
|
|
1773
|
+
reservation.refund();
|
|
1230
1774
|
sentMessages.delete(canonicalizeMsgId(frame.msgId));
|
|
1231
1775
|
}
|
|
1232
1776
|
return {
|
|
@@ -1248,7 +1792,7 @@ function describeSendFailure(error) {
|
|
|
1248
1792
|
case "EBUSY":
|
|
1249
1793
|
return "the session is alive but momentarily busy. Retry the same name shortly.";
|
|
1250
1794
|
case "ETIMEDOUT":
|
|
1251
|
-
return "the session accepted the connection but had not read the message after 5 seconds. It may still read it once it is free, so do not assume it was lost
|
|
1795
|
+
return "the session accepted the connection but had not read the message after 5 seconds. It may still read it once it is free, so do not assume it was lost or resend the same message; wait for a delivery receipt, then tell that session user if none arrives.";
|
|
1252
1796
|
default:
|
|
1253
1797
|
return error.message;
|
|
1254
1798
|
}
|
|
@@ -1261,10 +1805,13 @@ export {
|
|
|
1261
1805
|
flattenPeerLabel,
|
|
1262
1806
|
formatPeerEnvelope,
|
|
1263
1807
|
formatPeerDisplay,
|
|
1808
|
+
MAX_DROPPED_MSG_IDS,
|
|
1264
1809
|
MAX_FRAME_BYTES,
|
|
1810
|
+
MAX_RETAINED_REPLY_TOKEN_CHARS,
|
|
1265
1811
|
canonicalizeMsgId,
|
|
1266
1812
|
parsePeerFrame,
|
|
1267
1813
|
describeDeliveryStatus,
|
|
1814
|
+
describeDropReason,
|
|
1268
1815
|
parsePeerAuthLine,
|
|
1269
1816
|
MAX_SOCKET_PATH_BYTES,
|
|
1270
1817
|
SOCKET_DIR_NAME,
|
|
@@ -1275,12 +1822,19 @@ export {
|
|
|
1275
1822
|
listMessageablePeers,
|
|
1276
1823
|
advertisablePeerAddress,
|
|
1277
1824
|
isInProcessRecipient,
|
|
1825
|
+
PEER_ADMISSION_LIMITS,
|
|
1278
1826
|
MAX_HELD_MESSAGES,
|
|
1827
|
+
MAX_SENDER_KEY_CHARS,
|
|
1828
|
+
peerSenderKey,
|
|
1279
1829
|
parseHeldExpiry,
|
|
1280
1830
|
InboundGate,
|
|
1281
1831
|
describeHoldCause,
|
|
1282
1832
|
getOwnPeerIdentity,
|
|
1283
1833
|
settleSentPeerMessage,
|
|
1834
|
+
drainSendPacer,
|
|
1835
|
+
forgetSendPacerMessages,
|
|
1836
|
+
refundSendPacerMessage,
|
|
1837
|
+
refundSendPacerToken,
|
|
1284
1838
|
sendToPeer
|
|
1285
1839
|
};
|
|
1286
1840
|
/**
|