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,42 @@
|
|
|
1
|
+
const CMAKE_INT_MAX = 2_147_483_647;
|
|
2
|
+
|
|
3
|
+
export function cmakeBuildJobPlan(argv, environment = {}, maximum = 1024) {
|
|
4
|
+
const buildIndex = (argv || []).findIndex((value, index) => index > 0 && String(value) === "--build");
|
|
5
|
+
if (buildIndex < 0) return null;
|
|
6
|
+
const args = argv.slice(buildIndex + 1); const separator = args.indexOf("--");
|
|
7
|
+
const cmakeArgs = separator < 0 ? args : args.slice(0, separator);
|
|
8
|
+
if (hasPreset(cmakeArgs) || separator >= 0 && separator < args.length - 1) return unboundedPlan(true);
|
|
9
|
+
return directPlan(cmakeArgs, maximum) || environmentPlan(environment, maximum);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function directPlan(args, maximum) {
|
|
13
|
+
let plan = null;
|
|
14
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
15
|
+
const value = String(args[index] || ""); const flag = value.startsWith("--parallel") ? "--parallel" : value.startsWith("-j") ? "-j" : null;
|
|
16
|
+
if (!flag) continue;
|
|
17
|
+
if (value === flag) {
|
|
18
|
+
const next = args[index + 1];
|
|
19
|
+
if (next === undefined || cmakeIsFlag(next)) plan = unboundedPlan(false);
|
|
20
|
+
else { plan = numericPlan(next, maximum, false); index += 1; }
|
|
21
|
+
} else {
|
|
22
|
+
let suffix = value.slice(flag.length); if (suffix.startsWith("=")) suffix = suffix.slice(1);
|
|
23
|
+
plan = suffix ? numericPlan(suffix, maximum, false) : invalidPlan(false);
|
|
24
|
+
}
|
|
25
|
+
if (plan.invalid) return plan;
|
|
26
|
+
}
|
|
27
|
+
return plan;
|
|
28
|
+
}
|
|
29
|
+
function environmentPlan(environment, maximum) {
|
|
30
|
+
if (!Object.prototype.hasOwnProperty.call(environment || {}, "CMAKE_BUILD_PARALLEL_LEVEL")) return null;
|
|
31
|
+
const value = String(environment.CMAKE_BUILD_PARALLEL_LEVEL ?? "");
|
|
32
|
+
return value === "" ? unboundedPlan(true) : numericPlan(value, maximum, true);
|
|
33
|
+
}
|
|
34
|
+
function numericPlan(value, maximum, preserve) {
|
|
35
|
+
const text = String(value); if (!/^\+?\d+$/.test(text)) return invalidPlan(preserve);
|
|
36
|
+
const parsed = Number(text); if (!Number.isSafeInteger(parsed) || parsed < 1 || parsed > CMAKE_INT_MAX) return invalidPlan(preserve);
|
|
37
|
+
return parsed <= maximum ? { jobs: parsed, elastic: false, unbounded: false, preserve } : unboundedPlan(preserve);
|
|
38
|
+
}
|
|
39
|
+
function cmakeIsFlag(value) { const text = String(value || ""); return text.startsWith("-") && !/^-\d/.test(text); }
|
|
40
|
+
function hasPreset(args) { return args.some((value) => String(value) === "--preset" || String(value).startsWith("--preset=")); }
|
|
41
|
+
function invalidPlan(preserve) { return { jobs: null, elastic: false, unbounded: false, preserve, invalid: true }; }
|
|
42
|
+
function unboundedPlan(preserve) { return { jobs: null, elastic: false, unbounded: true, preserve }; }
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { availableParallelism } from "node:os";
|
|
2
|
+
import { cargoExplicitJobPlan } from "./resource-cargo-concurrency.mjs";
|
|
3
|
+
import { cmakeBuildJobPlan } from "./resource-cmake-concurrency.mjs";
|
|
4
|
+
import { goJobPlan } from "./resource-go-concurrency.mjs";
|
|
5
|
+
import { gradleJobPlan } from "./resource-gradle-concurrency.mjs";
|
|
6
|
+
import { makeCommandJobPlan, makeFlagsJobPlan } from "./resource-make-concurrency.mjs";
|
|
7
|
+
import { mavenJobPlan } from "./resource-maven-concurrency.mjs";
|
|
8
|
+
import { ninjaCommandJobPlan } from "./resource-ninja-command-concurrency.mjs";
|
|
9
|
+
import { ninjaJobserverPlan } from "./resource-ninja-concurrency.mjs";
|
|
10
|
+
export const DEFAULT_COMPILER_JOBS = 3;
|
|
11
|
+
export function cargoJobPlan(argv, environment = {}) { return cargoExplicitJobPlan(argv, environment) || { jobs: DEFAULT_COMPILER_JOBS, elastic: true, unbounded: false }; }
|
|
12
|
+
export function genericBuildJobPlan(base, argv, _text, options = {}) {
|
|
13
|
+
if (!["make", "ninja", "cmake", "gradle", "gradlew", "mvn", "mvnw", "go"].includes(base)) return null;
|
|
14
|
+
const fallback = { jobs: DEFAULT_COMPILER_JOBS, elastic: true, unbounded: false };
|
|
15
|
+
if (base === "make") return makeCommandJobPlan(argv) || makeFlagsJobPlan(options.environment) || fallback;
|
|
16
|
+
if (base === "go") return goJobPlan(argv, options.environment) || { jobs: DEFAULT_COMPILER_JOBS, elastic: true, unbounded: false };
|
|
17
|
+
if (base === "cmake") return cmakeBuildJobPlan(argv, options.environment) || { jobs: DEFAULT_COMPILER_JOBS, elastic: true, unbounded: false };
|
|
18
|
+
if (base === "ninja") return ninjaCommandJobPlan(argv) || ninjaJobserverPlan(argv, options.environment) || { jobs: DEFAULT_COMPILER_JOBS, elastic: true, unbounded: false };
|
|
19
|
+
if (["gradle", "gradlew"].includes(base)) return gradleJobPlan(argv, options.environment) || fallback;
|
|
20
|
+
return mavenJobPlan(argv, options.environment) || fallback;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function verificationFanoutPlan(environment = {}) {
|
|
24
|
+
const value = environment?.MBM_CHECK_CONCURRENCY;
|
|
25
|
+
if (hasConfiguredValue(value)) {
|
|
26
|
+
const configured = configuredJobs(value, 16);
|
|
27
|
+
return configured === null ? unboundedPlan() : { jobs: configured, elastic: false, unbounded: false };
|
|
28
|
+
}
|
|
29
|
+
return { jobs: Math.max(1, Math.min(4, availableParallelism())), elastic: true, unbounded: false };
|
|
30
|
+
}
|
|
31
|
+
function configuredJobs(value, maximum) {
|
|
32
|
+
if (!hasConfiguredValue(value)) return null;
|
|
33
|
+
const parsed = Number(value);
|
|
34
|
+
return Number.isSafeInteger(parsed) && parsed >= 1 && parsed <= maximum ? parsed : null;
|
|
35
|
+
}
|
|
36
|
+
function hasConfiguredValue(value) { return value !== undefined && value !== null && value !== ""; }
|
|
37
|
+
function unboundedPlan() { return { jobs: null, elastic: false, unbounded: true }; }
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { basename, isAbsolute, resolve } from "node:path";
|
|
3
|
+
import { cargoJobPlan, DEFAULT_COMPILER_JOBS, genericBuildJobPlan, verificationFanoutPlan } from "./resource-command-concurrency.mjs";
|
|
4
|
+
import { markElasticCompilerJobs, markPreservedCompilerJobs } from "./resource-elastic-request.mjs";
|
|
5
|
+
import { pytestWorkerPlan } from "./resource-pytest-concurrency.mjs";
|
|
6
|
+
import { swiftJobPlan } from "./resource-swift-concurrency.mjs";
|
|
7
|
+
import { xcodeResourcePlan } from "./resource-xcode-concurrency.mjs";
|
|
8
|
+
import { xcodeIsLightCommand } from "./resource-xcode-command.mjs";
|
|
9
|
+
import { xcodeNonBuildResourcePlan } from "./resource-xcode-non-build.mjs";
|
|
10
|
+
import { isTrustedLightExecutable } from "./resource-light-command.mjs";
|
|
11
|
+
import { releaseControlCommandIsLight } from "./resource-release-control-classification.mjs";
|
|
12
|
+
import { commandHeadIn, commandHeadIs, commandTokens, heavyShellScript, pythonModuleHeadIs, pythonModuleTokens, shellPayload, shellSegments } from "./resource-shell-analysis.mjs";
|
|
13
|
+
import { directInterpreterHeavyScript, packageManagerTokensHeavy, verificationPlanCommand } from "./resource-script-classification.mjs";
|
|
14
|
+
const GIB = 1024 ** 3;
|
|
15
|
+
export function resourceCommandProfile(command, args = [], options = {}) {
|
|
16
|
+
const rawCommand = String(command || "");
|
|
17
|
+
const argv = [rawCommand, ...args.map(String)];
|
|
18
|
+
const base = basename(argv[0]).toLowerCase();
|
|
19
|
+
const shellText = shellPayload(base, argv.slice(1));
|
|
20
|
+
const shellTokens = shellText ? shellSegments(shellText).map(commandTokens) : [];
|
|
21
|
+
const text = shellText || argv.join(" ");
|
|
22
|
+
const priority = normalizePriority(options.priority);
|
|
23
|
+
|
|
24
|
+
if (isTrustedDiskReclaimExecutable(rawCommand)) return profile("disk-reclaim", "io", 0.25, 0.75, 256, 0, priority);
|
|
25
|
+
if (isTrustedLightExecutable(rawCommand)) return lightProfile(priority);
|
|
26
|
+
if (base === "xcodebuild" && xcodeIsLightCommand(argv.slice(1))) return lightProfile(priority);
|
|
27
|
+
if (base === "xcodebuild") { const plan = xcodeNonBuildResourcePlan(argv.slice(1));
|
|
28
|
+
if (plan) return profile(plan.family, plan.resourceClass, plan.cpu, plan.io, plan.memoryMb, plan.diskBytes, priority, { unbounded: plan.unbounded, serializeProject: plan.serializeProject }); }
|
|
29
|
+
const buildProfile = compilerBuildProfile(base, argv, text, shellTokens, priority, options.environment);
|
|
30
|
+
if (buildProfile) return buildProfile;
|
|
31
|
+
return remainingCommandProfile(base, argv, text, shellTokens, priority, options.environment, options.releaseControlWorkspace === true);
|
|
32
|
+
}
|
|
33
|
+
function remainingCommandProfile(base, argv, text, shellTokens, priority, environment, releaseControlWorkspace) {
|
|
34
|
+
if (releaseControlWorkspace && releaseControlCommandIsLight(base, argv.slice(1))) return profile("release-control", "light", 0, 0, 0, 0, priority);
|
|
35
|
+
if (/^pytest(?:-\d+(?:\.\d+)*)?$/.test(base) || commandHeadIs(text, base, "pytest") || pythonModuleHeadIs(text, base, "pytest")) {
|
|
36
|
+
const plan = pytestWorkerPlan(argv, text);
|
|
37
|
+
return profile("pytest", plan.unbounded ? "unbounded" : "cpu", plan.unbounded ? 6 : plan.jobs, 0.12,
|
|
38
|
+
plan.unbounded ? 3072 : Math.max(768, plan.jobs * 768), 1 * GIB, priority, { unbounded: plan.unbounded });
|
|
39
|
+
}
|
|
40
|
+
if (verificationPlanCommand(base, argv.slice(1))
|
|
41
|
+
|| shellTokens.some((tokens) => verificationPlanCommand(tokens[0] || "", tokens.slice(1)))) {
|
|
42
|
+
const plan = verificationFanoutPlan(environment);
|
|
43
|
+
return profile("verification-plan", plan.unbounded ? "unbounded" : "mixed", plan.unbounded ? 6 : plan.jobs, 0.6,
|
|
44
|
+
plan.unbounded ? 4096 : Math.max(2048, plan.jobs * 1024), 3 * GIB, priority, {
|
|
45
|
+
compilerJobs: plan.jobs, elasticCompilerJobs: plan.elastic, elasticMemoryFloorMb: 2048,
|
|
46
|
+
unbounded: plan.unbounded, serializeProject: true,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if (directInterpreterHeavyScript(base, argv.slice(1))
|
|
50
|
+
|| shellTokens.some((tokens) => directInterpreterHeavyScript(tokens[0] || "", tokens.slice(1)))) {
|
|
51
|
+
return profile("script-heavy", "mixed", 2.5, 0.5, 2048, 3 * GIB, priority, { serializeProject: true });
|
|
52
|
+
}
|
|
53
|
+
if (commandHeadIn(text, base, ["rustc", "swiftc", "swift-frontend", "clang++", "clang", "gcc", "g++"])) {
|
|
54
|
+
return profile("compiler", "cpu", 1, 0.2, 1536, 1 * GIB, priority);
|
|
55
|
+
}
|
|
56
|
+
const packageScriptHeavy = packageManagerTokensHeavy([base, ...argv.slice(1)])
|
|
57
|
+
|| shellTokens.some((tokens) => packageManagerTokensHeavy(tokens));
|
|
58
|
+
if (packageScriptHeavy) {
|
|
59
|
+
return profile("js-build", "mixed", 2, 0.5, 2048, 3 * GIB, priority, { serializeProject: true });
|
|
60
|
+
}
|
|
61
|
+
if (commandHeadIs(text, base, "git") && /(?:^|\s)(?:add|checkout|clone|gc|repack|reset)(?:\s|$)/i.test(text)) {
|
|
62
|
+
return profile("git-write", "io", 1, 0.8, 768, 3 * GIB, priority, { serializeProject: true });
|
|
63
|
+
}
|
|
64
|
+
if (commandHeadIn(text, base, ["rsync", "ditto", "tar", "zip", "unzip"])) {
|
|
65
|
+
return profile("bulk-io", "io", 1, 0.9, 768, 6 * GIB, priority);
|
|
66
|
+
}
|
|
67
|
+
if (["sh", "bash", "zsh"].includes(base) && heavyShellScript(argv.slice(1), text)) {
|
|
68
|
+
return profile("script-heavy", "mixed", 2.5, 0.5, 2048, 3 * GIB, priority, { serializeProject: true });
|
|
69
|
+
}
|
|
70
|
+
return profile("generic-process", "adaptive", 0.5, 0.1, 512, 0, priority);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function resourceCommandEffectiveCwd(command, args, cwd, request = null) {
|
|
74
|
+
const baseCwd = resolve(cwd || process.cwd());
|
|
75
|
+
const base = basename(String(command || "")).toLowerCase();
|
|
76
|
+
const payload = shellPayload(base, args.map(String));
|
|
77
|
+
if (!payload || !request?.family) return baseCwd;
|
|
78
|
+
const expected = familyCommands(request.family);
|
|
79
|
+
let current = baseCwd;
|
|
80
|
+
let cwdCertain = true;
|
|
81
|
+
let resolvedCd = false;
|
|
82
|
+
for (const segment of shellSegments(payload)) {
|
|
83
|
+
const tokens = commandTokens(segment);
|
|
84
|
+
if (!tokens.length) continue;
|
|
85
|
+
if (tokens[0] === "cd") {
|
|
86
|
+
const raw = String(tokens[1] || "");
|
|
87
|
+
const canReanchor = isAbsolute(raw) || raw === "~" || raw.startsWith("~/");
|
|
88
|
+
const next = cwdCertain || canReanchor ? simpleCdPath(raw, current) : null;
|
|
89
|
+
if (next) { current = next; cwdCertain = true; resolvedCd = true; }
|
|
90
|
+
else cwdCertain = false;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (expected.includes(tokens[0]) || request.family === "pytest" && pythonModuleTokens(tokens, "pytest")) {
|
|
94
|
+
return cwdCertain ? current : baseCwd;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return request.serialize_project === true && resolvedCd && cwdCertain ? current : baseCwd;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function applyResourceProfileEnv(environment, request) {
|
|
101
|
+
const env = { ...environment };
|
|
102
|
+
if (request?.family === "cargo" && request.compiler_jobs && !env.CARGO_BUILD_JOBS) {
|
|
103
|
+
env.CARGO_BUILD_JOBS = String(request.compiler_jobs);
|
|
104
|
+
}
|
|
105
|
+
if (request?.family === "verification-plan" && request.compiler_jobs && !env.MBM_CHECK_CONCURRENCY) {
|
|
106
|
+
env.MBM_CHECK_CONCURRENCY = String(request.compiler_jobs);
|
|
107
|
+
}
|
|
108
|
+
return env;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function profile(family, resourceClass, cpu, io, memoryMb, diskBytes, priority, extra = {}) {
|
|
112
|
+
return markPreservedCompilerJobs(markElasticCompilerJobs({
|
|
113
|
+
family,
|
|
114
|
+
resource_class: resourceClass,
|
|
115
|
+
priority,
|
|
116
|
+
cpu: Math.max(0, Number(cpu) || 0),
|
|
117
|
+
io: Math.max(0, Number(io) || 0),
|
|
118
|
+
memory_mb: Math.max(0, Number(memoryMb) || 0),
|
|
119
|
+
disk_reserve_bytes: Math.max(0, Number(diskBytes) || 0),
|
|
120
|
+
heavy: resourceClass !== "light",
|
|
121
|
+
compiler_jobs: extra.compilerJobs ?? null,
|
|
122
|
+
unbounded: extra.unbounded === true,
|
|
123
|
+
serialize_project: extra.serializeProject === true,
|
|
124
|
+
}, extra.elasticCompilerJobs === true, extra.elasticMemoryFloorMb), extra.preserveCompilerJobs === true);
|
|
125
|
+
}
|
|
126
|
+
function compilerBuildProfile(base, argv, text, shellTokens, priority, environment) {
|
|
127
|
+
if (commandHeadIs(text, base, "cargo")) {
|
|
128
|
+
const plan = cargoJobPlan(argv, environment);
|
|
129
|
+
return profile("cargo", plan.unbounded ? "unbounded" : "mixed", plan.unbounded ? 6 : plan.jobs, 0.55, 3072, 4 * GIB, priority, {
|
|
130
|
+
compilerJobs: plan.jobs, elasticCompilerJobs: plan.elastic, preserveCompilerJobs: plan.preserve === true,
|
|
131
|
+
unbounded: plan.unbounded, serializeProject: true,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (commandHeadIs(text, base, "swift") && /(?:^|\s)(?:build|test|package)(?:\s|$)/i.test(text)) { const direct = base === "swift";
|
|
135
|
+
const shellSwift = !direct && shellTokens.length === 1 && basename(String(shellTokens[0]?.[0] || "")).toLowerCase() === "swift" ? shellTokens[0] : null;
|
|
136
|
+
const explicit = swiftJobPlan(direct ? argv : shellSwift); if (direct && explicit?.invalid) return profile("build-validation", "light", 0, 0, 0, 0, priority);
|
|
137
|
+
const plan = explicit || (direct ? { jobs: DEFAULT_COMPILER_JOBS, elastic: true, unbounded: false } : { jobs: null, elastic: false, unbounded: true }); const controllable = !plan.invalid && !plan.unbounded;
|
|
138
|
+
return profile("swift", controllable ? "mixed" : "unbounded", controllable ? plan.jobs : 6, 0.55, 3072, 4 * GIB, priority, { compilerJobs: controllable ? plan.jobs : null, elasticCompilerJobs: direct && plan.elastic, unbounded: !controllable, serializeProject: true });
|
|
139
|
+
}
|
|
140
|
+
if (commandHeadIs(text, base, "xcodebuild")) {
|
|
141
|
+
const plan = xcodeResourcePlan(base, argv, DEFAULT_COMPILER_JOBS); if (plan?.invalid) return profile("build-validation", "light", 0, 0, 0, 0, priority);
|
|
142
|
+
return profile("xcodebuild", plan.resourceClass, plan.cpu, 0.75, 4096, 8 * GIB, priority, { compilerJobs: plan.compilerJobs, elasticCompilerJobs: plan.elastic, unbounded: plan.unbounded, serializeProject: true });
|
|
143
|
+
}
|
|
144
|
+
const generic = commandHeadIn(text, base, ["make", "ninja", "gradle", "gradlew"])
|
|
145
|
+
|| commandHeadIs(text, base, "cmake") && /(?:^|\s)--build(?:\s|$)/.test(text)
|
|
146
|
+
|| commandHeadIn(text, base, ["mvn", "mvnw"]) && /(?:^|\s)(?:test|package|install)(?:\s|$)/.test(text)
|
|
147
|
+
|| commandHeadIs(text, base, "go") && /(?:^|\s)(?:test|build)(?:\s|$)/.test(text);
|
|
148
|
+
if (!generic) return null;
|
|
149
|
+
const plan = genericBuildJobPlan(base, argv, text, { environment });
|
|
150
|
+
if (plan?.invalid) return profile("build-validation", "light", 0, 0, 0, 0, priority);
|
|
151
|
+
const controllable = Boolean(plan && !plan.unbounded);
|
|
152
|
+
return profile("generic-build", controllable ? "mixed" : "unbounded", controllable ? plan.jobs : 6, 0.65, 3072, 5 * GIB, priority, {
|
|
153
|
+
compilerJobs: controllable ? plan.jobs : null, elasticCompilerJobs: controllable && plan.elastic,
|
|
154
|
+
preserveCompilerJobs: plan?.preserve === true, unbounded: !controllable, serializeProject: true,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
function lightProfile(priority) { return profile("light", "light", 0, 0, 0, 0, priority); }
|
|
158
|
+
function normalizePriority(value) { return ["interactive", "ordinary", "background"].includes(value) ? value : "ordinary"; }
|
|
159
|
+
function isTrustedDiskReclaimExecutable(command) {
|
|
160
|
+
return ["/bin/rm", "/usr/bin/rm", "/bin/rmdir", "/usr/bin/rmdir", "/usr/bin/unlink"].includes(command);
|
|
161
|
+
}
|
|
162
|
+
function familyCommands(family) {
|
|
163
|
+
if (family === "verification-plan") return ["npm", "pnpm", "yarn", "bun", "node"];
|
|
164
|
+
if (family === "js-build") return ["npm", "pnpm", "yarn", "bun"];
|
|
165
|
+
if (family === "generic-build") return ["make", "ninja", "cmake", "gradle", "gradlew", "mvn", "mvnw", "go"];
|
|
166
|
+
if (family === "bulk-io") return ["rsync", "ditto", "tar", "zip", "unzip"];
|
|
167
|
+
if (family === "compiler") return ["rustc", "swiftc", "swift-frontend", "clang++", "clang", "gcc", "g++"];
|
|
168
|
+
return [family];
|
|
169
|
+
}
|
|
170
|
+
function simpleCdPath(value, cwd) {
|
|
171
|
+
const raw = String(value || "");
|
|
172
|
+
if (!raw || raw === "-" || /[$`'"*?{}[\]]/.test(raw)) return null;
|
|
173
|
+
if (raw === "~") return homedir();
|
|
174
|
+
if (raw.startsWith("~/")) return resolve(homedir(), raw.slice(2));
|
|
175
|
+
return isAbsolute(raw) ? resolve(raw) : resolve(cwd, raw);
|
|
176
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { aggregateResourceLeases, resourceLeaseAccountingContext } from "./resource-lease-accounting.mjs";
|
|
2
|
+
|
|
3
|
+
export function resourceCoordinatorEvaluator(evaluate, processParents, fallbackPid) {
|
|
4
|
+
return (host, leases, request, now, candidate = null) => evaluate(host, leases, request, now, {
|
|
5
|
+
accounting: resourceLeaseAccountingContext(
|
|
6
|
+
leases, processParents, candidate?.owner?.pid || fallbackPid,
|
|
7
|
+
),
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function resourceCoordinatorAccounting(leases, processParents, requesterPid = 0) {
|
|
12
|
+
const accounting = resourceLeaseAccountingContext(leases, processParents, requesterPid);
|
|
13
|
+
const used = aggregateResourceLeases(leases, accounting);
|
|
14
|
+
return {
|
|
15
|
+
accounting,
|
|
16
|
+
resources: {
|
|
17
|
+
cpu: used.cpu, io: used.io, memory_mb: used.memory_mb,
|
|
18
|
+
disk_reserve_bytes: used.disk_reserve_bytes,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { aggregateResourceLeases } from "./resource-lease-accounting.mjs";
|
|
2
|
+
|
|
3
|
+
export function unobservedResourceCpu(leases, accounting = {}, sampledAtMs = 0) {
|
|
4
|
+
const sampledAt = Number(sampledAtMs);
|
|
5
|
+
if (!Number.isFinite(sampledAt) || sampledAt <= 0) return 0;
|
|
6
|
+
const all = aggregateResourceLeases(leases, accounting);
|
|
7
|
+
const observed = aggregateResourceLeases(
|
|
8
|
+
(leases || []).filter((lease) => leaseObservedBySample(lease, sampledAt)),
|
|
9
|
+
accounting,
|
|
10
|
+
);
|
|
11
|
+
return Math.max(0, all.cpu - observed.cpu);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function leaseObservedBySample(lease, sampledAtMs) {
|
|
15
|
+
const boundAt = Date.parse(String(lease?.bound_at || ""));
|
|
16
|
+
if (Number.isFinite(boundAt)) return boundAt <= sampledAtMs;
|
|
17
|
+
if (lease?.owner?.kind !== "process") return false;
|
|
18
|
+
const acquiredAt = Date.parse(String(lease?.acquired_at || ""));
|
|
19
|
+
return Number.isFinite(acquiredAt) && acquiredAt <= sampledAtMs;
|
|
20
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const GIB = 1024 ** 3;
|
|
2
|
+
|
|
3
|
+
export function resourceDiskHardFloorBytes(totalBytes) {
|
|
4
|
+
const total = Number(totalBytes);
|
|
5
|
+
if (!Number.isFinite(total) || total <= 0) return 0;
|
|
6
|
+
return Math.min(50 * GIB, Math.max(5 * GIB, total * 0.10));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function resourceDiskSoftFloorBytes(totalBytes) {
|
|
10
|
+
const total = Number(totalBytes);
|
|
11
|
+
if (!Number.isFinite(total) || total <= 0) return 0;
|
|
12
|
+
return Math.min(80 * GIB, Math.max(8 * GIB, total * 0.15));
|
|
13
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function elasticMemoryJobLimit(request, pressure, maximumJobs, floorValue) {
|
|
2
|
+
if (floorValue === null || floorValue === undefined || floorValue === "") return maximumJobs;
|
|
3
|
+
const floor = Number(floorValue); const limit = Number(pressure?.limits?.memory_mb);
|
|
4
|
+
const overcommit = Number(pressure?.limits?.memory_overcommit); const used = Number(pressure?.used?.memory_mb);
|
|
5
|
+
if (![floor, limit, overcommit, used].every(Number.isFinite)) return maximumJobs;
|
|
6
|
+
const incremental = Number(pressure?.requested?.memory_mb);
|
|
7
|
+
const covered = Number.isFinite(incremental) ? Math.max(0, Number(request.memory_mb) - incremental) : 0;
|
|
8
|
+
const available = Math.max(0, limit * overcommit + 1 - Math.max(0, used) + covered);
|
|
9
|
+
for (let jobs = maximumJobs; jobs >= 1; jobs -= 1) {
|
|
10
|
+
if (elasticMemoryMb(request, jobs, floor) <= available) return jobs;
|
|
11
|
+
}
|
|
12
|
+
return 1;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function elasticMemoryMb(request, jobs, floorValue) {
|
|
16
|
+
const floor = Number(floorValue);
|
|
17
|
+
if (!Number.isFinite(floor)) return request.memory_mb;
|
|
18
|
+
return Math.min(request.memory_mb, Math.max(floor, Math.ceil(request.memory_mb * jobs / request.compiler_jobs)));
|
|
19
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { elasticMemoryJobLimit, elasticMemoryMb } from "./resource-elastic-memory.mjs";
|
|
2
|
+
|
|
3
|
+
const ELASTIC_COMPILER_JOBS = Symbol("resource.elasticCompilerJobs");
|
|
4
|
+
const PRESERVE_COMPILER_JOBS = Symbol("resource.preserveCompilerJobs");
|
|
5
|
+
|
|
6
|
+
export function markPreservedCompilerJobs(request, enabled = true) {
|
|
7
|
+
if (enabled) request[PRESERVE_COMPILER_JOBS] = true;
|
|
8
|
+
return request;
|
|
9
|
+
}
|
|
10
|
+
export function preservesCompilerJobs(request) { return request?.[PRESERVE_COMPILER_JOBS] === true; }
|
|
11
|
+
export function markElasticCompilerJobs(request, enabled = true, memoryFloorMb = null) {
|
|
12
|
+
if (enabled && Number.isInteger(request?.compiler_jobs) && request.compiler_jobs > 1) {
|
|
13
|
+
request[ELASTIC_COMPILER_JOBS] = memoryFloorMb !== null && memoryFloorMb !== undefined && memoryFloorMb !== "" && Number.isFinite(Number(memoryFloorMb))
|
|
14
|
+
? { memory_floor_mb: Math.max(0, Number(memoryFloorMb)) } : {};
|
|
15
|
+
}
|
|
16
|
+
return request;
|
|
17
|
+
}
|
|
18
|
+
export function isElasticCompilerRequest(request) { return Boolean(request?.[ELASTIC_COMPILER_JOBS]); }
|
|
19
|
+
|
|
20
|
+
export function fitElasticRequestToPressure(request, pressure) {
|
|
21
|
+
if (!isElasticCompilerRequest(request) || !pressure || pressure.state === "red") return request;
|
|
22
|
+
const limit = Number(pressure.limits?.cpu); const busy = Number(pressure.observed?.cpu_busy_cores);
|
|
23
|
+
const unobserved = Number(pressure.observed?.unobserved_reserved_cpu ?? 0);
|
|
24
|
+
if (!Number.isFinite(limit) || !Number.isFinite(busy) || !Number.isFinite(unobserved)) return request;
|
|
25
|
+
const used = Number(pressure.used?.cpu); const overcommit = Number(pressure.limits?.cpu_overcommit); const incremental = Number(pressure.requested?.cpu);
|
|
26
|
+
const covered = Number.isFinite(incremental) ? Math.max(0, Number(request.cpu) - incremental) : 0;
|
|
27
|
+
const hostAvailable = Math.floor(limit + 0.25 - busy - Math.max(0, unobserved) + covered);
|
|
28
|
+
const reservedAvailable = Number.isFinite(used) && Number.isFinite(overcommit)
|
|
29
|
+
? Math.floor(limit * overcommit + 0.01 - Math.max(0, used) + covered) : request.compiler_jobs;
|
|
30
|
+
const floor = request[ELASTIC_COMPILER_JOBS]?.memory_floor_mb;
|
|
31
|
+
let jobs = Math.max(1, Math.min(request.compiler_jobs, hostAvailable, reservedAvailable));
|
|
32
|
+
jobs = Math.min(jobs, elasticMemoryJobLimit(request, pressure, jobs, floor));
|
|
33
|
+
if (jobs >= request.compiler_jobs) return request;
|
|
34
|
+
return { ...request, cpu: Math.min(Math.max(0, Number(request.cpu) || 0), jobs),
|
|
35
|
+
memory_mb: elasticMemoryMb(request, jobs, floor), compiler_jobs: jobs };
|
|
36
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const DEFAULT_MINIMUM_WAIT_MS = 2_000;
|
|
2
|
+
const DEFAULT_MAXIMUM_WAIT_MS = 10_000;
|
|
3
|
+
const REMOTE_PROCESS_SESSION_WAIT_MS = 0;
|
|
4
|
+
const MAXIMUM_CONFIGURED_WAIT_MS = 30 * 60_000;
|
|
5
|
+
const DEFAULT_TIMEOUT_FRACTION = 0.2;
|
|
6
|
+
|
|
7
|
+
export function foregroundResourceWaitMs(executionTimeoutMs, configuredWaitMs = undefined) {
|
|
8
|
+
if (configuredWaitMs !== undefined) return configuredResourceWaitMs(configuredWaitMs);
|
|
9
|
+
const timeout = positiveInteger(executionTimeoutMs, "foreground execution timeout");
|
|
10
|
+
const proportional = Math.floor(timeout * DEFAULT_TIMEOUT_FRACTION);
|
|
11
|
+
return Math.min(timeout, DEFAULT_MAXIMUM_WAIT_MS, Math.max(DEFAULT_MINIMUM_WAIT_MS, proportional));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function processSessionResourceWaitMs(configuredWaitMs = undefined, options = {}) {
|
|
15
|
+
if (configuredWaitMs !== undefined) return configuredResourceWaitMs(configuredWaitMs);
|
|
16
|
+
return options.remote === true ? REMOTE_PROCESS_SESSION_WAIT_MS : DEFAULT_MAXIMUM_WAIT_MS;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function configuredResourceWaitMs(value) {
|
|
20
|
+
const numeric = Number(value);
|
|
21
|
+
if (!Number.isFinite(numeric) || numeric < 0) throw new TypeError("resource wait must be a non-negative finite duration");
|
|
22
|
+
return Math.min(MAXIMUM_CONFIGURED_WAIT_MS, Math.floor(numeric));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function positiveInteger(value, label) {
|
|
26
|
+
const numeric = Number(value);
|
|
27
|
+
if (!Number.isFinite(numeric) || numeric <= 0) throw new TypeError(`${label} must be positive`);
|
|
28
|
+
return Math.max(1, Math.floor(numeric));
|
|
29
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export function goJobPlan(argv, environment = {}, maximum = 1024) { const inherited = goFlagsJobPlan(environment, maximum); return inherited?.fatal ? inherited : directGoPlan(argv, maximum) || inherited; }
|
|
2
|
+
export function goFlagsJobPlan(environment = {}, maximum = 1024) {
|
|
3
|
+
if (!Object.prototype.hasOwnProperty.call(environment || {}, "GOFLAGS")) return null;
|
|
4
|
+
const tokens = splitGoFlags(environment.GOFLAGS); if (!tokens) return invalidPlan(true, true);
|
|
5
|
+
let plan = null;
|
|
6
|
+
for (const value of tokens) {
|
|
7
|
+
if (["-p", "--p"].includes(value)) return invalidPlan(true, true);
|
|
8
|
+
if (/^--?p=/.test(value)) { plan = numericPlan(value.slice(value.indexOf("=") + 1), maximum, true); if (plan.fatal) return plan; }
|
|
9
|
+
}
|
|
10
|
+
return plan;
|
|
11
|
+
}
|
|
12
|
+
function directGoPlan(argv, maximum) { let plan = null; let ambiguous = false; let packageList = false; let inPackages = false; const testMode = argv?.[1] === "test";
|
|
13
|
+
for (let index = 2; index < (argv || []).length; index += 1) { const value = String(argv[index] || ""); if (["--", "-args", "--args"].includes(value)) break;
|
|
14
|
+
if (!value.startsWith("-")) {
|
|
15
|
+
if (!testMode) break; if (!packageList || inPackages) { packageList = true; inPackages = true; continue; }
|
|
16
|
+
if (ambiguous) continue; break;
|
|
17
|
+
}
|
|
18
|
+
if (testMode && inPackages) inPackages = false; let current = null;
|
|
19
|
+
if (["-p", "--p"].includes(value)) { if (ambiguous) return unboundedPlan(false); current = numericPlan(argv[index + 1], maximum, false); index += 1; }
|
|
20
|
+
else if (/^--?p=/.test(value)) { if (ambiguous) return unboundedPlan(false); current = numericPlan(value.slice(value.indexOf("=") + 1), maximum, false); }
|
|
21
|
+
else if (/^-p[-+]?\d/.test(value)) { if (ambiguous) return unboundedPlan(false); current = invalidPlan(false, true); }
|
|
22
|
+
else if (!value.includes("=")) ambiguous = true;
|
|
23
|
+
if (current?.fatal) return current; if (current) plan = current;
|
|
24
|
+
}
|
|
25
|
+
return plan;
|
|
26
|
+
}
|
|
27
|
+
function splitGoFlags(value) {
|
|
28
|
+
let text = String(value || ""); const result = [];
|
|
29
|
+
while (text.length) {
|
|
30
|
+
text = text.replace(/^[ \t\n\r]+/, ""); if (!text) break;
|
|
31
|
+
if (text[0] === "'" || text[0] === '"') {
|
|
32
|
+
const quote = text[0]; const end = text.indexOf(quote, 1); if (end < 0) return null;
|
|
33
|
+
result.push(text.slice(1, end)); text = text.slice(end + 1); continue;
|
|
34
|
+
}
|
|
35
|
+
const match = /^[^ \t\n\r]+/.exec(text); result.push(match[0]); text = text.slice(match[0].length);
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
function numericPlan(value, maximum, preserve) { const parsed = Number(value); if (!Number.isSafeInteger(parsed)) return invalidPlan(preserve, true);
|
|
40
|
+
if (parsed <= 0) return invalidPlan(preserve, false); return parsed <= maximum ? { jobs: parsed, elastic: false, unbounded: false, preserve } : unboundedPlan(preserve);
|
|
41
|
+
}
|
|
42
|
+
function invalidPlan(preserve, fatal) { return { jobs: null, elastic: false, unbounded: false, preserve, invalid: true, fatal }; }
|
|
43
|
+
function unboundedPlan(preserve) { return { jobs: null, elastic: false, unbounded: true, preserve }; }
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const WORKERS_PROPERTY = "org.gradle.workers.max";
|
|
2
|
+
const GRADLE_INT_MAX = 2_147_483_647;
|
|
3
|
+
|
|
4
|
+
export function gradleJobPlan(argv, environment = {}, maximum = 1024) {
|
|
5
|
+
const property = gradlePropertyPlan(argv, environment, maximum); if (property?.invalid) return property;
|
|
6
|
+
return gradleCliPlan(argv, maximum) || property;
|
|
7
|
+
}
|
|
8
|
+
function gradleCliPlan(argv, maximum) {
|
|
9
|
+
const values = [];
|
|
10
|
+
for (let index = 1; index < (argv || []).length; index += 1) {
|
|
11
|
+
const value = String(argv[index] || ""); if (value === "--") break;
|
|
12
|
+
if (value === "--max-workers") { values.push(argv[index + 1]); index += 1; }
|
|
13
|
+
else if (value.startsWith("--max-workers=")) values.push(value.slice("--max-workers=".length));
|
|
14
|
+
}
|
|
15
|
+
if (values.length > 1) return invalidPlan(false);
|
|
16
|
+
return values.length ? numericPlan(values[0], maximum, false) : null;
|
|
17
|
+
}
|
|
18
|
+
function gradlePropertyPlan(argv, environment, maximum) {
|
|
19
|
+
let property;
|
|
20
|
+
for (const key of ["JAVA_OPTS", "GRADLE_OPTS"]) {
|
|
21
|
+
if (!Object.prototype.hasOwnProperty.call(environment || {}, key)) continue;
|
|
22
|
+
const tokens = splitJvmOptions(environment[key]); if (!tokens) return unboundedPlan(true);
|
|
23
|
+
for (const token of tokens) { const value = propertyValue(token); if (value !== undefined) property = value; }
|
|
24
|
+
}
|
|
25
|
+
for (let index = 1; index < (argv || []).length; index += 1) {
|
|
26
|
+
const token = String(argv[index] || ""); if (token === "--") break;
|
|
27
|
+
const inline = propertyValue(token); if (inline !== undefined) property = inline;
|
|
28
|
+
else if (["-D", "--system-prop"].includes(token)) {
|
|
29
|
+
const next = String(argv[index + 1] || ""); if (next.startsWith(`${WORKERS_PROPERTY}=`)) property = next.slice(WORKERS_PROPERTY.length + 1);
|
|
30
|
+
index += 1;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return property === undefined ? null : numericPlan(property, maximum, true);
|
|
34
|
+
}
|
|
35
|
+
function propertyValue(token) {
|
|
36
|
+
for (const prefix of [`-D${WORKERS_PROPERTY}=`, `--system-prop=${WORKERS_PROPERTY}=`]) if (String(token).startsWith(prefix)) return String(token).slice(prefix.length);
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
function splitJvmOptions(value) {
|
|
40
|
+
const result = []; let token = ""; let quote = ""; let escaped = false; let active = false;
|
|
41
|
+
for (const char of String(value || "")) {
|
|
42
|
+
if (escaped) { token += char; escaped = false; active = true; }
|
|
43
|
+
else if (char === "\\" && quote !== "'") { escaped = true; active = true; }
|
|
44
|
+
else if (quote) { if (char === quote) quote = ""; else token += char; active = true; }
|
|
45
|
+
else if (["'", '"'].includes(char)) { quote = char; active = true; }
|
|
46
|
+
else if (/\s/.test(char)) { if (active) { result.push(token); token = ""; active = false; } }
|
|
47
|
+
else { token += char; active = true; }
|
|
48
|
+
}
|
|
49
|
+
if (quote || escaped) return null; if (active) result.push(token); return result;
|
|
50
|
+
}
|
|
51
|
+
function numericPlan(value, maximum, preserve) {
|
|
52
|
+
const text = String(value ?? ""); if (!/^[+-]?\d+$/.test(text)) return invalidPlan(preserve);
|
|
53
|
+
const parsed = Number(text); if (!Number.isSafeInteger(parsed) || parsed < 1 || parsed > GRADLE_INT_MAX) return invalidPlan(preserve);
|
|
54
|
+
return parsed <= maximum ? { jobs: parsed, elastic: false, unbounded: false, preserve } : unboundedPlan(preserve);
|
|
55
|
+
}
|
|
56
|
+
function invalidPlan(preserve) { return { jobs: null, elastic: false, unbounded: false, preserve, invalid: true }; }
|
|
57
|
+
function unboundedPlan(preserve) { return { jobs: null, elastic: false, unbounded: true, preserve }; }
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const HOST_SAMPLE_FRESH_MS = 500;
|
|
2
|
+
const HOST_CPU_PREVIOUS_MAX_AGE_MS = 2_000;
|
|
3
|
+
const HOST_IO_SAMPLE_FRESH_MS = 5_000;
|
|
4
|
+
const HOST_IO_HINT_MAX_AGE_MS = 30_000;
|
|
5
|
+
|
|
6
|
+
export function resourceHostNeedsFreshIo(request) {
|
|
7
|
+
return request?.resource_class === "io" || request?.resource_class === "unbounded";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function freshResourceHostSnapshot({ cached, current, sampleHost, cwd, request = null, scope = "" }) {
|
|
11
|
+
const scopedCached = cached?.sample_scope === scope ? cached : null;
|
|
12
|
+
const sampleAge = scopedCached ? current - Number(scopedCached.sampled_at_ms) : Number.POSITIVE_INFINITY;
|
|
13
|
+
const globalAge = cached ? current - Number(cached.sampled_at_ms) : Number.POSITIVE_INFINITY;
|
|
14
|
+
const cpuPrevious = cached && globalAge >= 0 && globalAge <= HOST_CPU_PREVIOUS_MAX_AGE_MS ? cached : null;
|
|
15
|
+
const needsIo = resourceHostNeedsFreshIo(request);
|
|
16
|
+
if (scopedCached && sampleAge >= 0 && sampleAge <= HOST_SAMPLE_FRESH_MS && (!needsIo || scopedCached.io_sampled === true)) return scopedCached;
|
|
17
|
+
const ioSampledAt = Number(scopedCached?.io_sampled_at_ms ?? scopedCached?.sampled_at_ms);
|
|
18
|
+
const ioAge = current - ioSampledAt;
|
|
19
|
+
if (!needsIo) {
|
|
20
|
+
const quick = { ...await Promise.resolve(sampleHost({ cwd, quick: true, previous: cpuPrevious })), sample_scope: scope };
|
|
21
|
+
if (scopedCached?.io_sampled !== true || !Number.isFinite(ioAge) || ioAge < 0 || ioAge > HOST_IO_HINT_MAX_AGE_MS) return quick;
|
|
22
|
+
return withCachedIo(quick, scopedCached);
|
|
23
|
+
}
|
|
24
|
+
if (scopedCached?.io_sampled === true && Number.isFinite(ioAge) && ioAge >= 0 && ioAge <= HOST_IO_SAMPLE_FRESH_MS) {
|
|
25
|
+
const quick = { ...await Promise.resolve(sampleHost({ cwd, quick: true, previous: cpuPrevious })), sample_scope: scope };
|
|
26
|
+
return withCachedIo(quick, scopedCached);
|
|
27
|
+
}
|
|
28
|
+
return { ...await Promise.resolve(sampleHost({ cwd, quick: false, previous: cpuPrevious })), sample_scope: scope };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function withCachedIo(sample, cached) {
|
|
32
|
+
return {
|
|
33
|
+
...sample,
|
|
34
|
+
disk_mb_per_s: cached.disk_mb_per_s ?? null,
|
|
35
|
+
disk_iops: cached.disk_iops ?? null,
|
|
36
|
+
io_sampled: true,
|
|
37
|
+
io_sampled_at_ms: Number.isFinite(Number(cached.io_sampled_at_ms)) ? Number(cached.io_sampled_at_ms) : Number(cached.sampled_at_ms),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { runResourceProbeAsync, runResourceProbeSync } from "./resource-probe-command.mjs";
|
|
2
|
+
|
|
3
|
+
export function sampleDarwinHost(sample, options = {}) {
|
|
4
|
+
const run = options.runCommand || ((command, args, timeoutMs) => runResourceProbeSync(command, args, { timeoutMs }));
|
|
5
|
+
applyDarwinResults(sample, {
|
|
6
|
+
pressure: run("/usr/bin/memory_pressure", ["-Q"]),
|
|
7
|
+
vm: run("/usr/bin/vm_stat", []),
|
|
8
|
+
io: options.quick === true ? null : run("/usr/sbin/iostat", ["-Id", "disk0", "1", "2"], 2_200),
|
|
9
|
+
thermal: run("/usr/bin/pmset", ["-g", "therm"]),
|
|
10
|
+
}, options.quick !== true);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function sampleDarwinHostAsync(options = {}) {
|
|
14
|
+
const run = options.runCommandAsync || ((command, args, timeoutMs) => runResourceProbeAsync(command, args, { timeoutMs }));
|
|
15
|
+
const [pressure, vm, io, thermal] = await Promise.all([
|
|
16
|
+
run("/usr/bin/memory_pressure", ["-Q"]),
|
|
17
|
+
run("/usr/bin/vm_stat", []),
|
|
18
|
+
options.quick === true ? Promise.resolve(null) : run("/usr/sbin/iostat", ["-Id", "disk0", "1", "2"], 2_200),
|
|
19
|
+
run("/usr/bin/pmset", ["-g", "therm"]),
|
|
20
|
+
]);
|
|
21
|
+
const sample = {};
|
|
22
|
+
applyDarwinResults(sample, { pressure, vm, io, thermal }, options.quick !== true);
|
|
23
|
+
return sample;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function applyDarwinResults(sample, results, sampledIo) {
|
|
27
|
+
const { pressure, vm, io, thermal } = results;
|
|
28
|
+
if (pressure?.ok) {
|
|
29
|
+
const match = /System-wide memory free percentage:\s*(\d+)%/i.exec(pressure.stdout);
|
|
30
|
+
if (match) sample.memory_free_percent = Number(match[1]);
|
|
31
|
+
}
|
|
32
|
+
if (vm?.ok) {
|
|
33
|
+
sample.pageouts_total = vmCounter(vm.stdout, "Pageouts");
|
|
34
|
+
sample.swapouts_total = vmCounter(vm.stdout, "Swapouts");
|
|
35
|
+
}
|
|
36
|
+
if (sampledIo) {
|
|
37
|
+
if (io?.ok) {
|
|
38
|
+
const rows = String(io.stdout || "").split("\n").map((line) => line.trim()).filter((line) => /^\d/.test(line));
|
|
39
|
+
const fields = rows.at(-1)?.split(/\s+/).map(Number) || [];
|
|
40
|
+
if (fields.length >= 3 && fields.every(Number.isFinite)) {
|
|
41
|
+
sample.disk_iops = fields[1];
|
|
42
|
+
sample.disk_mb_per_s = fields[2];
|
|
43
|
+
sample.io_sampled = true;
|
|
44
|
+
sample.io_sampled_at_ms = Date.now();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (thermal?.ok) sample.thermal_warning = !(/No thermal warning level has been recorded/i.test(thermal.stdout) && /No performance warning level has been recorded/i.test(thermal.stdout));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function vmCounter(text, label) {
|
|
52
|
+
const match = new RegExp(`^${label}:\\s*(\\d+)\\.?$`, "mi").exec(String(text || ""));
|
|
53
|
+
return match ? Number(match[1]) : null;
|
|
54
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
export function sampleLinuxHost(sample, options = {}) {
|
|
4
|
+
const read = options.readFile || readText;
|
|
5
|
+
const meminfo = read("/proc/meminfo");
|
|
6
|
+
const totalKb = meminfoKb(meminfo, "MemTotal");
|
|
7
|
+
const availableKb = meminfoKb(meminfo, "MemAvailable");
|
|
8
|
+
if (totalKb !== null && totalKb > 0) {
|
|
9
|
+
sample.total_memory_mb = Math.max(1024, Math.round(totalKb / 1024));
|
|
10
|
+
if (availableKb !== null) sample.memory_free_percent = Math.max(0, Math.min(100, availableKb * 100 / totalKb));
|
|
11
|
+
}
|
|
12
|
+
const cpu = read("/proc/pressure/cpu");
|
|
13
|
+
const memory = read("/proc/pressure/memory");
|
|
14
|
+
const io = read("/proc/pressure/io");
|
|
15
|
+
sample.psi_cpu_some_avg10 = psiAvg10(cpu, "some");
|
|
16
|
+
sample.psi_memory_some_avg10 = psiAvg10(memory, "some");
|
|
17
|
+
sample.psi_memory_full_avg10 = psiAvg10(memory, "full");
|
|
18
|
+
sample.psi_io_some_avg10 = psiAvg10(io, "some");
|
|
19
|
+
sample.psi_io_full_avg10 = psiAvg10(io, "full");
|
|
20
|
+
sample.io_sampled = sample.psi_io_some_avg10 !== null || sample.psi_io_full_avg10 !== null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function meminfoKb(text, key) {
|
|
24
|
+
const match = new RegExp(`^${key}:\\s*(\\d+)\\s+kB$`, "mi").exec(String(text || ""));
|
|
25
|
+
return match ? Number(match[1]) : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function psiAvg10(text, kind) {
|
|
29
|
+
const match = new RegExp(`^${kind}\\s+[^\\n]*?avg10=(\\d+(?:\\.\\d+)?)`, "mi").exec(String(text || ""));
|
|
30
|
+
return match ? Number(match[1]) : null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function readText(path) {
|
|
34
|
+
try { return readFileSync(path, "utf8"); } catch { return ""; }
|
|
35
|
+
}
|