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/stdio.mjs
CHANGED
|
@@ -1,29 +1,38 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
1
|
import { randomBytes } from "node:crypto";
|
|
3
2
|
import { LocalRuntime } from "./runtime.mjs";
|
|
3
|
+
import { packageVersion } from "./package-identity.mjs";
|
|
4
|
+
import { validateToolArguments } from "./tool-executor.mjs";
|
|
4
5
|
import { classifyOperationalError, createLogger } from "./log.mjs";
|
|
5
6
|
import { publicError } from "./errors.mjs";
|
|
6
7
|
import {
|
|
7
8
|
MCP_INSTRUCTIONS,
|
|
8
|
-
MCP_PROTOCOL_VERSION,
|
|
9
|
-
MCP_SUPPORTED_PROTOCOL_VERSIONS,
|
|
10
9
|
SERVER_NAME,
|
|
11
10
|
rpcError,
|
|
12
11
|
rpcResult,
|
|
13
12
|
toolResult,
|
|
14
13
|
toolsForPolicy,
|
|
15
14
|
} from "./tools.mjs";
|
|
15
|
+
import {
|
|
16
|
+
MCP_PROTOCOL_VERSIONS, MCP_REMOVED_PROTOCOL_MESSAGE, McpProtocolError, cacheableResult,
|
|
17
|
+
discoverResult, emptySubscriptionAcknowledgement, serverImplementation, subscriptionCompleteResult,
|
|
18
|
+
validateRequestMetadata, validateSubscriptionRequest,
|
|
19
|
+
} from "../shared/mcp-protocol.mjs";
|
|
16
20
|
|
|
17
21
|
const MAX_LINE_BYTES = 8 * 1024 * 1024;
|
|
18
|
-
const
|
|
22
|
+
const MAX_PENDING_TOOL_CALLS = 32;
|
|
23
|
+
const MCP_SERVER_INFO = Object.freeze(serverImplementation({
|
|
24
|
+
name: SERVER_NAME, title: "Machine Bridge MCP", version: packageVersion,
|
|
25
|
+
description: "Workspace-scoped local coding tools over MCP stdio or authenticated remote relay.",
|
|
26
|
+
}));
|
|
27
|
+
const MCP_SERVER_CAPABILITIES = Object.freeze({ tools: Object.freeze({ listChanged: false }) });
|
|
28
|
+
const MCP_DISCOVERY_TTL_MS = 300_000;
|
|
29
|
+
const MCP_TOOL_LIST_TTL_MS = 300_000;
|
|
30
|
+
const MCP_LIMIT_EXCEEDED = -31900;
|
|
19
31
|
|
|
20
32
|
export async function runStdioServer({ workspace, policy, logLevel = "info", jobRoot = "", resources = {}, resourceStatePath = "", browserStateRoot = "" }) {
|
|
21
33
|
const logger = createLogger({ component: "stdio", level: logLevel, stderrOnly: true, color: false });
|
|
22
34
|
const runtime = new LocalRuntime({ workspace, policy, logger, jobRoot, resources, resourceStatePath, browserStateRoot });
|
|
23
35
|
const pending = new Map();
|
|
24
|
-
let negotiatedVersion = MCP_PROTOCOL_VERSION;
|
|
25
|
-
let initialized = false;
|
|
26
|
-
|
|
27
36
|
const writes = new Set();
|
|
28
37
|
const send = (message) => {
|
|
29
38
|
if (message === null || message === undefined) return;
|
|
@@ -47,84 +56,124 @@ export async function runStdioServer({ workspace, policy, logLevel = "info", job
|
|
|
47
56
|
});
|
|
48
57
|
for (const callId of pending.values()) runtime.cancelCall(callId, "stdio input closed");
|
|
49
58
|
await Promise.allSettled([...writes]);
|
|
50
|
-
runtime.stop();
|
|
59
|
+
await runtime.stop();
|
|
51
60
|
|
|
52
61
|
async function handleLine(line) {
|
|
53
62
|
const message = parseJsonRpcLine(line, send);
|
|
54
|
-
if (!message
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
send(rpcError(message.id, -32002, "Server is not initialized"));
|
|
63
|
+
if (!message) return;
|
|
64
|
+
if (typeof message.method !== "string") {
|
|
65
|
+
send(rpcError(message.id, -32600, "Clients must not send JSON-RPC responses"));
|
|
58
66
|
return;
|
|
59
67
|
}
|
|
60
|
-
await handleInitializedMessage(message);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async function handleControlMessage(message) {
|
|
64
|
-
if (message.method === "initialize") {
|
|
65
|
-
const requested = asObject(message.params).protocolVersion;
|
|
66
|
-
negotiatedVersion = typeof requested === "string" && MCP_SUPPORTED_PROTOCOL_VERSIONS.includes(requested)
|
|
67
|
-
? requested
|
|
68
|
-
: MCP_PROTOCOL_VERSION;
|
|
69
|
-
initialized = true;
|
|
70
|
-
send(rpcResult(message.id, await initializationResult(negotiatedVersion, runtime)));
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
if (message.method === "notifications/initialized") return true;
|
|
74
68
|
if (message.method === "notifications/cancelled") {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (callId) runtime.cancelCall(callId, "MCP cancellation notification");
|
|
78
|
-
return true;
|
|
69
|
+
cancelRequest(message);
|
|
70
|
+
return;
|
|
79
71
|
}
|
|
80
|
-
if (message.method === "
|
|
81
|
-
send(
|
|
82
|
-
return
|
|
72
|
+
if (message.method === "initialize") {
|
|
73
|
+
send(rpcError(message.id, -32601, MCP_REMOVED_PROTOCOL_MESSAGE, { supported: [...MCP_PROTOCOL_VERSIONS] }));
|
|
74
|
+
return;
|
|
83
75
|
}
|
|
84
|
-
|
|
76
|
+
try {
|
|
77
|
+
validateRequestMetadata(message, MCP_PROTOCOL_VERSIONS);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (error instanceof McpProtocolError) {
|
|
80
|
+
send(rpcError(message.id, error.code, error.message, error.data));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
await handleMessage(message);
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
|
|
88
|
+
function cancelRequest(message) {
|
|
89
|
+
const requestId = asObject(message.params).requestId;
|
|
90
|
+
const callId = pending.get(jsonRpcIdKey(requestId));
|
|
91
|
+
if (callId) runtime.cancelCall(callId, "MCP cancellation notification");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function handleMessage(message) {
|
|
95
|
+
if (!("id" in message) || message.id === null) {
|
|
96
|
+
if (!message.method.startsWith("notifications/")) {
|
|
97
|
+
send(rpcError(null, -32600, "MCP requests require a non-null request id"));
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (message.method === "server/discover") {
|
|
102
|
+
send(rpcResult(message.id, discoverResult({
|
|
103
|
+
supportedVersions: MCP_PROTOCOL_VERSIONS,
|
|
104
|
+
capabilities: MCP_SERVER_CAPABILITIES,
|
|
105
|
+
instructions: MCP_INSTRUCTIONS,
|
|
106
|
+
ttlMs: MCP_DISCOVERY_TTL_MS,
|
|
107
|
+
serverInfo: MCP_SERVER_INFO,
|
|
108
|
+
})));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
88
111
|
if (message.method === "tools/list") {
|
|
89
|
-
send(rpcResult(message.id,
|
|
112
|
+
send(rpcResult(message.id, cacheableResult(
|
|
113
|
+
{ tools: toolsForPolicy(policy) },
|
|
114
|
+
{ ttlMs: MCP_TOOL_LIST_TTL_MS, cacheScope: "private", serverInfo: MCP_SERVER_INFO },
|
|
115
|
+
)));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (message.method === "subscriptions/listen") {
|
|
119
|
+
try {
|
|
120
|
+
validateSubscriptionRequest(message);
|
|
121
|
+
send(emptySubscriptionAcknowledgement(message.id));
|
|
122
|
+
send(rpcResult(message.id, subscriptionCompleteResult(message.id, MCP_SERVER_INFO)));
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (error instanceof McpProtocolError) send(rpcError(message.id, error.code, error.message, error.data));
|
|
125
|
+
else throw error;
|
|
126
|
+
}
|
|
90
127
|
return;
|
|
91
128
|
}
|
|
92
129
|
if (message.method === "tools/call") {
|
|
93
130
|
await handleToolCall(message);
|
|
94
131
|
return;
|
|
95
132
|
}
|
|
96
|
-
send(rpcError(message.id, -32601,
|
|
133
|
+
send(rpcError(message.id, -32601, "Method not found"));
|
|
97
134
|
}
|
|
98
135
|
|
|
99
136
|
async function handleToolCall(message) {
|
|
100
|
-
if (!("id" in message) || message.id === null) {
|
|
101
|
-
send(rpcError(null, -32600, "tools/call requires a non-null request id"));
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
137
|
const params = asObject(message.params);
|
|
105
138
|
const name = typeof params.name === "string" ? params.name : "";
|
|
106
139
|
if (!name) {
|
|
107
140
|
send(rpcError(message.id, -32602, "tools/call requires a tool name"));
|
|
108
141
|
return;
|
|
109
142
|
}
|
|
110
|
-
|
|
111
|
-
|
|
143
|
+
if (!toolsForPolicy(policy).some((tool) => tool.name === name)) {
|
|
144
|
+
send(rpcError(message.id, -32602, "Unknown or unavailable tool"));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const rawArgs = params.arguments === undefined ? {} : params.arguments;
|
|
148
|
+
const validation = validateToolArguments(name, rawArgs);
|
|
149
|
+
if (!validation.known || !validation.valid) {
|
|
150
|
+
send(rpcError(message.id, -32602, !validation.known
|
|
151
|
+
? "Unknown tool"
|
|
152
|
+
: "Tool arguments do not match the input schema", validation.valid ? undefined : {
|
|
153
|
+
validation_issues: validation.issues,
|
|
154
|
+
}));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (pending.size >= MAX_PENDING_TOOL_CALLS) {
|
|
158
|
+
send(rpcError(message.id, MCP_LIMIT_EXCEEDED, "Too many concurrent tool calls"));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
112
161
|
const key = jsonRpcIdKey(message.id);
|
|
113
162
|
if (key && pending.has(key)) {
|
|
114
163
|
send(rpcError(message.id, -32600, "Duplicate in-flight JSON-RPC request id"));
|
|
115
164
|
return;
|
|
116
165
|
}
|
|
166
|
+
const callId = `stdio_${randomBytes(16).toString("hex")}`;
|
|
117
167
|
if (key) pending.set(key, callId);
|
|
118
168
|
try {
|
|
119
|
-
const result = await runtime.executeTool(name,
|
|
120
|
-
send(rpcResult(message.id, toolResult(result)));
|
|
169
|
+
const result = await runtime.executeTool(name, rawArgs, { callId, origin: "stdio" });
|
|
170
|
+
send(rpcResult(message.id, toolResult(result, false, MCP_SERVER_INFO)));
|
|
121
171
|
} catch (error) {
|
|
122
|
-
send(rpcResult(message.id, toolResult({ error: publicError(error) }, true)));
|
|
172
|
+
send(rpcResult(message.id, toolResult({ error: publicError(error) }, true, MCP_SERVER_INFO)));
|
|
123
173
|
} finally {
|
|
124
174
|
if (key) pending.delete(key);
|
|
125
175
|
}
|
|
126
176
|
}
|
|
127
|
-
|
|
128
177
|
}
|
|
129
178
|
|
|
130
179
|
function parseJsonRpcLine(line, send) {
|
|
@@ -140,25 +189,6 @@ function parseJsonRpcLine(line, send) {
|
|
|
140
189
|
return message;
|
|
141
190
|
}
|
|
142
191
|
|
|
143
|
-
async function initializationResult(protocolVersion, runtime) {
|
|
144
|
-
const bootstrap = await runtime.sessionBootstrap({ path: "." }).catch((error) => {
|
|
145
|
-
runtime.logger?.debug?.("session bootstrap unavailable during initialize", { error_class: classifyOperationalError(error) });
|
|
146
|
-
return null;
|
|
147
|
-
});
|
|
148
|
-
const localInstructions = bootstrap?.instructions ? `\n\n--- LOCAL SESSION INSTRUCTIONS ---\n${bootstrap.instructions}` : "";
|
|
149
|
-
return {
|
|
150
|
-
protocolVersion,
|
|
151
|
-
capabilities: { tools: { listChanged: false }, logging: {} },
|
|
152
|
-
serverInfo: {
|
|
153
|
-
name: SERVER_NAME,
|
|
154
|
-
title: "Machine Bridge MCP",
|
|
155
|
-
version: PACKAGE_VERSION,
|
|
156
|
-
description: "Workspace-scoped local coding tools over MCP stdio or authenticated remote relay.",
|
|
157
|
-
},
|
|
158
|
-
instructions: `${MCP_INSTRUCTIONS}${localInstructions}`,
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
192
|
function consumeBoundedJsonLines(stream, { maxLineBytes, onLine, onOversize }) {
|
|
163
193
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
164
194
|
let chunks = [];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/** @typedef {{provider?: unknown}} DeviceIdentitySummaryInput */
|
|
4
|
+
/** @typedef {{url?: unknown, mcpServerUrl?: unknown, name?: unknown, deviceIdentity?: DeviceIdentitySummaryInput, pendingDeviceIdentity?: unknown, previousDeviceIdentities?: unknown[], oauthTokenVersion?: unknown}} WorkerSummaryInput */
|
|
5
|
+
|
|
6
|
+
/** @param {{schemaVersion?: unknown, policy?: Record<string, unknown>, worker?: WorkerSummaryInput, resources?: Record<string, unknown>}} state */
|
|
7
|
+
export function supportStateProjection(state) {
|
|
8
|
+
const worker = state?.worker && typeof state.worker === "object" ? state.worker : {};
|
|
9
|
+
const currentIdentity = worker.deviceIdentity && typeof worker.deviceIdentity === "object" ? worker.deviceIdentity : null;
|
|
10
|
+
const previous = Array.isArray(worker.previousDeviceIdentities) ? worker.previousDeviceIdentities : [];
|
|
11
|
+
const resources = state?.resources && typeof state.resources === "object" && !Array.isArray(state.resources) ? state.resources : {};
|
|
12
|
+
const policy = state?.policy && typeof state.policy === "object" ? state.policy : {};
|
|
13
|
+
return {
|
|
14
|
+
schema_version: Number.isSafeInteger(state?.schemaVersion) ? state.schemaVersion : null,
|
|
15
|
+
policy: {
|
|
16
|
+
profile: typeof policy.profile === "string" ? policy.profile : null,
|
|
17
|
+
origin: typeof policy.origin === "string" ? policy.origin : null,
|
|
18
|
+
revision: Number.isSafeInteger(policy.revision) ? policy.revision : null,
|
|
19
|
+
},
|
|
20
|
+
worker: {
|
|
21
|
+
configured: Boolean(worker.url || worker.mcpServerUrl || worker.name || currentIdentity),
|
|
22
|
+
endpoint_configured: Boolean(worker.url || worker.mcpServerUrl),
|
|
23
|
+
oauth_token_version_configured: Boolean(worker.oauthTokenVersion),
|
|
24
|
+
device_root_provider: currentIdentity ? String(currentIdentity.provider || "portable-jwk-v1") : null,
|
|
25
|
+
pending_device_root: Boolean(worker.pendingDeviceIdentity),
|
|
26
|
+
retired_device_root_count: previous.length,
|
|
27
|
+
},
|
|
28
|
+
resource_count: Object.keys(resources).length,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
const TUNNEL_INTERFACE = /^(?:utun|tun|tap|ppp|ipsec|wg|gif|stf)/i;
|
|
4
|
+
const LOOPBACK_INTERFACE = /^lo\d*$/i;
|
|
5
|
+
const PHYSICAL_INTERFACE = /^(?:en|eth|wlan|wl|wifi|wi-fi)\d*$/i;
|
|
6
|
+
|
|
7
|
+
/** @typedef {{code?: number, stdout?: string}} FixedProcessResult */
|
|
8
|
+
/** @typedef {(command: string, args: string[], timeoutMs: number, capture: boolean, maxOutputBytes: number, context: unknown) => Promise<FixedProcessResult>} FixedInternalRunner */
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {{
|
|
11
|
+
* platform?: string,
|
|
12
|
+
* runFixedInternal?: FixedInternalRunner,
|
|
13
|
+
* classifyError?: (error: unknown) => string,
|
|
14
|
+
* context?: unknown,
|
|
15
|
+
* }} SystemNetworkRouteOptions
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** @param {SystemNetworkRouteOptions} [options] */
|
|
19
|
+
export async function systemNetworkRouteCheck({
|
|
20
|
+
platform = process.platform,
|
|
21
|
+
runFixedInternal,
|
|
22
|
+
classifyError = () => "unavailable",
|
|
23
|
+
context = {},
|
|
24
|
+
} = {}) {
|
|
25
|
+
try {
|
|
26
|
+
const route = await inspectSystemNetworkRoute({ platform, runFixedInternal, context });
|
|
27
|
+
return route.supported && route.available ? {
|
|
28
|
+
layer: "system-network-route",
|
|
29
|
+
ok: true,
|
|
30
|
+
route_class: route.route_class,
|
|
31
|
+
operating_system_interception: route.operating_system_interception === true,
|
|
32
|
+
} : {
|
|
33
|
+
layer: "system-network-route",
|
|
34
|
+
ok: false,
|
|
35
|
+
skipped: true,
|
|
36
|
+
error_class: route.supported ? "unavailable" : "unsupported_platform",
|
|
37
|
+
};
|
|
38
|
+
} catch (error) {
|
|
39
|
+
return {
|
|
40
|
+
layer: "system-network-route",
|
|
41
|
+
ok: false,
|
|
42
|
+
skipped: true,
|
|
43
|
+
error_class: classifyError(error),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** @param {SystemNetworkRouteOptions} [options] */
|
|
49
|
+
export async function inspectSystemNetworkRoute({
|
|
50
|
+
platform = process.platform,
|
|
51
|
+
runFixedInternal,
|
|
52
|
+
context = {},
|
|
53
|
+
} = {}) {
|
|
54
|
+
if (platform !== "darwin" || typeof runFixedInternal !== "function") return { supported: false };
|
|
55
|
+
const result = await runFixedInternal("/sbin/route", ["-n", "get", "default"], 5_000, true, 16 * 1024, context);
|
|
56
|
+
if (result?.code !== 0) return { supported: true, available: false };
|
|
57
|
+
const match = String(result.stdout || "").match(/^\s*interface:\s*(\S+)\s*$/mi);
|
|
58
|
+
if (!match) return { supported: true, available: false };
|
|
59
|
+
const routeClass = classifySystemRouteInterface(match[1]);
|
|
60
|
+
return {
|
|
61
|
+
supported: true,
|
|
62
|
+
available: true,
|
|
63
|
+
route_class: routeClass,
|
|
64
|
+
operating_system_interception: routeClass === "tunnel-or-vpn",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** @param {unknown} value */
|
|
69
|
+
export function classifySystemRouteInterface(value) {
|
|
70
|
+
const name = String(value || "").trim();
|
|
71
|
+
if (!name) return "unknown";
|
|
72
|
+
if (TUNNEL_INTERFACE.test(name)) return "tunnel-or-vpn";
|
|
73
|
+
if (LOOPBACK_INTERFACE.test(name)) return "loopback";
|
|
74
|
+
if (PHYSICAL_INTERFACE.test(name)) return "physical-or-other";
|
|
75
|
+
return "other";
|
|
76
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function systemdRemovalDecision({ definitionPresent, disableCode, status }) {
|
|
2
|
+
const state = String(status?.state || "unknown");
|
|
3
|
+
const active = status?.active;
|
|
4
|
+
if (active === true || ["activating", "deactivating", "reloading", "maintenance"].includes(state)) {
|
|
5
|
+
return { removable: false, alreadyAbsent: false, reason: "service_active_or_transitioning" };
|
|
6
|
+
}
|
|
7
|
+
const safelyInactive = active === false && ["inactive", "failed"].includes(state);
|
|
8
|
+
const safelyAbsent = definitionPresent !== true && active === false && state === "unknown";
|
|
9
|
+
if (!safelyInactive && !safelyAbsent) {
|
|
10
|
+
return { removable: false, alreadyAbsent: false, reason: "status_unavailable" };
|
|
11
|
+
}
|
|
12
|
+
if (definitionPresent === true && Number(disableCode) !== 0) {
|
|
13
|
+
return { removable: false, alreadyAbsent: false, reason: "disable_failed" };
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
removable: true,
|
|
17
|
+
alreadyAbsent: definitionPresent !== true,
|
|
18
|
+
reason: definitionPresent === true ? "disabled" : "already_absent",
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import { performance } from "node:perf_hooks";
|
|
2
|
-
import
|
|
2
|
+
import catalog from "../shared/tool-catalog.json" with { type: "json" };
|
|
3
|
+
import { compileToolArgumentValidators } from "../shared/tool-argument-validation.mjs";
|
|
4
|
+
import { isRemoteDurableProcessTool, remoteDurableProcessTimeoutSeconds } from "../shared/foreground-timeout.mjs";
|
|
5
|
+
import { BridgeError, errorCode, normalizeBridgeError } from "./errors.mjs";
|
|
6
|
+
import { resourceAdmissionLogFields } from "./resource-admission-diagnostics.mjs";
|
|
7
|
+
import { normalizeToolResult } from "./tool-result-boundary.mjs";
|
|
8
|
+
import { createSecurityAuditFailureReporter } from "./security-audit-warning.mjs";
|
|
9
|
+
import { enqueueSecurityAudit } from "./security-audit-dispatch.mjs";
|
|
10
|
+
|
|
11
|
+
const TOOL_ARGUMENTS = compileToolArgumentValidators(catalog);
|
|
12
|
+
|
|
13
|
+
export function validateToolArguments(tool, value) {
|
|
14
|
+
return TOOL_ARGUMENTS.validate(tool, value);
|
|
15
|
+
}
|
|
3
16
|
|
|
4
17
|
export class ToolExecutor {
|
|
5
18
|
constructor(options = {}) {
|
|
@@ -9,18 +22,22 @@ export class ToolExecutor {
|
|
|
9
22
|
this.accountAccessGate = options.accountAccessGate;
|
|
10
23
|
this.operationAuthorizer = options.operationAuthorizer;
|
|
11
24
|
this.observability = options.observability;
|
|
25
|
+
this.securityAudit = options.securityAudit || null;
|
|
12
26
|
this.logger = options.logger || console;
|
|
27
|
+
this.onAuthorizedRelayActivityStart = typeof options.onAuthorizedRelayActivityStart === "function" ? options.onAuthorizedRelayActivityStart : null;
|
|
28
|
+
this.onAuthorizedRelayActivityEnd = typeof options.onAuthorizedRelayActivityEnd === "function" ? options.onAuthorizedRelayActivityEnd : null;
|
|
13
29
|
this.safeMessage = typeof options.safeMessage === "function" ? options.safeMessage : (error) => String(error?.message || error || "operation failed");
|
|
14
30
|
this.slowMs = Number.isFinite(Number(options.slowMs)) ? Math.max(1, Number(options.slowMs)) : 30_000;
|
|
15
31
|
this.pipeline = composeMiddleware([
|
|
16
32
|
lifecycleMiddleware(this.callRegistry),
|
|
17
|
-
observabilityMiddleware(this.observability, this.logger, this.safeMessage, this.slowMs),
|
|
18
|
-
authorizeMiddleware(this.policyGate, this.accountAccessGate, this.operationAuthorizer),
|
|
19
|
-
|
|
33
|
+
observabilityMiddleware(this.observability, this.securityAudit, this.logger, this.safeMessage, this.slowMs),
|
|
34
|
+
authorizeMiddleware(this.policyGate, this.accountAccessGate, this.operationAuthorizer, this.callRegistry),
|
|
35
|
+
validateArgumentsMiddleware(),
|
|
36
|
+
], invokeHandler(this.handlers, this.onAuthorizedRelayActivityStart, this.onAuthorizedRelayActivityEnd));
|
|
20
37
|
}
|
|
21
38
|
|
|
22
39
|
execute(tool, args = {}, request = {}) {
|
|
23
|
-
return this.pipeline({ tool: String(tool || ""), args
|
|
40
|
+
return this.pipeline({ tool: String(tool || ""), args, request });
|
|
24
41
|
}
|
|
25
42
|
}
|
|
26
43
|
|
|
@@ -28,19 +45,52 @@ export function composeMiddleware(middleware, terminal) {
|
|
|
28
45
|
return middleware.reduceRight((next, current) => (operation) => current(operation, next), terminal);
|
|
29
46
|
}
|
|
30
47
|
|
|
31
|
-
function authorizeMiddleware(policyGate, accountAccessGate, operationAuthorizer) {
|
|
48
|
+
function authorizeMiddleware(policyGate, accountAccessGate, operationAuthorizer, callRegistry) {
|
|
32
49
|
return async (operation, next) => {
|
|
33
50
|
policyGate.assert(operation.tool);
|
|
34
51
|
if (operation.context.origin === "relay") {
|
|
35
|
-
const
|
|
52
|
+
const authorization = operation.request.authorization;
|
|
53
|
+
const role = authorization?.role;
|
|
36
54
|
if (!role) throw new Error("relay tool call is missing an account role");
|
|
37
55
|
accountAccessGate.assert(role, operation.tool);
|
|
38
|
-
|
|
56
|
+
operation.context.authority = accountAccessGate.authority(authorization, policyGate.policy, "relay");
|
|
57
|
+
callRegistry.bindPrincipal(operation.context.callId, operation.context.authority.principal);
|
|
58
|
+
const decision = await operationAuthorizer?.authorize(operation);
|
|
59
|
+
if (decision) operation.context.operationAuthorization = decision;
|
|
60
|
+
} else {
|
|
61
|
+
operation.context.authority = accountAccessGate.authority({}, policyGate.policy, "local");
|
|
62
|
+
callRegistry.bindPrincipal(operation.context.callId, operation.context.authority.principal);
|
|
63
|
+
}
|
|
64
|
+
return next(operation);
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function validateArgumentsMiddleware() {
|
|
69
|
+
return async (operation, next) => {
|
|
70
|
+
const result = TOOL_ARGUMENTS.validate(operation.tool, operation.args);
|
|
71
|
+
if (!result.known) throw new BridgeError("not_found", `unknown tool: ${operation.tool}`);
|
|
72
|
+
if (!result.valid && !validRelayDurableProcessSchemaExtension(operation, result.issues)) {
|
|
73
|
+
throw new BridgeError("invalid_request", `tool arguments do not match the input schema: ${operation.tool}`, {
|
|
74
|
+
details: { tool: operation.tool, validation_issues: result.issues },
|
|
75
|
+
});
|
|
39
76
|
}
|
|
40
77
|
return next(operation);
|
|
41
78
|
};
|
|
42
79
|
}
|
|
43
80
|
|
|
81
|
+
function validRelayDurableProcessSchemaExtension(operation, issues) {
|
|
82
|
+
if (operation.context.origin !== "relay" || !isRemoteDurableProcessTool(operation.tool) || !Array.isArray(issues) || !issues.length) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
if (issues.some((issue) => issue?.instancePath !== "/timeout_seconds" || issue?.keyword !== "maximum")) return false;
|
|
86
|
+
try {
|
|
87
|
+
remoteDurableProcessTimeoutSeconds(operation.args?.timeout_seconds);
|
|
88
|
+
return true;
|
|
89
|
+
} catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
44
94
|
function lifecycleMiddleware(callRegistry) {
|
|
45
95
|
return async (operation, next) => {
|
|
46
96
|
const context = callRegistry.open({
|
|
@@ -52,16 +102,18 @@ function lifecycleMiddleware(callRegistry) {
|
|
|
52
102
|
operation.context = { ...operation.request.context, ...context };
|
|
53
103
|
try {
|
|
54
104
|
callRegistry.throwIfCancelled(operation.context);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
105
|
+
// Handler return is the local settlement point. Cooperative handlers observe cancellation
|
|
106
|
+
// before or during cancellable work; a later signal must not retroactively relabel a
|
|
107
|
+
// completed side effect as cancelled after observability/audit already recorded completion.
|
|
108
|
+
return await next(operation);
|
|
58
109
|
} finally {
|
|
59
110
|
callRegistry.finish(context.callId);
|
|
60
111
|
}
|
|
61
112
|
};
|
|
62
113
|
}
|
|
63
114
|
|
|
64
|
-
function observabilityMiddleware(observability, logger, safeMessage, slowMs) {
|
|
115
|
+
function observabilityMiddleware(observability, securityAudit, logger, safeMessage, slowMs) {
|
|
116
|
+
const auditFailureReporter = createSecurityAuditFailureReporter(logger);
|
|
65
117
|
return async (operation, next) => {
|
|
66
118
|
const started = performance.now();
|
|
67
119
|
observability.start(operation.tool);
|
|
@@ -71,41 +123,54 @@ function observabilityMiddleware(observability, logger, safeMessage, slowMs) {
|
|
|
71
123
|
origin: operation.context.origin,
|
|
72
124
|
}, "Tool call started");
|
|
73
125
|
try {
|
|
74
|
-
const
|
|
126
|
+
const normalizedResult = normalizeToolResult(await next(operation));
|
|
127
|
+
const result = normalizedResult.value;
|
|
75
128
|
const durationMs = performance.now() - started;
|
|
76
129
|
const slow = durationMs >= slowMs;
|
|
77
130
|
observability.finish(operation.tool, { status: "completed", durationMs, slow });
|
|
131
|
+
enqueueSecurityAudit(securityAudit, operation, { outcome: "completed", durationMs, outputBytes: normalizedResult.bytes }, auditFailureReporter);
|
|
78
132
|
logger.event?.("debug", slow ? "tool.call.slow" : "tool.call.completed", {
|
|
79
133
|
call_id: shortCallId(operation.context.callId), tool: operation.tool, origin: operation.context.origin, duration_ms: durationMs,
|
|
134
|
+
authority_role: operation.context.authority?.principal?.role || "local",
|
|
135
|
+
risk_category: operation.context.operationAuthorization?.category || "ordinary",
|
|
80
136
|
}, slow ? "Tool call completed slowly" : "Tool call completed");
|
|
81
137
|
return result;
|
|
82
138
|
} catch (error) {
|
|
83
|
-
const normalized = normalizeBridgeError(error, { safeMessage: () => safeMessage(error, operation.args) });
|
|
139
|
+
const normalized = normalizeBridgeError(error, { safeMessage: () => safeMessage(error, operation.args, operation.context) });
|
|
84
140
|
const durationMs = performance.now() - started;
|
|
85
141
|
const code = errorCode(normalized);
|
|
86
142
|
const status = code === "cancelled" ? "cancelled" : code === "timeout" ? "timeout" : "failed";
|
|
87
143
|
observability.finish(operation.tool, { status, durationMs, errorCode: code, slow: durationMs >= slowMs });
|
|
144
|
+
enqueueSecurityAudit(securityAudit, operation, { outcome: status, durationMs, errorCode: code }, auditFailureReporter);
|
|
145
|
+
const resourceAdmission = resourceAdmissionLogFields(normalized);
|
|
88
146
|
logger.event?.("debug", "tool.call.failed", {
|
|
89
147
|
call_id: shortCallId(operation.context.callId), tool: operation.tool, origin: operation.context.origin,
|
|
90
148
|
duration_ms: durationMs, error_code: code, retryable: normalized.retryable,
|
|
149
|
+
authority_role: operation.context.authority?.principal?.role || "local",
|
|
150
|
+
risk_category: operation.context.operationAuthorization?.category || "ordinary",
|
|
151
|
+
...resourceAdmission,
|
|
91
152
|
}, "Tool call failed");
|
|
92
153
|
throw normalized;
|
|
93
154
|
}
|
|
94
155
|
};
|
|
95
156
|
}
|
|
96
157
|
|
|
97
|
-
function invokeHandler(handlers) {
|
|
158
|
+
function invokeHandler(handlers, onAuthorizedRelayActivityStart, onAuthorizedRelayActivityEnd) {
|
|
98
159
|
return async (operation) => {
|
|
99
160
|
const handler = handlers[operation.tool];
|
|
100
161
|
if (typeof handler !== "function") throw new Error(`runtime handler is missing for tool: ${operation.tool}`);
|
|
101
|
-
|
|
162
|
+
const relayActivity = operation.context.origin === "relay";
|
|
163
|
+
if (relayActivity) bestEffortActivityHook(onAuthorizedRelayActivityStart);
|
|
164
|
+
try { return await handler(operation.args, operation.context); }
|
|
165
|
+
finally { if (relayActivity) bestEffortActivityHook(onAuthorizedRelayActivityEnd); }
|
|
102
166
|
};
|
|
103
167
|
}
|
|
104
168
|
|
|
105
|
-
function
|
|
106
|
-
|
|
169
|
+
function bestEffortActivityHook(callback) {
|
|
170
|
+
try { callback?.(); } catch { /* Auxiliary power-management hooks must not change tool settlement. */ }
|
|
107
171
|
}
|
|
108
172
|
|
|
109
|
-
|
|
110
|
-
|
|
173
|
+
|
|
174
|
+
function shortCallId(value) {
|
|
175
|
+
return String(value || "").slice(0, 20);
|
|
111
176
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { BridgeError } from "./errors.mjs";
|
|
3
|
+
|
|
4
|
+
export const MAX_TOOL_RESULT_BYTES = 7 * 1024 * 1024;
|
|
5
|
+
|
|
6
|
+
export function normalizeToolResult(value, options = {}) {
|
|
7
|
+
const maximumBytes = positiveInteger(options.maximumBytes, MAX_TOOL_RESULT_BYTES);
|
|
8
|
+
let serialized;
|
|
9
|
+
try {
|
|
10
|
+
serialized = JSON.stringify(value, rejectUnsupportedJsonValue);
|
|
11
|
+
} catch (error) {
|
|
12
|
+
if (error instanceof BridgeError) throw error;
|
|
13
|
+
throw new BridgeError("internal_error", "tool returned a result that could not be serialized", {
|
|
14
|
+
cause: error instanceof Error ? error : undefined,
|
|
15
|
+
expose: false,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
if (typeof serialized !== "string") {
|
|
19
|
+
throw new BridgeError("internal_error", "tool returned an unsupported result", { expose: false });
|
|
20
|
+
}
|
|
21
|
+
const bytes = Buffer.byteLength(serialized);
|
|
22
|
+
if (bytes > maximumBytes) {
|
|
23
|
+
throw new BridgeError("limit_exceeded", "tool result exceeded the relay response limit", {
|
|
24
|
+
retryable: false,
|
|
25
|
+
details: { maximum_bytes: maximumBytes, actual_bytes: bytes },
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
return Object.freeze({ value: JSON.parse(serialized), bytes });
|
|
30
|
+
} catch (error) {
|
|
31
|
+
throw new BridgeError("internal_error", "tool result normalization failed", {
|
|
32
|
+
cause: error instanceof Error ? error : undefined,
|
|
33
|
+
expose: false,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function rejectUnsupportedJsonValue(_key, current) {
|
|
39
|
+
if (typeof current === "bigint" || typeof current === "function" || typeof current === "symbol" || typeof current === "undefined") {
|
|
40
|
+
throw new BridgeError("internal_error", "tool returned a non-JSON value", { expose: false });
|
|
41
|
+
}
|
|
42
|
+
if (typeof current === "number" && !Number.isFinite(current)) {
|
|
43
|
+
throw new BridgeError("internal_error", "tool returned a non-finite number", { expose: false });
|
|
44
|
+
}
|
|
45
|
+
return current;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function positiveInteger(value, fallback) {
|
|
49
|
+
const parsed = Number(value);
|
|
50
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
|
|
51
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { lstatSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { filesystemIdentity, sameFilesystemIdentity } from "./filesystem-identity.mjs";
|
|
5
|
+
import { withOwnerStateLock } from "./owner-state-lock.mjs";
|
|
6
|
+
import { assertStateMaintenanceAvailable, expandHome, machineServiceControlRoot } from "./state.mjs";
|
|
7
|
+
|
|
8
|
+
const LOCK_PURPOSE = "toolchain-operation";
|
|
9
|
+
|
|
10
|
+
export function withToolchainOperationLock(stateRoot, callback, options = {}) {
|
|
11
|
+
if (typeof callback !== "function") throw new TypeError("toolchain operation lock requires a callback");
|
|
12
|
+
const root = resolve(expandHome(stateRoot));
|
|
13
|
+
assertStateMaintenanceAvailable(root);
|
|
14
|
+
const generation = stateGeneration(root);
|
|
15
|
+
const key = process.platform === "win32" ? root.toLowerCase() : root;
|
|
16
|
+
const digest = createHash("sha256").update(key).digest("hex").slice(0, 16);
|
|
17
|
+
return withOwnerStateLock(machineServiceControlRoot(options), async () => {
|
|
18
|
+
assertStateMaintenanceAvailable(root);
|
|
19
|
+
const current = stateGeneration(root);
|
|
20
|
+
if (!sameFilesystemIdentity(generation, current)) {
|
|
21
|
+
throw new Error("state root changed while waiting for private-toolchain ownership; retry from current state");
|
|
22
|
+
}
|
|
23
|
+
return callback();
|
|
24
|
+
}, {
|
|
25
|
+
purpose: LOCK_PURPOSE,
|
|
26
|
+
fileName: `toolchain-operation-${digest}.lock`,
|
|
27
|
+
label: "private toolchain operation",
|
|
28
|
+
timeoutMs: options.timeoutMs ?? 30_000,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function stateGeneration(root) {
|
|
33
|
+
let info;
|
|
34
|
+
try { info = lstatSync(root, { bigint: true }); }
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (error?.code === "ENOENT") throw new Error("state root must exist before a persistent private-toolchain operation", { cause: error });
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
if (info.isSymbolicLink() || !info.isDirectory()) throw new Error("state root must be a real directory before a persistent private-toolchain operation");
|
|
40
|
+
return filesystemIdentity(info, "private-toolchain state root");
|
|
41
|
+
}
|