machine-bridge-mcp 3.0.0-beta.17 → 3.0.0-beta.21

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.
Files changed (74) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +2 -0
  3. package/SECURITY.md +9 -7
  4. package/browser-extension/browser-error-boundary.js +45 -0
  5. package/browser-extension/browser-operations.js +1 -1
  6. package/browser-extension/manifest.json +1 -1
  7. package/browser-extension/service-worker.js +9 -8
  8. package/docs/ARCHITECTURE.md +17 -15
  9. package/docs/AUDIT.md +30 -0
  10. package/docs/ENGINEERING.md +3 -3
  11. package/docs/GETTING_STARTED.md +1 -1
  12. package/docs/LOCAL_AUTHORIZATION.md +2 -2
  13. package/docs/LOCAL_AUTOMATION.md +1 -1
  14. package/docs/LOGGING.md +3 -1
  15. package/docs/MULTI_ACCOUNT.md +2 -2
  16. package/docs/OPERATIONS.md +13 -5
  17. package/docs/OVERVIEW.md +2 -2
  18. package/docs/PRIVACY.md +1 -1
  19. package/docs/TESTING.md +17 -13
  20. package/docs/THREAT_MODEL.md +13 -8
  21. package/docs/UPGRADING.md +19 -1
  22. package/package.json +1 -1
  23. package/scripts/coverage-check.mjs +12 -0
  24. package/src/local/account-admin.mjs +68 -1
  25. package/src/local/agent-skill-discovery.mjs +16 -5
  26. package/src/local/app-automation.mjs +27 -7
  27. package/src/local/cli-options.mjs +1 -1
  28. package/src/local/path-inspection.mjs +23 -0
  29. package/src/local/process-tree-ownership.mjs +17 -5
  30. package/src/local/process-tree.mjs +6 -2
  31. package/src/local/relay-connection.mjs +24 -11
  32. package/src/local/resource-operations.mjs +37 -8
  33. package/src/local/runtime-activation.mjs +16 -0
  34. package/src/local/runtime-capabilities.mjs +15 -3
  35. package/src/local/runtime.mjs +7 -4
  36. package/src/local/worker-deployment.mjs +16 -6
  37. package/src/local/workspace-file-service.mjs +29 -11
  38. package/src/worker/daemon-socket-attachment.ts +52 -0
  39. package/src/worker/daemon-sockets.ts +17 -48
  40. package/src/worker/durable-stream-calls.ts +129 -0
  41. package/src/worker/durable-stream-result.ts +22 -0
  42. package/src/worker/http.ts +37 -15
  43. package/src/worker/index.ts +143 -146
  44. package/src/worker/mcp-pending-call-expiry.ts +20 -0
  45. package/src/worker/mcp-pending-call-inspection.ts +33 -0
  46. package/src/worker/mcp-pending-call-records.ts +58 -0
  47. package/src/worker/mcp-pending-call-storage.ts +18 -0
  48. package/src/worker/mcp-pending-call-store.ts +285 -0
  49. package/src/worker/mcp-resumption-index.ts +41 -0
  50. package/src/worker/mcp-resumption-records.ts +8 -2
  51. package/src/worker/mcp-resumption.ts +84 -86
  52. package/src/worker/mcp-stream-dispatch.ts +42 -54
  53. package/src/worker/mcp-stream-proxy.ts +7 -69
  54. package/src/worker/mcp-stream-subscription.ts +98 -0
  55. package/src/worker/oauth-controller.ts +11 -2
  56. package/src/worker/oauth-refresh-exchange.ts +147 -0
  57. package/src/worker/oauth-refresh-families.ts +59 -17
  58. package/src/worker/oauth-state.ts +13 -2
  59. package/src/worker/oauth-token-derivation.ts +33 -0
  60. package/src/worker/oauth-token-issuance.ts +106 -0
  61. package/src/worker/oauth-tokens.ts +12 -184
  62. package/src/worker/observability.ts +19 -0
  63. package/src/worker/pending-admission.ts +15 -0
  64. package/src/worker/pending-call-contract.ts +4 -7
  65. package/src/worker/pending-calls.ts +4 -10
  66. package/src/worker/runtime-alarm-storage.ts +35 -0
  67. package/src/worker/runtime-alarm.ts +30 -20
  68. package/src/worker/websocket-protocol.ts +4 -0
  69. package/src/worker/worker-edge-guard.ts +81 -0
  70. package/src/worker/worker-edge-log.ts +63 -0
  71. package/src/worker/worker-entry.ts +50 -0
  72. package/src/worker/worker-metadata.ts +39 -0
  73. package/src/worker/worker-static-routes.ts +38 -50
  74. package/wrangler.jsonc +8 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,55 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.0.0-beta.21 - 2026-07-27
4
+
5
+ ### Relay continuity and stable MCP catalog
6
+
7
+ - Keep `tools/list` stable for an authenticated account role instead of withdrawing almost every tool whenever the local relay is briefly unavailable. The Worker still fails every execution closed against the live daemon capability ceiling, and `server_info` now distinguishes the stable advertised catalog from the currently effective daemon/account intersection.
8
+ - Persist streamed daemon-call ownership, request correlation, operation deadlines, reconnect deadlines, and result transformation metadata in Durable Object storage. A hibernated or restarted Worker can recover the active call, a verified same-instance daemon can reclaim it, and a per-WebSocket connection generation prevents stale close events or delayed results from mutating the rebound call. Active-record expiry advances monotonically across repeated detach/rebind cycles instead of being capped by the original single-reconnect window.
9
+ - Make Durable Object alarms the sole deadline owner for persisted streamed calls while retaining the existing Promise/timer path for bounded JSON-only calls. A FIFO admission gate computes one combined 32-call ceiling across both paths. Cancellation, send failure, operation timeout, reconnect-grace expiry, daemon replacement, and successful completion all converge through one guarded terminal write.
10
+ - Classify Worker-requested transport and liveness invalidation as retryable relay recovery instead of a permanent protocol mismatch. The daemon now terminates only the affected socket, preserves ordinary disconnect cleanup, and reconnects automatically; Worker transient invalidation uses WebSocket 1012, while unknown protocol messages, authentication failure, and identity/version mismatch remain fatal. Close-only delivery is also classified from bounded reasons so loss of the preceding error frame cannot restart the daemon. A failed daemon `hello` send and a readiness-probe result lost to an ending relay generation are likewise transport races, not authentication or protocol violations.
11
+ - Add red-green persistence, stale-generation, exactly-once, stable-catalog, disconnected-execution, reconnect, cancellation, timeout, transient Worker-error/close-only recovery, and real Wrangler OAuth/MCP integration coverage.
12
+ - Repair POSIX process-tree escalation after workflow-level repeated full verification exposed a surviving anti-`SIGTERM` descendant. Ownership is refreshed immediately after graceful termination, and escalation falls back to targeted PID/start-time/PGID checks when a full process-table snapshot is unavailable under load; PID reuse still fails closed.
13
+ - Extend post-deployment Worker health convergence for edge propagation, and treat an already recorded current deployment fingerprint as verification-only unless `--force-worker` is explicitly supplied. Persistent candidate activation now compensates an early failure by restarting a service that was active before the transaction, after candidate and lock cleanup; restoration failures remain aggregated with the primary failure.
14
+
15
+ ### Audit and documentation
16
+
17
+ - Re-audit the relay lifecycle, tool-advertisement contract, pending-call accounting, storage validation, state-machine boundaries, privacy-safe diagnostics, and obsolete event-settlement code. Synchronize architecture, operations, logging, testing, security, privacy, upgrading, and audit documentation with the implemented continuity model and its residual failure boundaries.
18
+
19
+ ## 3.0.0-beta.20 - 2026-07-26
20
+
21
+ ### Fixed
22
+
23
+ - Rewrite the bounded Worker error-cause traversal with an explicit object type guard and `WeakSet<object>` cycle tracking. This preserves the eight-level/cycle-safe classification behavior while eliminating the CodeQL `js/comparison-between-incompatible-types` finding; the existing cyclic-cause regression test continues to enforce non-duplication.
24
+
25
+ ## 3.0.0-beta.19 - 2026-07-26
26
+
27
+ ### Fixed
28
+
29
+ - Restore the documented `account revoke-client CLIENT_ID` CLI command. The action was implemented end to end, but its positional-argument limit was omitted, so every valid client ID was rejected as an extra positional argument before the signed administration request could be sent. Add direct parser regression coverage for both `account clients` and `account revoke-client`.
30
+
31
+ ## 3.0.0-beta.18 - 2026-07-26
32
+
33
+ ### Fixed
34
+
35
+ - Prevent intermittent hosted-client account loss during refresh-token rotation. A consumed refresh token may now recover at most two same-client, same-resource, same-scope, same-DPoP retries inside a 30-second concurrency window. Both retries reproduce the original deployment-keyed HMAC replacement pair without creating another credential branch or extending expiration; retries beyond that bound return `temporarily_unavailable`, while replay after the window still revokes the complete family. Schema-2 refresh state migrates in place to schema 3.
36
+ - Normalize unexpected outer-Worker failures to a structured retryable `502 worker_gateway_error` instead of allowing `scriptThrewException`/Cloudflare 1101 to surface as a generic account connection failure. Logged error classes contain only error names/codes, never exception messages.
37
+ - Retry an internal terminal WebSocket subscription with bounded delays after transport closure or retryable 429/5xx responses. Normal streamed calls retain the fixed two-request Durable Object path; failure recovery is capped at three subscription attempts.
38
+ - Stop reading request bodies immediately after a declared or observed size violation instead of draining attacker-controlled bytes. Permission and I/O failures during write-path and workspace traversal checks now propagate rather than being misclassified as missing files.
39
+ - Make partial application and skill discovery explicit through bounded path-projected warnings and coarse error classes. Optional `session_bootstrap` failure remains non-fatal but is now visible in Worker observability.
40
+ - Bound account-administration responses to one MiB, cancel oversized bodies, and require successful replies to be JSON objects. Generated SSH key registration now attempts both cleanup targets and reports incomplete rollback instead of silently leaving an unregistered private key.
41
+ - Add a fixed browser-extension error boundary, remove raw debugger details from successful fallback results, and enforce the 32-operation concurrency ceiling independently inside the extension. Error-cause inspection is cycle-aware and capped at eight levels.
42
+
43
+ ### Quota and deployment hardening
44
+
45
+ - Serve all public discovery metadata and unknown-path 404 responses in the outer Worker. Only an exact stateful route-and-method allowlist can reach the rate limiter and Durable Object; invalid methods are rejected at the stateless edge.
46
+ - Add a Cloudflare Rate Limiting binding before Durable Object dispatch. Binding failure is fail-open because it is a quota guard rather than an authorization boundary; OAuth, session, and role checks remain inside the Durable Object.
47
+ - Coalesce Durable Object alarms: an already scheduled earlier alarm is reused instead of being rewritten on every daemon heartbeat, and empty alarm state avoids redundant deletes.
48
+ - Report refresh outcomes, estimated resumable-stream row writes, and alarm set/delete/no-op counters in Worker observability. Regression tests hold a normal stream to four storage-row writes before expiry cleanup.
49
+ - Rate-limit repeated edge degradation logs and report suppressed-event counts, while redacting sensitive field names. Remove duplicate `waitUntil` registration for one streamed terminal operation.
50
+ - Add hard critical-coverage thresholds for every new OAuth, stream, metadata, quota, edge-logging, and filesystem-state module rather than relying only on line-count architecture checks.
51
+ - Split OAuth refresh exchange, token issuance, terminal subscription, public metadata, and edge quota guards into focused modules rather than raising architecture limits.
52
+
3
53
  ## 3.0.0-beta.17 - 2026-07-26
