machine-bridge-mcp 3.0.0-beta.191 → 3.0.0-beta.195

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 CHANGED
@@ -1,10 +1,9 @@
1
1
  # Changelog
2
2
 
3
- ## 3.0.0-beta.191 - 2026-09-15
3
+ ## 3.0.0-beta.195 - 2026-09-18
4
4
 
5
- - Keep durable managed-job execution available across in-place Node package-manager upgrades by falling back from a removed daemon `process.execPath` only to the still-executable absolute Node launcher that originally started the daemon; diagnostics expose provenance/availability without local paths.
6
- - Make macOS sleep diagnosis retain actual timestamped `Sleep` records with a bounded 15-second power-log probe, avoiding the previous five-second timeout and broad filter that could hide evidence explaining relay suspension.
7
- - Add regressions for stale runtime launcher recovery and sleep-probe boundaries; relay authentication, replay, duplicate-side-effect prevention, and reconnect policy are unchanged.
5
+ - Update the fast-check development dependency from 4.9.0 to 4.10.0 and refresh its resolved lockfile entry.
6
+ - Preserve the beta.194 Wrangler/toolchain and relay changes while advancing release identity to beta.195 so this dependency update receives independent full verification, live candidate canary, and exact acceptance evidence.
8
7
 
9
8
  ## Historical releases
10
9
 
@@ -30,6 +30,6 @@
30
30
  "action": {
31
31
  "default_title": "Machine Bridge Browser"
32
32
  },
33
- "version_name": "3.0.0-beta.191",
33
+ "version_name": "3.0.0-beta.195",
34
34
  "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxryYkpZhq8+VAQLHcGS9BAHQcyKX8RHGIpIwvtIVRU/rcOcE0bNdnM0aZJ/h6xWQsGDHlhvjT2+1aJaAn/9k8473BRWajzVXld961CdHYVFVHoce2hHiSJ0xydWrHMMZhAm0mN0UzjEpgZ0tMw209efcZHIvSwuxhteZMRy4kyiVjwFlOf5oXFCxRuCJnPj3AK9CmCf4XgEBuPIJ0TZmjGHOOdBvJmbCNnAWXYEo5/mf7MfCGhV4IJ1hNuhpoNQfOFKMUcw9/v/IpT62XpfXdGYTfGYCmCjC+gntK1spbkr2P4/2+sYMQtLpse71mpSNGXfcf3abU55Vpn+gncSxRQIDAQAB"
35
35
  }
package/docs/AUDIT.md CHANGED
@@ -1,35 +1,31 @@
1
1
  # Current audit status
2
2
 
3
- This file is the current audit summary. Historical findings, closed incidents, and release-by-release evidence are retained by Git tags and repository history rather than repeated in the default maintenance path. For a historical snapshot, inspect this file at the corresponding version tag.
3
+ This file is a compact statement of current cross-cutting audit conclusions and residual limits. It is intentionally version-agnostic: release identity, candidate acceptance, publication state, and soak state are authoritative only in package metadata, release records, Git history/tags, and the external release systems they describe. This file is not a release-state authority.
4
4
 
5
5
  ## Current conclusions
6
6
 
