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,26 +1,63 @@
|
|
|
1
|
+
import type { AuthorityRevocation } from "../shared/authority-revocation.mjs";
|
|
2
|
+
import type { DaemonChannel } from "./daemon-channel.ts";
|
|
3
|
+
|
|
4
|
+
export type PendingCallOutcome =
|
|
5
|
+
| { ok: true; value: unknown }
|
|
6
|
+
| { ok: false; error: Error };
|
|
7
|
+
|
|
8
|
+
export type PendingCallSettlement = {
|
|
9
|
+
resolve: (value: unknown) => void;
|
|
10
|
+
reject: (error: Error) => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export class PendingCallRegistrationError extends Error {
|
|
14
|
+
readonly code: "conflict" | "limit_exceeded";
|
|
15
|
+
readonly retryable: boolean;
|
|
16
|
+
constructor(code: "conflict" | "limit_exceeded", message: string, retryable = false) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "PendingCallRegistrationError";
|
|
19
|
+
this.code = code;
|
|
20
|
+
this.retryable = retryable;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
1
24
|
export interface PendingCallRecord {
|
|
2
25
|
id: string;
|
|
3
|
-
socket?:
|
|
26
|
+
socket?: DaemonChannel;
|
|
4
27
|
daemonInstanceId?: string;
|
|
5
28
|
reconnectTimeout?: ReturnType<typeof setTimeout>;
|
|
29
|
+
reconnectDeadlineAt?: number;
|
|
30
|
+
onReconnectTimeout?: (record: PendingCallRecord) => Error;
|
|
6
31
|
clientRequestKey?: string;
|
|
32
|
+
owner_kind?: "account";
|
|
33
|
+
owner_account_id?: string;
|
|
34
|
+
owner_account_version?: number;
|
|
35
|
+
owner_client_id?: string;
|
|
36
|
+
owner_family_id?: string;
|
|
7
37
|
tool: string;
|
|
38
|
+
recovery?: Record<string, unknown>;
|
|
8
39
|
startedAt: number;
|
|
9
|
-
timeout
|
|
10
|
-
|
|
11
|
-
|
|
40
|
+
timeout?: ReturnType<typeof setTimeout>;
|
|
41
|
+
deadlineAt: number;
|
|
42
|
+
remainingTimeoutMs: number;
|
|
43
|
+
onTimeout: (record: PendingCallRecord) => Error;
|
|
44
|
+
redeliverAfterProvenMissing?: (record: PendingCallRecord, channel: DaemonChannel) => boolean;
|
|
45
|
+
settlement: PendingCallSettlement;
|
|
12
46
|
signal?: AbortSignal;
|
|
13
47
|
abortHandler?: () => void;
|
|
14
48
|
}
|
|
15
49
|
|
|
16
50
|
export interface RegisterPendingCall {
|
|
17
51
|
id: string;
|
|
18
|
-
socket:
|
|
52
|
+
socket: DaemonChannel;
|
|
19
53
|
daemonInstanceId?: string;
|
|
20
54
|
clientRequestKey?: string;
|
|
55
|
+
authority?: AuthorityRevocation;
|
|
21
56
|
tool: string;
|
|
57
|
+
recovery?: Record<string, unknown>;
|
|
22
58
|
timeoutMs: number;
|
|
23
59
|
onTimeout: (record: PendingCallRecord) => Error;
|
|
60
|
+
redeliverAfterProvenMissing?: (record: PendingCallRecord, channel: DaemonChannel) => boolean;
|
|
24
61
|
signal?: AbortSignal;
|
|
25
62
|
onAbort?: (record: PendingCallRecord) => Error;
|
|
26
63
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { PendingCallRecord } from "./pending-call-contract.ts";
|
|
2
|
+
|
|
3
|
+
type TimerHandle = ReturnType<typeof setTimeout>;
|
|
4
|
+
export type PendingCallDeadlineOptions = {
|
|
5
|
+
now?: () => number;
|
|
6
|
+
scheduler?: {
|
|
7
|
+
setTimeout: (callback: () => void, delay: number) => TimerHandle;
|
|
8
|
+
clearTimeout: (handle: TimerHandle) => void;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export class PendingCallDeadlines {
|
|
13
|
+
private readonly clock: () => number;
|
|
14
|
+
private readonly scheduler: Required<PendingCallDeadlineOptions>["scheduler"];
|
|
15
|
+
|
|
16
|
+
constructor(options: PendingCallDeadlineOptions = {}) {
|
|
17
|
+
this.clock = options.now ?? (() => performance.now());
|
|
18
|
+
this.scheduler = options.scheduler ?? { setTimeout, clearTimeout };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
now(): number {
|
|
22
|
+
return this.clock();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
nextDelayMs(record: PendingCallRecord): number {
|
|
26
|
+
const deadline = record.socket ? record.deadlineAt : record.reconnectDeadlineAt;
|
|
27
|
+
return Number.isFinite(deadline) ? Math.max(0, Number(deadline) - this.clock()) : Number.POSITIVE_INFINITY;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
isDue(record: PendingCallRecord, now = this.clock()): boolean {
|
|
31
|
+
const deadline = record.socket ? record.deadlineAt : record.reconnectDeadlineAt;
|
|
32
|
+
return Number.isFinite(deadline) && Number(deadline) <= now;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
armOperation(record: PendingCallRecord, delayMs: number, expire: (id: string) => void): void {
|
|
36
|
+
if (record.timeout) this.scheduler.clearTimeout(record.timeout);
|
|
37
|
+
const delay = boundedPendingDelayMs(delayMs);
|
|
38
|
+
record.remainingTimeoutMs = delay;
|
|
39
|
+
record.deadlineAt = this.clock() + delay;
|
|
40
|
+
record.timeout = this.scheduler.setTimeout(() => expire(record.id), delay);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
pauseOperation(record: PendingCallRecord): void {
|
|
44
|
+
record.remainingTimeoutMs = Math.max(1, Math.ceil(record.deadlineAt - this.clock()));
|
|
45
|
+
if (record.timeout) this.scheduler.clearTimeout(record.timeout);
|
|
46
|
+
record.timeout = undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
armReconnect(record: PendingCallRecord, delayMs: number, expire: (id: string) => void): void {
|
|
50
|
+
if (record.reconnectTimeout) this.scheduler.clearTimeout(record.reconnectTimeout);
|
|
51
|
+
const delay = boundedPendingDelayMs(delayMs);
|
|
52
|
+
record.reconnectDeadlineAt = this.clock() + delay;
|
|
53
|
+
record.reconnectTimeout = this.scheduler.setTimeout(() => expire(record.id), delay);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
clearReconnect(record: PendingCallRecord): void {
|
|
57
|
+
if (record.reconnectTimeout) this.scheduler.clearTimeout(record.reconnectTimeout);
|
|
58
|
+
record.reconnectTimeout = undefined;
|
|
59
|
+
record.reconnectDeadlineAt = undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
clear(record: PendingCallRecord): void {
|
|
63
|
+
if (record.timeout) this.scheduler.clearTimeout(record.timeout);
|
|
64
|
+
if (record.reconnectTimeout) this.scheduler.clearTimeout(record.reconnectTimeout);
|
|
65
|
+
record.reconnectDeadlineAt = undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function boundedPendingDelayMs(value: unknown, maximum = 2_147_483_647): number {
|
|
70
|
+
if (!Number.isSafeInteger(maximum) || maximum < 1) throw new TypeError("pending-call delay maximum must be a positive safe integer");
|
|
71
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1 || value > maximum) {
|
|
72
|
+
throw new RangeError(`pending-call delay must be an integer from 1 to ${maximum}`);
|
|
73
|
+
}
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
@@ -1,173 +1,179 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
1
|
+
import { toolCallAdmission, toolCallCapacityConfig, type ToolCallCapacityConfig } from "../shared/tool-call-capacity.mjs";
|
|
2
|
+
import relayContract from "../shared/relay-contract.json" with { type: "json" };
|
|
3
|
+
import { PendingCallRegistrationError, type PendingCallOutcome, type PendingCallRecord, type PendingCallSettlement, type RegisterPendingCall } from "./pending-call-contract.ts";
|
|
4
|
+
import { boundedPendingDelayMs, PendingCallDeadlines, type PendingCallDeadlineOptions } from "./pending-call-deadlines.ts";
|
|
5
|
+
import { pendingRegistrySnapshot } from "./pending-call-capacity.ts";
|
|
6
|
+
import { recordMatchesAuthorityRevocation, type AuthorityRevocation } from "../shared/authority-revocation.mjs";
|
|
7
|
+
import type { DaemonChannel } from "./daemon-channel.ts";
|
|
8
|
+
type PendingCallRegistryOptions = PendingCallDeadlineOptions & {
|
|
9
|
+
reservedCapacity?: number;
|
|
10
|
+
reservedTools?: Iterable<string>;
|
|
11
|
+
};
|
|
15
12
|
export class PendingCallRegistry {
|
|
16
|
-
private readonly
|
|
13
|
+
private readonly capacity: ToolCallCapacityConfig;
|
|
17
14
|
private readonly byId = new Map<string, PendingCallRecord>();
|
|
18
15
|
private readonly byRequestKey = new Map<string, string>();
|
|
19
|
-
|
|
20
|
-
constructor(maximum: number) {
|
|
21
|
-
this.
|
|
16
|
+
private readonly deadlines: PendingCallDeadlines;
|
|
17
|
+
constructor(maximum: number, options: PendingCallRegistryOptions = {}) {
|
|
18
|
+
this.capacity = toolCallCapacityConfig(maximum, options.reservedCapacity, options.reservedTools);
|
|
19
|
+
this.deadlines = new PendingCallDeadlines(options);
|
|
22
20
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
get size(): number { return this.byId.size; } hasRequestKey(requestKey?: string): boolean { return Boolean(requestKey && this.byRequestKey.has(requestKey)); }
|
|
22
|
+
resultOwnership(id: string, socket: DaemonChannel): "owned" | "missing" | "stale" {
|
|
23
|
+
const record = this.byId.get(id); return !record ? "missing" : record.socket === socket ? "owned" : "stale";
|
|
26
24
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
nextDeadlineDelayMs(): number { return Math.min(Number.POSITIVE_INFINITY, ...[...this.byId.values()].map((record) => this.deadlines.nextDelayMs(record))); }
|
|
26
|
+
async expireDue(now = this.deadlines.now()): Promise<number> {
|
|
27
|
+
const due = [...this.byId.values()].filter((record) => this.deadlines.isDue(record, now));
|
|
28
|
+
let expired = 0;
|
|
29
|
+
for (const record of due) expired += Number(await this.expireRecord(record));
|
|
30
|
+
return expired;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
register(input: RegisterPendingCall): Promise<unknown> {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return new Promise((resolve, reject) => {
|
|
40
|
-
const timeout = setTimeout(() => {
|
|
41
|
-
const record = this.take(input.id);
|
|
42
|
-
if (!record) return;
|
|
43
|
-
let error: unknown;
|
|
44
|
-
try { error = input.onTimeout(record); }
|
|
45
|
-
catch { error = new Error("pending daemon call timed out"); }
|
|
46
|
-
reject(error instanceof Error ? error : new Error("pending daemon call timed out"));
|
|
47
|
-
}, input.timeoutMs);
|
|
48
|
-
const abortHandler = () => {
|
|
49
|
-
const record = this.take(input.id);
|
|
50
|
-
if (!record) return;
|
|
51
|
-
let error: unknown;
|
|
52
|
-
try { error = input.onAbort?.(record); }
|
|
53
|
-
catch { error = new Error("pending daemon call was cancelled"); }
|
|
54
|
-
reject(error instanceof Error ? error : new Error("pending daemon call was cancelled"));
|
|
55
|
-
};
|
|
56
|
-
const record: PendingCallRecord = {
|
|
57
|
-
id: input.id,
|
|
58
|
-
socket: input.socket,
|
|
59
|
-
daemonInstanceId: input.daemonInstanceId,
|
|
60
|
-
clientRequestKey: input.clientRequestKey,
|
|
61
|
-
tool: String(input.tool || "unknown"),
|
|
62
|
-
startedAt,
|
|
63
|
-
timeout,
|
|
64
|
-
resolve,
|
|
65
|
-
reject,
|
|
66
|
-
signal: input.signal,
|
|
67
|
-
abortHandler: input.signal ? abortHandler : undefined,
|
|
68
|
-
};
|
|
69
|
-
this.byId.set(input.id, record);
|
|
70
|
-
if (input.clientRequestKey) this.byRequestKey.set(input.clientRequestKey, input.id);
|
|
71
|
-
if (input.signal?.aborted) abortHandler();
|
|
72
|
-
else input.signal?.addEventListener("abort", abortHandler, { once: true });
|
|
73
|
-
});
|
|
34
|
+
this.assertCanRegister(input);
|
|
35
|
+
let resolveResult!: (value: unknown) => void;
|
|
36
|
+
let rejectResult!: (error: Error) => void;
|
|
37
|
+
const result = new Promise<unknown>((resolve, reject) => { resolveResult = resolve; rejectResult = reject; });
|
|
38
|
+
this.add(input, { resolve: resolveResult, reject: rejectResult });
|
|
39
|
+
return result;
|
|
74
40
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
this.
|
|
80
|
-
return true;
|
|
41
|
+
resolve(id: string, socket: DaemonChannel, value: unknown): Promise<boolean> {
|
|
42
|
+
const record = this.byId.get(id); return !record || record.socket !== socket ? Promise.resolve(false) : this.finish(id, { ok: true, value });
|
|
43
|
+
}
|
|
44
|
+
reject(id: string, error: Error, socket?: DaemonChannel): Promise<boolean> {
|
|
45
|
+
const record = this.byId.get(id); return !record || (socket && record.socket !== socket) ? Promise.resolve(false) : this.finish(id, { ok: false, error });
|
|
81
46
|
}
|
|
82
47
|
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
if (!record || (socket && record.socket !== socket)) return false;
|
|
86
|
-
this.take(id)?.reject(error);
|
|
87
|
-
return true;
|
|
48
|
+
async cancelRequest(requestKey: string, onCancel: (record: PendingCallRecord) => Error): Promise<boolean> {
|
|
49
|
+
const id = this.byRequestKey.get(requestKey); return id ? this.fail(id, onCancel, "pending daemon call was cancelled") : false;
|
|
88
50
|
}
|
|
89
51
|
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
record.reject(onCancel(record));
|
|
96
|
-
return true;
|
|
52
|
+
async cancelAuthority(revocation: AuthorityRevocation, onCancel: (record: PendingCallRecord) => Error): Promise<number> {
|
|
53
|
+
const ids = [...this.byId.values()].filter((record) => recordMatchesAuthorityRevocation(record, revocation)).map((record) => record.id);
|
|
54
|
+
let cancelled = 0;
|
|
55
|
+
for (const id of ids) cancelled += Number(await this.fail(id, onCancel, "pending daemon authority was revoked"));
|
|
56
|
+
return cancelled;
|
|
97
57
|
}
|
|
98
58
|
|
|
99
|
-
rejectSocket(socket:
|
|
59
|
+
async rejectSocket(socket: DaemonChannel, createError: (record: PendingCallRecord) => Error): Promise<number> {
|
|
100
60
|
const ids = [...this.byId.values()].filter((record) => record.socket === socket).map((record) => record.id);
|
|
61
|
+
return this.rejectSocketIds(ids, socket, createError, "pending daemon call failed");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async rejectSocketIds(ids: Iterable<string>, socket: DaemonChannel, createError: (record: PendingCallRecord) => Error, fallback = "pending daemon call was not received", beforeReject?: (record: PendingCallRecord) => boolean): Promise<number> {
|
|
65
|
+
let rejected = 0;
|
|
101
66
|
for (const id of ids) {
|
|
102
|
-
const record = this.
|
|
103
|
-
if (record) record.reject(createError(record));
|
|
67
|
+
const record = this.byId.get(id); if (record?.socket === socket && beforeReject?.(record) !== true) rejected += Number(await this.fail(id, createError, fallback));
|
|
104
68
|
}
|
|
105
|
-
return
|
|
69
|
+
return rejected;
|
|
106
70
|
}
|
|
107
71
|
|
|
108
|
-
detachSocket(
|
|
109
|
-
socket: WebSocket,
|
|
110
|
-
graceMs: number,
|
|
111
|
-
createError: (record: PendingCallRecord) => Error,
|
|
112
|
-
): number {
|
|
72
|
+
detachSocket(socket: DaemonChannel, graceMs: number, createError: (record: PendingCallRecord) => Error): number {
|
|
113
73
|
const records = [...this.byId.values()].filter((record) => record.socket === socket);
|
|
114
|
-
const
|
|
74
|
+
const maximumGrace = boundedPendingDelayMs(graceMs, relayContract.reconnectGraceMs);
|
|
115
75
|
for (const record of records) {
|
|
116
76
|
record.socket = undefined;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const expired = this.take(record.id);
|
|
122
|
-
if (expired) expired.reject(createError(expired));
|
|
123
|
-
}, delay);
|
|
77
|
+
record.onReconnectTimeout = createError;
|
|
78
|
+
this.deadlines.pauseOperation(record);
|
|
79
|
+
const delay = Math.min(maximumGrace, record.remainingTimeoutMs);
|
|
80
|
+
this.deadlines.armReconnect(record, delay, (id) => { void this.expireReconnect(id); });
|
|
124
81
|
}
|
|
125
82
|
return records.length;
|
|
126
83
|
}
|
|
127
84
|
|
|
128
|
-
rebindInstance(daemonInstanceId: string, socket:
|
|
85
|
+
rebindInstance(daemonInstanceId: string, socket: DaemonChannel): string[] {
|
|
129
86
|
if (!daemonInstanceId) return [];
|
|
130
87
|
const rebound: string[] = [];
|
|
131
88
|
for (const record of this.byId.values()) {
|
|
132
|
-
if (record.
|
|
133
|
-
if (record.
|
|
134
|
-
record
|
|
89
|
+
if (record.daemonInstanceId !== daemonInstanceId || record.socket === socket) continue;
|
|
90
|
+
if (record.socket) this.deadlines.pauseOperation(record);
|
|
91
|
+
else this.deadlines.clearReconnect(record);
|
|
92
|
+
const remainingTimeoutMs = Math.max(1, Math.ceil(record.deadlineAt - this.deadlines.now()));
|
|
93
|
+
record.onReconnectTimeout = undefined;
|
|
135
94
|
record.socket = socket;
|
|
95
|
+
this.deadlines.armOperation(record, remainingTimeoutMs, (id) => { void this.expireOperation(id); });
|
|
136
96
|
rebound.push(record.id);
|
|
137
97
|
}
|
|
138
98
|
return rebound;
|
|
139
99
|
}
|
|
140
100
|
|
|
141
|
-
snapshot()
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
101
|
+
snapshot() {
|
|
102
|
+
return pendingRegistrySnapshot(this.byId.values(), this.byRequestKey.size, this.deadlines.now(), this.capacity);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private assertCanRegister(input: RegisterPendingCall): void {
|
|
106
|
+
const snapshot = this.snapshot();
|
|
107
|
+
const decision = toolCallAdmission({ active: snapshot.active, byTool: snapshot.by_tool }, this.capacity, input.tool);
|
|
108
|
+
if (!decision.allowed) {
|
|
109
|
+
const message = decision.reason === "ordinary_capacity"
|
|
110
|
+
? `ordinary daemon-call capacity reached (${decision.ordinaryMaximum}); control-plane capacity is reserved for diagnosis and recovery`
|
|
111
|
+
: "too many concurrent daemon tool calls";
|
|
112
|
+
throw new PendingCallRegistrationError("limit_exceeded", message, true);
|
|
150
113
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
114
|
+
if (this.byId.has(input.id)) throw new PendingCallRegistrationError("conflict", "duplicate internal daemon call id");
|
|
115
|
+
if (input.clientRequestKey && this.byRequestKey.has(input.clientRequestKey)) {
|
|
116
|
+
throw new PendingCallRegistrationError("conflict", "duplicate in-flight response-stream request key");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private add(input: RegisterPendingCall, settlement: PendingCallSettlement): void {
|
|
121
|
+
const startedAt = this.deadlines.now();
|
|
122
|
+
const timeoutMs = boundedPendingDelayMs(input.timeoutMs, relayContract.maximumRelayToolTimeoutMs);
|
|
123
|
+
const abortHandler = input.signal ? () => { void this.expire(input.id, input.onAbort, "pending daemon call was cancelled"); } : undefined;
|
|
124
|
+
const record: PendingCallRecord = {
|
|
125
|
+
id: input.id, socket: input.socket, daemonInstanceId: input.daemonInstanceId, clientRequestKey: input.clientRequestKey,
|
|
126
|
+
...(input.authority ? {
|
|
127
|
+
owner_kind: "account" as const, owner_account_id: input.authority.accountId,
|
|
128
|
+
owner_account_version: input.authority.accountVersion, owner_client_id: input.authority.clientId,
|
|
129
|
+
owner_family_id: input.authority.familyId,
|
|
130
|
+
} : {}),
|
|
131
|
+
tool: String(input.tool || "unknown"), ...(input.recovery ? { recovery: input.recovery } : {}),
|
|
132
|
+
startedAt, deadlineAt: startedAt + timeoutMs, remainingTimeoutMs: timeoutMs,
|
|
133
|
+
onTimeout: input.onTimeout, redeliverAfterProvenMissing: input.redeliverAfterProvenMissing, settlement, signal: input.signal, abortHandler,
|
|
158
134
|
};
|
|
135
|
+
this.byId.set(input.id, record);
|
|
136
|
+
if (input.clientRequestKey) this.byRequestKey.set(input.clientRequestKey, input.id);
|
|
137
|
+
this.deadlines.armOperation(record, timeoutMs, (id) => { void this.expireOperation(id); });
|
|
138
|
+
if (input.signal?.aborted) void this.expire(input.id, input.onAbort, "pending daemon call was cancelled");
|
|
139
|
+
else if (input.signal && abortHandler) input.signal.addEventListener("abort", abortHandler, { once: true });
|
|
159
140
|
}
|
|
160
141
|
|
|
142
|
+
private expireOperation(id: string): Promise<boolean> { return this.expireRecord(this.byId.get(id)); }
|
|
143
|
+
private expireReconnect(id: string): Promise<boolean> {
|
|
144
|
+
const record = this.byId.get(id); return record && !record.socket ? this.expireRecord(record) : Promise.resolve(false);
|
|
145
|
+
}
|
|
146
|
+
private expireRecord(record?: PendingCallRecord): Promise<boolean> {
|
|
147
|
+
if (!record) return Promise.resolve(false);
|
|
148
|
+
return record.socket
|
|
149
|
+
? this.fail(record.id, record.onTimeout, "pending daemon call timed out")
|
|
150
|
+
: this.fail(record.id, record.onReconnectTimeout, "pending daemon reconnect expired");
|
|
151
|
+
}
|
|
152
|
+
private expire(id: string, createError: ((record: PendingCallRecord) => Error) | undefined, fallback: string): Promise<boolean> {
|
|
153
|
+
const record = this.byId.get(id);
|
|
154
|
+
const applicable = record && (fallback.includes("reconnect") ? !record.socket : true);
|
|
155
|
+
return applicable ? this.fail(id, createError, fallback) : Promise.resolve(false);
|
|
156
|
+
}
|
|
157
|
+
private async fail(id: string, createError: ((record: PendingCallRecord) => Error) | undefined, fallback: string): Promise<boolean> {
|
|
158
|
+
const record = this.byId.get(id);
|
|
159
|
+
if (!record) return false;
|
|
160
|
+
let error: Error;
|
|
161
|
+
try { error = createError?.(record) ?? new Error(fallback); } catch { error = new Error(fallback); }
|
|
162
|
+
return this.finish(id, { ok: false, error });
|
|
163
|
+
}
|
|
164
|
+
private async finish(id: string, outcome: PendingCallOutcome): Promise<boolean> {
|
|
165
|
+
const record = this.take(id);
|
|
166
|
+
if (!record) return false;
|
|
167
|
+
outcome.ok ? record.settlement.resolve(outcome.value) : record.settlement.reject(outcome.error);
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
161
170
|
private take(id: string): PendingCallRecord | undefined {
|
|
162
171
|
const record = this.byId.get(id);
|
|
163
172
|
if (!record) return undefined;
|
|
164
|
-
|
|
165
|
-
if (record.reconnectTimeout) clearTimeout(record.reconnectTimeout);
|
|
173
|
+
this.deadlines.clear(record);
|
|
166
174
|
if (record.signal && record.abortHandler) record.signal.removeEventListener("abort", record.abortHandler);
|
|
167
175
|
this.byId.delete(id);
|
|
168
|
-
if (record.clientRequestKey && this.byRequestKey.get(record.clientRequestKey) === id)
|
|
169
|
-
this.byRequestKey.delete(record.clientRequestKey);
|
|
170
|
-
}
|
|
176
|
+
if (record.clientRequestKey && this.byRequestKey.get(record.clientRequestKey) === id) this.byRequestKey.delete(record.clientRequestKey);
|
|
171
177
|
return record;
|
|
172
178
|
}
|
|
173
179
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface AlarmStorage {
|
|
2
|
+
getAlarm(): Promise<number | null>;
|
|
3
|
+
setAlarm(scheduledTime: number | Date): Promise<void>;
|
|
4
|
+
deleteAlarm(): Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export async function writeEarliestRuntimeAlarm(input: {
|
|
8
|
+
storage: AlarmStorage;
|
|
9
|
+
nextDeadline: number;
|
|
10
|
+
now: number;
|
|
11
|
+
onError: (error: unknown) => void;
|
|
12
|
+
onMutation?: (action: "set" | "delete" | "noop") => void;
|
|
13
|
+
currentAlarm?: number | null;
|
|
14
|
+
}): Promise<void> {
|
|
15
|
+
try {
|
|
16
|
+
const current = input.currentAlarm === undefined ? await input.storage.getAlarm() : input.currentAlarm;
|
|
17
|
+
if (!Number.isFinite(input.nextDeadline)) {
|
|
18
|
+
if (current === null) {
|
|
19
|
+
input.onMutation?.("noop");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
await input.storage.deleteAlarm();
|
|
23
|
+
input.onMutation?.("delete");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const target = Math.max(input.now, input.nextDeadline);
|
|
27
|
+
if (current !== null && current > input.now && current <= target) {
|
|
28
|
+
input.onMutation?.("noop");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
await input.storage.setAlarm(target);
|
|
32
|
+
input.onMutation?.("set");
|
|
33
|
+
} catch (error) {
|
|
34
|
+
try { input.onError(error); }
|
|
35
|
+
catch { /* Alarm persistence already failed; observer failure cannot repair that storage outcome. */ }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { DAEMON_HELLO_TIMEOUT_MS, daemonLivenessDeadlineMs, daemonReadyDeadlineMs } from "./daemon-liveness.ts";
|
|
2
|
+
import type { DaemonChannel } from "./daemon-channel.ts";
|
|
3
|
+
import type { DaemonRegistry } from "./daemon-registry.ts";
|
|
4
|
+
import type { PendingCallRegistry } from "./pending-calls.ts";
|
|
5
|
+
import { writeEarliestRuntimeAlarm, type AlarmStorage } from "./runtime-alarm-storage.ts";
|
|
6
|
+
|
|
7
|
+
type InvalidateDaemonSocket = (
|
|
8
|
+
socket: WebSocket,
|
|
9
|
+
message: string,
|
|
10
|
+
closeReason: string,
|
|
11
|
+
errorCode?: string,
|
|
12
|
+
) => Promise<void>;
|
|
13
|
+
interface RuntimeAlarmContext {
|
|
14
|
+
storage: AlarmStorage;
|
|
15
|
+
pending: PendingCallRegistry;
|
|
16
|
+
daemonRegistry: DaemonRegistry;
|
|
17
|
+
invalidateDaemonSocket: InvalidateDaemonSocket;
|
|
18
|
+
invalidateDaemonChannel: (channel: DaemonChannel, message: string, errorCode?: string) => Promise<void>;
|
|
19
|
+
onScheduleError: (error: unknown) => void;
|
|
20
|
+
onAlarmMutation?: (action: "set" | "delete" | "noop") => void;
|
|
21
|
+
}
|
|
22
|
+
export async function processRuntimeAlarm(context: RuntimeAlarmContext, now = Date.now()): Promise<void> {
|
|
23
|
+
await context.pending.expireDue();
|
|
24
|
+
let nextDeadline = Number.POSITIVE_INFINITY;
|
|
25
|
+
for (const socket of context.daemonRegistry.candidateSockets()) {
|
|
26
|
+
const attachment = context.daemonRegistry.attachment(socket);
|
|
27
|
+
const connectedAt = Date.parse(attachment?.connectedAt ?? "");
|
|
28
|
+
const deadline = connectedAt + DAEMON_HELLO_TIMEOUT_MS;
|
|
29
|
+
if (!Number.isFinite(connectedAt) || deadline <= now) {
|
|
30
|
+
await context.invalidateDaemonSocket(
|
|
31
|
+
socket,
|
|
32
|
+
"daemon did not complete authentication",
|
|
33
|
+
"daemon hello timeout",
|
|
34
|
+
"daemon_hello_timeout",
|
|
35
|
+
);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
nextDeadline = Math.min(nextDeadline, deadline);
|
|
39
|
+
}
|
|
40
|
+
for (const socket of context.daemonRegistry.probingSockets()) {
|
|
41
|
+
const attachment = context.daemonRegistry.attachment(socket);
|
|
42
|
+
const readyDeadline = daemonReadyDeadlineMs(attachment);
|
|
43
|
+
const liveDeadline = daemonLivenessDeadlineMs(attachment);
|
|
44
|
+
if (!Number.isFinite(readyDeadline) || !Number.isFinite(liveDeadline) || Math.min(readyDeadline, liveDeadline) <= now) {
|
|
45
|
+
await context.invalidateDaemonSocket(
|
|
46
|
+
socket,
|
|
47
|
+
"daemon did not complete end-to-end readiness verification",
|
|
48
|
+
"daemon ready timeout",
|
|
49
|
+
"daemon_ready_timeout",
|
|
50
|
+
);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
nextDeadline = Math.min(nextDeadline, readyDeadline, liveDeadline);
|
|
54
|
+
}
|
|
55
|
+
for (const socket of context.daemonRegistry.readyRoleSockets()) {
|
|
56
|
+
const deadline = daemonLivenessDeadlineMs(context.daemonRegistry.readyAttachment(socket));
|
|
57
|
+
if (!Number.isFinite(deadline) || deadline <= now) {
|
|
58
|
+
await context.invalidateDaemonSocket(socket, "daemon became unresponsive", "daemon liveness timeout");
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
nextDeadline = Math.min(nextDeadline, deadline);
|
|
62
|
+
}
|
|
63
|
+
if (context.daemonRegistry.http) {
|
|
64
|
+
context.daemonRegistry.httpCandidates(now);
|
|
65
|
+
for (const channel of context.daemonRegistry.http.staleOwned(now)) {
|
|
66
|
+
await context.invalidateDaemonChannel(channel, "HTTPS fallback became unresponsive", "daemon_liveness_timeout");
|
|
67
|
+
}
|
|
68
|
+
nextDeadline = Math.min(nextDeadline, context.daemonRegistry.http.nextDeadline(now));
|
|
69
|
+
}
|
|
70
|
+
nextDeadline = Math.min(nextDeadline, pendingAlarmDeadline(context, now));
|
|
71
|
+
await writeEarliestRuntimeAlarm({
|
|
72
|
+
storage: context.storage,
|
|
73
|
+
nextDeadline,
|
|
74
|
+
now,
|
|
75
|
+
onError: context.onScheduleError,
|
|
76
|
+
onMutation: context.onAlarmMutation,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
export async function scheduleRuntimeAlarm(context: RuntimeAlarmContext, now = Date.now()): Promise<void> {
|
|
80
|
+
try {
|
|
81
|
+
let nextDeadline = Number.POSITIVE_INFINITY;
|
|
82
|
+
for (const socket of context.daemonRegistry.candidateSockets()) {
|
|
83
|
+
const connectedAt = Date.parse(context.daemonRegistry.attachment(socket)?.connectedAt ?? "");
|
|
84
|
+
if (!Number.isFinite(connectedAt)) {
|
|
85
|
+
await context.invalidateDaemonSocket(
|
|
86
|
+
socket,
|
|
87
|
+
"daemon candidate timestamp is invalid",
|
|
88
|
+
"invalid daemon candidate timestamp",
|
|
89
|
+
"daemon_hello_timeout",
|
|
90
|
+
);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
nextDeadline = Math.min(nextDeadline, connectedAt + DAEMON_HELLO_TIMEOUT_MS);
|
|
94
|
+
}
|
|
95
|
+
for (const socket of context.daemonRegistry.probingSockets()) {
|
|
96
|
+
const attachment = context.daemonRegistry.attachment(socket);
|
|
97
|
+
const readyDeadline = daemonReadyDeadlineMs(attachment);
|
|
98
|
+
const liveDeadline = daemonLivenessDeadlineMs(attachment);
|
|
99
|
+
if (!Number.isFinite(readyDeadline) || !Number.isFinite(liveDeadline)) {
|
|
100
|
+
await context.invalidateDaemonSocket(
|
|
101
|
+
socket,
|
|
102
|
+
"daemon readiness state is invalid",
|
|
103
|
+
"daemon ready timeout",
|
|
104
|
+
"daemon_ready_timeout",
|
|
105
|
+
);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
nextDeadline = Math.min(nextDeadline, readyDeadline, liveDeadline);
|
|
109
|
+
}
|
|
110
|
+
for (const socket of context.daemonRegistry.readyRoleSockets()) {
|
|
111
|
+
const deadline = daemonLivenessDeadlineMs(context.daemonRegistry.readyAttachment(socket));
|
|
112
|
+
if (!Number.isFinite(deadline)) {
|
|
113
|
+
await context.invalidateDaemonSocket(socket, "daemon became unresponsive", "invalid daemon liveness timestamp");
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
nextDeadline = Math.min(nextDeadline, deadline);
|
|
117
|
+
}
|
|
118
|
+
if (context.daemonRegistry.http) {
|
|
119
|
+
context.daemonRegistry.httpCandidates(now);
|
|
120
|
+
nextDeadline = Math.min(nextDeadline, context.daemonRegistry.http.nextDeadline(now));
|
|
121
|
+
}
|
|
122
|
+
const currentAlarm = await context.storage.getAlarm();
|
|
123
|
+
nextDeadline = Math.min(nextDeadline, pendingAlarmDeadline(context, now));
|
|
124
|
+
await writeEarliestRuntimeAlarm({
|
|
125
|
+
storage: context.storage, nextDeadline, now, currentAlarm,
|
|
126
|
+
onError: context.onScheduleError, onMutation: context.onAlarmMutation,
|
|
127
|
+
});
|
|
128
|
+
} catch (error) {
|
|
129
|
+
try { context.onScheduleError(error); }
|
|
130
|
+
catch { /* The error observer must not recursively fail the Durable Object alarm callback. */ }
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function pendingAlarmDeadline(context: RuntimeAlarmContext, now: number): number {
|
|
134
|
+
const delay = context.pending.nextDeadlineDelayMs();
|
|
135
|
+
return Number.isFinite(delay) ? now + Math.max(1, Math.ceil(delay)) : Number.POSITIVE_INFINITY;
|
|
136
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export function workerGlobalActivityVisible(authorization: { account_role_is_owner?: unknown }): boolean {
|
|
2
|
+
return authorization.account_role_is_owner === true;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function hiddenWorkerPending(value: Record<string, unknown>): Record<string, unknown> {
|
|
6
|
+
return {
|
|
7
|
+
maximum: value.maximum ?? 0,
|
|
8
|
+
ordinary_capacity: value.ordinary_capacity ?? 0,
|
|
9
|
+
reserved_capacity: value.reserved_capacity ?? 0,
|
|
10
|
+
activity_hidden_by_authority: true,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function hiddenWorkerActivity(): Record<string, unknown> {
|
|
15
|
+
return { activity_hidden_by_authority: true };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function projectDaemonStatus(value: Record<string, unknown>, fullToolNamesVisible: boolean): Record<string, unknown> {
|
|
19
|
+
if (fullToolNamesVisible) return value;
|
|
20
|
+
return {
|
|
21
|
+
connected: value.connected ?? false,
|
|
22
|
+
count: value.count ?? 0,
|
|
23
|
+
tool_count: value.tool_count ?? 0,
|
|
24
|
+
connected_at: value.connected_at ?? null,
|
|
25
|
+
last_seen_at: value.last_seen_at ?? null,
|
|
26
|
+
readiness_verified: value.readiness_verified ?? false,
|
|
27
|
+
readiness_timeout_ms: value.readiness_timeout_ms ?? null,
|
|
28
|
+
liveness_timeout_ms: value.liveness_timeout_ms ?? null,
|
|
29
|
+
policy: value.policy ?? null,
|
|
30
|
+
policy_scope: value.policy_scope ?? "daemon_capability_ceiling_not_account_authority",
|
|
31
|
+
tools: [],
|
|
32
|
+
tools_scope: value.tools_scope ?? "daemon_advertised_before_account_role_filtering",
|
|
33
|
+
tools_hidden_by_authority: true,
|
|
34
|
+
relay_transport: value.relay_transport ?? null,
|
|
35
|
+
};
|
|
36
|
+
}
|