4
54
 
5
55
  ### Fixed
package/README.md CHANGED
@@ -155,6 +155,8 @@ The shared source of truth is `src/shared/policy-contract.json`. The generated m
155
155
 
156
156
  For remote calls, `server_info.authorization.effective_policy` and `effective_tools` are authoritative. Daemon policy and tools describe only the local capability ceiling before account-role and host-side filtering.
157
157
 
158
+ `tools/list` is a stable discovery catalog for the authenticated account role. A brief relay interruption does not withdraw tool definitions or require a tools-list-changed notification. 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` distinguishes the stable advertised catalog from the currently effective daemon/account intersection.
159
+
158
160
  `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).
159
161
 
160
162
  ## Browser and application automation
package/SECURITY.md CHANGED
@@ -67,7 +67,7 @@ The roles are:
67
67
 
68
68
  No approval ID, refresh token, reconnect, client registration, or legacy lease can expand a role. Out-of-role operations fail with `authorization_denied`.
69
69
 
70
- The Worker filters the tool catalog, and the local runtime independently recomputes the role boundary before dispatch. Account disablement, role change, password rotation, account removal, client revocation, token-version rotation, and refresh-family replay invalidate the appropriate credentials.
70
+ The Worker filters the stable discovery catalog by account role. Discovery is not authorization: each call is separately intersected with the current end-to-end-ready daemon capability ceiling, and the local runtime independently recomputes the role and policy boundary before dispatch. Account disablement, role change, password rotation, account removal, client revocation, token-version rotation, and refresh-family replay invalidate the appropriate credentials.
71
71
 
72
72
  An OAuth client is bound to one account, account version, and role after successful authorization. It cannot silently switch accounts. Use `machine-mcp account clients` to inspect clients and `machine-mcp account revoke-client CLIENT_ID` to revoke one client and its credentials.
73
73
 
@@ -132,7 +132,7 @@ Version 3 has no long-lived `ACCOUNT_ADMIN_SECRET`. Account and OAuth-client adm
132
132
  - timestamp;
133
133
  - random nonce.
134
134
 
135
- The Worker verifies the root certificate, session signature, bounded timestamp, body hash, and nonce replay state. Nonce capacity fails closed instead of evicting live replay markers.
135
+ The Worker verifies the root certificate, session signature, bounded timestamp, body hash, and nonce replay state. Nonce capacity fails closed instead of evicting live replay markers. The local administration client accepts at most one MiB, cancels oversized responses, and requires every successful non-empty reply to be a JSON object; malformed success cannot be mistaken for an empty valid result.
136
136
 
137
137
  Account passwords are generated 256-bit tokens. The Worker stores independent salted verifiers, not plaintext passwords. A generated password is printed once by the command that creates or rotates it.
138
138
 
@@ -174,6 +174,8 @@ The browser broker:
174
174
 
175
175
  These controls do not make web content trustworthy. Pages may contain prompt injection, deceptive labels, hidden consequences, changing UI state, inaccessible cross-origin frames, or high-value authenticated sessions. Machine Bridge cannot prove the extension is loaded in an isolated profile.
176
176
 
177
+ The extension independently caps active operations at 32. Its public error boundary returns only fixed or allowlisted guidance; raw Chrome, DevTools, page, URL, selector, filesystem-path, account-shaped, and credential-shaped exception text is not forwarded to a remote client. Successful trusted-input fallback likewise reports a fixed reason rather than the local debugger failure.
178
+
177
179
  After trusted input dispatch begins, an ambiguous failure is reported as unknown outcome and is not automatically replayed.
178
180
 
179
181
  Local resources may be injected without returning their bytes through MCP, but the destination page or application still receives them. Screenshots and page source can themselves contain secrets.
@@ -192,15 +194,15 @@ Sensitive and persistence targets are owner-only. Generic remote file tools cann
192
194
 
193
195
  Direct processes use argv without shell parsing. Shell expansion is available only through the explicit shell tool.
194
196
 
195
- Process counts, stdin, output, timeouts, retained sessions, and tool-call concurrency are bounded. Timeout, cancellation, disconnect, daemon replacement, and shutdown use process-tree termination with bounded graceful and forced phases.
197
+ 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.
196
198
 
197
- Interactive process sessions die with runtime disconnect or replacement. Retained output sessions and process control are bound to account, account version, OAuth client, and refresh family.
199
+ 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.
198
200
 
199
201
  The daemon does not enforce universal CPU, memory, disk, or network quotas. An authorized owner process can still exhaust host or external resources.
200
202
 
201
203
  ## Local resources and managed jobs
202
204
 
203
- Registered resources store canonical paths and bounded metadata, not file contents. Private resource files require restrictive permissions on Unix-like systems unless explicitly overridden.
205
+ Registered resources store canonical paths and bounded metadata, not file contents. Private resource files require restrictive permissions on Unix-like systems unless explicitly overridden. Generated SSH private-key bytes are never returned through MCP. If state registration fails after a new key pair is created, both files are removed; failure to complete that rollback is surfaced as a compound error rather than silently leaving an unregistered private key.
204
206
 
205
207
  At job acceptance, referenced resources are reopened, bounded, hashed, and copied into a private runtime area. Changed or unavailable resources fail closed. Environment injection may be visible to same-user process inspection; private file-path substitution or stdin is generally safer.
206
208
 
@@ -224,7 +226,7 @@ Destructive state removal validates marker files, selected workspace, known layo
224
226
 
225
227
  Only one verified daemon is active. Candidates have preflight, hello, readiness, and liveness deadlines. A candidate cannot displace the current daemon before authentication and end-to-end readiness.
226
228
 
227
- Pending calls are bounded, socket-bound, request-bound, timed out, cancellable, and recoverable only for the same verified daemon instance during the documented reconnect grace period.
229
+ Pending calls are bounded, socket-generation-bound, request-bound, timed out, cancellable, and recoverable only for the same verified daemon instance during the documented reconnect grace period. Worker transport/liveness invalidation is retryable and cannot by itself stop the daemon process; unknown protocol messages, authentication rejection, and identity/version mismatch remain fatal.
228
230
 
229
231
  Request bodies, messages, traversals, files, output, OAuth stores, nonce stores, sessions, and failure identities are bounded. These controls do not replace Cloudflare MFA, WAF/rate limits, billing alerts, or external cost controls.
230
232
 
@@ -261,4 +263,4 @@ See [docs/AUDIT.md](docs/AUDIT.md) for historical findings and residual limitati
261
263
 
262
264
  SSE event identifiers are cursors, not bearer credentials. Recovery requires a valid OAuth Bearer/DPoP request and the original signed `MCP-Session-Id`; a cursor from another token or session is reported as not found. `GET /mcp` only replays an existing stream, while POST always represents new work.
263
265
 