7
- - MCP execution is current-protocol-only. Remote HTTP and stdio support MCP `2026-07-28`; removed initialization/session/replay requests fail closed and cannot dispatch old behavior.
8
- - Browser computer observation requires the current atomic observation capability, and desktop visual-point decisions require the formal capability interface. Runtime method-presence fallbacks are not accepted as compatibility.
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
- - 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
- - 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.
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.
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 one fixed fifteen-second window beyond its original settlement deadline, including when that original settlement deadline was already at the ordinary tool ceiling. 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.
21
- - Compound-task continuity is now explicit planning guidance rather than an implicit host behavior. Multi-command, multi-project, long-running, or interruption-sensitive non-interactive work is routed toward one durable managed job, recovery guidance requires resuming the same accepted job after reconnect, and built-in working agreements reject directory-name freshness as checkout identity evidence. Direct shell remains an available fallback; durability is ownership semantics, not a claim that the relay transport cannot fail.
22
- - Workspace relocation now has an explicit offline state-migration invariant. The beta.175 candidate exposed stale global selection after the beta165 worktree was archived; beta.176 then proved that changing selection alone still leaves the deployed profile and machine-service owner keyed to the historical canonical path; beta.177 exposed targetless `service stop` still loading selected-workspace state. The unaccepted beta.178 candidate added the migration path, but owner-machine recovery then exposed two post-verification gaps: read-only inspection can leave an unpopulated destination shell, and a recurring external launchd helper that stopped the daemon before later preflight checks converted a populated-destination rejection into repeated service removal. Beta.179 makes targetless stop provider-global/state-free, accepts only absent or provably unpopulated destination profiles, proves provider/job/lock quiescence before pruning any empty shell, never recursively clears destination state, tolerates only an absent historical owner entry script while validating one that still exists, canonicalizes path aliases through `realpath`, and injects `HOME` into macOS launchd definitions. Recurring external migration helpers and compatibility symlinks are not accepted repairs.
23
-
24
- - Beta.180 is a controlled, unaccepted continuity candidate. Its target is two-layer fault containment rather than an impossible no-disconnect promise: keep signed HTTPS standby and the fallback proxy transport warm so a transient WSS 1006 can take the bounded exact-generation takeover path without a cold start, and make long/compound/keep-working execution explicitly owned by one durable task supervisor instead of host-fragmented one-step calls. The candidate must preserve no-replay and fail-closed transport ownership, keep short/interactive routing unchanged, and may not be called accepted until frozen-tree verification and live owner-machine activation evidence exist.
25
-
26
- - Beta.181 supersedes the activated-but-unaccepted beta.180 candidate after live owner-machine routing exposed two semantic classification defects rather than a relay failure: `非交互工作` and conditional `外部输入或授权` wording could exclude task supervision, while a project token could weak-match an unrelated installed application sharing one lexical fragment. The repair narrows interactive intent to explicit process/input contexts, requires lexical-token evidence for partial application matches, and makes an already-positive task-supervisor continuation contract authoritative for primary route selection. beta.180 relay standby/takeover behavior is unchanged.
27
- - Beta.182 is the reviewed predecessor candidate that added repository-specific durable routing for known long prerelease commands, extracted reconnect result settlement into a focused policy module while retaining the then-existing per-tool settlement ceiling, and recorded the controlled relay A/B. Its packaged bytes remain associated with beta.182 and are not reused after the independent re-review changed shipped source.
28
- - Beta.183 supersedes beta.182 after that re-review found package-affecting continuity, routing, privacy, and auditability defects. Repository-specific long workflows now request durable task supervision through project-provided registered-command metadata rather than hard-coded script names in generic routing; explicit read-only/negated/hypothetical/interactive/existing-job wording is kept out of new-job creation, and weak application-name matches require UI-operation intent. Worker reconnect settlement keeps execution/redelivery on the original deadline but grants the verified same-daemon terminal-result owner one fixed non-cumulative 15-second delivery-only extension beyond the original settlement deadline. Managed-job output redaction is byte-first and truncation-boundary safe; saturated retention tolerates a stale active-to-terminal dependency-plan deletion race without weakening genuine active-state fail-closed behavior; multiline static module edges are included in the architecture graph; and public worktree resolution no longer depends on maintainer-home tooling. The beta.182 controlled application-proxy A/B remains evidence only for the induced fault branch and does not identify the cause of spontaneous historical 1006 resets.
29
- - Beta.186 is the source candidate produced by an independent beta.185 review. The first review confirmed two issues: a zero-wait `read_job` starts with exactly the 10-second managed-read headroom, so any positive reconnect delay made daemon-proven non-delivery ineligible for redelivery; and the output-redaction sink itself accepted an empty literal even though current resource materialization already filters empty patterns. Subsequent hardening reproduced four adjacent boundary defects before acceptance: truncation-tail protection could receive truthy non-string redaction entries; repeated missing acknowledgements could re-enter redelivery policy; `resume_calls_ack.missing_ids` was not bound to the exact resume set emitted on that connection; and shorter protected bytes/paths/literals could partially replace a longer overlapping value and leave its suffix visible. The candidate now uses one literal-pattern filter, processes overlapping protected values longest-first, binds resume acknowledgement to the exact connection-scoped resume set, consumes one semantic acknowledgement while treating only its exact duplicate as idempotent, and records the channel of the one successful same-ID transport redelivery. A different later acknowledgement or out-of-set ID is a protocol error, while a later reconnect that again proves non-delivery returns the existing retryable no-side-effect failure. Low-headroom `read_job` redelivery still becomes `wait_ms: 0`, preserves the original execution deadline, and refuses less than one second of execution budget; fresh dispatch still requires the ordinary 10-second reconciliation headroom. The existing socket protocol-error counter is now driven by real WebSocket and signed-HTTPS ready-channel rejection paths without recording daemon payload values. Here “production protocol rejection paths” means the shipped Worker handler paths that can serve production traffic; it is a source-path reachability claim, not evidence that beta.186 has been deployed to or observed in a production environment. Other report proposals are not treated as defects without stronger evidence: request-stream closure is deliberately cancellation for request-scoped work; the 2-second durable-process initial settlement window is a response-coalescing bound rather than task lifetime and same-response `read_job` continuation remains the orchestration contract; historical macOS relocation already canonicalizes existing realpath ancestors, so platform-wide lowercasing would be unsound on case-sensitive volumes; beta.179 and beta.181 have passed acceptance records and are retained; and disabling fallback-proxy keep-alive is not justified by the current fault-injection evidence alone.
30
-
31
- ## Residual review requirements
32
-
33
- 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.
34
-
35
- Beta.185 is the latest repository acceptance record and remains the accepted prior-byte baseline. Beta.186 is a new local source candidate and must not inherit beta.185 acceptance: frozen-tree verification, packaging/install/security checks, and any exact-candidate live activation evidence required by the changed surface must be produced again before acceptance. The spontaneous historical upstream reset source remains unassigned within the current privacy-bounded evidence; a future relay reset may still occur. npm publication remains separately gated.
7
+ - **Authority remains an intersection, never an accumulation.** Effective MCP authority is bounded by authenticated account role, daemon capability ceiling, and tool-specific policy. Reconnects, retries, stale host schemas, browser focus changes, or recovery metadata do not expand authority.
8
+ - **Removed protocol behavior stays removed.** Remote HTTP and stdio use the current MCP protocol only. Removed initialization, session, and replay behavior fails closed instead of silently falling back to historical compatibility.
9
+ - **State publication is atomic and identity-sensitive.** Security-sensitive state readers reject symlinks, unexpected hard links, malformed content, permission failures, and persistent identity changes. Bounded retries are reserved for explicitly classified atomic-publication races; durable state is complete before it becomes visible.
10
+ - **Durable execution owns continuity explicitly.** Managed jobs persist recovery state independently of the hosted MCP response. Idempotency keys bind uncertain acceptance retries only while the retained record exists; cancellation, dependency state, cleanup, output budgets, and terminal persistence have explicit fail-closed boundaries. Callers recover the same accepted job instead of replaying its side effect.
11
+ - **Daemon-owned Node children use a verified runtime launcher.** A live concrete runtime executable is preferred; if it disappears after a package-manager upgrade, only the original absolute Node launcher is eligible as fallback. Diagnostics expose provenance booleans rather than executable paths, and an unavailable launcher requires service restart instead of PATH guessing.
12
+ - **Relay recovery separates transport from execution semantics.** WebSocket and signed HTTPS are transport paths, not permission or replay authorities. Their authenticated handshake/resume state is generation-scoped per transport, ended-generation control messages are discarded before reconciliation or interruption, and a protocol violation is routed only to its originating current transport. Ambiguous side effects are not transparently replayed. Same-daemon terminal-result retention is bounded, cancellation removes ownership, and repeated reconnects cannot accumulate execution time or delivery grace. Fatal protocol diagnostics retain only sanitized error/source/generation-class/stage/disposition fields, while fallback takeover history remains privacy-bounded across both control paths.
13
+ - **Operating-system sleep is an observable suspension boundary, not a network guarantee.** On supported macOS hosts, diagnostics correlate bounded sleep history with runtime stalls and recovered relay outages. Machine Bridge cannot keep JavaScript or a socket executing while the machine is suspended, and sleep correlation does not identify unrelated upstream network causes.
14
+ - **Browser automation remains existing-profile and snapshot-bound.** The extension controls an already installed Chromium profile; hosted calls bind explicit tab identity, and later Computer Use actions bind the exact observation snapshot. Mutation response loss, timeout, extension replacement, or ambiguous trusted-input settlement yields an unknown/non-retryable boundary that must be inspected before any retry.
15
+ - **Desktop automation is permission- and evidence-bound.** Application actions use structured Accessibility operations or an explicitly enabled snapshot-bound visual backend. The runtime does not silently substitute arbitrary scripting or unverified pixel guessing when required authority/evidence is unavailable.
16
+ - **Logging and diagnostics are privacy-minimized by contract.** Runtime logs are structured and must not contain tool arguments, command output, credentials, account identity, arbitrary URLs, private paths, or other user content. Explicit local CLI/tool results are a separate user-requested output surface. Security-audit telemetry is content-free, bounded, and hash-chained; diagnostics expose coarse provenance/health rather than secrets or private values.
17
+ - **Resource admission is cooperative scheduling, not a hostile-code sandbox.** CPU, memory, I/O, disk-reserve, queue, and result budgets protect service continuity, but ordinary execution still has the daemon OS user's ambient authority unless a dedicated low-privilege account or VM/container supplies the stronger boundary.
18
+ - **Release and package evidence remain separate from audit prose.** Shipped-source or packed-document changes require synchronized package identity and fresh verification. Candidate acceptance, publication, activation, and stable promotion must be proven by their dedicated records and external systems; no sentence in this audit summary inherits or confers release authority.
19
+
20
+ ## Residual limits
21
+
22
+ - The default full profile is intentionally powerful. It is appropriate only where the daemon OS user is itself an acceptable trust boundary; stronger hostile-code isolation requires a separate OS account or VM/container.
23
+ - The relay can recover from many transport failures, but no software can promise uninterrupted connectivity across machine sleep, host suspension, upstream outages, VPN/TUN behavior, or process replacement. Recovery evidence must not be restated as a no-disconnect guarantee.
24
+ - Machine Bridge can observe that a hosted tool call reached the daemon and can retain durable job results, but it cannot prove that a ChatGPT host turn ended normally or that a final UI frame rendered to the user.
25
+ - Browser status proves the connected extension identity/capabilities, not that the paired profile is the user's intended daily versus isolated profile. Unpacked extensions may require an explicit reload after upgrades before browser automation counts as exercised.
26
+ - Privacy-preserving diagnostics deliberately leave some root causes unassigned. In particular, spontaneous upstream transport resets may remain indistinguishable once exposing packet contents, private URLs, or user traffic would be required to disambiguate them.
27
+ - Passing local tests is necessary but not sufficient release evidence. External CI, package integrity, installation, deployment/activation, live boundary tests, and soak requirements remain separate when the changed surface requires them.
28
+
29
+ ## Historical findings
30
+
31
+ Historical incidents, closed hypotheses, release-by-release evidence, and superseded candidate details belong in Git history/tags and the [changelog](../CHANGELOG.md), not in this current summary. Inspect the file at the corresponding historical revision when reconstructing an old incident or release decision.
package/docs/LOGGING.md CHANGED
@@ -72,7 +72,7 @@ Brief network interruptions are expected on laptop network changes, Worker deplo
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
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
- 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.
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. A fatal relay protocol log retains only a sanitized protocol error code, source transport (WebSocket or HTTPS), coarse connection-generation class, handshake stage, and fatal/retry disposition; it never records the numeric session generation, endpoint, credentials, account identity, call IDs, or message payload. Default logs therefore describe the affected layer, duration, classification, and recovery behavior rather than printing raw close envelopes.
76
76
 
