machine-bridge-mcp 2.0.0 → 3.0.0-beta.102
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/CHANGELOG.md +1142 -0
- package/CONTRIBUTING.md +23 -25
- package/GOVERNANCE.md +2 -2
- package/README.md +58 -20
- package/SECURITY.md +161 -120
- package/SUPPORT.md +1 -1
- package/browser-extension/broker-auth.js +83 -0
- package/browser-extension/broker-liveness.js +35 -0
- package/browser-extension/browser-error-boundary.js +96 -0
- package/browser-extension/browser-operations.js +1456 -101
- package/browser-extension/devtools-input.js +195 -34
- package/browser-extension/devtools-observation.js +429 -0
- package/browser-extension/devtools-session.js +48 -0
- package/browser-extension/manifest.json +4 -3
- package/browser-extension/page-automation.js +578 -118
- package/browser-extension/pairing-bootstrap.js +42 -0
- package/browser-extension/pairing.js +28 -12
- package/browser-extension/service-worker.js +126 -106
- package/docs/AGENT_CONTEXT.md +13 -10
- package/docs/ARCHITECTURE.md +159 -67
- package/docs/AUDIT.md +1302 -4
- package/docs/CLIENTS.md +19 -6
- package/docs/COMPUTER_USE.md +297 -0
- package/docs/ENGINEERING.md +72 -13
- package/docs/GETTING_STARTED.md +18 -20
- package/docs/LOCAL_AUTHORIZATION.md +115 -72
- package/docs/LOCAL_AUTOMATION.md +27 -21
- package/docs/LOGGING.md +40 -19
- package/docs/MANAGED_JOBS.md +37 -34
- package/docs/MULTI_ACCOUNT.md +98 -37
- package/docs/OPERATIONS.md +145 -51
- package/docs/OVERVIEW.md +14 -11
- package/docs/POLICY_REFERENCE.md +71 -3
- package/docs/PRIVACY.md +41 -2
- package/docs/PROJECT_STANDARDS.md +29 -6
- package/docs/RELEASING.md +238 -64
- package/docs/TESTING.md +150 -39
- package/docs/THREAT_MODEL.md +164 -67
- package/docs/TOOL_REFERENCE.md +653 -60
- package/docs/UPGRADING.md +78 -18
- package/native/macos/MachineBridgeBackgroundInput.swift +1051 -0
- package/native/macos/MachineBridgeBackgroundInputSmokeFixture.swift +295 -0
- package/native/macos/MachineBridgeTrustBroker.swift +233 -0
- package/package.json +94 -16
- package/scripts/accepted-candidate-tarball.mjs +77 -0
- package/scripts/candidate-runtime-store.mjs +93 -0
- package/scripts/check-plan.mjs +73 -3
- package/scripts/check-runner.mjs +129 -17
- package/scripts/commit-message-check.mjs +5 -2
- package/scripts/consumer-package-security.mjs +293 -0
- package/scripts/coverage-check.mjs +251 -15
- package/scripts/coverage-generation.mjs +41 -0
- package/scripts/coverage-range-merge.mjs +41 -0
- package/scripts/foreground-daemon-recovery.mjs +88 -0
- package/scripts/generate-policy-reference.mjs +28 -5
- package/scripts/generate-tool-reference.mjs +1 -1
- package/scripts/generate-worker-types.mjs +21 -11
- package/scripts/github-backlog.mjs +11 -10
- package/scripts/github-push.mjs +62 -16
- package/scripts/github-release-asset.mjs +58 -0
- package/scripts/github-release.mjs +241 -132
- package/scripts/global-package-installation.mjs +68 -0
- package/scripts/hardened-npm-session.mjs +42 -0
- package/scripts/install-published-prerelease.mjs +198 -0
- package/scripts/local-release-acceptance.mjs +103 -45
- package/scripts/macos-background-input-smoke.mjs +515 -0
- package/scripts/network-retry.mjs +8 -1
- package/scripts/npm-global-prefix.mjs +27 -0
- package/scripts/npm-publication-policy.mjs +17 -0
- package/scripts/official-mcp-conformance.mjs +267 -0
- package/scripts/persistent-activation-process.mjs +101 -0
- package/scripts/prepare-pinned-npm.mjs +12 -69
- package/scripts/prerelease-activation.mjs +160 -0
- package/scripts/privacy-check.mjs +14 -6
- package/scripts/promotion-digest.mjs +128 -0
- package/scripts/publish-npm.mjs +260 -0
- package/scripts/published-release.mjs +132 -0
- package/scripts/release-acceptance.mjs +62 -54
- package/scripts/release-browser-extension-store.mjs +172 -0
- package/scripts/release-candidate-manifest.mjs +52 -0
- package/scripts/release-channel.mjs +106 -0
- package/scripts/release-diagnostic.mjs +41 -0
- package/scripts/release-impact-check.mjs +12 -22
- package/scripts/release-oauth-canary-core.mjs +325 -0
- package/scripts/release-oauth-canary-evidence.mjs +100 -0
- package/scripts/release-oauth-canary.mjs +127 -0
- package/scripts/release-publication-guard.mjs +67 -0
- package/scripts/release-soak.mjs +284 -0
- package/scripts/release-state.mjs +1 -1
- package/scripts/run-checks.mjs +44 -7
- package/scripts/run-worker-dry-run.mjs +21 -0
- package/scripts/sarif-security-gate.mjs +2 -1
- package/scripts/sbom-check.mjs +106 -0
- package/scripts/start-release-candidate.mjs +267 -44
- package/scripts/syntax-check.mjs +36 -14
- package/scripts/verification-generation-guard.mjs +15 -0
- package/scripts/verification-idle-sleep-guard.mjs +37 -0
- package/scripts/verification-state.mjs +108 -0
- package/scripts/wrangler-command-lifecycle.mjs +115 -0
- package/src/local/account-access.mjs +13 -1
- package/src/local/account-admin-response.mjs +92 -0
- package/src/local/account-admin.mjs +52 -19
- package/src/local/agent-context-projection.mjs +26 -7
- package/src/local/agent-context.mjs +88 -49
- package/src/local/agent-skill-discovery.mjs +16 -5
- package/src/local/app-automation-macos-jxa.mjs +350 -0
- package/src/local/app-automation.mjs +873 -163
- package/src/local/application-capability-projection.mjs +24 -0
- package/src/local/authority-context.mjs +106 -0
- package/src/local/autostart-log-maintenance.mjs +36 -0
- package/src/local/browser-bridge-http.mjs +5 -3
- package/src/local/browser-bridge.mjs +109 -31
- package/src/local/browser-broker-auth-http.mjs +67 -0
- package/src/local/browser-broker-auth.mjs +98 -0
- package/src/local/browser-broker-routes.mjs +63 -20
- package/src/local/browser-broker-server.mjs +17 -7
- package/src/local/browser-command.mjs +79 -30
- package/src/local/browser-computer-observation-service.mjs +266 -0
- package/src/local/browser-extension-identity.mjs +50 -0
- package/src/local/browser-extension-path.mjs +22 -0
- package/src/local/browser-extension-protocol.mjs +50 -11
- package/src/local/browser-operation-service.mjs +147 -117
- package/src/local/browser-pairing-grant.mjs +109 -0
- package/src/local/browser-pairing-http.mjs +36 -0
- package/src/local/browser-pairing-launch.mjs +58 -0
- package/src/local/browser-pairing-store.mjs +85 -48
- package/src/local/browser-request-registry.mjs +40 -14
- package/src/local/browser-request-settlement.mjs +78 -0
- package/src/local/browser-resource-input.mjs +74 -0
- package/src/local/browser-trusted-input-health.mjs +63 -0
- package/src/local/call-authority.mjs +21 -0
- package/src/local/call-capacity.mjs +45 -0
- package/src/local/call-registry-drain.mjs +54 -0
- package/src/local/call-registry.mjs +30 -15
- package/src/local/capability-observer.mjs +5 -0
- package/src/local/capability-ranking.mjs +3 -2
- package/src/local/child-process-settlement.mjs +132 -0
- package/src/local/cli-account-admin.mjs +40 -6
- package/src/local/cli-activate.mjs +162 -0
- package/src/local/cli-local-admin.mjs +20 -45
- package/src/local/cli-options.mjs +12 -13
- package/src/local/cli-ready-output.mjs +52 -0
- package/src/local/cli-service.mjs +91 -11
- package/src/local/cli.mjs +232 -150
- package/src/local/computer-use-application-observation.mjs +224 -0
- package/src/local/computer-use-arguments.mjs +270 -0
- package/src/local/computer-use-deadline.mjs +50 -0
- package/src/local/computer-use-dispatch-settlement.mjs +40 -0
- package/src/local/computer-use-expectation.mjs +171 -0
- package/src/local/computer-use-observation.mjs +769 -0
- package/src/local/computer-use-recovery.mjs +82 -0
- package/src/local/computer-use-result-budget.mjs +145 -0
- package/src/local/computer-use-snapshot-store.mjs +67 -0
- package/src/local/computer-use.mjs +2255 -0
- package/src/local/daemon-http-relay-auth.mjs +26 -0
- package/src/local/daemon-http-relay-connection.mjs +298 -0
- package/src/local/daemon-http-relay-request.mjs +64 -0
- package/src/local/daemon-http-relay-sequence.mjs +49 -0
- package/src/local/daemon-process.mjs +40 -7
- package/src/local/delegated-process-sandbox.mjs +148 -0
- package/src/local/device-identity.mjs +156 -49
- package/src/local/device-root-provider.mjs +81 -0
- package/src/local/directory-metadata.mjs +47 -0
- package/src/local/doctor-reporting.mjs +23 -0
- package/src/local/durable-process-spec.mjs +80 -0
- package/src/local/errors.mjs +25 -15
- package/src/local/exclusive-file.mjs +74 -26
- package/src/local/execution-limits.mjs +9 -0
- package/src/local/execution-routing.mjs +276 -0
- package/src/local/execution-surface.mjs +23 -0
- package/src/local/file-mutation-coordinator.mjs +47 -0
- package/src/local/file-snapshot-preservation.mjs +24 -0
- package/src/local/filesystem-identity.mjs +54 -0
- package/src/local/fixed-process-environment.mjs +21 -0
- package/src/local/full-access-test.mjs +31 -13
- package/src/local/git-commit.mjs +69 -0
- package/src/local/git-config-safety.mjs +55 -0
- package/src/local/git-log-parser.mjs +41 -0
- package/src/local/git-metadata-boundary.mjs +77 -0
- package/src/local/git-metadata-tree-safety.mjs +59 -0
- package/src/local/git-operation-state.mjs +19 -0
- package/src/local/git-service.mjs +165 -30
- package/src/local/hardened-npm-download-timeout.mjs +46 -0
- package/src/local/hardened-npm-download.mjs +109 -0
- package/src/local/hardened-npm-extract.mjs +20 -0
- package/src/local/hardened-npm-verification.mjs +104 -0
- package/src/local/hardened-npm.mjs +199 -0
- package/src/local/job-runner.mjs +244 -89
- package/src/local/lifecycle.mjs +6 -0
- package/src/local/log.mjs +19 -45
- package/src/local/macos-background-input.mjs +398 -0
- package/src/local/macos-idle-sleep-assertion.mjs +65 -0
- package/src/local/macos-trust-broker.mjs +404 -0
- package/src/local/managed-job-cancellation.mjs +42 -0
- package/src/local/managed-job-capacity.mjs +16 -0
- package/src/local/managed-job-directory-generation.mjs +60 -0
- package/src/local/managed-job-directory.mjs +75 -0
- package/src/local/managed-job-durable-process.mjs +49 -0
- package/src/local/managed-job-lock.mjs +46 -14
- package/src/local/managed-job-plan.mjs +38 -14
- package/src/local/managed-job-projection.mjs +23 -15
- package/src/local/managed-job-retention.mjs +128 -0
- package/src/local/managed-job-runner-claim.mjs +70 -0
- package/src/local/managed-job-runner.mjs +35 -10
- package/src/local/managed-job-storage.mjs +22 -6
- package/src/local/managed-job-terminal-maintenance.mjs +40 -0
- package/src/local/managed-job-terminal.mjs +150 -0
- package/src/local/managed-jobs.mjs +424 -287
- package/src/local/npm-cli.mjs +42 -0
- package/src/local/npm-environment.mjs +37 -0
- package/src/local/operation-authorization.mjs +68 -324
- package/src/local/operation-risk.mjs +51 -4
- package/src/local/owner-state-lock.mjs +118 -0
- package/src/local/package-identity.mjs +9 -0
- package/src/local/patch.mjs +20 -18
- package/src/local/path-inspection.mjs +23 -0
- package/src/local/policy.mjs +19 -0
- package/src/local/private-toolchain-integrity.mjs +54 -0
- package/src/local/process-execution.mjs +87 -75
- package/src/local/process-foreground-timeout.mjs +50 -0
- package/src/local/process-identity.mjs +41 -7
- package/src/local/process-nonreplayable-settlement.mjs +50 -0
- package/src/local/process-result-projection.mjs +22 -0
- package/src/local/process-session-events.mjs +47 -0
- package/src/local/process-session-remote-activity.mjs +10 -0
- package/src/local/process-session-termination.mjs +73 -0
- package/src/local/process-sessions.mjs +144 -112
- package/src/local/process-tracker.mjs +140 -13
- package/src/local/process-tree-ownership-types.d.ts +37 -0
- package/src/local/process-tree-ownership.mjs +57 -0
- package/src/local/process-tree-signal.mjs +34 -0
- package/src/local/process-tree-snapshot.mjs +77 -0
- package/src/local/process-tree-supervisor.mjs +69 -0
- package/src/local/process-tree.mjs +11 -53
- package/src/local/project-metadata.mjs +19 -6
- package/src/local/relay-call-recovery.mjs +58 -38
- package/src/local/relay-connection-classification.mjs +131 -0
- package/src/local/relay-connection-support.mjs +91 -0
- package/src/local/relay-connection.mjs +179 -272
- package/src/local/relay-diagnostics.mjs +91 -0
- package/src/local/relay-heartbeat.mjs +119 -0
- package/src/local/relay-liveness.mjs +109 -0
- package/src/local/relay-peer-diagnostics.mjs +29 -0
- package/src/local/release-runtime-lock.mjs +18 -0
- package/src/local/remote-activity-idle-sleep-guard.mjs +77 -0
- package/src/local/remote-configuration.mjs +48 -0
- package/src/local/resilient-relay-connection.mjs +178 -0
- package/src/local/resource-admission-diagnostics.mjs +52 -0
- package/src/local/resource-admission-policy.mjs +115 -0
- package/src/local/resource-admission.mjs +339 -0
- package/src/local/resource-build-root.mjs +83 -0
- package/src/local/resource-cargo-concurrency.mjs +38 -0
- package/src/local/resource-cmake-concurrency.mjs +42 -0
- package/src/local/resource-command-concurrency.mjs +37 -0
- package/src/local/resource-command-profile.mjs +176 -0
- package/src/local/resource-coordinator-accounting.mjs +21 -0
- package/src/local/resource-cpu-window.mjs +20 -0
- package/src/local/resource-disk-headroom.mjs +13 -0
- package/src/local/resource-elastic-memory.mjs +19 -0
- package/src/local/resource-elastic-request.mjs +36 -0
- package/src/local/resource-foreground-wait.mjs +29 -0
- package/src/local/resource-go-concurrency.mjs +43 -0
- package/src/local/resource-gradle-concurrency.mjs +57 -0
- package/src/local/resource-host-cache.mjs +39 -0
- package/src/local/resource-host-darwin.mjs +54 -0
- package/src/local/resource-host-linux.mjs +35 -0
- package/src/local/resource-host-sample-file.mjs +23 -0
- package/src/local/resource-host-snapshot.mjs +99 -0
- package/src/local/resource-lease-accounting.mjs +119 -0
- package/src/local/resource-light-command.mjs +6 -0
- package/src/local/resource-make-concurrency.mjs +42 -0
- package/src/local/resource-maven-concurrency.mjs +38 -0
- package/src/local/resource-ninja-command-concurrency.mjs +20 -0
- package/src/local/resource-ninja-concurrency.mjs +28 -0
- package/src/local/resource-operations.mjs +26 -10
- package/src/local/resource-pressure.mjs +67 -0
- package/src/local/resource-probe-command.mjs +35 -0
- package/src/local/resource-process-admission.mjs +59 -0
- package/src/local/resource-process-ancestry-cache.mjs +43 -0
- package/src/local/resource-process-ancestry.mjs +32 -0
- package/src/local/resource-process-priority.mjs +15 -0
- package/src/local/resource-project-key.mjs +53 -0
- package/src/local/resource-pytest-concurrency.mjs +33 -0
- package/src/local/resource-release-control-classification.mjs +14 -0
- package/src/local/resource-release-control-executable.mjs +70 -0
- package/src/local/resource-release-control-workspace.mjs +41 -0
- package/src/local/resource-request-contract.mjs +30 -0
- package/src/local/resource-script-classification.mjs +44 -0
- package/src/local/resource-shell-analysis.mjs +58 -0
- package/src/local/resource-staging-recovery.mjs +97 -0
- package/src/local/resource-swift-concurrency.mjs +31 -0
- package/src/local/resource-transaction-lock.mjs +189 -0
- package/src/local/resource-wait.mjs +48 -0
- package/src/local/resource-waiters.mjs +139 -0
- package/src/local/resource-xcode-command.mjs +7 -0
- package/src/local/resource-xcode-concurrency.mjs +34 -0
- package/src/local/resource-xcode-non-build.mjs +19 -0
- package/src/local/runtime-activation.mjs +700 -0
- package/src/local/runtime-activity-projection.mjs +48 -0
- package/src/local/runtime-capabilities.mjs +48 -15
- package/src/local/runtime-diagnostic-state.mjs +14 -0
- package/src/local/runtime-diagnostics.mjs +40 -13
- package/src/local/runtime-info-projection.mjs +60 -0
- package/src/local/runtime-info-relay-projection.mjs +21 -0
- package/src/local/runtime-paths.mjs +2 -1
- package/src/local/runtime-process-routing.mjs +33 -0
- package/src/local/runtime-relay-connection-options.mjs +44 -0
- package/src/local/runtime-relay-control.mjs +98 -0
- package/src/local/runtime-relay.mjs +26 -50
- package/src/local/runtime-reporting.mjs +37 -21
- package/src/local/runtime-resource-service.mjs +77 -0
- package/src/local/runtime-tool-handlers.mjs +13 -10
- package/src/local/runtime.mjs +244 -208
- package/src/local/secure-file.mjs +73 -20
- package/src/local/security-audit-dispatch.mjs +35 -0
- package/src/local/security-audit-log.mjs +215 -0
- package/src/local/security-audit-state.mjs +139 -0
- package/src/local/security-audit-storage.mjs +135 -0
- package/src/local/security-audit-warning.mjs +31 -0
- package/src/local/security-audit-worker.mjs +87 -0
- package/src/local/service-convergence.mjs +42 -1
- package/src/local/service-definition.mjs +24 -0
- package/src/local/service-environment.mjs +18 -10
- package/src/local/service-owner.mjs +146 -0
- package/src/local/service-ownership.mjs +17 -0
- package/src/local/service-restart-handoff.mjs +64 -0
- package/src/local/service-restart-scheduler.mjs +49 -0
- package/src/local/service-runtime-convergence.mjs +52 -0
- package/src/local/service-runtime.mjs +105 -0
- package/src/local/service-status.mjs +60 -0
- package/src/local/service.mjs +385 -92
- package/src/local/shell.mjs +39 -19
- package/src/local/ssh-key.mjs +158 -42
- package/src/local/state-inventory.mjs +50 -24
- package/src/local/state-owner-lock-inventory.mjs +23 -0
- package/src/local/state-root-owned-namespaces.mjs +83 -0
- package/src/local/state-root-retirement.mjs +89 -0
- package/src/local/state.mjs +386 -96
- package/src/local/stdio.mjs +96 -66
- package/src/local/support-state-projection.mjs +30 -0
- package/src/local/system-network-route.mjs +76 -0
- package/src/local/systemd-removal.mjs +20 -0
- package/src/local/tool-executor.mjs +85 -20
- package/src/local/tool-result-boundary.mjs +51 -0
- package/src/local/toolchain-operation-lock.mjs +41 -0
- package/src/local/tools.mjs +7 -5
- package/src/local/trusted-executable.mjs +53 -0
- package/src/local/trusted-git-executable.mjs +34 -0
- package/src/local/trusted-github-cli.mjs +24 -0
- package/src/local/windows-service-convergence.mjs +49 -0
- package/src/local/windows-service.mjs +52 -45
- package/src/local/worker-deployment-fingerprint.mjs +102 -0
- package/src/local/worker-deployment.mjs +48 -60
- package/src/local/worker-health.mjs +12 -4
- package/src/local/worker-secret-file.mjs +37 -17
- package/src/local/workspace-file-service.mjs +156 -239
- package/src/local/workspace-file-transaction.mjs +189 -0
- package/src/local/workspace-search.mjs +55 -0
- package/src/local/wrangler-toolchain/package-lock.json +1505 -0
- package/src/local/wrangler-toolchain/package.json +23 -0
- package/src/local/wrangler-toolchain-verification.mjs +135 -0
- package/src/local/wrangler-toolchain.mjs +188 -0
- package/src/shared/access-contract.json +7 -1
- package/src/shared/activation-recovery.mjs +49 -0
- package/src/shared/admin-auth.d.mts +2 -1
- package/src/shared/admin-auth.mjs +3 -3
- package/src/shared/authority-revocation.d.mts +9 -0
- package/src/shared/authority-revocation.mjs +28 -0
- package/src/shared/daemon-auth.d.mts +11 -0
- package/src/shared/daemon-auth.mjs +17 -0
- package/src/shared/device-session-auth.d.mts +20 -0
- package/src/shared/device-session-auth.mjs +51 -0
- package/src/shared/foreground-timeout.d.mts +15 -0
- package/src/shared/foreground-timeout.mjs +70 -0
- package/src/shared/log-redaction.d.mts +9 -0
- package/src/shared/log-redaction.mjs +51 -0
- package/src/shared/mcp-protocol.d.mts +27 -0
- package/src/shared/mcp-protocol.mjs +275 -0
- package/src/shared/project-overview-projection.d.mts +4 -0
- package/src/shared/project-overview-projection.mjs +96 -0
- package/src/shared/relay-contract.json +28 -0
- package/src/shared/result-projection.d.mts +2 -1
- package/src/shared/result-projection.mjs +13 -2
- package/src/shared/server-metadata.json +15 -9
- package/src/shared/tool-argument-validation.d.mts +17 -0
- package/src/shared/tool-argument-validation.mjs +325 -0
- package/src/shared/tool-call-capacity.d.mts +41 -0
- package/src/shared/tool-call-capacity.mjs +59 -0
- package/src/shared/tool-catalog.json +626 -57
- package/src/worker/access.ts +12 -0
- package/src/worker/account-admin.ts +55 -33
- package/src/worker/authority-revocations.ts +174 -0
- package/src/worker/authority.ts +52 -6
- package/src/worker/daemon-auth.ts +49 -68
- package/src/worker/daemon-channel.ts +36 -0
- package/src/worker/daemon-http-auth.ts +63 -0
- package/src/worker/daemon-http-channel.ts +120 -0
- package/src/worker/daemon-http-controller.ts +163 -0
- package/src/worker/daemon-http-protocol.ts +62 -0
- package/src/worker/daemon-http-registry.ts +82 -0
- package/src/worker/daemon-last-observation.ts +30 -0
- package/src/worker/daemon-liveness.ts +8 -6
- package/src/worker/daemon-ready-dispatch.ts +17 -0
- package/src/worker/daemon-ready-messages.ts +73 -0
- package/src/worker/daemon-ready-waiters.ts +79 -0
- package/src/worker/daemon-recovery-budget.ts +13 -0
- package/src/worker/daemon-registry.ts +58 -0
- package/src/worker/daemon-relay-diagnostics.ts +127 -0
- package/src/worker/daemon-socket-attachment.ts +55 -0
- package/src/worker/daemon-sockets.ts +72 -62
- package/src/worker/daemon-status.ts +38 -0
- package/src/worker/device-session-verifier.ts +129 -0
- package/src/worker/dpop.ts +157 -0
- package/src/worker/durable-process-timeout.ts +29 -0
- package/src/worker/errors.ts +57 -0
- package/src/worker/http.ts +112 -26
- package/src/worker/index.ts +446 -440
- package/src/worker/mcp-access.ts +57 -0
- package/src/worker/mcp-controller.ts +188 -0
- package/src/worker/mcp-http-accept.ts +11 -0
- package/src/worker/mcp-http-contract.ts +322 -0
- package/src/worker/mcp-initialization-compat.ts +141 -0
- package/src/worker/mcp-jsonrpc.ts +11 -29
- package/src/worker/mcp-removed-protocol.ts +17 -0
- package/src/worker/mcp-response-proxy.ts +135 -0
- package/src/worker/mcp-response-stream.ts +65 -0
- package/src/worker/mcp-stale-schema-compat.ts +57 -0
- package/src/worker/mcp-stream-proxy-contract.ts +48 -0
- package/src/worker/mcp-tool-call-input.ts +23 -0
- package/src/worker/nonce-store.ts +33 -35
- package/src/worker/oauth-authorization-page.ts +21 -1
- package/src/worker/oauth-client-admin.ts +52 -0
- package/src/worker/oauth-client-contract.ts +38 -0
- package/src/worker/oauth-controller.ts +107 -47
- package/src/worker/oauth-field-contract.ts +31 -0
- package/src/worker/oauth-record-contract.ts +7 -0
- package/src/worker/oauth-refresh-authority.ts +27 -0
- package/src/worker/oauth-refresh-exchange.ts +162 -0
- package/src/worker/oauth-refresh-families.ts +130 -27
- package/src/worker/oauth-refresh-persistence.ts +116 -0
- package/src/worker/oauth-state.ts +33 -18
- package/src/worker/oauth-store-validation.ts +131 -0
- package/src/worker/oauth-token-derivation.ts +33 -0
- package/src/worker/oauth-token-issuance.ts +119 -0
- package/src/worker/oauth-tokens.ts +56 -190
- package/src/worker/observability.ts +38 -5
- package/src/worker/pending-admission.ts +15 -0
- package/src/worker/pending-call-capacity.ts +108 -0
- package/src/worker/pending-call-contract.ts +42 -5
- package/src/worker/pending-call-deadlines.ts +75 -0
- package/src/worker/pending-calls.ts +131 -125
- package/src/worker/runtime-alarm-storage.ts +37 -0
- package/src/worker/runtime-alarm.ts +136 -0
- package/src/worker/server-info-activity.ts +36 -0
- package/src/worker/server-info-tool-delivery.ts +15 -0
- package/src/worker/server-info.ts +168 -0
- package/src/worker/tool-catalog.ts +47 -1
- package/src/worker/tool-timeout.ts +75 -14
- package/src/worker/websocket-protocol.ts +26 -2
- package/src/worker/worker-edge-guard.ts +90 -0
- package/src/worker/worker-edge-log.ts +65 -0
- package/src/worker/worker-entry.ts +52 -0
- package/src/worker/worker-mcp-config.ts +23 -0
- package/src/worker/worker-metadata.ts +48 -0
- package/src/worker/worker-rate-limit-key.ts +30 -0
- package/src/worker/worker-runtime-config.ts +20 -0
- package/src/worker/worker-static-routes.ts +54 -0
- package/src/worker/worker-task-lifetime.ts +18 -0
- package/tsconfig.local.json +17 -1
- package/wrangler.jsonc +13 -0
- package/src/local/cli-approval.mjs +0 -117
- package/src/local/operation-state-lock.mjs +0 -92
- package/src/worker/mcp-session.ts +0 -72
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
import WebSocket from "ws";
|
|
2
|
+
import { performance } from "node:perf_hooks";
|
|
2
3
|
import { classifyOperationalError } from "./log.mjs";
|
|
3
4
|
import { proxyAgentForWebSocket } from "./network-proxy.mjs";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
import { RelayLiveness } from "./relay-liveness.mjs";
|
|
6
|
+
import {
|
|
7
|
+
boundedPositiveInteger, classifyRelayTransportError, formatAttempts, formatDuration, normalizeWorkerUrl, redactUrl,
|
|
8
|
+
relayHttpStatusFromError, sanitizeCloseReason, terminateSocket, tracedTlsConnection,
|
|
9
|
+
} from "./relay-connection-support.mjs";
|
|
10
|
+
import {
|
|
11
|
+
APPLICATION_PROXY_ROUTE_SCOPE, preferredRelayCloseCategory, relayOutageFields, relayRecoveryFields, relayStatusSnapshot,
|
|
12
|
+
} from "./relay-diagnostics.mjs";
|
|
13
|
+
import {
|
|
14
|
+
acknowledgementMismatch, isSupersededClose, readinessMismatch, reconnectDelay, relayCloseCategory, relayCloseUserCause,
|
|
15
|
+
relayFatalMessage, relayOutageUserAction, relayServerErrorReconnectCategory, sanitizeProtocolErrorCode, welcomeMismatch,
|
|
16
|
+
} from "./relay-connection-classification.mjs";
|
|
17
|
+
export {
|
|
18
|
+
acknowledgementMismatch, isRelayReadyContext, isSupersededClose, readinessMismatch, reconnectDelay, relayCloseCategory,
|
|
19
|
+
relayOutageUserAction, relayServerErrorReconnectCategory, welcomeMismatch,
|
|
20
|
+
} from "./relay-connection-classification.mjs";
|
|
7
21
|
const DEFAULT_CONNECT_TIMEOUT_MS = 15_000;
|
|
8
22
|
const DEFAULT_HANDSHAKE_TIMEOUT_MS = 10_000;
|
|
9
23
|
const DEFAULT_READINESS_TIMEOUT_MS = 15_000;
|
|
10
24
|
const DEFAULT_OUTAGE_WARN_AFTER_MS = 10_000;
|
|
11
25
|
const DEFAULT_OUTAGE_WARN_REPEAT_MS = 60_000;
|
|
12
26
|
const DEFAULT_OUTAGE_WARN_MAX_REPEAT_MS = 15 * 60_000;
|
|
13
|
-
const MAX_CLOSE_REASON_CHARS = 128;
|
|
14
|
-
const MAX_PROTOCOL_ERROR_CODE_CHARS = 64;
|
|
15
|
-
|
|
16
27
|
const DEFAULT_SCHEDULER = Object.freeze({
|
|
17
28
|
setTimeout: (callback, delay) => setTimeout(callback, delay),
|
|
18
29
|
clearTimeout: (timer) => clearTimeout(timer),
|
|
@@ -35,13 +46,13 @@ export class RelayConnection {
|
|
|
35
46
|
this.onFatal = typeof options.onFatal === "function" ? options.onFatal : () => {};
|
|
36
47
|
this.WebSocketClass = options.WebSocketClass || WebSocket;
|
|
37
48
|
this.scheduler = options.scheduler || DEFAULT_SCHEDULER;
|
|
38
|
-
this.now = typeof options.now === "function" ? options.now :
|
|
49
|
+
this.now = typeof options.now === "function" ? options.now : () => performance.now();
|
|
50
|
+
this.wallNow = typeof options.wallNow === "function" ? options.wallNow : Date.now;
|
|
39
51
|
this.reconnectDelay = typeof options.reconnectDelay === "function" ? options.reconnectDelay : reconnectDelay;
|
|
40
52
|
this.proxyAgentForUrl = typeof options.proxyAgentForUrl === "function" ? options.proxyAgentForUrl : proxyAgentForWebSocket;
|
|
41
53
|
this.networkRoute = "unresolved";
|
|
54
|
+
this.networkRouteScope = APPLICATION_PROXY_ROUTE_SCOPE;
|
|
42
55
|
this.maxPayload = boundedPositiveInteger(options.maxPayload, 8 * 1024 * 1024);
|
|
43
|
-
this.heartbeatIntervalMs = boundedPositiveInteger(options.heartbeatIntervalMs, DEFAULT_HEARTBEAT_INTERVAL_MS);
|
|
44
|
-
this.heartbeatTimeoutMs = boundedPositiveInteger(options.heartbeatTimeoutMs, DEFAULT_HEARTBEAT_TIMEOUT_MS);
|
|
45
56
|
this.connectTimeoutMs = boundedPositiveInteger(options.connectTimeoutMs, DEFAULT_CONNECT_TIMEOUT_MS);
|
|
46
57
|
this.handshakeTimeoutMs = boundedPositiveInteger(options.handshakeTimeoutMs, DEFAULT_HANDSHAKE_TIMEOUT_MS);
|
|
47
58
|
this.readinessTimeoutMs = boundedPositiveInteger(options.readinessTimeoutMs, DEFAULT_READINESS_TIMEOUT_MS);
|
|
@@ -59,40 +70,71 @@ export class RelayConnection {
|
|
|
59
70
|
this.readinessProbeDelivered = false;
|
|
60
71
|
this.hasConnected = false;
|
|
61
72
|
this.connectedAt = 0;
|
|
62
|
-
this.
|
|
73
|
+
this.lastReadyInboundSilenceMs = 0;
|
|
63
74
|
this.reconnectAttempt = 0;
|
|
64
75
|
this.reconnectTimer = null;
|
|
65
76
|
this.connectTimer = null;
|
|
66
|
-
this.heartbeatTimer = null;
|
|
67
77
|
this.handshakeTimer = null;
|
|
68
78
|
this.readinessTimer = null;
|
|
69
79
|
this.outageWarnTimer = null;
|
|
70
80
|
this.outageStartedAt = 0;
|
|
81
|
+
this.outageStartedWallAt = 0;
|
|
71
82
|
this.outageAttempts = 0;
|
|
72
83
|
this.outageNoticeEmitted = false;
|
|
73
84
|
this.outageWarningCount = 0;
|
|
74
85
|
this.lastOutageWarnAt = 0;
|
|
86
|
+
this.outageCount = 0;
|
|
75
87
|
this.lastCloseCategory = "connection_interrupted";
|
|
88
|
+
this.lastCloseCode = 0;
|
|
76
89
|
this.lastTransportErrorClass = "";
|
|
90
|
+
this.lastDisconnectedAt = 0;
|
|
91
|
+
this.lastReadyAt = 0;
|
|
92
|
+
this.lastReadyWallAt = 0;
|
|
93
|
+
this.lastReadyDurationMs = 0;
|
|
94
|
+
this.lastReconnectDelayMs = 0;
|
|
95
|
+
this.nextReconnectAt = 0;
|
|
96
|
+
this.nextReconnectWallAt = 0;
|
|
97
|
+
this.connectAttemptStartedAt = null;
|
|
98
|
+
this.lastConnectDurationMs = 0;
|
|
99
|
+
this.lastConnectStage = "idle";
|
|
100
|
+
this.lastConnectHttpStatus = null;
|
|
77
101
|
this.pendingCloseCategory = "";
|
|
78
102
|
this.connectedOnce = null;
|
|
79
103
|
this.connectedOnceResolve = null;
|
|
80
104
|
this.connectedOnceReject = null;
|
|
81
105
|
this.sessionGeneration = 0;
|
|
82
106
|
this.activeSessionId = 0;
|
|
107
|
+
this.liveness = new RelayLiveness({
|
|
108
|
+
...options,
|
|
109
|
+
scheduler: this.scheduler,
|
|
110
|
+
now: this.now,
|
|
111
|
+
logger: this.logger,
|
|
112
|
+
currentSocket: () => this.socket,
|
|
113
|
+
isActive: () => !this.closed && this.authenticated && this.isSocketOpen(this.socket),
|
|
114
|
+
sendApplicationHeartbeat: (_now, socket) => this.sendOnSocket(socket, { type: "heartbeat", ts: this.wallNow() }),
|
|
115
|
+
onTransportError: (error, socket) => {
|
|
116
|
+
this.lastTransportErrorClass = classifyRelayTransportError(error);
|
|
117
|
+
this.pendingCloseCategory = preferredRelayCloseCategory(this.pendingCloseCategory, "relay_transport_error");
|
|
118
|
+
this.logger.debug?.("remote relay transport ping failed", { error_class: this.lastTransportErrorClass });
|
|
119
|
+
terminateSocket(socket);
|
|
120
|
+
},
|
|
121
|
+
onTransportTimeout: ({ silentForMs, eventLoopLagMs }, socket) => {
|
|
122
|
+
if (!socket) return;
|
|
123
|
+
this.logger.debug?.("remote relay transport pong timed out", { silent_for_ms: silentForMs, event_loop_lag_ms: eventLoopLagMs });
|
|
124
|
+
this.pendingCloseCategory = preferredRelayCloseCategory(this.pendingCloseCategory, "relay_transport_timeout");
|
|
125
|
+
terminateSocket(socket);
|
|
126
|
+
},
|
|
127
|
+
onApplicationTimeout: ({ silentForMs, eventLoopLagMs }, socket) => {
|
|
128
|
+
if (!socket) return;
|
|
129
|
+
this.logger.debug?.("remote relay application heartbeat timed out", { silent_for_ms: silentForMs, event_loop_lag_ms: eventLoopLagMs });
|
|
130
|
+
this.pendingCloseCategory = preferredRelayCloseCategory(this.pendingCloseCategory, "relay_heartbeat_timeout");
|
|
131
|
+
terminateSocket(socket);
|
|
132
|
+
},
|
|
133
|
+
});
|
|
83
134
|
}
|
|
84
135
|
|
|
85
136
|
status() {
|
|
86
|
-
return
|
|
87
|
-
authenticated: this.authenticated,
|
|
88
|
-
ready: this.ready,
|
|
89
|
-
readiness_probe_delivered: this.readinessProbeDelivered,
|
|
90
|
-
closed: this.closed,
|
|
91
|
-
network_route: this.networkRoute,
|
|
92
|
-
reconnect_attempt: this.reconnectAttempt,
|
|
93
|
-
outage_active: this.outageStartedAt > 0,
|
|
94
|
-
session_generation: this.sessionGeneration,
|
|
95
|
-
};
|
|
137
|
+
return relayStatusSnapshot(this, this.now());
|
|
96
138
|
}
|
|
97
139
|
|
|
98
140
|
currentSessionId() {
|
|
@@ -111,12 +153,15 @@ export class RelayConnection {
|
|
|
111
153
|
}
|
|
112
154
|
|
|
113
155
|
stop() {
|
|
156
|
+
const settlePendingStart = this.connectedOnceResolve;
|
|
157
|
+
this.connectedOnceResolve = null;
|
|
158
|
+
this.connectedOnceReject = null;
|
|
114
159
|
this.closed = true;
|
|
115
160
|
this.authenticated = false;
|
|
116
161
|
this.ready = false;
|
|
117
162
|
this.readinessProbeDelivered = false;
|
|
118
163
|
this.activeSessionId = 0;
|
|
119
|
-
this.
|
|
164
|
+
this.liveness.stop();
|
|
120
165
|
this.clearTimer("connectTimer", "clearTimeout");
|
|
121
166
|
this.clearTimer("handshakeTimer", "clearTimeout");
|
|
122
167
|
this.clearTimer("readinessTimer", "clearTimeout");
|
|
@@ -125,10 +170,12 @@ export class RelayConnection {
|
|
|
125
170
|
const socket = this.socket;
|
|
126
171
|
this.socket = null;
|
|
127
172
|
if (socket) {
|
|
128
|
-
try { socket.close(1000, "daemon shutdown"); }
|
|
173
|
+
try { socket.close(1000, "daemon shutdown"); }
|
|
174
|
+
catch { /* Relay shutdown state is already authoritative and the transport may have closed concurrently. */ }
|
|
129
175
|
}
|
|
130
176
|
this.resetOutage();
|
|
131
177
|
this.reconnectAttempt = 0;
|
|
178
|
+
settlePendingStart?.(false);
|
|
132
179
|
}
|
|
133
180
|
|
|
134
181
|
send(value) {
|
|
@@ -140,7 +187,8 @@ export class RelayConnection {
|
|
|
140
187
|
const sessionId = Number(expectedSessionId) || 0;
|
|
141
188
|
if (!sessionId || sessionId !== this.activeSessionId) return { ok: false, reason: "session_ended" };
|
|
142
189
|
if (!this.authenticated || !this.isSocketOpen(this.socket)) return { ok: false, reason: "transport_unavailable" };
|
|
143
|
-
|
|
190
|
+
const preReadyControl = ["relay_probe_result", "resume_calls_ack", "authority_revoke_ack"].includes(String(value?.type || ""));
|
|
191
|
+
if (!this.ready && !preReadyControl) return { ok: false, reason: "transport_unavailable" };
|
|
144
192
|
if (!this.sendOnSocket(this.socket, value)) return { ok: false, reason: "send_failed" };
|
|
145
193
|
if (value?.type === "relay_probe_result") this.readinessProbeDelivered = true;
|
|
146
194
|
return { ok: true, reason: "sent" };
|
|
@@ -148,7 +196,7 @@ export class RelayConnection {
|
|
|
148
196
|
|
|
149
197
|
interrupt(category = "relay_transport_error") {
|
|
150
198
|
if (this.closed || !this.socket) return false;
|
|
151
|
-
this.pendingCloseCategory =
|
|
199
|
+
this.pendingCloseCategory = preferredRelayCloseCategory(this.pendingCloseCategory, category);
|
|
152
200
|
terminateSocket(this.socket);
|
|
153
201
|
return true;
|
|
154
202
|
}
|
|
@@ -163,16 +211,16 @@ export class RelayConnection {
|
|
|
163
211
|
return false;
|
|
164
212
|
}
|
|
165
213
|
this.logger.debug?.("remote relay welcome received");
|
|
166
|
-
Promise.resolve(this.helloMessage(message)).then((hello) => {
|
|
214
|
+
Promise.resolve(this.helloMessage(message, this.status())).then((hello) => {
|
|
167
215
|
if (this.socket !== socket || this.closed || this.authenticated) return;
|
|
168
|
-
|
|
216
|
+
this.sendOnSocket(socket, hello);
|
|
169
217
|
}).catch((error) => {
|
|
218
|
+
if (this.socket !== socket || this.closed || this.authenticated) return;
|
|
170
219
|
this.logger.debug?.("could not create daemon authentication proof", { error_class: classifyOperationalError(error) });
|
|
171
220
|
this.failPermanently("relay_authentication_failed");
|
|
172
221
|
});
|
|
173
222
|
return true;
|
|
174
223
|
}
|
|
175
|
-
|
|
176
224
|
acknowledge(message = {}) {
|
|
177
225
|
const socket = this.socket;
|
|
178
226
|
if (this.closed || this.authenticated || !this.isSocketOpen(socket)) return false;
|
|
@@ -188,13 +236,12 @@ export class RelayConnection {
|
|
|
188
236
|
this.activeSessionId = this.sessionGeneration;
|
|
189
237
|
this.clearTimer("handshakeTimer", "clearTimeout");
|
|
190
238
|
this.connectedAt = this.now();
|
|
191
|
-
this.
|
|
192
|
-
this.startHeartbeat();
|
|
239
|
+
this.liveness.start();
|
|
193
240
|
this.clearTimer("readinessTimer", "clearTimeout");
|
|
194
241
|
this.readinessTimer = this.scheduler.setTimeout(() => {
|
|
195
242
|
if (this.socket !== socket || this.closed || this.ready) return;
|
|
196
243
|
this.logger.debug?.("remote relay end-to-end readiness probe timed out", { timeout_ms: this.readinessTimeoutMs });
|
|
197
|
-
this.pendingCloseCategory = "relay_readiness_timeout";
|
|
244
|
+
this.pendingCloseCategory = preferredRelayCloseCategory(this.pendingCloseCategory, "relay_readiness_timeout");
|
|
198
245
|
terminateSocket(socket);
|
|
199
246
|
}, this.readinessTimeoutMs);
|
|
200
247
|
this.readinessTimer?.unref?.();
|
|
@@ -218,17 +265,20 @@ export class RelayConnection {
|
|
|
218
265
|
this.ready = true;
|
|
219
266
|
this.clearTimer("readinessTimer", "clearTimeout");
|
|
220
267
|
this.reconnectAttempt = 0;
|
|
268
|
+
this.lastReadyAt = this.now();
|
|
269
|
+
this.lastReadyWallAt = this.wallNow();
|
|
270
|
+
this.nextReconnectAt = 0;
|
|
271
|
+
this.nextReconnectWallAt = 0;
|
|
272
|
+
this.lastReconnectDelayMs = 0;
|
|
221
273
|
|
|
222
274
|
if (!this.hasConnected) {
|
|
223
275
|
this.logger.info?.("remote relay connected and end-to-end result delivery verified");
|
|
224
276
|
} else if (this.outageStartedAt > 0) {
|
|
225
277
|
const outageMs = Math.max(0, this.now() - this.outageStartedAt);
|
|
226
278
|
if (this.outageNoticeEmitted) {
|
|
227
|
-
|
|
228
|
-
this.logger.
|
|
229
|
-
|
|
230
|
-
attempts: this.outageAttempts,
|
|
231
|
-
});
|
|
279
|
+
const recoveryFields = relayRecoveryFields(this, outageMs);
|
|
280
|
+
this.logger.warn?.(`remote relay connection restored after ${formatDuration(outageMs)} (${formatAttempts(this.outageAttempts)})`, recoveryFields);
|
|
281
|
+
this.logger.debug?.("remote relay outage recovery details", recoveryFields);
|
|
232
282
|
} else {
|
|
233
283
|
this.logger.debug?.("remote relay connection recovered after a brief interruption", {
|
|
234
284
|
outage_ms: outageMs,
|
|
@@ -253,18 +303,18 @@ export class RelayConnection {
|
|
|
253
303
|
|
|
254
304
|
handleServerError(message = {}) {
|
|
255
305
|
const errorCode = sanitizeProtocolErrorCode(message?.error);
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
if (
|
|
306
|
+
const reconnectCategory = relayServerErrorReconnectCategory(errorCode, {
|
|
307
|
+
authenticated: this.authenticated,
|
|
308
|
+
ready: this.ready,
|
|
309
|
+
});
|
|
310
|
+
this.logger.debug?.(
|
|
311
|
+
reconnectCategory ? "remote relay requested connection recovery" : "remote relay reported a protocol error",
|
|
312
|
+
{ error_code: errorCode, reconnect_category: reconnectCategory || "none" },
|
|
313
|
+
);
|
|
314
|
+
if (reconnectCategory) {
|
|
265
315
|
const socket = this.socket;
|
|
266
316
|
if (this.closed || !socket) return true;
|
|
267
|
-
this.pendingCloseCategory =
|
|
317
|
+
this.pendingCloseCategory = preferredRelayCloseCategory(this.pendingCloseCategory, reconnectCategory);
|
|
268
318
|
terminateSocket(socket);
|
|
269
319
|
return true;
|
|
270
320
|
}
|
|
@@ -275,26 +325,36 @@ export class RelayConnection {
|
|
|
275
325
|
connect() {
|
|
276
326
|
if (this.closed || this.socket) return;
|
|
277
327
|
const wsUrl = `${this.workerUrl.replace(/^http/i, "ws")}/daemon/ws`;
|
|
328
|
+
this.connectAttemptStartedAt = this.now();
|
|
329
|
+
this.lastConnectStage = "socket_constructing";
|
|
330
|
+
this.lastConnectHttpStatus = null;
|
|
278
331
|
this.logger.debug?.("connecting to remote relay", { endpoint: redactUrl(wsUrl), attempt: this.reconnectAttempt + 1 });
|
|
279
332
|
let socket;
|
|
280
333
|
try {
|
|
281
334
|
const proxy = this.proxyAgentForUrl(wsUrl);
|
|
282
335
|
const headers = this.connectionHeaders();
|
|
283
336
|
if (!headers || typeof headers !== "object" || Array.isArray(headers)) throw new Error("relay connection headers are invalid");
|
|
284
|
-
this.networkRoute = proxy?.agent ? "proxy" : "
|
|
337
|
+
this.networkRoute = proxy?.agent ? "application-http-proxy" : "system-network-stack";
|
|
338
|
+
this.lastConnectStage = proxy?.agent ? "proxy_connecting" : "tcp_connecting";
|
|
285
339
|
socket = new this.WebSocketClass(wsUrl, {
|
|
286
340
|
headers,
|
|
287
341
|
maxPayload: this.maxPayload,
|
|
288
342
|
...(proxy?.agent ? { agent: proxy.agent } : {}),
|
|
343
|
+
...(!proxy?.agent && this.WebSocketClass === WebSocket ? {
|
|
344
|
+
createConnection: tracedTlsConnection((stage) => this.observeConnectStage(stage)),
|
|
345
|
+
} : {}),
|
|
289
346
|
});
|
|
290
347
|
this.logger.debug?.("remote relay network route selected", { route: this.networkRoute });
|
|
291
348
|
} catch (error) {
|
|
292
349
|
if (error?.code === "relay_proxy_configuration") {
|
|
293
|
-
this.networkRoute = "invalid-proxy-configuration";
|
|
350
|
+
this.networkRoute = "invalid-application-proxy-configuration";
|
|
294
351
|
this.failPermanently("relay_proxy_configuration");
|
|
295
352
|
return;
|
|
296
353
|
}
|
|
297
354
|
this.lastTransportErrorClass = classifyRelayTransportError(error);
|
|
355
|
+
this.lastCloseCode = 0;
|
|
356
|
+
this.lastDisconnectedAt = this.wallNow();
|
|
357
|
+
this.finishConnectAttempt();
|
|
298
358
|
this.logger.debug?.("remote relay connection could not be created", { error_class: this.lastTransportErrorClass });
|
|
299
359
|
this.scheduleReconnect("connection_interrupted");
|
|
300
360
|
return;
|
|
@@ -304,7 +364,8 @@ export class RelayConnection {
|
|
|
304
364
|
this.connectTimer = this.scheduler.setTimeout(() => {
|
|
305
365
|
if (this.socket !== socket || this.closed || this.isSocketOpen(socket)) return;
|
|
306
366
|
this.logger.debug?.("remote relay transport connection timed out", { timeout_ms: this.connectTimeoutMs });
|
|
307
|
-
this.pendingCloseCategory = "relay_connect_timeout";
|
|
367
|
+
this.pendingCloseCategory = preferredRelayCloseCategory(this.pendingCloseCategory, "relay_connect_timeout");
|
|
368
|
+
this.finishConnectAttempt();
|
|
308
369
|
terminateSocket(socket);
|
|
309
370
|
}, this.connectTimeoutMs);
|
|
310
371
|
this.connectTimer?.unref?.();
|
|
@@ -312,16 +373,18 @@ export class RelayConnection {
|
|
|
312
373
|
socket.on("open", () => {
|
|
313
374
|
this.clearTimer("connectTimer", "clearTimeout");
|
|
314
375
|
if (this.socket !== socket || this.closed) {
|
|
315
|
-
try { socket.close(1000, "stale daemon connection"); }
|
|
376
|
+
try { socket.close(1000, "stale daemon connection"); }
|
|
377
|
+
catch { /* Stale transport ownership has already been rejected; concurrent close is harmless. */ }
|
|
316
378
|
return;
|
|
317
379
|
}
|
|
318
|
-
this.lastInboundAt = this.now();
|
|
319
380
|
this.logger.debug?.("remote relay transport opened; awaiting device challenge");
|
|
381
|
+
this.observeConnectStage("websocket_open");
|
|
382
|
+
this.finishConnectAttempt();
|
|
320
383
|
this.clearTimer("handshakeTimer", "clearTimeout");
|
|
321
384
|
this.handshakeTimer = this.scheduler.setTimeout(() => {
|
|
322
385
|
if (this.socket !== socket || this.closed || this.ready) return;
|
|
323
386
|
this.logger.debug?.("remote relay authentication acknowledgement timed out", { timeout_ms: this.handshakeTimeoutMs });
|
|
324
|
-
this.pendingCloseCategory = "relay_handshake_timeout";
|
|
387
|
+
this.pendingCloseCategory = preferredRelayCloseCategory(this.pendingCloseCategory, "relay_handshake_timeout");
|
|
325
388
|
terminateSocket(socket);
|
|
326
389
|
}, this.handshakeTimeoutMs);
|
|
327
390
|
this.handshakeTimer?.unref?.();
|
|
@@ -329,12 +392,13 @@ export class RelayConnection {
|
|
|
329
392
|
|
|
330
393
|
socket.on("message", (data) => {
|
|
331
394
|
if (this.socket !== socket || this.closed) return;
|
|
332
|
-
this.
|
|
395
|
+
this.liveness.observeApplicationInbound();
|
|
333
396
|
// Bind results to the generation that received this message.
|
|
334
397
|
const relayContext = {
|
|
335
398
|
sessionId: this.activeSessionId,
|
|
336
399
|
authenticated: this.authenticated === true,
|
|
337
400
|
ready: this.ready === true,
|
|
401
|
+
transport: "websocket",
|
|
338
402
|
};
|
|
339
403
|
try {
|
|
340
404
|
const outcome = this.onMessage(data, relayContext);
|
|
@@ -346,25 +410,37 @@ export class RelayConnection {
|
|
|
346
410
|
}
|
|
347
411
|
});
|
|
348
412
|
|
|
413
|
+
socket.on("pong", () => {
|
|
414
|
+
if (this.socket !== socket || this.closed) return;
|
|
415
|
+
this.liveness.observeInbound();
|
|
416
|
+
});
|
|
417
|
+
|
|
349
418
|
socket.on("close", (code, reason) => {
|
|
350
419
|
if (this.socket !== socket) return;
|
|
351
420
|
const wasReady = this.ready;
|
|
352
421
|
const wasAuthenticated = this.authenticated;
|
|
422
|
+
const readyInboundSilenceMs = this.liveness.silenceMs(this.now());
|
|
353
423
|
this.socket = null;
|
|
354
424
|
this.authenticated = false;
|
|
355
425
|
this.ready = false;
|
|
356
426
|
this.readinessProbeDelivered = false;
|
|
357
427
|
this.activeSessionId = 0;
|
|
428
|
+
this.liveness.stop();
|
|
358
429
|
this.clearTimer("connectTimer", "clearTimeout");
|
|
359
|
-
this.clearTimer("heartbeatTimer", "clearInterval");
|
|
360
430
|
this.clearTimer("handshakeTimer", "clearTimeout");
|
|
361
431
|
this.clearTimer("readinessTimer", "clearTimeout");
|
|
362
432
|
const reasonText = sanitizeCloseReason(reason);
|
|
363
433
|
const category = this.pendingCloseCategory || relayCloseCategory(code, reasonText);
|
|
364
434
|
this.pendingCloseCategory = "";
|
|
365
|
-
|
|
435
|
+
if (category !== "relay_transport_error") this.lastTransportErrorClass = "";
|
|
436
|
+
const disconnectedAt = this.now();
|
|
437
|
+
const connectedForMs = wasAuthenticated && this.connectedAt > 0 ? Math.max(0, disconnectedAt - this.connectedAt) : 0;
|
|
438
|
+
if (wasReady) this.lastReadyInboundSilenceMs = readyInboundSilenceMs;
|
|
439
|
+
this.lastDisconnectedAt = this.wallNow();
|
|
440
|
+
if (wasReady && this.lastReadyAt > 0) this.lastReadyDurationMs = Math.max(0, disconnectedAt - this.lastReadyAt);
|
|
441
|
+
this.lastCloseCode = Number(code) || 0;
|
|
366
442
|
this.logger.debug?.("remote relay transport closed", {
|
|
367
|
-
close_code:
|
|
443
|
+
close_code: this.lastCloseCode,
|
|
368
444
|
close_reason: reasonText || "<none>",
|
|
369
445
|
category,
|
|
370
446
|
ready: wasReady,
|
|
@@ -387,10 +463,10 @@ export class RelayConnection {
|
|
|
387
463
|
this.clearTimer("reconnectTimer", "clearTimeout");
|
|
388
464
|
this.clearTimer("outageWarnTimer", "clearTimeout");
|
|
389
465
|
this.logger.warn?.("daemon connection was replaced by a newer verified instance");
|
|
390
|
-
queueMicrotask(() => {
|
|
391
|
-
try {
|
|
392
|
-
this.
|
|
393
|
-
}
|
|
466
|
+
queueMicrotask(async () => {
|
|
467
|
+
try {
|
|
468
|
+
await this.onSuperseded();
|
|
469
|
+
} catch (error) { this.logger.error?.("daemon superseded callback failed", { error_class: classifyOperationalError(error) }); }
|
|
394
470
|
});
|
|
395
471
|
return;
|
|
396
472
|
}
|
|
@@ -402,12 +478,18 @@ export class RelayConnection {
|
|
|
402
478
|
socket.on("error", (error) => {
|
|
403
479
|
if (this.socket !== socket || this.closed) return;
|
|
404
480
|
this.lastTransportErrorClass = classifyRelayTransportError(error);
|
|
481
|
+
const httpStatus = relayHttpStatusFromError(error);
|
|
482
|
+
if (httpStatus) {
|
|
483
|
+
this.lastConnectStage = "http_rejected";
|
|
484
|
+
this.lastConnectHttpStatus = httpStatus;
|
|
485
|
+
}
|
|
486
|
+
this.finishConnectAttempt();
|
|
405
487
|
this.logger.debug?.("remote relay transport error", { error_class: this.lastTransportErrorClass });
|
|
406
488
|
if (this.lastTransportErrorClass === "authentication_failed") {
|
|
407
489
|
this.failPermanently("relay_authentication_failed");
|
|
408
490
|
return;
|
|
409
491
|
}
|
|
410
|
-
this.pendingCloseCategory = "relay_transport_error";
|
|
492
|
+
this.pendingCloseCategory = preferredRelayCloseCategory(this.pendingCloseCategory, "relay_transport_error");
|
|
411
493
|
terminateSocket(socket);
|
|
412
494
|
});
|
|
413
495
|
}
|
|
@@ -420,9 +502,9 @@ export class RelayConnection {
|
|
|
420
502
|
this.ready = false;
|
|
421
503
|
this.readinessProbeDelivered = false;
|
|
422
504
|
this.activeSessionId = 0;
|
|
505
|
+
this.liveness.stop();
|
|
423
506
|
this.socket = null;
|
|
424
507
|
this.clearTimer("connectTimer", "clearTimeout");
|
|
425
|
-
this.clearTimer("heartbeatTimer", "clearInterval");
|
|
426
508
|
this.clearTimer("handshakeTimer", "clearTimeout");
|
|
427
509
|
this.clearTimer("readinessTimer", "clearTimeout");
|
|
428
510
|
this.clearTimer("reconnectTimer", "clearTimeout");
|
|
@@ -446,21 +528,33 @@ export class RelayConnection {
|
|
|
446
528
|
}
|
|
447
529
|
this.logger.error?.(message);
|
|
448
530
|
this.logger.debug?.("remote relay fatal details", { category, cause: relayCloseUserCause(category) });
|
|
449
|
-
queueMicrotask(() => {
|
|
450
|
-
try {
|
|
451
|
-
this.
|
|
452
|
-
}
|
|
531
|
+
queueMicrotask(async () => {
|
|
532
|
+
try {
|
|
533
|
+
await this.onFatal(error);
|
|
534
|
+
} catch (callbackError) { this.logger.error?.("relay fatal callback failed", { error_class: classifyOperationalError(callbackError) }); }
|
|
453
535
|
});
|
|
454
536
|
}
|
|
455
537
|
|
|
456
538
|
scheduleReconnect(category) {
|
|
457
539
|
if (this.closed || this.reconnectTimer) return;
|
|
458
540
|
this.recordOutage(category);
|
|
459
|
-
const delay = this.reconnectDelay(this.reconnectAttempt
|
|
541
|
+
const delay = this.reconnectDelay(this.reconnectAttempt++, Math.random, this.lastConnectDurationMs, this.connectTimeoutMs);
|
|
542
|
+
this.lastReconnectDelayMs = delay;
|
|
543
|
+
this.nextReconnectAt = this.now() + delay;
|
|
544
|
+
this.nextReconnectWallAt = this.wallNow() + delay;
|
|
460
545
|
this.scheduleOutageWarning();
|
|
461
|
-
this.logger.debug?.("scheduling daemon reconnect", {
|
|
546
|
+
this.logger.debug?.("scheduling daemon reconnect", {
|
|
547
|
+
delay_ms: delay,
|
|
548
|
+
next_reconnect_at: new Date(this.nextReconnectWallAt).toISOString(),
|
|
549
|
+
attempt: this.outageAttempts,
|
|
550
|
+
close_category: this.lastCloseCategory,
|
|
551
|
+
network_route: this.networkRoute,
|
|
552
|
+
network_route_scope: this.networkRouteScope,
|
|
553
|
+
});
|
|
462
554
|
this.reconnectTimer = this.scheduler.setTimeout(() => {
|
|
463
555
|
this.reconnectTimer = null;
|
|
556
|
+
this.nextReconnectAt = 0;
|
|
557
|
+
this.nextReconnectWallAt = 0;
|
|
464
558
|
this.connect();
|
|
465
559
|
}, delay);
|
|
466
560
|
this.reconnectTimer?.unref?.();
|
|
@@ -473,34 +567,19 @@ export class RelayConnection {
|
|
|
473
567
|
return true;
|
|
474
568
|
} catch (error) {
|
|
475
569
|
this.lastTransportErrorClass = classifyRelayTransportError(error);
|
|
476
|
-
this.pendingCloseCategory = "relay_transport_error";
|
|
570
|
+
this.pendingCloseCategory = preferredRelayCloseCategory(this.pendingCloseCategory, "relay_transport_error");
|
|
477
571
|
this.logger.debug?.("remote relay send failed", { error_class: this.lastTransportErrorClass });
|
|
478
572
|
terminateSocket(socket);
|
|
479
573
|
return false;
|
|
480
574
|
}
|
|
481
575
|
}
|
|
482
576
|
|
|
483
|
-
startHeartbeat() {
|
|
484
|
-
this.clearTimer("heartbeatTimer", "clearInterval");
|
|
485
|
-
this.heartbeatTimer = this.scheduler.setInterval(() => {
|
|
486
|
-
const socket = this.socket;
|
|
487
|
-
if (this.closed || !this.authenticated || !this.isSocketOpen(socket)) return;
|
|
488
|
-
const silentForMs = Math.max(0, this.now() - this.lastInboundAt);
|
|
489
|
-
if (silentForMs >= this.heartbeatTimeoutMs) {
|
|
490
|
-
this.logger.debug?.("remote relay heartbeat timed out", { silent_for_ms: silentForMs });
|
|
491
|
-
this.pendingCloseCategory = "relay_heartbeat_timeout";
|
|
492
|
-
terminateSocket(socket);
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
this.sendOnSocket(socket, { type: "heartbeat", ts: this.now() });
|
|
496
|
-
}, this.heartbeatIntervalMs);
|
|
497
|
-
this.heartbeatTimer?.unref?.();
|
|
498
|
-
}
|
|
499
|
-
|
|
500
577
|
recordOutage(category) {
|
|
501
578
|
const now = this.now();
|
|
502
579
|
if (this.outageStartedAt === 0) {
|
|
503
580
|
this.outageStartedAt = now;
|
|
581
|
+
this.outageStartedWallAt = this.wallNow();
|
|
582
|
+
this.outageCount += 1;
|
|
504
583
|
this.outageAttempts = 0;
|
|
505
584
|
this.outageNoticeEmitted = false;
|
|
506
585
|
this.lastOutageWarnAt = 0;
|
|
@@ -535,27 +614,22 @@ export class RelayConnection {
|
|
|
535
614
|
this.outageWarningCount += 1;
|
|
536
615
|
this.lastOutageWarnAt = this.now();
|
|
537
616
|
const cause = relayCloseUserCause(this.lastCloseCategory);
|
|
538
|
-
const action = outageMs
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
this.logger.
|
|
542
|
-
this.logger.debug?.("remote relay outage details", {
|
|
543
|
-
outage_seconds: roundSeconds(outageMs),
|
|
544
|
-
attempts: this.outageAttempts,
|
|
545
|
-
cause,
|
|
546
|
-
...(this.lastTransportErrorClass ? { error_class: this.lastTransportErrorClass } : {}),
|
|
547
|
-
});
|
|
617
|
+
const action = relayOutageUserAction(this.lastCloseCategory, outageMs);
|
|
618
|
+
const outageFields = relayOutageFields(this, this.now(), cause);
|
|
619
|
+
this.logger.warn?.(`remote relay unavailable for ${formatDuration(outageMs)}; reconnecting automatically (${formatAttempts(this.outageAttempts)}; ${cause}).${action}`, outageFields);
|
|
620
|
+
this.logger.debug?.("remote relay outage details", outageFields);
|
|
548
621
|
}
|
|
549
622
|
|
|
550
623
|
resetOutage() {
|
|
551
624
|
this.clearTimer("outageWarnTimer", "clearTimeout");
|
|
552
625
|
this.outageStartedAt = 0;
|
|
626
|
+
this.outageStartedWallAt = 0;
|
|
553
627
|
this.outageAttempts = 0;
|
|
554
628
|
this.outageNoticeEmitted = false;
|
|
555
629
|
this.outageWarningCount = 0;
|
|
556
630
|
this.lastOutageWarnAt = 0;
|
|
557
|
-
this.
|
|
558
|
-
this.
|
|
631
|
+
this.nextReconnectAt = 0;
|
|
632
|
+
this.nextReconnectWallAt = 0;
|
|
559
633
|
this.pendingCloseCategory = "";
|
|
560
634
|
}
|
|
561
635
|
|
|
@@ -569,182 +643,15 @@ export class RelayConnection {
|
|
|
569
643
|
isSocketOpen(socket) {
|
|
570
644
|
return Boolean(socket && socket.readyState === this.WebSocketClass.OPEN);
|
|
571
645
|
}
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
export function relayCloseCategory(code, reason = "") {
|
|
575
|
-
const numeric = Number(code);
|
|
576
|
-
const reasonText = String(reason || "");
|
|
577
|
-
if (isSupersededClose(numeric, reasonText)) return "superseded";
|
|
578
|
-
if (numeric === 1008 && reasonText === "daemon hello timeout") return "relay_handshake_timeout";
|
|
579
|
-
if (numeric === 1008 && reasonText === "daemon ready timeout") return "relay_readiness_timeout";
|
|
580
|
-
if (numeric === 1008 && ["stale daemon candidate", "expired daemon candidate"].includes(reasonText)) return "relay_restarting_or_unavailable";
|
|
581
|
-
if (numeric === 1008 && ["daemon hello required", "missing daemon attachment", "invalid daemon candidate timestamp"].includes(reasonText)) return "relay_protocol_error";
|
|
582
|
-
if (numeric === 1000) return "normal_close";
|
|
583
|
-
if (numeric === 1001 || numeric === 1012 || numeric === 1013) return "relay_restarting_or_unavailable";
|
|
584
|
-
if (numeric === 1006) return "connection_interrupted";
|
|
585
|
-
if (numeric === 1002) return "relay_protocol_error";
|
|
586
|
-
if (numeric === 1007) return "invalid_transport_payload";
|
|
587
|
-
if (numeric === 1008) return "relay_policy_rejected";
|
|
588
|
-
if (numeric === 1009) return "message_too_large";
|
|
589
|
-
if (numeric === 1011) return "relay_internal_error";
|
|
590
|
-
return "unexpected_close";
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
function relayFatalMessage(category) {
|
|
594
|
-
if (category === "relay_protocol_mismatch") {
|
|
595
|
-
return "remote relay identity or version does not match this daemon; upgrade and redeploy both components";
|
|
596
|
-
}
|
|
597
|
-
if (category === "relay_protocol_error") {
|
|
598
|
-
return "remote relay protocol error; upgrade and redeploy both components, then restart the daemon";
|
|
599
|
-
}
|
|
600
|
-
if (category === "relay_proxy_configuration") {
|
|
601
|
-
return "remote relay proxy configuration is invalid; check HTTP_PROXY, HTTPS_PROXY, and NO_PROXY";
|
|
602
|
-
}
|
|
603
|
-
return "remote relay rejected the daemon connection; verify credentials or redeploy the Worker";
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
function relayCloseUserCause(category) {
|
|
607
|
-
const causes = {
|
|
608
|
-
connection_interrupted: "connection interrupted",
|
|
609
|
-
relay_restarting_or_unavailable: "relay restarting or temporarily unavailable",
|
|
610
|
-
relay_policy_rejected: "relay rejected the connection",
|
|
611
|
-
relay_internal_error: "relay internal error",
|
|
612
|
-
relay_protocol_mismatch: "relay identity or version mismatch",
|
|
613
|
-
relay_authentication_failed: "relay authentication failed",
|
|
614
|
-
relay_connect_timeout: "relay connection attempt timed out",
|
|
615
|
-
relay_handshake_timeout: "relay authentication acknowledgement timed out",
|
|
616
|
-
relay_readiness_timeout: "end-to-end relay readiness verification timed out",
|
|
617
|
-
relay_heartbeat_timeout: "relay stopped responding",
|
|
618
|
-
relay_transport_error: "relay transport error",
|
|
619
|
-
relay_protocol_error: "relay protocol error",
|
|
620
|
-
relay_proxy_configuration: "relay proxy configuration invalid",
|
|
621
|
-
invalid_transport_payload: "invalid transport payload",
|
|
622
|
-
message_too_large: "message exceeded the relay limit",
|
|
623
|
-
normal_close: "connection closed",
|
|
624
|
-
unexpected_close: "unexpected connection close",
|
|
625
|
-
superseded: "connection superseded",
|
|
626
|
-
};
|
|
627
|
-
return causes[String(category || "")] || "connection interrupted";
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
export function welcomeMismatch(message, expectedServer = "", expectedVersion = "") {
|
|
631
|
-
if (!message || typeof message !== "object" || Array.isArray(message)) return "invalid_welcome";
|
|
632
|
-
if (message.type !== "welcome") return "unexpected_welcome_type";
|
|
633
|
-
if (expectedServer && message.server !== expectedServer) return "server_identity_mismatch";
|
|
634
|
-
if (expectedVersion && message.version !== expectedVersion) return "server_version_mismatch";
|
|
635
|
-
if (typeof message.server !== "string" || !message.server || typeof message.version !== "string" || !message.version) return "incomplete_welcome";
|
|
636
|
-
return "";
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
export function acknowledgementMismatch(message, expectedServer = "", expectedVersion = "") {
|
|
640
|
-
if (!message || typeof message !== "object" || Array.isArray(message)) return "invalid_acknowledgement";
|
|
641
|
-
if (message.type !== "hello_ack") return "unexpected_acknowledgement_type";
|
|
642
|
-
if (expectedServer && message.server !== expectedServer) return "server_identity_mismatch";
|
|
643
|
-
if (expectedVersion && message.version !== expectedVersion) return "server_version_mismatch";
|
|
644
|
-
if (typeof message.server !== "string" || !message.server || typeof message.version !== "string" || !message.version) return "incomplete_acknowledgement";
|
|
645
|
-
return "";
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
export function readinessMismatch(message, expectedServer = "", expectedVersion = "") {
|
|
649
|
-
if (!message || typeof message !== "object" || Array.isArray(message)) return "invalid_readiness_acknowledgement";
|
|
650
|
-
if (message.type !== "ready_ack") return "unexpected_readiness_acknowledgement_type";
|
|
651
|
-
if (expectedServer && message.server !== expectedServer) return "server_identity_mismatch";
|
|
652
|
-
if (expectedVersion && message.version !== expectedVersion) return "server_version_mismatch";
|
|
653
|
-
if (typeof message.server !== "string" || !message.server || typeof message.version !== "string" || !message.version) return "incomplete_readiness_acknowledgement";
|
|
654
|
-
return "";
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
export function isRelayReadyContext(relayContext = {}, relay = null) {
|
|
658
|
-
if (relayContext?.ready === true) return true;
|
|
659
|
-
if (relayContext?.ready === false) return false;
|
|
660
|
-
return Number(relayContext?.sessionId) > 0 && relay?.status?.()?.ready === true;
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
export function isSupersededClose(code, reason) {
|
|
664
|
-
return Number(code) === 1012 && String(reason || "") === "replaced by verified daemon";
|
|
665
|
-
}
|
|
666
646
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
return base + Math.floor(random() * 1000);
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
function normalizeWorkerUrl(value) {
|
|
674
|
-
let url;
|
|
675
|
-
try { url = new URL(String(value || "")); } catch { throw new Error("invalid Worker URL"); }
|
|
676
|
-
if (url.protocol !== "https:") throw new Error("Worker URL must use HTTPS");
|
|
677
|
-
if (url.username || url.password) throw new Error("Worker URL must not contain credentials");
|
|
678
|
-
if (url.pathname !== "/" || url.search || url.hash) throw new Error("Worker URL must be an origin without a path, query, or fragment");
|
|
679
|
-
return url.origin;
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
function sanitizeCloseReason(value) {
|
|
683
|
-
let text;
|
|
684
|
-
try { text = Buffer.isBuffer(value) ? value.toString("utf8") : String(value || ""); } catch { text = ""; }
|
|
685
|
-
return text.replace(/[\r\n\t\u0000-\u001f\u007f]/g, " ").trim().slice(0, MAX_CLOSE_REASON_CHARS);
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
function terminateSocket(socket) {
|
|
689
|
-
try {
|
|
690
|
-
if (typeof socket?.terminate === "function") socket.terminate();
|
|
691
|
-
else socket?.close?.();
|
|
692
|
-
} catch {}
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
function redactUrl(value) {
|
|
696
|
-
try {
|
|
697
|
-
const url = new URL(String(value));
|
|
698
|
-
return `${url.protocol}//${url.host}${url.pathname}`;
|
|
699
|
-
} catch {
|
|
700
|
-
return "<relay-url>";
|
|
647
|
+
observeConnectStage(stage) {
|
|
648
|
+
if (!Number.isFinite(this.connectAttemptStartedAt)) return;
|
|
649
|
+
this.lastConnectStage = String(stage || "socket_constructing");
|
|
701
650
|
}
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
function classifyRelayTransportError(error) {
|
|
705
|
-
const directStatus = Number(error?.statusCode ?? error?.response?.statusCode);
|
|
706
|
-
if (directStatus === 401 || directStatus === 403) return "authentication_failed";
|
|
707
|
-
const match = /^Unexpected server response: (\d{3})$/.exec(String(error?.message || ""));
|
|
708
|
-
if (match && [401, 403].includes(Number(match[1]))) return "authentication_failed";
|
|
709
|
-
return classifyOperationalError(error);
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
function boundedPositiveInteger(value, fallback) {
|
|
713
|
-
const number = Number(value);
|
|
714
|
-
return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback;
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
function sanitizeProtocolErrorCode(value) {
|
|
718
|
-
const code = String(value || "unknown_error").replace(/[^A-Za-z0-9_-]/g, "_").slice(0, MAX_PROTOCOL_ERROR_CODE_CHARS);
|
|
719
|
-
return code || "unknown_error";
|
|
720
|
-
}
|
|
721
651
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
function formatDuration(milliseconds) {
|
|
728
|
-
let seconds = Math.max(1, Math.round(Number(milliseconds) / 1000));
|
|
729
|
-
const units = [
|
|
730
|
-
["day", 86_400],
|
|
731
|
-
["hour", 3_600],
|
|
732
|
-
["minute", 60],
|
|
733
|
-
["second", 1],
|
|
734
|
-
];
|
|
735
|
-
const parts = [];
|
|
736
|
-
for (const [label, size] of units) {
|
|
737
|
-
if (seconds < size && parts.length === 0) continue;
|
|
738
|
-
const amount = Math.floor(seconds / size);
|
|
739
|
-
if (amount > 0) {
|
|
740
|
-
parts.push(`${amount} ${label}${amount === 1 ? "" : "s"}`);
|
|
741
|
-
seconds -= amount * size;
|
|
742
|
-
}
|
|
743
|
-
if (parts.length === 2) break;
|
|
652
|
+
finishConnectAttempt() {
|
|
653
|
+
if (!Number.isFinite(this.connectAttemptStartedAt)) return;
|
|
654
|
+
this.lastConnectDurationMs = Math.max(0, this.now() - this.connectAttemptStartedAt);
|
|
655
|
+
this.connectAttemptStartedAt = null;
|
|
744
656
|
}
|
|
745
|
-
return parts.join(" ") || "1 second";
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
function roundSeconds(milliseconds) {
|
|
749
|
-
return Math.max(1, Math.round(milliseconds / 1000));
|
|
750
657
|
}
|