machine-bridge-mcp 3.0.0-beta.155 → 3.0.0-beta.156
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 +6 -0
- package/README.md +2 -0
- package/SECURITY.md +6 -0
- package/browser-extension/manifest.json +1 -1
- package/docs/ARCHITECTURE.md +2 -2
- package/docs/AUDIT.md +10 -0
- package/docs/OPERATIONS.md +6 -2
- package/package.json +1 -1
- package/scripts/coverage-check.mjs +1 -0
- package/src/local/daemon-http-relay-request.mjs +2 -2
- package/src/local/network-proxy.mjs +24 -2
- package/src/local/relay-connection-classification.mjs +1 -1
- package/src/local/service-environment.mjs +1 -0
- package/src/worker/index.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.0.0-beta.156 - 2026-08-31
|
|
4
|
+
|
|
5
|
+
- Add a relay-only `MBM_RELAY_PROXY` application egress override for deployments where relay continuity must not follow ordinary `NO_PROXY` or shared environment-proxy selection. A non-empty value fixes both the preferred WebSocket and signed HTTP fallback to the same HTTP(S) CONNECT proxy; proxy failure keeps normal relay recovery but never authorizes a direct retry. Empty/unset values preserve the existing `HTTPS_PROXY`/`HTTP_PROXY`/`NO_PROXY` behavior, and Worker deployment health remains on that standard model.
|
|
6
|
+
- Persist `MBM_RELAY_PROXY` in the existing owner-only service network-environment snapshot so launchd/systemd/Windows logon startup does not lose a session-only dedicated relay route. Diagnostics continue to expose only the coarse `application-http-proxy` route and never reveal the dedicated endpoint or credentials. Documentation makes the trust boundary explicit: Machine Bridge does not bind the proxy's upstream socket or bypass an operating-system VPN/TUN by itself; true route isolation uses a loopback-only sidecar whose upstream socket and DNS path are independently routed outside the unstable tunnel.
|
|
7
|
+
- Add regressions for dedicated WSS routing despite matching `NO_PROXY`, real signed HTTP-fallback traversal through the same CONNECT proxy, invalid dedicated proxy schemes, and service-environment persistence. This packaged reliability fix blocks beta.155 soak promotion, advances package/runtime identity to beta.156, and leaves hosted tool schema generation at 21 because no MCP tool or owner-visible diagnostic contract changes.
|
|
8
|
+
|
|
3
9
|
## 3.0.0-beta.155 - 2026-08-30
|
|
4
10
|
|
|
5
11
|
- Close the hosted managed-job cross-conversation discovery boundary found during an independent beta.154 review. Hosted acceptance now returns deterministic HMAC-SHA256 `recovery_key` and `control_key` capabilities bound to account version, OAuth client, refresh family, role, job ID, and purpose. Hosted `read_job` requires the read capability, `cancel_job` requires the control capability, and every `depends_on` reference requires an exact `dependency_recovery` mapping. The Worker verifies these capabilities before daemon dispatch and strips them from daemon arguments; they are not persisted in managed-job state or mirrored into MCP parameter headers. Hosted `list_jobs` is now aggregate-only and omits job IDs, names, and `recent_process_recovery` handles, while local CLI/stdio retains global owner administration. This prevents Machine Bridge's own shared owner inventory from becoming cross-conversation recovery authority; it cannot distinguish conversations if a host itself forwards another conversation's valid capability.
|
package/README.md
CHANGED
|
@@ -129,6 +129,8 @@ https://<worker>.<account>.workers.dev/mcp
|
|
|
129
129
|
|
|
130
130
|
Remote readiness is end-to-end. A daemon becomes available only after a Worker probe traverses the same authenticated local dispatch and result-delivery path used by real tool calls. A replacement daemon is verified before it displaces a healthy incumbent.
|
|
131
131
|
|
|
132
|
+
When the relay must use a proxy but should not inherit an operating-system VPN/TUN path, set `MBM_RELAY_PROXY` to a dedicated HTTP(S) proxy endpoint. It takes precedence over `HTTPS_PROXY`/`HTTP_PROXY` and `NO_PROXY` for both the preferred WebSocket relay and signed HTTP fallback; a configured proxy failure never falls back to a direct relay connection. A common deployment is a loopback-only sidecar whose own upstream socket is pinned to the intended physical/network interface. Machine Bridge does not itself bind the sidecar's upstream socket, so pointing `MBM_RELAY_PROXY` at a remote proxy does not by itself bypass an operating-system tunnel. See [docs/OPERATIONS.md](docs/OPERATIONS.md).
|
|
133
|
+
|
|
132
134
|
For account roles, OAuth lifecycle, supported callback behavior, and tenancy limits, read [docs/GETTING_STARTED.md](docs/GETTING_STARTED.md) and [docs/MULTI_ACCOUNT.md](docs/MULTI_ACCOUNT.md).
|
|
133
135
|
|
|
134
136
|
## Local stdio quick start
|
package/SECURITY.md
CHANGED
|
@@ -190,6 +190,12 @@ Computer Use screenshots share the ordinary MCP result-size boundary. When an im
|
|
|
190
190
|
|
|
191
191
|
Local resources may be injected without returning their bytes through MCP, but the destination page or application still receives them. Screenshots and page source can themselves contain secrets. In particular, raw serialized HTML may contain hidden bootstrap/session/account/authentication values that are not visible in the rendered page; `browser_get_source` therefore should be used only when raw markup is required, with semantic inspection preferred for routine browser work.
|
|
192
192
|
|
|
193
|
+
## Network egress boundaries
|
|
194
|
+
|
|
195
|
+
Machine Bridge can select an application-layer HTTP(S) proxy for relay traffic. A non-empty `MBM_RELAY_PROXY` takes precedence over standard proxy/`NO_PROXY` resolution for both the WebSocket relay and signed HTTP fallback, and a failed proxy connection is not retried directly. Proxy URLs and credentials are not returned through runtime diagnostics or operational logs.
|
|
196
|
+
|
|
197
|
+
This is not an operating-system network-isolation boundary. Machine Bridge does not bind the proxy's upstream socket to a physical interface, implement an independent routing table, or prevent a system VPN/TUN from intercepting traffic to a remotely addressed proxy. When relay traffic must be isolated from such a tunnel, use a loopback-only sidecar whose own outbound socket and DNS path are explicitly routed outside that tunnel. The sidecar and upstream proxy remain separate trusted network components.
|
|
198
|
+
|
|
193
199
|
## Filesystem and mutation integrity
|
|
194
200
|
|
|
195
201
|
Workspace-confined profiles canonicalize existing paths and write ancestors. Final symbolic-link writes are rejected. Patch add, update, delete, and move destinations are classified before mutation.
|
|
@@ -30,6 +30,6 @@
|
|
|
30
30
|
"action": {
|
|
31
31
|
"default_title": "Machine Bridge Browser"
|
|
32
32
|
},
|
|
33
|
-
"version_name": "3.0.0-beta.
|
|
33
|
+
"version_name": "3.0.0-beta.156",
|
|
34
34
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxryYkpZhq8+VAQLHcGS9BAHQcyKX8RHGIpIwvtIVRU/rcOcE0bNdnM0aZJ/h6xWQsGDHlhvjT2+1aJaAn/9k8473BRWajzVXld961CdHYVFVHoce2hHiSJ0xydWrHMMZhAm0mN0UzjEpgZ0tMw209efcZHIvSwuxhteZMRy4kyiVjwFlOf5oXFCxRuCJnPj3AK9CmCf4XgEBuPIJ0TZmjGHOOdBvJmbCNnAWXYEo5/mf7MfCGhV4IJ1hNuhpoNQfOFKMUcw9/v/IpT62XpfXdGYTfGYCmCjC+gntK1spbkr2P4/2+sYMQtLpse71mpSNGXfcf3abU55Vpn+gncSxRQIDAQAB"
|
|
35
35
|
}
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -286,13 +286,13 @@ Managed jobs use the same argv/environment primitives but a different lifecycle.
|
|
|
286
286
|
|
|
287
287
|
Worker deployment is an explicit evidence state machine owned by `worker-deployment.mjs`. Wrangler upload is the authoritative remote write. Public `/healthz` is a subsequent read used to verify Worker identity and package version; it is not a transaction commit signal for the upload and does not attest the active device-authentication secret. The v5 deployment fingerprint is an HMAC over individually length-framed protocol marker, Worker name, file count, normalized relative paths, and bytes. Required Worker/shared/config inputs are traversed with `lstat`, and file bytes use bounded descriptor-first no-follow reads with multiple-hard-link rejection; absent, inaccessible, symlinked, special, or identity-changing inputs fail before upload. After a successful Wrangler result, local state atomically records the detected `workers.dev` URL, MCP URL, content/secret fingerprint, deployed package version, and timestamp before health verification begins. If verification is ambiguous, the next start compares the same fingerprint and performs a read-only verification rather than repeating the remote write. Owner-authorized candidate activation adds the missing end-to-end evidence: device preflight, signed challenge authentication, and readiness probing. An explicit authentication rejection after current-version health permits one same-name redeployment with the unchanged selected identity; ordinary timeout, proxy, TLS, network, and temporary health failures do not.
|
|
288
288
|
|
|
289
|
-
`worker-health.mjs` owns bounded health I/O: exact HTTPS `workers.dev` origin and Worker-name validation, environment-proxy selection, request timeout, redirect rejection, response-size limit, JSON/identity/version validation, and coarse error classification. `network-proxy.mjs` is shared by remote HTTP health probes and
|
|
289
|
+
`worker-health.mjs` owns bounded health I/O: exact HTTPS `workers.dev` origin and Worker-name validation, environment-proxy selection, request timeout, redirect rejection, response-size limit, JSON/identity/version validation, and coarse error classification. `network-proxy.mjs` is shared by remote HTTP health probes and relay construction. Worker health continues to honor the standard `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` model. Relay WebSocket and signed HTTP fallback additionally accept the relay-only `MBM_RELAY_PROXY` override: a non-empty value selects that HTTP(S) proxy before standard environment resolution, while an empty value restores the standard model. Proxy URLs and credentials are never exposed. Local browser-broker health uses `loopback-health.mjs`, which accepts only canonical `http://127.0.0.1:<port>/healthz`, disables agent reuse, bounds the response, and deliberately bypasses environment proxies. Definitive stale evidence is retried for propagation and then permits a same-name redeploy; timeout, TLS, network, proxy, and temporary server failure remain ambiguous and fail without upload.
|
|
290
290
|
|
|
291
291
|
Worker-name mutation is a separate identity transition. Existing state rejects a different name unless the caller also supplies the explicit force option. An authorized transition clears the current URL/fingerprint and appends the prior validated name to bounded uninstall inventory. This prevents a health-retry workaround from silently becoming a new remote resource while preserving cleanup of intentional replacements.
|
|
292
292
|
|
|
293
293
|
## Daemon reconnect and replacement
|
|
294
294
|
|
|
295
|
-
The local `RelayConnection` treats proxy selection, transport construction, WebSocket open, authentication, end-to-end readiness, and outage recovery as separate states. The shared proxy module maps WebSocket targets to
|
|
295
|
+
The local `RelayConnection` treats proxy selection, transport construction, WebSocket open, authentication, end-to-end readiness, and outage recovery as separate states. The shared proxy module maps WebSocket targets to HTTP(S) CONNECT routing, rejects non-HTTP(S) proxy schemes, and creates the proxy agent without exposing its URL or credentials. Normal relay selection uses standard environment-proxy resolution and honors `NO_PROXY`; a non-empty `MBM_RELAY_PROXY` instead fixes both WSS and signed HTTP fallback to that explicit proxy and deliberately bypasses `NO_PROXY`. Proxy connection failure does not authorize a direct relay attempt. The override controls only Machine Bridge's application-layer proxy hop: physical-interface binding, upstream proxy protocol, and upstream DNS belong to the selected sidecar/proxy implementation, which is why a loopback sidecar with independently bound outbound routing is the supported way to isolate relay continuity from an operating-system VPN/TUN. Invalid proxy configuration is a fatal configuration error rather than a retryable outage.
|
|
296
296
|
|
|
297
297
|
A connection-attempt deadline terminates sockets stuck in `CONNECTING`. After open, the daemon sends `hello`; `hello_ack` establishes an authenticated relay generation and starts liveness monitoring, but does not resolve startup or advertise readiness. The Worker then sends a random `relay_probe`. Its result must traverse the local runtime dispatcher and `sendForSession` on that generation before `ready_ack` marks the connection usable. The daemon rejects ordinary tool calls before that state and rejects a premature readiness acknowledgement without locally recorded probe delivery. Independent handshake and readiness deadlines terminate candidates that authenticate but cannot return results. Once authenticated, `RelayLiveness` sends a protocol-level WebSocket Ping every five seconds. The full ten-second Pong deadline begins only after the sender write callback proves the control frame left the local queue. Ping/application-send completions are fenced to the exact current WebSocket generation, so a callback from a superseded socket cannot mutate the replacement connection; a Ping callback that never completes has its own thirty-second local dispatch bound and becomes `relay_transport_send_timeout` even when the receive direction remains active. Reaching that first-stage deadline no longer immediately kills a ready WSS: `relay-transport-confirmation.mjs` opens one bounded fifteen-second application-confirmation window and emits the existing JSON heartbeat only after the connection is fully ready. A later protocol Pong or the explicit JSON application `pong` is bidirectional transport proof and clears suspicion; unrelated application messages update receive-side liveness only and cannot prove that daemon-to-Worker writes are succeeding. `ResilientRelayConnection` concurrently prewarms signed HTTPS in standby without Worker-side takeover; confirmed WSS recovery stops that prewarm, while a real disconnect upgrades it to exact-generation takeover and preempts any stale standby request. A scheduling-responsive true black hole therefore remains bounded to one five-second probe interval plus ten-second Pong response and fifteen-second independent confirmation, while a single ten-to-fifteen-second persistent-flow stall no longer becomes an avoidable reconnect storm. A detected local event-loop stall cancels remote suspicion and follows the separate recovery-grace branch rather than being counted as network failure. Transport Ping remains active during authenticated probing, but application heartbeat/confirmation is gated on verified readiness because the Worker probing state accepts only the readiness-probe result. Same-instance reconnect also performs explicit call-ownership reconciliation: the Worker sends its still-waiting IDs; the daemon snapshots the union of active calls and unacknowledged results, completes replacement-channel readiness, then returns `resume_calls_ack.missing_ids` only for IDs absent from that ownership union. Those IDs alone may receive one same-ID transport redelivery inside the original remaining deadline; if that cannot be done safely they settle retryably with `side_effects_started=false`. Active calls and retained terminal results continue on existing ownership, and no possibly executed tool call is automatically replayed. A separate JSON application heartbeat remains at twenty-five seconds and retains a seventy-five-second application-silence timeout, so protocol-level Pong cannot mask a Worker application path that has stopped responding. On the Worker, authenticated application-heartbeat activity refresh is synchronous and its JSON `pong` is queued before Durable Object alarm inspection or mutation; heartbeat and terminal-result paths then own one explicit coalesced alarm schedule. Persistent deadline work therefore cannot sit in front of application-liveness acknowledgement or be scheduled twice through a hidden touch helper. The Worker's ninety-second daemon-liveness deadline remains an independent wider fallback across Durable Object hibernation. Worker error codes `daemon_transport_error` and `daemon_liveness_timeout` are connection-recovery signals, not protocol incompatibility: they terminate only the current socket, run normal disconnect cleanup, and enter bounded reconnect. Local protocol-watchdog expiry is classified separately as `relay_transport_timeout`; local application-silence expiry retains `relay_heartbeat_timeout`. The same Worker classification is applied to the close frame if the preceding error frame is lost. Failure to send `hello`, or failure to deliver a readiness-probe result because its socket/session ended, follows the same transport-recovery path rather than being promoted to authentication or protocol failure. Unknown Worker errors, authentication rejection, malformed readiness sequencing, and identity/version mismatch remain fatal. Outage reminders run on their own exponential-backoff timer rather than depending on another transport callback. Each new authenticated hello also carries a schema-versioned, bounded relay diagnostic summary, including `previous_ready_inbound_silence_ms` so the silent half-open interval before close is not collapsed into the shorter close-to-ready outage duration. The local connection additionally keeps `recent_outages`, a newest-first in-memory ring capped at eight completed reconnect episodes. The ring contains only bounded outage numbers, first/final disconnect and ready timestamps, durations, close/error classes, prior-ready duration/silence, the first disconnect's fixed protocol-Ping/application-confirmation phase and millisecond ages, coarse application-route class, and connection-stage timings; a protocol/application Pong that clears transport suspicion without rebuilding WSS is not a completed outage and stays only in heartbeat diagnostics. Because the hello is sent before the recovering WebSocket can receive its final `ready_ack`, its carried ring cannot yet contain that current episode. The Worker therefore sanitizes the prior ring into the probing attachment and, only when promotion proves end-to-end readiness, synthesizes the now-completed current episode from the already bounded scalar fields before marking `outage_active=false`. Authenticated `server_info.daemon.relay_transport` can consequently retain several sub-warning-threshold reconnects instead of losing all but the latest one, without logging them by default, persisting a tool transcript, exposing endpoints/interfaces/DNS data, or treating near-miss liveness suspicions as outages.
|
|
298
298
|
|
package/docs/AUDIT.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Security and privacy audit notes
|
|
2
2
|
|
|
3
|
+
## 2026-08-31 beta.155 relay egress isolation review and beta.156 remediation
|
|
4
|
+
|
|
5
|
+
**Repeated short relay interruptions during beta.155 soak were traced below the Machine Bridge transport state machine rather than to daemon lifecycle, sleep, or resource pressure.** Privacy-bounded runtime diagnostics showed an awake operational daemon with healthy resource admission and no event-loop stall while the relay WebSocket received a connection reset. Operating-system network evidence at the same boundary showed broad flow churn on the active packet-tunnel route immediately before the relay socket closed, and an earlier interruption reproduced the same pattern. This is sufficient to classify the blocking failure as shared tunnel/upstream path churn; it does not identify a specific remote proxy node or claim that every future reset has the same cause.
|
|
6
|
+
|
|
7
|
+
**The existing standard environment-proxy support was necessary but not a complete isolation boundary.** `HTTPS_PROXY`/`HTTP_PROXY` can move CONNECT semantics into Machine Bridge, but a remotely addressed proxy socket is still opened through the operating-system route and can therefore remain inside the same VPN/TUN failure domain. Beta.156 adds `MBM_RELAY_PROXY` as a relay-only application proxy override shared by the preferred WebSocket and signed HTTP fallback. A non-empty override bypasses standard proxy/`NO_PROXY` selection, rejects non-HTTP(S) proxy schemes through the existing typed configuration boundary, and never authorizes direct fallback after proxy failure. Background service environment persistence includes the new key without logging its value. Worker deployment health deliberately remains on the standard proxy model so relay egress policy does not silently change deployment behavior.
|
|
8
|
+
|
|
9
|
+
**Physical-route ownership remains outside Machine Bridge.** The supported isolation topology is a loopback-only HTTP CONNECT sidecar whose own upstream proxy socket and resolver are pinned to the intended non-tunnel network path. Machine Bridge does not add another TUN, implement third-party proxy protocols, bind arbitrary interfaces itself, or claim that a remote `MBM_RELAY_PROXY` endpoint bypasses system routing. Regression coverage proves default WSS construction ignores `NO_PROXY` when the dedicated override is set, the real HTTP fallback traverses the same local CONNECT proxy, invalid dedicated schemes fail closed, and service installation retains the setting across background startup.
|
|
10
|
+
|
|
11
|
+
**Release consequence.** This is a blocking beta.155 soak defect and changes packaged local runtime, service-environment behavior, tests, and documentation. Package/runtime identity advances to beta.156 while hosted tool schema generation remains 21 because MCP tool schemas/results and owner-visible diagnostic shapes are unchanged. Beta.155 soak evidence cannot authorize stable promotion after this packaged fix; beta.156 requires fresh frozen verification, candidate/install-only proof, live activation and relay-egress observation, acceptance, publication under the beta channel, and a restarted major-prerelease soak interval. npm publication remains the separate explicit owner authorization boundary.
|
|
12
|
+
|
|
3
13
|
## 2026-08-30 beta.154 independent hosted-isolation/privacy review and beta.155 remediation
|
|
4
14
|
|
|
5
15
|
**The beta.154 release remained locally healthy under its existing verification plan, but the review found contract gaps outside that model.** A clean `main` baseline passed the 104-task fast plan, the complete 131-task plan, reachable-history privacy scanning, and both production/full npm audits with zero vulnerabilities. Independent delimiter-edge probes nevertheless proved that several free-form credential patterns used a terminal word boundary even though their token alphabet allows hyphen/underscore: valid-shaped GitLab, Google, and API-secret values ending in `-` could miss redaction entirely, while Slack/Machine Bridge/JWT cases could redact only a prefix and leave the final delimiter. The publication scanner independently carried parallel versions of the same common patterns, so both defenses could drift while their existing fixtures stayed green. Beta.155 moves common credential shapes into one shared source, adds current Machine Bridge password/OAuth/managed-job capability forms, uses alphabet-aware termination, and asserts exact normalized output/detection at delimiter edges.
|
package/docs/OPERATIONS.md
CHANGED
|
@@ -40,7 +40,7 @@ machine-mcp doctor
|
|
|
40
40
|
machine-mcp --verbose
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
Run these commands from the same environment used for startup so `HTTPS_PROXY`, `HTTP_PROXY`, and `NO_PROXY` are identical. After the foreground connection succeeds, run `machine-mcp service install` from that same PowerShell session. Installation stores only an allowlisted proxy/custom-CA environment snapshot in private local state so the logon task does not lose session-only `$env:` settings after reboot. A later start with no proxy variables does not erase the saved snapshot; set a variable explicitly to an empty value and reinstall to clear it. `machine-mcp service status` reports only the saved key names. Debug logs expose only the selected route and classified error; they never print a proxy endpoint or credentials.
|
|
43
|
+
Run these commands from the same environment used for startup so `MBM_RELAY_PROXY`, `HTTPS_PROXY`, `HTTP_PROXY`, and `NO_PROXY` are identical. After the foreground connection succeeds, run `machine-mcp service install` from that same PowerShell session. Installation stores only an allowlisted proxy/custom-CA environment snapshot in private local state so the logon task does not lose session-only `$env:` settings after reboot. A later start with no proxy variables does not erase the saved snapshot; set a variable explicitly to an empty value and reinstall to clear it. `machine-mcp service status` reports only the saved key names. Debug logs expose only the selected route and classified error; they never print a proxy endpoint or credentials.
|
|
44
44
|
|
|
45
45
|
### Worker quota controls
|
|
46
46
|
|
|
@@ -99,7 +99,11 @@ Brief retryable outages recover automatically. On a verified current daemon chan
|
|
|
99
99
|
|
|
100
100
|
A foreground MCP response is not durable delivery. Hosted synchronous calls reserve room for Worker and host settlement instead of occupying the complete interaction window: ordinary daemon-backed tools default to 20 seconds of remote execution plus a separate five-second Worker settlement margin; ordinary configurable browser/application foreground tools also default to 20 seconds, while compound `computer_observe` and `computer_act` default to 30 seconds; all configurable browser/application foreground tools retain their explicit 45-second maximum. Remote `exec_command`, `run_process`, and `run_local_command` no longer keep the child process inside that response lifetime. Each remote process request must carry a unique caller-held `idempotency_key` before dispatch; reuse that same key only when recovering an ambiguous acceptance response. The daemon commits the authorized operation as a principal-bound one-step managed job, launches it with interactive resource-admission priority, and returns a `job_id` inside a 10-second acceptance budget; the Worker keeps a separate five-second settlement margin and adds principal-bound `recovery_key`/`control_key` capabilities to the hosted result. Preserve all three together: hosted `read_job` requires the read capability, hosted `cancel_job` requires the control capability, and a bare `job_id` is not remote recovery authority. If that acceptance response is lost to settlement timeout, HTTP response cancellation, or relay reconnect expiry after dispatch, the public error remains non-retryable for generic callers but carries the original key and the explicit recovery action `retry_same_tool_arguments_with_same_idempotency_key`; this reconciles against the retained job instead of authorizing a blind duplicate. The detached child may execute for up to 600 seconds after admission, but the managed runner can separately wait up to thirty minutes for cooperative machine-user resource admission before the child is spawned; the child execution deadline begins only after that admission succeeds. The shared ceiling is exposed machine-readably as `server_info.tool_delivery.managed_job_resource_admission_wait_max_ms`, because the same pre-spawn boundary applies to ordinary durable process jobs and owner `start_job` steps rather than to process tools alone. While the runner is in this pre-spawn state, `read_job.current_phase` is `resource_admission`; no command has started yet. An owner can correlate a long-running status at that phase with `diagnose_runtime.runtime.resource_admission` rather than interpreting it as a slow child process; a delegated non-owner should treat the phase itself as evidence that the child has not spawned, retain the same `job_id`, and avoid blind replay rather than attempting the owner-only machine-wide diagnostic. After admission, the phase returns to `steps`, `finally_steps`, or `recovery-cleanup` as appropriate. Completed step records preserve `duration_ms` as the total orchestration duration. Local/owner reads additionally expose `resource_admission_ms` as the pre-spawn portion so a delayed successful child can be distinguished from slow execution after the fact; delegated non-owner reads omit that machine-user scheduling timing rather than turning shared-host contention into a more precise cross-workload signal. The detached job survives MCP disconnect, relay reconnect, daemon restart, or service replacement. Non-owner process authority is unchanged: automatic durable execution still uses the delegated workspace sandbox and does not grant owner-only `start_job`. If a cached host schema omits a current required field, the Worker rejects before daemon dispatch with a normal no-side-effect tool error and requests a `tools/list` refresh rather than surfacing a protocol-only validation failure. Discovery instructions and tool descriptions both carry orchestration semantics, so `server/discover` and `tools/list` each advertise `ttlMs=0` and every host-visible tool description carries `Tool schema generation N`. `server_info.tool_delivery.tool_schema_generation`, `tool_schema_server_version`, `discovery_ttl_ms`, and `tool_list_ttl_ms` identify the live contract; `host_visible_schema_known_to_server=false` is equally important because a healthy new daemon/Worker cannot prove that an external host discarded an older cached action/tool snapshot. `host_turn_deadline_observable=false` means Machine Bridge cannot pre-compute the external assistant-turn deadline, while `managed_jobs_detached_from_mcp_response=true` records that an accepted durable job is not owned by that response lifetime. After an activation that changes hosted semantics, compare the live `server_info` generation and changed invocation behavior with the governed Workspace Action control snapshot when that product layer is applicable; automation may perform the supported refresh/review path without another conversational approval. Host-internal cache inspection is intentionally excluded from operational release verification. `start_process` remains the explicit daemon-lifetime path when interactive stdin or session-style incremental output is required, but hosted calls use a 10-second execution / 15-second settlement envelope and do not queue behind resource pressure: the first failed admission returns retryable `unavailable`; owner-local callers retain the cooperative wait. Hosted `read_process` supports paced same-response follow-up: each actual output/exit blocking wait lasts at most one second. If another would-block remote read arrives inside the fifteen-second blocking cooldown, the daemon keeps that same MCP call open until output/exit or the cooldown boundary rather than returning an immediate running checkpoint; the Worker reserves enough execution/settlement headroom for that server-side pacing. Results use `status_polling_mode=paced_followup` while the process remains live, plus `blocking_poll_throttled` and `next_blocking_poll_after_ms`; callers must not busy-loop and should respect that cooldown. A new hosted call waits at most fifteen seconds for daemon readiness, but that wait is charged against the call's existing execution budget; an in-flight disconnect likewise never pauses or extends the original absolute deadline. Pending-call reconnect retention is also bounded by the smaller of reconnect grace and that original remaining deadline, and diagnostics distinguish `original call deadline expired during reconnect` from a true full `reconnect grace expired` rather than labeling both cases as the latter. Owner-local stdio/CLI calls retain their synchronous local contract because they do not depend on a hosted response stream. Keep unrelated mutations and verification independently terminal, and never infer task success merely because a durable launch was accepted. For one coherent non-interactive sequence, prefer a repository umbrella command or multi-step `start_job` rather than creating many one-step durable process carriers. If the current task needs the result, hosted `read_job` may follow the known durable `job_id` with its preserved `recovery_key` repeatedly in the same assistant response until terminal state while calls continue to be accepted; active relay reads report `status_polling_mode=bounded_followup` and no longer recommend forced handoff. The normal hosted read is a 40-second server-side long-poll. Terminal settlement returns on the next bounded five-second internal poll; nonterminal status/phase/dependency progress is coalesced for at least 30 seconds by default, and `current_step`-only churn does not wake the hosted call. `wait_ms=0` is the explicit immediate-checkpoint mode, while public hosted `wait_ms` is capped at 60 seconds. The default stays at 40 seconds because live host evidence showed that overlong single requests can outlive the host invocation even though the durable job itself remains healthy; beta.151 reproduced that class with a second explicit 180-second `read_job` returning `mcp_network_error` while generation-18 continuity evidence recorded zero unplanned ready-socket disconnects. The coalescing floor reduces host-visible event density and does not shorten the managed job, the assistant task, or the six-hour managed-step ceiling. Do not busy-loop, do not replace server-side pacing with rapid immediate reads, do not use repeated `list_jobs`, `server_info`, or `diagnose_runtime` calls as substitute polling surfaces, and do not infer or preempt a host/tool deadline from elapsed wall-clock time. Hosted `list_jobs` is aggregate-only and intentionally cannot rediscover lost job IDs/names/recovery handles; detailed global inventory remains local CLI/stdio administration. Return the `job_id`, status, and current phase for later recovery only after an actual host/tool boundary is observed, external input or authorization is required, or the user explicitly requested a checkpoint; only a terminal status is task-completion evidence.
|
|
101
101
|
|
|
102
|
-
The daemon honors `HTTPS_PROXY`/`HTTP_PROXY` and `NO_PROXY` through standard environment-proxy resolution for remote Worker health and relay traffic. `wss:` targets use HTTPS proxy selection and `ws:` targets use HTTP proxy selection.
|
|
102
|
+
The daemon honors `HTTPS_PROXY`/`HTTP_PROXY` and `NO_PROXY` through standard environment-proxy resolution for remote Worker health and ordinary relay traffic. `wss:` targets use HTTPS proxy selection and `ws:` targets use HTTP proxy selection. `MBM_RELAY_PROXY` is a relay-only override for deployments that need a stable egress boundary: when it is non-empty, both WebSocket relay construction and the signed HTTP fallback use that exact HTTP(S) proxy instead of consulting `HTTPS_PROXY`/`HTTP_PROXY` or `NO_PROXY`. A connection failure through the selected proxy follows normal relay recovery; it never retries the same attempt directly. Setting `MBM_RELAY_PROXY` explicitly to an empty value restores the standard environment-proxy model.
|
|
103
|
+
|
|
104
|
+
To isolate relay continuity from an operating-system VPN/TUN, point `MBM_RELAY_PROXY` at a loopback-only sidecar and configure that sidecar to bind its upstream proxy socket to the intended physical/network interface. Machine Bridge deliberately does not implement proxy protocols beyond HTTP CONNECT and does not bind the sidecar's upstream socket itself. Therefore a remote `MBM_RELAY_PROXY` value can still be routed through an operating-system tunnel, while a correctly bound local sidecar owns the route below Machine Bridge. DNS for the sidecar's upstream proxy hostname is likewise the sidecar's responsibility; use a resolver/path that does not re-enter the tunnel being isolated. Avoid a second system TUN solely for Machine Bridge, because competing default-route ownership recreates the same failure class at another layer.
|
|
105
|
+
|
|
106
|
+
Only HTTP and HTTPS proxy URLs are accepted. Invalid URLs or unsupported protocols fail startup with corrective guidance instead of entering the reconnect loop. Remote-owner `diagnose_runtime.runtime.relay.network_route` reports the resulting coarse application route, while local stdio `server_info.runtime.relay.network_route` reports `system-network-stack`, `application-http-proxy`, or `invalid-application-proxy-configuration`. The route remains `application-http-proxy` for either standard or dedicated proxy selection so diagnostics do not reveal proxy identity. This field describes only Machine Bridge application-level proxy selection: an operating-system VPN/TUN may still intercept `system-network-stack` traffic or the route to a remotely addressed proxy. `network_route_scope`, outage timestamps/durations, close category/code, transport error class, and next retry timing make that distinction explicit; proxy endpoints and credentials are never returned or logged. Worker deployment health continues to use standard `HTTPS_PROXY`/`HTTP_PROXY`/`NO_PROXY`; set those separately when the deployment-health path also requires a proxy. The browser-broker CLI health probe is a separate loopback-only path: it accepts only canonical `127.0.0.1`, uses direct Node HTTP with no proxy agent, and does not depend on `NO_PROXY`.
|
|
103
107
|
|
|
104
108
|
## Browser extension setup and diagnosis
|
|
105
109
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "machine-bridge-mcp",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.156",
|
|
4
4
|
"description": "Cross-client MCP bridge for local agent context, structured browser and application automation, files, Git, processes, resources, and durable jobs over stdio or OAuth relay.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -72,6 +72,7 @@ const tests = [
|
|
|
72
72
|
"tests/computer-use-test.mjs",
|
|
73
73
|
"tests/computer-use-result-budget-test.mjs",
|
|
74
74
|
"tests/relay-connection-test.mjs",
|
|
75
|
+
"tests/relay-http-fallback-test.mjs",
|
|
75
76
|
"tests/managed-job-boundary-test.mjs",
|
|
76
77
|
"tests/managed-jobs-test.mjs",
|
|
77
78
|
"tests/account-admin-test.mjs",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
import https from "node:https";
|
|
3
|
-
import {
|
|
3
|
+
import { proxyAgentForRelayHttp } from "./network-proxy.mjs";
|
|
4
4
|
|
|
5
5
|
export function postDaemonHttpRelay({ url, headers, body, timeoutMs, maximumResponseBytes, signal }) {
|
|
6
6
|
return new Promise((resolve, reject) => {
|
|
@@ -8,7 +8,7 @@ export function postDaemonHttpRelay({ url, headers, body, timeoutMs, maximumResp
|
|
|
8
8
|
const client = target.protocol === "https:" ? https : target.protocol === "http:" ? http : null;
|
|
9
9
|
if (!client) { reject(relayRequestError("daemon_http_invalid_url", "daemon HTTP relay URL must use HTTP or HTTPS")); return; }
|
|
10
10
|
let proxy;
|
|
11
|
-
try { proxy =
|
|
11
|
+
try { proxy = proxyAgentForRelayHttp(target.href); }
|
|
12
12
|
catch (error) { reject(error); return; }
|
|
13
13
|
let settled = false;
|
|
14
14
|
let timer = null;
|
|
@@ -2,13 +2,23 @@ import { HttpsProxyAgent } from "https-proxy-agent";
|
|
|
2
2
|
import { getProxyForUrl } from "proxy-from-env";
|
|
3
3
|
|
|
4
4
|
const HTTP_PROTOCOLS = new Set(["http:", "https:"]);
|
|
5
|
+
export const RELAY_PROXY_ENVIRONMENT_KEY = "MBM_RELAY_PROXY";
|
|
5
6
|
|
|
6
|
-
export function proxyAgentForWebSocket(webSocketUrl, proxyResolver = getProxyForUrl) {
|
|
7
|
+
export function proxyAgentForWebSocket(webSocketUrl, proxyResolver = getProxyForUrl, environment = process.env) {
|
|
7
8
|
const target = new URL(String(webSocketUrl));
|
|
8
9
|
if (target.protocol !== "ws:" && target.protocol !== "wss:") throw new Error("relay WebSocket URL must use ws or wss");
|
|
9
10
|
const lookupUrl = new URL(target);
|
|
10
11
|
lookupUrl.protocol = target.protocol === "wss:" ? "https:" : "http:";
|
|
11
|
-
return
|
|
12
|
+
return proxyAgentForRelayLookup(lookupUrl, proxyResolver, environment, {
|
|
13
|
+
errorCode: "relay_proxy_configuration",
|
|
14
|
+
subject: "relay proxy",
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function proxyAgentForRelayHttp(httpUrl, proxyResolver = getProxyForUrl, environment = process.env) {
|
|
19
|
+
const target = new URL(String(httpUrl));
|
|
20
|
+
if (!HTTP_PROTOCOLS.has(target.protocol)) throw new Error("relay HTTP URL must use http or https");
|
|
21
|
+
return proxyAgentForRelayLookup(target, proxyResolver, environment, {
|
|
12
22
|
errorCode: "relay_proxy_configuration",
|
|
13
23
|
subject: "relay proxy",
|
|
14
24
|
});
|
|
@@ -23,9 +33,21 @@ export function proxyAgentForHttp(httpUrl, proxyResolver = getProxyForUrl) {
|
|
|
23
33
|
});
|
|
24
34
|
}
|
|
25
35
|
|
|
36
|
+
function proxyAgentForRelayLookup(lookupUrl, proxyResolver, environment, context) {
|
|
37
|
+
const explicitProxy = Object.hasOwn(environment || {}, RELAY_PROXY_ENVIRONMENT_KEY)
|
|
38
|
+
? String(environment[RELAY_PROXY_ENVIRONMENT_KEY] ?? "").trim()
|
|
39
|
+
: "";
|
|
40
|
+
if (explicitProxy) return proxyAgentForValue(explicitProxy, context);
|
|
41
|
+
return proxyAgentForLookup(lookupUrl, proxyResolver, context);
|
|
42
|
+
}
|
|
43
|
+
|
|
26
44
|
function proxyAgentForLookup(lookupUrl, proxyResolver, context) {
|
|
27
45
|
const proxyValue = String(proxyResolver(lookupUrl.href) || "").trim();
|
|
28
46
|
if (!proxyValue) return { agent: null, mode: "direct" };
|
|
47
|
+
return proxyAgentForValue(proxyValue, context);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function proxyAgentForValue(proxyValue, context) {
|
|
29
51
|
let proxyUrl;
|
|
30
52
|
try {
|
|
31
53
|
proxyUrl = new URL(proxyValue);
|
|
@@ -48,7 +48,7 @@ export function relayFatalMessage(category) {
|
|
|
48
48
|
return "remote relay protocol error; upgrade and redeploy both components, then restart the daemon";
|
|
49
49
|
}
|
|
50
50
|
if (category === "relay_proxy_configuration") {
|
|
51
|
-
return "remote relay proxy configuration is invalid; check HTTP_PROXY, HTTPS_PROXY, and NO_PROXY";
|
|
51
|
+
return "remote relay proxy configuration is invalid; check MBM_RELAY_PROXY, HTTP_PROXY, HTTPS_PROXY, and NO_PROXY";
|
|
52
52
|
}
|
|
53
53
|
if (category === "relay_device_session_expired") {
|
|
54
54
|
return "daemon device session expired; restart the daemon to obtain a fresh root-signed session certificate";
|
package/src/worker/index.ts
CHANGED
|
@@ -61,7 +61,7 @@ import {
|
|
|
61
61
|
closeWebSocketQuietly, daemonErrorCloseCode, isObjectRecord, rejectDaemonMessage,
|
|
62
62
|
sendWebSocketQuietly, trySendWebSocket,
|
|
63
63
|
} from "./websocket-protocol.ts";
|
|
64
|
-
const SERVER_VERSION = "3.0.0-beta.
|
|
64
|
+
const SERVER_VERSION = "3.0.0-beta.156";
|
|
65
65
|
const MCP_SERVER_INFO = mcpServerInfo(SERVER_VERSION);
|
|
66
66
|
const MAX_DAEMON_MESSAGE_BYTES = 8 * 1024 * 1024;
|
|
67
67
|
const DAEMON_RECONNECT_GRACE_MS = relayContract.reconnectGraceMs; const NEW_CALL_RECONNECT_GRACE_MS = relayContract.newCallReconnectGraceMs;
|