77
77
  Streamed-call diagnostics are deliberately coarse. MCP `2026-07-28` request/response-stream ownership and the Worker pending-call registry are memory-only; there is no protocol-session, terminal-result, subscriber, or replay store. Worker event counters are scoped to the current isolate and say so in `metric_scope`, while `server_info` exposes only bounded current pending-call/capacity state. These diagnostics do not prove public SSE consumption or MCP-host receipt. Logs and `server_info` must not include tool arguments, terminal results, command text, request keys, account identifiers, raw call IDs, raw connection generations, mirrored parameter values, private paths, or subscriber payloads. A stale-generation result is counted as unmatched rather than logged with its envelope.
78
78
 
@@ -93,7 +93,7 @@ After the host path recovers, compare authenticated `server_info`, `machine-mcp
93
93
 
94
94
  ### Relay interruption messages
95
95
 
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.
96
+ A reconnect warning proves a transport interruption, not a daemon crash. When the daemon instead exits on a relay protocol error, inspect the same default service-log entry for the sanitized protocol error code, source transport, coarse generation class, handshake stage, and disposition; those fields distinguish a current-generation fatal WebSocket violation from a retryable HTTPS fallback violation without exposing raw connection identifiers. 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
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "machine-bridge-mcp",
3
- "version": "3.0.0-beta.191",
3
+ "version": "3.0.0-beta.195",
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",
@@ -272,10 +272,10 @@
272
272
  "devDependencies": {
273
273
  "@types/node": "26.4.0",
274
274
  "eslint": "10.9.1",
275
- "fast-check": "4.9.0",
275
+ "fast-check": "4.10.0",
276
276
  "globals": "17.11.0",
277
277
  "typescript": "7.0.2",
278
- "wrangler": "4.127.1"
278
+ "wrangler": "4.131.2"
279
279
  },
280
280
  "keywords": [
281
281
  "mcp",
@@ -307,7 +307,7 @@
307
307
  "esbuild@0.28.1": true,
308
308
  "fsevents": false,
309
309
  "sharp@0.35.4": true,
310
- "workerd@1.20260828.1": true
310
+ "workerd@1.20260911.1": true
311
311
  },
312
312
  "packageManager": "npm@12.0.2",
313
313
  "devEngines": {
@@ -335,7 +335,7 @@ export class RelayConnection {
335
335
  terminateSocket(socket);
336
336
  return true;
337
337
  }
338
- this.failPermanently("relay_protocol_error");
338
+ this.failPermanently("relay_protocol_error", { diagnostics: message?.diagnostics || null });
339
339
  return true;
340
340
  }
341
341
 
@@ -521,7 +521,7 @@ export class RelayConnection {
521
521
  });
522
522
  }
523
523
 
524
- failPermanently(category, { socketAlreadyClosed = false, wasReady = this.ready } = {}) {
524
+ failPermanently(category, { socketAlreadyClosed = false, wasReady = this.ready, diagnostics = null } = {}) {
525
525
  if (this.closed) return;
526
526
  const socket = this.socket;
527
527
  this.closed = true;
@@ -550,11 +550,12 @@ export class RelayConnection {
550
550
  this.connectedOnceResolve = null;
551
551
  this.connectedOnceReject = null;
552
552
  this.resetOutage();
553
+ if (diagnostics) this.logger.error?.(message, diagnostics);
553
554
  if (!this.hasConnected && reject) {
554
555
  reject(error);
555
556
  return;
556
557
  }
557
- this.logger.error?.(message);
558
+ if (!diagnostics) this.logger.error?.(message);
558
559
  this.logger.debug?.("remote relay fatal details", { category, cause: relayCloseUserCause(category) });
559
560
  queueMicrotask(async () => {
560
561
  try {
@@ -1,6 +1,7 @@
1
1
  import relayContract from "../shared/relay-contract.json" with { type: "json" };
2
2
  import { RelayConnection } from "./relay-connection.mjs";
3
3
  import { DaemonHttpRelayConnection } from "./daemon-http-relay-connection.mjs";
4
+ import { relayServerErrorReconnectCategory, sanitizeProtocolErrorCode } from "./relay-connection-classification.mjs";
4
5
 
5
6
  export class ResilientRelayConnection {
6
7
  constructor(options = {}) {
@@ -107,6 +108,14 @@ export class ResilientRelayConnection {
107
108
  return this.activeTransport === "https" ? this.http.currentSessionId() : this.websocket.currentSessionId();
108
109
  }
109
110
 
111
+ isCurrentSession(relayContext = {}) {
112
+ const sessionId = Number(relayContext?.sessionId) || 0;
113
+ if (!sessionId) return false;
114
+ if (relayContext?.transport === "https") return sessionId === this.http.currentSessionId();
115
+ if (relayContext?.transport === "websocket") return sessionId === this.websocket.currentSessionId();
116
+ return false;
117
+ }
118
+
110
119
  send(value) {
111
120
  if (this.activeTransport === "websocket") return this.websocket.send(value);
112
121
  if (this.activeTransport === "https") return this.http.send(value);
@@ -124,6 +133,13 @@ export class ResilientRelayConnection {
124
133
  return this.websocket.interrupt(category);
125
134
  }
126
135
 
136
+ interruptForContext(category, relayContext = {}) {
137
+ if (!this.isCurrentSession(relayContext)) return false;
138
+ return relayContext?.transport === "https"
139
+ ? this.http.interrupt(category)
140
+ : this.websocket.interrupt(category);
141
+ }
142
+
127
143
  refreshAuthentication() {
128
144
  const websocketInterrupted = this.websocket.interrupt("relay_session_rotated");
129
145
  const httpInterrupted = this.http.interrupt("relay_session_rotated");
@@ -139,14 +155,40 @@ export class ResilientRelayConnection {
139
155
  return this.websocket.acknowledge(message);
140
156
  }
141
157
  confirmReady(message, relayContext = {}) {
158
+ if (!this.isCurrentSession(relayContext)) return false;
142
159
  return relayContext?.transport === "https" ? this.http.confirmReady(message) : this.websocket.confirmReady(message);
143
160
  }
144
161
  observeApplicationPong(relayContext = {}) {
145
162
  return relayContext?.transport === "https" ? false : this.websocket.observeApplicationPong(relayContext);
146
163
  }
147
164
  handleServerError(message, relayContext = {}) {
148
- if (relayContext?.transport === "https") return this.http.interrupt(message?.error);
149
- return this.websocket.handleServerError(message);
165
+ const sessionId = Number(relayContext?.sessionId) || 0;
166
+ if (sessionId && !this.isCurrentSession(relayContext)) {
167
+ this.logger.debug?.("discarded relay error from an ended transport generation", {
168
+ source_transport: relayTransport(relayContext), connection_generation: "stale",
169
+ handshake_stage: relayHandshakeStage(relayContext),
170
+ });
171
+ return false;
172
+ }
173
+ const errorCode = sanitizeProtocolErrorCode(message?.error);
174
+ const reconnectCategory = relayServerErrorReconnectCategory(errorCode, {
175
+ authenticated: relayContext?.authenticated === true, ready: relayContext?.ready === true,
176
+ });
177
+ const sourceTransport = relayTransport(relayContext);
178
+ const diagnostics = {
179
+ error_code: errorCode, source_transport: sourceTransport,
180
+ connection_generation: sessionId ? "current" : "unbound",
181
+ handshake_stage: relayHandshakeStage(relayContext),
182
+ };
183
+ if (sourceTransport === "https") {
184
+ if (!reconnectCategory) this.logger.warn?.(
185
+ "remote HTTPS relay reported a protocol error; restarting the fallback transport",
186
+ { ...diagnostics, disposition: "retry_transport" },
187
+ );
188
+ return this.http.interrupt(errorCode);
189
+ }
190
+ return this.websocket.handleServerError(reconnectCategory
191
+ ? message : { ...message, error: errorCode, diagnostics: { ...diagnostics, disposition: "fatal_runtime" } });
150
192
  }
151
193
 
152
194
  handleReady(transport, event) {
@@ -278,3 +320,14 @@ function positiveInteger(value, fallback) {
278
320
  const number = Number(value);
279
321
  return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback;
280
322
  }
323
+
324
+ function relayTransport(relayContext = {}) {
325
+ const transport = String(relayContext?.transport || "");
326
+ return transport === "websocket" || transport === "https" ? transport : "unknown";
327
+ }
328
+
329
+ function relayHandshakeStage(relayContext = {}) {
330
+ if (relayContext?.ready === true) return "post_ready";
331
+ if (relayContext?.authenticated === true) return "authenticated_pre_ready";
332
+ return "pre_authentication";
333
+ }
@@ -18,7 +18,7 @@ export function runtimeRelayConnectionOptions(runtime, input) {
18
18
  helloMessage: async (welcome, relayStatus) => ({
19
19
  type: "hello", instance_id: runtime.relayInstanceId, tools: runtime.tools(), policy: runtime.policy,
20
20
  protocol_versions: MCP_SUPPORTED_PROTOCOL_VERSIONS,
21
- relay_diagnostics: relayHandshakeDiagnostics(relayStatus),
21
+ relay_diagnostics: relayHandshakeDiagnostics(runtime.relay?.status?.() || relayStatus),
22
22
  authentication: await createDaemonAuthentication(currentSessionIdentity(), welcome, runtime.relayInstanceId),
23
23
  }),
24
24
  onMessage,
@@ -40,7 +40,7 @@ export function runtimeRelayConnectionOptions(runtime, input) {
40
40
  ownedCallIds: () => runtime.relayOwnedCallIds(),
41
41
  onMessage,
42
42
  },
43
- onDisconnect: () => runtime.handleRelayDisconnect(),
44
- onReady: () => runtime.handleRelayReady(),
43
+ onDisconnect: (event) => runtime.handleRelayDisconnect(event),
44
+ onReady: (event) => runtime.handleRelayReady(event),
45
45
  };
46
46
  }
@@ -3,30 +3,69 @@ import { handleRuntimeRelayAcknowledgement } from "./runtime-relay-acknowledgeme
3
3
  import { normalizeRelayResumeCalls } from "./runtime-relay.mjs";
4
4
  import { normalizeAuthorityRevocation } from "../shared/authority-revocation.mjs";
5
5
 
6
+ export function handleRuntimeRelayProtocolViolation(runtime, errorCode, relayContext = {}) {
7
+ const sessionId = Number(relayContext?.sessionId) || 0;
8
+ if (staleRelayGeneration(runtime, relayContext)) {
9
+ runtime.logger?.event?.("debug", "relay.protocol_violation.stale_generation_discarded", {
10
+ source_transport: relayTransport(relayContext) || "unknown",
11
+ connection_generation: "stale",
12
+ handshake_stage: relayHandshakeStage(relayContext),
13
+ }, "Discarded a protocol violation from an ended relay generation");
14
+ return;
15
+ }
16
+ if (runtime.relay) {
17
+ runtime.relay.handleServerError({ type: "error", error: errorCode }, relayContext);
18
+ return;
19
+ }
20
+ runtime.logger?.error?.("remote relay protocol error; upgrade and redeploy both components, then restart the daemon", {
21
+ error_code: safeMessageType(errorCode),
22
+ source_transport: relayTransport(relayContext) || "unknown",
23
+ connection_generation: sessionId ? "unknown" : "unbound",
24
+ handshake_stage: relayHandshakeStage(relayContext),
25
+ disposition: "fatal_runtime",
26
+ });
27
+ }
28
+
29
+ export function handleRuntimeRelayDisconnect(runtime, relayContext = {}) {
30
+ const transport = relayTransport(relayContext);
31
+ const states = relayResumeState(runtime);
32
+ if (!transport) states.clear();
33
+ else clearRelayResumeState(runtime, transport);
34
+ runtime.relayCallRecovery.disconnected();
35
+ }
36
+
6
37
  export async function handleRuntimeRelayControlMessage(runtime, message, relayContext = {}) {
7
38
  if (message.type === "welcome") {
8
39
  runtime.relay?.observeWelcome(message, relayContext);
9
40
  return true;
10
41
  }
11
42
  if (message.type === "hello_ack") {
12
- runtime.relayResumeSessionId = 0;
13
- runtime.relayResumeMissingIds = [];
43
+ clearRelayResumeState(runtime, relayTransport(relayContext) || "websocket");
14
44
  runtime.relay?.acknowledge(message, relayContext);
15
45
  return true;
16
46
  }
47
+ if (staleRelayGeneration(runtime, relayContext)) {
48
+ runtime.logger?.event?.("debug", "relay.control.stale_generation_discarded", {
49
+ source_transport: relayTransport(relayContext) || "unknown",
50
+ connection_generation: "stale",
51
+ handshake_stage: relayHandshakeStage(relayContext),
52
+ message_type: safeMessageType(message?.type),
53
+ }, "Discarded a control message from an ended relay generation");
54
+ return true;
55
+ }
17
56
  if (message.type === "resume_calls") return handleResumeCalls(runtime, message, relayContext);
18
57
  if (message.type === "authority_revoke") {
19
58
  const sessionId = Number(relayContext.sessionId) || 0;
20
59
  const revocationId = String(message.revocation_id || "");
21
60
  const revocation = normalizeAuthorityRevocation(message);
22
61
  if (!sessionId || relayContext.authenticated !== true || !/^revoke_[A-Za-z0-9_-]{43}$/.test(revocationId) || !revocation) {
23
- runtime.handleRelayProtocolViolation("invalid_authority_revoke");
62
+ runtime.handleRelayProtocolViolation("invalid_authority_revoke", relayContext);
24
63
  return true;
25
64
  }
26
65
  try {
27
66
  await runtime.applyAuthorityRevocation(revocation);
28
67
  } catch (error) {
29
- runtime.relay?.interrupt?.("local_authority_revocation_retry");
68
+ interruptRelayContext(runtime, "local_authority_revocation_retry", relayContext);
30
69
  throw error;
31
70
  }
32
71
  runtime.relay?.sendForSession?.({ type: "authority_revoke_ack", revocation_id: revocationId }, sessionId);
@@ -34,29 +73,30 @@ export async function handleRuntimeRelayControlMessage(runtime, message, relayCo
34
73
  }
35
74
  if (message.type === "ready_ack") {
36
75
  const sessionId = Number(relayContext.sessionId) || 0;
37
- if (!sessionId || sessionId !== runtime.relayResumeSessionId) {
38
- runtime.handleRelayProtocolViolation("resume_calls_required");
76
+ const key = relayResumeKey(relayContext);
77
+ const resume = key ? relayResumeState(runtime).get(key) : null;
78
+ if (!sessionId || !resume) {
79
+ runtime.handleRelayProtocolViolation("resume_calls_required", relayContext);
39
80
  return true;
40
81
  }
41
82
  if (!runtime.relay?.confirmReady(message, relayContext)) {
42
- runtime.relay?.interrupt?.("relay_transport_error");
83
+ interruptRelayContext(runtime, "relay_transport_error", relayContext);
43
84
  return true;
44
85
  }
45
86
  const acknowledgement = runtime.relay?.sendForSession?.({
46
87
  type: "resume_calls_ack",
47
- missing_ids: Array.isArray(runtime.relayResumeMissingIds) ? runtime.relayResumeMissingIds : [],
88
+ missing_ids: Array.isArray(resume.missingIds) ? resume.missingIds : [],
48
89
  }, sessionId);
49
90
  if (!acknowledgement?.ok) {
50
- runtime.relay?.interrupt?.("relay_transport_error");
91
+ interruptRelayContext(runtime, "relay_transport_error", relayContext);
51
92
  return true;
52
93
  }
53
- runtime.relayResumeSessionId = 0;
54
- runtime.relayResumeMissingIds = [];
94
+ relayResumeState(runtime).delete(key);
55
95
  return true;
56
96
  }
57
97
  if (message.type === "pong") return handlePong(runtime, relayContext);
58
98
  if (message.type === "tool_result_ack" || message.type === "daemon_draining_ack") {
59
- return handleRuntimeRelayAcknowledgement(runtime, message, relayContext);
99
+ return handleRuntimeRelayAcknowledgement(runtimeForRelayContext(runtime, relayContext), message, relayContext);
60
100
  }
61
101
  if (message.type === "error") {
62
102
  runtime.relay?.handleServerError(message, relayContext);
@@ -66,7 +106,7 @@ export async function handleRuntimeRelayControlMessage(runtime, message, relayCo
66
106
  if (!isRelayReadyContext(relayContext, runtime.relay)
67
107
  || typeof message.id !== "string"
68
108
  || !/^call_[A-Za-z0-9_-]{8,240}$/.test(message.id)) {
69
- runtime.handleRelayProtocolViolation("invalid_cancel_call");
109
+ runtime.handleRelayProtocolViolation("invalid_cancel_call", relayContext);
70
110
  return true;
71
111
  }
72
112
  runtime.cancelRelayCall(message.id, "caller_cancelled");
@@ -78,13 +118,19 @@ export async function handleRuntimeRelayControlMessage(runtime, message, relayCo
78
118
  function handleResumeCalls(runtime, message, relayContext) {
79
119
  const sessionId = Number(relayContext.sessionId) || 0;
80
120
  const resume = normalizeRelayResumeCalls(message);
81
- if (!resume.ok || !sessionId || relayContext.authenticated !== true || relayContext.ready === true) {
82
- runtime.handleRelayProtocolViolation("invalid_resume_calls");
121
+ const key = relayResumeKey(relayContext);
122
+ if (!resume.ok || !sessionId || !key || relayContext.authenticated !== true || relayContext.ready === true) {
123
+ runtime.handleRelayProtocolViolation("invalid_resume_calls", relayContext);
83
124
  return true;
84
125
  }
126
+ const states = relayResumeState(runtime);
127
+ if (states.has(key)) {
128
+ runtime.handleRelayProtocolViolation("invalid_resume_calls", relayContext);
129
+ return true;
130
+ }
131
+ clearRelayResumeState(runtime, relayTransport(relayContext));
85
132
  const missingIds = runtime.reconcileRelayCalls(resume.ids);
86
- runtime.relayResumeSessionId = sessionId;
87
- runtime.relayResumeMissingIds = Array.isArray(missingIds) ? missingIds : [];
133
+ states.set(key, { missingIds: Array.isArray(missingIds) ? missingIds : [] });
88
134
  return true;
89
135
  }
90
136
 
@@ -93,3 +139,65 @@ function handlePong(runtime, relayContext) {
93
139
  runtime.relayCallRecovery.pulse();
94
140
  return true;
95
141
  }
142
+
143
+ function relayResumeState(runtime) {
144
+ if (!(runtime.relayResumeStates instanceof Map)) runtime.relayResumeStates = new Map();
145
+ return runtime.relayResumeStates;
146
+ }
147
+
148
+ function relayTransport(relayContext = {}) {
149
+ const value = String(relayContext?.transport || "");
150
+ return value === "websocket" || value === "https" ? value : "";
151
+ }
152
+
153
+ function relayResumeKey(relayContext = {}) {
154
+ const transport = relayTransport(relayContext);
155
+ const sessionId = Number(relayContext?.sessionId) || 0;
156
+ return transport && sessionId ? transport + ":" + sessionId : "";
157
+ }
158
+
159
+ function clearRelayResumeState(runtime, transport = "") {
160
+ const states = relayResumeState(runtime);
161
+ if (!transport) {
162
+ states.clear();
163
+ return;
164
+ }
165
+ const prefix = transport + ":";
166
+ for (const key of states.keys()) {
167
+ if (key.startsWith(prefix)) states.delete(key);
168
+ }
169
+ }
170
+
171
+ function staleRelayGeneration(runtime, relayContext = {}) {
172
+ const sessionId = Number(relayContext?.sessionId) || 0;
173
+ if (!sessionId || !relayTransport(relayContext) || typeof runtime.relay?.isCurrentSession !== "function") return false;
174
+ return runtime.relay.isCurrentSession(relayContext) !== true;
175
+ }
176
+
177
+ function interruptRelayContext(runtime, category, relayContext) {
178
+ if (typeof runtime.relay?.interruptForContext === "function") {
179
+ return runtime.relay.interruptForContext(category, relayContext);
180
+ }
181
+ return runtime.relay?.interrupt?.(category);
182
+ }
183
+
184
+ function runtimeForRelayContext(runtime, relayContext) {
185
+ return {
186
+ relay: runtime.relay,
187
+ relayCallRecovery: runtime.relayCallRecovery,
188
+ relayShutdownDrain: runtime.relayShutdownDrain,
189
+ handleRelayProtocolViolation(errorCode) {
190
+ return runtime.handleRelayProtocolViolation(errorCode, relayContext);
191
+ },
192
+ };
193
+ }
194
+
195
+ function relayHandshakeStage(relayContext = {}) {
196
+ if (relayContext?.ready === true) return "post_ready";
197
+ if (relayContext?.authenticated === true) return "authenticated_pre_ready";
198
+ return "pre_authentication";
199
+ }
200
+
201
+ function safeMessageType(value) {
202
+ return String(value || "unknown").replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 64) || "unknown";
203
+ }
@@ -41,7 +41,7 @@ import { policyForContext } from "./authority-context.mjs";
41
41
  import { createRuntimeRelayConnection, normalizeRelayToolCall } from "./runtime-relay.mjs"; import { relayRecoveryCapacityRejection } from "./relay-recovery-admission.mjs";
42
42
  import { runtimeControlPlaneSnapshot } from "./runtime-diagnostic-state.mjs";
43
43
  import { shortCallId } from "./short-identifiers.mjs";
44
- import { handleRuntimeRelayControlMessage } from "./runtime-relay-control.mjs";
44
+ import { handleRuntimeRelayControlMessage, handleRuntimeRelayDisconnect, handleRuntimeRelayProtocolViolation } from "./runtime-relay-control.mjs";
45
45
  import { RelayCallRecovery } from "./relay-call-recovery.mjs";
46
46
  import { RuntimeRelayShutdownDrain } from "./runtime-relay-shutdown-drain.mjs";
47
47
  import { RuntimeResourceService } from "./runtime-resource-service.mjs";
@@ -83,7 +83,7 @@ export class LocalRuntime {
83
83
  this.relayInstanceId = `daemon_${randomBytes(18).toString("base64url")}`;
84
84
  this.activeRelayCalls = new Map();
85
85
  this.suppressedRelayResults = new Map();
86
- this.relayResumeSessionId = 0; this.relayResumeMissingIds = [];
86
+ this.relayResumeStates = new Map();
87
87
  this.remoteActivityIdleSleepGuard = new RemoteActivityIdleSleepGuard({ logger: this.logger, mode: idleSleepMode });
88
88
  this.callRegistry = new CallRegistry({
89
89
  maximum: MAX_CONCURRENT_TOOL_CALLS,
@@ -359,11 +359,7 @@ export class LocalRuntime {
359
359
  handleRelayControlMessage(message, relayContext = {}) { return handleRuntimeRelayControlMessage(this, message, relayContext); }
360
360
 
361
361
  handleRelayProtocolViolation(errorCode, relayContext = {}) {
362
- if (this.relay) {
363
- this.relay.handleServerError({ type: "error", error: errorCode }, relayContext);
364
- return;
365
- }
366
- this.logger.error?.("remote relay protocol error; upgrade and redeploy both components, then restart the daemon");
362
+ return handleRuntimeRelayProtocolViolation(this, errorCode, relayContext);
367
363
  }
368
364
 
369
365
  handleRelayProbe(message, relayContext = {}) {
@@ -452,11 +448,7 @@ export class LocalRuntime {
452
448
 
453
449
  relayOwnedCallIds() { return this.relayCallRecovery?.ownedCallIds?.() ?? [...this.activeRelayCalls.keys()]; }
454
450
 
455
- handleRelayDisconnect() {
456
- this.relayResumeSessionId = 0;
457
- this.relayResumeMissingIds = [];
458
- this.relayCallRecovery.disconnected();
459
- }
451
+ handleRelayDisconnect(relayContext = {}) { return handleRuntimeRelayDisconnect(this, relayContext); }
460
452
 
461
453
  handleRelayReady() {
462
454
  this.relayCallRecovery.ready();
@@ -8,7 +8,7 @@
8
8
  "name": "machine-bridge-mcp-wrangler-toolchain",
9
9
  "version": "1.0.0",
10
10
  "dependencies": {
11
- "wrangler": "4.127.1"
11
+ "wrangler": "4.131.2"
12
12
  },
13
13
  "engines": {
14
14
  "node": ">=26.0.0",
@@ -40,9 +40,9 @@
40
40
  }
41
41
  },
42
42
  "node_modules/@cloudflare/workerd-darwin-64": {
43
- "version": "1.20260828.1",
44
- "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260828.1.tgz",
45
- "integrity": "sha512-CVd+xPhqUESg8Xhq09TZx0wl4FSirfJGOzvbPz2yHhBIvmNHFFQkSN3rkd7wEwnhQQk37Xi0/aD6ykPLJbmGiQ==",
43
+ "version": "1.20260911.1",
44
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260911.1.tgz",
45
+ "integrity": "sha512-785eaY1bkR1cm4Z/PCUeteZYmTMe6lre2zz63/GdGGimsoMsKxgl4brFPRukim8iv28EyD1XoCB/VPYF20BERA==",
46
46
  "cpu": [
47
47
  "x64"
48
48
  ],
@@ -56,9 +56,9 @@
56
56
  }
57
57
  },
58
58
  "node_modules/@cloudflare/workerd-darwin-arm64": {
59
- "version": "1.20260828.1",
60
- "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260828.1.tgz",
61
- "integrity": "sha512-5HDPXRM152vU5JveByGFk34X57TVyIsfp4cabepAf45DC0MKvm52ucJqAjW1h8bvW4X+zRw9GU35OHF9FEC9Ww==",
59
+ "version": "1.20260911.1",
60
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260911.1.tgz",
61
+ "integrity": "sha512-WU4bFqEN0H7ndGWxoedegv95DmNVBtv0ncXcHG9nYFTUI78sxEb0qoT3U6Ga4hyBkzsJFBX/zvVBIGX3qKldGA==",
62
62
  "cpu": [
63
63
  "arm64"
64
64
  ],
@@ -72,9 +72,9 @@
72
72
  }
73
73
  },
74
74
  "node_modules/@cloudflare/workerd-linux-64": {
75
- "version": "1.20260828.1",
76
- "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260828.1.tgz",
77
- "integrity": "sha512-MQ1Ll9P7F72HHUKizbb7BlDfbY8fRoNMpbIpZoU6uKsSkneFICWSKv6UlgU9EQZ+w0i7TMa12iUgJ8l29eRI9A==",
75
+ "version": "1.20260911.1",
76
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260911.1.tgz",
77
+ "integrity": "sha512-0Y2gy62oxQxWa38qinSPE6zNL5+JmumJtDY9AWW1HB8KHuATxN71o5MGzmVFfB8PwZsiHfUd2Sv7O22krCOrhw==",
78
78
  "cpu": [
79
79
  "x64"
80
80
  ],
@@ -88,9 +88,9 @@
88
88
  }
89
89
  },
90
90
  "node_modules/@cloudflare/workerd-linux-arm64": {
91
- "version": "1.20260828.1",
92
- "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260828.1.tgz",
93
- "integrity": "sha512-FBTaUQ1xcU9jcp4OyBPcH8x0QiFvc1iuZL2GkD8zp2q1WyTVHYOptRDQUU+cuHjt0rQ2EIKVPBjahPxfa0joBw==",
91
+ "version": "1.20260911.1",
92
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260911.1.tgz",
93
+ "integrity": "sha512-kttNPnx1r2lCqFUoMH62z7CqGV+j4QBbw5fdtaz4pzOrzBv0AWkNATt7onFUe+SwP8zhcepMtbm2F4kKzTf6VA==",
94
94
  "cpu": [
95
95
  "arm64"
96
96
  ],
@@ -104,9 +104,9 @@
104
104
  }
105
105
  },
106
106
  "node_modules/@cloudflare/workerd-windows-64": {
107
- "version": "1.20260828.1",
108
- "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260828.1.tgz",
109
- "integrity": "sha512-yvr77hC7dUbvK5K+SCg062kkPq3sx+drV1PcgHslzHDYcJBtT0V3X80qLE49LW1vq2svaeNmsVQS+vHsqWu8cQ==",
107
+ "version": "1.20260911.1",
108
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260911.1.tgz",
109
+ "integrity": "sha512-5iO/YfoBDOgO3CrHdkiiVP8SL3O2jC+c6Ux3d378TSPKLhU5+CgHjtE/ZSodWQrzr4FzFRqdW8S7n5nbyD1MHQ==",
110
110
  "cpu": [
111
111
  "x64"
112
112
  ],
@@ -1276,15 +1276,15 @@
1276
1276
  }
1277
1277
  },
1278
1278
  "node_modules/miniflare": {
1279
- "version": "5.20260828.0-alpha",
1280
- "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-5.20260828.0-alpha.tgz",
1281
- "integrity": "sha512-6nbxhZEcz/UET3Y1OnYPsrAUjUmuFoib3ynUqteRdn1YnDxsLg8cwgZJZCk9QmtOmGzXwzXzgE/d/C0dJAPtVw==",
1279
+ "version": "5.20260911.1-alpha",
1280
+ "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-5.20260911.1-alpha.tgz",
1281
+ "integrity": "sha512-7IDj9monoYcCPrS8HfcTt90T3pDwKGvNEAR1Y061KbJhBd5JkStOwOpHMUDFBo9PEbjWVDxPICAnXGNNV2LNfQ==",
1282
1282
  "license": "MIT",
1283
1283
  "dependencies": {
1284
1284
  "@cspotcode/source-map-support": "0.8.1",
1285
- "sharp": "0.35.2",
1285
+ "sharp": "0.35.4",
1286
1286
  "undici": "7.29.0",
1287
- "workerd": "1.20260828.1",
1287
+ "workerd": "1.20260911.1",
1288
1288
  "ws": "8.21.0",
1289
1289
  "youch": "4.1.0-beta.10"
1290
1290
  },
@@ -1403,9 +1403,9 @@
1403
1403
  }
1404
1404
  },
1405
1405
  "node_modules/workerd": {
1406
- "version": "1.20260828.1",
1407
- "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260828.1.tgz",
1408
- "integrity": "sha512-pB9yvt0kkwZDAGZHmpY59r0o3hM0DzdW6BJERqwZOhunZ3ssOyDSgQxOQer2cSZW4YCFeOTIQYN1qwhK5wv/Cw==",
1406
+ "version": "1.20260911.1",
1407
+ "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260911.1.tgz",
1408
+ "integrity": "sha512-vRr8QdBxueQOZJO1hRCI73EZlix87IAyBAcSyI3rA1VB+6oxjw3oaqzYnIV8C4IOPtUgihbdMAgzkb5GM4V7DQ==",
1409
1409
  "hasInstallScript": true,
1410
1410
  "license": "Apache-2.0",
1411
1411
  "bin": {
@@ -1415,27 +1415,27 @@
1415
1415
  "node": ">=16"
1416
1416
  },
1417
1417
  "optionalDependencies": {
1418
- "@cloudflare/workerd-darwin-64": "1.20260828.1",
1419
- "@cloudflare/workerd-darwin-arm64": "1.20260828.1",
1420
- "@cloudflare/workerd-linux-64": "1.20260828.1",
1421
- "@cloudflare/workerd-linux-arm64": "1.20260828.1",
1422
- "@cloudflare/workerd-windows-64": "1.20260828.1"
1418
+ "@cloudflare/workerd-darwin-64": "1.20260911.1",
1419
+ "@cloudflare/workerd-darwin-arm64": "1.20260911.1",
1420
+ "@cloudflare/workerd-linux-64": "1.20260911.1",
1421
+ "@cloudflare/workerd-linux-arm64": "1.20260911.1",
1422
+ "@cloudflare/workerd-windows-64": "1.20260911.1"
1423
1423
  }
1424
1424
  },
1425
1425
  "node_modules/wrangler": {
1426
- "version": "4.127.1",
1427
- "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.127.1.tgz",
1428
- "integrity": "sha512-OzsiNgaI8i681L/+KnAKc+uEZ5D57xK5JuNvCOpRKICF4/5Q3Cu1oTGuUiT/f3GDUqQb3gzXNT0tfOHGMEtknw==",
1426
+ "version": "4.131.2",
1427
+ "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.131.2.tgz",
1428
+ "integrity": "sha512-jmkGE7monbPKyYQr1FPQN+SARVhddqw2fhXOmTKCw4lroqlFGSS6rit/RTvPi/qzNLKrXxkS8DhWXasJnStplg==",
1429
1429
  "license": "MIT OR Apache-2.0",
1430
1430
  "dependencies": {
1431
1431
  "@cloudflare/kv-asset-handler": "0.5.0",
1432
1432
  "@cloudflare/unenv-preset": "2.16.1",
1433
1433
  "blake3-wasm": "2.1.5",
1434
1434
  "esbuild": "0.28.1",
1435
- "miniflare": "5.20260828.0-alpha",
1435
+ "miniflare": "5.20260911.1-alpha",
1436
1436
  "path-to-regexp": "6.3.0",
1437
1437
  "unenv": "2.0.0-rc.24",
1438
- "workerd": "1.20260828.1"
1438
+ "workerd": "1.20260911.1"
1439
1439
  },
1440
1440
  "bin": {
1441
1441
  "cf-wrangler": "bin/cf-wrangler.js",
@@ -1449,7 +1449,7 @@
1449
1449
  "fsevents": "2.3.3"
1450
1450
  },
1451
1451
  "peerDependencies": {
1452
- "@cloudflare/workers-types": "^5.20260828.1"
1452
+ "@cloudflare/workers-types": "^5.20260911.1"
1453
1453
  },
1454
1454
  "peerDependenciesMeta": {
1455
1455
  "@cloudflare/workers-types": {
@@ -8,7 +8,7 @@
8
8
  "npm": ">=12.0.0"
9
9
  },
10
10
  "dependencies": {
11
- "wrangler": "4.127.1"
11
+ "wrangler": "4.131.2"
12
12
  },
13
13
  "overrides": {
14
14
  "sharp": "0.35.4",
@@ -18,6 +18,6 @@
18
18
  "esbuild@0.28.1": true,
19
19
  "fsevents": false,
20
20
  "sharp@0.35.4": true,
21
- "workerd@1.20260828.1": true
21
+ "workerd@1.20260911.1": true
22
22
  }
23
23
  }
@@ -152,13 +152,13 @@ async function runNpm(npmCli, args, cwd, runCommand, options, timeoutMs, allowFa
152
152
  }
153
153
 
154
154
  function validateTemplate(manifest, lock) {
155
- if (manifest.private !== true || manifest.dependencies?.wrangler !== "4.127.1") {
155
+ if (manifest.private !== true || manifest.dependencies?.wrangler !== "4.131.2") {
156
156
  throw new Error("Wrangler toolchain manifest lost its exact private Wrangler dependency");
157
157
  }
158
158
  if (manifest.overrides?.undici !== "7.29.0" || manifest.overrides?.sharp !== "0.35.4") {
159
159
  throw new Error("Wrangler toolchain manifest lost its security overrides");
160
160
  }
161
- const expectedScripts = { "esbuild@0.28.1": true, fsevents: false, "sharp@0.35.4": true, "workerd@1.20260828.1": true };
161
+ const expectedScripts = { "esbuild@0.28.1": true, fsevents: false, "sharp@0.35.4": true, "workerd@1.20260911.1": true };
162
162
  if (JSON.stringify(manifest.allowScripts) !== JSON.stringify(expectedScripts)) {
163
163
  throw new Error("Wrangler toolchain manifest lost its exact install-script policy");
164
164
  }
@@ -57,7 +57,7 @@ import { hostedManagedJobDaemonArguments, projectHostedManagedJobResult } from "
57
57
  import { cancelManagedJobMonitorClaimsIfAvailable, claimManagedJobMonitor, hasManagedJobMonitorClaimIfAvailable, ManagedJobMonitorClaimStore } from "./mcp-job-monitor-claims.ts";
58
58
  import { JOB_MONITOR_CLAIM_TOOL, JOB_MONITOR_READ_TOOL, JOB_MONITOR_RENDER_TOOL, managedJobMonitorReadDaemonArguments, projectManagedJobMonitorStatus, renderManagedJobMonitor } from "./mcp-job-monitor-tools.ts";
59
59
  import { closeWebSocketQuietly, daemonErrorCloseCode, isObjectRecord, rejectDaemonMessage, sendWebSocketQuietly, trySendWebSocket } from "./websocket-protocol.ts";
60
- const SERVER_VERSION = "3.0.0-beta.191";
60
+ const SERVER_VERSION = "3.0.0-beta.195";
61
61
  const MCP_SERVER_INFO = mcpServerInfo(SERVER_VERSION);
62
62
  const MAX_DAEMON_MESSAGE_BYTES = 8 * 1024 * 1024;
63
63
  const DAEMON_RECONNECT_GRACE_MS = relayContract.reconnectGraceMs; const NEW_CALL_RECONNECT_GRACE_MS = relayContract.newCallReconnectGraceMs;