chainlesschain 0.165.6 → 0.165.8
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/package.json +2 -1
- package/src/assets/web-panel/assets/{AIOps-vfoDb6HZ.js → AIOps-B3gWmXjo.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-Cm5f7p93.js → ActionButton-G9pdT-0e.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-yRuvgWxa.js → Analytics-Ca30J7m2.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-BROsC4h1.js → AppLayout-BtVTm8PU.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-CimHApb1.js → Artifacts-BhViNa6u.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DI8kr8zI.js → Audit-DhKq-FnY.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DSLzKy-n.js → BackgroundAgents-BL0SVbnE.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DkH_n2r1.js → Backup-BWkblnFi.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CKSrUoRA.js → BaseInput-ClaRNrF8.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-ClwfMXUS.js → Chat-sZE2Z77o.js} +4 -4
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CjMaqfL7.js → ChatBubbleRenderer-BAfKOnRe.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-BYTehcQG.js → Checkbox-Cvhay5WM.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-B8AMcp6J.js → Codegen-BpejL-4N.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BNSogPo3.js → Col-g2Ki6g_1.js} +1 -1
- package/src/assets/web-panel/assets/{Community-JxPvGb8O.js → Community-D2xKbNo9.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DF7bFAgE.js → Compact-DbL1Pds3.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Bvhqnwrq.js → Compliance-DGtZCV4c.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BDS0CJvM.js → Cowork-4kQpoNsI.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DqvRfxoQ.js → Cron-BxTYXXAN.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CDcIvnqS.js → Crosschain-k0J0LT2N.js} +1 -1
- package/src/assets/web-panel/assets/{DID-D7jaUxU3.js → DID-BaqhoPgN.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-B5A18wEV.js → Dashboard-T4RVPIM1.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-_6IVkGag.js → Dropdown-DszOToWq.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-C9PeiKCG.js → EmailListRenderer-C9KJU7HF.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DXjuEHrX.js → FamilyGuardDashboard-Cmr0JnsA.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-B4IAR9zv.js → Federation-BI-_2xZX.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CxQeSNx1.js → FormItemContext-DsnDEPsl.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-B8GS-Y3N.js → GenericCardRenderer-BLncAKKK.js} +1 -1
- package/src/assets/web-panel/assets/{Git-BS8SouUX.js → Git-BPQhGXDn.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-Dq9aYb5Q.js → Governance-CyElbUQW.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BrCEe9AJ.js → Inference-CcSntBQD.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DybOpiY2.js → KnowledgeGraph-p6e7HZ2r.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DBpzbRfx.js → Logs-CNEtr-wz.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-Bj9xI0YH.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-Cc7CCSCu.js → Marketplace-Dd2notAJ.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DFaIhOfu.js → McpTools-BBsN2CSW.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-KCqlp9ol.js → Memory-Dg2uTvwx.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-Bys84ySN.js → MobileBridge-r1YMRiJV.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-B-6qU-je.js → MobileProjects-C-89kSm4.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BgY-fvCq.js → Mtc-CrPZcVgf.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-B8pCmqQJ.js → MtcAudit-sxi7hqJb.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-BbOmYutA.js → Multisig-CAgOMfF1.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-BocLQz_M.js → NLProgramming-DafaPts3.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CSwbxEop.js → Notes-CJM-xUW_.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-CqFNSYG3.js → NotificationSettings-L0ZjfAh_.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-C6gMfAk7.js → OrderTableRenderer-BB1CvCK0.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-QrDXVdMJ.js → Organization-DeLtgAD8.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-Cxntotjt.js → Overflow-CKC2b10z.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CIdBOwNq.js → P2P-eyf2rzY1.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-CT5aP8rD.js → PdhVaultBrowser-5upNIOW8.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-DH7OM8XU.js → Permissions-B8p3W6JK.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CnJqbWce.js → PersonalDataHub-BnRMxTq3.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-5C3HG1kH.js → Pipeline-DjCNI1li.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-B4yIp7xK.js → Privacy-u6gXLbWT.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BMvt-2M0.js → ProjectInit-BZAw4tkM.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CvSTEMRG.js → ProjectSettings-B1Tr72q7.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BKBJxEek.js → Projects-DO7HFuBQ.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-BdwUqFWN.js → Providers-BjCztyhj.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DtnCKlTm.js → QrScannerModal-ZWyCUbR1.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-sTbgwyNr.js → QuickAsk-DhDjeW4G.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Bzr14gSb.js → Recommend-R_CN2MuU.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-C_TF5IaJ.js → RemoteSession-uxFcujeu.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-GCAM4H2K.js → Reputation-DX8D7rks.js} +1 -1
- package/src/assets/web-panel/assets/{Row-Dir0r6OQ.js → Row-BYahQILD.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-CpGnlecX.js → RssFeed-CUSQ2tm-.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DMMamC6u.js → Search-CnN_S8Cs.js} +1 -1
- package/src/assets/web-panel/assets/{Security-yqay5yG-.js → Security-DcZlh-O4.js} +4 -4
- package/src/assets/web-panel/assets/{Services-C5i2wh9G.js → Services-ZhBOjzIX.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-vUqk35yj.js → Skeleton-D5Qnq_lI.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DzcSkhot.js → Skills-BC9cZ3Be.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CB6qbYNr.js → Sla-DauJ3E8_.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-DbNeZ_fh.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-D-RrsxTJ.js → SyncSettings-DUvJbSct.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DFxdOJiq.js → Tasks-BXa-5noQ.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-SKAob_Aq.js → Templates-JxaiUt_3.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BPfsfHQ-.js → Tenant-BW4ERQN8.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-8iw7FpUf.js → Terminal-DltUbdtv.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-C4Gjj5Su.js → TimelineRenderer-CMoZz0Tc.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-COYhN18e.js → Tokens-D_VEV60K.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-B-JzXcf6.js → Trigger-CCXTCs0R.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BJ5bg0nI.js → Trust-lkGRIPcG.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-BG_ByRMf.js → UkeySign-BfGeQ9HX.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-B3kOnSiU.js → VideoEditing-BEC7pYvc.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CcoGFnK7.js → Wallet-BZdG24o0.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DdT8vRC_.js → WebAuthn-yoEddedp.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-CZu3M9N8.js → WorkflowEditor-BXZEprOW.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DxJt3Qba.js → chat-gg0bzZal.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DdYfwJaB.js → colors-DjRVRuin.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BG3SUzwj.js → compact-item-su-dieAc.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-By51z2-1.js → createContext-Ci3S8oKF.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CxbFX9uH.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-B0apZ3KJ.js → hasIn-D8F10heH.js} +1 -1
- package/src/assets/web-panel/assets/{index-DE76D-kA.js → index-11wtYQiF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BkvcEo4p.js → index-1IgxgALJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bos5_aMV.js → index-1agckWSq.js} +1 -1
- package/src/assets/web-panel/assets/{index-BfWYsB9t.js → index-5JqhujnR.js} +1 -1
- package/src/assets/web-panel/assets/{index-CAhHirVH.js → index-6Tniqk1s.js} +1 -1
- package/src/assets/web-panel/assets/{index-BYINoRLK.js → index-8Z-8BIlN.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5rLiEo0.js → index-BCI5v0yB.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZ_0qm4g.js → index-BDJntYmJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CWEuZWCK.js → index-BGzixOGZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BLIbGOLX.js → index-BMMOVoLD.js} +1 -1
- package/src/assets/web-panel/assets/{index-bP_VlDb4.js → index-BTFpdBtt.js} +1 -1
- package/src/assets/web-panel/assets/{index-B3rWuPMW.js → index-Ba_HyPnE.js} +1 -1
- package/src/assets/web-panel/assets/{index-Da_WBdLX.js → index-BbF1IHof.js} +1 -1
- package/src/assets/web-panel/assets/{index-RD6O-0C6.js → index-BgT9su_e.js} +1 -1
- package/src/assets/web-panel/assets/{index-CpjZhgNd.js → index-BzPB6kGZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Q2SCX-MI.js → index-C6ygzplr.js} +1 -1
- package/src/assets/web-panel/assets/{index-BIrJfqO4.js → index-CC3ZDjzY.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBPf3TpM.js → index-CEmXRhAE.js} +1 -1
- package/src/assets/web-panel/assets/index-CJGOlduY.js +1 -0
- package/src/assets/web-panel/assets/{index-Cp6e-hGw.js → index-CMPqd5Cv.js} +1 -1
- package/src/assets/web-panel/assets/{index-kf57LJ93.js → index-COWd8uGx.js} +1 -1
- package/src/assets/web-panel/assets/{index-CW93tZDv.js → index-CPIYSp4H.js} +1 -1
- package/src/assets/web-panel/assets/{index-BewKYrX9.js → index-CR0h6seT.js} +1 -1
- package/src/assets/web-panel/assets/{index-p1_8SbI1.js → index-CUmDhC4j.js} +1 -1
- package/src/assets/web-panel/assets/index-CkBCN5yH.js +1 -0
- package/src/assets/web-panel/assets/{index-ZxtP8jG7.js → index-CusgZPtc.js} +1 -1
- package/src/assets/web-panel/assets/{index-BWF8_mkx.js → index-DBAETXft.js} +1 -1
- package/src/assets/web-panel/assets/{index-BP94CrBV.js → index-DIJ2qFOC.js} +1 -1
- package/src/assets/web-panel/assets/{index-CNIxXMmN.js → index-Dbq4NMET.js} +1 -1
- package/src/assets/web-panel/assets/{index-CzmQElZy.js → index-DkMEF5VT.js} +1 -1
- package/src/assets/web-panel/assets/{index-ZFbps9po.js → index-DkfQR3hM.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dz49VOyQ.js → index-DnLucOn9.js} +1 -1
- package/src/assets/web-panel/assets/{index-oxRrmFO9.js → index-DugB_aKa.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bissefox.js → index-DyLUarhZ.js} +3 -3
- package/src/assets/web-panel/assets/{index-DaIz0NAW.js → index-T6_6ygJZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-SQqOlR3d.js → index-Tu8eWqh-.js} +1 -1
- package/src/assets/web-panel/assets/{index-DCn8IcxV.js → index-W7XUgdVL.js} +1 -1
- package/src/assets/web-panel/assets/{index-3jWkIu5j.js → index-dHZinfko.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1C9i79A.js → index-jyNgp5FI.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BWJktcg8.js → initDefaultProps-Cv4p8e4w.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BEXBzUFV.js → motion-B8EwmMOw.js} +1 -1
- package/src/assets/web-panel/assets/{move-BUKwn2sx.js → move-B8R9F8TK.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-FTI7btqQ.js → mtc-parser-DoFYz5Bp.js} +1 -1
- package/src/assets/web-panel/assets/{omit-CsLtSPH3.js → omit-CHWYLBjI.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DWZxsT_S.js → pickAttrs-CXuPDO77.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-Bcfs43CX.js → placementArrow-BaAtUUon.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-a9bcC1p-.js → responsiveObserve-DCc4zcDD.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BDTzgOXN.js → slide-B_lx89oZ.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Iq1j_HUg.js → statusUtils-DE8DPmR0.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DAZEYxeh.js → styleChecker-1ktXOsL_.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DVkjyDrh.js → useFlexGapSupport-xH89-wjh.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-wXTpNTHt.js → useFs-DscvfW5p.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CtQM_aZr.js → usePersonalDataHub-RdMKQta7.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-hjEoG85v.js → vnode-Z3wTni2T.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-BHVpyKg_.js → zoom-B0iChCsm.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +3 -3
- package/src/commands/agent.js +91 -60
- package/src/commands/git.js +39 -0
- package/src/commands/plugin.js +17 -2
- package/src/commands/session-group.js +131 -0
- package/src/commands/session-location.js +48 -0
- package/src/commands/session.js +11 -0
- package/src/commands/sync-providers.js +4 -1
- package/src/commands/update.js +1 -0
- package/src/data/changelog.json +27 -1
- package/src/gateways/ws/ws-agent-handler.js +20 -12
- package/src/harness/jsonl-session-store.js +279 -37
- package/src/harness/mcp-client.js +701 -91
- package/src/harness/session-index.js +15 -4
- package/src/lib/agent-worktree.js +8 -1
- package/src/lib/auto-mode-safety-classifier.js +3 -17
- package/src/lib/background-agent-keeper-protocol.js +26 -6
- package/src/lib/background-agent-supervisor.js +17 -1
- package/src/lib/browser-evidence.js +1405 -0
- package/src/lib/chrome-connector.js +465 -79
- package/src/lib/claude-project-auto-memory.js +481 -0
- package/src/lib/claude-project-storage-layout.js +104 -0
- package/src/lib/cloud/cloud-client.js +40 -0
- package/src/lib/execution-location-local-supervisor.mjs +226 -0
- package/src/lib/execution-location-runner-lifecycle.js +991 -0
- package/src/lib/execution-location-target-preflight.js +333 -0
- package/src/lib/execution-location-target.js +783 -13
- package/src/lib/file-lines.js +6 -1
- package/src/lib/gateway-client-resilience.js +571 -0
- package/src/lib/gitlab-mr-worktree.js +176 -0
- package/src/lib/hooks-v2-producers.js +119 -15
- package/src/lib/hooks-v2-workspace-context.js +44 -1
- package/src/lib/host-resource-budget.js +295 -0
- package/src/lib/ide-context.js +15 -2
- package/src/lib/llm-config-defaults.js +43 -0
- package/src/lib/mcp-lifecycle-authority.js +887 -0
- package/src/lib/mcp-oauth.js +101 -20
- package/src/lib/mcp-tls.js +103 -3
- package/src/lib/paths.js +260 -32
- package/src/lib/permission-request.js +48 -0
- package/src/lib/plugin-runtime/bin.js +3 -1
- package/src/lib/plugin-runtime/hooks.js +3 -1
- package/src/lib/plugin-runtime/install.js +117 -1
- package/src/lib/plugin-runtime/lsp.js +4 -4
- package/src/lib/plugin-runtime/marketplace-archive-source.js +40 -5
- package/src/lib/plugin-runtime/marketplace-catalog.js +13 -2
- package/src/lib/plugin-runtime/marketplace-command-descriptor.js +343 -0
- package/src/lib/plugin-runtime/marketplace-command-source.js +580 -0
- package/src/lib/plugin-runtime/marketplace-source-adapter.js +33 -8
- package/src/lib/plugin-runtime/mcp.js +3 -1
- package/src/lib/plugin-runtime/monitors.js +3 -1
- package/src/lib/plugin-runtime/remote-source.js +151 -6
- package/src/lib/plugin-runtime/settings.js +3 -1
- package/src/lib/plugin-runtime/trust.js +274 -13
- package/src/lib/process-execution-broker/credential-agent.js +210 -41
- package/src/lib/process-execution-broker/index.js +15 -1
- package/src/lib/process-execution-broker/linux-cgroup-v2.js +391 -0
- package/src/lib/process-execution-broker/platform-sandbox.js +50 -28
- package/src/lib/project-mcp-trust.js +165 -10
- package/src/lib/resume-session.js +9 -6
- package/src/lib/session-core-singletons.js +26 -11
- package/src/lib/session-host-lease.js +52 -16
- package/src/lib/session-lifecycle-scope.js +153 -0
- package/src/lib/session-message-fabric.js +168 -21
- package/src/lib/session-projection.js +244 -4
- package/src/lib/session-store-guard.js +3 -1
- package/src/lib/session-workbench-store.js +466 -0
- package/src/lib/spellcheck.js +211 -0
- package/src/lib/sub-agent-context.js +9 -9
- package/src/lib/sync-cli-db.js +485 -129
- package/src/lib/sync-credentials.js +31 -20
- package/src/lib/web-fetch.js +112 -40
- package/src/lib/web-search.js +127 -61
- package/src/lib/workspace-trust.js +793 -0
- package/src/repl/agent-repl.js +124 -5
- package/src/repl/permission-prompt.js +10 -12
- package/src/runtime/agent-core.js +311 -18
- package/src/runtime/bootstrap.js +37 -13
- package/src/runtime/coding-agent-contract-shared.cjs +11 -4
- package/src/runtime/headless-runner.js +234 -36
- package/src/runtime/headless-stream.js +181 -6
- package/src/runtime/mcp-config.js +298 -8
- package/src/runtime/policies/agent-policy.js +8 -0
- package/src/runtime/status-diagnostics-lite.js +9 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-CFyt8iUd.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-BHUI7cox.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DorRZcI9.js +0 -1
- package/src/assets/web-panel/assets/index-BETiL0a3.js +0 -1
- package/src/assets/web-panel/assets/index-BhAVHuW8.js +0 -1
package/src/lib/file-lines.js
CHANGED
|
@@ -62,9 +62,14 @@ function joinedLineBuffer(parts, byteLength) {
|
|
|
62
62
|
|
|
63
63
|
function recordRead(ioMetrics, bytes) {
|
|
64
64
|
if (!ioMetrics || typeof ioMetrics !== "object") return;
|
|
65
|
+
const normalizedBytes = Math.max(0, Number(bytes) || 0);
|
|
65
66
|
ioMetrics.readCalls = Math.max(0, Number(ioMetrics.readCalls) || 0) + 1;
|
|
66
67
|
ioMetrics.bytesRead =
|
|
67
|
-
Math.max(0, Number(ioMetrics.bytesRead) || 0) +
|
|
68
|
+
Math.max(0, Number(ioMetrics.bytesRead) || 0) + normalizedBytes;
|
|
69
|
+
ioMetrics.maxReadBytes = Math.max(
|
|
70
|
+
Math.max(0, Number(ioMetrics.maxReadBytes) || 0),
|
|
71
|
+
normalizedBytes,
|
|
72
|
+
);
|
|
68
73
|
}
|
|
69
74
|
|
|
70
75
|
/**
|
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded resilience layer for private Runner / gateway SSE connections.
|
|
3
|
+
*
|
|
4
|
+
* The layer deliberately receives TLS and proxy credentials through callbacks.
|
|
5
|
+
* It never parses certificate/key paths or retains proxy values, so callers
|
|
6
|
+
* can use a managed credential source that is refreshed before every connect.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const MAX_RECONNECT_ATTEMPTS = 8;
|
|
10
|
+
const MAX_IDLE_TIMEOUT_MS = 10 * 60_000;
|
|
11
|
+
const MIN_IDLE_TIMEOUT_MS = 100;
|
|
12
|
+
const MAX_EVENT_ID_BYTES = 256;
|
|
13
|
+
const MAX_EVENT_DATA_BYTES = 1024 * 1024;
|
|
14
|
+
const MAX_SSE_PENDING_BYTES = MAX_EVENT_DATA_BYTES + 8 * 1024;
|
|
15
|
+
const MAX_SSE_FRAME_BYTES = MAX_EVENT_DATA_BYTES + 64 * 1024;
|
|
16
|
+
const MAX_SSE_FRAME_LINES = 4096;
|
|
17
|
+
|
|
18
|
+
function gatewayError(code, message) {
|
|
19
|
+
const error = new Error(message);
|
|
20
|
+
error.code = code;
|
|
21
|
+
return error;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function positiveInteger(value, fallback, minimum, maximum) {
|
|
25
|
+
if (value === undefined || value === null) return fallback;
|
|
26
|
+
const number = Number(value);
|
|
27
|
+
if (!Number.isSafeInteger(number) || number < minimum || number > maximum) {
|
|
28
|
+
throw gatewayError(
|
|
29
|
+
"CC_GATEWAY_OPTIONS_INVALID",
|
|
30
|
+
"gateway option is invalid",
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function safeEventId(value) {
|
|
37
|
+
const id = String(value || "");
|
|
38
|
+
if (
|
|
39
|
+
Buffer.byteLength(id, "utf8") > MAX_EVENT_ID_BYTES ||
|
|
40
|
+
/[\r\n\u0000-\u001f\u007f]/u.test(id)
|
|
41
|
+
) {
|
|
42
|
+
throw gatewayError(
|
|
43
|
+
"CC_GATEWAY_SSE_EVENT_INVALID",
|
|
44
|
+
"gateway event id is invalid",
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
return id;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function safeRelativePath(value) {
|
|
51
|
+
const path = String(value || "");
|
|
52
|
+
if (
|
|
53
|
+
!path.startsWith("/") ||
|
|
54
|
+
path.startsWith("//") ||
|
|
55
|
+
/[\\?#\r\n\u0000]/u.test(path)
|
|
56
|
+
) {
|
|
57
|
+
throw gatewayError(
|
|
58
|
+
"CC_GATEWAY_PATH_INVALID",
|
|
59
|
+
"gateway request path is invalid",
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
for (const segment of path.split("/")) {
|
|
63
|
+
let decoded;
|
|
64
|
+
try {
|
|
65
|
+
decoded = decodeURIComponent(segment);
|
|
66
|
+
} catch {
|
|
67
|
+
throw gatewayError(
|
|
68
|
+
"CC_GATEWAY_PATH_INVALID",
|
|
69
|
+
"gateway request path is invalid",
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
if (
|
|
73
|
+
decoded === "." ||
|
|
74
|
+
decoded === ".." ||
|
|
75
|
+
/[\\/\r\n\u0000]/u.test(decoded)
|
|
76
|
+
) {
|
|
77
|
+
throw gatewayError(
|
|
78
|
+
"CC_GATEWAY_PATH_INVALID",
|
|
79
|
+
"gateway request path is invalid",
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return path;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function responseContentType(response) {
|
|
87
|
+
const headers = response?.headers;
|
|
88
|
+
if (headers && typeof headers.get === "function") {
|
|
89
|
+
return String(headers.get("content-type") || "").toLowerCase();
|
|
90
|
+
}
|
|
91
|
+
return "";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Translate a gateway failure to a small stable vocabulary. Raw upstream
|
|
96
|
+
* messages, URLs, proxy configuration and bodies are intentionally discarded.
|
|
97
|
+
*/
|
|
98
|
+
export function normalizeGatewayError(errorOrResponse) {
|
|
99
|
+
if (typeof errorOrResponse?.status === "number") {
|
|
100
|
+
const status = errorOrResponse.status;
|
|
101
|
+
if (status === 401 || status === 403) {
|
|
102
|
+
return gatewayError(
|
|
103
|
+
"CC_GATEWAY_UPSTREAM_AUTH_REJECTED",
|
|
104
|
+
"gateway upstream authentication was rejected",
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
if (status === 408 || status === 425 || status === 429 || status >= 500) {
|
|
108
|
+
return gatewayError(
|
|
109
|
+
"CC_GATEWAY_UPSTREAM_UNAVAILABLE",
|
|
110
|
+
"gateway upstream is temporarily unavailable",
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return gatewayError(
|
|
114
|
+
"CC_GATEWAY_UPSTREAM_REJECTED",
|
|
115
|
+
"gateway upstream rejected the request",
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const codes = [
|
|
120
|
+
errorOrResponse?.code,
|
|
121
|
+
errorOrResponse?.cause?.code,
|
|
122
|
+
errorOrResponse?.name,
|
|
123
|
+
errorOrResponse?.cause?.name,
|
|
124
|
+
].map((value) => String(value || "").toUpperCase());
|
|
125
|
+
if (
|
|
126
|
+
codes.some((code) =>
|
|
127
|
+
["ABORT_ERR", "ERR_ABORTED", "ABORTERROR"].includes(code),
|
|
128
|
+
)
|
|
129
|
+
) {
|
|
130
|
+
return gatewayError("CC_GATEWAY_ABORTED", "gateway request was aborted");
|
|
131
|
+
}
|
|
132
|
+
if (
|
|
133
|
+
[
|
|
134
|
+
"ECONNRESET",
|
|
135
|
+
"ETIMEDOUT",
|
|
136
|
+
"ECONNREFUSED",
|
|
137
|
+
"EAI_AGAIN",
|
|
138
|
+
"ENETUNREACH",
|
|
139
|
+
"ENOTFOUND",
|
|
140
|
+
"UND_ERR_SOCKET",
|
|
141
|
+
].some((code) => codes.includes(code))
|
|
142
|
+
) {
|
|
143
|
+
return gatewayError(
|
|
144
|
+
"CC_GATEWAY_TRANSPORT_UNAVAILABLE",
|
|
145
|
+
"gateway transport is temporarily unavailable",
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
return gatewayError(
|
|
149
|
+
"CC_GATEWAY_TRANSPORT_FAILED",
|
|
150
|
+
"gateway transport failed",
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function isRetryableGatewayError(error) {
|
|
155
|
+
return new Set([
|
|
156
|
+
"CC_GATEWAY_SSE_IDLE_TIMEOUT",
|
|
157
|
+
"CC_GATEWAY_TRANSPORT_UNAVAILABLE",
|
|
158
|
+
"CC_GATEWAY_UPSTREAM_UNAVAILABLE",
|
|
159
|
+
]).has(error?.code);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function parseSseBlock(lines, state, onEvent, onKeepalive) {
|
|
163
|
+
if (lines.length === 0) return;
|
|
164
|
+
let event = "message";
|
|
165
|
+
let id = state.lastEventId;
|
|
166
|
+
const data = [];
|
|
167
|
+
let sawField = false;
|
|
168
|
+
|
|
169
|
+
for (const line of lines) {
|
|
170
|
+
if (line.startsWith(":")) {
|
|
171
|
+
onKeepalive?.();
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const separator = line.indexOf(":");
|
|
175
|
+
const field = separator === -1 ? line : line.slice(0, separator);
|
|
176
|
+
const rawValue = separator === -1 ? "" : line.slice(separator + 1);
|
|
177
|
+
const value = rawValue.startsWith(" ") ? rawValue.slice(1) : rawValue;
|
|
178
|
+
if (field === "event") {
|
|
179
|
+
event = value || "message";
|
|
180
|
+
sawField = true;
|
|
181
|
+
} else if (field === "id") {
|
|
182
|
+
id = safeEventId(value);
|
|
183
|
+
sawField = true;
|
|
184
|
+
} else if (field === "data") {
|
|
185
|
+
data.push(value);
|
|
186
|
+
sawField = true;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (!sawField) return;
|
|
190
|
+
state.lastEventId = id;
|
|
191
|
+
// SSE permits id-only frames to update the replay cursor. They are not
|
|
192
|
+
// application events and must not make consumers process an empty message.
|
|
193
|
+
if (data.length === 0) return;
|
|
194
|
+
const body = data.join("\n");
|
|
195
|
+
if (Buffer.byteLength(body, "utf8") > MAX_EVENT_DATA_BYTES) {
|
|
196
|
+
throw gatewayError(
|
|
197
|
+
"CC_GATEWAY_SSE_EVENT_INVALID",
|
|
198
|
+
"gateway event is too large",
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
onEvent?.(Object.freeze({ event, id, data: body }));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function invalidSseFrame() {
|
|
205
|
+
return gatewayError(
|
|
206
|
+
"CC_GATEWAY_SSE_FRAME_INVALID",
|
|
207
|
+
"gateway event frame is invalid",
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function queueSseFrameLine(state, line) {
|
|
212
|
+
const bytes = Buffer.byteLength(line, "utf8");
|
|
213
|
+
if (
|
|
214
|
+
state.frameLineCount >= MAX_SSE_FRAME_LINES ||
|
|
215
|
+
state.frameBytes + bytes > MAX_SSE_FRAME_BYTES
|
|
216
|
+
) {
|
|
217
|
+
throw invalidSseFrame();
|
|
218
|
+
}
|
|
219
|
+
state.lines.push(line);
|
|
220
|
+
state.frameLineCount += 1;
|
|
221
|
+
state.frameBytes += bytes;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function resetSseFrame(state) {
|
|
225
|
+
state.lines = [];
|
|
226
|
+
state.frameLineCount = 0;
|
|
227
|
+
state.frameBytes = 0;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function consumeSseChunk(pending, chunk, state, onEvent, onKeepalive) {
|
|
231
|
+
// Measure the only line that can extend `pending` before concatenating or
|
|
232
|
+
// allocating a list of lines. A malicious peer therefore cannot grow an
|
|
233
|
+
// unterminated frame through arbitrarily fragmented chunks.
|
|
234
|
+
const firstNewline = chunk.indexOf("\n");
|
|
235
|
+
const pendingPrefix =
|
|
236
|
+
firstNewline === -1 ? chunk : chunk.slice(0, firstNewline);
|
|
237
|
+
if (
|
|
238
|
+
Buffer.byteLength(pending, "utf8") +
|
|
239
|
+
Buffer.byteLength(pendingPrefix, "utf8") >
|
|
240
|
+
MAX_SSE_PENDING_BYTES
|
|
241
|
+
) {
|
|
242
|
+
throw invalidSseFrame();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const buffer = pending + chunk;
|
|
246
|
+
let start = 0;
|
|
247
|
+
for (;;) {
|
|
248
|
+
const newline = buffer.indexOf("\n", start);
|
|
249
|
+
if (newline === -1) return buffer.slice(start);
|
|
250
|
+
const line = buffer.slice(start, newline);
|
|
251
|
+
const normalized = line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
252
|
+
if (normalized === "") {
|
|
253
|
+
parseSseBlock(state.lines, state, onEvent, onKeepalive);
|
|
254
|
+
resetSseFrame(state);
|
|
255
|
+
} else {
|
|
256
|
+
queueSseFrameLine(state, normalized);
|
|
257
|
+
}
|
|
258
|
+
start = newline + 1;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function withIdleDeadline(reader, idleTimeoutMs, deps) {
|
|
263
|
+
let timer = null;
|
|
264
|
+
const idle = new Promise((_, reject) => {
|
|
265
|
+
timer = deps.setTimeout(() => {
|
|
266
|
+
reject(
|
|
267
|
+
gatewayError(
|
|
268
|
+
"CC_GATEWAY_SSE_IDLE_TIMEOUT",
|
|
269
|
+
"gateway event stream became idle",
|
|
270
|
+
),
|
|
271
|
+
);
|
|
272
|
+
// Reject before resolving cancellation: otherwise a compliant reader
|
|
273
|
+
// can report `done` first and silently turn an idle timeout into a clean
|
|
274
|
+
// EOF, skipping the reconnect path.
|
|
275
|
+
Promise.resolve(reader.cancel?.()).catch(() => {});
|
|
276
|
+
}, idleTimeoutMs);
|
|
277
|
+
});
|
|
278
|
+
return Promise.race([reader.read(), idle]).finally(() => {
|
|
279
|
+
if (timer !== null) deps.clearTimeout(timer);
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function consumeSse(response, options, deps) {
|
|
284
|
+
if (!response?.ok) throw normalizeGatewayError(response);
|
|
285
|
+
if (!/^text\/event-stream(?:;|$)/u.test(responseContentType(response))) {
|
|
286
|
+
throw gatewayError(
|
|
287
|
+
"CC_GATEWAY_SSE_CONTENT_TYPE_INVALID",
|
|
288
|
+
"gateway did not return an event stream",
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
if (!response.body || typeof response.body.getReader !== "function") {
|
|
292
|
+
throw gatewayError(
|
|
293
|
+
"CC_GATEWAY_SSE_BODY_INVALID",
|
|
294
|
+
"gateway event stream body is unavailable",
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const reader = response.body.getReader();
|
|
299
|
+
const decoder = new TextDecoder();
|
|
300
|
+
const state = {
|
|
301
|
+
lines: [],
|
|
302
|
+
frameBytes: 0,
|
|
303
|
+
frameLineCount: 0,
|
|
304
|
+
lastEventId: options.lastEventId || "",
|
|
305
|
+
};
|
|
306
|
+
let pending = "";
|
|
307
|
+
try {
|
|
308
|
+
for (;;) {
|
|
309
|
+
const record = await withIdleDeadline(
|
|
310
|
+
reader,
|
|
311
|
+
options.idleTimeoutMs,
|
|
312
|
+
deps,
|
|
313
|
+
);
|
|
314
|
+
if (record.done) break;
|
|
315
|
+
const chunk = decoder.decode(record.value, { stream: true });
|
|
316
|
+
pending = consumeSseChunk(
|
|
317
|
+
pending,
|
|
318
|
+
chunk,
|
|
319
|
+
state,
|
|
320
|
+
options.onEvent,
|
|
321
|
+
options.onKeepalive,
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
pending = consumeSseChunk(
|
|
325
|
+
pending,
|
|
326
|
+
decoder.decode(),
|
|
327
|
+
state,
|
|
328
|
+
options.onEvent,
|
|
329
|
+
options.onKeepalive,
|
|
330
|
+
);
|
|
331
|
+
if (pending) {
|
|
332
|
+
queueSseFrameLine(
|
|
333
|
+
state,
|
|
334
|
+
pending.endsWith("\r") ? pending.slice(0, -1) : pending,
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
parseSseBlock(state.lines, state, options.onEvent, options.onKeepalive);
|
|
338
|
+
return Object.freeze({ lastEventId: state.lastEventId, ended: true });
|
|
339
|
+
} catch (error) {
|
|
340
|
+
// Keep the most recently confirmed replay cursor private to this recovery
|
|
341
|
+
// loop. It is never rendered or persisted by the transport layer.
|
|
342
|
+
if (error && typeof error === "object" && state.lastEventId) {
|
|
343
|
+
error.lastEventId = state.lastEventId;
|
|
344
|
+
}
|
|
345
|
+
throw error;
|
|
346
|
+
} finally {
|
|
347
|
+
try {
|
|
348
|
+
reader.releaseLock?.();
|
|
349
|
+
} catch {
|
|
350
|
+
// A timed-out read may still be settling its cancellation. The stream
|
|
351
|
+
// has already been rejected with the canonical idle error above.
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function validateBaseUrl(value) {
|
|
357
|
+
let url;
|
|
358
|
+
try {
|
|
359
|
+
url = new URL(String(value || ""));
|
|
360
|
+
} catch {
|
|
361
|
+
throw gatewayError("CC_GATEWAY_URL_INVALID", "gateway base URL is invalid");
|
|
362
|
+
}
|
|
363
|
+
if (
|
|
364
|
+
!new Set(["https:", "http:"]).has(url.protocol) ||
|
|
365
|
+
url.username ||
|
|
366
|
+
url.password
|
|
367
|
+
) {
|
|
368
|
+
throw gatewayError("CC_GATEWAY_URL_INVALID", "gateway base URL is invalid");
|
|
369
|
+
}
|
|
370
|
+
return new URL(url.href.replace(/\/+$/u, ""));
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function safeHeaders(value) {
|
|
374
|
+
if (value == null) return {};
|
|
375
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
376
|
+
throw gatewayError(
|
|
377
|
+
"CC_GATEWAY_OPTIONS_INVALID",
|
|
378
|
+
"gateway headers are invalid",
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
const headers = {};
|
|
382
|
+
for (const [name, entry] of Object.entries(value)) {
|
|
383
|
+
if (
|
|
384
|
+
!/^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,128}$/u.test(name) ||
|
|
385
|
+
/^(?:proxy-authorization|host|connection)$/iu.test(name) ||
|
|
386
|
+
typeof entry !== "string" ||
|
|
387
|
+
entry.length > 16 * 1024 ||
|
|
388
|
+
/[\r\n\u0000]/u.test(entry)
|
|
389
|
+
) {
|
|
390
|
+
throw gatewayError(
|
|
391
|
+
"CC_GATEWAY_OPTIONS_INVALID",
|
|
392
|
+
"gateway headers are invalid",
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
headers[name] = entry;
|
|
396
|
+
}
|
|
397
|
+
return headers;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function transportDispatcher(value) {
|
|
401
|
+
const dispatcher = value?.dispatcher;
|
|
402
|
+
if (
|
|
403
|
+
!dispatcher ||
|
|
404
|
+
(typeof dispatcher !== "object" && typeof dispatcher !== "function") ||
|
|
405
|
+
typeof dispatcher.dispatch !== "function"
|
|
406
|
+
) {
|
|
407
|
+
throw gatewayError(
|
|
408
|
+
"CC_GATEWAY_OPTIONS_INVALID",
|
|
409
|
+
"gateway transport is invalid",
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
return dispatcher;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* A narrow, injected gateway client. TLS and proxy helpers are invoked once per
|
|
417
|
+
* connection attempt, which provides hot rotation without retaining secrets.
|
|
418
|
+
*
|
|
419
|
+
* Providers return a `{ dispatcher }` capability rather than raw credentials.
|
|
420
|
+
* In particular, `proxyAuthHelper` owns the proxy route and authentication and
|
|
421
|
+
* must return the complete proxy dispatcher (it receives TLS material so it
|
|
422
|
+
* can compose the transport when both are required). The origin request never
|
|
423
|
+
* receives a `Proxy-Authorization` header.
|
|
424
|
+
*/
|
|
425
|
+
export class ResilientGatewayClient {
|
|
426
|
+
constructor(options = {}) {
|
|
427
|
+
this.baseUrl = validateBaseUrl(options.baseUrl);
|
|
428
|
+
if (typeof options.fetch !== "function") {
|
|
429
|
+
throw gatewayError(
|
|
430
|
+
"CC_GATEWAY_OPTIONS_INVALID",
|
|
431
|
+
"gateway fetch is required",
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
this.fetch = options.fetch;
|
|
435
|
+
this.tlsProvider = options.tlsProvider || null;
|
|
436
|
+
this.proxyAuthHelper = options.proxyAuthHelper || null;
|
|
437
|
+
if (this.tlsProvider !== null && typeof this.tlsProvider !== "function") {
|
|
438
|
+
throw gatewayError(
|
|
439
|
+
"CC_GATEWAY_OPTIONS_INVALID",
|
|
440
|
+
"gateway TLS provider is invalid",
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
if (
|
|
444
|
+
this.proxyAuthHelper !== null &&
|
|
445
|
+
typeof this.proxyAuthHelper !== "function"
|
|
446
|
+
) {
|
|
447
|
+
throw gatewayError(
|
|
448
|
+
"CC_GATEWAY_OPTIONS_INVALID",
|
|
449
|
+
"gateway proxy helper is invalid",
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
this.deps = {
|
|
453
|
+
setTimeout: options.setTimeout || setTimeout,
|
|
454
|
+
clearTimeout: options.clearTimeout || clearTimeout,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
async _request(path, options = {}) {
|
|
459
|
+
const relativePath = safeRelativePath(path);
|
|
460
|
+
const prefix = this.baseUrl.pathname.replace(/\/+$/u, "");
|
|
461
|
+
const endpoint = new URL(`${prefix}${relativePath}`, this.baseUrl.origin);
|
|
462
|
+
if (
|
|
463
|
+
endpoint.origin !== this.baseUrl.origin ||
|
|
464
|
+
(prefix &&
|
|
465
|
+
endpoint.pathname !== prefix &&
|
|
466
|
+
!endpoint.pathname.startsWith(`${prefix}/`))
|
|
467
|
+
) {
|
|
468
|
+
throw gatewayError(
|
|
469
|
+
"CC_GATEWAY_PATH_INVALID",
|
|
470
|
+
"gateway request path is invalid",
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
const headers = safeHeaders(options.headers);
|
|
474
|
+
headers.Accept = "text/event-stream";
|
|
475
|
+
if (options.lastEventId)
|
|
476
|
+
headers["Last-Event-ID"] = safeEventId(options.lastEventId);
|
|
477
|
+
|
|
478
|
+
let tls = null;
|
|
479
|
+
let tlsDispatcher = null;
|
|
480
|
+
try {
|
|
481
|
+
tls = this.tlsProvider
|
|
482
|
+
? await this.tlsProvider({ origin: endpoint.origin })
|
|
483
|
+
: null;
|
|
484
|
+
if (tls != null) tlsDispatcher = transportDispatcher(tls);
|
|
485
|
+
} catch {
|
|
486
|
+
throw gatewayError(
|
|
487
|
+
"CC_GATEWAY_TLS_UNAVAILABLE",
|
|
488
|
+
"gateway TLS material is unavailable",
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
let proxyDispatcher = null;
|
|
493
|
+
if (this.proxyAuthHelper) {
|
|
494
|
+
try {
|
|
495
|
+
const transport = await this.proxyAuthHelper({
|
|
496
|
+
origin: endpoint.origin,
|
|
497
|
+
tls,
|
|
498
|
+
});
|
|
499
|
+
proxyDispatcher = transportDispatcher(transport);
|
|
500
|
+
} catch {
|
|
501
|
+
throw gatewayError(
|
|
502
|
+
"CC_GATEWAY_PROXY_AUTH_UNAVAILABLE",
|
|
503
|
+
"gateway proxy authentication is unavailable",
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
try {
|
|
509
|
+
return await this.fetch(endpoint.href, {
|
|
510
|
+
method: "GET",
|
|
511
|
+
headers,
|
|
512
|
+
signal: options.signal,
|
|
513
|
+
...(proxyDispatcher || tlsDispatcher
|
|
514
|
+
? { dispatcher: proxyDispatcher || tlsDispatcher }
|
|
515
|
+
: {}),
|
|
516
|
+
});
|
|
517
|
+
} catch (error) {
|
|
518
|
+
throw normalizeGatewayError(error);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
async stream(path, options = {}) {
|
|
523
|
+
const idleTimeoutMs = positiveInteger(
|
|
524
|
+
options.idleTimeoutMs,
|
|
525
|
+
30_000,
|
|
526
|
+
MIN_IDLE_TIMEOUT_MS,
|
|
527
|
+
MAX_IDLE_TIMEOUT_MS,
|
|
528
|
+
);
|
|
529
|
+
const maxReconnects = positiveInteger(
|
|
530
|
+
options.maxReconnects,
|
|
531
|
+
2,
|
|
532
|
+
0,
|
|
533
|
+
MAX_RECONNECT_ATTEMPTS,
|
|
534
|
+
);
|
|
535
|
+
let lastEventId = options.lastEventId
|
|
536
|
+
? safeEventId(options.lastEventId)
|
|
537
|
+
: "";
|
|
538
|
+
let reconnects = 0;
|
|
539
|
+
for (;;) {
|
|
540
|
+
try {
|
|
541
|
+
const response = await this._request(path, {
|
|
542
|
+
headers: options.headers,
|
|
543
|
+
signal: options.signal,
|
|
544
|
+
lastEventId,
|
|
545
|
+
});
|
|
546
|
+
const result = await consumeSse(
|
|
547
|
+
response,
|
|
548
|
+
{
|
|
549
|
+
idleTimeoutMs,
|
|
550
|
+
lastEventId,
|
|
551
|
+
onEvent: options.onEvent,
|
|
552
|
+
onKeepalive: options.onKeepalive,
|
|
553
|
+
},
|
|
554
|
+
this.deps,
|
|
555
|
+
);
|
|
556
|
+
return Object.freeze({ ...result, reconnects });
|
|
557
|
+
} catch (error) {
|
|
558
|
+
const normalized = error?.code ? error : normalizeGatewayError(error);
|
|
559
|
+
if (normalized.lastEventId)
|
|
560
|
+
lastEventId = safeEventId(normalized.lastEventId);
|
|
561
|
+
if (
|
|
562
|
+
!isRetryableGatewayError(normalized) ||
|
|
563
|
+
reconnects >= maxReconnects
|
|
564
|
+
) {
|
|
565
|
+
throw normalized;
|
|
566
|
+
}
|
|
567
|
+
reconnects += 1;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|