264
- The Worker stores a bounded terminal response for two minutes to bridge transport loss. Records are limited to 64 streams and 1.5 MiB each and include SHA-256 integrity metadata. This protects against accidental storage corruption, not compromise of the Worker account or Durable Object. A pending record found after Worker restart is reported as an ambiguous execution outcome because the local side effect may already have occurred; clients must reconcile before retrying non-idempotent tools.
266
+ The Worker stores bounded stream and call state to bridge transport loss. Active streamed-call records contain opaque ownership/generation identifiers, request correlation, deadlines, and no tool arguments; terminal records retain at most 1.5 MiB for two minutes and include SHA-256 integrity metadata. The index is limited to 64 streams. This protects continuity and detects accidental storage corruption, not compromise of the Worker account or Durable Object. A valid persisted call remains pending after Worker restart. Only a pending stream record with no durable call owner is reported as an ambiguous execution outcome; clients must reconcile before retrying a non-idempotent tool in that case.
@@ -0,0 +1,45 @@
1
+ (() => {
2
+ const SAFE_EXACT = new Set([
3
+ "unsupported browser tab action",
4
+ "no active browser tab",
5
+ "navigate requires url",
6
+ "trusted input is unavailable for this action",
7
+ "trusted input currently requires the top frame; use input_mode=dom for a subframe",
8
+ "this page cannot be scripted by a browser extension",
9
+ "page automation module is unavailable",
10
+ "browser request cancelled",
11
+ "tab closed during navigation wait",
12
+ "element reference is stale; inspect the page again",
13
+ "element did not become geometrically stable before timeout",
14
+ "matched element is not a file input",
15
+ "no form or submit control found",
16
+ "matched element is not associated with a form",
17
+ "select option was not found",
18
+ "trusted input requires a valid tab",
19
+ "trusted input target has no usable viewport point",
20
+ ]);
21
+
22
+ function publicError(error) {
23
+ const message = String(error?.message || error || "");
24
+ if (message.startsWith("unknown browser method:")) return "unknown browser method";
25
+ if (message.startsWith("invalid CSS selector:")) return "invalid CSS selector";
26
+ if (/^selector matched \d+ elements;/.test(message)) return "selector matched multiple elements; use ref or index to disambiguate";
27
+ if (message.startsWith("browser wait timed out")) return "browser wait timed out";
28
+ if (message.startsWith("trusted browser input may have been partially dispatched")) {
29
+ return "trusted browser input may have been partially dispatched; the action outcome is unknown. Inspect the page before retrying.";
30
+ }
31
+ if (message.startsWith("form submission failed after")) {
32
+ return "form submission failed after partial changes; inspect the page before retrying";
33
+ }
34
+ if (message.startsWith("element was not actionable before timeout")) return "element was not actionable before timeout";
35
+ if (message.startsWith("element was not clickable before timeout")) return "element was not clickable before timeout";
36
+ if (message.startsWith("unsupported element action:")) return "unsupported element action";
37
+ if (message.startsWith("unsupported key modifier:") || message.startsWith("unsupported key:")) return "unsupported keyboard input";
38
+ return SAFE_EXACT.has(message) ? message : "browser operation failed";
39
+ }
40
+
41
+ Object.defineProperty(globalThis, "__machineBridgeBrowserErrorBoundary", {
42
+ value: Object.freeze({ publicError }),
43
+ configurable: false,
44
+ });
45
+ })();
@@ -368,7 +368,7 @@
368
368
  ...fallback.result,
369
369
  input_mode: "dom",
370
370
  trusted_input_fallback: true,
371
- fallback_reason: String(error?.message || error).slice(0, 500),
371
+ fallback_reason: "trusted_input_unavailable_before_dispatch",
372
372
  };
373
373
  }
374
374
  }
@@ -30,6 +30,6 @@
30
30
  "action": {
31
31
  "default_title": "Machine Bridge Browser"
32
32
  },
33
- "version_name": "3.0.0-beta.17",
33
+ "version_name": "3.0.0-beta.21",
34
34
  "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxryYkpZhq8+VAQLHcGS9BAHQcyKX8RHGIpIwvtIVRU/rcOcE0bNdnM0aZJ/h6xWQsGDHlhvjT2+1aJaAn/9k8473BRWajzVXld961CdHYVFVHoce2hHiSJ0xydWrHMMZhAm0mN0UzjEpgZ0tMw209efcZHIvSwuxhteZMRy4kyiVjwFlOf5oXFCxRuCJnPj3AK9CmCf4XgEBuPIJ0TZmjGHOOdBvJmbCNnAWXYEo5/mf7MfCGhV4IJ1hNuhpoNQfOFKMUcw9/v/IpT62XpfXdGYTfGYCmCjC+gntK1spbkr2P4/2+sYMQtLpse71mpSNGXfcf3abU55Vpn+gncSxRQIDAQAB"
35
35
  }
@@ -1,13 +1,12 @@
1
- importScripts("devtools-input.js", "browser-operations.js");
2
-
1
+ importScripts("browser-error-boundary.js", "devtools-input.js", "browser-operations.js");
3
2
  let socket = null;
4
3
  let reconnectTimer = null;
5
4
  let reconnectAttempt = 0;
6
5
  const MAX_RESULT_BYTES = 7 * 1024 * 1024;
7
6
  const BROWSER_EXTENSION_PROTOCOL = 3;
8
7
  const HANDSHAKE_TIMEOUT_MS = 3000;
8
+ const MAX_ACTIVE_REQUESTS = 32;
9
9
  const activeRequests = new Map();
10
-
11
10
  chrome.runtime.onInstalled.addListener(() => { ensureReconnectAlarm(); void connectFromStorage(); });
12
11
  chrome.runtime.onStartup.addListener(() => { ensureReconnectAlarm(); void connectFromStorage(); });
13
12
  chrome.alarms.onAlarm.addListener((alarm) => {
@@ -21,7 +20,6 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
21
20
  return true;
22
21
  });
23
22
  chrome.action.onClicked.addListener((tab) => void handleActionClick(tab));
