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
package/src/local/job-runner.mjs
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
-
import { chmodSync,
|
|
3
|
+
import { chmodSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { basename, join, resolve } from "node:path";
|
|
5
5
|
import { performance } from "node:perf_hooks";
|
|
6
6
|
import { executionEnv } from "./shell.mjs";
|
|
7
|
+
import { attachChildProcessSettlement, childExitedBeforeTimeout } from "./child-process-settlement.mjs";
|
|
7
8
|
import { terminateProcessTreeWithEscalation } from "./process-tree.mjs";
|
|
8
|
-
import {
|
|
9
|
+
import { removeOwnedJsonFileSync, replaceFileAtomicallySync } from "./exclusive-file.mjs";
|
|
9
10
|
import { createMonotonicDeadline } from "./monotonic-deadline.mjs";
|
|
10
|
-
import { currentProcessStartTimeMs } from "./process-identity.mjs";
|
|
11
|
+
import { currentProcessStartTimeMs, processState } from "./process-identity.mjs";
|
|
11
12
|
import { readBoundedRegularFileSync } from "./secure-file.mjs";
|
|
13
|
+
import { managedJobCancellationRequested } from "./managed-job-cancellation.mjs";
|
|
14
|
+
import { MacosIdleSleepAssertion } from "./macos-idle-sleep-assertion.mjs";
|
|
15
|
+
import relayContract from "../shared/relay-contract.json" with { type: "json" };
|
|
16
|
+
import { MANAGED_JOB_ID } from "./managed-job-directory.mjs";
|
|
17
|
+
import { ACTIVE_JOB_STATES, isTerminalManagedJobStatus, managedJobFinalStatus, persistManagedJobTerminal } from "./managed-job-terminal.mjs";
|
|
18
|
+
import { sanitizeLogText } from "./log.mjs";
|
|
19
|
+
import { confirmRunnerClaim } from "./managed-job-runner-claim.mjs";
|
|
20
|
+
import { ResourceCoordinator } from "./resource-admission.mjs";
|
|
21
|
+
import { acquireProcessResources, bindProcessResources, releaseProcessResources, releaseProcessResourcesQuietly } from "./resource-process-admission.mjs";
|
|
22
|
+
import { delegatedProcessCommand } from "./delegated-process-sandbox.mjs";
|
|
12
23
|
|
|
13
24
|
const RESOURCE_TOKEN = /\{\{resource:([a-z][a-z0-9._-]{0,63})\}\}/g;
|
|
14
25
|
const TEMP_TOKEN = /\{\{temp:([a-z][a-z0-9._-]{0,63})\}\}/g;
|
|
@@ -17,16 +28,22 @@ const MAX_OUTPUT_BYTES = 64 * 1024;
|
|
|
17
28
|
const MAX_JOB_CAPTURE_BYTES = 256 * 1024;
|
|
18
29
|
const MAX_RESULT_BYTES = 4 * 1024 * 1024;
|
|
19
30
|
const MAX_STATUS_BYTES = 256 * 1024;
|
|
20
|
-
const
|
|
31
|
+
const RECOVERY_LOCK_HANDOFF_WAIT_MS = 30_000;
|
|
32
|
+
const FATAL_IDENTITY_FIELDS = [
|
|
33
|
+
"job_id", "name", "plan_sha256", "created_at", "approval", "owner_kind", "owner_account_id",
|
|
34
|
+
"owner_account_version", "owner_client_id", "owner_family_id",
|
|
35
|
+
];
|
|
21
36
|
|
|
22
37
|
const options = parseArgs(process.argv.slice(2));
|
|
23
38
|
const jobDirInput = typeof options.jobDir === "string" ? options.jobDir.trim() : "";
|
|
24
39
|
if (!jobDirInput) throw new Error("--job-dir is required");
|
|
25
40
|
const jobDir = resolve(jobDirInput);
|
|
26
|
-
if (!
|
|
41
|
+
if (!MANAGED_JOB_ID.test(basename(jobDir))) throw new Error("--job-dir must name a managed job directory");
|
|
27
42
|
const recover = options.recover === true;
|
|
28
43
|
const recoveryLockToken = typeof process.env.MBM_RECOVERY_LOCK_TOKEN === "string" ? process.env.MBM_RECOVERY_LOCK_TOKEN : "";
|
|
44
|
+
const launchToken = typeof process.env.MBM_RUNNER_LAUNCH_TOKEN === "string" ? process.env.MBM_RUNNER_LAUNCH_TOKEN : "";
|
|
29
45
|
delete process.env.MBM_RECOVERY_LOCK_TOKEN;
|
|
46
|
+
delete process.env.MBM_RUNNER_LAUNCH_TOKEN;
|
|
30
47
|
const planFile = join(jobDir, "plan.json");
|
|
31
48
|
const statusFile = join(jobDir, "status.json");
|
|
32
49
|
const resultFile = join(jobDir, "result.json");
|
|
@@ -36,6 +53,7 @@ const resourcesDir = join(runtimeDir, "resources");
|
|
|
36
53
|
const temporaryFilesDir = join(runtimeDir, "files");
|
|
37
54
|
const runnerPidFile = join(jobDir, "runner.pid");
|
|
38
55
|
const RUNNER_PROCESS_STARTED_AT = new Date(currentProcessStartTimeMs()).toISOString();
|
|
56
|
+
const resourceCoordinator = new ResourceCoordinator();
|
|
39
57
|
|
|
40
58
|
class JobCancelledError extends Error {
|
|
41
59
|
constructor() {
|
|
@@ -47,28 +65,41 @@ class JobCancelledError extends Error {
|
|
|
47
65
|
|
|
48
66
|
let activeChild = null;
|
|
49
67
|
let activeChildCancellationAware = false;
|
|
50
|
-
let
|
|
68
|
+
let activeChildTermination = null;
|
|
51
69
|
let cancelRequested = false;
|
|
70
|
+
let runnerClaimConfirmed = false;
|
|
71
|
+
let jobIdleSleepAssertion = null;
|
|
52
72
|
for (const signal of ["SIGTERM", "SIGINT"]) {
|
|
53
73
|
process.on(signal, () => requestCancellation());
|
|
54
74
|
}
|
|
55
75
|
|
|
56
76
|
const initial = readJson(statusFile, MAX_STATUS_BYTES);
|
|
57
77
|
assertLaunchState(initial);
|
|
58
|
-
createExclusiveFileSync(runnerPidFile, `${JSON.stringify({ pid: process.pid, processStartedAt: RUNNER_PROCESS_STARTED_AT })}\n`, { mode: 0o600 });
|
|
59
78
|
try {
|
|
79
|
+
await confirmRunnerClaim({
|
|
80
|
+
file: runnerPidFile, pid: process.pid, processStartedAt: RUNNER_PROCESS_STARTED_AT, launchToken,
|
|
81
|
+
});
|
|
82
|
+
if (initial.owner_kind === "account") {
|
|
83
|
+
jobIdleSleepAssertion = new MacosIdleSleepAssertion({ logger: managedJobIdleSleepLogger() });
|
|
84
|
+
jobIdleSleepAssertion.acquire();
|
|
85
|
+
}
|
|
60
86
|
if (recover) await releaseRecoveryClaim();
|
|
87
|
+
runnerClaimConfirmed = true;
|
|
61
88
|
const plan = readJson(planFile, 1024 * 1024);
|
|
62
89
|
assertPlanIntegrity(plan, initial);
|
|
63
90
|
await main(plan, initial);
|
|
64
91
|
} catch (error) {
|
|
65
92
|
recordFatalRunnerError(error);
|
|
93
|
+
process.exitCode = 1;
|
|
94
|
+
} finally {
|
|
95
|
+
jobIdleSleepAssertion?.release();
|
|
66
96
|
}
|
|
67
97
|
|
|
98
|
+
|
|
68
99
|
async function releaseRecoveryClaim() {
|
|
69
100
|
if (!/^[a-f0-9]{32}$/.test(recoveryLockToken)) throw new Error("recovery runner is missing its ownership token");
|
|
70
101
|
const file = join(jobDir, "recovery.lock");
|
|
71
|
-
const deadline = createMonotonicDeadline(
|
|
102
|
+
const deadline = createMonotonicDeadline(RECOVERY_LOCK_HANDOFF_WAIT_MS);
|
|
72
103
|
while (!deadline.expired()) {
|
|
73
104
|
if (removeOwnedJsonFileSync(file, { pid: process.pid, token: recoveryLockToken })) return;
|
|
74
105
|
await new Promise((resolvePromise) => { setTimeout(resolvePromise, 10); });
|
|
@@ -104,7 +135,7 @@ async function main(plan, initial) {
|
|
|
104
135
|
for (let index = 0; index < plan.steps.length; index += 1) {
|
|
105
136
|
if (isCancellationRequested()) throw new JobCancelledError();
|
|
106
137
|
updateStatus(status, { status: "running", current_phase: "steps", current_step: index });
|
|
107
|
-
const result = await runStep(plan.steps[index], index, "steps", plan, resourceContext, true, captureBudget);
|
|
138
|
+
const result = await runStep(plan.steps[index], index, "steps", plan, resourceContext, true, captureBudget, status);
|
|
108
139
|
mainResults.push(result);
|
|
109
140
|
if (result.timed_out && !plan.steps[index].allow_failure) throw new Error(`step ${index + 1} timed out`);
|
|
110
141
|
if (result.code !== 0 && !plan.steps[index].allow_failure) throw new Error(`step ${index + 1} exited ${result.code}`);
|
|
@@ -117,7 +148,7 @@ async function main(plan, initial) {
|
|
|
117
148
|
try {
|
|
118
149
|
for (let index = 0; index < plan.finally_steps.length; index += 1) {
|
|
119
150
|
updateStatus(status, { status: "cleaning", current_phase: recover ? "recovery-cleanup" : "finally_steps", current_step: index });
|
|
120
|
-
const result = await runStep(plan.finally_steps[index], index, "finally_steps", plan, resourceContext, false, captureBudget);
|
|
151
|
+
const result = await runStep(plan.finally_steps[index], index, "finally_steps", plan, resourceContext, false, captureBudget, status);
|
|
121
152
|
cleanupResults.push(result);
|
|
122
153
|
if (result.timed_out && !plan.finally_steps[index].allow_failure && !cleanupError) cleanupError = new Error(`cleanup step ${index + 1} timed out`);
|
|
123
154
|
if (result.code !== 0 && !plan.finally_steps[index].allow_failure && !cleanupError) cleanupError = new Error(`cleanup step ${index + 1} exited ${result.code}`);
|
|
@@ -125,15 +156,23 @@ async function main(plan, initial) {
|
|
|
125
156
|
} catch (error) {
|
|
126
157
|
cleanupError ||= error;
|
|
127
158
|
}
|
|
128
|
-
rmSync(runtimeDir, { recursive: true, force: true });
|
|
129
159
|
}
|
|
130
160
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
161
|
+
let cancellationMarker = false;
|
|
162
|
+
try { cancellationMarker = managedJobCancellationRequested(cancelFile); }
|
|
163
|
+
catch (error) {
|
|
164
|
+
mainError = mainError
|
|
165
|
+
? new AggregateError([mainError, error], "managed job failed and cancellation state could not be verified")
|
|
166
|
+
: error;
|
|
167
|
+
}
|
|
168
|
+
const cancelled = mainError instanceof JobCancelledError || cancellationMarker;
|
|
169
|
+
const finalStatus = managedJobFinalStatus({ recover, cancelled, mainError, cleanupError });
|
|
170
|
+
if (cleanupError && classifyError(cleanupError) === "resource_error") {
|
|
171
|
+
const stage = cleanupError?.details?.admission_reason || cleanupError?.details?.reason || cleanupError?.code
|
|
172
|
+
|| (/resource lease/i.test(String(cleanupError?.message || "")) ? "resource_lease" : "resource_operation");
|
|
173
|
+
try { process.stderr.write(`managed job cleanup infrastructure failure: stage=${sanitizeLogText(stage, 64)}\n`); }
|
|
174
|
+
catch { /* Last-resort diagnostics must not replace the managed job's primary failure. */ }
|
|
175
|
+
}
|
|
137
176
|
|
|
138
177
|
const result = {
|
|
139
178
|
job_id: status.job_id,
|
|
@@ -148,24 +187,23 @@ async function main(plan, initial) {
|
|
|
148
187
|
capture_remaining_bytes: captureBudget.remaining,
|
|
149
188
|
finished_at: new Date().toISOString(),
|
|
150
189
|
};
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
190
|
+
const terminal = persistManagedJobTerminal({
|
|
191
|
+
statusFile, resultFile,
|
|
192
|
+
artifacts: [runtimeDir, planFile, runnerPidFile, cancelFile],
|
|
193
|
+
status: { ...status, runner_pid: process.pid, runner_process_started_at: RUNNER_PROCESS_STARTED_AT },
|
|
194
|
+
result, writeJson,
|
|
195
|
+
removeFile: (file) => rmSync(file, { recursive: true, force: true }),
|
|
196
|
+
maxStatusBytes: MAX_STATUS_BYTES, maxResultBytes: MAX_RESULT_BYTES,
|
|
158
197
|
});
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
try { rmSync(cancelFile, { force: true }); } catch {}
|
|
198
|
+
Object.assign(status, terminal.status);
|
|
199
|
+
reportTerminalPersistenceFailure(terminal);
|
|
162
200
|
}
|
|
163
201
|
|
|
164
202
|
function assertLaunchState(status) {
|
|
165
203
|
if (!status || typeof status !== "object" || Array.isArray(status)) throw new Error("job status is unavailable or invalid");
|
|
166
204
|
const expected = recover ? "interrupted" : "queued";
|
|
167
205
|
if (status.status !== expected) throw new Error(`runner cannot start job in status: ${status.status}`);
|
|
168
|
-
if (status.approval
|
|
206
|
+
if (status.approval !== "mcp") throw new Error("runner cannot start a managed job without direct execution authority");
|
|
169
207
|
}
|
|
170
208
|
|
|
171
209
|
function assertPlanIntegrity(plan, status) {
|
|
@@ -176,10 +214,24 @@ function assertPlanIntegrity(plan, status) {
|
|
|
176
214
|
}
|
|
177
215
|
|
|
178
216
|
function recordFatalRunnerError(error) {
|
|
179
|
-
rmSync(runtimeDir, { recursive: true, force: true });
|
|
180
217
|
const now = new Date().toISOString();
|
|
181
|
-
|
|
182
|
-
|
|
218
|
+
try {
|
|
219
|
+
process.stderr.write(`managed job runner fatal: error_class=${classifyError(error)} message=${sanitizeLogText(error?.message || error, 512)}\n`);
|
|
220
|
+
} catch { /* Last-resort diagnostics must not prevent terminal-state recovery. */ }
|
|
221
|
+
if (!runnerClaimConfirmed) {
|
|
222
|
+
reportFatalRecordSkipped("runner_claim_unconfirmed");
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
let status;
|
|
226
|
+
try { status = readJson(statusFile, MAX_STATUS_BYTES); }
|
|
227
|
+
catch (stateError) {
|
|
228
|
+
reportFatalRecordSkipped(classifyError(stateError));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (!fatalRunnerStatusIsCurrent(status, initial)) {
|
|
232
|
+
reportFatalRecordSkipped("state_integrity");
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
183
235
|
const finalStatus = recover ? "recovery_failed" : "runner_failed";
|
|
184
236
|
const result = {
|
|
185
237
|
job_id: status.job_id ?? null,
|
|
@@ -192,27 +244,62 @@ function recordFatalRunnerError(error) {
|
|
|
192
244
|
cleanup_error_class: recover ? classifyError(error) : null,
|
|
193
245
|
finished_at: now,
|
|
194
246
|
};
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
247
|
+
const terminal = persistManagedJobTerminal({
|
|
248
|
+
statusFile, resultFile,
|
|
249
|
+
artifacts: [runtimeDir, planFile, runnerPidFile, cancelFile],
|
|
250
|
+
status: {
|
|
198
251
|
...status,
|
|
199
|
-
status: finalStatus,
|
|
200
|
-
current_phase: null,
|
|
201
|
-
current_step: null,
|
|
202
252
|
runner_pid: process.pid,
|
|
203
253
|
runner_process_started_at: RUNNER_PROCESS_STARTED_AT,
|
|
204
|
-
updated_at: now,
|
|
205
|
-
finished_at: now,
|
|
206
|
-
error_class: result.error_class,
|
|
207
254
|
cleanup_guarantee: "best-effort-finally-and-recovery",
|
|
208
|
-
},
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
255
|
+
},
|
|
256
|
+
result, writeJson,
|
|
257
|
+
removeFile: (file) => rmSync(file, { recursive: true, force: true }),
|
|
258
|
+
maxStatusBytes: MAX_STATUS_BYTES, maxResultBytes: MAX_RESULT_BYTES,
|
|
259
|
+
});
|
|
260
|
+
reportTerminalPersistenceFailure(terminal);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function fatalRunnerStatusIsCurrent(status, accepted) {
|
|
264
|
+
if (!status || typeof status !== "object" || Array.isArray(status)
|
|
265
|
+
|| !accepted || typeof accepted !== "object" || Array.isArray(accepted)) return false;
|
|
266
|
+
if (isTerminalManagedJobStatus(status.status) || !ACTIVE_JOB_STATES.has(status.status)) return false;
|
|
267
|
+
if (status.job_id !== basename(jobDir) || FATAL_IDENTITY_FIELDS.some((field) => status[field] !== accepted[field])) return false;
|
|
268
|
+
const runnerPid = Number(status.runner_pid || 0);
|
|
269
|
+
const acceptedPid = Number(accepted.runner_pid || 0);
|
|
270
|
+
if (runnerPid && runnerPid !== process.pid && runnerPid !== acceptedPid) return false;
|
|
271
|
+
const runnerStartedAt = String(status.runner_process_started_at || "");
|
|
272
|
+
const acceptedStartedAt = String(accepted.runner_process_started_at || "");
|
|
273
|
+
return !runnerStartedAt || runnerStartedAt === RUNNER_PROCESS_STARTED_AT || runnerStartedAt === acceptedStartedAt;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function reportFatalRecordSkipped(reason) {
|
|
277
|
+
try { process.stderr.write(`managed job fatal terminal record skipped: reason=${sanitizeLogText(reason, 64)}\n`); }
|
|
278
|
+
catch { /* A closed diagnostic stream is irrelevant to the already-decided state transition. */ }
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function reportTerminalPersistenceFailure(terminal) {
|
|
282
|
+
if (terminal.statusPersisted && terminal.artifactsScrubbed && !terminal.statusErrorClass) return;
|
|
283
|
+
const fields = [
|
|
284
|
+
`status_persisted=${terminal.statusPersisted}`,
|
|
285
|
+
`result_persisted=${terminal.resultPersisted}`,
|
|
286
|
+
`artifacts_scrubbed=${terminal.artifactsScrubbed}`,
|
|
287
|
+
`status_error=${terminal.statusErrorClass || "none"}`,
|
|
288
|
+
`result_error=${terminal.resultErrorClass || "none"}`,
|
|
289
|
+
`cleanup_error=${terminal.cleanupErrorClass || "none"}`,
|
|
290
|
+
];
|
|
291
|
+
try { process.stderr.write(`managed job terminal persistence incomplete: ${fields.join(" ")}\n`); }
|
|
292
|
+
catch { /* Persistence outcome is authoritative even if its fallback diagnostic cannot be written. */ }
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function managedJobIdleSleepLogger() {
|
|
296
|
+
return { event(_level, _name, fields) {
|
|
297
|
+
try { process.stderr.write(`managed job idle-sleep assertion unavailable: error_class=${sanitizeLogText(fields?.error_class || "unknown", 64)}\n`); }
|
|
298
|
+
catch { /* Auxiliary power diagnostics must not replace managed-job settlement. */ }
|
|
299
|
+
} };
|
|
213
300
|
}
|
|
214
301
|
|
|
215
|
-
async function runStep(step, index, phase, plan, resourceContext, cancellationAware, captureBudget) {
|
|
302
|
+
async function runStep(step, index, phase, plan, resourceContext, cancellationAware, captureBudget, status) {
|
|
216
303
|
const argv = step.argv.map((value) => substitute(value, plan, resourceContext));
|
|
217
304
|
const envOverrides = Object.fromEntries(Object.entries(step.env || {}).map(([key, value]) => [key, substitute(value, plan, resourceContext)]));
|
|
218
305
|
const envResourceValues = Object.fromEntries(Object.entries(step.env_resources || {}).map(([key, name]) => [key, resourceEnvValue(name, resourceContext.bytes)]));
|
|
@@ -235,6 +322,19 @@ async function runStep(step, index, phase, plan, resourceContext, cancellationAw
|
|
|
235
322
|
cancellationAware,
|
|
236
323
|
captureOutput: step.capture_output !== "discard",
|
|
237
324
|
captureBudget,
|
|
325
|
+
resourcePriority: plan?.execution_priority === "interactive" ? "interactive" : "background",
|
|
326
|
+
delegatedProcess: plan?.delegated_process === true,
|
|
327
|
+
workspace: plan.workspace,
|
|
328
|
+
onAdmissionStart: () => updateStatus(status, {
|
|
329
|
+
status: phase === "steps" ? "running" : "cleaning",
|
|
330
|
+
current_phase: "resource_admission",
|
|
331
|
+
current_step: index,
|
|
332
|
+
}),
|
|
333
|
+
onAdmissionComplete: () => updateStatus(status, {
|
|
334
|
+
status: phase === "steps" ? "running" : "cleaning",
|
|
335
|
+
current_phase: phase === "finally_steps" && recover ? "recovery-cleanup" : phase,
|
|
336
|
+
current_step: index,
|
|
337
|
+
}),
|
|
238
338
|
});
|
|
239
339
|
return {
|
|
240
340
|
index,
|
|
@@ -245,6 +345,7 @@ async function runStep(step, index, phase, plan, resourceContext, cancellationAw
|
|
|
245
345
|
signal: raw.signal,
|
|
246
346
|
timed_out: raw.timedOut,
|
|
247
347
|
duration_ms: performance.now() - started,
|
|
348
|
+
resource_admission_ms: raw.resourceAdmissionMs,
|
|
248
349
|
stdout: step.capture_output === "discard" ? "" : redactOutput(raw.stdout, resourceContext),
|
|
249
350
|
stderr: step.capture_output === "discard" ? "" : redactOutput(raw.stderr, resourceContext),
|
|
250
351
|
output_discarded: step.capture_output === "discard",
|
|
@@ -255,31 +356,87 @@ async function runStep(step, index, phase, plan, resourceContext, cancellationAw
|
|
|
255
356
|
};
|
|
256
357
|
}
|
|
257
358
|
|
|
258
|
-
function spawnStep(argv, {
|
|
359
|
+
async function spawnStep(argv, {
|
|
360
|
+
cwd, env, input, timeoutMs, cancellationAware, captureOutput, captureBudget,
|
|
361
|
+
resourcePriority = "background", delegatedProcess = false, workspace = "",
|
|
362
|
+
onAdmissionStart = null, onAdmissionComplete = null,
|
|
363
|
+
}) {
|
|
364
|
+
if (cancellationAware && isCancellationRequested()) throw new JobCancelledError();
|
|
365
|
+
const admissionStarted = performance.now();
|
|
366
|
+
onAdmissionStart?.();
|
|
367
|
+
let admitted;
|
|
368
|
+
let resourceAdmissionMs = 0;
|
|
369
|
+
try {
|
|
370
|
+
admitted = await acquireProcessResources(resourceCoordinator, argv[0], argv.slice(1), env, {
|
|
371
|
+
cwd, priority: resourcePriority, waitMs: relayContract.maximumManagedJobResourceAdmissionWaitMs,
|
|
372
|
+
cancelCheck: () => { if (cancellationAware && isCancellationRequested()) throw new JobCancelledError(); },
|
|
373
|
+
});
|
|
374
|
+
onAdmissionComplete?.();
|
|
375
|
+
resourceAdmissionMs = performance.now() - admissionStarted;
|
|
376
|
+
} catch (error) {
|
|
377
|
+
if (admitted?.lease) {
|
|
378
|
+
try { await releaseProcessResources(admitted.lease); }
|
|
379
|
+
catch (cleanupError) {
|
|
380
|
+
throw new AggregateError([error, cleanupError], "managed job admission status update and resource lease cleanup both failed");
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
throw error;
|
|
384
|
+
}
|
|
259
385
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
260
|
-
|
|
261
|
-
|
|
386
|
+
let child;
|
|
387
|
+
try {
|
|
388
|
+
const launch = delegatedProcess
|
|
389
|
+
? delegatedProcessCommand({
|
|
390
|
+
command: admitted.command,
|
|
391
|
+
args: admitted.args,
|
|
392
|
+
workspace,
|
|
393
|
+
runtimeDir,
|
|
394
|
+
forceDelegated: true,
|
|
395
|
+
})
|
|
396
|
+
: { command: admitted.command, args: admitted.args };
|
|
397
|
+
child = spawn(launch.command, launch.args, {
|
|
398
|
+
cwd, env: admitted.environment, detached: process.platform !== "win32", windowsHide: true,
|
|
399
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
400
|
+
});
|
|
401
|
+
} catch (error) {
|
|
402
|
+
void releaseProcessResourcesQuietly(admitted.lease).then(() => rejectPromise(error));
|
|
262
403
|
return;
|
|
263
404
|
}
|
|
264
|
-
const child = spawn(argv[0], argv.slice(1), {
|
|
265
|
-
cwd,
|
|
266
|
-
env,
|
|
267
|
-
detached: process.platform !== "win32",
|
|
268
|
-
windowsHide: true,
|
|
269
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
270
|
-
});
|
|
271
405
|
activeChild = child;
|
|
272
406
|
activeChildCancellationAware = cancellationAware;
|
|
407
|
+
let terminationSettlement = null;
|
|
408
|
+
const terminateAndSettle = () => {
|
|
409
|
+
if (terminationSettlement) return terminationSettlement;
|
|
410
|
+
terminationSettlement = new Promise((resolveTermination) => {
|
|
411
|
+
try {
|
|
412
|
+
terminateProcessTreeWithEscalation(child, {
|
|
413
|
+
onTerminationSettled: resolveTermination,
|
|
414
|
+
});
|
|
415
|
+
} catch {
|
|
416
|
+
resolveTermination();
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
return terminationSettlement;
|
|
420
|
+
};
|
|
421
|
+
activeChildTermination = terminateAndSettle;
|
|
422
|
+
let resourceBindError = null; let childError = null;
|
|
423
|
+
const resourceBinding = bindProcessResources(admitted.lease, child).catch((error) => {
|
|
424
|
+
resourceBindError = error;
|
|
425
|
+
void terminateAndSettle();
|
|
426
|
+
});
|
|
273
427
|
let stdout = Buffer.alloc(0);
|
|
274
428
|
let stderr = Buffer.alloc(0);
|
|
275
429
|
let stdoutTruncated = 0;
|
|
276
430
|
let stderrTruncated = 0;
|
|
277
431
|
let timedOut = false;
|
|
278
432
|
let closed = false;
|
|
279
|
-
let killTimer = null;
|
|
280
433
|
const timer = setTimeout(() => {
|
|
434
|
+
if (childExitedBeforeTimeout({ exitCode: child.exitCode, signalCode: child.signalCode, processState: processState(child.pid) })) {
|
|
435
|
+
settlement.onExit(child.exitCode, child.signalCode);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
281
438
|
timedOut = true;
|
|
282
|
-
|
|
439
|
+
void terminateAndSettle();
|
|
283
440
|
}, timeoutMs);
|
|
284
441
|
timer.unref?.();
|
|
285
442
|
const cancellationPoll = setInterval(() => {
|
|
@@ -300,38 +457,37 @@ function spawnStep(argv, { cwd, env, input, timeoutMs, cancellationAware, captur
|
|
|
300
457
|
stderr = next.buffer;
|
|
301
458
|
stderrTruncated += next.truncated;
|
|
302
459
|
});
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}));
|
|
460
|
+
const settlement = attachChildProcessSettlement(child, {
|
|
461
|
+
onSettle(code, signal) {
|
|
462
|
+
finish(() => {
|
|
463
|
+
if (resourceBindError) { rejectPromise(resourceBindError); return; }
|
|
464
|
+
if (childError) { rejectPromise(childError); return; }
|
|
465
|
+
if (cancellationAware && isCancellationRequested()) { rejectPromise(new JobCancelledError()); return; }
|
|
466
|
+
resolvePromise({
|
|
467
|
+
code: Number.isInteger(code) ? code : 1,
|
|
468
|
+
signal: signal ? String(signal) : null,
|
|
469
|
+
timedOut, resourceAdmissionMs, stdout, stderr, stdoutTruncated, stderrTruncated,
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
},
|
|
473
|
+
});
|
|
474
|
+
child.on("error", (error) => { childError ||= error; });
|
|
319
475
|
if (input && input.length) child.stdin.end(input);
|
|
320
476
|
else child.stdin.end();
|
|
321
477
|
|
|
322
478
|
function finish(callback) {
|
|
323
479
|
if (closed) return;
|
|
324
480
|
closed = true;
|
|
481
|
+
settlement.cancel();
|
|
325
482
|
clearTimeout(timer);
|
|
326
|
-
if (killTimer && !timedOut) clearTimeout(killTimer);
|
|
327
483
|
clearInterval(cancellationPoll);
|
|
328
484
|
activeChild = null;
|
|
329
485
|
activeChildCancellationAware = false;
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
486
|
+
activeChildTermination = null;
|
|
487
|
+
void resourceBinding
|
|
488
|
+
.then(() => terminationSettlement || undefined)
|
|
489
|
+
.then(() => releaseProcessResources(admitted.lease))
|
|
490
|
+
.then(callback, rejectPromise);
|
|
335
491
|
}
|
|
336
492
|
});
|
|
337
493
|
}
|
|
@@ -362,7 +518,7 @@ function materializeResources(resources) {
|
|
|
362
518
|
if (text.length > 0) patterns.push(text);
|
|
363
519
|
const trimmed = text.replace(/[\r\n]+$/, "");
|
|
364
520
|
if (trimmed.length > 0 && trimmed !== text) patterns.push(trimmed);
|
|
365
|
-
} catch {}
|
|
521
|
+
} catch { /* Binary resources still use byte-level base64/hex redaction below. */ }
|
|
366
522
|
if (data.length > 0 && data.length <= 64 * 1024) {
|
|
367
523
|
patterns.push(data.toString("base64"), data.toString("hex"));
|
|
368
524
|
}
|
|
@@ -471,16 +627,12 @@ function updateStatus(status, changes) {
|
|
|
471
627
|
|
|
472
628
|
function requestCancellation() {
|
|
473
629
|
cancelRequested = true;
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
if (cancellationEscalation) return;
|
|
477
|
-
cancellationEscalation = terminateProcessTreeWithEscalation(child, {
|
|
478
|
-
onEscalated: () => { cancellationEscalation = null; },
|
|
479
|
-
});
|
|
630
|
+
if (!activeChild || !activeChildCancellationAware) return;
|
|
631
|
+
void activeChildTermination?.();
|
|
480
632
|
}
|
|
481
633
|
|
|
482
634
|
function isCancellationRequested() {
|
|
483
|
-
return cancelRequested ||
|
|
635
|
+
return cancelRequested || managedJobCancellationRequested(cancelFile);
|
|
484
636
|
}
|
|
485
637
|
|
|
486
638
|
function appendLimited(current, chunk, limit, budget) {
|
|
@@ -504,7 +656,10 @@ function writeJson(file, value, maxBytes) {
|
|
|
504
656
|
}
|
|
505
657
|
|
|
506
658
|
function readJson(file, maxBytes) {
|
|
507
|
-
return JSON.parse(
|
|
659
|
+
return JSON.parse(readBoundedRegularFileSync(file, maxBytes, "managed job runner state", {
|
|
660
|
+
verifyPathIdentity: true,
|
|
661
|
+
rejectMultipleLinks: true,
|
|
662
|
+
}).toString("utf8"));
|
|
508
663
|
}
|
|
509
664
|
|
|
510
665
|
function readBoundedFile(file, maxBytes) {
|
package/src/local/lifecycle.mjs
CHANGED
|
@@ -31,6 +31,12 @@ export class LifecycleController {
|
|
|
31
31
|
this.transition("failed");
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
markStopFailed(error) {
|
|
35
|
+
if (this.state !== "stopping") throw new BridgeError("conflict", `${this.name} cannot fail stop from state ${this.state}`);
|
|
36
|
+
this.failureCode = String(error?.code || error?.name || "execution_failed").slice(0, 64);
|
|
37
|
+
this.transition("stop_failed");
|
|
38
|
+
}
|
|
39
|
+
|
|
34
40
|
beginStop() {
|
|
35
41
|
if (this.state === "stopped" || this.state === "stopping") return false;
|
|
36
42
|
this.transition("stopping");
|
package/src/local/log.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import { errorCode } from "./errors.mjs";
|
|
4
|
+
import { sanitizePortableLogText } from "../shared/log-redaction.mjs";
|
|
4
5
|
|
|
5
6
|
const COLORS = {
|
|
6
7
|
reset: "\x1b[0m",
|
|
@@ -18,20 +19,6 @@ const MAX_LOG_OBJECT_KEYS = 48;
|
|
|
18
19
|
const LEVEL_RANK = Object.freeze({ debug: 10, info: 20, success: 20, warn: 30, error: 40 });
|
|
19
20
|
const SENSITIVE_KEY = /(authorization|cookie|password|passwd|secret|token|api[_-]?key|private[_-]?key|credential)/i;
|
|
20
21
|
const LOCAL_PATH_KEY = /(path|paths|cwd|workspace|directory|(?:^|[_-])dir(?:$|[_-])|root|home)/i;
|
|
21
|
-
const SECRET_VALUE = /\b(?:account_admin|account_password|daemon_secret|token_version|mcp_at|mcp_code)_[A-Za-z0-9_-]+\b/g;
|
|
22
|
-
const BEARER_VALUE = /\bBearer\s+[A-Za-z0-9._~+\/-]+=*\b/gi;
|
|
23
|
-
const EMAIL_VALUE = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
|
|
24
|
-
const AWS_ACCESS_KEY = /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g;
|
|
25
|
-
const GITHUB_TOKEN = /\bgh[pousr]_[A-Za-z0-9_]{30,}\b/g;
|
|
26
|
-
const GITLAB_TOKEN = /\bglpat-[A-Za-z0-9_-]{20,}\b/g;
|
|
27
|
-
const NPM_TOKEN = /\bnpm_[A-Za-z0-9]{30,}\b/g;
|
|
28
|
-
const SLACK_TOKEN = /\bxox[aboprs]-[A-Za-z0-9-]{10,}\b/g;
|
|
29
|
-
const GOOGLE_API_KEY = /\bAIza[A-Za-z0-9_-]{30,}\b/g;
|
|
30
|
-
const PAYMENT_API_KEY = /\b(?:sk|rk|pk)_live_[A-Za-z0-9]{16,}\b/g;
|
|
31
|
-
const JWT_VALUE = /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g;
|
|
32
|
-
const URL_CREDENTIALS = /https?:\/\/[^\s/@:"'<>]+:[^\s/@"'<>]+@[^\s/"'<>]+/gi;
|
|
33
|
-
const API_SECRET = /\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b/g;
|
|
34
|
-
const PRIVATE_KEY_HEADER = /-----BEGIN\s+(?:(?:OPENSSH|RSA|EC|DSA)\s+|ENCRYPTED\s+)?PRIVATE\s+KEY-----/g;
|
|
35
22
|
const HOME_PATHS = [...new Set([process.env.HOME, process.env.USERPROFILE, safeHomeDirectory()].filter(value => typeof value === "string" && value.length > 1))]
|
|
36
23
|
.sort((left, right) => right.length - left.length);
|
|
37
24
|
|
|
@@ -46,24 +33,36 @@ export function createLogger(options = {}) {
|
|
|
46
33
|
|
|
47
34
|
const write = (stream, level, label, color, message, fields) => {
|
|
48
35
|
if (LEVEL_RANK[level] < LEVEL_RANK[minimumLevel]) return;
|
|
36
|
+
const sanitizedMessage = sanitizeLogText(message, MAX_LOG_MESSAGE_CHARS);
|
|
37
|
+
if (options.format === "json") {
|
|
38
|
+
const entry = sanitizeLogValue({
|
|
39
|
+
...(fields && typeof fields === "object" ? fields : {}),
|
|
40
|
+
timestamp: new Date().toISOString(),
|
|
41
|
+
level: level === "success" ? "info" : level,
|
|
42
|
+
component,
|
|
43
|
+
message: sanitizedMessage,
|
|
44
|
+
});
|
|
45
|
+
stream.write(`${JSON.stringify(entry)}\n`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
49
48
|
const prefix = useColor ? `${color}${label}${COLORS.reset}` : label;
|
|
50
49
|
const suffix = formatFields(fields);
|
|
51
|
-
stream.write(`${prefix} ${component}: ${
|
|
50
|
+
stream.write(`${prefix} ${component}: ${sanitizedMessage}${suffix}\n`);
|
|
52
51
|
};
|
|
53
52
|
|
|
54
53
|
const event = (level, name, fields = {}, message = "") => {
|
|
55
54
|
const normalizedLevel = normalizeEventLevel(level);
|
|
56
55
|
const eventName = sanitizeEventName(name);
|
|
57
|
-
const payload = { event: eventName
|
|
56
|
+
const payload = { ...fields, event: eventName };
|
|
58
57
|
const humanMessage = message || humanizeEventName(eventName);
|
|
59
58
|
if (options.format === "json") {
|
|
60
59
|
if (LEVEL_RANK[normalizedLevel] < LEVEL_RANK[minimumLevel]) return;
|
|
61
60
|
const entry = sanitizeLogValue({
|
|
61
|
+
...payload,
|
|
62
62
|
timestamp: new Date().toISOString(),
|
|
63
63
|
level: normalizedLevel === "success" ? "info" : normalizedLevel,
|
|
64
64
|
component,
|
|
65
65
|
message: sanitizeLogText(humanMessage, MAX_LOG_MESSAGE_CHARS),
|
|
66
|
-
...payload,
|
|
67
66
|
});
|
|
68
67
|
const target = normalizedLevel === "info" || normalizedLevel === "success" ? stdout : stderr;
|
|
69
68
|
target.write(`${JSON.stringify(entry)}\n`);
|
|
@@ -83,6 +82,7 @@ export function createLogger(options = {}) {
|
|
|
83
82
|
return {
|
|
84
83
|
verbose: minimumLevel === "debug",
|
|
85
84
|
level: minimumLevel,
|
|
85
|
+
format: options.format === "json" ? "json" : "text",
|
|
86
86
|
child(childComponent) {
|
|
87
87
|
return createLogger({ ...options, component: childComponent });
|
|
88
88
|
},
|
|
@@ -137,7 +137,7 @@ function sanitizeLogValue(value, key = "", seen = new WeakSet(), depth = 0) {
|
|
|
137
137
|
if (seen.has(value)) return "<circular>";
|
|
138
138
|
seen.add(value);
|
|
139
139
|
if (Array.isArray(value)) return value.slice(0, MAX_LOG_ARRAY_ITEMS).map(item => sanitizeLogValue(item, "", seen, depth + 1));
|
|
140
|
-
const out =
|
|
140
|
+
const out = Object.create(null);
|
|
141
141
|
for (const [childKey, childValue] of Object.entries(value).slice(0, MAX_LOG_OBJECT_KEYS)) {
|
|
142
142
|
out[childKey] = sanitizeLogValue(childValue, childKey, seen, depth + 1);
|
|
143
143
|
}
|
|
@@ -145,33 +145,7 @@ function sanitizeLogValue(value, key = "", seen = new WeakSet(), depth = 0) {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
export function sanitizeLogText(value, maxChars = MAX_LOG_MESSAGE_CHARS) {
|
|
148
|
-
|
|
149
|
-
try { raw = String(value ?? ""); } catch { raw = "<unprintable>"; }
|
|
150
|
-
let sanitized = raw
|
|
151
|
-
.replace(SECRET_VALUE, "<redacted-secret>")
|
|
152
|
-
.replace(BEARER_VALUE, "Bearer <redacted>")
|
|
153
|
-
.replace(AWS_ACCESS_KEY, "<redacted-cloud-key>")
|
|
154
|
-
.replace(GITHUB_TOKEN, "<redacted-access-token>")
|
|
155
|
-
.replace(GITLAB_TOKEN, "<redacted-access-token>")
|
|
156
|
-
.replace(NPM_TOKEN, "<redacted-access-token>")
|
|
157
|
-
.replace(SLACK_TOKEN, "<redacted-access-token>")
|
|
158
|
-
.replace(GOOGLE_API_KEY, "<redacted-cloud-key>")
|
|
159
|
-
.replace(PAYMENT_API_KEY, "<redacted-api-secret>")
|
|
160
|
-
.replace(JWT_VALUE, "<redacted-bearer-token>")
|
|
161
|
-
.replace(URL_CREDENTIALS, "<redacted-credential-url>")
|
|
162
|
-
.replace(API_SECRET, "<redacted-api-secret>")
|
|
163
|
-
.replace(PRIVATE_KEY_HEADER, "<redacted-private-key-header>")
|
|
164
|
-
.replace(EMAIL_VALUE, "<redacted-email>");
|
|
165
|
-
for (const home of HOME_PATHS) sanitized = sanitized.split(home).join("<home>");
|
|
166
|
-
sanitized = sanitized
|
|
167
|
-
.replace(/\/(?:Users|home)\/[^/\s"'<>]+(?=\/|$)/g, "<home>")
|
|
168
|
-
.replace(/\b[A-Za-z]:\\Users\\[^\\\s"'<>]+(?=\\|$)/g, "<home>")
|
|
169
|
-
.replace(/[\u200B-\u200F\u202A-\u202E\u2066-\u2069\uFEFF]/g, "")
|
|
170
|
-
.replace(/[\r\n\t]/g, match => match === "\t" ? "\\t" : "\\n")
|
|
171
|
-
.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "?");
|
|
172
|
-
if (!Number.isFinite(Number(maxChars)) || Number(maxChars) <= 0) return "";
|
|
173
|
-
const limit = Math.max(16, Number(maxChars));
|
|
174
|
-
return sanitized.length > limit ? `${sanitized.slice(0, limit - 1)}…` : sanitized;
|
|
148
|
+
return sanitizePortableLogText(value, { maxChars, homePaths: HOME_PATHS });
|
|
175
149
|
}
|
|
176
150
|
|
|
177
151
|
function safeHomeDirectory() {
|