machine-bridge-mcp 3.0.0-beta.173 → 3.0.0-beta.174
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 +8 -9
- package/README.md +1 -1
- package/SECURITY.md +1 -1
- package/browser-extension/manifest.json +1 -1
- package/docs/AUDIT.md +2 -1
- package/docs/OPERATIONS.md +1 -1
- package/docs/PRIVACY.md +1 -1
- package/package.json +1 -1
- package/src/shared/relay-contract.json +1 -0
- package/src/worker/daemon-recovery-budget.ts +9 -1
- package/src/worker/index.ts +1 -1
- package/src/worker/pending-call-contract.ts +1 -0
- package/src/worker/pending-calls.ts +9 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 3.0.0-beta.
|
|
4
|
-
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
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.
|
|
3
|
+
## 3.0.0-beta.174 - 2026-09-09
|
|
4
|
+
|
|
5
|
+
- Harden brief same-daemon relay interruptions after a live beta.173 incident showed WebSocket close `1006` on the application-proxy route and one reconnect attempt returning HTTP `502`, while local daemon, filesystem/process, resource-admission, event-loop, and sleep evidence remained healthy. The evidence locates the failure at the relay/proxy transport boundary but does not identify a specific upstream provider.
|
|
6
|
+
- Preserve an in-memory pending result owner for up to 15 additional seconds after its original Worker settlement deadline when the same daemon reconnects, capped by the tool's existing maximum settlement lifetime. This delivery-only grace lets an already-executed terminal result settle the original request instead of turning a short transport outage into a user-visible timeout.
|
|
7
|
+
- Keep execution authority separate from result delivery: transparent redelivery after `resume_calls_ack.missing_ids` still uses the original daemon execution deadline, so the longer settlement owner cannot authorize a new execution, duplicate a side effect, or create client-visible MCP replay state. Repeated same-instance handovers cannot cumulatively extend the absolute delivery deadline.
|
|
8
|
+
- Add deterministic coverage for late terminal-result delivery, reconnect-expiry classification, repeated handover, the existing 50-second ordinary settlement ceiling, and the negative case where the original execution budget has expired but the result owner is still intentionally retained.
|
|
9
|
+
- Keep relay recovery observability content-free: normal logs and public diagnostics do not gain call IDs, arguments, results, credentials, proxy endpoints, raw socket errors, or personal paths from this recovery path.
|
|
10
|
+
- Advance package, Worker, and browser-extension identity to `3.0.0-beta.174`; hosted tool schema generation remains 27 because no MCP tool argument/result contract changes in this release.
|
|
12
11
|
|
|
13
12
|
## Historical releases
|
|
14
13
|
|
package/README.md
CHANGED
|
@@ -174,7 +174,7 @@ The shared source of truth is `src/shared/policy-contract.json`. The generated m
|
|
|
174
174
|
|
|
175
175
|
For routine remote health checks, prefer `server_info` with `detail: "summary"`; the empty/default call remains full diagnostics and, for owner/full callers, includes a privacy-bounded durable continuity summary that survives Worker isolate replacement without retaining identities, call IDs, tool arguments, results, endpoints, or close reasons. For routine workspace inventory, `project_overview` also accepts `detail: "summary"`; it preserves policy/tool counts and top-level names/types without repeating exact tool arrays, account identity, routing fingerprints, or per-entry paths/sizes. Its empty/default call likewise remains full for compatibility. For remote calls, `server_info.authorization.effective_policy` and, when exact membership is needed, the full projection's `effective_tools` are authoritative. Daemon policy and tools describe only the local capability ceiling before account-role and host-side filtering.
|
|
176
176
|
|
|
177
|
-
`tools/list` is the authenticated account's current discovery catalog. Discovery instructions and tool descriptions carry execution/orchestration semantics, so both `server/discover` and `tools/list` advertise `ttlMs=0`. Current MCP 2026-07-28 remote discovery also advertises `tools.listChanged=true`: a client that opts into `toolsListChanged` through `subscriptions/listen` receives a correlated acknowledgement and level-trigger `notifications/tools/list_changed` event, then re-fetches `tools/list`. The request-scoped subscription remains open until explicit cancellation or the advertised bounded server lease expires; the lease is a fail-safe for HTTP disconnects that the Worker runtime cannot reliably observe and does not replace the initial level-trigger/refetch contract. Every host-visible tool description carries `Tool schema generation N`; `server_info.tool_delivery` exposes the current `tool_schema_generation`, `tool_schema_server_version`, and `tool_list_ttl_ms`, while explicitly reporting that Machine Bridge cannot observe which schema generation an external host has actually cached. A generation change therefore requires the subscription/refetch path or another host-side schema refresh plus post-activation verification. Discovery is not authority: every `tools/call` is still intersected with the current end-to-end-ready daemon policy and tool ceiling, and fails retryably with `unavailable` when no daemon is ready. `server_info.tool_delivery` also distinguishes the advertised catalog from the currently effective daemon/account intersection. WebSocket is the preferred daemon transport: verified ready traffic resumes immediately, but reconnect attempt history resets only after five seconds of generation-stable ready uptime so a shorter ready/close flap keeps its prior exponential backoff position. It requests a protocol-level Ping after five seconds and gives an actually dispatched Ping its full ten-second Pong deadline, then uses one independent fifteen-second application-confirmation window before a ready WSS may be terminated as a transport black hole. A protocol Pong or explicit application `pong` during that second stage preserves WSS; ordinary tool/control inbound remains receive-side evidence and cannot clear transport suspicion, while local event-loop stalls cancel remote suspicion and use the separate recovery-grace path. The periodic application heartbeat remains twenty-five/seventy-five seconds after end-to-end readiness, and the Worker keeps a wider ninety-second fallback. WSS connect attempts have a thirty-second outer budget. Signed HTTPS is independent of that budget: on first-stage WSS suspicion the same root-certified ephemeral device identity prewarms HTTPS in standby, and a real WSS loss promotes that path to exact-generation takeover while aborting any obsolete standby request. Fallback requests bind the fixed route/origin/server/version, a short-lived nonce, timestamp, and exact body hash; they use a seven-second request deadline, twelve-second liveness window, one-second ordinary poll cadence, and a 750 ms minimum request-start interval. The first authenticated exchange enters probing immediately, so verified readiness requires at most two bounded exchanges rather than a separate challenge round trip. Candidate → probing → verified-ready handover prevents the Worker from dispatching until the daemon has processed `ready_ack` and returned sequenced `https_ready`; a same-instance takeover may retire a Worker-side zombie WSS only after the signed candidate preconditions pass. Both directions use bounded contiguous transport sequences, so a lost HTTP response retransmits the same transport envelope and duplicates are discarded before business handling; this does not restore MCP sessions, recovery GET, `Last-Event-ID`, or public result persistence. Same-instance `resume_calls` / `resume_calls_ack` remains authoritative for in-flight ownership. A planned daemon shutdown is a different boundary: before a ready runtime closes its relay it sends `daemon_draining`; the Worker settles calls still owned by that daemon with structured `daemon_planned_drain` recovery and acknowledges the drain. In-flight `read_job` is explicitly read-only recoverable with `recovery.mode=read_same_job` plus the original `job_id`; resume by reading that same job after reconnect. This does not transfer an executing call to a new daemon process, whose random instance identity remains intentionally distinct, and it cannot revive an external assistant turn that has already ended. The daemon sends `resume_calls_ack.missing_ids` only after replacement readiness, only for IDs absent from both its active-call set and unacknowledged-result ledger, and only while it still has fail-closed proof that missing ownership means the call did not execute locally. If a completed-but-unacknowledged result expires, `diagnose_runtime.runtime.relay_result_recovery.automatic_redelivery_safe` becomes false and missing-ID automatic redelivery is disabled rather than risking duplicate side effects. A safe proven-undelivered call may be retransmitted with the same call ID, arguments, authority, and a reduced timeout inside the original deadline; a call that may have executed is never automatically replayed. A new call may wait up to fifteen seconds for a verified daemon channel, but measured recovery time is deducted from that call's original execution budget instead of extending the hosted foreground envelope. Hosted synchronous calls otherwise retain their ordinary 20-second execution plus separate five-second Worker settlement margin; configurable browser/application tools retain 20-second ordinary defaults, compound `computer_observe` / `computer_act` retain 30-second defaults, and the explicit remote maximum remains 45 seconds. Remote `exec_command`, `run_process`, and `run_local_command` require a caller-held `idempotency_key`, commit a principal-bound one-step managed job, and remain recoverable through bounded same-response `read_job` follow-up when the current task needs terminal state. Hosted active `read_job` uses a server-side 40-second long-poll by default and returns earlier on meaningful job progress or terminal state; `wait_ms=0` requests an immediate checkpoint, while every public hosted call is capped at 60 seconds. The default remains 40 seconds: live hosted evidence carried both the default and an explicit 60-second read, while beta.151 later reproduced `mcp_network_error` on a second explicit 180-second read even though the Worker-to-daemon WebSocket remained continuously ready. Longer tasks therefore keep the same `job_id` and use another server-paced read rather than one overlong host request. This keeps long-task waiting inside Machine Bridge within the demonstrated per-call host lifetime; the 40-second interval also bounds interaction density to at most 150 reads for a synthetic unchanged 100-minute job, but that arithmetic does not prove that one assistant response can survive the aggregate duration or call count. If a real host/tool boundary ends a response, preserve the durable identifier and resume the same operation later rather than resubmitting its side effect. `start_process` remains daemon-lifetime interactive state; hosted `read_process` permits paced same-response follow-up, defaults an omitted relay `wait_ms` to the one-second blocking cap, and paces another would-block read inside the fifteen-second cooldown within that same MCP call until output/exit or the cooldown boundary. Active job/process reads do not force a user-turn handoff. Callers must not infer or preempt a host/tool deadline from elapsed wall-clock time: while calls continue to be accepted and the task still needs the result, bounded same-response follow-up may continue. Handoff is reserved for an actual observed host/tool boundary, required external input or authorization, or an explicit user checkpoint, while busy loops and status-surface substitution remain prohibited. The durable process façade preserves account/tool authority and delegated workspace sandbox rather than expanding privileges.
|
|
177
|
+
`tools/list` is the authenticated account's current discovery catalog. Discovery instructions and tool descriptions carry execution/orchestration semantics, so both `server/discover` and `tools/list` advertise `ttlMs=0`. Current MCP 2026-07-28 remote discovery also advertises `tools.listChanged=true`: a client that opts into `toolsListChanged` through `subscriptions/listen` receives a correlated acknowledgement and level-trigger `notifications/tools/list_changed` event, then re-fetches `tools/list`. The request-scoped subscription remains open until explicit cancellation or the advertised bounded server lease expires; the lease is a fail-safe for HTTP disconnects that the Worker runtime cannot reliably observe and does not replace the initial level-trigger/refetch contract. Every host-visible tool description carries `Tool schema generation N`; `server_info.tool_delivery` exposes the current `tool_schema_generation`, `tool_schema_server_version`, and `tool_list_ttl_ms`, while explicitly reporting that Machine Bridge cannot observe which schema generation an external host has actually cached. A generation change therefore requires the subscription/refetch path or another host-side schema refresh plus post-activation verification. Discovery is not authority: every `tools/call` is still intersected with the current end-to-end-ready daemon policy and tool ceiling, and fails retryably with `unavailable` when no daemon is ready. `server_info.tool_delivery` also distinguishes the advertised catalog from the currently effective daemon/account intersection. WebSocket is the preferred daemon transport: verified ready traffic resumes immediately, but reconnect attempt history resets only after five seconds of generation-stable ready uptime so a shorter ready/close flap keeps its prior exponential backoff position. It requests a protocol-level Ping after five seconds and gives an actually dispatched Ping its full ten-second Pong deadline, then uses one independent fifteen-second application-confirmation window before a ready WSS may be terminated as a transport black hole. A protocol Pong or explicit application `pong` during that second stage preserves WSS; ordinary tool/control inbound remains receive-side evidence and cannot clear transport suspicion, while local event-loop stalls cancel remote suspicion and use the separate recovery-grace path. The periodic application heartbeat remains twenty-five/seventy-five seconds after end-to-end readiness, and the Worker keeps a wider ninety-second fallback. WSS connect attempts have a thirty-second outer budget. Signed HTTPS is independent of that budget: on first-stage WSS suspicion the same root-certified ephemeral device identity prewarms HTTPS in standby, and a real WSS loss promotes that path to exact-generation takeover while aborting any obsolete standby request. Fallback requests bind the fixed route/origin/server/version, a short-lived nonce, timestamp, and exact body hash; they use a seven-second request deadline, twelve-second liveness window, one-second ordinary poll cadence, and a 750 ms minimum request-start interval. The first authenticated exchange enters probing immediately, so verified readiness requires at most two bounded exchanges rather than a separate challenge round trip. Candidate → probing → verified-ready handover prevents the Worker from dispatching until the daemon has processed `ready_ack` and returned sequenced `https_ready`; a same-instance takeover may retire a Worker-side zombie WSS only after the signed candidate preconditions pass. Both directions use bounded contiguous transport sequences, so a lost HTTP response retransmits the same transport envelope and duplicates are discarded before business handling; this does not restore MCP sessions, recovery GET, `Last-Event-ID`, or public result persistence. Same-instance `resume_calls` / `resume_calls_ack` remains authoritative for in-flight ownership. A planned daemon shutdown is a different boundary: before a ready runtime closes its relay it sends `daemon_draining`; the Worker settles calls still owned by that daemon with structured `daemon_planned_drain` recovery and acknowledges the drain. In-flight `read_job` is explicitly read-only recoverable with `recovery.mode=read_same_job` plus the original `job_id`; resume by reading that same job after reconnect. This does not transfer an executing call to a new daemon process, whose random instance identity remains intentionally distinct, and it cannot revive an external assistant turn that has already ended. The daemon sends `resume_calls_ack.missing_ids` only after replacement readiness, only for IDs absent from both its active-call set and unacknowledged-result ledger, and only while it still has fail-closed proof that missing ownership means the call did not execute locally. If a completed-but-unacknowledged result expires, `diagnose_runtime.runtime.relay_result_recovery.automatic_redelivery_safe` becomes false and missing-ID automatic redelivery is disabled rather than risking duplicate side effects. A safe proven-undelivered call may be retransmitted with the same call ID, arguments, authority, and a reduced timeout inside the original execution deadline; a call that may have executed is never automatically replayed. Separately, if that same daemon already executed a call and its terminal result is delayed by the transport break, the Worker may retain the original in-memory settlement owner for up to fifteen additional seconds beyond its original settlement deadline, capped by the tool's existing maximum settlement lifetime. That delivery-only grace cannot authorize redelivery or new execution, cannot accumulate across repeated handovers, and does not create client-visible replay or durable result state. A new call may wait up to fifteen seconds for a verified daemon channel, but measured recovery time is deducted from that call's original execution budget instead of extending the hosted foreground envelope. Hosted synchronous calls otherwise retain their ordinary 20-second execution plus separate five-second Worker settlement margin; configurable browser/application tools retain 20-second ordinary defaults, compound `computer_observe` / `computer_act` retain 30-second defaults, and the explicit remote maximum remains 45 seconds. Remote `exec_command`, `run_process`, and `run_local_command` require a caller-held `idempotency_key`, commit a principal-bound one-step managed job, and remain recoverable through bounded same-response `read_job` follow-up when the current task needs terminal state. Hosted active `read_job` uses a server-side 40-second long-poll by default and returns earlier on meaningful job progress or terminal state; `wait_ms=0` requests an immediate checkpoint, while every public hosted call is capped at 60 seconds. The default remains 40 seconds: live hosted evidence carried both the default and an explicit 60-second read, while beta.151 later reproduced `mcp_network_error` on a second explicit 180-second read even though the Worker-to-daemon WebSocket remained continuously ready. Longer tasks therefore keep the same `job_id` and use another server-paced read rather than one overlong host request. This keeps long-task waiting inside Machine Bridge within the demonstrated per-call host lifetime; the 40-second interval also bounds interaction density to at most 150 reads for a synthetic unchanged 100-minute job, but that arithmetic does not prove that one assistant response can survive the aggregate duration or call count. If a real host/tool boundary ends a response, preserve the durable identifier and resume the same operation later rather than resubmitting its side effect. `start_process` remains daemon-lifetime interactive state; hosted `read_process` permits paced same-response follow-up, defaults an omitted relay `wait_ms` to the one-second blocking cap, and paces another would-block read inside the fifteen-second cooldown within that same MCP call until output/exit or the cooldown boundary. Active job/process reads do not force a user-turn handoff. Callers must not infer or preempt a host/tool deadline from elapsed wall-clock time: while calls continue to be accepted and the task still needs the result, bounded same-response follow-up may continue. Handoff is reserved for an actual observed host/tool boundary, required external input or authorization, or an explicit user checkpoint, while busy loops and status-surface substitution remain prohibited. The durable process façade preserves account/tool authority and delegated workspace sandbox rather than expanding privileges.
|
|
178
178
|
|
|
179
179
|
`full` is the daemon capability ceiling. An authenticated owner may exercise it without per-operation approval IDs. Delegated reviewer, editor, and operator accounts remain inside immutable role ceilings; out-of-role operations are denied rather than converted into a temporary elevation workflow. Process sessions, retained output, and managed jobs are additionally bound to account, client, and refresh-token family. See [local authorization](docs/LOCAL_AUTHORIZATION.md).
|
|
180
180
|
|
package/SECURITY.md
CHANGED
|
@@ -211,7 +211,7 @@ Sensitive and persistence targets are owner-only. Generic remote file tools cann
|
|
|
211
211
|
|
|
212
212
|
Direct processes use argv without shell parsing. Shell expansion is available only through the explicit shell tool.
|
|
213
213
|
|
|
214
|
-
Process counts, stdin, output, timeouts, retained sessions, and tool-call concurrency are bounded. Timeout, explicit cancellation, reconnect-grace expiry, runtime shutdown, and non-recoverable daemon replacement use process-tree termination with bounded graceful and forced phases. A transient relay or HTTP/SSE disconnect is not cancellation. Streamed-call ownership and deadlines survive Durable Object hibernation, while a random per-WebSocket generation prevents an obsolete socket from settling or detaching a rebound call.
|
|
214
|
+
Process counts, stdin, output, timeouts, retained sessions, and tool-call concurrency are bounded. Timeout, explicit cancellation, reconnect-grace expiry, runtime shutdown, and non-recoverable daemon replacement use process-tree termination with bounded graceful and forced phases. A transient relay or HTTP/SSE disconnect is not cancellation. After a same-daemon relay interruption, the Worker may keep only the in-memory result-settlement owner alive for up to fifteen additional seconds beyond its original settlement deadline, capped by the existing tool settlement maximum; this does not extend execution/redelivery authority, weaken cancellation, authorize replay, or persist a terminal result. Streamed-call ownership and deadlines survive Durable Object hibernation, while a random per-WebSocket generation prevents an obsolete socket from settling or detaching a rebound call.
|
|
215
215
|
|
|
216
216
|
Interactive process sessions die when their owning runtime stops or is replaced; an ordinary same-process relay reconnect does not itself destroy them. Retained output sessions and process control are bound to account, account version, OAuth client, and refresh family.
|
|
217
217
|
|
|
@@ -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.174",
|
|
34
34
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxryYkpZhq8+VAQLHcGS9BAHQcyKX8RHGIpIwvtIVRU/rcOcE0bNdnM0aZJ/h6xWQsGDHlhvjT2+1aJaAn/9k8473BRWajzVXld961CdHYVFVHoce2hHiSJ0xydWrHMMZhAm0mN0UzjEpgZ0tMw209efcZHIvSwuxhteZMRy4kyiVjwFlOf5oXFCxRuCJnPj3AK9CmCf4XgEBuPIJ0TZmjGHOOdBvJmbCNnAWXYEo5/mf7MfCGhV4IJ1hNuhpoNQfOFKMUcw9/v/IpT62XpfXdGYTfGYCmCjC+gntK1spbkr2P4/2+sYMQtLpse71mpSNGXfcf3abU55Vpn+gncSxRQIDAQAB"
|
|
35
35
|
}
|
package/docs/AUDIT.md
CHANGED
|
@@ -17,9 +17,10 @@ This file is the current audit summary. Historical findings, closed incidents, a
|
|
|
17
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
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
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.
|
|
20
|
+
- Same-daemon relay recovery now separates execution authority from terminal-result delivery. A transient transport loss may retain only the original in-memory settlement owner for up to fifteen additional seconds beyond its original settlement deadline, capped by the existing tool settlement maximum. Safe missing-call redelivery remains bound to the original execution deadline, repeated handovers cannot accumulate grace, cancellation still removes ownership, and no client-visible replay or persistent result store is introduced.
|
|
20
21
|
|
|
21
22
|
## Residual review requirements
|
|
22
23
|
|
|
23
24
|
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.
|
|
24
25
|
|
|
25
|
-
The beta.
|
|
26
|
+
The beta.174 candidate adds the relay-result delivery hardening required by the observed beta.173 transport interruption without weakening the already accepted beta.173 package evidence. Unit and integration evidence must prove late same-daemon terminal-result delivery inside the fixed fifteen-second settlement grace, absolute rather than cumulative handover deadlines, preservation of the existing maximum settlement ceilings, and refusal to redispatch a daemon-proven-missing call after its original execution authority has expired. The package, Worker, and extension identities must remain synchronized at beta.174 while the beta.173 acceptance record stays unchanged. Owner-machine acceptance should activate the beta.174 candidate and recheck current relay readiness/version after daemon replacement; identifying the external proxy/upstream component responsible for a future transport reset remains outside what the current privacy-bounded evidence can prove.
|
package/docs/OPERATIONS.md
CHANGED
|
@@ -97,7 +97,7 @@ A reconnect warning proves a transport interruption, not a daemon crash. Compare
|
|
|
97
97
|
|
|
98
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
|
-
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.
|
|
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. For an already-dispatched call, a disconnect never pauses or extends the original execution/redelivery deadline. The Worker may separately retain the same in-memory result-settlement owner for up to fifteen seconds beyond its original settlement deadline, capped by the tool's existing maximum settlement lifetime, solely so an already-executed terminal result from the verified same daemon can still reach the original request. Repeated disconnect/rebind cycles cannot accumulate that delivery grace, cancellation still removes the owner, and the longer settlement deadline is never used to authorize another execution. Reconnect retention is bounded by reconnect grace and that fixed delivery deadline. Diagnostics distinguish `original call deadline expired during reconnect`, `terminal result delivery grace expired during reconnect`, and a true full `reconnect grace expired` rather than collapsing those separate boundaries. 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
|
|
package/docs/PRIVACY.md
CHANGED
|
@@ -95,7 +95,7 @@ Recovered prerelease activation is also a publication-sensitive evidence boundar
|
|
|
95
95
|
For an accidental publication, remove the value from the current tree and release artifacts, determine whether it is merely identifying metadata or an active credential, and rotate/revoke any credential immediately. Public Git and npm history are immutable in ordinary workflows: replacing the current file does not erase old commits or a published package. A coordinated history rewrite, cache invalidation request, or replacement release may be appropriate, but those actions are disruptive and require an explicit repository-owner decision.
|
|
96
96
|
## Current response delivery
|
|
97
97
|
|
|
98
|
-
MCP `2026-07-28` response streams are request-scoped, not persisted, and not resumable by the client. Closing a public response stream cancels that request and releases its transient Worker ownership. The private Durable Object cancellation control is bounded by a short internal settlement deadline so an unresponsive control fetch cannot keep the public response open indefinitely; that deadline carries only stream-control identity and does not add content logging or durable replay state. Because control and direct requests can arrive in either order, the Durable Object also retains a bounded short-lived in-memory cancellation tombstone when a private cancel arrives before direct dispatch ownership opens. The tombstone contains only the random stream-control key plus expiry bookkeeping; it contains no tool name, arguments, result, path, account credential, or user content. Capacity exhaustion fails closed for that bounded lifetime rather than evicting cancellation evidence and starting an orphaned side effect. The Worker's pending-call registry remains in memory and does not persist tool arguments or terminal results as protocol replay state.
|
|
98
|
+
MCP `2026-07-28` response streams are request-scoped, not persisted, and not resumable by the client. Closing a public response stream cancels that request and releases its transient Worker ownership. The private Durable Object cancellation control is bounded by a short internal settlement deadline so an unresponsive control fetch cannot keep the public response open indefinitely; that deadline carries only stream-control identity and does not add content logging or durable replay state. Because control and direct requests can arrive in either order, the Durable Object also retains a bounded short-lived in-memory cancellation tombstone when a private cancel arrives before direct dispatch ownership opens. The tombstone contains only the random stream-control key plus expiry bookkeeping; it contains no tool name, arguments, result, path, account credential, or user content. Capacity exhaustion fails closed for that bounded lifetime rather than evicting cancellation evidence and starting an orphaned side effect. The Worker's pending-call registry remains in memory and does not persist tool arguments or terminal results as protocol replay state. After a brief same-daemon relay interruption, that registry may retain the original settlement owner for up to fifteen additional seconds beyond its original settlement deadline, capped by the tool's existing maximum settlement lifetime, so an already-executed terminal result can still reach the initiating request. The grace stores no new content, cannot authorize another execution or redelivery, does not accumulate across handovers, and disappears on normal settlement, cancellation, reconnect expiry, or runtime loss.
|
|
99
99
|
|
|
100
100
|
`toolsListChanged` subscriptions have one additional server-side retention bound because public HTTP disconnect is not a reliable Workers/Durable Object lifecycle signal. Each subscription receives its acknowledgement and level-trigger list-change notification immediately and then remains live for at most the advertised 10-second lease unless explicit cancellation, authority revocation, or an observable request abort releases it sooner. Lease state contains only transient account-scoped stream ownership/capacity bookkeeping; it does not persist tool contents, request arguments, or client receipt evidence. The existing 8-per-account / 32-global active limits and bounded 64-account opened-history remain in force.
|
|
101
101
|
|
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.174",
|
|
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",
|
|
@@ -13,8 +13,16 @@ export function daemonToolTimeoutBudgetAfterDelay(budget: DaemonToolTimeoutBudge
|
|
|
13
13
|
return Object.freeze({ executionTimeoutMs: Math.floor(executionTimeoutMs), settlementTimeoutMs: Math.floor(settlementTimeoutMs) });
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export function daemonReconnectExpiry(
|
|
16
|
+
export function daemonReconnectExpiry(
|
|
17
|
+
record: Pick<PendingCallRecord, "remainingTimeoutMs" | "originalDeadlineAt" | "deadlineAt">, reconnectGraceMs: number,
|
|
18
|
+
) {
|
|
17
19
|
const grace = Math.min(relayContract.reconnectGraceMs, Math.max(1, Math.floor(reconnectGraceMs)));
|
|
20
|
+
if (record.remainingTimeoutMs < grace && record.deadlineAt > record.originalDeadlineAt) {
|
|
21
|
+
return {
|
|
22
|
+
reason: "terminal_result_delivery_grace_expired",
|
|
23
|
+
message: "terminal result delivery grace expired during reconnect",
|
|
24
|
+
};
|
|
25
|
+
}
|
|
18
26
|
return record.remainingTimeoutMs < grace
|
|
19
27
|
? { reason: "original_call_deadline_expired_during_reconnect", message: "original call deadline expired during reconnect" }
|
|
20
28
|
: { reason: "reconnect_grace_expired", message: "reconnect grace expired" };
|
package/src/worker/index.ts
CHANGED
|
@@ -55,7 +55,7 @@ import { hostedManagedJobDaemonArguments, projectHostedManagedJobResult } from "
|
|
|
55
55
|
import { cancelManagedJobMonitorClaimsIfAvailable, claimManagedJobMonitor, hasManagedJobMonitorClaimIfAvailable, ManagedJobMonitorClaimStore } from "./mcp-job-monitor-claims.ts";
|
|
56
56
|
import { JOB_MONITOR_CLAIM_TOOL, JOB_MONITOR_READ_TOOL, JOB_MONITOR_RENDER_TOOL, managedJobMonitorReadDaemonArguments, projectManagedJobMonitorStatus, renderManagedJobMonitor } from "./mcp-job-monitor-tools.ts";
|
|
57
57
|
import { closeWebSocketQuietly, daemonErrorCloseCode, isObjectRecord, rejectDaemonMessage, sendWebSocketQuietly, trySendWebSocket } from "./websocket-protocol.ts";
|
|
58
|
-
const SERVER_VERSION = "3.0.0-beta.
|
|
58
|
+
const SERVER_VERSION = "3.0.0-beta.174";
|
|
59
59
|
const MCP_SERVER_INFO = mcpServerInfo(SERVER_VERSION);
|
|
60
60
|
const MAX_DAEMON_MESSAGE_BYTES = 8 * 1024 * 1024;
|
|
61
61
|
const DAEMON_RECONNECT_GRACE_MS = relayContract.reconnectGraceMs; const NEW_CALL_RECONNECT_GRACE_MS = relayContract.newCallReconnectGraceMs;
|
|
@@ -31,7 +31,6 @@ export class PendingCallRegistry {
|
|
|
31
31
|
for (const record of due) expired += Number(await this.expireRecord(record));
|
|
32
32
|
return expired;
|
|
33
33
|
}
|
|
34
|
-
|
|
35
34
|
register(input: RegisterPendingCall): Promise<unknown> {
|
|
36
35
|
this.assertCanRegister(input);
|
|
37
36
|
let resolveResult!: (value: unknown) => void;
|
|
@@ -46,11 +45,9 @@ export class PendingCallRegistry {
|
|
|
46
45
|
reject(id: string, error: Error, socket?: DaemonChannel): Promise<boolean> {
|
|
47
46
|
const record = this.byId.get(id); return !record || (socket && record.socket !== socket) ? Promise.resolve(false) : this.finish(id, { ok: false, error });
|
|
48
47
|
}
|
|
49
|
-
|
|
50
48
|
async cancelRequest(requestKey: string, onCancel: (record: PendingCallRecord) => Error): Promise<boolean> {
|
|
51
49
|
const id = this.byRequestKey.get(requestKey); return id ? this.fail(id, onCancel, "pending daemon call was cancelled") : false;
|
|
52
50
|
}
|
|
53
|
-
|
|
54
51
|
async cancelAuthority(revocation: AuthorityRevocation, onCancel: (record: PendingCallRecord) => Error): Promise<number> {
|
|
55
52
|
const ids = [...this.byId.values()].filter((record) => recordMatchesAuthorityRevocation(record, revocation)).map((record) => record.id);
|
|
56
53
|
let cancelled = 0;
|
|
@@ -78,6 +75,7 @@ export class PendingCallRegistry {
|
|
|
78
75
|
record.socket = undefined;
|
|
79
76
|
record.onReconnectTimeout = createError;
|
|
80
77
|
this.deadlines.pauseOperation(record);
|
|
78
|
+
this.extendSettlementForReconnect(record);
|
|
81
79
|
const delay = Math.min(maximumGrace, record.remainingTimeoutMs);
|
|
82
80
|
this.deadlines.armReconnect(record, delay, (id) => { void this.expireReconnect(id); });
|
|
83
81
|
}
|
|
@@ -91,6 +89,7 @@ export class PendingCallRegistry {
|
|
|
91
89
|
if (record.daemonInstanceId !== daemonInstanceId || record.socket === socket) continue;
|
|
92
90
|
if (record.socket) this.deadlines.pauseOperation(record);
|
|
93
91
|
else this.deadlines.clearReconnect(record);
|
|
92
|
+
this.extendSettlementForReconnect(record);
|
|
94
93
|
const remainingTimeoutMs = Math.max(1, Math.ceil(record.deadlineAt - this.deadlines.now()));
|
|
95
94
|
record.onReconnectTimeout = undefined;
|
|
96
95
|
record.socket = socket;
|
|
@@ -124,7 +123,7 @@ export class PendingCallRegistry {
|
|
|
124
123
|
owner_family_id: input.authority.familyId,
|
|
125
124
|
} : {}),
|
|
126
125
|
tool: String(input.tool || "unknown"), ...(input.recovery ? { recovery: input.recovery } : {}),
|
|
127
|
-
startedAt, deadlineAt: startedAt + timeoutMs, remainingTimeoutMs: timeoutMs,
|
|
126
|
+
startedAt, originalDeadlineAt: startedAt + timeoutMs, deadlineAt: startedAt + timeoutMs, remainingTimeoutMs: timeoutMs,
|
|
128
127
|
onTimeout: input.onTimeout, redeliverAfterProvenMissing: input.redeliverAfterProvenMissing, settlement, signal: input.signal, abortHandler,
|
|
129
128
|
};
|
|
130
129
|
this.byId.set(input.id, record);
|
|
@@ -135,6 +134,12 @@ export class PendingCallRegistry {
|
|
|
135
134
|
}
|
|
136
135
|
|
|
137
136
|
private expireOperation(id: string): Promise<boolean> { return this.expireRecord(this.byId.get(id)); }
|
|
137
|
+
private extendSettlementForReconnect(record: PendingCallRecord): void {
|
|
138
|
+
const maximumDeadlineAt = record.startedAt + pendingCallTimeoutMaximumMs(record.tool);
|
|
139
|
+
const recoveryDeadlineAt = Math.min(maximumDeadlineAt, record.originalDeadlineAt + relayContract.reconnectResultDeliveryGraceMs);
|
|
140
|
+
if (recoveryDeadlineAt > record.deadlineAt) record.deadlineAt = recoveryDeadlineAt;
|
|
141
|
+
record.remainingTimeoutMs = Math.max(1, Math.ceil(record.deadlineAt - this.deadlines.now()));
|
|
142
|
+
}
|
|
138
143
|
private expireReconnect(id: string): Promise<boolean> {
|
|
139
144
|
const record = this.byId.get(id); return record && !record.socket ? this.expireRecord(record) : Promise.resolve(false);
|
|
140
145
|
}
|