24
-
25
23
  async function handleActionClick(tab) {
26
24
  if (tab?.id && parsePairingPage(tab.url)) {
27
25
  await confirmRepairFromTab(tab);
@@ -31,12 +29,10 @@ async function handleActionClick(tab) {
31
29
  const pairUrl = pairingUrlFromEndpoint(current.endpoint) || "http://127.0.0.1:39393/pair";
32
30
  await chrome.tabs.create({ url: pairUrl });
33
31
  }
34
-
35
32
  function pairingUrlFromEndpoint(endpoint) {
36
33
  const parsed = parseBrokerEndpoint(endpoint);
37
34
  return parsed ? `http://127.0.0.1:${parsed.port}/pair` : "";
38
35
  }
39
-
40
36
  function parsePairingPage(value) {
41
37
  let parsed;
42
38
  try { parsed = new URL(String(value || "")); } catch { return null; }
@@ -299,6 +295,12 @@ async function handleMessage(ws, raw, onReady = () => {}) {
299
295
  closeSocketQuietly(ws, 1002, "duplicate browser request id");
300
296
  return;
301
297
  }
298
+ if (activeRequests.size >= MAX_ACTIVE_REQUESTS) {
299
+ if (!sendResponse(ws, message.id, false, null, "too many concurrent browser requests")) {
300
+ closeSocketQuietly(ws, 1011, "browser overload response delivery failed");
301
+ }
302
+ return;
303
+ }
302
304
  const state = { cancelled: false, timeoutMs: browserOperations().boundedRequestTimeout(message.timeout_ms), socket: ws };
303
305
  activeRequests.set(message.id, state);
304
306
  try {
@@ -309,7 +311,7 @@ async function handleMessage(ws, raw, onReady = () => {}) {
309
311
  closeSocketQuietly(ws, 1011, "browser response delivery failed");
310
312
  }
311
313
  } catch (error) {
312
- if (!state.cancelled && !sendResponse(ws, message.id, false, null, String(error?.message || error).slice(0, 2000))) {
314
+ if (!state.cancelled && !sendResponse(ws, message.id, false, null, globalThis.__machineBridgeBrowserErrorBoundary.publicError(error))) {
313
315
  closeSocketQuietly(ws, 1011, "browser error delivery failed");
314
316
  }
315
317
  } finally {
@@ -336,7 +338,6 @@ function sendResponse(ws, id, ok, result, error = "") {
336
338
  return sendSocketQuietly(ws, payload);
337
339
  }
338
340
 
339
-
340
341
  function browserOperations() {
341
342
  const api = globalThis.__machineBridgeBrowserOperations;
342
343
  if (!api || typeof api.dispatch !== "function") throw new Error("browser operations module is unavailable");
@@ -64,7 +64,7 @@ See [Session instructions, skills, commands, and capability discovery](AGENT_CON
64
64
 
65
65
  `BrowserBridgeManager` owns only connection orchestration for the loopback HTTP/WebSocket broker, owner/client failover, routed requests, cancellation, extension replacement, and start/stop generation control. Every asynchronous startup boundary rechecks the generation so a listener or upstream socket cannot appear after `stop()` has invalidated that start. `browser-operation-service.mjs` owns MCP-facing browser argument normalization, resource-backed values/uploads, form semantics, screenshot conversion, and status presentation. Extension version/capability parsing lives in the strict checked `browser-extension-protocol.mjs`; pairing files and local HTML/Host/Origin helpers live in `browser-pairing-store.mjs`. The first runtime for the machine-level state root becomes broker owner; additional workspaces and stdio runtimes authenticate to `/runtime` and proxy through the same extension socket. This preserves one extension pairing while allowing multiple local MCP runtimes.
66
66
 
67
- The packaged Manifest V3 extension runs in the user's existing Chromium profile. Its service worker is limited to pairing, transport, acknowledged protocol readiness, cancellation, and response routing. Fixed `browser-operations.js` owns tab lifecycle, aggregate frame/source budgets, waits, screenshots, and input-backend selection; fixed `page-automation.js` is injected into selected frames for snapshot-version-2 semantics, stable refs, bounded DOM/text traversal, actionability checks, open-Shadow-DOM traversal, structured DOM operations, multi-field forms, and resource-backed file inputs. Fixed `devtools-input.js` exposes only bounded mouse, keyboard, and text sequences through the Chromium debugger API; callers cannot select CDP methods. Trusted sessions attach for one action and detach in `finally`; DOM fallback is allowed only before any DevTools Input dispatch, preventing duplicate side effects after an ambiguous command failure. Protocol 3 requires bidirectional `hello`/`hello_ack` plus exact packaged-version and capability equality; pairing state and replacement are committed only after validation, so an invalid candidate cannot displace or overwrite the working configuration. The broker validates loopback hostnames, canonical extension IDs, matching pairing/broker ports, bearer subprotocols, message sizes, concurrency, and deadlines. Pairing material is owner-only and omitted from MCP/log output.
67
+ The packaged Manifest V3 extension runs in the user's existing Chromium profile. Its service worker is limited to pairing, transport, acknowledged protocol readiness, cancellation, bounded request lifecycle, and response routing. It independently caps active operations at 32 even though the local broker enforces the same ceiling. Fixed `browser-error-boundary.js` maps only an allowlist of actionable messages to the remote protocol; unclassified Chrome, DevTools, page, selector, URL, path, and account-shaped exception text becomes the fixed `browser operation failed` result. Fixed `browser-operations.js` owns tab lifecycle, aggregate frame/source budgets, waits, screenshots, and input-backend selection; successful trusted-input fallback reports a fixed reason rather than the local debugger exception. Fixed `page-automation.js` is injected into selected frames for snapshot-version-2 semantics, stable refs, bounded DOM/text traversal, actionability checks, open-Shadow-DOM traversal, structured DOM operations, multi-field forms, and resource-backed file inputs. Fixed `devtools-input.js` exposes only bounded mouse, keyboard, and text sequences through the Chromium debugger API; callers cannot select CDP methods. Trusted sessions attach for one action and detach in `finally`; DOM fallback is allowed only before any DevTools Input dispatch, preventing duplicate side effects after an ambiguous command failure. Protocol 3 requires bidirectional `hello`/`hello_ack` plus exact packaged-version and capability equality; pairing state and replacement are committed only after validation, so an invalid candidate cannot displace or overwrite the working configuration. The broker validates loopback hostnames, canonical extension IDs, matching pairing/broker ports, bearer subprotocols, message sizes, concurrency, and deadlines. Pairing material is owner-only and omitted from MCP/log output.
68
68
 
69
69
  See [Local application and browser automation](LOCAL_AUTOMATION.md).
70
70
 
@@ -106,24 +106,26 @@ The stdio server implements newline-delimited JSON-RPC over stdin/stdout. It neg
106
106
 
107
107
  ### Cloudflare Worker and Durable Object
108
108
 
109
- Public `/healthz`, `/`, and CORS preflight are answered by the outer Worker without Durable Object requests, so activation and doctor checks do not consume free-tier DO volume. All other requests route to one named Durable Object. It owns:
109
+ Public `/healthz`, `/`, discovery metadata, CORS preflight, and unknown-path 404s are answered by the outer Worker without Durable Object requests. Only an exact stateful route allowlist passes a burst guard and reaches one named Durable Object. It owns:
110
110
 
111
111
  - OAuth clients, authorization codes, hashed access-token records, an independently versioned hashed refresh-token store, and throttling metadata;
112
112
  - one active end-to-end-verified daemon WebSocket plus bounded candidate and probing sockets;
113
113
  - policy/tool metadata attached to the active socket;
114
- - a bounded in-memory map of pending daemon calls, with monotonic operation/reconnect deadlines projected onto Durable Object alarms and rechecked at every event boundary;
114
+ - a bounded in-memory map for JSON-only daemon calls whose initiating request still owns the terminal Promise;
115
+ - one short FIFO admission gate that computes the combined 32-call ceiling across the in-memory and persistent paths;
116
+ - a bounded persistent index for streamed daemon-call ownership, opaque connection generation, request correlation, result-transform metadata, and monotonic operation/reconnect deadlines;
115
117
  - bounded resumable MCP delivery metadata and terminal responses for recently disconnected SSE clients.
116
118
 
117
- `BridgeRoom` owns Durable Object routing, MCP authorization/dispatch, daemon WebSocket lifecycle, pending relay-call composition, cancellation, and resumable state. `mcp-stream-proxy.ts` owns the outer-Worker transport adapter: it strips public internal-control headers, obtains a bounded authenticated descriptor, creates the client-facing SSE stream, and waits for the terminal result through one authenticated internal WebSocket subscription. `mcp-stream-channel.ts` owns Durable Object subscriber registration, single-subscriber replacement, and hibernation-safe terminal push. `mcp-access.ts` owns shared Bearer/DPoP authorization for POST and recovery GET. `mcp-resumption-http.ts` owns recovery routing and signed session/protocol binding and returns descriptors rather than a long-lived response. `mcp-resumption.ts` owns stream admission, transaction ordering, immediate pending/terminal polls, expiry, replay, and lifecycle state; `mcp-resumption-records.ts` owns the compact metadata index, terminal-message bounds, serialization, and SHA-256 integrity metadata. `mcp-stream.ts` owns SSE sequence-zero/sequence-one framing and heartbeats. `mcp-jsonrpc.ts` owns JSON-RPC shape validation, result/error framing, MCP tool-result projection, session-instruction bounds, and protocol-header validation. `websocket-protocol.ts` owns record validation plus best-effort send/close/rejection helpers. `OAuthController` owns OAuth-store pruning, registration throttling, authorization submission, account-admin routing, token exchange, access-token verification, and the serialization queue for OAuth mutations. Worker-internal TypeScript imports use explicit `.ts` specifiers and JSON import attributes, so the same modules are directly executable under the pinned Node runtime and bundled by Wrangler.
119
+ `BridgeRoom` owns stateful routing, MCP authorization/dispatch, daemon WebSocket lifecycle, cancellation, and composition of the extracted state machines. `worker-entry.ts` owns outer-Worker static routing, stateful admission, SSE proxy selection, and privacy-safe gateway failures; `worker-static-routes.ts` and `worker-metadata.ts` own stateless public responses; `worker-edge-guard.ts` owns the burst guard and quota classification. `mcp-stream-proxy.ts` owns public SSE adaptation, while `mcp-stream-subscription.ts` owns bounded terminal-subscription retries and payload validation. `mcp-stream-channel.ts` owns Durable Object subscriber registration, single-subscriber replacement, and hibernation-safe terminal push. `mcp-access.ts` owns shared Bearer/DPoP authorization for POST and recovery GET. `mcp-resumption-http.ts` owns recovery routing and signed session/protocol binding and returns descriptors rather than a long-lived response. `mcp-resumption.ts` owns stream admission, immediate pending/terminal polls, expiry, replay, and guarded terminal writes; `mcp-resumption-records.ts` and `mcp-resumption-index.ts` own record validation, compact indexing, terminal-message bounds, integrity metadata, pruning, and eviction. `mcp-pending-call-store.ts` and `mcp-pending-call-records.ts` own persistent streamed-call identity, capacity, request-key uniqueness, deadlines, detach/rebind, and connection-generation checks. `durable-stream-calls.ts` owns streamed-call cancellation, timeout, settlement, and combined observability; `runtime-alarm.ts` and `runtime-alarm-storage.ts` own earliest-deadline projection and coalesced alarm writes. `daemon-sockets.ts` owns socket role transitions, while `daemon-socket-attachment.ts` owns bounded attachment decoding. `mcp-stream.ts` owns SSE framing and heartbeats. `mcp-jsonrpc.ts` owns JSON-RPC shape validation, result/error framing, MCP tool-result projection, session-instruction bounds, and protocol-header validation. `websocket-protocol.ts` owns record validation plus best-effort send/close/rejection helpers. `OAuthController` owns OAuth-store pruning, registration throttling, authorization submission, account-admin routing, token exchange, access-token verification, and the serialization queue for OAuth mutations. Worker-internal TypeScript imports use explicit `.ts` specifiers and JSON import attributes, so the same modules are directly executable under the pinned Node runtime and bundled by Wrangler.
118
120
 
119
- The Worker verifies OAuth, validates MCP envelopes and optional protocol headers, converts `tools/call` into WebSocket messages, correlates explicit cancellation by access-token hash, signed MCP session, and JSON-RPC ID, and formats text/structured/image results. An HTTP/SSE disconnect is transport disposal, not MCP cancellation. For streamed daemon tools, `BridgeRoom` commits a recovery record, registers an event-settled pending call, sends the daemon envelope, and immediately returns an internal descriptor without retaining a terminal Promise. The later WebSocket result, explicit cancellation, timeout, send failure, or reconnect-grace expiry persists the terminal JSON-RPC envelope. JSON-only calls retain the ordinary Promise path. The outer Worker sends `stream:0`, heartbeats, and `stream:1`; the Durable Object accepts one hibernatable subscriber WebSocket per stream and pushes the terminal JSON-RPC envelope once. Authenticated `GET /mcp` with `Last-Event-ID` resumes only the original OAuth-token/MCP-session stream; POST always creates new work. Public requests cannot select internal descriptor/subscribe modes because the outer boundary removes those headers before forwarding. At most 64 records and 1.5 MiB of terminal JSON per record are retained for two minutes. The recovery store tracks active stream identifiers, not live Promises; a transient terminal map is used only when persistence fails. If the Durable Object restarts with a pending record but no active owner, recovery reports that side effects may have occurred and requires reconciliation before retry. It has no local filesystem or process API.
121
+ The Worker verifies OAuth, validates MCP envelopes and optional protocol headers, converts `tools/call` into WebSocket messages, correlates explicit cancellation by access-token hash, signed MCP session, and JSON-RPC ID, and formats text/structured/image results. An HTTP/SSE disconnect is transport disposal, not MCP cancellation. For streamed daemon tools, `BridgeRoom` first commits the recovery record, then transactionally attaches the durable call ID, daemon-process identity, per-WebSocket connection generation, client request key, operation deadline, and optional bounded result transform before sending the daemon envelope and returning an internal descriptor. The later WebSocket result, explicit cancellation, operation timeout, send failure, or reconnect-grace expiry converges through one guarded terminal write. A Durable Object restart can therefore rediscover the call and keep it pending; only a pending stream record with no durable call owner becomes the restart-ambiguity result. JSON-only calls retain the ordinary in-event Promise path. The outer Worker sends `stream:0`, heartbeats, and `stream:1`; the Durable Object accepts one hibernatable subscriber WebSocket per stream and pushes the terminal JSON-RPC envelope once. Authenticated `GET /mcp` with `Last-Event-ID` resumes only the original OAuth-token/MCP-session stream; POST always creates new work. Public requests cannot select internal descriptor/subscribe modes because the outer boundary removes those headers before forwarding. At most 64 records and 1.5 MiB of terminal JSON per record are retained for two minutes. The recovery store tracks stream and call ownership, not live Promises; a transient terminal map is used only when persistence fails. It has no local filesystem or process API.
120
122
 
121
123
 
122
124
  ### Daemon device authentication
123
125
 
124
126
  The local daemon uses a long-term P-256 device root to certify a 24-hour ephemeral session key. Worker deployment receives only the root public JWK. The default provider on every platform is an owner-only portable private JWK. macOS can instead use non-exportable Secure Enclave material only when `MBM_MACOS_TRUST_BROKER` identifies an app-like broker whose Apple signature, Team ID, canonical non-symlink executable path with no group/other write access, provisioning-backed Keychain capability, and actual create/delete key probe all validate. The enrolled state binds that broker identity, Keychain tag, and public key. One root signature at daemon startup certifies the in-memory session key. Every WebSocket attempt then carries a short-lived preflight signed by the session key and bound to Worker origin, package version, nonce, timestamp, and the root certificate. The nonce is consumed once through bounded Durable Object state. After upgrade, the Worker issues a fresh challenge and accepts tools only after a second session signature binds the challenge, origin, package version, daemon instance ID, and timestamp. Reconnects reuse the in-memory key without touching the root. End-to-end readiness still requires an ordinary relay probe before a verified candidate replaces the incumbent.
125
127
 
126
- The primary OAuth store separates trusted client registrations and named accounts from authorization codes and access-token records. A separate versioned Durable Object key owns refresh-token families, consumed-token markers, and family revocation. A `client_id` identifies an MCP application and redirect URIs; account records identify the authorized human or service identity. Codes and tokens bind client ID, account ID, account version, role, scope, resource, deployment token version, family identity, and expiration where applicable. Only hashes of bearer tokens are persisted. Access tokens last fifteen minutes; refresh tokens rotate, expire after fourteen idle days or thirty absolute family days, and reuse of a consumed refresh token revokes the complete family, including active access tokens. The Worker carries account, client, and refresh-family identity with every relayed call so long-lived runtime objects cannot cross principals. One bridge-specific Durable Object and one local runtime remain the normal topology for a workspace/trust domain; see [MULTI_ACCOUNT.md](MULTI_ACCOUNT.md).
128
+ The primary OAuth store separates trusted client registrations and named accounts from authorization codes and access-token records. A separate versioned Durable Object key owns refresh-token families, consumed-token markers, and family revocation. A `client_id` identifies an MCP application and redirect URIs; account records identify the authorized human or service identity. Codes and tokens bind client ID, account ID, account version, role, scope, resource, deployment token version, family identity, and expiration where applicable. Only hashes of bearer tokens are persisted. Access tokens last fifteen minutes; refresh tokens rotate and expire after fourteen idle days or thirty absolute family days. An identity-equivalent retry may reproduce the same deployment-keyed HMAC replacement pair at most twice inside a 30-second concurrency window without extending its expiration; retry-budget exhaustion is throttled, while reuse after that window revokes the complete family, including active access tokens. The Worker carries account, client, and refresh-family identity with every relayed call so long-lived runtime objects cannot cross principals. One bridge-specific Durable Object and one local runtime remain the normal topology for a workspace/trust domain; see [MULTI_ACCOUNT.md](MULTI_ACCOUNT.md).
127
129
 
128
130
  The daemon attachment deliberately omits workspace path/name/hash and process ID. Explicit authenticated tools may return workspace metadata according to local path-display policy.
129
131
 
@@ -168,13 +170,13 @@ Remote OAuth binds each code, access token, and refresh token to a named Machine
168
170
  3. The Worker validates authorization parameters before displaying a password form.
169
171
  4. The user verifies client name and redirect URI and enters a Machine Bridge account name and password.
170
172
  5. The Worker creates a five-minute code bound to client, redirect, resource, normalized scope, and PKCE challenge.
171
- 6. A valid verifier exchanges the one-time code for an expiring access token and refresh token; only their hashes are stored. A refresh request is bound to the original public client, account, scope, resource, and deployment token version, and atomically replaces the refresh token so replay returns `invalid_grant`.
172
- 7. The MCP client initializes against the sole current protocol version; an obsolete client must upgrade rather than enter a legacy execution path. The Worker returns a stateless HMAC-bound `MCP-Session-Id`, and later request/cancellation correlation is scoped by OAuth token, MCP session, JSON-RPC id type, and id value. Two clients may therefore reuse the same JSON-RPC id concurrently without collision. Sessionless POSTs remain independent and are not inserted into a token-global cancellation index. When the daemon advertises `session_bootstrap`, the Worker requests bounded local instructions and appends them to the initialization result; failure degrades to static instructions.
173
+ 6. A valid verifier exchanges the one-time code for an expiring access token and refresh token; only their hashes are stored. A refresh request is bound to the original public client, account, scope, resource, account version/role, deployment token version, and optional DPoP key. Rotation derives one replacement pair from the consumed token and the private deployment token version, then permits at most two identity-equivalent responses with that exact pair during a 30-second concurrency window; over-budget retries are throttled, and replay after the window revokes the family.
174
+ 7. The MCP client initializes against the sole current protocol version; an obsolete client must upgrade rather than enter a legacy execution path. The Worker returns a stateless HMAC-bound `MCP-Session-Id`, and later request/cancellation correlation is scoped by OAuth token, MCP session, JSON-RPC id type, and id value. Two clients may therefore reuse the same JSON-RPC id concurrently without collision. Sessionless POSTs remain independent and are not inserted into a token-global cancellation index. When the daemon advertises `session_bootstrap`, the Worker requests bounded local instructions and appends them to the initialization result; failure degrades to static instructions and increments the bounded `session_bootstrap_failed` observability counter.
173
175
  8. A new daemon first authenticates as a bounded `probing` socket. The Worker sends a random `relay_probe`; the local runtime returns it through the normal session-bound result-delivery path; only the matching result produces `ready_ack`, promotion to the active daemon, and safe replacement of an incumbent connection.
174
- 9. `tools/list` is derived only from the active end-to-end-verified daemon; without one, only `server_info` is advertised.
175
- 10. `tools/call` receives a random relay call ID and is bound to the current daemon socket, that daemon process's ephemeral instance identifier, and the authenticated client request key. When the client accepts `text/event-stream`, `BridgeRoom` commits recovery state, registers an event-settled pending call, sends the daemon envelope, and returns a bounded descriptor immediately; the outer Worker owns the SSE priming frame, keepalives, and one internal terminal subscription. No unresolved terminal Promise or Durable Object `waitUntil` owns the dispatch. JSON-only clients retain the single terminal response.
176
+ 9. `tools/list` is a stable package-and-account-role discovery catalog and declares `listChanged: false`; a brief relay interruption does not mutate it. `server_info.authorization.effective_tools` is the live daemon/account intersection and is the authority diagnostic.
177
+ 10. `tools/call` receives a random relay call ID and is bound to the daemon process's ephemeral instance identifier, a random per-WebSocket connection generation, and the authenticated client request key. When the client accepts `text/event-stream`, `BridgeRoom` commits recovery state plus durable call ownership and deadlines before sending the daemon envelope, then returns a bounded descriptor immediately; the outer Worker owns the SSE priming frame, keepalives, and one internal terminal subscription. No unresolved terminal Promise, JavaScript timer, or Durable Object `waitUntil` owns the streamed dispatch. JSON-only clients retain the single terminal response.
176
178
  11. The runtime validates policy and arguments, executes the tool, and returns a bounded result. Closing or losing the HTTP response stream only makes that stream unwritable; it is not an MCP cancellation and does not remove the pending request.
177
- 12. If the socket remains ready, the Durable Object accepts the result only from that socket. If it drops, the Worker detaches the pending call for at most two minutes and accepts completion only after a replacement socket with the same daemon-process identifier has passed the end-to-end readiness probe. The local runtime preserves the operation and queues a completion over the same shared interval. The Worker pauses the record's remaining normal deadline while detached and resumes it after same-instance rebinding, so connected calls are not granted an unconditional recovery extension.
179
+ 12. If the socket remains ready, the Durable Object accepts the result only from that connection generation. If it drops, the Worker durably detaches the streamed call for at most two minutes and accepts completion only after a replacement socket with the same daemon-process identifier has passed the end-to-end readiness probe and atomically acquired a new connection generation. A delayed result or close event from the old socket cannot settle or detach the rebound call. The local runtime preserves the operation and queues a completion over the same shared interval. The Worker pauses the record's remaining normal deadline while detached and resumes it after same-instance rebinding, so connected calls are not granted an unconditional recovery extension.
178
180
  13. Only a matching session-scoped `notifications/cancelled` request removes the pending indexes and sends best-effort cancellation to a connected daemon. Local completion that races with explicit cancellation is discarded. On every readiness handover, the Worker first sends an authoritative bounded `resume_calls` set; the runtime cancels active calls and queued results absent from that set before accepting `ready_ack`. A request explicitly cancelled while disconnected therefore cannot be revived by a fast reconnect.
179
181
  14. If same-instance readiness does not return before the grace deadline, the Worker rejects the detached request and the local runtime cancels ordinary calls, terminates their process trees, and discards queued results. A newly started daemon has a different instance identifier and cannot inherit prior calls.
180
182
  15. `start_job` is different: after durable acceptance, the detached runner is no longer bound to the relay call or socket. Later cancellation uses `cancel_job` or the local CLI.
@@ -231,9 +233,9 @@ The default `full` profile passes the complete parent environment. Isolated envi
231
233
 
232
234
  Large object results use `structuredContent` as the authoritative representation. The human text mirror is complete only below the shared 16 KiB projection threshold; above it, both local stdio and the Worker return the same compact byte-count/field summary instead of serializing the object a second time. `process-result-projection.mjs`, `process-output-stream.mjs`, and the shared result projector keep lifecycle, byte retention, and MCP presentation as separate boundaries.
233
235
 
234
- Startup-lock waits, daemon takeover, process-session reads, managed-job recovery handoff, browser/page waits, application-cache freshness, and in-memory duration metrics use monotonic elapsed time, so wall-clock correction cannot extend or prematurely terminate their configured duration. Persisted timestamps and retention/credential expiry continue to use wall time. Process sessions retain bounded byte buffers with monotonic offsets, accept bounded stdin, support short output/exit waits, and are capped per runtime. Valid UTF-8 is returned as text; byte slices that are not valid UTF-8 also include lossless base64 data. Head/tail previews trim incomplete UTF-8 boundary code points instead of introducing replacement characters. Session IDs are random. Running sessions are killed on runtime stop, remote disconnect, or daemon replacement.
236
+ Startup-lock waits, daemon takeover, process-session reads, managed-job recovery handoff, browser/page waits, application-cache freshness, and in-memory duration metrics use monotonic elapsed time, so wall-clock correction cannot extend or prematurely terminate their configured duration. Persisted timestamps and retention/credential expiry continue to use wall time. Process sessions retain bounded byte buffers with monotonic offsets, accept bounded stdin, support short output/exit waits, and are capped per runtime. Valid UTF-8 is returned as text; byte slices that are not valid UTF-8 also include lossless base64 data. Head/tail previews trim incomplete UTF-8 boundary code points instead of introducing replacement characters. Session IDs are random. Running sessions are killed on runtime stop or non-recoverable daemon replacement; a transient same-process relay disconnect enters bounded recovery instead of being treated as cancellation.
235
237
 
236
- Child processes run in a separate process group where supported. Timeout, cancellation, disconnect, and replacement send termination to process trees, with a referenced forced-escalation timer that remains alive even when the direct child exits before a resistant descendant. Windows uses tree-aware task termination.
238
+ Child processes run in a separate process group where supported. Timeout, explicit cancellation, reconnect-grace expiry, runtime shutdown, and non-recoverable replacement send termination to process trees, with a referenced forced-escalation timer that remains alive even when the direct child exits before a resistant descendant. POSIX ownership is captured before `SIGTERM`, refreshed immediately afterward to include descendants created near the timeout boundary, and revalidated before `SIGKILL` by PID, process start time, and process-group ID. If a full process-table snapshot is unavailable, each captured PID is queried directly; ambiguous identity or PID reuse still fails closed. Windows uses tree-aware task termination.
237
239
 
238
240
  Managed jobs use the same argv/environment primitives but a different lifecycle. Each job is capped at 16 main and 16 finally steps, 50 retained jobs, 64 registered resources, 8 MiB of referenced resource bytes, 512 KiB of temporary-file content, and bounded per-step output. They are non-interactive. Resource paths/stdin/environment are injected only inside the runner. Exact resource output redaction is defense in depth; discard capture is the strong option when a command may echo credentials.
239
241
 
@@ -249,13 +251,13 @@ Worker-name mutation is a separate identity transition. Existing state rejects a
249
251
 
250
252
  The local `RelayConnection` treats proxy selection, transport construction, WebSocket open, authentication, end-to-end readiness, and outage recovery as separate states. The shared proxy module maps WebSocket targets to standard HTTP(S) environment-proxy resolution, honors `NO_PROXY`, rejects non-HTTP(S) proxy schemes, and creates the proxy agent without exposing its URL or credentials. Invalid proxy configuration is a fatal configuration error rather than a retryable outage.
251
253
 
252
- A connection-attempt deadline terminates sockets stuck in `CONNECTING`. After open, the daemon sends `hello`; `hello_ack` establishes an authenticated relay generation and starts heartbeats, but does not resolve startup or advertise readiness. The Worker then sends a random `relay_probe`. Its result must traverse the local runtime dispatcher and `sendForSession` on that generation before `ready_ack` marks the connection usable. The daemon rejects ordinary tool calls before that state and rejects a premature readiness acknowledgement without locally recorded probe delivery. Independent handshake and readiness deadlines terminate candidates that authenticate but cannot return results. Once ready, application heartbeats require inbound activity; a silent half-open socket is terminated and reconnected. Outage reminders run on their own exponential-backoff timer rather than depending on another transport callback.
254
+ A connection-attempt deadline terminates sockets stuck in `CONNECTING`. After open, the daemon sends `hello`; `hello_ack` establishes an authenticated relay generation and starts heartbeats, but does not resolve startup or advertise readiness. The Worker then sends a random `relay_probe`. Its result must traverse the local runtime dispatcher and `sendForSession` on that generation before `ready_ack` marks the connection usable. The daemon rejects ordinary tool calls before that state and rejects a premature readiness acknowledgement without locally recorded probe delivery. Independent handshake and readiness deadlines terminate candidates that authenticate but cannot return results. Once ready, application heartbeats require inbound activity; a silent half-open socket is terminated and reconnected. Worker error codes `daemon_transport_error` and `daemon_liveness_timeout` are connection-recovery signals, not protocol incompatibility: they terminate only the current socket, run normal disconnect cleanup, and enter bounded reconnect. The same classification is applied to the close frame if the preceding error frame is lost. Failure to send `hello`, or failure to deliver a readiness-probe result because its socket/session ended, follows the same transport-recovery path rather than being promoted to authentication or protocol failure. Unknown Worker errors, authentication rejection, malformed readiness sequencing, and identity/version mismatch remain fatal. Outage reminders run on their own exponential-backoff timer rather than depending on another transport callback.
253
255
 
254
256
  Reconnect uses bounded exponential backoff with jitter. Brief self-healing interruptions are debug-only. An unresolved outage is promoted to a rate-limited warning after a grace period, and recovery produces one summary. Raw close codes and reason strings remain debug-only.
255
257
 
256
258
  The Worker stores socket transitions in `DaemonSocketRegistry`: `candidate` before hello, `probing` after authentication, `daemon` only after the end-to-end result probe, and `expired` after terminal failure. Durable Object alarms enforce separate hello, readiness, and steady-state liveness deadlines across hibernation. A healthy incumbent remains active while a replacement is probed; a malformed, silent, incompatible, or identity-mismatched replacement is closed without displacing it. Only a verified candidate receives `ready_ack` and then replaces the old socket. Ready daemons stay live only while inbound traffic refreshes `lastSeenAt`; silent half-open or hibernation-restored sockets are reclaimed instead of advertising `daemon.connected` while tool calls time out.
257
259
 
258
- Each daemon process generates a random bounded `instance_id` at startup and includes it in every reconnect hello. Pending calls normally retain their assigned socket. On an unexpected socket loss, only those records are detached and the shared two-minute relay contract bounds recovery. During verified same-instance handover, the Worker transfers both already-detached calls and still-attached calls from the incumbent socket to the replacement before closing the incumbent; this prevents the asynchronous close event from creating a detached call after the only rebind pass. If replacement acknowledgement fails, ownership is restored to the still-open same-instance incumbent. Another process cannot inherit or resolve these calls. The local runtime mirrors that state machine by preserving active calls and completed-result envelopes until relay readiness returns. Before `ready_ack`, the Worker sends the exact IDs that still have remote waiters; the runtime cancels everything else and only then replays retained results through the verified socket. Grace expiry restores the terminal behavior: reject remote waiters, cancel local ordinary calls, terminate process trees, and discard undeliverable results. The shared execution envelope remains independent of reconnect grace. Worker operation countdown is paused while detached or transferred and resumed with its remaining budget, so handover cannot reset the normal timeout. In-memory timers are only the fast path: the earliest monotonic pending deadline is also scheduled as a Durable Object alarm, and every HTTP or WebSocket event performs a compensating overdue scan. A transient alarm-storage failure is observable but does not turn an already-dispatched operation into a false terminal failure; the next event scan remains the bounded recovery path. This does not make calls durable across daemon restart or machine failure; managed jobs remain the separate durable mechanism.
260
+ Each daemon process generates a random bounded `instance_id` at startup and includes it in every reconnect hello. Each accepted WebSocket additionally receives a random `connection_id` generation stored in its hibernation attachment. JSON-only pending calls retain an in-memory socket reference; streamed calls persist the opaque generation instead. On an unexpected socket loss, only calls owned by that generation are detached and the shared two-minute relay contract bounds recovery. During verified same-instance handover, the Worker transfers both already-detached and still-attached calls to the replacement generation before closing the incumbent. A delayed close or result from the incumbent fails the generation check and cannot mutate the rebound record. If replacement acknowledgement fails, ownership is restored to the still-open same-instance incumbent. Another process cannot inherit or resolve these calls. The local runtime mirrors that state machine by preserving active calls and completed-result envelopes until relay readiness returns. Before `ready_ack`, the Worker sends the exact IDs that still have remote waiters; the runtime cancels everything else and only then replays retained results through the verified socket. Grace expiry restores the terminal behavior: reject remote waiters, cancel local ordinary calls, terminate process trees, and discard undeliverable results. The shared execution envelope remains independent of reconnect grace. Worker operation countdown is paused while detached or transferred and resumed with its remaining budget, so handover cannot reset the normal timeout. The active stream record expiry is extended to cover each new reconnect deadline, remaining operation budget, and terminal replay window; repeated successful reconnect cycles cannot outlive and delete their own ownership record. JavaScript timers remain only the low-latency owner for JSON-only calls. Streamed operation and reconnect deadlines live in the durable call record, the earliest deadline is projected onto one Durable Object alarm, and every HTTP or WebSocket event performs a compensating overdue scan. A transient alarm-storage failure is observable but does not turn an already-dispatched operation into a false terminal failure; the next event scan remains the bounded recovery path. This does not make calls durable across daemon-process restart or machine failure; managed jobs remain the separate durable mechanism.
259
261
 
260
262
  ## Persistence
261
263
 
package/docs/AUDIT.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Security and privacy audit notes
2
2
 
3
+ ## 2026-07-27 version 3.0.0-beta.21 relay-continuity and repository audit
4
+
5
+ The reported symptom was a temporary loss of every local command surface, including `pwd`, followed by automatic daemon reconnection. Process evidence rejects the premise that the local daemon crashed: launchd retained one daemon PID, one start time, and `runs=1` throughout the observed interval. The failing layer was the relay/Worker connection and result-delivery path. Historical service logs also contain abnormal WebSocket closure and heartbeat-outage intervals that later recovered without a daemon restart. The exact network origin of each `1006` remains unknowable from those logs alone; a system VPN/TUN, proxy route, intermediary, or edge connection can all produce the same transport-level symptom.
6
+
7
+ A separate failure occurred during this audit: at 2026-07-27 05:24:33 UTC the local service emitted `remote relay protocol error`, exited with code 1, and launchd advanced from one run to two before reconnecting a replacement process. The retained log does not include the raw Worker error code, so that exact triggering frame cannot be reconstructed. Static and red-green analysis nevertheless found a concrete fatal-amplification family: `daemon_transport_error` and `daemon_liveness_timeout`, emitted by the Worker for transient socket failure, fell through the local `handleServerError` default and invoked `failPermanently`; a failed `hello` send was relabelled as authentication failure; and a readiness-probe result lost to an ending socket/session was relabelled as protocol violation. Beta.21 routes all three transport races through ordinary socket teardown and bounded reconnect, classifies error-frame and close-only forms consistently, uses WebSocket 1012 for transient Worker invalidation, and preserves permanent failure only for unknown/incompatible protocol, genuine authentication failure, malformed readiness sequencing, or identity/version mismatch.
8
+
9
+ The audit found two Machine Bridge amplification defects. First, the MCP initialize response declared `tools.listChanged=false`, but `tools/list` was derived from the momentary ready-daemon set. A brief relay interruption therefore withdrew every tool except `server_info` without the protocol notification required for a mutable catalog. Beta.21 makes discovery stable by package catalog and account role. This does not broaden authority: every call still requires a live end-to-end-ready daemon, daemon policy/tool permission, and account-role permission. `server_info.tool_delivery` now reports stable advertised and live effective scopes separately, and disconnected calls fail retryably with `unavailable` instead of mutating the catalog.
10
+
11
+ Second, streamed calls returned from their initiating Durable Object event but retained ownership in an in-memory pending registry backed by JavaScript timers. That model either prevents hibernation or loses ownership if the object is evicted; an alarm alone cannot recover an in-memory record. Beta.21 persists the call ID, daemon-process identity, random per-WebSocket connection generation, client request key, operation/reconnect deadline, and bounded result-transform metadata in the stream record before dispatch. Durable Object alarms and event-entry sweeps are the cross-event deadline owners. A restarted object can rediscover the call, the same verified daemon process can atomically rebind it, and a delayed close or result from the old socket fails the generation check. Success, rejection, cancellation, send failure, operation timeout, reconnect-grace expiry, and duplicate terminal delivery converge through one guarded terminal transaction. A FIFO admission gate enforces one 32-call ceiling across persistent and JSON-only calls. Active-record expiry advances over every new reconnect and remaining-operation budget, so repeated successful handovers cannot delete a still-owned call. JSON-only calls retain the prior bounded Promise/timer path.
12
+
13
+ The broader repository review refused line-budget exceptions and extracted outer Worker routing, pending-call persistence, record validation, stream-index maintenance, durable settlement, result projection, alarm storage, and socket-attachment decoding into focused modules. It removed the obsolete transient `registerEvent` settlement branch, added architecture rules forbidding its return, preserved Node strip-only TypeScript compatibility, and added fault-directed tests for restart recovery, corrupt records, fixed storage-write budgets, alarm expiry, stale generations, exactly-once settlement, stable discovery, and disconnected execution. Logging and privacy rules prohibit arguments, results, request keys, account identifiers, raw call/connection IDs, private paths, and subscriber payloads from operational diagnostics.
14
+
15
+ The workflow-level global verification deliberately repeated the complete project-native gate and exposed a separate real cleanup defect after two earlier green full runs. Under load, the POSIX process-tree ownership snapshot could miss a newly spawned descendant or the later full-table `ps` scan could fail; if the direct parent exited after `SIGTERM`, the conservative identity check then skipped `SIGKILL`, leaving an anti-`SIGTERM` descendant reparented to init. The fix refreshes process-group ownership after graceful termination and, during escalation, performs a targeted `ps -p` identity check for each captured PID when the full group scan yields no match. Five consecutive complete self-tests leave no descendant behind, while PID/start-time/PGID mismatch continues to suppress escalation.
16
+
17
+ `npm run check:fast` passes all 63 repository tasks after the implementation. Full, dependency, package, Worker dry-run, privacy-history, and workflow-bundle verification remain separate evidence and must pass before merge readiness is claimed. This source audit does not deploy a Worker, replace the running daemon, activate a candidate, rotate credentials, publish npm, push Git history, create a tag, or record live acceptance.
18
+
19
+ ## 2026-07-26 version 3.0.0-beta.18 account-continuity and quota audit
20
+
21
+ The repeated hosted-client message `We couldn't connect your account. Please try again.` occurred while the Worker, Durable Object, daemon, Wrangler login, and end-to-end doctor probes were healthy. The remaining credential path had a destructive concurrency assumption: refresh tokens rotated once, and any immediate second use revoked the complete family—including the replacement credentials just returned to a concurrent request. A network retry or two refreshes racing through the same hosted connector could therefore convert a recoverable response race into total account loss. Beta.18 records a bounded source snapshot for 30 seconds and uses a deployment-keyed, domain-separated HMAC to reproduce the original replacement pair for at most two identity-equivalent retry responses. Retries neither create another credential branch nor extend expiration; the Worker returns a retryable 429 after that budget, and retains whole-family revocation for replay after the grace window. Client, resource, scope, account/version/role, deployment token version, and DPoP binding are revalidated on every retry.
22
+
23
+ The broader quota review found that beta.17 removed time-proportional stream polling but still forwarded public discovery and random paths to the Durable Object, rewrote liveness alarms on every heartbeat, exposed Preview URLs, and rethrew outer failures as platform exceptions. Beta.18 moves all stateless metadata and 404s to the outer Worker, adds a stateful-route rate-limit binding, coalesces alarm writes, exposes logical storage-write/alarm counters, retries terminal subscription transport failure within a hard bound, converts unexpected edge failures to a privacy-safe structured 502, and keeps the automatically provisioned `workers.dev` endpoint as the complete default deployment path.
24
+
25
+ A second fault-directed pass found issues outside the original incident scope. Bounded request readers retained at most the configured bytes but continued draining the rest of an oversized stream, allowing attacker-controlled duration work. Invalid methods on known stateful routes reached the rate limiter and Durable Object before returning 405. Several write-path and recursive-walk checks treated every `lstat`/`opendir` error as “missing,” so permission or I/O failure could be evaluated under a false path state. Repeated platform degradation also emitted one log per request, and newly split security modules were absent from the critical-coverage gate. The final implementation cancels body readers as soon as a declared or observed limit is crossed, rejects invalid methods before stateful dispatch, distinguishes only `ENOENT` from real filesystem failure, exposes partial application/skill discovery warnings, suppresses repeated edge logs with a reported duplicate count, removes an unreachable revoked-family branch and duplicate stream `waitUntil`, and establishes coverage floors for every new boundary module.
26
+
27
+ A third pass across local administration, sensitive-resource rollback, and the browser extension found additional boundary defects. The account administration client accepted an unbounded response and silently converted invalid successful JSON into an empty object. Newly generated SSH key files could remain unregistered if state persistence and best-effort cleanup both failed, because cleanup errors were discarded. The browser extension returned arbitrary local/page exception text and debugger fallback details to the remote caller, relied only on the broker for its concurrency ceiling, and the edge error classifier followed cyclic `cause` chains without a depth limit. Skill-symlink warnings also risked embedding absolute paths through native filesystem messages. These paths now use bounded strict JSON, complete two-file key rollback with compound failure, a fixed browser error boundary and fallback reason, an independent 32-request extension ceiling, bounded cycle-aware error traversal, and coarse path-projected discovery warnings.
28
+
29
+ Tests cover schema-2 migration, two idempotent concurrent refresh retries that reproduce the original access/refresh pair, retry-budget exhaustion without family revocation, post-grace whole-family revocation, DPoP proof replay and client/resource/scope isolation, static-route/unknown-path/invalid-method DO bypass, rate-limit denial and fail-open binding outage, nested/cyclic/deep quota classification, sanitized and throttled gateway logs, immediate oversized-body cancellation, bounded strict administration responses, complete and incomplete SSH rollback, fail-closed filesystem state inspection, privacy-safe partial discovery reporting, fixed browser-error projection and independent concurrency, fixed normal stream request/write budgets, single keepalive registration, subscription retry bounds, and alarm set/no-op/delete behavior. This source audit does not claim that an in-Worker rate limiter can enforce the account-wide Workers Free daily limit against distributed traffic; it also records the deployment-wide per-location bucket as a localized availability trade-off.
30
+
31
+ No live Worker deployment, daemon replacement, npm publication, tag, push, or acceptance is implied by this source audit. The exact beta.18 candidate must be owner-activated and observed before acceptance.
32
+
3
33
  ## 2026-07-25 version 3.0.0-beta.16 pending-call recovery and boundary audit
4
34
 
5
35
  The reported incident exposed two separate failure domains. The host/connector layer returned `No shard mapper found` for a temporary high-replication backfill keyspace and then rejected even minimal MCP calls. The exact text did not exist in Machine Bridge source, deployed Worker events, daemon logs, or local command output; Worker HTTP server-error counters remained unchanged. The original shard-routing failure therefore occurred before or outside the deployed Worker/daemon boundary. The evidence is insufficient to identify the owner of that upstream temporary store, so this audit does not misattribute it to Cloudflare, the local daemon, OAuth, or Git. The host path later recovered without credential rotation or state deletion.
@@ -21,7 +21,7 @@ This document records project-wide decisions that must survive individual fixes,
21
21
  15. **Ambiguous health is not permission to repeat a remote write.** A successful Wrangler deployment is recorded before secondary health verification. Timeout, proxy, TLS, network, and temporary service failures preserve the deployment fingerprint and fail for diagnosis; only bounded evidence of a stale identity/version permits automatic same-name redeployment. Changing the Worker name is an explicit remote-resource transition, not a retry strategy.
22
22
  16. **Execution continuity and delivery continuity are separate proof obligations.** Keeping work alive after a client transport closes is insufficient unless the same authenticated principal can recover a terminal result or a durable handle. Fresh requests and replay endpoints must remain separate so recovery cannot accidentally duplicate a non-idempotent operation.
23
23
  17. **Remote compound commands are not persistence evidence.** When a remote edit and a long test share one relay call, a transport interruption can obscure whether the edit completed. High-impact writes must be followed by an independent read of stable anchors or a Git diff before tests and conclusions rely on them.
24
- 18. **Durable state owners do not retain cross-event terminal Promises.** A Durable Object that must accept cancellation, status, or recovery requests cannot retain the public SSE response, an internal request waiting for completion, or an unresolved Promise owned by the initiating fetch event. The outer Worker owns streaming; streamed daemon calls are registered and returned immediately, then settled by later WebSocket, cancellation, timeout, send-failure, or reconnect-expiry events. Descriptor requests remain short; terminal delivery uses one authenticated hibernatable WebSocket subscription. Both are admitted only on the internal service-binding path and are unreachable through caller-supplied internal headers.
24
+ 18. **Durable state owners do not retain cross-event terminal Promises or depend on JavaScript timers as durable ownership.** A Durable Object that must accept cancellation, status, recovery, or hibernation cannot retain the public SSE response, an internal request waiting for completion, or an unresolved Promise owned by the initiating fetch event. The outer Worker owns streaming. Stream initiation transactionally persists the stream plus daemon-call ownership, connection generation, operation deadline, reconnect deadline, request correlation, and bounded result-transform metadata before sending work. Later WebSocket, cancellation, timeout, send-failure, or reconnect-expiry events converge through one guarded terminal write. Durable Object alarms and compensating event-entry sweeps own cross-event deadlines; JavaScript timers remain only for bounded JSON-response calls. Descriptor requests remain short; terminal delivery uses one authenticated hibernatable WebSocket subscription. Both are admitted only on the internal service-binding path and are unreachable through caller-supplied internal headers.
25
25
 
26
26
  A proposed change that conflicts with an invariant requires an explicit owner decision and corresponding documentation update. It must not be hidden inside an unrelated refactor.
27
27
 
@@ -63,7 +63,7 @@ Rules:
63
63
  - Adapters may translate data but should not duplicate policy or schemas.
64
64
  - Every protocol control message emitted by one side must be explicitly accepted, rejected, or version-gated by the other side, with an end-to-end contract test covering the message name and semantics.
65
65
  - State transitions are explicit; readiness is not inferred from a lower-level event. An open WebSocket is not authenticated until `hello_ack`, and authenticated transport is not service readiness until an end-to-end result probe has returned on the same relay session. Pre-ready work and premature readiness acknowledgements fail closed.
66
- - Every externally controlled input is bounded before expensive allocation, traversal, parsing, storage, or execution.
66
+ - Every externally controlled input is bounded before expensive allocation, traversal, parsing, storage, or execution. A byte limit constrains bytes actually consumed and duration work, not only the subset retained in memory; once a declared or observed bound is crossed, cancel or close the source immediately.
67
67
  - Externally controlled string keys must not use prototype-chain membership or truthiness on ordinary objects. Use `Map`, `Set`, `Object.hasOwn`, or null-prototype records for command dispatch, enums, ACLs, form fields, registries, and other key-addressed contracts.
68
68
  - Repository text must not contain invisible ASCII controls other than tab, CR, and LF; architecture tests enforce this even when JavaScript syntax remains valid.
69
69
  - Persistent mutations use owner-only files, bounded no-follow reads, flushed atomic replacement, and integrity checks appropriate to the data.
@@ -72,7 +72,7 @@ Rules:
72
72
  - Exclusive locks use the shared complete-before-visible hard-link claim. Reclamation requires process identity plus a matching file snapshot/token; do not unlink a path merely because an earlier read looked stale.
73
73
  - Service providers normalize success/failure to one result contract. Definition removal follows the shared platform-stop → verified-daemon-stop → remove order.
74
74
  - Retry is limited to classified transient failures. Authentication, authorization, validation, integrity, and policy errors fail immediately.
75
- - Cleanup-only catches may be best effort, but primary failures must not be silently discarded.
75
+ - Cleanup-only catches may be best effort, but primary failures must not be silently discarded. Rollback of newly created credentials, keys, or other sensitive artifacts is part of the primary integrity result: incomplete rollback must be reported explicitly after attempting every cleanup target.
76
76
  - New work should not increase an already broad orchestration module when the behavior has an independent lifecycle or test surface. Extract the domain first.
77
77
 
78
78
  `runtime.mjs` owns local tool semantics. `relay-connection.mjs` owns authenticated relay connection lifecycle. The CLI orchestrates them; it must not become the second implementation of either.
@@ -170,7 +170,7 @@ The first start performs these operations:
170
170
 
171
171
  The foreground command remains attached to the terminal. Keep it running while testing. The remote Worker cannot execute local tools when no authenticated daemon is connected.
172
172
 
173
- The Worker name is a persistent workspace identity, not a retry counter. A successful Wrangler upload is recorded before health verification, so a later timeout does not require a new name and does not make the next start upload again. Supplying a different `--worker-name` for an initialized workspace requires `--force-worker` because it intentionally creates/replaces a separate Cloudflare Worker identity.
173
+ The Worker name is a persistent workspace identity, not a retry counter. A successful Wrangler upload is recorded before health verification, so a later timeout does not require a new name and does not make the next start upload again. Supplying a different `--worker-name` for an initialized workspace requires `--force-worker` because it intentionally creates/replaces a separate Cloudflare Worker identity. The printed `workers.dev` MCP URL is the standard public endpoint and requires no separately owned domain.
174
174
 
175
175
  To run only in the background after setup:
176
176
 
@@ -25,7 +25,7 @@ Local stdio does not use remote OAuth accounts. It runs as the local owner under
25
25
  | `operator` | Workspace-confined editing and direct process execution | No unrestricted paths, credentials, browser/desktop control, or persistent job creation |
26
26
  | `owner` | Complete bridge authority within the daemon policy ceiling | Generic path-based tools cannot target Machine Bridge control-plane state; owner shell remains OS-user authority |
27
27
 
28
- The daemon may advertise the complete catalog as its capability ceiling. The Worker filters `tools/list` by account role, and the local runtime independently recomputes and validates the same role boundary before dispatch.
28
+ The package catalog defines the stable discovery surface. The Worker filters `tools/list` by account role without rewriting it during a brief daemon outage. The live daemon advertisement remains the execution ceiling, and the local runtime independently recomputes and validates the role and policy boundary before dispatch.
29
29
 
30
30
  ## Trusted OAuth clients
31
31
 
@@ -36,7 +36,7 @@ Dynamic OAuth registration creates an untrusted client record. The first success
36
36
  - the account role;
37
37
  - the OAuth client ID.
38
38
 
39
- A client cannot silently switch to another account. Account disablement, role changes, password rotation, client revocation, token-version rotation, and refresh-token replay invalidate the relevant credentials.
39
+ A client cannot silently switch to another account. Account disablement, role changes, password rotation, client revocation, token-version rotation, and refresh-token replay outside the bounded concurrent-refresh window invalidate the relevant credentials.
40
40
 
41
41
  Inspect trusted clients locally:
42
42
 
@@ -93,7 +93,7 @@ Menu-bar and menu subtrees are not recursively expanded by default. This keeps m
93
93
 
94
94
  ## Capability discovery and automatic selection
95
95
 
96
- `resolve_task_capabilities` rescans instruction files, skills, explicit/automatic package commands, and relevant local automation metadata on every call. It ranks matching skills and commands, optionally loads the best skill, and compares every canonical-full task with cached installed-application names, so a task that directly names an app does not need generic “app/window” wording. Application inventory is refreshed after a bounded cache interval.
96
+ `resolve_task_capabilities` rescans instruction files, skills, explicit/automatic package commands, and relevant local automation metadata on every call. It ranks matching skills and commands, optionally loads the best skill, and compares every canonical-full task with cached installed-application names, so a task that directly names an app does not need generic “app/window” wording. Application inventory is refreshed after a bounded cache interval. Per-root discovery failures are returned as bounded `warnings`, and capability resolution reports `application_discovery.available`, warning count, truncation, and a coarse error class instead of silently treating an unreadable inventory as an empty successful scan.
97
97
 
98
98
  This is the strongest reliable server-side automation boundary available through MCP: discovery, refresh, ranking, and progressive skill loading are automatic. The MCP host still owns the model loop and decides whether a recommended tool is exposed, approved, or invoked. Machine Bridge cannot force ChatGPT web or another host to make a call that the host declines.
99
99
 
package/docs/LOGGING.md CHANGED
@@ -64,7 +64,9 @@ Brief network interruptions are expected on laptop network changes, Worker deplo
64
64
  - failure to receive `hello_ack` within the handshake deadline, or `ready_ack` within the independent end-to-end readiness deadline, terminates the candidate socket and retries;
65
65
  - lack of inbound heartbeat activity terminates a half-open socket and reconnects.
66
66
 
67
- A WebSocket close code such as `1006` means the transport ended without a normal close handshake. It is useful for debug diagnosis but not useful as the default user message. Default logs therefore describe the effect and recovery behavior rather than printing `{"code":1006,"reason":""}`.
67
+ A WebSocket close code such as `1006` means the transport ended without a normal close handshake. 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.
68
+
69
+ Persisted streamed-call diagnostics are deliberately coarse. Logs and `server_info` may report aggregate active/detached counts, oldest age, tool-name counts, alarm mutations, unmatched-result counts, and whether a call was transient or durable. They must not include tool arguments, terminal results, command text, request keys, account identifiers, raw call IDs, raw connection generations, private paths, or subscriber payloads. A stale-generation result is counted as unmatched rather than logged with its envelope.
68
70
 
69
71
  Examples:
70
72