machine-bridge-mcp 3.0.0-beta.167 → 3.0.0-beta.173
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 +9 -6
- package/README.md +2 -1
- package/SECURITY.md +2 -2
- package/browser-extension/manifest.json +1 -1
- package/docs/AUDIT.md +8 -1
- package/docs/LOGGING.md +1 -1
- package/docs/OPERATIONS.md +8 -6
- package/docs/UPGRADING.md +2 -0
- package/package.json +3 -3
- package/scripts/consumer-package-security.mjs +1 -1
- package/src/local/cli-activate.mjs +4 -0
- package/src/local/cli-idle-sleep.mjs +15 -0
- package/src/local/cli-options.mjs +5 -0
- package/src/local/cli.mjs +12 -8
- package/src/local/daemon-http-relay-connection.mjs +3 -1
- package/src/local/device-root-provider.mjs +6 -0
- package/src/local/hosted-file-read-budget.mjs +53 -0
- package/src/local/idle-sleep-mode.mjs +9 -0
- package/src/local/job-runner.mjs +10 -2
- package/src/local/macos-idle-sleep-assertion.mjs +24 -15
- package/src/local/macos-idle-sleep-recovery.mjs +52 -0
- package/src/local/network-proxy.mjs +14 -0
- package/src/local/process-lock-read-retry.mjs +15 -0
- package/src/local/process-session-read.mjs +3 -1
- package/src/local/relay-connection-classification.mjs +3 -2
- package/src/local/remote-activity-idle-sleep-guard.mjs +21 -21
- package/src/local/remote-idle-sleep-assertions.mjs +41 -0
- package/src/local/resilient-relay-connection.mjs +6 -0
- package/src/local/runtime-activation.mjs +3 -1
- package/src/local/runtime-device-session.mjs +111 -0
- package/src/local/runtime-package-identity.mjs +61 -0
- package/src/local/runtime-relay-connection-options.mjs +5 -3
- package/src/local/runtime-relay.mjs +3 -2
- package/src/local/runtime.mjs +21 -6
- package/src/local/security-audit-activity.mjs +23 -1
- package/src/local/service-environment.mjs +1 -0
- package/src/local/service-restartability.mjs +20 -0
- package/src/local/service.mjs +32 -19
- package/src/local/state.mjs +15 -2
- package/src/local/workspace-file-service.mjs +10 -11
- package/src/local/wrangler-toolchain/package-lock.json +119 -119
- package/src/local/wrangler-toolchain/package.json +2 -2
- package/src/local/wrangler-toolchain.mjs +2 -2
- package/src/shared/relay-contract.json +4 -0
- package/src/shared/server-metadata.json +3 -3
- package/src/worker/hosted-result-budget.ts +28 -0
- package/src/worker/index.ts +1 -1
- package/src/worker/mcp-stale-schema-compat.ts +13 -6
- package/src/worker/tool-catalog.ts +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 3.0.0-beta.
|
|
4
|
-
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
3
|
+
## 3.0.0-beta.173 - 2026-09-09
|
|
4
|
+
|
|
5
|
+
- Keep root-certified daemon session certificates capped at 24 hours while removing the avoidable daemon-stop boundary for the default portable JWK root: the runtime derives a fresh ephemeral session ten minutes before expiry and reconnects the same daemon instance through existing relay reconciliation.
|
|
6
|
+
- Make WebSocket preflight/challenge authentication and signed HTTPS fallback polls read one shared current-session provider, so a rollover cannot leave the preferred and fallback transports on different certificate generations.
|
|
7
|
+
- Cover delayed timers after system suspension: when an authentication boundary is reached at or after the renewal point, a portable root synchronously renews before signing. Renewal failure retains the previous session, uses bounded 1/5/30/300-second retry, and the existing `relay_device_session_expired` fatal path remains fail closed if no valid renewal is available.
|
|
8
|
+
- Do not grant unattended signing to Secure Enclave roots. They retain their user-presence semantics and supervised-restart fallback. Owner runtime info exposes only coarse renewal generation/expiry/due/failure state, never key or certificate material.
|
|
9
|
+
- Raise the reviewed Sharp security floor from 0.35.3 to 0.35.4 in both the main development tree and the private Wrangler control-plane toolchain after the beta.171 live-activation preflight detected a new high-severity libheif advisory chain. The same Wrangler 4.127.1 / workerd 1.20260828.1 control-plane versions remain in place, and fresh production-only audits report zero vulnerabilities.
|
|
10
|
+
- Bound process-lock snapshot reads across the daemon's atomic startup-readiness publication. A path/descriptor identity mismatch retries only `MBM_IDENTITY_CHANGED` up to four reads before failing closed; hard-link, symlink, permission, oversized-file, and unrelated storage failures keep their existing immediate failure semantics. This closes the beta.172 post-ready activation race where the candidate Worker/service was already healthy but the activation subprocess failed while observing a concurrently replaced daemon lock.
|
|
11
|
+
- Advance package, Worker, and browser-extension identity to `3.0.0-beta.173`; hosted tool schema generation remains 27 because no MCP tool argument/result contract changes in this release.
|
|
9
12
|
|
|
10
13
|
## Historical releases
|
|
11
14
|
|
package/README.md
CHANGED
|
@@ -129,7 +129,7 @@ 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.
|
|
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. By default 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 silently retries the same transport directly. Deployments that need the HTTPS fallback in a different application-layer fault domain may also set `MBM_RELAY_FALLBACK_PROXY`: a non-empty value selects an explicit fallback-only HTTP(S) proxy, while an explicitly empty value restores standard `HTTPS_PROXY`/`HTTP_PROXY`/`NO_PROXY` resolution for the fallback without changing the WebSocket route. A common primary deployment is a loopback-only sidecar whose own upstream socket is pinned to the intended physical/network interface. Machine Bridge does not itself bind either route below the application layer, so a `system-network-stack` fallback may still be intercepted by an operating-system VPN/TUN. See [docs/OPERATIONS.md](docs/OPERATIONS.md).
|
|
133
133
|
|
|
134
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).
|
|
135
135
|
|
|
@@ -224,6 +224,7 @@ machine-mcp
|
|
|
224
224
|
machine-mcp status
|
|
225
225
|
machine-mcp doctor
|
|
226
226
|
machine-mcp workspace show|set|reset
|
|
227
|
+
machine-mcp idle-sleep show|set MODE
|
|
227
228
|
machine-mcp service status|install|start|stop|uninstall
|
|
228
229
|
machine-mcp account list|clients|revoke-client|add|role|enable|disable|rotate-password|remove
|
|
229
230
|
machine-mcp browser status|setup|pair|path
|
package/SECURITY.md
CHANGED
|
@@ -193,9 +193,9 @@ Local resources may be injected without returning their bytes through MCP, but t
|
|
|
193
193
|
|
|
194
194
|
## Network egress boundaries
|
|
195
195
|
|
|
196
|
-
Machine Bridge can select
|
|
196
|
+
Machine Bridge can select application-layer HTTP(S) proxy routes for relay traffic. A non-empty `MBM_RELAY_PROXY` takes precedence over standard proxy/`NO_PROXY` resolution for the WebSocket relay and, unless separately overridden, the signed HTTP fallback. `MBM_RELAY_FALLBACK_PROXY` may explicitly select a different fallback-only HTTP(S) proxy; when that key is present with an empty value, only the fallback returns to standard `HTTPS_PROXY`/`HTTP_PROXY`/`NO_PROXY` resolution. A failed selected route is never silently rewritten into an unconfigured direct retry. Proxy URLs and credentials are not returned through runtime diagnostics or operational logs.
|
|
197
197
|
|
|
198
|
-
This is not an operating-system network-isolation boundary. Machine Bridge does not bind
|
|
198
|
+
This is not an operating-system network-isolation boundary. Machine Bridge does not bind proxy upstream sockets to physical interfaces, implement an independent routing table, or prevent a system VPN/TUN from intercepting traffic to a remotely addressed proxy or a fallback using the system network stack. 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. Separate application-layer routes reduce shared failure coupling but do not by themselves prove independent operating-system failure domains. The sidecar and upstream proxy remain separate trusted network components.
|
|
199
199
|
|
|
200
200
|
## Filesystem and mutation integrity
|
|
201
201
|
|
|
@@ -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.173",
|
|
34
34
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxryYkpZhq8+VAQLHcGS9BAHQcyKX8RHGIpIwvtIVRU/rcOcE0bNdnM0aZJ/h6xWQsGDHlhvjT2+1aJaAn/9k8473BRWajzVXld961CdHYVFVHoce2hHiSJ0xydWrHMMZhAm0mN0UzjEpgZ0tMw209efcZHIvSwuxhteZMRy4kyiVjwFlOf5oXFCxRuCJnPj3AK9CmCf4XgEBuPIJ0TZmjGHOOdBvJmbCNnAWXYEo5/mf7MfCGhV4IJ1hNuhpoNQfOFKMUcw9/v/IpT62XpfXdGYTfGYCmCjC+gntK1spbkr2P4/2+sYMQtLpse71mpSNGXfcf3abU55Vpn+gncSxRQIDAQAB"
|
|
35
35
|
}
|
package/docs/AUDIT.md
CHANGED
|
@@ -9,10 +9,17 @@ This file is the current audit summary. Historical findings, closed incidents, a
|
|
|
9
9
|
- Durable managed-job recovery, idempotent submission, ambiguous-side-effect settlement, process-tree termination confirmation, WebSocket/HTTPS relay recovery, authorization intersections, atomic state writes, resource admission, and rollback evidence remain current product guarantees rather than historical compatibility.
|
|
10
10
|
- Persisted-state migration is treated separately from protocol compatibility. Browser pairing and OAuth refresh-family readers that protect credential continuity or replay prevention remain until their supported one-way migration can be moved to an explicit upgrade phase and proven complete; they are not deleted merely because the executable protocol is current-only.
|
|
11
11
|
- A hosted transient process result promised for recovery remains non-evictable during its fixed 24-hour undelivered-result grace. Delegated pending recovery retains the account-scoped capacity boundary that prevents one principal from exhausting the shared retained-state store.
|
|
12
|
+
- Hosted result pressure is now bounded independently from execution correctness: relay `read_file` returns at most a 64 KiB complete serialized result with whole-line continuation, relay `read_process` pages at 32 KiB, and account-owned transient one-step process carriers capture at most 32 KiB aggregate output while retaining explicit truncation counts. Local/stdio capacities are unchanged.
|
|
13
|
+
- Owner diagnostics expose only content-free 15-minute result-byte aggregates alongside call density and keep `host_side_events_observable=false`; they cannot prove ChatGPT host-turn termination or final-response receipt.
|
|
12
14
|
- Resource-state reads retry only `MBM_IDENTITY_CHANGED` for at most four observations; permission, link, malformed-content, and persistent identity failures remain fail closed.
|
|
15
|
+
- Relay application-layer fault domains are now independently configurable: WSS may remain on `MBM_RELAY_PROXY` while signed HTTP fallback uses a distinct `MBM_RELAY_FALLBACK_PROXY` or explicitly returns to standard environment-proxy resolution. This reduces common-mode application-proxy coupling but is not evidence that an operating-system VPN/TUN or shared upstream cannot affect both routes.
|
|
16
|
+
- macOS idle-sleep continuity now has an explicit persistent policy rather than an implicit one-size-fits-all lease. `activity` remains the default; `ac-continuous` and `continuous` add daemon-lifetime assertions with different battery semantics, and every assertion uses bounded desired-state self-healing after unexpected child failure. Diagnostics expose only coarse mode/recovery ownership and bounded unprotected duration.
|
|
17
|
+
- Portable JWK device roots now renew the daemon's 24-hour root-certified ephemeral session in-process before expiry. WSS and signed HTTPS share one current-session provider; suspension-delayed authentication renews synchronously at the authentication boundary, while renewal failures retain the old session and preserve the existing fail-closed expiry path. Secure Enclave roots remain non-renewable without user presence.
|
|
18
|
+
- The reviewed Sharp security floor is 0.35.4 in both the main development graph and the private Wrangler toolchain. The beta.171 activation preflight was blocked before Worker/service mutation when a newly published libheif advisory caused npm audit to report three high-severity aggregate findings through Sharp -> Miniflare -> Wrangler; an isolated 0.35.4 override retained Wrangler 4.127.1 and workerd 1.20260828.1 while reducing both current production-only audits to zero vulnerabilities.
|
|
19
|
+
- Process-lock readers now treat only `MBM_IDENTITY_CHANGED` as a bounded atomic-publication race and retry at most four observations. This is narrower than generic activation recovery: persistent identity churn and every other storage/link/permission failure still fail closed. The change is driven by beta.172 live evidence where the exact candidate Worker and service were already verified ready, but the activation subprocess observed the daemon lock while startup-readiness publication atomically replaced that file and therefore returned failure instead of writing the activation record.
|
|
13
20
|
|
|
14
21
|
## Residual review requirements
|
|
15
22
|
|
|
16
23
|
A green fast or full suite is necessary but not sufficient security evidence for publication. Release acceptance still requires the package/install/security gates and any hosted or live boundary evidence required by the changed surface. This summary does not authorize deployment or npm publication.
|
|
17
24
|
|
|
18
|
-
|
|
25
|
+
The beta.173 candidate combines the session-rollover repair, the Sharp security-floor refresh required by the failed beta.171 activation preflight, and the bounded process-lock observation repair required by the beta.172 post-ready activation race. Unit and integration evidence must prove proactive portable-root rotation, suspension-delayed synchronous renewal, bounded failure retry without identity loss, shared WSS/HTTPS identity selection, preservation of the existing fatal expiry behavior when renewal is unavailable, exact 0.35.4 Sharp resolution in both audited dependency graphs, recovery from transient process-lock identity replacement, and fail-closed behavior after four identity changes or any unrelated storage failure. Owner-machine acceptance should include another persistent candidate activation so the exact failure mode is exercised against a real daemon replacement; a real 24-hour session soak remains separate long-horizon evidence.
|
package/docs/LOGGING.md
CHANGED
|
@@ -70,7 +70,7 @@ Brief network interruptions are expected on laptop network changes, Worker deplo
|
|
|
70
70
|
- failure to receive `hello_ack` within the handshake deadline, or `ready_ack` within the independent end-to-end readiness deadline, terminates the candidate socket and retries;
|
|
71
71
|
- authenticated transports request protocol-level WebSocket Ping every five seconds. A sender callback starts the full ten-second Pong deadline only after actual local dispatch. If that deadline expires on a fully ready WSS, the relay runtime records `relay.transport.suspect`, opens one fifteen-second application-confirmation window, sends a JSON heartbeat, and prewarms HTTPS in standby instead of killing the socket immediately. A later protocol Pong or explicit JSON application `pong` records `relay.transport.recovered` and preserves WSS; ordinary inbound tool/control traffic remains receive-side evidence and cannot clear transport suspicion. Only confirmation expiry records `relay.transport.confirmation_failed` and closes as `relay_transport_timeout`. A thirty-second local Ping-dispatch failure remains the distinct `relay.transport.send_timeout` / `relay_transport_send_timeout` path even if unrelated inbound traffic continues. Send-completion callbacks are exact-WebSocket-generation fenced, so a callback from a superseded socket cannot mutate current relay diagnostics or confirmation state. These anomaly events contain only bounded timing/state fields;
|
|
72
72
|
- a separate periodic twenty-five-second application heartbeat refreshes Worker daemon activity and retains a seventy-five-second application-silence timeout; it begins only after end-to-end readiness, so authenticated probing cannot send a message type that the Worker probing state does not accept. Protocol-level Pong therefore cannot mask a Worker application path that has stopped replying. The Worker queues the heartbeat's JSON `pong` before Durable Object alarm inspection or mutation, then performs one explicit coalesced schedule, so storage latency is not allowed to sit ahead of application-liveness acknowledgement;
|
|
73
|
-
- a late local transport-watchdog tick is classified as `runtime.event_loop.stall`, sends a fresh transport probe, and defers disconnect for a bounded recovery interval instead of being mislabeled as immediate remote failure; a macOS sleep/wake interval may legitimately produce this warning without a daemon fault. The heartbeat snapshot retains the lag of the last actual stall separately from the rolling maximum. Owner `diagnose_runtime` can compare that end time and duration with a bounded fixed `pmset` sleep-history projection and reports `matched_system_sleep` only when both dimensions agree within the fixed tolerance; an unmatched stall remains unclassified rather than being labeled synchronous JavaScript blockage by elimination. The same diagnostic also intersects the most recent completed relay disconnect interval with that bounded sleep history and reports only outage/overlap timing plus a fixed classification; `majority_system_sleep_overlap` means host suspension dominated the observed outage, so a retained `connection_reset` is aftermath evidence rather than sufficient independent-network evidence. An authorized, schema-valid remote tool call enters
|
|
73
|
+
- a late local transport-watchdog tick is classified as `runtime.event_loop.stall`, sends a fresh transport probe, and defers disconnect for a bounded recovery interval instead of being mislabeled as immediate remote failure; a macOS sleep/wake interval may legitimately produce this warning without a daemon fault. The heartbeat snapshot retains the lag of the last actual stall separately from the rolling maximum. Owner `diagnose_runtime` can compare that end time and duration with a bounded fixed `pmset` sleep-history projection and reports `matched_system_sleep` only when both dimensions agree within the fixed tolerance; an unmatched stall remains unclassified rather than being labeled synchronous JavaScript blockage by elimination. The same diagnostic also intersects the most recent completed relay disconnect interval with that bounded sleep history and reports only outage/overlap timing plus a fixed classification; `majority_system_sleep_overlap` means host suspension dominated the observed outage, so a retained `connection_reset` is aftermath evidence rather than sufficient independent-network evidence. An authorized, schema-valid remote tool call enters macOS activity protection only after policy/account/operation authorization and argument validation succeed; remote process sessions extend that ownership through child settlement. The default `activity` mode keeps the existing shared `/usr/bin/caffeinate -i -s -w <owner-pid>` assertion and fixed thirty-minute rolling inactivity grace. `ac-continuous` adds a daemon-lifetime `-s` assertion, while `continuous` holds `-i -s` for the daemon lifetime and does not arm inactivity grace. Unexpected assertion-child failure retains desired-state ownership and schedules bounded 1/5/30-second recovery; explicit release/shutdown cancels pending recovery. `runtime.idle_sleep_guard.unavailable` still records only a coarse `error_class`, never argv, paths, PID, tool name/content, session identity, or job identity; mode, generation, restart count, recovery-pending state, and bounded unprotected-duration telemetry are exposed through diagnostics rather than logs. Remote account managed-job runners own the same self-healing assertion primitive only after runner-claim confirmation and persisted account ownership validation; local managed jobs do not acquire this remote-continuity assertion. The remote runner fallback stderr diagnostic remains the fixed text `managed job idle-sleep assertion unavailable` plus a sanitized coarse `error_class`, without job name/id, workspace path, argv, environment, or captured output.
|
|
74
74
|
|
|
75
75
|
A WebSocket close code such as `1006` means the transport ended without a normal close handshake, but it does not identify who initiated termination: Machine Bridge's own liveness recovery calls `terminate()` when a transport/send timeout is confirmed, and that local hard close can surface as 1006. Diagnose the cause from `last_close_category`, transport-confirmation/send-timeout evidence, and retained network milestones rather than treating 1006 itself as proof of a remote/network-initiated close. If it recovers inside ten seconds, the warning-level service log is intentionally silent and the authenticated `daemon.relay_transport` snapshot is the post-event evidence surface. It is useful for debug diagnosis but not useful as the default user message. It is not evidence that the daemon process restarted. Worker `daemon_transport_error` / `daemon_liveness_timeout` messages and their 1012 close frames are likewise retryable connection conditions, not upgrade instructions. Only an unknown/incompatible Worker error, authentication failure, or identity/version mismatch may produce the fatal protocol/configuration log and daemon exit. Default logs therefore describe the affected layer, duration, classification, and recovery behavior rather than printing raw close envelopes.
|
|
76
76
|
|
package/docs/OPERATIONS.md
CHANGED
|
@@ -10,11 +10,11 @@ machine-mcp service status
|
|
|
10
10
|
|
|
11
11
|
Routine remote checks should use authenticated `server_info` with `detail: "summary"`; request the default/full projection only when the caller's authority permits and exact effective-tool, OAuth/account, or detailed owner observability is actually needed. Non-owner full responses intentionally retain hidden markers/counts instead of cross-principal activity, resource aliases, stable device-key identity, or daemon-only tool names. Remote `diagnose_runtime` is owner-only because its fixed probes expose machine-wide control-plane activity; narrower roles use `server_info`/`project_overview` for authority-scoped readiness and workspace state. `status` prints redacted profile state and verifies the deployed Worker version. Resource source paths remain redacted. `doctor` checks Node.js, the package-installed Wrangler binary, Cloudflare login, Worker health, the configured policy, the automatic-without-per-operation-prompts authorization model, and the same fixed local filesystem/process/shell/job-storage/resource probes exposed to the remote owner by `diagnose_runtime`. It constructs an isolated local runtime: `diagnosticScope.running_service_process_inspected=false` and `remote_relay_inspected=false` are deliberate, so a green doctor result is not evidence that the launchd/systemd/Scheduled Task daemon retained its Worker WebSocket. Inspect authenticated `server_info.daemon.relay_transport` for the running service relay. Authenticated `server_info.authorization.execution_model` reports the authority contract and identifies whether the account has daemon-OS-user ambient authority. Public `/healthz` output contains only server identity and version; daemon details require an authenticated `server_info` call.
|
|
12
12
|
|
|
13
|
-
For interruption analysis, prefer one owner `diagnose_runtime` call over a chain of inventory probes. It now includes `runtime.managed_jobs.recent_activity`, `runtime.security_audit.recent_activity`, bounded `runtime.resource_admission.waiters.diagnostics`, `runtime.system_sleep`, `runtime.event_loop_pause_analysis`, and `runtime.relay_outage_analysis`. The audit aggregate contains only counts, bounded tool names, failure totals,
|
|
13
|
+
For interruption analysis, prefer one owner `diagnose_runtime` call over a chain of inventory probes. It now includes `runtime.managed_jobs.recent_activity`, `runtime.security_audit.recent_activity`, bounded `runtime.resource_admission.waiters.diagnostics`, `runtime.system_sleep`, `runtime.event_loop_pause_analysis`, and `runtime.relay_outage_analysis`. The audit aggregate contains only counts, bounded tool names, failure totals, calls-per-minute density, and numeric result-pressure fields (`output_bytes_last_15m`, `maximum_output_bytes_last_15m`, `large_result_calls_last_15m`, and `peak_output_bytes_per_minute_last_15m`) derived from the existing content-free hash-chained audit log; it contains no tool arguments or result content. Its `coverage=daemon_reached_relay_tool_calls_only` and `host_side_events_observable=false` fields make the evidence boundary explicit: host-only discovery/control-plane/final-delivery events are not counted. The waiter projection reports only resource-request shape and the current admission reason. On macOS with shell-capable owner diagnostics, the fixed power probe reduces `pmset` history to a small list of sleep start/end/duration/reason classes; it never returns raw power-log lines. `event_loop_pause_analysis.classification=matched_system_sleep` requires both the recorded runtime-stall end time and duration to match one of those bounded operating-system sleep intervals within a fixed tolerance. `relay_outage_analysis` separately compares the most recent completed `recent_outages[0].disconnected_at` -> `recent_outages[0].ready_at` interval with the same bounded sleep history and reports exact overlap duration/ratio; an active outage uses `outage_started_at` instead of the later `last_disconnected_at`. `majority_system_sleep_overlap` means at least half of that observed relay outage occurred while macOS was suspended. A sleeping JavaScript process may be unable to observe the stale socket until wake, so a zero-overlap close-to-ready interval is not automatically awake-network evidence: `wake_boundary_system_sleep_aftermath` is emitted only when the disconnect occurs within the fixed wake tolerance and that same sleep independently matches the event-loop stall in both end time and duration. Either sleep classification makes a retained `connection_reset`/timeout transport aftermath rather than sufficient evidence of a separate network root cause. `no_matching_recent_system_sleep` remains the classification for an awake reset or a merely coincidental near-wake reset without same-sleep stall evidence; it leaves the cause unassigned rather than guessing a VPN, edge, Worker, or host cause. Full relay diagnostics additionally expose `recent_outages`, a newest-first in-memory history capped at eight completed WebSocket reconnect episodes. Each entry contains only bounded outage numbering, first/final disconnect and ready timestamps, duration, close/error classes, previous-ready duration/silence, first-disconnect liveness phase/timing, coarse application-route class, and connection-stage timings. `disconnected_at` is the first outage transition and `last_disconnect_at` is the final failed reconnect transition. A protocol/application Pong that clears a liveness suspicion without rebuilding the WebSocket remains heartbeat evidence and is deliberately absent from `recent_outages`; the array is therefore reconnect history, not a list of every transient transport suspicion. ChatGPT host-turn termination/final-message receipt remains explicitly unobservable.
|
|
14
14
|
|
|
15
|
-
`runtime.idle_sleep_guard`
|
|
15
|
+
`runtime.idle_sleep_guard` carries coarse activity/grace/release timestamps plus `mode`, `requests_idle_sleep_prevention`, `requests_system_sleep_prevention_on_ac`, `assertion_generation`, `restart_count`, `recovery_pending`, and bounded current/last unprotected duration. These fields are diagnostic ownership evidence only. The default `activity` mode is backward compatible: authorized relay activity holds `/usr/bin/caffeinate -i -s -w <owner-pid>` through execution and the fixed thirty-minute inactivity grace. In `activity` and `ac-continuous`, that grace begins only after the last owned daemon-side activity settles; a new authorized activity cancels a pending release and receives the full grace after it later settles. `machine-mcp idle-sleep set ac-continuous` adds a daemon-lifetime `/usr/bin/caffeinate -s -w <daemon-pid>` assertion while keeping the normal activity assertion, so idle-sleep prevention on battery remains activity-scoped. `machine-mcp idle-sleep set continuous` instead holds `/usr/bin/caffeinate -i -s -w <daemon-pid>` for the daemon lifetime and does not arm inactivity grace; restart the daemon/service after changing the persisted mode. Every assertion uses fixed 1/5/30-second recovery after unexpected child exit or setup failure, while explicit release/shutdown cancels recovery. The `-s` request is effective only on AC power; `continuous` adds `-i` specifically to request Idle Sleep prevention on battery as well. None of these modes claims to defeat explicit sleep, lid-close policy, power loss, or operating-system behavior outside the documented assertion contracts. Remote account managed-job runners retain their separate runner-owned assertion and now inherit the same bounded child self-healing.
|
|
16
16
|
|
|
17
|
-
Remote durable carriers also reduce event amplification at source. After durable acceptance, the original `exec_command`, `run_process`, or `run_local_command` response waits up to `server_info.tool_delivery.remote_process_initial_settlement_wait_ms` for a short helper to settle, while hosted `start_job` uses the same underlying two-second window exposed separately as `remote_managed_job_initial_settlement_wait_ms`. If the accepted job becomes terminal inside that window, terminal status/result are returned immediately with `follow_up_read_required=false`; otherwise the response retains the same durable recovery envelope and `follow_up_read_required=true`. This response coalescing changes only the common helper-plus-read event count; it does not reduce the 600-second one-step process execution budget, the thirty-minute pre-spawn admission allowance, dependency waiting, or the six-hour managed-job step ceiling.
|
|
17
|
+
Remote durable carriers also reduce event amplification at source. After durable acceptance, the original `exec_command`, `run_process`, or `run_local_command` response waits up to `server_info.tool_delivery.remote_process_initial_settlement_wait_ms` for a short helper to settle, while hosted `start_job` uses the same underlying two-second window exposed separately as `remote_managed_job_initial_settlement_wait_ms`. If the accepted job becomes terminal inside that window, terminal status/result are returned immediately with `follow_up_read_required=false`; otherwise the response retains the same durable recovery envelope and `follow_up_read_required=true`. This response coalescing changes only the common helper-plus-read event count; it does not reduce the 600-second one-step process execution budget, the thirty-minute pre-spawn admission allowance, dependency waiting, or the six-hour managed-job step ceiling. Hosted `exec_command`/`run_process`/`run_local_command` transient carriers now cap aggregate captured stdout/stderr at 32 KiB and disclose omitted bytes; redirect complete large output to a file. Hosted `read_file` caps the complete serialized result at 64 KiB and, when `end_line` is omitted, returns the largest whole-line page that fits plus `next_start_line`; hosted `read_process` caps each page at 32 KiB. Local/stdio capacities are unchanged.
|
|
18
18
|
|
|
19
19
|
Repository-controlled release and publication commands emit terminal failures as one JSON object with exactly `event` and `error` fields. The event is a fixed validated identifier; the error is bounded and passes through portable credential, URL, email, home-path, and control-character redaction before JSON serialization. Treat the object as one log record. Do not replace it with raw subprocess output or interpolate the error into a prefix string when adding a new release path.
|
|
20
20
|
|
|
@@ -95,15 +95,15 @@ After the host path recovers, compare authenticated `server_info`, `machine-mcp
|
|
|
95
95
|
|
|
96
96
|
A reconnect warning proves a transport interruption, not a daemon crash. Compare daemon PID and process start time with `connected_at`, `last_seen_at`, `daemon.relay_transport.last_close_category`, `last_close_code`, `outage_count`, `recent_outages`, `outage_attempts`, `previous_ready_inbound_silence_ms`, `last_connect_milestones_ms`, and the coarse network-route class. In each completed `recent_outages` entry, `disconnected_at` is the first transition into that outage, `last_disconnect_at` is the final failed reconnect/close transition, `ready_at` is verified recovery, and `duration_ms` measures first-disconnect-to-ready time. The bounded `probe_dispatch_pending_at_start`, `probe_dispatch_age_ms_at_start`, `probe_outstanding_at_start`, `probe_age_ms_at_start`, `transport_confirmation_pending_at_start`, and `application_inbound_silence_ms_at_start` fields freeze the liveness phase at that first transition. Use them to test hypotheses such as a failure coinciding with the first five-second protocol Ping, but do not treat that timing correlation as proof that Ping caused the reset. Also distinguish a planned restart from an accidental outage: a current-generation daemon sends `daemon_draining` before relay close, affected calls receive `reason=daemon_planned_drain`, and owner/full `server_info.worker.continuity_evidence` schema 2 durably retains the planned-drain count/time plus bounded socket-disconnect and client-cancellation observations across Worker isolate replacement. Treat `ready_socket_disconnects`, `unplanned_ready_socket_disconnects`, and `last_ready_socket_disconnect` as the qualified durable socket-outage evidence; the broader `socket_disconnects` counters include candidate/probing transport churn. `last_socket_disconnect.role`, `was_ready`, and `connected_at` identify whether the newest first-observed close/error had ever become the authoritative ready channel. Planned-drain intent is serialized into the WebSocket attachment before close so a Durable Object isolate replacement cannot turn a planned shutdown into an unplanned disconnect. Schema-1 disconnect counts are intentionally reset during the schema-2 transition because old isolates could count stale/expired close callbacks more than once. `worker.observability.continuity` remains isolate-local and may reset; use the qualified durable summary for post-incident correlation rather than relying on a later close-1006 inference. For hosted `read_job`, recover with the same returned `job_id` plus its `recovery_key`; do not resubmit the job's underlying mutation. If the job predates capability issuance or its capability was never delivered, use local CLI/stdio administration instead of attempting hosted global discovery. `last_connect_milestones_ms` contains only bounded relative timings for the most recent connection attempt phases such as DNS resolution, TCP connect, TLS establishment, HTTP rejection, and WebSocket open; `last_failed_connect_stage`, `last_failed_connect_duration_ms`, `last_failed_connect_milestones_ms`, and `last_failed_connect_http_status` retain the most recent failed attempt even after a later retry succeeds. `last_transport_error_ready` and `last_transport_error_authenticated` distinguish failure of an already-established channel from a pre-readiness connection failure. `last_transport_error_reason` is a strict privacy-safe allowlist (`connection_reset`, `connection_timeout`, `network_unreachable`, bounded DNS/TLS classes, or `unknown`) rather than the raw operating-system message. The signed HTTPS fallback retains its last error class/reason after a later successful poll while resetting the current `http_poll_failures` count, so post-recovery diagnosis can determine whether WSS and HTTPS failed through the same system-network episode. None of these fields contains a hostname, address, DNS answer, certificate, close reason, or proxy endpoint. While no daemon channel is ready, `server_info.daemon.previous_connection` retains only the last verified channel's transport, connected/last-seen/disconnected timestamps, and sanitized relay diagnostics; it excludes policy, tools, account identity, daemon instance/connection identity, call IDs, arguments, and results, and it never participates in routing or authorization. `outage_duration_ms` measures the close-to-ready recovery episode; `previous_ready_inbound_silence_ms` measures how long the preceding ready socket had stopped producing inbound transport proof before it actually closed. The second value is therefore the field that exposes a black-holed OPEN WebSocket whose visible reconnect later completes quickly. A VPN/TUN UI may remain “connected” while its upstream route is unusable. Local OS logs can be compared with the exact `last_disconnected_at` timestamp, but an interface-quality change or tunnel-process correlation is not by itself proof of which product, node, edge, or upstream failed. Correlated failure of an independent HTTPS client at the same timestamp—for example an external API `unexpected EOF` while the relay records WebSocket 1006/`connection_reset`—is stronger evidence of a shared system-network/VPN/TUN episode than of a Machine Bridge event-loop or resource-admission failure; it still does not identify the failing tunnel node or upstream provider. Machine Bridge reports only coarse route/proxy classes and never sends or logs interface names, addresses, DNS answers, proxy credentials, or Worker secrets.
|
|
97
97
|
|
|
98
|
-
Brief retryable outages recover automatically. On a verified current daemon channel, `server_info.daemon.relay_transport.outage_active=false`; retained fields describe the immediately preceding transport episode rather than claiming a current outage. WebSocket remains preferred and requests a protocol-level probe after five seconds. Calling `ws.ping()` only queues the control frame; it is not treated as remote-probe dispatch until the WebSocket sender's write callback confirms that the Ping actually left the local send queue. The local sender has a separate thirty-second bounded dispatch window, while a confirmed Ping retains its full ten-second Pong deadline. This deliberately prevents compression/backpressure or a slow local socket queue from spending the remote-response budget before any probe was transmitted. A protocol Pong that arrives while a Ping callback is still pending records bidirectional proof for that dispatch round; if the local write callback then completes inside the thirty-second dispatch budget, it does not arm a stale future Pong deadline. Unrelated application inbound is receive-side evidence only and cannot prove the daemon-to-Worker direction. A local queue whose Ping write callback still has not completed after thirty seconds is classified as `relay_transport_send_timeout` even if unrelated inbound traffic continues. One dispatched Ping that reaches the ten-second response deadline does not hard-kill an otherwise ready WSS: the relay runtime enters a fifteen-second `transport_confirmation_pending` window, sends the existing JSON application heartbeat as an independent path check, and prewarms signed HTTPS in standby without taking ownership away from the still-ready WSS. A protocol Pong or the explicit JSON application `pong` clears suspicion and stops standby prewarm; ordinary tool/control traffic does not, because it proves only the Worker-to-daemon receive direction. Only a second-stage confirmation window that receives no application `pong` becomes `relay_transport_timeout` and terminates the WSS. This keeps a true black hole bounded while no longer amplifying a roughly ten-to-fifteen-second persistent-flow stall into an immediate reconnect storm. `heartbeat.probe_dispatch_*`, `heartbeat.transport_confirmation_*`, and bounded sender-backlog fields distinguish local send delay, first-stage response loss, successful second-stage recovery, and confirmed two-stage failure. The separate periodic JSON application heartbeat remains twenty-five seconds with a seventy-five-second application-silence timeout, begins only after verified relay readiness, and the Worker keeps a wider ninety-second WebSocket liveness fallback. This is a detection/recovery bound, not a guarantee that a degraded network can complete another WebSocket handshake inside the same interval. WebSocket connect attempts have a thirty-second outer budget so a degraded but still valid DNS/TCP/TLS/WebSocket upgrade is not misclassified by an unrealistically narrow connection cutoff. The daemon also explicitly disables client `permessage-deflate`: the relay carries bounded control/JSON traffic, while `ws` enables compression by default on clients and compression adds sender-state/CPU overhead that can queue later frames; the stability path does not need that optional negotiation. The fallback still begins independently rather than waiting thirty seconds for WSS. On first-stage WSS liveness suspicion, the same root-certified ephemeral daemon identity prewarms signed HTTPS in standby; if WSS proves live during the second-stage confirmation, that standby poller stops. If the WSS actually disconnects, fallback switches to exact-generation takeover immediately; an in-flight standby request is aborted and replaced rather than being allowed to consume up to its own request deadline before takeover can start. That in-memory session certificate intentionally has a 24-hour maximum lifetime. It is valid for ordinary reconnects during that lifetime, but it is not silently extended: if a later WSS reconnect/authentication attempt discovers that the daemon session has expired, the daemon terminates with `relay_device_session_expired` instead of retrying forever with unusable credentials. Installed launchd/systemd/Windows service supervision restarts the failed daemon and obtains a fresh root-signed session after normal runtime cleanup; the default portable root does this without user interaction. A manually run daemon must be restarted by its operator, and a configured Secure Enclave root retains its existing user-presence requirement when the new daemon start signs the replacement session. Ordinary standby/ready fallback requests retain the seven-second deadline; exact-generation takeover requests use a three-second deadline until verified readiness. The ordinary one-second ready poll cadence, five-second standby-prewarm cadence, bounded one/two/four/five-second retry backoff, 750 ms hard minimum request-start interval, and twelve-second liveness window are unchanged. A new daemon-backed call waits at most fifteen seconds for some verified daemon channel, and the measured wait is deducted from that call's original execution budget; the takeover budget is deliberately small enough for one fully timed-out stale request, the first retry backoff, and two further takeover exchanges to fit inside that ceiling. After an established WSS disappears, the daemon explicitly marks its signed HTTP request as a takeover of the Worker-issued `connection_id` for that exact disconnected WebSocket generation. Once candidate preconditions pass, HTTPS may retire only that targeted same-instance zombie WSS that the Worker has not yet observed closing. If a newer same-instance WSS is already ready before the HTTP request arrives, the old generation no longer matches and the stale takeover remains standby instead of retiring the recovered socket. A takeover request without the exact Worker-issued WebSocket connection ID is invalid rather than being treated as an instance-only legacy takeover. Malformed, stale, wrongly targeted, or different-instance requests cannot preempt a healthy incumbent. During replacement, the daemon reconciles `resume_calls`, processes `ready_ack`, proves local readiness, and only then returns `resume_calls_ack.missing_ids`. A missing ID therefore proves both that the same daemon has no active/unacknowledged-result ownership for that call and that the replacement channel is ready. If the initiating MCP response is still open and at least one second remains in the original execution budget, the Worker may transparently retransmit exactly that same call ID, arguments, authority, and a reduced timeout. `read_job` is stricter: redelivery requires the full ten-second reconciliation headroom to remain, otherwise the Worker declines redelivery and returns retryable recovery failure rather than rewriting the call into an under-budget immediate read. If safe redelivery cannot be accepted, the call falls back to retryable `unavailable` with `side_effects_started=false`. Calls that may have executed, retained terminal results, different-daemon calls, and ambiguous mutations are never automatically replayed. Completed relay results that are still waiting for Worker acknowledgement remain bounded in daemon memory and consume the same recovery-ownership capacity as active calls: 16 total with two control-plane slots reserved for `diagnose_runtime`/`list_roots`. When ordinary recovery ownership reaches 14, another ordinary relay call is rejected before execution with retryable `limit_exceeded` and `side_effects_started=false`; the two reserved diagnostic/recovery calls remain available until total capacity reaches 16. The retained-result implementation also keeps one non-admission emergency ownership slot solely for a violated internal capacity invariant: if an already-executed result reaches retention after the normal 16-entry ceiling is unexpectedly full, that one result remains retained for acknowledgement/reconnect ownership instead of being sent unowned and later misclassified as safe to redeliver. Use of that slot emits an error-level capacity event and may make diagnostics temporarily report ownership above the normal maximum; a second such overflow is not sent. This slot is not usable admission capacity and must never be counted to raise the 16-call execution ceiling. An acknowledgement that is permanently lost cannot pin a result forever: first retention is monotonic and the result expires after the 315-second maximum Worker settlement lifetime on the next live relay heartbeat; the disconnected path still uses the shorter reconnect-grace cleanup. `diagnose_runtime.runtime.relay_result_recovery` exposes only aggregate `active_calls`, `retained_results`, active ownership, and capacity counts—never call IDs, tool arguments, or results. Compare outage intervals with sleep/wake records and `diagnose_runtime.runtime.relay.heartbeat` before classifying them as active network faults; local stdio `server_info.runtime.relay.heartbeat` exposes the same state. On macOS, an authorized, schema-valid remote tool call also enters a bounded idle-sleep guard with a fixed thirty-minute rolling inactivity grace; the service does not depend on shell-only environment overrides that launchd would not persist as configuration, and relay heartbeats do not count as user activity. Authorized relay handlers hold one shared `/usr/bin/caffeinate -i -s -w <daemon-pid>` assertion for their full execution lifetime; `-s` strengthens system-sleep prevention only on AC power while `-i` remains the baseline Idle Sleep request; concurrent handlers share the child, the thirty-minute default inactivity grace begins only after the last one settles, and a new authorized handler cancels any pending release timer so the full grace restarts after that activity settles. A remote `start_process` extends the same assertion only after resource admission succeeds and keeps it until the session child settles, so a long process session is not reduced to the handler grace window. Remote account managed-job runners independently hold `/usr/bin/caffeinate -i -s -w <runner-pid>` after their ownership claim is confirmed and persisted ownership identifies an account-backed job, then retain it through admission, steps, cleanup, and terminal persistence; local managed jobs do not acquire this remote-continuity assertion, and daemon reconnect/replacement does not own the remote runner protection. `diagnose_runtime.runtime.idle_sleep_guard` reports only daemon-side supported/enabled/active/grace/error-class state plus whether the fixed child requests the AC-only system-sleep assertion; it intentionally does not enumerate process-session or job identities. Runtime shutdown terminates process sessions before releasing the daemon guard. None of these assertions claim to prevent explicit sleep or lid-close sleep. A nonzero `event_loop_stall_count` with a large `max_event_loop_lag_ms` means the local daemon was not scheduled promptly; a short stall enters recovery grace, sends a fresh transport probe, and deliberately postpones disconnect. A large stall that aligns with `pmset` Sleep/Wake is suspension evidence. Independently, `relay_outage_analysis` can show either that a close-to-ready interval was itself dominated by system sleep or that the socket failure was first observable at the wake boundary while the same sleep matches the runtime pause; a genuinely awake outage without either evidence remains real transport evidence without identifying which network/host layer caused it. A large `previous_ready_inbound_silence_ms` without a matching local stall remains useful pre-close half-open evidence. Use `--verbose` only when close codes, liveness deadlines, and retry delays are required.
|
|
98
|
+
Brief retryable outages recover automatically. On a verified current daemon channel, `server_info.daemon.relay_transport.outage_active=false`; retained fields describe the immediately preceding transport episode rather than claiming a current outage. WebSocket remains preferred and requests a protocol-level probe after five seconds. Calling `ws.ping()` only queues the control frame; it is not treated as remote-probe dispatch until the WebSocket sender's write callback confirms that the Ping actually left the local send queue. The local sender has a separate thirty-second bounded dispatch window, while a confirmed Ping retains its full ten-second Pong deadline. This deliberately prevents compression/backpressure or a slow local socket queue from spending the remote-response budget before any probe was transmitted. A protocol Pong that arrives while a Ping callback is still pending records bidirectional proof for that dispatch round; if the local write callback then completes inside the thirty-second dispatch budget, it does not arm a stale future Pong deadline. Unrelated application inbound is receive-side evidence only and cannot prove the daemon-to-Worker direction. A local queue whose Ping write callback still has not completed after thirty seconds is classified as `relay_transport_send_timeout` even if unrelated inbound traffic continues. One dispatched Ping that reaches the ten-second response deadline does not hard-kill an otherwise ready WSS: the relay runtime enters a fifteen-second `transport_confirmation_pending` window, sends the existing JSON application heartbeat as an independent path check, and prewarms signed HTTPS in standby without taking ownership away from the still-ready WSS. A protocol Pong or the explicit JSON application `pong` clears suspicion and stops standby prewarm; ordinary tool/control traffic does not, because it proves only the Worker-to-daemon receive direction. Only a second-stage confirmation window that receives no application `pong` becomes `relay_transport_timeout` and terminates the WSS. This keeps a true black hole bounded while no longer amplifying a roughly ten-to-fifteen-second persistent-flow stall into an immediate reconnect storm. `heartbeat.probe_dispatch_*`, `heartbeat.transport_confirmation_*`, and bounded sender-backlog fields distinguish local send delay, first-stage response loss, successful second-stage recovery, and confirmed two-stage failure. The separate periodic JSON application heartbeat remains twenty-five seconds with a seventy-five-second application-silence timeout, begins only after verified relay readiness, and the Worker keeps a wider ninety-second WebSocket liveness fallback. This is a detection/recovery bound, not a guarantee that a degraded network can complete another WebSocket handshake inside the same interval. WebSocket connect attempts have a thirty-second outer budget so a degraded but still valid DNS/TCP/TLS/WebSocket upgrade is not misclassified by an unrealistically narrow connection cutoff. The daemon also explicitly disables client `permessage-deflate`: the relay carries bounded control/JSON traffic, while `ws` enables compression by default on clients and compression adds sender-state/CPU overhead that can queue later frames; the stability path does not need that optional negotiation. The fallback still begins independently rather than waiting thirty seconds for WSS. On first-stage WSS liveness suspicion, the same root-certified ephemeral daemon identity prewarms signed HTTPS in standby; if WSS proves live during the second-stage confirmation, that standby poller stops. If the WSS actually disconnects, fallback switches to exact-generation takeover immediately; an in-flight standby request is aborted and replaced rather than being allowed to consume up to its own request deadline before takeover can start. That in-memory session certificate intentionally has a 24-hour maximum lifetime. For the default portable JWK root, the runtime derives a fresh root-certified ephemeral session ten minutes before expiry, swaps the shared WSS/HTTPS authentication provider, and reconnects the same daemon instance so existing reconnect reconciliation can resume in-flight relay calls without `runtime.stop()`. If the machine was suspended across the renewal timer, the next authentication boundary synchronously renews before signing instead of first attempting an expired certificate. `server_info.runtime.trust.daemon_session` exposes only coarse renewal state such as `automatic_renewal`, `session_generation`, `expires_at`, `renewal_due_at`, and failure class/count; it never exposes session keys or certificate material. A Secure Enclave root does not receive unattended renewal because signing may require user presence. If unattended renewal is unavailable or repeatedly fails until the session is truly expired, the existing fail-closed path still terminates with `relay_device_session_expired` rather than retrying forever with unusable credentials; installed launchd/systemd/Windows supervision may then restart the daemon and obtain a new root-signed session, while a manually run daemon requires operator restart. Ordinary standby/ready fallback requests retain the seven-second deadline; exact-generation takeover requests use a three-second deadline until verified readiness. The ordinary one-second ready poll cadence, five-second standby-prewarm cadence, bounded one/two/four/five-second retry backoff, 750 ms hard minimum request-start interval, and twelve-second liveness window are unchanged. A new daemon-backed call waits at most fifteen seconds for some verified daemon channel, and the measured wait is deducted from that call's original execution budget; the takeover budget is deliberately small enough for one fully timed-out stale request, the first retry backoff, and two further takeover exchanges to fit inside that ceiling. After an established WSS disappears, the daemon explicitly marks its signed HTTP request as a takeover of the Worker-issued `connection_id` for that exact disconnected WebSocket generation. Once candidate preconditions pass, HTTPS may retire only that targeted same-instance zombie WSS that the Worker has not yet observed closing. If a newer same-instance WSS is already ready before the HTTP request arrives, the old generation no longer matches and the stale takeover remains standby instead of retiring the recovered socket. A takeover request without the exact Worker-issued WebSocket connection ID is invalid rather than being treated as an instance-only legacy takeover. Malformed, stale, wrongly targeted, or different-instance requests cannot preempt a healthy incumbent. During replacement, the daemon reconciles `resume_calls`, processes `ready_ack`, proves local readiness, and only then returns `resume_calls_ack.missing_ids`. A missing ID therefore proves both that the same daemon has no active/unacknowledged-result ownership for that call and that the replacement channel is ready. If the initiating MCP response is still open and at least one second remains in the original execution budget, the Worker may transparently retransmit exactly that same call ID, arguments, authority, and a reduced timeout. `read_job` is stricter: redelivery requires the full ten-second reconciliation headroom to remain, otherwise the Worker declines redelivery and returns retryable recovery failure rather than rewriting the call into an under-budget immediate read. If safe redelivery cannot be accepted, the call falls back to retryable `unavailable` with `side_effects_started=false`. Calls that may have executed, retained terminal results, different-daemon calls, and ambiguous mutations are never automatically replayed. Completed relay results that are still waiting for Worker acknowledgement remain bounded in daemon memory and consume the same recovery-ownership capacity as active calls: 16 total with two control-plane slots reserved for `diagnose_runtime`/`list_roots`. When ordinary recovery ownership reaches 14, another ordinary relay call is rejected before execution with retryable `limit_exceeded` and `side_effects_started=false`; the two reserved diagnostic/recovery calls remain available until total capacity reaches 16. The retained-result implementation also keeps one non-admission emergency ownership slot solely for a violated internal capacity invariant: if an already-executed result reaches retention after the normal 16-entry ceiling is unexpectedly full, that one result remains retained for acknowledgement/reconnect ownership instead of being sent unowned and later misclassified as safe to redeliver. Use of that slot emits an error-level capacity event and may make diagnostics temporarily report ownership above the normal maximum; a second such overflow is not sent. This slot is not usable admission capacity and must never be counted to raise the 16-call execution ceiling. An acknowledgement that is permanently lost cannot pin a result forever: first retention is monotonic and the result expires after the 315-second maximum Worker settlement lifetime on the next live relay heartbeat; the disconnected path still uses the shorter reconnect-grace cleanup. `diagnose_runtime.runtime.relay_result_recovery` exposes only aggregate `active_calls`, `retained_results`, active ownership, and capacity counts—never call IDs, tool arguments, or results. Compare outage intervals with sleep/wake records and `diagnose_runtime.runtime.relay.heartbeat` before classifying them as active network faults; local stdio `server_info.runtime.relay.heartbeat` exposes the same state. On macOS, authorized relay activity is protected only after policy/account/operation authorization and argument validation succeed, and relay heartbeats do not count as user activity. The persisted idle-sleep mode is `activity` by default, `ac-continuous`, or `continuous`: `activity` retains the existing shared `/usr/bin/caffeinate -i -s -w <daemon-pid>` assertion plus fixed thirty-minute inactivity grace; `ac-continuous` adds a daemon-lifetime `-s -w <daemon-pid>` assertion while keeping that activity lease; `continuous` holds `-i -s -w <daemon-pid>` for the daemon lifetime and does not arm inactivity grace. A remote `start_process` extends activity ownership only after resource admission and through child settlement. Remote account managed-job runners independently hold the same `-i -s -w <runner-pid>` primitive after runner-claim/account-ownership validation and through admission, steps, cleanup, and terminal persistence. Every assertion retains desired-state recovery after unexpected child failure with fixed 1/5/30-second backoff; explicit release/shutdown disables recovery before terminating the child. `diagnose_runtime.runtime.idle_sleep_guard` reports only coarse mode/activity/recovery ownership and bounded unprotected-duration telemetry; it does not enumerate process-session or job identities. Runtime shutdown terminates process sessions before releasing daemon assertions. None of these modes claims to prevent explicit sleep, lid-close sleep, power loss, or operating-system behavior outside `caffeinate` contracts. A nonzero `event_loop_stall_count` with a large `max_event_loop_lag_ms` means the local daemon was not scheduled promptly; a short stall enters recovery grace, sends a fresh transport probe, and deliberately postpones disconnect. A large stall that aligns with `pmset` Sleep/Wake is suspension evidence. Independently, `relay_outage_analysis` can show either that a close-to-ready interval was itself dominated by system sleep or that the socket failure was first observable at the wake boundary while the same sleep matches the runtime pause; a genuinely awake outage without either evidence remains real transport evidence without identifying which network/host layer caused it. A large `previous_ready_inbound_silence_ms` without a matching local stall remains useful pre-close half-open evidence. Use `--verbose` only when close codes, liveness deadlines, and retry delays are required.
|
|
99
99
|
|
|
100
100
|
A foreground MCP response is not durable delivery. For an active hosted `start_job`, `ui_monitor_candidate=true` plus `ui_monitor_render_tool=render_job_monitor` and a fresh `ui_monitor_id` means the host advertised MCP Apps support, but `start_job` itself remains data-only and does not mount UI. **Job polling ownership is not task ownership.** If the current assistant still has task-local work that depends on this job's terminal result, do not render the monitor merely to shorten the response; preserve `job_id` + `recovery_key` and continue bounded server-paced `read_job` calls in the same response while calls are accepted. Render only when the current task can deliberately transfer this job's status polling without needing its terminal result for remaining same-response work. When intentionally rendering, preserve the start-result ID, call `render_job_monitor` once with the exact accepted `job_id` + `recovery_key` + `ui_monitor_id`; it verifies existing read authority, activates that pre-issued correlation ID, and mounts the Job Monitor resource. Do not depend on the render tool result being visible to the initiating planner. Immediately call `read_job` with the same `job_id`, `recovery_key`, and start-result `ui_monitor_id`. A matching current View claim on an ordinary job reports `ui_monitor_claimed=true`, `status_polling_mode=ui_monitor`, `host_turn_handoff_recommended=false`, and `follow_up_read_required=false`; that proves the View can own subsequent status polling for this job only. The sole exception is a persisted `continuation_mode=task_supervisor` whose reviewed durable plan already encapsulates all remaining noninteractive task work: a later active claimed read may report `host_turn_handoff_recommended=true`, allowing the host turn to end because the supervisor job owns the remaining execution lifecycle. Machine Bridge can prove the persisted declaration, active state, and current View claim, but not conversation-level completeness; that remains an owner/caller semantic assertion. The monitor itself remains status-only. The mounted View then uses app-only `read_job_monitor`, not the model-visible `read_job`, for its fixed 40-second status long-polls. That app-only call verifies the same principal-bound `recovery_key`, strips the recovery/monitor values before daemon dispatch, and uses a 60-second client response watchdog and may be retried by the View for at most six attempts with bounded 1/2/4/8/15/30-second backoff after a missing/transport-failed response or an explicitly retryable tool error; it never re-submits the managed job. Exhausted retries and non-retryable status errors pause the View for manual inspection. An older View claim with another monitor ID is not handoff evidence. 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
102
|
When diagnosing the Job Monitor, keep coordination health separate from managed-job execution health. `read_job_monitor` is status-only: absence of command output or step records is intentional, not truncation. A monitor-storage failure after `start_job` acceptance must not be reported as job-acceptance failure; the result should simply omit UI-monitor candidacy and continue through model-visible `read_job`. Likewise, failure to read monitor claim metadata means the model has no proof of UI ownership and should keep normal continuation, while failure to delete monitor metadata during an authority revocation does not invalidate the revocation. The View never displays raw host/runtime exception text and does not accept ambient tool-result notifications as state. If the View pauses with a generic monitor error, recover from the same durable `job_id`/`recovery_key` on the normal read path rather than resubmitting the underlying job. Operational logs for monitor-coordination degradation contain only a bounded operation label plus sanitized error class, not job output, paths, principal IDs, monitor IDs, or capabilities.
|
|
103
103
|
|
|
104
|
-
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,
|
|
104
|
+
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 primary override for deployments that need a stable egress boundary: when it is non-empty, WebSocket relay construction and, by default, the signed HTTP fallback use that exact HTTP(S) proxy instead of consulting `HTTPS_PROXY`/`HTTP_PROXY` or `NO_PROXY`. `MBM_RELAY_FALLBACK_PROXY` is an optional fallback-only override. When it is non-empty, signed HTTP uses that exact proxy independently of `MBM_RELAY_PROXY`; when the key is present with an empty value, signed HTTP returns to standard `HTTPS_PROXY`/`HTTP_PROXY`/`NO_PROXY` resolution while WebSocket continues using its primary route. A connection failure through a selected route follows normal relay recovery and never silently retries the same transport through an unconfigured direct path. Setting `MBM_RELAY_PROXY` itself explicitly to an empty value continues to restore the standard environment-proxy model for the primary route.
|
|
105
105
|
|
|
106
|
-
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
|
|
106
|
+
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. If continuity also requires an application-layer route that does not share that sidecar, configure `MBM_RELAY_FALLBACK_PROXY` separately; an empty fallback value is useful only when the standard system/env-proxy route is intentionally the desired alternate path. Machine Bridge deliberately does not implement proxy protocols beyond HTTP CONNECT and does not bind the sidecar's upstream socket itself. Therefore a remote proxy value can still be routed through an operating-system tunnel, and `system-network-stack` does not prove tunnel bypass. DNS for each explicit upstream proxy remains that proxy/sidecar's responsibility. Avoid a second system TUN solely for Machine Bridge, because competing default-route ownership recreates the same failure class at another layer.
|
|
107
107
|
|
|
108
108
|
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`.
|
|
109
109
|
|
|
@@ -193,6 +193,8 @@ If activation encounters a failure after remote preparation, compensation instal
|
|
|
193
193
|
|
|
194
194
|
Persistent candidate activation deliberately uses a stricter ownership transaction than ordinary `service install`. Installing the candidate service definition leaves its machine-global `service_owner` record `pending`; activation passes that pending identity into provider start, verifies the exact service-mode daemon and Worker version, and commits ownership only after convergence while the machine-service lock is still held. A failed initial start may reuse the same pending transaction for compatible candidate recovery, but recovery also commits only after exact convergence. If the candidate remains active without converging, activation stops the uncommitted provider; if final owner commit itself fails after readiness, activation stops the verified provider and retains the pending owner for explicit recovery. It never rewrites the owner to the old committed version after the provider definition may already have changed, because that would create an old-owner/new-definition mismatch.
|
|
195
195
|
|
|
196
|
+
Before persistent candidate activation crosses any provider, daemon, Worker, runtime, or service-definition mutation boundary, it runs a mandatory read-only restartability preflight while the transaction locks are held. The preflight binds the candidate entrypoint to the exact Machine Bridge package name and version, validates the persisted service-network environment against the candidate's supported schema/keys, and constructs the target platform service specification/definition in memory. It does not create service directories, logs, owner records, environment files, or platform definitions. If the preflight fails—including on a persisted environment key the candidate does not understand—activation releases its transaction locks and leaves the running provider untouched; daemon takeover, Worker preparation/repair, candidate runtime creation, service installation, and recovery start are not attempted.
|
|
197
|
+
|
|
196
198
|
Stopping the previous service is itself a mutation with an ambiguous-settlement boundary. On macOS, `launchctl bootout` may return or become observable before launchd has fully removed the service. The stop helper therefore watches for a safely unloaded provider for up to roughly ten seconds. Once bootout has been dispatched against an initially active service, rollback remains required even if that observation window ends without a verified stop. Activation failure recovery does not make one best-effort `service start` call and leave: it retries the idempotent previous-provider start/bootstrap operation and verifies the exact prior service-mode daemon identity for a bounded 30 attempts. This prevents a late bootout from racing a one-shot rollback and leaving the login daemon offline. A successfully restored previous service does not convert the candidate failure into success; the original candidate error remains the terminal result, with restoration serving only to recover availability.
|
|
197
199
|
|
|
198
200
|
```sh
|
package/docs/UPGRADING.md
CHANGED
|
@@ -50,6 +50,8 @@ The tool catalog is enforced with bounded schema validation at the Worker and lo
|
|
|
50
50
|
9. Verify a safe workspace read, one ordinary edit, one representative current MCP tool call, and one owner-only action appropriate for the deployment.
|
|
51
51
|
10. Restore persistent/background service operation only after the foreground path is healthy.
|
|
52
52
|
|
|
53
|
+
Persistent activation has an additional fail-closed transaction boundary: before it stops the existing provider or takes over the daemon lock, the candidate performs a read-only restartability preflight against its exact package entry/version, the persisted service-network environment, and the platform service definition it would install. An unsupported persisted service-environment key or invalid candidate package identity fails the command before production service mutation. Preserve the existing service and state for diagnosis; do not hand-edit `service-environment.json`, delete service ownership state, or force activation past this check.
|
|
54
|
+
|
|
53
55
|
ChatGPT host-control-plane UI is not a separate conversational authorization boundary. Existing MCP discovery and harmless invocation-validator probes remain the preferred first-line evidence, but automation may inspect and operate the supported Apps/Plugins/admin refresh/review flow when a governed Action control snapshot is genuinely needed for upgrade diagnosis or publication verification. Prefer in-place refresh/review before recreation or republication, verify settlement after every mutation, and never replay an unknown-outcome UI mutation blindly. Host-internal cache inspection is outside the upgrade workflow.
|
|
54
56
|
|
|
55
57
|
When `MBM_MACOS_TRUST_BROKER` is intentionally configured, verify that provisioned broker before activation. Do not replace failed trust material with an ad-hoc helper or hand-edit the device-root state.
|
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.173",
|
|
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",
|
|
@@ -304,7 +304,7 @@
|
|
|
304
304
|
"allowScripts": {
|
|
305
305
|
"esbuild@0.28.1": true,
|
|
306
306
|
"fsevents": false,
|
|
307
|
-
"sharp@0.35.
|
|
307
|
+
"sharp@0.35.4": true,
|
|
308
308
|
"workerd@1.20260828.1": true
|
|
309
309
|
},
|
|
310
310
|
"packageManager": "npm@12.0.2",
|
|
@@ -321,7 +321,7 @@
|
|
|
321
321
|
}
|
|
322
322
|
},
|
|
323
323
|
"overrides": {
|
|
324
|
-
"sharp": "0.35.
|
|
324
|
+
"sharp": "0.35.4",
|
|
325
325
|
"brace-expansion": "5.0.9",
|
|
326
326
|
"undici": "7.29.0"
|
|
327
327
|
}
|
|
@@ -154,7 +154,7 @@ export function validateConsumerTree(tree, options = {}) {
|
|
|
154
154
|
if (item.name === "undici" && vulnerableUndici(item.version)) {
|
|
155
155
|
throw new Error(`consumer dependency tree contains vulnerable undici ${item.version}`);
|
|
156
156
|
}
|
|
157
|
-
if (item.name === "sharp" && compareNumericVersion(item.version, "0.35.
|
|
157
|
+
if (item.name === "sharp" && compareNumericVersion(item.version, "0.35.4") < 0) {
|
|
158
158
|
throw new Error(`consumer dependency tree contains unsupported sharp ${item.version}`);
|
|
159
159
|
}
|
|
160
160
|
}
|
|
@@ -3,6 +3,7 @@ import { effectiveLogFormat, effectiveLogLevel } from "./cli-options.mjs";
|
|
|
3
3
|
import { createLogger } from "./log.mjs";
|
|
4
4
|
import { activatePersistentRuntime } from "./runtime-activation.mjs";
|
|
5
5
|
import { autostartStatus, installAutostart, startAutostart, stopAutostart } from "./service.mjs";
|
|
6
|
+
import { preflightServiceRestartability } from "./service-restartability.mjs";
|
|
6
7
|
import { startOwnedServiceRuntime } from "./service-runtime.mjs";
|
|
7
8
|
import { acquireMachineServiceLockWithWait, acquireStartupLockWithWait, daemonLockPathForState, loadState, readDaemonLockOwner } from "./state.mjs";
|
|
8
9
|
import { workerHealth } from "./worker-health.mjs";
|
|
@@ -65,6 +66,9 @@ export function createActivateCommand({
|
|
|
65
66
|
previousRuntime: owner ? { version: owner.version, entryScript: owner.entryScript } : null,
|
|
66
67
|
};
|
|
67
68
|
},
|
|
69
|
+
preflightRestartability: () => preflightServiceRestartability({
|
|
70
|
+
workspace, stateRoot: state.paths.stateRoot, entryScript: process.argv[1], expectedVersion,
|
|
71
|
+
}),
|
|
68
72
|
stopAutostart: () => stopAutostart({ logger: structuredLogger(true) }),
|
|
69
73
|
acquireDaemonLock: () => acquireDaemonLockWithTakeover(state, {
|
|
70
74
|
takeOverServiceOwner: true,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { configuredIdleSleepMode, defaultStateRoot, expandHome, setConfiguredIdleSleepMode } from "./state.mjs";
|
|
2
|
+
|
|
3
|
+
export function idleSleepCommand(args = {}) {
|
|
4
|
+
const action = String(args._?.[0] || "show");
|
|
5
|
+
const stateRoot = args.stateDir ? expandHome(String(args.stateDir)) : defaultStateRoot();
|
|
6
|
+
if (action === "show") { console.log(configuredIdleSleepMode(stateRoot)); return; }
|
|
7
|
+
if (action === "set") {
|
|
8
|
+
const mode = args._?.[1];
|
|
9
|
+
if (!mode) throw new Error("idle-sleep set requires activity, ac-continuous, or continuous");
|
|
10
|
+
const configured = setConfiguredIdleSleepMode(mode, stateRoot);
|
|
11
|
+
console.log(`Idle-sleep mode set to ${configured}. Restart the daemon/service to apply it.`);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
throw new Error(`Unknown idle-sleep action: ${action}`);
|
|
15
|
+
}
|
|
@@ -28,6 +28,7 @@ const COMMAND_OPTIONS = new Map(Object.entries({
|
|
|
28
28
|
"full-test": new Set(["workspace", "stateDir", "json"]),
|
|
29
29
|
"rotate-secrets": new Set(["workspace", "stateDir", "quiet"]),
|
|
30
30
|
workspace: new Set(["workspace", "stateDir"]),
|
|
31
|
+
"idle-sleep": new Set(["stateDir"]),
|
|
31
32
|
service: new Set(["workspace", "stateDir", "quiet"]),
|
|
32
33
|
autostart: new Set(["workspace", "stateDir", "quiet"]),
|
|
33
34
|
resource: new Set(["workspace", "stateDir", "allowInsecurePermissions", "showPaths", "json"]),
|
|
@@ -50,6 +51,10 @@ const ACTION_POSITIONAL_RULES = new Map(Object.entries({
|
|
|
50
51
|
const action = String(args._[0] || "show");
|
|
51
52
|
return { max: action === "set" || action === "select" ? 2 : 1, tooMany: `workspace ${action} received too many positional arguments`, workspaceConflictAfter: 1 };
|
|
52
53
|
},
|
|
54
|
+
"idle-sleep"(args) {
|
|
55
|
+
const action = String(args._[0] || "show");
|
|
56
|
+
return { max: action === "set" ? 2 : 1, tooMany: `idle-sleep ${action} received too many positional arguments` };
|
|
57
|
+
},
|
|
53
58
|
service(args) {
|
|
54
59
|
const action = String(args._[0] || "status");
|
|
55
60
|
return { max: ["install", "status", "stop"].includes(action) ? 2 : 1, tooMany: `service ${action} received too many positional arguments`, workspaceConflictAfter: 1 };
|
package/src/local/cli.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import { effectiveLogFormat, effectiveLogLevel, normalizeCommand, parseArgs, val
|
|
|
11
11
|
import { createLocalAdminCommands } from "./cli-local-admin.mjs";
|
|
12
12
|
import { createServiceCommand } from "./cli-service.mjs";
|
|
13
13
|
import { createActivateCommand } from "./cli-activate.mjs";
|
|
14
|
+
import { idleSleepCommand } from "./cli-idle-sleep.mjs";
|
|
14
15
|
import { generateAccountPassword } from "./account-admin.mjs";
|
|
15
16
|
import { accountAdminClient, createAccountCommand } from "./cli-account-admin.mjs";
|
|
16
17
|
export { resolvePolicy } from "./cli-policy.mjs";
|
|
@@ -22,7 +23,7 @@ import { runFullAccessTest } from "./full-access-test.mjs";
|
|
|
22
23
|
import { stopAndRemoveAutostart } from "./service-lifecycle.mjs";
|
|
23
24
|
import { stopOwnedPlatformService } from "./service-ownership.mjs";
|
|
24
25
|
import { loadServiceEnvironment } from "./service-environment.mjs";
|
|
25
|
-
import { createDeviceSessionForRoot, deviceRootProviderStatus, ensurePreferredDeviceRoot } from "./device-root-provider.mjs";
|
|
26
|
+
import { createDeviceSessionForRoot, createUnattendedDeviceSessionFactory, deviceRootProviderStatus, ensurePreferredDeviceRoot } from "./device-root-provider.mjs";
|
|
26
27
|
import { convergeRemoteConfiguration } from "./remote-configuration.mjs";
|
|
27
28
|
import { workerHealth } from "./worker-health.mjs";
|
|
28
29
|
import { DOCTOR_RUNTIME_SCOPE, doctorRuntimeCheckProjection } from "./doctor-reporting.mjs";
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
acquireMachineServiceLockWithWait,
|
|
36
37
|
acquireMaintenanceLock,
|
|
37
38
|
acquireStartupLockWithWait,
|
|
39
|
+
configuredIdleSleepMode,
|
|
38
40
|
daemonLockPathForState,
|
|
39
41
|
defaultFirstRunWorkspace,
|
|
40
42
|
defaultStateRoot,
|
|
@@ -54,7 +56,6 @@ import {
|
|
|
54
56
|
setSelectedWorkspace,
|
|
55
57
|
} from "./state.mjs";
|
|
56
58
|
import { packageName, packageVersion } from "./package-identity.mjs";
|
|
57
|
-
|
|
58
59
|
const localAdminCommands = createLocalAdminCommands({ chooseWorkspace, confirm });
|
|
59
60
|
const accountCommand = createAccountCommand({ chooseWorkspace, confirm });
|
|
60
61
|
const serviceCommand = createServiceCommand({ chooseWorkspace, stateRootFromArgs, structuredLogger, acquireMachineServiceLockWithWait, currentPackageVersion });
|
|
@@ -76,6 +77,7 @@ const COMMAND_HANDLERS = new Map([
|
|
|
76
77
|
["doctor", doctorCommand],
|
|
77
78
|
["full-test", fullTestCommand],
|
|
78
79
|
["workspace", workspaceCommand],
|
|
80
|
+
["idle-sleep", idleSleepCommand],
|
|
79
81
|
["service", serviceCommand],
|
|
80
82
|
["autostart", serviceCommand],
|
|
81
83
|
["rotate-secrets", rotateSecretsCommand],
|
|
@@ -307,7 +309,8 @@ async function startRemoteRuntime({ args, workspace, state, daemonLock, logger,
|
|
|
307
309
|
try {
|
|
308
310
|
const readiness = await prepareRemoteState({ args, workspace, state, logger,
|
|
309
311
|
ensureWorkerDeployment: dependencies.ensureWorkerDeployment });
|
|
310
|
-
runtime = createRemoteRuntime({ args, workspace, state, daemonLock,
|
|
312
|
+
runtime = createRemoteRuntime({ args, workspace, state, daemonLock,
|
|
313
|
+
deviceSessionIdentity: readiness.deviceSessionIdentity, renewDeviceSession: readiness.renewDeviceSession });
|
|
311
314
|
await runtime.start();
|
|
312
315
|
if (typeof daemonLock.update !== "function") throw new Error("daemon lock cannot publish startup readiness");
|
|
313
316
|
daemonLock.update({ startupReady: true, startupReadyAt: new Date().toISOString() });
|
|
@@ -356,17 +359,16 @@ async function prepareRemoteState({ args, workspace, state, logger, onRemotePrep
|
|
|
356
359
|
currentPackageVersion(),
|
|
357
360
|
{ profileDir: state.paths.profileDir, reason: "Authorize Machine Bridge startup" },
|
|
358
361
|
);
|
|
362
|
+
const renewDeviceSession = createUnattendedDeviceSessionFactory(state.worker.deviceIdentity, state.worker.url, "machine-bridge-mcp", currentPackageVersion());
|
|
359
363
|
const initialOwner = args.daemonOnly || provisionInitialOwner === false
|
|
360
364
|
? null
|
|
361
365
|
: await ensureInitialOwnerAccount(state, deviceSessionIdentity);
|
|
362
366
|
if (!args.daemonOnly && !args.noAutostart) {
|
|
363
367
|
await installAutostartBestEffort({ workspace, stateRoot: state.paths.stateRoot, entryScript: process.argv[1], logger });
|
|
364
368
|
}
|
|
365
|
-
return { initialOwner, deviceSessionIdentity };
|
|
369
|
+
return { initialOwner, deviceSessionIdentity, renewDeviceSession };
|
|
366
370
|
}
|
|
367
371
|
|
|
368
|
-
|
|
369
|
-
|
|
370
372
|
async function ensureInitialOwnerAccount(state, deviceSessionIdentity) {
|
|
371
373
|
const client = await accountAdminClient(state, deviceSessionIdentity);
|
|
372
374
|
const existing = await client.list();
|
|
@@ -376,11 +378,11 @@ async function ensureInitialOwnerAccount(state, deviceSessionIdentity) {
|
|
|
376
378
|
return { ...created.account, password };
|
|
377
379
|
}
|
|
378
380
|
|
|
379
|
-
function createRemoteRuntime({ args, workspace, state, daemonLock, deviceSessionIdentity, exitOnTerminal = true }) {
|
|
381
|
+
function createRemoteRuntime({ args, workspace, state, daemonLock, deviceSessionIdentity, renewDeviceSession = null, exitOnTerminal = true }) {
|
|
380
382
|
const terminalState = { error: null };
|
|
381
383
|
const runtime = new LocalRuntime({
|
|
382
384
|
workerUrl: state.worker.url,
|
|
383
|
-
deviceIdentity: deviceSessionIdentity,
|
|
385
|
+
deviceIdentity: deviceSessionIdentity, renewDeviceSession,
|
|
384
386
|
expectedRelayVersion: currentPackageVersion(),
|
|
385
387
|
workspace,
|
|
386
388
|
policy: state.policy,
|
|
@@ -391,6 +393,7 @@ function createRemoteRuntime({ args, workspace, state, daemonLock, deviceSession
|
|
|
391
393
|
resourceStatePath: state.paths.statePath,
|
|
392
394
|
browserStateRoot: state.paths.stateRoot,
|
|
393
395
|
deviceRootStatus: deviceRootProviderStatus(state.worker.deviceIdentity),
|
|
396
|
+
idleSleepMode: configuredIdleSleepMode(state.paths.stateRoot),
|
|
394
397
|
onSuperseded: () => {
|
|
395
398
|
if (!exitOnTerminal) {
|
|
396
399
|
terminalState.error ??= new Error("candidate daemon was superseded before service handoff");
|
|
@@ -833,6 +836,7 @@ Commands:
|
|
|
833
836
|
client-config Print stdio client configuration snippets
|
|
834
837
|
workspace show Show remembered workspace
|
|
835
838
|
workspace set Re-select workspace; prompts with current/default path
|
|
839
|
+
idle-sleep show|set MODE Show/set activity, ac-continuous, or continuous; restart after set
|
|
836
840
|
service status Show autostart status
|
|
837
841
|
service install Install login autostart for remembered/current workspace
|
|
838
842
|
service start Ensure the installed autostart service is running (idempotent)
|
|
@@ -17,6 +17,8 @@ export class DaemonHttpRelayConnection {
|
|
|
17
17
|
this.workerUrl = String(options.workerUrl || "").replace(/\/$/, "");
|
|
18
18
|
this.endpoint = `${this.workerUrl}/daemon/http`;
|
|
19
19
|
this.deviceIdentity = options.deviceIdentity;
|
|
20
|
+
this.deviceIdentityProvider = typeof options.deviceIdentityProvider === "function"
|
|
21
|
+
? options.deviceIdentityProvider : () => this.deviceIdentity;
|
|
20
22
|
this.expectedServer = String(options.expectedServer || "");
|
|
21
23
|
this.expectedVersion = String(options.expectedVersion || "");
|
|
22
24
|
this.instanceId = String(options.instanceId || "");
|
|
@@ -196,7 +198,7 @@ export class DaemonHttpRelayConnection {
|
|
|
196
198
|
this.inFlight = controller;
|
|
197
199
|
try {
|
|
198
200
|
const headers = createDaemonHttpRelayHeaders(
|
|
199
|
-
this.
|
|
201
|
+
this.deviceIdentityProvider(), this.workerUrl, this.expectedServer, this.expectedVersion, body, this.wallNow(),
|
|
200
202
|
);
|
|
201
203
|
const response = await this.postRequest({
|
|
202
204
|
url: this.endpoint, headers, body,
|
|
@@ -55,6 +55,12 @@ export async function createDeviceSessionForRoot(identity, workerOrigin, server,
|
|
|
55
55
|
return finalizeDeviceSessionIdentity(draft, signature, now);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
export function createUnattendedDeviceSessionFactory(identity, workerOrigin, server, version) {
|
|
59
|
+
if (isMacosSecureDeviceRoot(identity)) return null;
|
|
60
|
+
const root = validateDeviceIdentity(identity);
|
|
61
|
+
return (now = Date.now()) => createDeviceSessionIdentity(root, workerOrigin, server, version, now);
|
|
62
|
+
}
|
|
63
|
+
|
|
58
64
|
export function deviceRootProviderStatus(identity, { env = process.env } = {}) {
|
|
59
65
|
if (isMacosSecureDeviceRoot(identity)) {
|
|
60
66
|
return {
|