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/tools.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import serverMetadata from "../shared/server-metadata.json" with { type: "json" };
|
|
2
2
|
import { projectMcpResult } from "../shared/result-projection.mjs";
|
|
3
|
+
import { completeResult } from "../shared/mcp-protocol.mjs";
|
|
3
4
|
export {
|
|
4
5
|
DEFAULT_POLICY_PROFILE,
|
|
5
6
|
DEFAULT_POLICY_REVISION,
|
|
@@ -10,6 +11,7 @@ export {
|
|
|
10
11
|
allToolNames,
|
|
11
12
|
assertCanonicalFullPolicy,
|
|
12
13
|
assertToolAllowed,
|
|
14
|
+
intersectPolicies,
|
|
13
15
|
isCanonicalFullPolicy,
|
|
14
16
|
normalizePolicy,
|
|
15
17
|
policyAllowsAvailability,
|
|
@@ -26,13 +28,13 @@ export const MCP_PROTOCOL_VERSION = String(serverMetadata.protocolVersion);
|
|
|
26
28
|
export const MCP_SUPPORTED_PROTOCOL_VERSIONS = Object.freeze(serverMetadata.supportedProtocolVersions.map((value) => String(value)));
|
|
27
29
|
export const MCP_INSTRUCTIONS = Object.freeze(serverMetadata.instructions.map((value) => String(value))).join("\n");
|
|
28
30
|
|
|
29
|
-
export function toolResult(value, isError = false) {
|
|
31
|
+
export function toolResult(value, isError = false, serverInfo) {
|
|
30
32
|
const special = specialMcpResult(value);
|
|
31
|
-
if (special) return { ...special, isError };
|
|
33
|
+
if (special) return completeResult({ ...special, isError }, serverInfo);
|
|
32
34
|
const projection = projectMcpResult(value);
|
|
33
35
|
const result = { content: [{ type: "text", text: projection.text }], isError };
|
|
34
|
-
if (projection.
|
|
35
|
-
return result;
|
|
36
|
+
if (projection.hasStructuredContent) result.structuredContent = structuredClone(projection.structuredContent);
|
|
37
|
+
return completeResult(result, serverInfo);
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
export function rpcResult(id, result) {
|
|
@@ -50,7 +52,7 @@ function specialMcpResult(value) {
|
|
|
50
52
|
const special = value && typeof value === "object" && !Array.isArray(value) ? value.$mcp : null;
|
|
51
53
|
if (!special || typeof special !== "object" || !Array.isArray(special.content)) return null;
|
|
52
54
|
const result = { content: structuredClone(special.content) };
|
|
53
|
-
if (
|
|
55
|
+
if (Object.prototype.hasOwnProperty.call(special, "structuredContent")) {
|
|
54
56
|
result.structuredContent = structuredClone(special.structuredContent);
|
|
55
57
|
}
|
|
56
58
|
return result;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { accessSync, constants as fsConstants, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { BridgeError } from "./errors.mjs";
|
|
5
|
+
|
|
6
|
+
export function resolveTrustedExecutable(options = {}) {
|
|
7
|
+
const platform = String(options.platform || process.platform);
|
|
8
|
+
const candidates = Array.isArray(options.candidates) ? options.candidates : [];
|
|
9
|
+
const deniedRoots = [options.workspace, options.stateRoot, options.runtimeDir, options.home || safeHome()]
|
|
10
|
+
.filter(Boolean).map((value) => canonicalOrResolved(String(value), options.realpath || realpathSync));
|
|
11
|
+
const realpath = options.realpath || realpathSync;
|
|
12
|
+
const stat = options.stat || statSync;
|
|
13
|
+
const access = options.access || accessSync;
|
|
14
|
+
for (const candidate of candidates) {
|
|
15
|
+
if (!path.isAbsolute(String(candidate || ""))) continue;
|
|
16
|
+
let canonical;
|
|
17
|
+
let info;
|
|
18
|
+
try {
|
|
19
|
+
canonical = realpath(String(candidate));
|
|
20
|
+
info = stat(canonical);
|
|
21
|
+
if (!info.isFile()) continue;
|
|
22
|
+
if (platform !== "win32") access(canonical, fsConstants.X_OK);
|
|
23
|
+
} catch { continue; }
|
|
24
|
+
if (platform !== "win32" && (Number(info.mode) & 0o022) !== 0) continue;
|
|
25
|
+
if (deniedRoots.some((root) => isWithin(canonical, root, platform))) continue;
|
|
26
|
+
return canonical;
|
|
27
|
+
}
|
|
28
|
+
const label = String(options.label || "required executable").replace(/[^A-Za-z0-9._ -]/g, "").trim() || "required executable";
|
|
29
|
+
const reason = String(options.reason || "trusted_executable_unavailable").replace(/[^A-Za-z0-9_-]/g, "_");
|
|
30
|
+
throw new BridgeError("unavailable", `trusted ${label} is unavailable`, { details: { reason, platform } });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createTrustedExecutableResolver(options = {}) {
|
|
34
|
+
const injected = typeof options.resolve === "function" ? options.resolve : null;
|
|
35
|
+
let cached = "";
|
|
36
|
+
return () => {
|
|
37
|
+
if (cached) return cached;
|
|
38
|
+
cached = String(injected ? injected() : resolveTrustedExecutable(options));
|
|
39
|
+
if (!path.isAbsolute(cached)) throw new BridgeError("unavailable", `trusted ${options.label || "executable"} is unavailable`);
|
|
40
|
+
return cached;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function canonicalOrResolved(value, realpath) {
|
|
45
|
+
try { return realpath(path.resolve(value)); } catch { return path.resolve(value); }
|
|
46
|
+
}
|
|
47
|
+
function isWithin(candidate, root, platform) {
|
|
48
|
+
const left = platform === "win32" ? candidate.toLowerCase() : candidate;
|
|
49
|
+
const right = platform === "win32" ? root.toLowerCase() : root;
|
|
50
|
+
const relative = path.relative(right, left);
|
|
51
|
+
return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
|
|
52
|
+
}
|
|
53
|
+
function safeHome() { try { return os.homedir(); } catch { return ""; } }
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { createTrustedExecutableResolver, resolveTrustedExecutable } from "./trusted-executable.mjs";
|
|
3
|
+
|
|
4
|
+
export function resolveTrustedGitExecutable(options = {}) {
|
|
5
|
+
const platform = String(options.platform || process.platform);
|
|
6
|
+
return resolveTrustedExecutable({
|
|
7
|
+
...options,
|
|
8
|
+
platform,
|
|
9
|
+
label: "Git executable",
|
|
10
|
+
reason: "trusted_git_executable_unavailable",
|
|
11
|
+
candidates: Array.isArray(options.candidates) ? options.candidates : defaultGitCandidates(platform, options.env || process.env),
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createTrustedGitResolver(options = {}) {
|
|
16
|
+
const platform = String(options.platform || process.platform);
|
|
17
|
+
return createTrustedExecutableResolver({
|
|
18
|
+
...options,
|
|
19
|
+
platform,
|
|
20
|
+
label: "Git executable",
|
|
21
|
+
reason: "trusted_git_executable_unavailable",
|
|
22
|
+
candidates: Array.isArray(options.candidates) ? options.candidates : defaultGitCandidates(platform, options.env || process.env),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function defaultGitCandidates(platform, env) {
|
|
27
|
+
if (platform === "win32") {
|
|
28
|
+
return [env.ProgramFiles, env["ProgramFiles(x86)"]]
|
|
29
|
+
.filter(Boolean)
|
|
30
|
+
.flatMap((root) => [path.join(root, "Git", "cmd", "git.exe"), path.join(root, "Git", "bin", "git.exe")]);
|
|
31
|
+
}
|
|
32
|
+
if (platform === "darwin") return ["/usr/bin/git", "/opt/homebrew/bin/git", "/usr/local/bin/git"];
|
|
33
|
+
return ["/usr/bin/git", "/bin/git", "/usr/local/bin/git"];
|
|
34
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { resolveTrustedExecutable } from "./trusted-executable.mjs";
|
|
3
|
+
|
|
4
|
+
export function resolveTrustedGithubCli(options = {}) {
|
|
5
|
+
const platform = String(options.platform || process.platform);
|
|
6
|
+
return resolveTrustedExecutable({
|
|
7
|
+
...options,
|
|
8
|
+
platform,
|
|
9
|
+
label: "GitHub CLI executable",
|
|
10
|
+
reason: "trusted_github_cli_unavailable",
|
|
11
|
+
candidates: Array.isArray(options.candidates) ? options.candidates : githubCliCandidates(platform, options.env || process.env),
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function githubCliCandidates(platform, env) {
|
|
16
|
+
if (platform === "win32") {
|
|
17
|
+
return [
|
|
18
|
+
env.ProgramFiles && path.join(env.ProgramFiles, "GitHub CLI", "gh.exe"),
|
|
19
|
+
env.LOCALAPPDATA && path.join(env.LOCALAPPDATA, "Programs", "GitHub CLI", "gh.exe"),
|
|
20
|
+
].filter(Boolean);
|
|
21
|
+
}
|
|
22
|
+
if (platform === "darwin") return ["/opt/homebrew/bin/gh", "/usr/local/bin/gh", "/usr/bin/gh"];
|
|
23
|
+
return ["/usr/bin/gh", "/usr/local/bin/gh", "/snap/bin/gh"];
|
|
24
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { waitForStableActiveStatus } from "./service-convergence.mjs";
|
|
4
|
+
|
|
5
|
+
/** @typedef {{ statusAttempts?: unknown, statusDelayMs?: unknown, stableSamples?: unknown, sleep?: (milliseconds: number) => Promise<void> }} WindowsConvergenceOptions */
|
|
6
|
+
/** @typedef {Record<string, unknown> & { active?: boolean }} WindowsStatus */
|
|
7
|
+
|
|
8
|
+
/** @param {() => WindowsStatus | Promise<WindowsStatus>} readStatus @param {WindowsConvergenceOptions} [options] */
|
|
9
|
+
export async function stableWindowsStatus(readStatus, options = {}) {
|
|
10
|
+
if (typeof readStatus !== "function") throw new TypeError("Windows status reader is required");
|
|
11
|
+
return waitForStableActiveStatus(readStatus, {
|
|
12
|
+
attempts: boundedPositiveInteger(options.statusAttempts, 10),
|
|
13
|
+
delayMs: boundedPositiveInteger(options.statusDelayMs, 100),
|
|
14
|
+
stableSamples: boundedPositiveInteger(options.stableSamples, 5),
|
|
15
|
+
sleep: typeof options.sleep === "function" ? options.sleep : undefined,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** @param {() => WindowsStatus | Promise<WindowsStatus>} readStatus @param {(status: WindowsStatus) => boolean} predicate @param {WindowsConvergenceOptions} [options] */
|
|
20
|
+
export async function waitForWindowsStatus(readStatus, predicate, options = {}) {
|
|
21
|
+
if (typeof readStatus !== "function") throw new TypeError("Windows status reader is required");
|
|
22
|
+
if (typeof predicate !== "function") throw new TypeError("Windows status predicate is required");
|
|
23
|
+
const attempts = boundedPositiveInteger(options.statusAttempts, 10);
|
|
24
|
+
const delayMs = boundedPositiveInteger(options.statusDelayMs, 100);
|
|
25
|
+
const sleep = typeof options.sleep === "function"
|
|
26
|
+
? options.sleep
|
|
27
|
+
: (/** @type {number} */ milliseconds) => new Promise(resolve => { setTimeout(resolve, milliseconds); });
|
|
28
|
+
let status = await readStatus();
|
|
29
|
+
for (let index = 1; index < attempts && !predicate(status); index += 1) {
|
|
30
|
+
await sleep(delayMs);
|
|
31
|
+
status = await readStatus();
|
|
32
|
+
}
|
|
33
|
+
return status;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** @param {WindowsConvergenceOptions} [options] */
|
|
37
|
+
export function windowsStatusWaitOptions(options = {}) {
|
|
38
|
+
return {
|
|
39
|
+
attempts: boundedPositiveInteger(options.statusAttempts, 10),
|
|
40
|
+
delayMs: boundedPositiveInteger(options.statusDelayMs, 100),
|
|
41
|
+
sleep: typeof options.sleep === "function" ? options.sleep : undefined,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** @param {unknown} value @param {number} fallback */
|
|
46
|
+
export function boundedPositiveInteger(value, fallback) {
|
|
47
|
+
const number = Number(value);
|
|
48
|
+
return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback;
|
|
49
|
+
}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { waitForInactiveStatus } from "./service-convergence.mjs";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
} from "./
|
|
5
|
-
export {
|
|
6
|
-
windowsBatchArgument, windowsCommandLineArgument, windowsLauncherContent,
|
|
7
|
-
windowsLauncherPath, windowsTaskAction, writeWindowsLauncher,
|
|
8
|
-
} from "./windows-launcher.mjs";
|
|
2
|
+
import { stableWindowsStatus, waitForWindowsStatus, windowsStatusWaitOptions } from "./windows-service-convergence.mjs";
|
|
3
|
+
import { windowsLauncherPath, windowsTaskAction, writeWindowsLauncher } from "./windows-launcher.mjs";
|
|
4
|
+
import { removeServiceDefinitionIfCurrent, snapshotServiceDefinition } from "./service-definition.mjs";
|
|
5
|
+
export { windowsBatchArgument, windowsCommandLineArgument, windowsLauncherContent, windowsLauncherPath, windowsTaskAction, writeWindowsLauncher } from "./windows-launcher.mjs";
|
|
9
6
|
|
|
10
7
|
export const WINDOWS_TASK = "MachineBridgeMCP";
|
|
11
8
|
const WINDOWS_STATUS_SCRIPT = [
|
|
@@ -28,7 +25,9 @@ export async function installWindowsTask(spec, logger = console, options = {}) {
|
|
|
28
25
|
"/RL", "LIMITED",
|
|
29
26
|
"/F",
|
|
30
27
|
]);
|
|
31
|
-
const status = await waitForWindowsStatus(
|
|
28
|
+
const status = await waitForWindowsStatus(
|
|
29
|
+
() => statusWindowsTask({ ...options, run }), value => value.installed === true, options,
|
|
30
|
+
);
|
|
32
31
|
const ok = create?.code === 0 && status.installed === true;
|
|
33
32
|
if (ok) logger.info?.("Windows Scheduled Task installed for user logon");
|
|
34
33
|
else logger.warn?.("Windows Scheduled Task installation failed", { reason: windowsServiceFailureReason(create, status) });
|
|
@@ -50,24 +49,46 @@ export async function startWindowsTask(logger = console, options = {}) {
|
|
|
50
49
|
const before = await statusWindowsTask({ ...options, run });
|
|
51
50
|
if (before.installed === null) return { ok: false, provider: "schtasks", task: WINDOWS_TASK, reason: "task_status_unavailable", status: before };
|
|
52
51
|
if (before.installed === false) return { ok: false, provider: "schtasks", task: WINDOWS_TASK, reason: "not_installed", status: before };
|
|
52
|
+
if (before.active === true) {
|
|
53
|
+
const existing = await stableWindowsStatus(() => statusWindowsTask({ ...options, run }), options);
|
|
54
|
+
if (existing.stable) {
|
|
55
|
+
logger.info?.("Windows Scheduled Task is already running");
|
|
56
|
+
return {
|
|
57
|
+
ok: true, provider: "schtasks", task: WINDOWS_TASK, installed: true,
|
|
58
|
+
active_before: true, active: true, already_running: true, reason: "already_running", status: existing.status,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
53
62
|
const command = await run("schtasks", ["/Run", "/TN", WINDOWS_TASK]);
|
|
54
63
|
const after = await waitForWindowsStatus(
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
() => statusWindowsTask({ ...options, run }),
|
|
65
|
+
status => status.active === true || completedSince(before, status), options,
|
|
57
66
|
);
|
|
58
|
-
const
|
|
59
|
-
|
|
67
|
+
const stability = after.active === true
|
|
68
|
+
? await stableWindowsStatus(() => statusWindowsTask({ ...options, run }), options)
|
|
69
|
+
: { stable: false, status: after };
|
|
70
|
+
const observed = stability.status;
|
|
71
|
+
const completed = completedSince(before, observed);
|
|
72
|
+
const ok = stability.stable;
|
|
60
73
|
if (ok) logger.info?.("Windows Scheduled Task started");
|
|
61
|
-
else logger.warn?.("Windows Scheduled Task did not
|
|
74
|
+
else logger.warn?.("Windows Scheduled Task did not remain active after the start request");
|
|
62
75
|
return {
|
|
63
76
|
ok,
|
|
64
77
|
provider: "schtasks",
|
|
65
78
|
task: WINDOWS_TASK,
|
|
66
79
|
command,
|
|
67
|
-
status:
|
|
68
|
-
|
|
80
|
+
status: observed,
|
|
81
|
+
active_before: false,
|
|
82
|
+
active: observed?.active === true && stability.stable,
|
|
83
|
+
reason: stability.stable ? "started" : completed ? "completed_without_persistence" : "start_not_stable",
|
|
69
84
|
};
|
|
70
85
|
}
|
|
86
|
+
export async function restartWindowsTask(logger = console, options = {}) {
|
|
87
|
+
const stopped = await stopWindowsTask(logger, options);
|
|
88
|
+
if (!stopped.ok) return { ok: false, provider: "schtasks", task: WINDOWS_TASK, reason: "stop_failed", stop: stopped };
|
|
89
|
+
const started = await startWindowsTask(logger, options);
|
|
90
|
+
return { ...started, restarted: started.ok === true, stop: stopped };
|
|
91
|
+
}
|
|
71
92
|
|
|
72
93
|
export async function stopWindowsTask(logger = console, options = {}) {
|
|
73
94
|
const run = requiredRun(options.run);
|
|
@@ -81,6 +102,7 @@ export async function stopWindowsTask(logger = console, options = {}) {
|
|
|
81
102
|
installed: before.installed,
|
|
82
103
|
active_before: false,
|
|
83
104
|
active: false,
|
|
105
|
+
restore_required: false,
|
|
84
106
|
already_stopped: true,
|
|
85
107
|
reason: before.installed ? "already_stopped" : "not_installed",
|
|
86
108
|
status: before,
|
|
@@ -101,6 +123,7 @@ export async function stopWindowsTask(logger = console, options = {}) {
|
|
|
101
123
|
installed: after?.installed !== false,
|
|
102
124
|
active_before: true,
|
|
103
125
|
active: after?.active !== false,
|
|
126
|
+
restore_required: ok,
|
|
104
127
|
already_stopped: false,
|
|
105
128
|
command,
|
|
106
129
|
status: after,
|
|
@@ -110,17 +133,25 @@ export async function stopWindowsTask(logger = console, options = {}) {
|
|
|
110
133
|
|
|
111
134
|
export async function uninstallWindowsTask(logger = console, options = {}) {
|
|
112
135
|
const run = requiredRun(options.run);
|
|
136
|
+
const launcher = options.stateRoot ? windowsLauncherPath(options.stateRoot) : "";
|
|
137
|
+
const launcherIdentity = launcher ? snapshotServiceDefinition(launcher, "Windows service launcher") : null;
|
|
113
138
|
const stopped = await stopWindowsTask(logger, { ...options, run });
|
|
114
139
|
if (!stopped.ok) return { ok: false, provider: "schtasks", task: WINDOWS_TASK, stop: stopped, reason: "stop_failed" };
|
|
115
140
|
const command = await run("schtasks", ["/Delete", "/TN", WINDOWS_TASK, "/F"]);
|
|
116
141
|
const status = await waitForWindowsStatus(
|
|
117
|
-
value => value.installed === false,
|
|
118
|
-
{ ...options, run },
|
|
142
|
+
() => statusWindowsTask({ ...options, run }), value => value.installed === false, options,
|
|
119
143
|
);
|
|
120
|
-
const
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
|
|
144
|
+
const taskRemoved = status.installed === false;
|
|
145
|
+
if (!taskRemoved) {
|
|
146
|
+
logger.warn?.("Windows Scheduled Task removal could not be verified");
|
|
147
|
+
return { ok: false, provider: "schtasks", task: WINDOWS_TASK, stop: stopped, command, status, reason: "removal_not_observed" };
|
|
148
|
+
}
|
|
149
|
+
if (launcher && !removeServiceDefinitionIfCurrent(launcher, launcherIdentity, "Windows service launcher")) {
|
|
150
|
+
logger.warn?.("Windows service launcher changed during removal; the replacement was retained.");
|
|
151
|
+
return { ok: false, provider: "schtasks", task: WINDOWS_TASK, stop: stopped, command, status, reason: "launcher_changed" };
|
|
152
|
+
}
|
|
153
|
+
logger.info?.("Windows Scheduled Task removed");
|
|
154
|
+
return { ok: true, provider: "schtasks", task: WINDOWS_TASK, stop: stopped, command, status, reason: "removed" };
|
|
124
155
|
}
|
|
125
156
|
|
|
126
157
|
export async function statusWindowsTask(options = {}) {
|
|
@@ -168,32 +199,8 @@ function windowsServiceFailureReason(command, status) {
|
|
|
168
199
|
return "task_installation_unverified";
|
|
169
200
|
}
|
|
170
201
|
|
|
171
|
-
async function waitForWindowsStatus(predicate, options) {
|
|
172
|
-
const attempts = boundedPositiveInteger(options.statusAttempts, 10);
|
|
173
|
-
const delayMs = boundedPositiveInteger(options.statusDelayMs, 100);
|
|
174
|
-
const sleep = typeof options.sleep === "function" ? options.sleep : milliseconds => new Promise(resolve => { setTimeout(resolve, milliseconds); });
|
|
175
|
-
let status = await statusWindowsTask(options);
|
|
176
|
-
for (let index = 1; index < attempts && !predicate(status); index += 1) {
|
|
177
|
-
await sleep(delayMs);
|
|
178
|
-
status = await statusWindowsTask(options);
|
|
179
|
-
}
|
|
180
|
-
return status;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function windowsStatusWaitOptions(options) {
|
|
184
|
-
return {
|
|
185
|
-
attempts: boundedPositiveInteger(options.statusAttempts, 10),
|
|
186
|
-
delayMs: boundedPositiveInteger(options.statusDelayMs, 100),
|
|
187
|
-
sleep: typeof options.sleep === "function" ? options.sleep : undefined,
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
202
|
|
|
191
203
|
function requiredRun(run) {
|
|
192
204
|
if (typeof run !== "function") throw new Error("Windows service command runner is required");
|
|
193
205
|
return run;
|
|
194
206
|
}
|
|
195
|
-
|
|
196
|
-
function boundedPositiveInteger(value, fallback) {
|
|
197
|
-
const number = Number(value);
|
|
198
|
-
return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback;
|
|
199
|
-
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { createHmac } from "node:crypto";
|
|
2
|
+
import { lstatSync, readdirSync, realpathSync } from "node:fs";
|
|
3
|
+
import path, { resolve } from "node:path";
|
|
4
|
+
import { publicDeviceJwkJson } from "./device-identity.mjs";
|
|
5
|
+
import { readBoundedRegularFileSync } from "./secure-file.mjs";
|
|
6
|
+
import { deploymentDeviceIdentity } from "./state.mjs";
|
|
7
|
+
import { packageRoot } from "./package-identity.mjs";
|
|
8
|
+
|
|
9
|
+
const MAX_WORKER_DEPLOY_SOURCE_BYTES = 16 * 1024 * 1024;
|
|
10
|
+
const REQUIRED_DEPLOYMENT_PATHS = Object.freeze([
|
|
11
|
+
"src/worker",
|
|
12
|
+
"src/shared",
|
|
13
|
+
"wrangler.jsonc",
|
|
14
|
+
"tsconfig.json",
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
export function workerDeploymentFingerprint(state, options = {}) {
|
|
18
|
+
const source = workerDeployHashFiles(options.packageRoot || packageRoot);
|
|
19
|
+
const keyMaterial = [
|
|
20
|
+
publicDeviceJwkJson(deploymentDeviceIdentity(state)),
|
|
21
|
+
String(state.worker.oauthTokenVersion || ""),
|
|
22
|
+
].join("\0");
|
|
23
|
+
const fingerprint = createHmac("sha256", keyMaterial);
|
|
24
|
+
addFingerprintField(fingerprint, "mbm-worker-deploy-v5");
|
|
25
|
+
addFingerprintField(fingerprint, String(state.worker.name || ""));
|
|
26
|
+
addFingerprintField(fingerprint, String(source.files.length));
|
|
27
|
+
for (const file of source.files) {
|
|
28
|
+
addFingerprintField(fingerprint, path.relative(source.root, file).replaceAll(path.sep, "/"));
|
|
29
|
+
addFingerprintField(fingerprint, readBoundedRegularFileSync(
|
|
30
|
+
file,
|
|
31
|
+
MAX_WORKER_DEPLOY_SOURCE_BYTES,
|
|
32
|
+
"Worker deployment source",
|
|
33
|
+
{ verifyPathIdentity: true, rejectMultipleLinks: true },
|
|
34
|
+
));
|
|
35
|
+
}
|
|
36
|
+
return fingerprint.digest("hex");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function addFingerprintField(hash, value) {
|
|
40
|
+
const bytes = Buffer.isBuffer(value) ? value : Buffer.from(String(value), "utf8");
|
|
41
|
+
const length = Buffer.alloc(8);
|
|
42
|
+
length.writeBigUInt64BE(BigInt(bytes.length));
|
|
43
|
+
hash.update(length);
|
|
44
|
+
hash.update(bytes);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function workerDeployHashFiles(root) {
|
|
48
|
+
const canonicalRoot = requireRealDeploymentRoot(root);
|
|
49
|
+
const files = [];
|
|
50
|
+
for (const item of REQUIRED_DEPLOYMENT_PATHS) collectRequiredHashPath(canonicalRoot, item, files);
|
|
51
|
+
return Object.freeze({ root: canonicalRoot, files: files.sort() });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function requireRealDeploymentRoot(root) {
|
|
55
|
+
const target = resolve(root);
|
|
56
|
+
let info;
|
|
57
|
+
try { info = lstatSync(target); }
|
|
58
|
+
catch (error) {
|
|
59
|
+
if (error?.code === "ENOENT") throw new Error(`Worker deployment package root is missing: ${target}`);
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
if (info.isSymbolicLink() || !info.isDirectory()) {
|
|
63
|
+
throw new Error(`Worker deployment package root must be a real directory: ${target}`);
|
|
64
|
+
}
|
|
65
|
+
return realpathSync(target);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function collectRequiredHashPath(root, relativePath, out) {
|
|
69
|
+
let current = root;
|
|
70
|
+
const parts = relativePath.split("/");
|
|
71
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
72
|
+
current = resolve(current, parts[index]);
|
|
73
|
+
const info = requiredPathInfo(current);
|
|
74
|
+
if (info.isSymbolicLink()) throw new Error(`Worker deployment source must not be a symbolic link: ${current}`);
|
|
75
|
+
if (index < parts.length - 1 && !info.isDirectory()) {
|
|
76
|
+
throw new Error(`Worker deployment source ancestor must be a real directory: ${current}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
collectHashFiles(current, out);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function requiredPathInfo(target) {
|
|
83
|
+
try { return lstatSync(target); }
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (error?.code === "ENOENT") throw new Error(`Worker deployment required source is missing: ${target}`);
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function collectHashFiles(target, out) {
|
|
91
|
+
const info = requiredPathInfo(target);
|
|
92
|
+
if (info.isSymbolicLink()) throw new Error(`Worker deployment source must not be a symbolic link: ${target}`);
|
|
93
|
+
if (info.isFile()) {
|
|
94
|
+
if (/\.(ts|js|mjs|json|jsonc|yaml|yml|lock)$/.test(target)) out.push(target);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (!info.isDirectory()) throw new Error(`Worker deployment source must be a regular file or directory: ${target}`);
|
|
98
|
+
for (const entry of readdirSync(target, { withFileTypes: true })) {
|
|
99
|
+
if (entry.name === "node_modules" || entry.name === ".wrangler" || entry.name.endsWith(".d.ts")) continue;
|
|
100
|
+
collectHashFiles(resolve(target, entry.name), out);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import path, { resolve } from "node:path";
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
4
3
|
import { runWrangler } from "./shell.mjs";
|
|
5
|
-
import {
|
|
4
|
+
import { saveState } from "./state.mjs";
|
|
5
|
+
import { packageRoot } from "./package-identity.mjs";
|
|
6
6
|
import { withWorkerSecretsFile } from "./worker-secret-file.mjs";
|
|
7
|
-
import {
|
|
7
|
+
import { workerDeploymentFingerprint } from "./worker-deployment-fingerprint.mjs";
|
|
8
|
+
export { workerDeploymentFingerprint } from "./worker-deployment-fingerprint.mjs";
|
|
8
9
|
import {
|
|
9
10
|
normalizeWorkerOrigin,
|
|
10
11
|
retryWorkerHealth,
|
|
@@ -12,11 +13,24 @@ import {
|
|
|
12
13
|
workerHealthUserReason,
|
|
13
14
|
} from "./worker-health.mjs";
|
|
14
15
|
|
|
16
|
+
const DEFAULT_DEPLOYMENT_HEALTH_ATTEMPTS = 20;
|
|
17
|
+
|
|
18
|
+
async function ensureWranglerAuthenticated({ args, runWranglerFn, stateRoot, logger }) {
|
|
19
|
+
const whoami = await runWranglerFn(["whoami"], { capture: true, allowFailure: true, stateRoot });
|
|
20
|
+
if (whoami.code === 0) return;
|
|
21
|
+
if (args.json) throw workerAuthenticationRequiredError();
|
|
22
|
+
logger.info?.("Wrangler is not logged in; opening Cloudflare login");
|
|
23
|
+
await runWranglerFn(["login"], { stateRoot });
|
|
24
|
+
const verified = await runWranglerFn(["whoami"], { capture: true, allowFailure: true, stateRoot });
|
|
25
|
+
if (verified.code !== 0) throw workerAuthenticationRequiredError();
|
|
26
|
+
}
|
|
27
|
+
|
|
15
28
|
export async function ensureWorkerDeployment(state, args = {}, options = {}) {
|
|
16
29
|
const logger = options.logger || console;
|
|
17
30
|
const expectedVersion = options.expectedVersion || currentPackageVersion(options.packageRoot || packageRoot);
|
|
18
31
|
const desiredHash = workerDeploymentFingerprint(state, { packageRoot: options.packageRoot || packageRoot });
|
|
19
32
|
const runWranglerFn = options.runWrangler || runWrangler;
|
|
33
|
+
const wranglerStateRoot = options.stateRoot || state.paths?.stateRoot;
|
|
20
34
|
const saveStateFn = options.saveState || saveState;
|
|
21
35
|
const retryHealthFn = options.retryHealth || retryWorkerHealth;
|
|
22
36
|
const withSecretsFileFn = options.withSecretsFile || withWorkerSecretsFile;
|
|
@@ -24,35 +38,37 @@ export async function ensureWorkerDeployment(state, args = {}, options = {}) {
|
|
|
24
38
|
const complete = hasCompleteWorkerState(state.worker);
|
|
25
39
|
|
|
26
40
|
if (!args.forceWorker && !args.rotateSecrets && complete && state.worker.deployHash === desiredHash) {
|
|
27
|
-
const
|
|
41
|
+
const recordedCurrentDeployment = state.worker.deployedVersion === expectedVersion;
|
|
42
|
+
const attempts = recordedCurrentDeployment
|
|
43
|
+
? (options.recordedDeploymentHealthAttempts ?? DEFAULT_DEPLOYMENT_HEALTH_ATTEMPTS)
|
|
44
|
+
: (options.existingHealthAttempts ?? 2);
|
|
45
|
+
const health = await retryHealthFn(state.worker.url, expectedVersion, attempts, healthOptions);
|
|
28
46
|
if (health.ok) {
|
|
29
|
-
logger.success?.("Worker unchanged and healthy"
|
|
47
|
+
logger.success?.("Worker unchanged and healthy");
|
|
30
48
|
logger.debug?.("Worker health route", { network_route: health.networkRoute || "unknown" });
|
|
31
49
|
return state.worker;
|
|
32
50
|
}
|
|
51
|
+
logger.debug?.("Worker health check detail", { health_error: health.error, network_route: health.networkRoute || "unknown" });
|
|
52
|
+
if (recordedCurrentDeployment) {
|
|
53
|
+
throw workerVerificationError(health.error, { deploymentSucceeded: false, recordedCurrentDeployment: true });
|
|
54
|
+
}
|
|
33
55
|
if (!workerHealthRequiresRedeploy(health.error)) {
|
|
34
|
-
logger.debug?.("Worker health check detail", { health_error: health.error, network_route: health.networkRoute || "unknown" });
|
|
35
56
|
throw workerVerificationError(health.error, { deploymentSucceeded: false });
|
|
36
57
|
}
|
|
37
58
|
logger.warn?.("Recorded Worker is stale; redeploying the same Worker", { reason: workerHealthUserReason(health.error) });
|
|
38
|
-
logger.debug?.("Worker health check detail", { health_error: health.error, network_route: health.networkRoute || "unknown" });
|
|
39
59
|
}
|
|
40
60
|
|
|
41
61
|
logger.info?.("Checking Cloudflare Wrangler login");
|
|
42
|
-
|
|
43
|
-
if (whoami.code !== 0) {
|
|
44
|
-
logger.info?.("Wrangler is not logged in; opening Cloudflare login");
|
|
45
|
-
await runWranglerFn(["login"]);
|
|
46
|
-
}
|
|
62
|
+
await ensureWranglerAuthenticated({ args, runWranglerFn, stateRoot: wranglerStateRoot, logger });
|
|
47
63
|
|
|
48
|
-
logger.info?.("Deploying Cloudflare Worker"
|
|
64
|
+
logger.info?.("Deploying Cloudflare Worker");
|
|
49
65
|
const deploy = await withSecretsFileFn(state, secretFile => runWranglerFn([
|
|
50
66
|
"deploy",
|
|
51
67
|
"--name", state.worker.name,
|
|
52
68
|
"--minify",
|
|
53
69
|
"--keep-vars",
|
|
54
70
|
"--secrets-file", secretFile,
|
|
55
|
-
], { capture: true }));
|
|
71
|
+
], { capture: true, stateRoot: wranglerStateRoot }));
|
|
56
72
|
|
|
57
73
|
const detectedUrl = extractWorkerUrl(deploy.stdout, state.worker.name) || extractWorkerUrl(deploy.stderr, state.worker.name);
|
|
58
74
|
const recordedUrl = workerUrlMatchesName(state.worker.url, state.worker.name) ? state.worker.url : "";
|
|
@@ -68,33 +84,16 @@ export async function ensureWorkerDeployment(state, args = {}, options = {}) {
|
|
|
68
84
|
state.worker.updatedAt = new Date().toISOString();
|
|
69
85
|
saveStateFn(state);
|
|
70
86
|
|
|
71
|
-
const health = await retryHealthFn(state.worker.url, expectedVersion, options.deploymentHealthAttempts
|
|
87
|
+
const health = await retryHealthFn(state.worker.url, expectedVersion, options.deploymentHealthAttempts ?? DEFAULT_DEPLOYMENT_HEALTH_ATTEMPTS, healthOptions);
|
|
72
88
|
if (!health.ok) {
|
|
73
89
|
logger.debug?.("Worker post-deployment health detail", { health_error: health.error, network_route: health.networkRoute || "unknown" });
|
|
74
90
|
throw workerVerificationError(health.error, { deploymentSucceeded: true });
|
|
75
91
|
}
|
|
76
|
-
logger.success?.("Worker ready", {
|
|
92
|
+
logger.success?.("Worker ready", { version: health.version });
|
|
77
93
|
logger.debug?.("Worker health route", { network_route: health.networkRoute || "unknown" });
|
|
78
94
|
return state.worker;
|
|
79
95
|
}
|
|
80
96
|
|
|
81
|
-
export function workerDeploymentFingerprint(state, options = {}) {
|
|
82
|
-
const root = resolve(options.packageRoot || packageRoot);
|
|
83
|
-
const keyMaterial = [
|
|
84
|
-
String(state.worker.accountAdminSecret || ""),
|
|
85
|
-
publicDeviceJwkJson(state.worker.deviceIdentity),
|
|
86
|
-
String(state.worker.oauthTokenVersion || ""),
|
|
87
|
-
].join("\0");
|
|
88
|
-
const fingerprint = createHmac("sha256", keyMaterial);
|
|
89
|
-
fingerprint.update("mbm-worker-deploy-v4");
|
|
90
|
-
fingerprint.update(String(state.worker.name || ""));
|
|
91
|
-
for (const file of workerDeployHashFiles(root)) {
|
|
92
|
-
fingerprint.update(path.relative(root, file));
|
|
93
|
-
fingerprint.update(readFileSync(file, "utf8"));
|
|
94
|
-
}
|
|
95
|
-
return fingerprint.digest("hex");
|
|
96
|
-
}
|
|
97
|
-
|
|
98
97
|
export function extractWorkerUrl(text = "", workerName = "") {
|
|
99
98
|
const candidates = [...String(text).matchAll(/https:\/\/[^\s"'<>]+/g)]
|
|
100
99
|
.map((match) => match[0].replace(/[),.;:!?]+$/, ""));
|
|
@@ -119,37 +118,17 @@ export function workerUrlMatchesName(workerUrl, workerName) {
|
|
|
119
118
|
}
|
|
120
119
|
|
|
121
120
|
function hasCompleteWorkerState(worker = {}) {
|
|
122
|
-
return Boolean(worker.url && worker.mcpServerUrl && worker.
|
|
121
|
+
return Boolean(worker.url && worker.mcpServerUrl && worker.deviceIdentity && worker.oauthTokenVersion && worker.name);
|
|
123
122
|
}
|
|
124
123
|
|
|
125
|
-
function
|
|
126
|
-
const files = [];
|
|
127
|
-
for (const item of ["src/worker", "src/shared", "wrangler.jsonc", "tsconfig.json"]) {
|
|
128
|
-
collectHashFiles(resolve(root, item), files);
|
|
129
|
-
}
|
|
130
|
-
return files.sort();
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function collectHashFiles(target, out) {
|
|
134
|
-
if (!existsSync(target)) return;
|
|
135
|
-
const info = statSync(target);
|
|
136
|
-
if (info.isFile()) {
|
|
137
|
-
if (/\.(ts|js|mjs|json|jsonc|yaml|yml|lock)$/.test(target)) out.push(target);
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
if (!info.isDirectory()) return;
|
|
141
|
-
for (const entry of readdirSync(target, { withFileTypes: true })) {
|
|
142
|
-
if (entry.name === "node_modules" || entry.name === ".wrangler" || entry.name.endsWith(".d.ts")) continue;
|
|
143
|
-
collectHashFiles(resolve(target, entry.name), out);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function workerVerificationError(reason, { deploymentSucceeded }) {
|
|
124
|
+
function workerVerificationError(reason, { deploymentSucceeded, recordedCurrentDeployment = false }) {
|
|
148
125
|
const readable = workerHealthUserReason(reason);
|
|
149
126
|
const guidance = workerVerificationGuidance(reason);
|
|
150
127
|
const message = deploymentSucceeded
|
|
151
128
|
? `Cloudflare reported the Worker deployment succeeded, but ${readable}. The deployment fingerprint was saved, so retrying will verify the same Worker instead of deploying again. ${guidance}`
|
|
152
|
-
:
|
|
129
|
+
: recordedCurrentDeployment
|
|
130
|
+
? `The current Worker deployment fingerprint is already recorded, but ${readable}. No deployment was attempted because duplicating the recorded deployment is unsafe; use --force-worker only after diagnosis. ${guidance}`
|
|
131
|
+
: `The recorded Worker could not be verified because ${readable}. No deployment was attempted. ${guidance}`;
|
|
153
132
|
const error = new Error(message);
|
|
154
133
|
error.code = "worker_health_unverified";
|
|
155
134
|
error.healthError = reason;
|
|
@@ -170,6 +149,15 @@ function workerVerificationGuidance(reason) {
|
|
|
170
149
|
return "Run machine-mcp doctor and inspect the health endpoint before forcing another deployment.";
|
|
171
150
|
}
|
|
172
151
|
|
|
152
|
+
function workerAuthenticationRequiredError() {
|
|
153
|
+
const error = new Error(
|
|
154
|
+
"Cloudflare Wrangler is not authenticated; JSON mode will not start an interactive login. Complete Wrangler authentication in an ordinary owner terminal before retrying.",
|
|
155
|
+
);
|
|
156
|
+
error.code = "worker_authentication_required";
|
|
157
|
+
error.sideEffectsStarted = false;
|
|
158
|
+
return error;
|
|
159
|
+
}
|
|
160
|
+
|
|
173
161
|
function currentPackageVersion(root) {
|
|
174
162
|
const pkg = JSON.parse(readFileSync(resolve(root, "package.json"), "utf8"));
|
|
175
163
|
return String(pkg.version);
|