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
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { lstatSync, mkdirSync, renameSync, rmSync, rmdirSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { removeOwnedJsonFileSync, replaceFileAtomicallySync } from "./exclusive-file.mjs";
|
|
5
|
+
import { filesystemIdentity, filesystemTimeMs } from "./filesystem-identity.mjs";
|
|
6
|
+
import { createMonotonicDeadline } from "./monotonic-deadline.mjs";
|
|
7
|
+
import { currentProcessStartTimeMs, inspectProcessInstance } from "./process-identity.mjs";
|
|
8
|
+
import { readBoundedRegularFileSync } from "./secure-file.mjs";
|
|
9
|
+
import { recoverResourceTransactionOwnerStaging } from "./resource-staging-recovery.mjs";
|
|
10
|
+
const LOCK_NAME = "transaction.lock";
|
|
11
|
+
const OWNER_NAME = "owner.json";
|
|
12
|
+
const LOCK_PURPOSE = "resource-coordinator";
|
|
13
|
+
const LOCK_WAIT_MS = 5_000;
|
|
14
|
+
const INCOMPLETE_OWNER_GRACE_MS = 5_000;
|
|
15
|
+
const MAX_OWNER_BYTES = 8 * 1024;
|
|
16
|
+
// Keep the schema-1 directory wire shape while beta.60 can still be the live daemon.
|
|
17
|
+
// The directory mkdir is the cross-version transition mutex; owner.json is atomically
|
|
18
|
+
// materialized immediately afterwards and incomplete claims are only reclaimable after
|
|
19
|
+
// the bounded incomplete-owner grace.
|
|
20
|
+
export async function withResourceTransactionLock(root, callback, options = {}) {
|
|
21
|
+
if (typeof callback !== "function") throw new TypeError("resource transaction lock requires a callback");
|
|
22
|
+
const lockPath = join(root, LOCK_NAME);
|
|
23
|
+
const now = typeof options.now === "function" ? options.now : Date.now;
|
|
24
|
+
const sleep = typeof options.sleep === "function" ? options.sleep : defaultSleep;
|
|
25
|
+
const random = typeof options.random === "function" ? options.random : Math.random;
|
|
26
|
+
const deadline = createMonotonicDeadline(positiveInteger(options.timeoutMs, LOCK_WAIT_MS));
|
|
27
|
+
const owner = directoryOwner(now());
|
|
28
|
+
|
|
29
|
+
while (true) {
|
|
30
|
+
const acquired = tryAcquireDirectoryLock(lockPath, owner);
|
|
31
|
+
if (acquired) return runWithDirectoryLock(lockPath, acquired, owner.token, callback);
|
|
32
|
+
const observed = inspectExistingLock(lockPath, now());
|
|
33
|
+
if (observed.kind === "missing") continue;
|
|
34
|
+
if (observed.reclaimable && typeof options.beforeReclaim === "function") await options.beforeReclaim(observed);
|
|
35
|
+
if (observed.reclaimable && reclaimObservedLock(lockPath, observed, options)) continue;
|
|
36
|
+
if (deadline.expired()) throw Object.assign(new Error("resource coordinator transaction state is busy; retry after the current operation finishes"), { code: "MBM_RESOURCE_TRANSACTION_BUSY" });
|
|
37
|
+
await sleep(Math.min(25 + Math.floor(random() * 50), Math.max(1, deadline.remainingMs())));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function tryAcquireDirectoryLock(lockPath, owner) {
|
|
42
|
+
try { mkdirSync(lockPath, { mode: 0o700 }); }
|
|
43
|
+
catch (error) { if (error?.code === "EEXIST") return null; throw error; }
|
|
44
|
+
const identity = directoryIdentity(lstatDirectory(lockPath));
|
|
45
|
+
try {
|
|
46
|
+
replaceFileAtomicallySync(join(lockPath, OWNER_NAME), `${JSON.stringify(owner)}\n`, { mode: 0o600 });
|
|
47
|
+
return identity;
|
|
48
|
+
} catch (error) {
|
|
49
|
+
let cleanupError = null;
|
|
50
|
+
try { removeDirectoryGeneration(lockPath, identity, null, "failed acquisition"); } catch (failure) { cleanupError = failure; }
|
|
51
|
+
if (cleanupError) throw new AggregateError([error, cleanupError], "resource transaction acquisition failed and cleanup was incomplete");
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function runWithDirectoryLock(lockPath, identity, token, callback) {
|
|
57
|
+
let result;
|
|
58
|
+
let callbackError = null;
|
|
59
|
+
try { result = await callback(); } catch (error) { callbackError = error; }
|
|
60
|
+
let releaseError = null;
|
|
61
|
+
try {
|
|
62
|
+
if (!removeDirectoryGeneration(lockPath, identity, token, "release")) {
|
|
63
|
+
releaseError = new Error("resource coordinator transaction lock changed before release; state may require inspection");
|
|
64
|
+
}
|
|
65
|
+
} catch (error) {
|
|
66
|
+
releaseError = new Error("resource coordinator transaction lock release failed; state may require inspection", { cause: error });
|
|
67
|
+
}
|
|
68
|
+
if (callbackError && releaseError) throw new AggregateError([callbackError, releaseError], "resource transaction failed and lock release was incomplete");
|
|
69
|
+
if (releaseError) throw releaseError;
|
|
70
|
+
if (callbackError) throw callbackError;
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function inspectExistingLock(lockPath, now) {
|
|
75
|
+
let info;
|
|
76
|
+
try { info = lstatSync(lockPath, { bigint: true }); }
|
|
77
|
+
catch (error) { if (error?.code === "ENOENT") return { kind: "missing", reclaimable: false }; throw error; }
|
|
78
|
+
if (info.isSymbolicLink()) throw new Error("resource coordinator transaction lock must not be a symbolic link");
|
|
79
|
+
if (info.isDirectory()) return inspectDirectoryLock(lockPath, info, now);
|
|
80
|
+
if (info.isFile()) return inspectPriorFileLock(lockPath);
|
|
81
|
+
throw new Error("resource coordinator transaction lock has an unsupported filesystem type");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function inspectDirectoryLock(lockPath, info, now) {
|
|
85
|
+
const identity = directoryIdentity(info);
|
|
86
|
+
let owner;
|
|
87
|
+
try { owner = readJson(join(lockPath, OWNER_NAME), MAX_OWNER_BYTES, "resource transaction owner"); }
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (!isMissing(error)) throw error;
|
|
90
|
+
const age = now - filesystemTimeMs(info.mtimeMs, "resource transaction lock modification time");
|
|
91
|
+
return { kind: "directory", identity, owner: null, reclaimable: age >= INCOMPLETE_OWNER_GRACE_MS };
|
|
92
|
+
}
|
|
93
|
+
if (!validDirectoryOwner(owner)) throw new Error("resource coordinator transaction owner is invalid");
|
|
94
|
+
const status = inspectProcessInstance({ pid: owner.pid, startedAt: owner.started_at, processStartedAt: owner.process_started_at });
|
|
95
|
+
return { kind: "directory", identity, owner, reclaimable: status.reclaimable === true };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function inspectPriorFileLock(lockPath) {
|
|
99
|
+
const owner = readJson(lockPath, MAX_OWNER_BYTES, "resource transaction owner-state lock");
|
|
100
|
+
if (!validPriorFileOwner(owner)) throw new Error("resource coordinator prior owner-state lock is invalid");
|
|
101
|
+
const status = inspectProcessInstance(owner);
|
|
102
|
+
return { kind: "file", owner, reclaimable: status.reclaimable === true };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function reclaimObservedLock(lockPath, observed, options = {}) {
|
|
106
|
+
if (observed.kind === "file") {
|
|
107
|
+
return removeOwnedJsonFileSync(lockPath, { token: observed.owner.token, purpose: LOCK_PURPOSE }, { maxBytes: MAX_OWNER_BYTES });
|
|
108
|
+
}
|
|
109
|
+
return removeDirectoryGeneration(lockPath, observed.identity, observed.owner?.token ?? null, "stale recovery", {
|
|
110
|
+
expectedOwnerMissing: observed.owner === null,
|
|
111
|
+
beforeRestore: options.beforeRestore,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function removeDirectoryGeneration(lockPath, expectedIdentity, expectedToken, reason, { expectedOwnerMissing = false, beforeRestore = null } = {}) {
|
|
116
|
+
if (expectedOwnerMissing) {
|
|
117
|
+
let current; try { current = lstatDirectory(lockPath); } catch (error) { if (error?.code === "ENOENT") return false; throw error; }
|
|
118
|
+
if (!sameDirectoryIdentity(expectedIdentity, directoryIdentity(current))) return false;
|
|
119
|
+
try { readJson(join(lockPath, OWNER_NAME), MAX_OWNER_BYTES, "resource transaction owner"); return false; }
|
|
120
|
+
catch (error) { if (!isMissing(error)) throw error; }
|
|
121
|
+
if (!recoverResourceTransactionOwnerStaging(lockPath)) return false;
|
|
122
|
+
try { rmdirSync(lockPath); return true; }
|
|
123
|
+
catch (error) { if (["ENOENT", "ENOTEMPTY", "EEXIST"].includes(error?.code)) return false; throw error; }
|
|
124
|
+
}
|
|
125
|
+
const quarantine = `${lockPath}.stale.${process.pid}.${randomBytes(8).toString("hex")}`;
|
|
126
|
+
try { renameSync(lockPath, quarantine); }
|
|
127
|
+
catch (error) { if (error?.code === "ENOENT") return false; throw error; }
|
|
128
|
+
let verified = false;
|
|
129
|
+
try {
|
|
130
|
+
const movedInfo = lstatDirectory(quarantine);
|
|
131
|
+
if (!sameDirectoryIdentity(expectedIdentity, directoryIdentity(movedInfo))) throw new Error(`resource transaction directory changed during ${reason}`);
|
|
132
|
+
if (expectedToken !== null) {
|
|
133
|
+
const movedOwner = readJson(join(quarantine, OWNER_NAME), MAX_OWNER_BYTES, "resource transaction owner");
|
|
134
|
+
if (!validDirectoryOwner(movedOwner) || movedOwner.token !== expectedToken) throw new Error(`resource transaction owner changed during ${reason}`);
|
|
135
|
+
}
|
|
136
|
+
verified = true;
|
|
137
|
+
} finally { if (!verified) restoreQuarantine(lockPath, quarantine, beforeRestore); }
|
|
138
|
+
rmSync(quarantine, { recursive: true, force: false });
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
function restoreQuarantine(lockPath, quarantine, beforeRestore = null) {
|
|
142
|
+
if (typeof beforeRestore === "function") beforeRestore({ lockPath, quarantine });
|
|
143
|
+
try { lstatSync(lockPath); throw new Error("resource transaction lock was replaced before quarantine restore; state requires inspection"); }
|
|
144
|
+
catch (error) { if (error?.code !== "ENOENT") throw error; }
|
|
145
|
+
try { renameSync(quarantine, lockPath); }
|
|
146
|
+
catch (error) { throw new Error("resource transaction quarantine could not be restored; state requires inspection", { cause: error }); }
|
|
147
|
+
}
|
|
148
|
+
function directoryOwner(now) {
|
|
149
|
+
return { schema_version: 1, token: randomBytes(16).toString("hex"), pid: process.pid,
|
|
150
|
+
started_at: new Date(now).toISOString(), process_started_at: new Date(currentProcessStartTimeMs()).toISOString() };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function validDirectoryOwner(owner) {
|
|
154
|
+
return Number.isInteger(owner?.pid) && owner.pid > 0
|
|
155
|
+
&& Number.isFinite(Date.parse(String(owner.started_at || "")))
|
|
156
|
+
&& Number.isFinite(Date.parse(String(owner.process_started_at || "")))
|
|
157
|
+
&& /^[a-f0-9]{32}$/.test(String(owner.token || ""));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function validPriorFileOwner(owner) {
|
|
161
|
+
return Number.isInteger(owner?.pid) && owner.pid > 0
|
|
162
|
+
&& owner.purpose === LOCK_PURPOSE
|
|
163
|
+
&& Number.isFinite(Date.parse(String(owner.startedAt || "")))
|
|
164
|
+
&& Number.isFinite(Date.parse(String(owner.processStartedAt || "")))
|
|
165
|
+
&& /^[a-f0-9]{32}$/.test(String(owner.token || ""));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function readJson(file, maxBytes, label) {
|
|
169
|
+
const text = readBoundedRegularFileSync(file, maxBytes, label, { verifyPathIdentity: true, rejectMultipleLinks: true }).toString("utf8");
|
|
170
|
+
let value;
|
|
171
|
+
try { value = JSON.parse(text); } catch { throw new Error(`${label} is not valid JSON`); }
|
|
172
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must contain a JSON object`);
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function lstatDirectory(path) {
|
|
177
|
+
const info = lstatSync(path, { bigint: true });
|
|
178
|
+
if (info.isSymbolicLink() || !info.isDirectory()) throw new Error("resource coordinator transaction lock must be a real directory");
|
|
179
|
+
return info;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function directoryIdentity(info) {
|
|
183
|
+
const identity = filesystemIdentity(info, "resource transaction directory");
|
|
184
|
+
return { dev: identity.dev, ino: identity.ino };
|
|
185
|
+
}
|
|
186
|
+
function sameDirectoryIdentity(left, right) { return left?.dev === right?.dev && left?.ino === right?.ino; }
|
|
187
|
+
function isMissing(error) { return error?.code === "ENOENT" || error?.cause?.code === "ENOENT"; }
|
|
188
|
+
function positiveInteger(value, fallback) { const number = Number(value); return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback; }
|
|
189
|
+
function defaultSleep(milliseconds) { return new Promise((resolvePromise) => { setTimeout(resolvePromise, milliseconds); }); }
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const changeSignals = new WeakMap();
|
|
2
|
+
|
|
3
|
+
export function resourceSleep(ms, signal) {
|
|
4
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
5
|
+
let timer;
|
|
6
|
+
const cleanup = () => signal?.removeEventListener("abort", onAbort);
|
|
7
|
+
const onAbort = () => {
|
|
8
|
+
clearTimeout(timer);
|
|
9
|
+
cleanup();
|
|
10
|
+
rejectPromise(signal?.reason instanceof Error ? signal.reason : new Error("resource admission cancelled"));
|
|
11
|
+
};
|
|
12
|
+
timer = setTimeout(() => { cleanup(); resolvePromise(); }, ms);
|
|
13
|
+
if (signal?.aborted) onAbort();
|
|
14
|
+
else signal?.addEventListener("abort", onAbort, { once: true });
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function resourceChangeSignal(owner) {
|
|
19
|
+
let controller = changeSignals.get(owner);
|
|
20
|
+
if (!controller || controller.signal.aborted) { controller = new AbortController(); changeSignals.set(owner, controller); }
|
|
21
|
+
return controller.signal;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function signalResourceChange(owner) {
|
|
25
|
+
const previous = changeSignals.get(owner);
|
|
26
|
+
changeSignals.set(owner, new AbortController());
|
|
27
|
+
previous?.abort(new Error("resource capacity changed"));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function waitForResourceChange(changeSignal, ms, sleep = resourceSleep, signal) {
|
|
31
|
+
const combined = signal ? AbortSignal.any([signal, changeSignal]) : changeSignal;
|
|
32
|
+
try { await sleep(ms, combined); return false; }
|
|
33
|
+
catch (error) { if (changeSignal.aborted && !signal?.aborted) return true; throw error; }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function resourceRetryDelayMs({ attempt = 0, priority = "ordinary", reason = "", remainingMs = 0, random = Math.random } = {}) {
|
|
37
|
+
const base = priority === "interactive" ? 100 : priority === "background" ? 500 : 200;
|
|
38
|
+
const cap = priority === "interactive" ? 600 : priority === "background" ? 3_000 : 1_500;
|
|
39
|
+
const step = Math.max(0, Math.min(4, Math.floor(Number(attempt) || 0)));
|
|
40
|
+
let minimum = Math.min(cap, base * (2 ** step));
|
|
41
|
+
if (reason === "host_pressure_red") minimum = Math.max(minimum, Math.min(cap, priority === "interactive" ? 300 : 600));
|
|
42
|
+
else if (reason === "cpu_pressure_window") minimum = Math.max(minimum, Math.min(cap, priority === "interactive" ? 150 : 300));
|
|
43
|
+
const maximum = Math.min(cap, Math.max(minimum, Math.floor(minimum * 1.5)));
|
|
44
|
+
const unit = Math.max(0, Math.min(1, Number(random?.()) || 0));
|
|
45
|
+
const delay = minimum + Math.floor((maximum - minimum) * unit);
|
|
46
|
+
const remaining = Math.max(1, Math.floor(Number(remainingMs) || 1));
|
|
47
|
+
return Math.max(1, Math.min(delay, remaining));
|
|
48
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { createExclusiveFileSync, removeOwnedJsonFileSync } from "./exclusive-file.mjs";
|
|
4
|
+
import { currentProcessStartTimeMs, inspectProcessInstance } from "./process-identity.mjs";
|
|
5
|
+
import { readBoundedRegularFileSync } from "./secure-file.mjs";
|
|
6
|
+
import { validateResourceRequest } from "./resource-request-contract.mjs";
|
|
7
|
+
const SCHEMA = 1;
|
|
8
|
+
const WAITER_FILE = /^wait_[a-f0-9]{32}\.json$/;
|
|
9
|
+
const MAX_WAITER_BYTES = 16 * 1024;
|
|
10
|
+
const WAITER_GRACE_MS = 5_000;
|
|
11
|
+
const AGING_MS = 120_000;
|
|
12
|
+
const PROTECTION_FLOOR_MS = AGING_MS;
|
|
13
|
+
const LEASE_DRAIN_REASONS = new Set(["project_resource_busy", "cpu_reservation", "io_reservation", "memory_reservation", "disk_reserve_floor"]);
|
|
14
|
+
export function createResourceWaiter(waitersDir, request, waitMs, now = Date.now()) {
|
|
15
|
+
const waiterId = randomBytes(16).toString("hex");
|
|
16
|
+
const token = randomBytes(32).toString("hex");
|
|
17
|
+
const expiresAt = now + Math.max(5_000, Number(waitMs) || 0) + WAITER_GRACE_MS;
|
|
18
|
+
const waiter = {
|
|
19
|
+
schema_version: SCHEMA,
|
|
20
|
+
waiter_id: waiterId,
|
|
21
|
+
token,
|
|
22
|
+
enqueued_at: new Date(now).toISOString(),
|
|
23
|
+
expires_at: new Date(expiresAt).toISOString(),
|
|
24
|
+
owner: { pid: process.pid, process_started_at: new Date(currentProcessStartTimeMs()).toISOString() },
|
|
25
|
+
request: normalizeWaiterRequest(request),
|
|
26
|
+
};
|
|
27
|
+
createExclusiveFileSync(waiterPath(waitersDir, waiterId), `${JSON.stringify(waiter)}\n`, { mode: 0o600 });
|
|
28
|
+
return waiter;
|
|
29
|
+
}
|
|
30
|
+
export function removeResourceWaiter(waitersDir, waiter) {
|
|
31
|
+
if (!waiter?.waiter_id || !waiter?.token) return false;
|
|
32
|
+
return removeOwnedJsonFileSync(waiterPath(waitersDir, waiter.waiter_id), {
|
|
33
|
+
waiter_id: waiter.waiter_id, token: waiter.token,
|
|
34
|
+
}, { maxBytes: MAX_WAITER_BYTES });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function pruneAndReadResourceWaiters(waitersDir, entries, now = Date.now()) {
|
|
38
|
+
const waiters = [];
|
|
39
|
+
for (const entry of entries) {
|
|
40
|
+
if (!entry.isFile() || !WAITER_FILE.test(entry.name)) throw new Error("resource coordinator waiter directory contains an unexpected entry");
|
|
41
|
+
const file = join(waitersDir, entry.name);
|
|
42
|
+
let waiter;
|
|
43
|
+
try { waiter = readWaiter(file); }
|
|
44
|
+
catch (error) { if (error?.code === "ENOENT" || error?.cause?.code === "ENOENT") continue; throw error; }
|
|
45
|
+
validateWaiter(waiter);
|
|
46
|
+
if (waiterIsStale(waiter, now)) {
|
|
47
|
+
if (!removeResourceWaiter(waitersDir, waiter)) throw new Error("resource waiter changed during stale pruning");
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
waiters.push(waiter);
|
|
51
|
+
}
|
|
52
|
+
return waiters;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function selectedResourceWaiter(waiters, leases, host, evaluate, now = Date.now()) { return resourceWaiterSelectionState(waiters, leases, host, evaluate, now).selected; }
|
|
56
|
+
export function resourceWaiterDrainActive(waiters, leases, host, evaluate, now = Date.now()) { return resourceWaiterSelectionState(waiters, leases, host, evaluate, now).drainActive; }
|
|
57
|
+
function resourceWaiterSelectionState(waiters, leases, host, evaluate, now) {
|
|
58
|
+
const ranked = [...waiters].sort((left, right) => compareWaiters(left, right, now));
|
|
59
|
+
const selected = ranked.find((waiter) => evaluate(host, leases, waiter.request, now, waiter).admitted) || null;
|
|
60
|
+
const protectedWaiter = leases.length ? ranked.find((waiter) => {
|
|
61
|
+
const current = evaluate(host, leases, waiter.request, now, waiter); const empty = evaluate(host, [], waiter.request, now, waiter);
|
|
62
|
+
return resourceWaiterProtected(waiter, now) && LEASE_DRAIN_REASONS.has(current.reason) && (empty.admitted || empty.reason === "cpu_pressure_window");
|
|
63
|
+
}) : null;
|
|
64
|
+
const drainActive = Boolean(protectedWaiter && (!selected || compareWaiters(protectedWaiter, selected, now) <= 0));
|
|
65
|
+
return { selected: drainActive ? null : selected, drainActive };
|
|
66
|
+
}
|
|
67
|
+
export function resourceWaiterRank(waiter, now = Date.now()) {
|
|
68
|
+
const base = waiterBaseRank(waiter);
|
|
69
|
+
const enqueued = Date.parse(String(waiter?.enqueued_at || ""));
|
|
70
|
+
const ageBoost = Number.isFinite(enqueued) ? Math.floor(Math.max(0, now - enqueued) / AGING_MS) : 0;
|
|
71
|
+
return Math.max(0, base - ageBoost);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function resourceWaiterProtected(waiter, now = Date.now()) {
|
|
75
|
+
const enqueued = Date.parse(String(waiter?.enqueued_at || ""));
|
|
76
|
+
if (!Number.isFinite(enqueued) || resourceWaiterRank(waiter, now) !== 0) return false;
|
|
77
|
+
const protectionAge = Math.max(PROTECTION_FLOOR_MS, waiterBaseRank(waiter) * AGING_MS);
|
|
78
|
+
return Math.max(0, now - enqueued) >= protectionAge;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function resourceWaiterQueueSnapshot(waiters, now = Date.now()) {
|
|
82
|
+
const byPriority = { interactive: 0, ordinary: 0, background: 0 };
|
|
83
|
+
let oldest = null;
|
|
84
|
+
for (const waiter of waiters || []) {
|
|
85
|
+
const priority = ["interactive", "ordinary", "background"].includes(waiter?.request?.priority) ? waiter.request.priority : "ordinary";
|
|
86
|
+
byPriority[priority] += 1;
|
|
87
|
+
const enqueued = Date.parse(String(waiter?.enqueued_at || ""));
|
|
88
|
+
if (Number.isFinite(enqueued) && (oldest === null || enqueued < oldest)) oldest = enqueued;
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
active: (waiters || []).length,
|
|
92
|
+
by_priority: byPriority,
|
|
93
|
+
protected: (waiters || []).filter((waiter) => resourceWaiterProtected(waiter, now)).length,
|
|
94
|
+
oldest_ms: oldest === null ? 0 : Math.max(0, now - oldest),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function waiterBaseRank(waiter) {
|
|
99
|
+
return waiter?.request?.priority === "interactive" ? 0 : waiter?.request?.priority === "ordinary" ? 1 : 2;
|
|
100
|
+
}
|
|
101
|
+
function compareWaiters(left, right, now) {
|
|
102
|
+
const rank = resourceWaiterRank(left, now) - resourceWaiterRank(right, now);
|
|
103
|
+
if (rank) return rank;
|
|
104
|
+
const time = Date.parse(left.enqueued_at) - Date.parse(right.enqueued_at);
|
|
105
|
+
return time || String(left.waiter_id).localeCompare(String(right.waiter_id));
|
|
106
|
+
}
|
|
107
|
+
function waiterIsStale(waiter, now) {
|
|
108
|
+
if (Date.parse(waiter.expires_at) < now) return true;
|
|
109
|
+
const status = inspectProcessInstance({
|
|
110
|
+
pid: waiter.owner.pid,
|
|
111
|
+
startedAt: waiter.enqueued_at,
|
|
112
|
+
processStartedAt: waiter.owner.process_started_at,
|
|
113
|
+
});
|
|
114
|
+
return status.reclaimable === true;
|
|
115
|
+
}
|
|
116
|
+
function validateWaiter(waiter) {
|
|
117
|
+
if (waiter?.schema_version !== SCHEMA || !/^[a-f0-9]{32}$/.test(String(waiter.waiter_id || ""))
|
|
118
|
+
|| !/^[a-f0-9]{64}$/.test(String(waiter.token || ""))) throw new Error("resource coordinator waiter is invalid");
|
|
119
|
+
if (!Number.isFinite(Date.parse(String(waiter.enqueued_at || ""))) || !Number.isFinite(Date.parse(String(waiter.expires_at || "")))
|
|
120
|
+
|| !Number.isInteger(waiter.owner?.pid) || waiter.owner.pid <= 0
|
|
121
|
+
|| !Number.isFinite(Date.parse(String(waiter.owner?.process_started_at || "")))) throw new Error("resource coordinator waiter ownership is invalid");
|
|
122
|
+
validateResourceRequest(waiter.request);
|
|
123
|
+
}
|
|
124
|
+
function readWaiter(file) {
|
|
125
|
+
const text = readBoundedRegularFileSync(file, MAX_WAITER_BYTES, "resource waiter", {
|
|
126
|
+
verifyPathIdentity: true, rejectMultipleLinks: true,
|
|
127
|
+
}).toString("utf8");
|
|
128
|
+
const value = JSON.parse(text);
|
|
129
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("resource coordinator waiter is invalid");
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
function waiterPath(waitersDir, id) {
|
|
133
|
+
if (!/^[a-f0-9]{32}$/.test(String(id || ""))) throw new Error("invalid resource waiter id");
|
|
134
|
+
return join(waitersDir, `wait_${id}.json`);
|
|
135
|
+
}
|
|
136
|
+
function normalizeWaiterRequest(request = {}) {
|
|
137
|
+
const keys = ["family", "resource_class", "priority", "cpu", "io", "memory_mb", "disk_reserve_bytes", "heavy", "compiler_jobs", "unbounded", "serialize_project", "contention_key"];
|
|
138
|
+
return Object.fromEntries(keys.map((key) => [key, request[key] ?? null]));
|
|
139
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function xcodeBuildJobPlan(argv, maximum = 1024) {
|
|
2
|
+
let plan = null; let seen = 0;
|
|
3
|
+
for (let index = 1; index < (argv || []).length; index += 1) {
|
|
4
|
+
const value = String(argv[index] || "");
|
|
5
|
+
if (value === "-jobs") {
|
|
6
|
+
seen += 1; if (seen > 1 || index + 1 >= argv.length) return invalidPlan();
|
|
7
|
+
plan = xcodeJobValuePlan(argv[index + 1], maximum); index += 1;
|
|
8
|
+
} else if (value.startsWith("-jobs=")) {
|
|
9
|
+
return unboundedPlan();
|
|
10
|
+
} else if (value.startsWith("-jobs")) return unboundedPlan();
|
|
11
|
+
}
|
|
12
|
+
return plan;
|
|
13
|
+
}
|
|
14
|
+
export function xcodeHasIndependentTestFanout(argv) {
|
|
15
|
+
return (argv || []).slice(1).some((value) => ["test", "test-without-building"].includes(String(value)));
|
|
16
|
+
}
|
|
17
|
+
export function xcodeResourcePlan(base, argv, defaultJobs = 3, maximum = 1024) {
|
|
18
|
+
if (base !== "xcodebuild") return { resourceClass: "unbounded", cpu: 6, compilerJobs: null, elastic: false, unbounded: true };
|
|
19
|
+
const build = xcodeBuildJobPlan(argv, maximum); if (build?.invalid) return build;
|
|
20
|
+
const plan = build || { jobs: defaultJobs, elastic: true, unbounded: false }; const unbounded = plan.unbounded || xcodeHasIndependentTestFanout(argv);
|
|
21
|
+
return { resourceClass: unbounded ? "unbounded" : "mixed", cpu: unbounded ? 6 : plan.jobs, compilerJobs: plan.unbounded ? null : plan.jobs, elastic: !unbounded && plan.elastic, unbounded };
|
|
22
|
+
}
|
|
23
|
+
function xcodeJobValuePlan(value, maximum) {
|
|
24
|
+
const text = String(value ?? "");
|
|
25
|
+
if (/^\+?\d+$/.test(text)) {
|
|
26
|
+
const parsed = Number(text); if (!Number.isSafeInteger(parsed)) return unboundedPlan();
|
|
27
|
+
if (parsed < 1) return invalidPlan();
|
|
28
|
+
return parsed <= maximum ? { jobs: parsed, elastic: false, unbounded: false } : unboundedPlan();
|
|
29
|
+
}
|
|
30
|
+
if (/^-\d+$/.test(text)) return invalidPlan();
|
|
31
|
+
return unboundedPlan();
|
|
32
|
+
}
|
|
33
|
+
function invalidPlan() { return { jobs: null, elastic: false, unbounded: false, invalid: true }; }
|
|
34
|
+
function unboundedPlan() { return { jobs: null, elastic: false, unbounded: true }; }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const GIB = 1024 ** 3;
|
|
2
|
+
const PACKAGE = new Set(["-resolvePackageDependencies"]);
|
|
3
|
+
const ARTIFACT = new Set(["-exportArchive", "-exportNotarizedApp", "-exportLocalizations", "-importLocalizations", "-create-xcframework"]);
|
|
4
|
+
const MAINTENANCE = new Set(["-runFirstLaunch", "-downloadAllPlatforms", "-prepareDeviceSupport", "-downloadPlatform", "-importPlatform", "-downloadComponent", "-importComponent"]);
|
|
5
|
+
const DELETE = new Set(["-deleteComponent"]);
|
|
6
|
+
const QUERY = new Set(["-showComponent", "-showdestinations", "-showTestPlans", "-showBuildSettings", "-showBuildSettingsForIndex", "-find-executable", "-find-library"]);
|
|
7
|
+
|
|
8
|
+
export function xcodeNonBuildResourcePlan(args) {
|
|
9
|
+
const flags = new Set(args.map(String));
|
|
10
|
+
if ([...MAINTENANCE].some((flag) => flags.has(flag))) return plan("xcode-maintenance", "unbounded", 6, 0.95, 2048, 8 * GIB, true, false);
|
|
11
|
+
if ([...ARTIFACT].some((flag) => flags.has(flag))) return plan("xcode-artifact", "io", 1, 0.9, 1536, 8 * GIB, false, true);
|
|
12
|
+
if ([...PACKAGE].some((flag) => flags.has(flag))) return plan("xcode-package-resolution", "mixed", 2, 0.65, 1536, 4 * GIB, false, true);
|
|
13
|
+
if ([...DELETE].some((flag) => flags.has(flag))) return plan("xcode-maintenance", "io", 1, 0.9, 1024, 0, false, false);
|
|
14
|
+
if ([...QUERY].some((flag) => flags.has(flag))) return plan("xcode-query", "adaptive", 0.75, 0.2, 768, 0, false, false);
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
function plan(family, resourceClass, cpu, io, memoryMb, diskBytes, unbounded, serializeProject) {
|
|
18
|
+
return { family, resourceClass, cpu, io, memoryMb, diskBytes, unbounded, serializeProject };
|
|
19
|
+
}
|