machine-bridge-mcp 3.0.0-beta.154 → 3.0.0-beta.155
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.md +2 -2
- package/SECURITY.md +6 -2
- package/browser-extension/manifest.json +1 -1
- package/docs/ARCHITECTURE.md +2 -2
- package/docs/AUDIT.md +12 -0
- package/docs/CLIENTS.md +2 -2
- package/docs/LOCAL_AUTOMATION.md +1 -1
- package/docs/LOGGING.md +1 -1
- package/docs/MANAGED_JOBS.md +6 -2
- package/docs/MULTI_ACCOUNT.md +2 -2
- package/docs/OPERATIONS.md +2 -2
- package/docs/PRIVACY.md +5 -3
- package/docs/PROJECT_STANDARDS.md +5 -3
- package/docs/RELEASING.md +3 -1
- package/docs/TESTING.md +2 -2
- package/docs/TOOL_REFERENCE.md +2 -2
- package/package.json +1 -1
- package/scripts/generate-tool-reference.mjs +1 -1
- package/scripts/privacy-check.mjs +14 -12
- package/src/local/managed-job-state-validation.mjs +26 -0
- package/src/local/managed-jobs.mjs +4 -19
- package/src/shared/log-redaction.mjs +18 -14
- package/src/shared/sensitive-value-patterns.mjs +21 -0
- package/src/shared/server-metadata.json +7 -7
- package/src/shared/tool-catalog.json +1 -1
- package/src/worker/hosted-browser-target-schema.ts +31 -0
- package/src/worker/index.ts +9 -4
- package/src/worker/managed-job-capability.ts +61 -0
- package/src/worker/managed-job-hosted-authority.ts +108 -0
- package/src/worker/managed-job-hosted-schema.ts +62 -0
- package/src/worker/mcp-stale-schema-compat.ts +65 -2
- package/src/worker/tool-call-recovery.ts +2 -2
- package/src/worker/tool-catalog.ts +5 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.0.0-beta.155 - 2026-08-30
|
|
4
|
+
|
|
5
|
+
- Close the hosted managed-job cross-conversation discovery boundary found during an independent beta.154 review. Hosted acceptance now returns deterministic HMAC-SHA256 `recovery_key` and `control_key` capabilities bound to account version, OAuth client, refresh family, role, job ID, and purpose. Hosted `read_job` requires the read capability, `cancel_job` requires the control capability, and every `depends_on` reference requires an exact `dependency_recovery` mapping. The Worker verifies these capabilities before daemon dispatch and strips them from daemon arguments; they are not persisted in managed-job state or mirrored into MCP parameter headers. Hosted `list_jobs` is now aggregate-only and omits job IDs, names, and `recent_process_recovery` handles, while local CLI/stdio retains global owner administration. This prevents Machine Bridge's own shared owner inventory from becoming cross-conversation recovery authority; it cannot distinguish conversations if a host itself forwards another conversation's valid capability.
|
|
6
|
+
- Stop hosted browser content/action tools from implicitly following the shared Chromium active tab. Hosted source/inspection/wait/action/form/screenshot/upload calls now require an explicit `tab_id` obtained from `browser_list_tabs`, and hosted `computer_observe(surface="browser")` requires the same explicit target before snapshot creation. Snapshot-bound `computer_act` remains unchanged. This removes a concurrent-conversation retargeting path without changing local browser behavior.
|
|
7
|
+
- Repair credential/log privacy defense in depth. Runtime free-form sanitization and repository publication scanning now import one shared sensitive-value pattern catalog, cover current Machine Bridge password/OAuth/managed-job capability formats, and use token-alphabet-aware negative lookaheads instead of terminal `\\b` for credentials whose alphabets include hyphen/underscore/equal. Regression fixtures prove complete redaction/detection at delimiter-edge values rather than merely checking that the original full string disappeared.
|
|
8
|
+
- Improve stale hosted-schema recovery. A cached `read_job.wait_ms` above the current 60-second hosted maximum now receives explicit `tools/list` refresh and same-job server-paced continuation guidance; cached schemas missing the new managed-job `recovery_key`/`control_key` receive no-side-effect refresh guidance rather than a generic business-argument error. Tool schema generation advances from 20 to 21.
|
|
9
|
+
- Align managed-job contention and module boundaries. Transition-lock contention now returns typed retryable `conflict` with `job_transition_pending=true` instead of human retry text wrapped in a generic execution failure, and managed-job state/directory validation moves to a focused module rather than consuming the remaining `managed-jobs.mjs` growth budget. Current architecture/testing documentation is corrected from the obsolete five-minute hosted `read_job` maximum to the actual 60-second maximum and 70/75-second execution/settlement budget at that maximum. Multi-account documentation now states the intentional local owner global-administrator exception explicitly.
|
|
10
|
+
- Package/runtime identity advances to beta.155 because these are host-visible schema, authorization, and browser-targeting changes. Beta.154 release evidence remains historical evidence for the published beta.154 bytes and is not reused or rewritten; beta.155 requires fresh verification, candidate evidence, live generation-21 host refresh/argument probes, acceptance, and provider gates before any publication.
|
|
11
|
+
|
|
3
12
|
## 3.0.0-beta.154 - 2026-08-29
|
|
4
13
|
|
|
5
14
|
- Supersede the locally accepted beta.153 candidate after exact-head PR #114 failed the required JavaScript/TypeScript CodeQL gate with `js/file-system-race` in `exclusive-publication-recovery.mjs`. The finding was actionable: beta.153 recognized a committed two-link exclusive-publication crash residue by inspecting target/alias paths and then deleted the reserved staging pathname. Rechecking identity before `unlink` could not make a pathname-based destructive operation race-free under Node's path APIs, so no CodeQL suppression or accepted-SARIF exception is added.
|
package/README.md
CHANGED
|
@@ -187,13 +187,13 @@ machine-mcp browser status
|
|
|
187
187
|
|
|
188
188
|
Load the printed unpacked-extension directory into the intended Chromium profile. Reload the extension after every Machine Bridge upgrade. The broker validates a versioned capability handshake, keeps pairing state local and owner-only, and does not return the pairing token through MCP.
|
|
189
189
|
|
|
190
|
-
Machine Bridge does not launch or identify a separate browser profile. It controls whichever profile contains the extension, including that profile's tabs and login state. Read [docs/LOCAL_AUTOMATION.md](docs/LOCAL_AUTOMATION.md) before enabling it.
|
|
190
|
+
Machine Bridge does not launch or identify a separate browser profile. It controls whichever profile contains the extension, including that profile's tabs and login state. Because browser focus is shared machine state rather than hosted conversation identity, hosted browser content/action tools require an explicit `tab_id` from `browser_list_tabs`, and hosted browser `computer_observe` requires the same explicit target before snapshot creation. Read [docs/LOCAL_AUTOMATION.md](docs/LOCAL_AUTOMATION.md) before enabling it.
|
|
191
191
|
|
|
192
192
|
For stateful GUI trajectories, owner/full callers can use the higher-level `computer_observe` / `computer_act` pair. `computer_observe` creates one bounded browser or application snapshot with semantic evidence and native MCP image content when available; `computer_act` consumes the exact snapshot as one-shot mutation authority, dispatches at most once, observes post-state, and reports dispatch/effect settlement separately so ambiguous mutations are not automatically replayed. Their `timeout_seconds` value is one end-to-end budget for the compound operation rather than a fresh timeout for each internal screenshot, Accessibility/DOM preflight, dispatch, verification, or post-observation stage. See [docs/COMPUTER_USE.md](docs/COMPUTER_USE.md).
|
|
193
193
|
|
|
194
194
|
## Durable work and local resources
|
|
195
195
|
|
|
196
|
-
Remote request-owned foreground work uses the hosted reply-safe budgets described above; configurable browser/application calls may explicitly request at most 45 seconds, while remote `exec_command`, `run_process`, and `run_local_command` are durable one-step jobs with a 10-second acceptance envelope and an independent 1–600-second child execution budget after admission. A continuous process that legitimately needs more than 600 seconds must use `start_job`: managed-job main/finally steps default to 600 seconds and may explicitly request up to 21,600 seconds (six hours), with resource admission occurring before that execution timer begins. The Worker retains separate settlement ownership for five additional seconds, but neither that margin nor its internal stream metrics prove that an external MCP host consumed the terminal frame. Keep unrelated mutations and validation independently terminal, but batch one coherent non-interactive command sequence into a repository umbrella command or multi-step `start_job` instead of creating one host-visible one-step job per tiny probe. A timeout is a protocol result, not proof that descendant cleanup has already completed; a remote owner can inspect `diagnose_runtime.runtime.processes`, while local stdio exposes `server_info.runtime.processes`. Non-owner accounts receive authority-scoped readiness rather than machine-wide process activity. Remote process sessions are for interactive stdin or incremental output, not a substitute for ordinary durable work: hosted `read_process` reports `status_polling_mode=paced_followup`, caps the actual output/exit blocking wait at one second, and paces a repeated would-block read inside the fifteen-second cooldown within that same MCP call until output/exit or the cooldown boundary instead of returning a rapid running checkpoint. When the current task needs more output or terminal state, the same session may be read again in the same assistant response without busy-looping. Non-interactive work should use durable `run_process`/`read_job`; multi-step, cleanup-sensitive, or daemon-restart-surviving workflows should use managed jobs, which persist ordered argv steps and `finally_steps` under owner-only local state and continue across an MCP disconnect. Durable acceptance does not force a hosted-turn handoff: active relay-origin `read_job` reports `status_polling_mode=bounded_followup` and `host_turn_handoff_recommended=false`. Its hosted default is a 40-second server-side long-poll, so an unchanged long job occupies one bounded live MCP response rather than forcing rapid host-side checkpoints. Terminal settlement returns on the next bounded five-second internal poll; nonterminal status/phase/dependency progress is coalesced for at least 30 seconds by default, and `current_step`-only churn does not wake the hosted call. `wait_ms=0` is available only when an immediate checkpoint is actually wanted, and public hosted `wait_ms` is capped at 60 seconds; longer work continues through another paced read of the same `job_id`. At the default, a synthetic 100-minute unchanged job has an anti-amplification ceiling of 150 status reads, while continuously changing nonterminal progress is separately bounded by the 30-second coalescing floor; those are density estimates rather than proof of aggregate same-response host lifetime. A known job may be followed through paced same-response `read_job` calls while those calls continue to be accepted and the task still needs the result; after an actual host/tool boundary, later recovery must continue from the same `job_id`. Completed one-step process carriers are lower-priority terminal retention than explicit managed jobs, so removable helper history is reclaimed first under the 512-state durable cap. A hosted helper whose current response still requires `read_job` continuation keeps stronger private recovery priority for the fixed thirty-minute grace because Machine Bridge explicitly told the caller to follow that durable handle. Once Machine Bridge produces a terminal hosted response for the helper—during initial settlement or a later `read_job`—the private marker drops to the bounded newest-16 transient delivery reserve because only outer response-delivery uncertainty remains. `list_jobs.jobs` keeps a 50-record durable-first primary window
|
|
196
|
+
Remote request-owned foreground work uses the hosted reply-safe budgets described above; configurable browser/application calls may explicitly request at most 45 seconds, while remote `exec_command`, `run_process`, and `run_local_command` are durable one-step jobs with a 10-second acceptance envelope and an independent 1–600-second child execution budget after admission. A continuous process that legitimately needs more than 600 seconds must use `start_job`: managed-job main/finally steps default to 600 seconds and may explicitly request up to 21,600 seconds (six hours), with resource admission occurring before that execution timer begins. The Worker retains separate settlement ownership for five additional seconds, but neither that margin nor its internal stream metrics prove that an external MCP host consumed the terminal frame. Keep unrelated mutations and validation independently terminal, but batch one coherent non-interactive command sequence into a repository umbrella command or multi-step `start_job` instead of creating one host-visible one-step job per tiny probe. A timeout is a protocol result, not proof that descendant cleanup has already completed; a remote owner can inspect `diagnose_runtime.runtime.processes`, while local stdio exposes `server_info.runtime.processes`. Non-owner accounts receive authority-scoped readiness rather than machine-wide process activity. Remote process sessions are for interactive stdin or incremental output, not a substitute for ordinary durable work: hosted `read_process` reports `status_polling_mode=paced_followup`, caps the actual output/exit blocking wait at one second, and paces a repeated would-block read inside the fifteen-second cooldown within that same MCP call until output/exit or the cooldown boundary instead of returning a rapid running checkpoint. When the current task needs more output or terminal state, the same session may be read again in the same assistant response without busy-looping. Non-interactive work should use durable `run_process`/`read_job`; multi-step, cleanup-sensitive, or daemon-restart-surviving workflows should use managed jobs, which persist ordered argv steps and `finally_steps` under owner-only local state and continue across an MCP disconnect. Hosted durable acceptance returns `job_id`, `recovery_key`, and `control_key`: preserve them together, use the read capability for `read_job` and `depends_on`, and use the control capability for `cancel_job`; a bare job ID is not remote recovery authority. Durable acceptance does not force a hosted-turn handoff: active relay-origin `read_job` reports `status_polling_mode=bounded_followup` and `host_turn_handoff_recommended=false`. Its hosted default is a 40-second server-side long-poll, so an unchanged long job occupies one bounded live MCP response rather than forcing rapid host-side checkpoints. Terminal settlement returns on the next bounded five-second internal poll; nonterminal status/phase/dependency progress is coalesced for at least 30 seconds by default, and `current_step`-only churn does not wake the hosted call. `wait_ms=0` is available only when an immediate checkpoint is actually wanted, and public hosted `wait_ms` is capped at 60 seconds; longer work continues through another paced read of the same `job_id` plus its preserved `recovery_key`. At the default, a synthetic 100-minute unchanged job has an anti-amplification ceiling of 150 status reads, while continuously changing nonterminal progress is separately bounded by the 30-second coalescing floor; those are density estimates rather than proof of aggregate same-response host lifetime. A known job may be followed through paced same-response `read_job` calls while those calls continue to be accepted and the task still needs the result; after an actual host/tool boundary, later recovery must continue from the same `job_id`. Completed one-step process carriers are lower-priority terminal retention than explicit managed jobs, so removable helper history is reclaimed first under the 512-state durable cap. A hosted helper whose current response still requires `read_job` continuation keeps stronger private recovery priority for the fixed thirty-minute grace because Machine Bridge explicitly told the caller to follow that durable handle. Once Machine Bridge produces a terminal hosted response for the helper—during initial settlement or a later `read_job`—the private marker drops to the bounded newest-16 transient delivery reserve because only outer response-delivery uncertainty remains. The daemon/local `list_jobs.jobs` inventory keeps a 50-record durable-first primary window and `recent_process_recovery` remains capped at 16 additional authority-visible handles for local administration. The hosted Worker projection deliberately removes those job IDs/names/handles and exposes aggregate retained/capacity/activity state only, so one hosted conversation cannot discover another workflow's durable handle through shared owner inventory. Hosted recovery therefore depends on the capability returned by the original acceptance; legacy or lost-capability jobs require local CLI/stdio administration. Owner/local `capacity` diagnostics expose only coarse `durable_terminal` and `transient_terminal` counts; this improves recovery visibility without pretending that Worker acknowledgement proves an external host rendered the final assistant message. Long cross-job workflows can declare `depends_on`: the dependent job remains pre-execution `queued/dependency_wait` without spawning its main child until all upstream jobs succeed, and an upstream failure settles `dependency_failed` instead of leaving a file-poll loop waiting for an artifact that can never appear. Active/staged dependency plans pin referenced retained results until the dependency-bearing plan is terminal. A valid `job_id` that is no longer retained returns typed `not_found`; that absence is not proof that its underlying side effect never executed. `list_jobs` remains inventory rather than a substitute polling loop, and `server_info`/`diagnose_runtime` remain diagnostic surfaces rather than alternate wait channels. Elapsed minutes are not evidence that an external host deadline is near; return the durable recovery identifier for a later turn only after an actual host/tool boundary is observed, external input or authorization is required, or the user explicitly requested a checkpoint.
|
|
197
197
|
|
|
198
198
|
On macOS, authorized remote activity uses a bounded idle-sleep assertion so ordinary system Idle Sleep does not suspend an active remote workflow. Relay handlers share the assertion for their execution lifetime plus a fixed thirty-minute rolling inactivity grace; each new authorized remote activity cancels a pending release and restarts the full grace after the last concurrent handler settles. An admitted remote process session extends daemon-side ownership until its child settles, and an account-backed managed-job runner owns a runner-bound assertion from confirmed claim through terminal persistence. Local managed jobs do not acquire the remote-continuity assertion. These protections do not override explicit sleep or lid-close behavior.
|
|
199
199
|
|
package/SECURITY.md
CHANGED
|
@@ -216,7 +216,11 @@ Registered resources store canonical paths and bounded metadata, not file conten
|
|
|
216
216
|
|
|
217
217
|
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.
|
|
218
218
|
|
|
219
|
-
Managed jobs are durability, not sandboxing. Only `owner` may create remote persistent plans. Long-lived jobs bind to account, account version, OAuth client, and refresh family.
|
|
219
|
+
Managed jobs are durability, not sandboxing. Only `owner` may create remote persistent plans. Long-lived jobs bind to account, account version, OAuth client, and refresh family. The local owner role is intentionally a global object administrator, so daemon-local owner/CLI inspection is not a conversation-isolation boundary.
|
|
220
|
+
|
|
221
|
+
Hosted managed-job recovery adds a separate Worker capability boundary. `start_job`, `stage_job`, and durable one-step process acceptance return deterministic HMAC-SHA256 `recovery_key` and `control_key` values bound to account version, OAuth client, refresh family, role, job ID, and purpose. Hosted `read_job` and dependency references require the read capability; hosted `cancel_job` requires the control capability. The Worker verifies them before daemon dispatch and strips them from daemon arguments. Capability values are derived with domain separation from the existing Worker OAuth identity key, are not persisted in local job state, are not mirrored into MCP parameter headers, and are covered by ordinary log/privacy redaction. Rotating the OAuth identity key invalidates outstanding hosted capabilities. Hosted `list_jobs` returns aggregate state only, while local CLI/stdio remains the global administration surface. This prevents Machine Bridge inventory from turning one hosted conversation into another conversation's job-discovery authority, but it cannot identify host-side conversation misrouting if the host itself supplies the other conversation's still-valid capability.
|
|
222
|
+
|
|
223
|
+
Job directories must remain canonical real children of the owner-only job root. Cancellation markers are atomically replaced owner-only timestamp files; unreadable, malformed, symlinked, or hard-linked markers fail closed instead of being treated as no cancellation.
|
|
220
224
|
|
|
221
225
|
`stage_job` is non-executing and cannot be promoted by a terminal approval command. `machine-mcp job approve` was removed. Execution requires trusted `start_job` authority or an explicit local `machine-mcp job submit PLAN.json` action.
|
|
222
226
|
|
|
@@ -242,7 +246,7 @@ Request bodies, messages, traversals, files, output, OAuth stores, nonce stores,
|
|
|
242
246
|
|
|
243
247
|
## Logs, audit, and privacy
|
|
244
248
|
|
|
245
|
-
Operational logs omit tool arguments, command text, stdin, file/patch contents, form values, and outputs. Known credential forms, private-key material, embedded-credential URLs, user-home paths, email addresses, and control characters are recursively redacted as defense in depth.
|
|
249
|
+
Operational logs omit tool arguments, command text, stdin, file/patch contents, form values, and outputs. Known credential forms, private-key material, embedded-credential URLs, user-home paths, email addresses, and control characters are recursively redacted as defense in depth. Runtime log sanitization and repository privacy scanning share the same common credential-pattern source; token alphabets that allow hyphen, underscore, or padding use explicit alphabet-aware termination rather than a generic word boundary, including current Machine Bridge account-password/OAuth/managed-job capability formats.
|
|
246
250
|
|
|
247
251
|
The local security audit is a bounded SHA-256 hash chain. It records operation type, risk category, outcome, duration, byte counts, and pseudonymous target/principal references. Risky target correlation and account/client/family identity are HMAC-keyed with fresh per-daemon runtime keys before persistent audit state applies its existing per-file salt, so the public salt stored beside the chain is not sufficient to recompute references from guessed paths, short commands, or account identifiers. Those references are intentionally not stable cross-restart identities. The audit does not record commands, paths, contents, fields, or results.
|
|
248
252
|
|
|
@@ -30,6 +30,6 @@
|
|
|
30
30
|
"action": {
|
|
31
31
|
"default_title": "Machine Bridge Browser"
|
|
32
32
|
},
|
|
33
|
-
"version_name": "3.0.0-beta.
|
|
33
|
+
"version_name": "3.0.0-beta.155",
|
|
34
34
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxryYkpZhq8+VAQLHcGS9BAHQcyKX8RHGIpIwvtIVRU/rcOcE0bNdnM0aZJ/h6xWQsGDHlhvjT2+1aJaAn/9k8473BRWajzVXld961CdHYVFVHoce2hHiSJ0xydWrHMMZhAm0mN0UzjEpgZ0tMw209efcZHIvSwuxhteZMRy4kyiVjwFlOf5oXFCxRuCJnPj3AK9CmCf4XgEBuPIJ0TZmjGHOOdBvJmbCNnAWXYEo5/mf7MfCGhV4IJ1hNuhpoNQfOFKMUcw9/v/IpT62XpfXdGYTfGYCmCjC+gntK1spbkr2P4/2+sYMQtLpse71mpSNGXfcf3abU55Vpn+gncSxRQIDAQAB"
|
|
35
35
|
}
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -81,7 +81,7 @@ Native MCP image content and structured state share the ordinary 7 MiB tool-resu
|
|
|
81
81
|
|
|
82
82
|
`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.
|
|
83
83
|
|
|
84
|
-
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, Computer Use observation dispatch, and input-backend selection; `devtools-session.js` serializes same-tab debugger ownership and independently bounds debugger attach, each CDP command, and detach cleanup so one non-settling Chrome promise cannot retain the tab queue past its settlement budget. `devtools-observation.js` captures bounded Accessibility/DOMSnapshot/layout/screenshot evidence and treats a session timeout as a failed CDP capture that can fall back to semantic observation; `devtools-input.js` exposes only fixed trusted mouse, drag, wheel, keyboard, and text sequences, and a command timeout after Input dispatch remains non-fallback-safe/unknown. Callers cannot select CDP methods. Fixed `page-automation.js` is injected into selected frames for snapshot-version-3 semantics, per-document epochs, stable refs, bounded DOM/text traversal, actionability checks, open-Shadow-DOM traversal, structured DOM operations, multi-field forms, and resource-backed file inputs. Browser mutation classification has one source in `browser-extension-protocol.mjs`; once a mutating request has been handed to the extension transport, `browser-request-settlement.mjs` maps timeout, disconnect, malformed response, send uncertainty, or a completed mutation whose oversized/unserializable result cannot be delivered to fixed non-retryable unknown settlement while preserving structured side-effect/cancellation evidence. The local MCP and extension response paths are both pinned to the same 7 MiB result ceiling by an architecture guard, so cross-runtime size drift cannot silently change that settlement boundary. Pre-send failures and read-only failures retain their ordinary definite/retryable semantics. 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, one-time role-bound HMAC WebSocket proofs, message sizes, concurrency, and deadlines. Long-lived extension/runtime tokens remain owner-only HMAC keys and are never placed in WebSocket subprotocols. Clients first fetch a five-second challenge through an internal-header loopback endpoint, authenticate the broker's server proof, then present a one-time client proof; the broker consumes it once. Public pairing status and the long-lived broker `/pair` document are permanently token-free. An explicit pair action first binds an OS-assigned one-shot loopback listener owned by the current Machine Bridge process, then opens that temporary page with a 30-second bootstrap only in the URL fragment. The temporary listener serves exactly one pairing page and then closes (or expires after 30 seconds), so a process occupying the long-lived broker port cannot redirect the bootstrap. The `document_start` content script removes the fragment before page scripts run and keeps it only in the extension isolated world. The fragment secret is never sent in the HTTP request; instead it authenticates a bounded `/pair-auth` exchange. The broker derives the same secret from the owner-only extension token, proves possession first, consumes the matching client proof once, and only then releases the long-lived extension token to the extension. Manual repair reuses the isolated-world bootstrap through extension messaging rather than reading credentials from page DOM. Pairing material is omitted from MCP/log output.
|
|
84
|
+
The packaged Manifest V3 extension runs in the user's existing Chromium profile. Because that profile and its active-tab focus are shared machine state rather than hosted conversation identity, the Worker-hosted schema requires an explicit `tab_id` for browser source/inspection/wait/action/form/screenshot/upload calls and for `computer_observe(surface="browser")`; only local surfaces retain any active-tab convenience default. Later `computer_act` remains snapshot-bound to the explicitly observed target, so another hosted conversation changing browser focus cannot silently retarget an already-scoped operation. 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, Computer Use observation dispatch, and input-backend selection; `devtools-session.js` serializes same-tab debugger ownership and independently bounds debugger attach, each CDP command, and detach cleanup so one non-settling Chrome promise cannot retain the tab queue past its settlement budget. `devtools-observation.js` captures bounded Accessibility/DOMSnapshot/layout/screenshot evidence and treats a session timeout as a failed CDP capture that can fall back to semantic observation; `devtools-input.js` exposes only fixed trusted mouse, drag, wheel, keyboard, and text sequences, and a command timeout after Input dispatch remains non-fallback-safe/unknown. Callers cannot select CDP methods. Fixed `page-automation.js` is injected into selected frames for snapshot-version-3 semantics, per-document epochs, stable refs, bounded DOM/text traversal, actionability checks, open-Shadow-DOM traversal, structured DOM operations, multi-field forms, and resource-backed file inputs. Browser mutation classification has one source in `browser-extension-protocol.mjs`; once a mutating request has been handed to the extension transport, `browser-request-settlement.mjs` maps timeout, disconnect, malformed response, send uncertainty, or a completed mutation whose oversized/unserializable result cannot be delivered to fixed non-retryable unknown settlement while preserving structured side-effect/cancellation evidence. The local MCP and extension response paths are both pinned to the same 7 MiB result ceiling by an architecture guard, so cross-runtime size drift cannot silently change that settlement boundary. Pre-send failures and read-only failures retain their ordinary definite/retryable semantics. 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, one-time role-bound HMAC WebSocket proofs, message sizes, concurrency, and deadlines. Long-lived extension/runtime tokens remain owner-only HMAC keys and are never placed in WebSocket subprotocols. Clients first fetch a five-second challenge through an internal-header loopback endpoint, authenticate the broker's server proof, then present a one-time client proof; the broker consumes it once. Public pairing status and the long-lived broker `/pair` document are permanently token-free. An explicit pair action first binds an OS-assigned one-shot loopback listener owned by the current Machine Bridge process, then opens that temporary page with a 30-second bootstrap only in the URL fragment. The temporary listener serves exactly one pairing page and then closes (or expires after 30 seconds), so a process occupying the long-lived broker port cannot redirect the bootstrap. The `document_start` content script removes the fragment before page scripts run and keeps it only in the extension isolated world. The fragment secret is never sent in the HTTP request; instead it authenticates a bounded `/pair-auth` exchange. The broker derives the same secret from the owner-only extension token, proves possession first, consumes the matching client proof once, and only then releases the long-lived extension token to the extension. Manual repair reuses the isolated-world bootstrap through extension messaging rather than reading credentials from page DOM. Pairing material is omitted from MCP/log output.
|
|
85
85
|
|
|
86
86
|
See [Local application and browser automation](LOCAL_AUTOMATION.md).
|
|
87
87
|
|
|
@@ -258,7 +258,7 @@ The default `full` profile passes the complete parent environment. Isolated envi
|
|
|
258
258
|
|
|
259
259
|
`execution-limits.mjs` is the shared source for local tool-call concurrency, one-shot process timeout/stdin/output limits, and process-session count/stdin/output/retention limits. Remote-owner `diagnose_runtime.runtime.execution_guardrails` reports those enforced limits, while local stdio `server_info.runtime.execution_guardrails` exposes the same contract together with explicit `not-enforced` values for CPU quota, memory quota, and network isolation. Public one-shot commands inline at most 32 KiB per stream. When either stream exceeds that preview, the runtime keeps up to 1 MiB per stream in a closed in-memory process session for thirty minutes and returns an `output_session_id`; `read_process` then reads monotonic byte-offset pages. Relay-origin process reads use paced same-response follow-up. `process-session-read.mjs` owns read/wait/output orchestration while `process-session-remote-poll.mjs` owns only hosted projection/cooldown policy, leaving `process-sessions.mjs` focused on session lifecycle and ownership. The async read helper returns its internal plan/output snapshot before hosted projection is finalized; after the await the manager re-checks cancellation, then completes projection synchronously, so cancellation at the Promise boundary is not lost and a child exit there cannot produce `running=false` with stale cooldown metadata. The Worker and daemon cap the actual output/exit blocking wait at one second; a live session that has just consumed a remote blocking read enters a fifteen-second would-block cooldown. A repeated would-block request inside that cooldown is paced inside the same MCP call until output/exit or the cooldown boundary instead of returning an immediate running checkpoint, and the Worker execution budget covers that server-side delay. When `wait_for_exit=true`, ordinary output notifications remain readable but do not release the cooldown stage; only process exit or the monotonic cooldown deadline advances the call to its bounded one-second exit-wait stage. Every live relay-origin read identifies `status_polling_mode=paced_followup` and keeps `host_turn_handoff_recommended=false`; terminal reads identify `status_polling_mode=terminal`. The cooldown fields are explicitly blocking-only (`blocking_poll_throttled`, `next_blocking_poll_after_ms`) so a zero-wait status read is not misrepresented as globally rate-limited. Same-response follow-up is allowed when the task needs additional output or terminal state, but callers must not busy-loop and should respect the reported cooldown. Elapsed wall-clock time is not a host-deadline signal: callers continue bounded follow-up while calls are accepted and hand progress back only after an actual host/tool boundary is observed, external input/authorization is required, or the user explicitly requested a checkpoint. This state is per in-memory process session, does not alter local stdio/CLI wait behavior, and is intentionally a hosted pacing bound rather than durable scheduling. The oldest exited session is evicted before an active session is refused, so continuation retention is explicitly best effort rather than durable. The continuation stores command basename and cwd metadata but not argv or shell text. It is memory-only and disappears on runtime stop or daemon replacement.
|
|
260
260
|
|
|
261
|
-
Resource admission is a separate cooperative boundary, not an OS quota. `resource-foreground-wait.mjs` owns the ordinary process-start wait budget: a one-shot foreground call defaults to 20% of its execution timeout with a two-second floor, ten-second ceiling, and never more than the complete execution timeout. Owner-local process-session startup defaults to a ten-second cooperative wait, while relay-origin `start_process` performs one admission attempt without queueing by default so known host pressure is returned before the request-owned response budget is consumed. An explicit configured override remains authoritative for controlled diagnostics/tests. `LocalRuntime` leaves this default unconfigured in production so the services actually apply it, while explicit overrides are validated and capped at thirty minutes. These admission waits occur before process spawn and do not enlarge an outer relay deadline; cancellation propagates through the same coordinator wait. Detached managed-job steps use the shared durable-delivery admission ceiling instead: the runner may wait up to thirty minutes before spawn, child execution timeout starts only after admission succeeds, and persisted job status reports `current_phase=resource_admission` during that pre-spawn interval. Hosted managed-job status supports bounded autonomous follow-up: `managed-job-hosted-status.mjs` owns the relay-only projection, so relay-origin `read_job` reports `status_polling_mode=bounded_followup` plus `host_turn_handoff_recommended=false` for active jobs while terminal reads report `status_polling_mode=terminal`; local job reads retain their prior shape. Worker tool guidance allows a known active job to be followed again in the same assistant response when terminal state is required, while prohibiting busy loops, repeated `list_jobs` substitution, and speculative handoff based only on elapsed wall-clock time. `managed-job-read-wait.mjs` keeps that autonomy out of the host spin loop: the runtime `read_job` handler performs a relay-only 40-second default long-poll with five-second lightweight status-only probes, monotonic deadline accounting, and cancellation checks. Terminal settlement returns on the next bounded probe. Nonterminal status/phase/dependency progress is coalesced until at least the 30-second hosted progress floor (or the caller's explicitly shorter wait), and `current_step` alone is excluded from the hosted wake signature; local explicit waits retain step-progress wake behavior. The public maximum
|
|
261
|
+
Resource admission is a separate cooperative boundary, not an OS quota. `resource-foreground-wait.mjs` owns the ordinary process-start wait budget: a one-shot foreground call defaults to 20% of its execution timeout with a two-second floor, ten-second ceiling, and never more than the complete execution timeout. Owner-local process-session startup defaults to a ten-second cooperative wait, while relay-origin `start_process` performs one admission attempt without queueing by default so known host pressure is returned before the request-owned response budget is consumed. An explicit configured override remains authoritative for controlled diagnostics/tests. `LocalRuntime` leaves this default unconfigured in production so the services actually apply it, while explicit overrides are validated and capped at thirty minutes. These admission waits occur before process spawn and do not enlarge an outer relay deadline; cancellation propagates through the same coordinator wait. Detached managed-job steps use the shared durable-delivery admission ceiling instead: the runner may wait up to thirty minutes before spawn, child execution timeout starts only after admission succeeds, and persisted job status reports `current_phase=resource_admission` during that pre-spawn interval. Hosted managed-job status supports bounded autonomous follow-up: `managed-job-hosted-status.mjs` owns the relay-only projection, so relay-origin `read_job` reports `status_polling_mode=bounded_followup` plus `host_turn_handoff_recommended=false` for active jobs while terminal reads report `status_polling_mode=terminal`; local job reads retain their prior shape. Worker tool guidance allows a known active job to be followed again in the same assistant response when terminal state is required, while prohibiting busy loops, repeated `list_jobs` substitution, and speculative handoff based only on elapsed wall-clock time. `managed-job-read-wait.mjs` keeps that autonomy out of the host spin loop: the runtime `read_job` handler performs a relay-only 40-second default long-poll with five-second lightweight status-only probes, monotonic deadline accounting, and cancellation checks. Terminal settlement returns on the next bounded probe. Nonterminal status/phase/dependency progress is coalesced until at least the 30-second hosted progress floor (or the caller's explicitly shorter wait), and `current_step` alone is excluded from the hosted wake signature; local explicit waits retain step-progress wake behavior. The public hosted maximum is 60 seconds; the 40-second default remains intentionally separate so ordinary continuation leaves additional host/Worker settlement headroom, while longer jobs continue through another server-paced read of the same capability-bound job rather than one overlong host request. Full runner/recovery reconciliation is bounded to thirty-second intervals inside the advertised wait rather than executing on every lightweight probe. The monotonic wait deadline starts before the initial full read, so initial reconciliation consumes the same per-call budget, and an unchanged timeout returns the latest secure persisted progress state without adding a second heavyweight reconcile after the deadline. Coherent non-interactive command sequences should use a repository umbrella command or a multi-step managed job so local command fan-out does not become host-visible tool-event fan-out; this density control does not shorten the managed task or its six-hour step ceiling. Hosted reads first verify a healthy runner with async process-start identity sampling; `managed-job-hosted-reconcile.mjs` owns that relay-only liveness/recovery decision, and only a non-current/ambiguous runner enters the existing recovery reconciliation path, so a normal long-running job no longer launches synchronous `ps` from the relay event loop every reconcile interval. `ManagedJobManager.read()` remains the local synchronous persistence/ownership projection while relay reads use `readHosted()`; `readProgress()` deliberately omits runner reconciliation and terminal-result projection during unchanged waits. Relay-origin `list_jobs` remains an `inventory` surface and does not recommend handoff, but its hosted projection is aggregate-only: it omits job IDs, names, and `recent_process_recovery` handles. Hosted acceptance returns deterministic HMAC-derived `recovery_key` and `control_key` capabilities bound to account version, OAuth client, refresh family, role, job ID, and capability purpose. The Worker validates these credentials before daemon dispatch, strips them before constructing the daemon request, requires `recovery_key` for `read_job` and every `depends_on` reference, and requires `control_key` for `cancel_job`. The capabilities are derived from the existing Worker OAuth identity key and are never persisted with local job state; rotating that identity key invalidates outstanding hosted capabilities. Local CLI/stdio administration intentionally retains global owner visibility. This boundary prevents Machine Bridge's own hosted global inventory from causing cross-conversation job discovery, but it cannot distinguish two host conversations if the host itself incorrectly supplies the other conversation's still-valid capability. Ordinary managed-job result/status projection remains separate from this hosted pacing policy. Owner diagnostics separately expose the Boolean `waiters.drain_active`, computed by the same fairness selection state machine, so Green host/resource pressure is not confused with an aged protected waiter intentionally reserving a drain window. A fixed CPU request that exceeds the machine's best-case priority-specific launch window is structurally incapable of becoming admissible while the configured CPU headroom remains in force; it therefore returns non-retryable `cpu_request_exceeds_launch_window` before entering the retry sleep loop instead of masquerading as transient `cpu_pressure_window`. Explicit worker counts/argv are never rewritten and the headroom is not relaxed; elastic/unbounded requests retain their pressure-aware fitting behavior. `resource-command-profile.mjs` classifies known light operations, bounded adaptive unknowns, and known CPU/I/O/mixed build families. `resource-script-classification.mjs` classifies only an actually executed shell/Node/Python script operand or package-manager script name, and `resource-shell-analysis.mjs` owns conservative shell token/segment parsing. This lets direct orchestration roots reserve startup capacity before descendant fan-out without letting an unrelated argument such as a test filename or release note impersonate a heavy script. The arbitrary-process zero-resource allowlist is intentionally identity-bound: `resource-light-command.mjs` accepts only a small set of standard absolute executables for constant/output, process-table, uptime, and sleep probes. Bare PATH-resolved names and every arbitrary shell invocation remain adaptive even when their apparent command is cheap, because executable resolution, startup configuration, repository configuration, path behavior, options, helpers, or input size can change the actual work. Caller-controlled Git, filesystem metadata/query commands, lookup helpers, recursive/search/file-stream processors, `find`, application launchers, and script interpreters therefore never receive zero-resource admission from basename alone. Implementation-owned Git/diagnostic probes use their separate fixed-argv/internal boundary and do not depend on this allowlist. `resource-admission.mjs` persists per-user owner-only leases and waiters outside workspace state; ownership is bound to PID plus process-start identity and, where supported, the isolated process group. Releasing the caller-side lease does not delete a bound POSIX reservation while that isolated process group still exists: the persisted lease remains until ordinary pruning observes the group gone, so a detached descendant cannot become unaccounted merely because its direct caller returned. Independent nested process roots retain their own full durable leases for crash recovery, but live accounting does not blindly add an orchestration envelope and every child reservation. `resource-process-ancestry.mjs` samples the live parent graph through `resource-process-ancestry-cache.mjs`; the async cache coalesces concurrent requests and retains a completed snapshot for one second so admission retries do not repeatedly enumerate the complete process table. If ancestry cannot be sampled, accounting falls back to conservative full summation. `resource-lease-accounting.mjs` forms an ephemeral lease forest whose effective vector is the component-wise maximum of a node's own envelope and the sum of its direct lease children. A pending nested request is charged only for the additional vector it contributes to that forest. Same-key contention is exempted only for the request's actual ancestor lease chain; siblings and unrelated roots still serialize.
|
|
262
262
|
|
|
263
263
|
Live host-pressure observation is deliberately asynchronous-only. `resource-probe-command.mjs` owns bounded `execFile` child-probe transport and intentionally exposes no synchronous `spawnSync` path; `resource-host-darwin.mjs` runs Darwin memory, VM, disk, and thermal probes concurrently, `resource-process-ancestry.mjs` samples the parent graph asynchronously, and `resource-host-snapshot.mjs` composes those results without blocking the daemon event loop on `ps`, `memory_pressure`, `vm_stat`, `iostat`, or `pmset`. The previous sync host/Darwin/process-parent samplers and sync ancestry cache are removed rather than retained as test-only APIs that a future runtime path could accidentally call. Process-start identity used when binding a spawned lease is also sampled asynchronously before the coordinator file lock is taken. Lease/waiter pruning uses a separate one-second cached async process-start snapshot collected outside the transaction lock; while holding the lock it performs only current PID liveness plus in-memory generation comparison. Missing snapshot evidence fails closed for a live PID, while a proven generation mismatch remains reclaimable before isolated process-group liveness can preserve the lease. Current lease/waiter staging recovery consumes the same snapshot. The expired beta.104 transaction-owner directory migration path is no longer executable; unsupported legacy transaction-lock directories are retained unchanged and fail closed so stale migration code cannot become a destructive recovery surface. `resource-host-cache.mjs` treats a same-project general host snapshot as fresh for 500 milliseconds while retaining successful I/O evidence for at most five seconds; a stale general sample can therefore refresh cheap CPU/memory/load data without waiting for another one-second `iostat` interval, but an older throughput peak is not copied into a current quick sample. Timestamps represent sample completion rather than sample start. `resource-admission-policy.mjs` evaluates effective CPU, memory, I/O, disk-reserve, startup-window, and host-pressure values without imposing global serialism. macOS `iostat -Id` exposes transfer and throughput activity, not device utilization/saturation; fresh high IOPS/MB-per-second evidence therefore tightens I/O capacity as Yellow pressure but cannot by itself mark the host Red. Red remains reserved for direct critical evidence such as thermal/memory/disk-headroom failure or a severe load backlog corroborated by current CPU/I/O evidence. Linux additionally uses `MemAvailable` and optional PSI avg10 observations through `resource-host-linux.mjs`; sustained `psi_io_full_avg10 >= 60` is direct critical I/O-stall evidence and therefore Red, while lower I/O PSI remains a Yellow capacity-throttling signal. The soft free-disk floor is `min(80 GiB, max(8 GiB, 15% of volume))`; the post-reservation hard floor is `min(50 GiB, max(5 GiB, 10% of volume))`. Disk-only hard-red pressure has one narrow self-recovery exception: only an internally classified direct standard absolute deletion executable with the exact small `disk-reclaim` envelope may enter using Yellow capacity limits, and only when `disk_free_headroom_critical` is the sole critical reason. PATH-resolved or shell-composed deletion cannot claim that class, and thermal, memory, PSI, CPU/load, or other Red evidence still blocks it. Diagnostics expose observed busy CPU next to reserved CPU and their ratio as a mismatch signal, not as process attribution. `resource-admission-diagnostics.mjs` owns the privacy-safe snapshot/check projection, while `resource-admission-diagnostic-error.mjs` separately classifies bounded transaction/staging-lock contention as retryable coordinator-busy snapshot unavailability instead of inflating the diagnostics module or mislabeling contention as process execution failure. `resource-waiters.mjs` normally skips an older request that does not fit, but a sufficiently aged rank-zero waiter blocked by coordinator-owned project/CPU/I/O/memory capacity can enter a protected drain phase if it is structurally feasible after current leases disappear. Fixed implementation-owned probes bypass this coordinator so diagnostics/recovery remain usable during resource pressure.
|
|
264
264
|
|
package/docs/AUDIT.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Security and privacy audit notes
|
|
2
2
|
|
|
3
|
+
## 2026-08-30 beta.154 independent hosted-isolation/privacy review and beta.155 remediation
|
|
4
|
+
|
|
5
|
+
**The beta.154 release remained locally healthy under its existing verification plan, but the review found contract gaps outside that model.** A clean `main` baseline passed the 104-task fast plan, the complete 131-task plan, reachable-history privacy scanning, and both production/full npm audits with zero vulnerabilities. Independent delimiter-edge probes nevertheless proved that several free-form credential patterns used a terminal word boundary even though their token alphabet allows hyphen/underscore: valid-shaped GitLab, Google, and API-secret values ending in `-` could miss redaction entirely, while Slack/Machine Bridge/JWT cases could redact only a prefix and leave the final delimiter. The publication scanner independently carried parallel versions of the same common patterns, so both defenses could drift while their existing fixtures stayed green. Beta.155 moves common credential shapes into one shared source, adds current Machine Bridge password/OAuth/managed-job capability forms, uses alphabet-aware termination, and asserts exact normalized output/detection at delimiter edges.
|
|
6
|
+
|
|
7
|
+
**Hosted durable recovery was principal-bound but not conversation-safe when several ChatGPT workflows shared one owner principal.** The daemon intentionally lets the local `owner` role inspect globally retained objects, while hosted `list_jobs` exposed detailed job IDs/names and `recent_process_recovery` handles to that same owner. A separate hosted conversation could therefore discover another workflow's durable identifier even though MCP transport identity is explicitly not conversation identity. The documentation also overstated account/client/family isolation by omitting the owner global-administrator exception. Beta.155 does not invent an untrusted `conversation_id`. Instead, accepted hosted jobs/process carriers receive purpose-scoped HMAC capabilities bound to account version, OAuth client, refresh family, role, job ID, and purpose. `read_job`/dependencies require the read capability, `cancel_job` requires a distinct control capability, Worker verification occurs before daemon dispatch, capability fields are stripped before forwarding and are not MCP header mirrors, and hosted `list_jobs` becomes aggregate-only. Local CLI/stdio retains intentional global administration. This closes Machine Bridge-created cross-conversation discovery but cannot identify a host that itself supplies another conversation's valid capability.
|
|
8
|
+
|
|
9
|
+
**The review also found two independent freshness/contract defects.** The currently connected host exposed a complete generation-11 tool snapshot while live `server_info` and beta.154 source were generation 20; a harmless `read_job(wait_ms=300000)` boundary probe reached beta.154 and was rejected at the current 60000 ms maximum, proving the host validator snapshot was stale. `mcp-stale-schema-compat.ts` already translated an obsolete `read_process` maximum into schema-refresh guidance but omitted `read_job`, so the real stale path looked like an ordinary business-argument error. Beta.155 adds explicit refresh/same-job guidance for the changed wait limit and missing managed-job capabilities. Separately, current `ARCHITECTURE.md` and `TESTING.md` still described the pre-beta.152 five-minute hosted read ceiling/310-315 second execution-settlement budget despite the runtime's 60-second maximum; those live normative docs are corrected while historical audit records remain untouched.
|
|
10
|
+
|
|
11
|
+
**Additional concurrency and maintainability hardening follows the same causal model.** Hosted browser content/action tools no longer rely on the shared active Chromium tab; they require explicit `tab_id`, and hosted browser `computer_observe` requires the same target before snapshot creation. Managed-job transition-lock contention now returns typed retryable `conflict` instead of retry prose wrapped in a generic error. Managed-job state validation is extracted rather than raising the near-full monolith's line budget. Repository/release guidance now requires worktree/branch/HEAD, package version, source schema generation, live version/generation, and the unfiltered hosted catalog to be treated as separate identities; dirty historical worktrees must be preserved before cleanup so a canonical-looking directory name cannot silently choose an obsolete review baseline.
|
|
12
|
+
|
|
13
|
+
**Release consequence.** These are public hosted authorization/schema/browser-targeting changes, so the uncommitted remediation candidate advances to beta.155 and tool schema generation 21. Beta.154 acceptance remains immutable historical evidence for beta.154 bytes; it is not deleted or reused. No beta.155 acceptance is fabricated by this review. Fresh frozen verification, candidate/install-only proof, guarded activation, generation-21 hosted refresh/validator probes, capability-bound live recovery, acceptance, and exact-head provider gates remain mandatory before publication, while npm publication remains the separate explicit owner boundary.
|
|
14
|
+
|
|
3
15
|
## 2026-08-29 beta.154 exclusive-publication CodeQL and TOCTOU review
|
|
4
16
|
|
|
5
17
|
**Beta.153 is blocked by hosted security analysis despite complete local/live acceptance.** Exact-head PR #114 reported `js/file-system-race` at `src/local/exclusive-publication-recovery.mjs`. The beta.153 recovery path opened and revalidated the committed target and its reserved same-inode staging alias, but then performed a pathname `unlink` of that alias. The finding is substantive: another same-user filesystem mutation can change a path entry between the last identity check and a destructive pathname syscall, and Node does not expose a portable descriptor-relative unlink primitive that would make this particular cleanup proof atomic. The repository therefore does not suppress the alert, add a SARIF exception, or treat repeated pre-unlink stats as a security repair.
|
package/docs/CLIENTS.md
CHANGED
|
@@ -172,10 +172,10 @@ Machine Bridge itself does not block files because their names look sensitive. I
|
|
|
172
172
|
|
|
173
173
|
Expected file-operation failures arrive as ordinary MCP tool-error results, not JSON-RPC transport failures. Clients should branch first on `structuredContent.error.code`, then optionally on the bounded `details.reason`. For example, `conflict/already_exists`, `conflict/hash_mismatch`, `conflict/text_ambiguous`, and `conflict/context_not_found` require a fresh read and reconciliation; `not_found/text_not_found` means the requested edit fragment is absent; `invalid_request` means the request or patch syntax must change. Do not log or display tool arguments to reconstruct diagnostics: public error details intentionally omit paths, file content, edit fragments, and compared hashes.
|
|
174
174
|
|
|
175
|
-
Remote configurable browser/application foreground tools default to 20 seconds, compound `computer_observe`/`computer_act` default to 30 seconds, and all configurable foreground tools advertise a 45-second maximum. Those values bound daemon execution; the Worker records its settlement deadline five seconds later. Remote `exec_command`, `run_process`, and `run_local_command` use a separate durable 10-second acceptance contract with independently bounded detached execution. Hosted `read_process` supports paced same-response follow-up: the actual output/exit blocking wait is at most one second, while a repeated would-block request inside the fifteen-second cooldown remains inside that same MCP call until output/exit or the cooldown boundary instead of returning a rapid running checkpoint. A live result reports `status_polling_mode=paced_followup` plus `next_blocking_poll_after_ms`; callers may continue reading when the current task needs output or terminal state, but must not busy-loop. Admission and transport latency may consume part of an individual request interval, and none of these values guarantees host receipt. They are not evidence for a guessed whole-assistant-turn deadline: clients must not stop merely because some number of wall-clock minutes has elapsed. Missing or role-hidden tools, non-object arguments, and requests above current limits fail at the shared Worker schema boundary before daemon dispatch; schema failures include `side_effects_started=false`. SSE-capable current requests receive the same pre-dispatch validation as JSON responses and never allocate a recovery stream. Do not treat this as a retry invitation for the same oversized mutation, and do not attempt to evade a host refusal by renaming, encoding, or switching to another arbitrary execution tool. Durable acceptance does not force a hosted-turn handoff. When the current task needs terminal state, a known active job may be followed through bounded same-response `read_job` calls while calls continue to be accepted; relay reads report `status_polling_mode=bounded_followup` and `host_turn_handoff_recommended=false`. Hosted active `read_job` defaults to a 40-second server-side long-poll. Terminal settlement returns on the next bounded five-second internal poll; nonterminal status/phase/dependency progress is coalesced for at least 30 seconds by default, and `current_step`-only churn does not wake the hosted call. `wait_ms=0` requests an immediate checkpoint, while public hosted `wait_ms` is capped at 60 seconds. The default is deliberately 40 seconds rather than equal to that maximum: live acceptance must prove that the target host carries the omitted-parameter default without terminating the tool call, and longer jobs continue through another paced read of the same `job_id` rather than a longer single HTTP request. Per-call survival does not establish aggregate assistant-response lifetime; a host may still impose a real boundary after many individually successful reads. Do not replace server-side pacing with a rapid host-side checkpoint loop. Handoff is justified only after an actual host/tool boundary is observed, external input or authorization is required, or the user explicitly requested a checkpoint. After a real host boundary, resume the same durable `job_id` rather than resubmitting the operation. For a coherent non-interactive workflow with several local commands, prefer a repository umbrella command or one multi-step `start_job` rather than a chain of one-step process carriers; this is a host-event-density control, not a task-duration limit. A valid job ID that is no longer retained returns typed `not_found`; this is missing recovery evidence, not proof that its side effect never executed. Completed one-step process carriers use lower-priority terminal retention than explicit managed jobs, so removable helper history is reclaimed first within the shared 512-state durable store
|
|
175
|
+
Remote configurable browser/application foreground tools default to 20 seconds, compound `computer_observe`/`computer_act` default to 30 seconds, and all configurable foreground tools advertise a 45-second maximum. Hosted browser content/action calls require an explicit `tab_id` obtained from `browser_list_tabs`, and hosted `computer_observe(surface="browser")` requires the same explicit target before it issues a snapshot; they do not silently retarget to whichever shared Chromium tab happens to be active. Those values bound daemon execution; the Worker records its settlement deadline five seconds later. Remote `exec_command`, `run_process`, and `run_local_command` use a separate durable 10-second acceptance contract with independently bounded detached execution. Hosted `read_process` supports paced same-response follow-up: the actual output/exit blocking wait is at most one second, while a repeated would-block request inside the fifteen-second cooldown remains inside that same MCP call until output/exit or the cooldown boundary instead of returning a rapid running checkpoint. A live result reports `status_polling_mode=paced_followup` plus `next_blocking_poll_after_ms`; callers may continue reading when the current task needs output or terminal state, but must not busy-loop. Admission and transport latency may consume part of an individual request interval, and none of these values guarantees host receipt. They are not evidence for a guessed whole-assistant-turn deadline: clients must not stop merely because some number of wall-clock minutes has elapsed. Missing or role-hidden tools, non-object arguments, and requests above current limits fail at the shared Worker schema boundary before daemon dispatch; schema failures include `side_effects_started=false`. SSE-capable current requests receive the same pre-dispatch validation as JSON responses and never allocate a recovery stream. Do not treat this as a retry invitation for the same oversized mutation, and do not attempt to evade a host refusal by renaming, encoding, or switching to another arbitrary execution tool. Durable acceptance does not force a hosted-turn handoff. When the current task needs terminal state, a known active job may be followed through bounded same-response `read_job` calls while calls continue to be accepted; relay reads report `status_polling_mode=bounded_followup` and `host_turn_handoff_recommended=false`. Hosted active `read_job` defaults to a 40-second server-side long-poll. Terminal settlement returns on the next bounded five-second internal poll; nonterminal status/phase/dependency progress is coalesced for at least 30 seconds by default, and `current_step`-only churn does not wake the hosted call. `wait_ms=0` requests an immediate checkpoint, while public hosted `wait_ms` is capped at 60 seconds. The default is deliberately 40 seconds rather than equal to that maximum: live acceptance must prove that the target host carries the omitted-parameter default without terminating the tool call, and longer jobs continue through another paced read of the same `job_id` rather than a longer single HTTP request. Per-call survival does not establish aggregate assistant-response lifetime; a host may still impose a real boundary after many individually successful reads. Do not replace server-side pacing with a rapid host-side checkpoint loop. Handoff is justified only after an actual host/tool boundary is observed, external input or authorization is required, or the user explicitly requested a checkpoint. After a real host boundary, resume the same durable `job_id` rather than resubmitting the operation. For a coherent non-interactive workflow with several local commands, prefer a repository umbrella command or one multi-step `start_job` rather than a chain of one-step process carriers; this is a host-event-density control, not a task-duration limit. A valid job ID that is no longer retained returns typed `not_found`; this is missing recovery evidence, not proof that its side effect never executed. Completed one-step process carriers use lower-priority terminal retention than explicit managed jobs, so removable helper history is reclaimed first within the shared 512-state durable store. Hosted `list_jobs` is an aggregate-only inventory surface: the Worker removes job IDs, names, and `recent_process_recovery` handles, while local CLI/stdio retains the detailed bounded inventory for administration. The same pacing principle applies to read-only status/diagnostic surfaces such as `server_info` and `diagnose_runtime`: use them to capture relevant evidence rather than switching surfaces merely to wait on the same background state. `server/discover` and `tools/list` both advertise `ttlMs=0`; discovery instructions therefore refresh alongside the tool contract, every host-visible tool description carries `Tool schema generation N`, and `server_info.tool_delivery` exposes the current generation/version plus `discovery_ttl_ms=0` and `tool_list_ttl_ms=0` while reporting that the server cannot know which generation an external host has cached. `host_turn_deadline_observable=false` means Machine Bridge also cannot predict when the external host will terminate an assistant turn; `managed_jobs_detached_from_mcp_response=true` means that boundary does not itself cancel an already accepted durable job. A release that changes tool semantics therefore verifies the live Machine Bridge runtime/discovery contract and harmless changed-generation invocation behavior after activation. When ChatGPT workspace governance freezes approved actions, use the Workspace Action control snapshot after automation performs the supported refresh/review path as the product-level publication evidence. Opaque host-internal cache inspection is intentionally excluded from release acceptance and must not trigger reconnect, refresh, recreation, or republication. For durable workflows that must survive a later host boundary:
|
|
176
176
|
|
|
177
177
|
1. register credentials locally as resource aliases so their values never enter MCP arguments;
|
|
178
|
-
2. choose a fresh `idempotency_key` and submit a complete policy-authorized `start_job` plan before the workflow depends on later cleanup calls; hosted
|
|
178
|
+
2. choose a fresh `idempotency_key` and submit a complete policy-authorized `start_job` plan before the workflow depends on later cleanup calls; hosted `start_job` requires that key before dispatch so an ambiguous acceptance response can be retried with the same logical arguments instead of creating a second job. Preserve the returned `job_id`, `recovery_key`, and `control_key` together: hosted `read_job` requires `recovery_key`, `cancel_job` requires `control_key`, and `depends_on` requires an exact `dependency_recovery` mapping. These capabilities are purpose/principal bound and are not discoverable through hosted `list_jobs`; batch coherent non-interactive local commands into that managed plan (or a repository umbrella command) instead of creating a one-step tool event for every small probe; `stage_job` is only a non-running draft, while an explicit local operator may use `machine-mcp job submit PLAN.json`;
|
|
179
179
|
3. use job-scoped temporary files or remote stdin scripts;
|
|
180
180
|
4. put idempotent cleanup in `finally_steps`;
|
|
181
181
|
5. inspect/cancel through `machine-mcp job ...` if the host later denies tools.
|
package/docs/LOCAL_AUTOMATION.md
CHANGED
|
@@ -57,7 +57,7 @@ The broker is machine-global rather than workspace-global. One local owner liste
|
|
|
57
57
|
|
|
58
58
|
- `browser_status` reports broker role, authenticated extension protocol/version/capabilities, reload state, supported operations, pairing URL, and extension path without returning the pairing token. Concurrent callers may share one broker-startup operation, but each request re-checks its own cancellation after shared startup settles.
|
|
59
59
|
- `pair_browser_extension` opens the local pairing page and returns setup steps. Cancellation is checked immediately before the OS launcher; once the launcher invocation is attempted, timeout/cancellation/response loss is an unknown page-open outcome, so inspect the browser before opening the pairing page again.
|
|
60
|
-
- `browser_list_tabs` lists current tabs.
|
|
60
|
+
- `browser_list_tabs` lists current tabs. Hosted browser content/action calls must choose an explicit `tab_id` from this inventory; the Worker does not let them fall back to whichever tab is currently active because browser focus is shared machine state, not hosted conversation identity. Local/stdio callers retain the underlying convenience defaults where documented.
|
|
61
61
|
- `browser_manage_tabs` creates, activates, or closes a selected tab. Once the corresponding Chrome mutation API is invoked, a rejected or lost response is not proof that the tab mutation did not happen; inspect the tab inventory before retrying.
|
|
62
62
|
- `browser_get_source` returns a bounded iterative DOM serialization only when raw markup is genuinely required. Treat the entire result as sensitive: serialized source can expose hidden bootstrap state, session/account identifiers, authentication material, or other values that are not visible in the rendered page. Prefer `browser_inspect_page` for routine semantic/actionability work. `max_bytes` is one aggregate request budget across at most 64 accessible frames; omitted frames and node/byte truncation are explicit.
|
|
63
63
|
- `browser_inspect_page` returns snapshot version 3 with one aggregate `max_elements` budget, at most 64 accessible frames, a 100,000-node per-frame scan ceiling, bounded page-controlled strings, URL-userinfo redaction, and explicit scan/frame truncation. Each control includes a reusable `ref` plus visibility, enabled/editable state, viewport geometry, and a per-document epoch used by Computer Use to reject stale same-URL replacements. References are held in a 10,000-entry per-frame LRU; navigation, element replacement, or bounded eviction makes an older ref stale and requires a new inspection.
|
package/docs/LOGGING.md
CHANGED
|
@@ -118,7 +118,7 @@ Application discovery and Accessibility operations follow the same rule: permiss
|
|
|
118
118
|
|
|
119
119
|
## Bounding and redaction
|
|
120
120
|
|
|
121
|
-
Messages, strings, object depth, object key counts, array item counts, and serialized field payloads are bounded. Tool-argument validation reports only a bounded tool name, JSON Pointer path, schema keyword, and constraint message; it never includes the rejected value. Control characters and Unicode display controls are neutralized. Fields with secret-like names and path-like keys are recursively redacted. Free-form sanitization covers generic private-key headers, AWS/GitHub/GitLab/npm/Slack/Google/live-payment/API token forms, JWT-shaped values, URLs with embedded credentials, email addresses, and user-home paths.
|
|
121
|
+
Messages, strings, object depth, object key counts, array item counts, and serialized field payloads are bounded. Tool-argument validation reports only a bounded tool name, JSON Pointer path, schema keyword, and constraint message; it never includes the rejected value. Control characters and Unicode display controls are neutralized. Fields with secret-like names and path-like keys are recursively redacted. Free-form sanitization covers generic private-key headers, AWS/GitHub/GitLab/npm/Slack/Google/live-payment/API token forms, current Machine Bridge account-password/OAuth/managed-job capability forms, JWT-shaped values, URLs with embedded credentials, email addresses, and user-home paths. Runtime logging and repository privacy scanning import the same common credential-pattern source; credential alphabets that allow hyphen/underscore/padding use alphabet-aware negative termination rather than a generic word boundary, preventing suffix-preserving partial redaction.
|
|
122
122
|
|
|
123
123
|
Local and Worker free-form strings use the same portable value sanitizer. Worker fields are therefore inspected by content even when their key is not secret-shaped. Both structured loggers assign their authoritative metadata after sanitizing caller fields. Local `timestamp`, `level`, `component`, `message`, and `event`, plus Worker `timestamp`, `level`, `component`, and `event`, therefore cannot be forged or replaced by an event payload. Local-only recursive path-key redaction and environment-derived home aliases remain additional protections around the portable rules.
|
|
124
124
|
|
package/docs/MANAGED_JOBS.md
CHANGED
|
@@ -13,7 +13,7 @@ can therefore stop after step 2 or 3 and leave local or remote temporary state b
|
|
|
13
13
|
|
|
14
14
|
Machine Bridge managed jobs reduce this failure mode by accepting the complete execution and cleanup plan in one call. After acceptance, an independent local runner owns the lifecycle. It does not depend on the MCP socket remaining connected.
|
|
15
15
|
|
|
16
|
-
Recovery inventory is deliberately ordered by recoverability rather than recency alone. `list_jobs.jobs` returns at most 50 primary records, with unreadable state, active jobs, staged plans, and durable terminal history ahead of transient one-step helper history so a burst of short helpers cannot push an older long-running or pre-spawn-waiting job out of that bounded primary window. A hosted one-step helper whose current response still requires `read_job` continuation retains stronger private recovery priority for thirty minutes because Machine Bridge explicitly instructed the caller to follow that durable handle. Once Machine Bridge produces a terminal hosted response—during initial settlement or a later `read_job`—the helper drops to the bounded newest-16 transient delivery reserve.
|
|
16
|
+
Recovery inventory is deliberately ordered by recoverability rather than recency alone. `list_jobs.jobs` returns at most 50 primary records, with unreadable state, active jobs, staged plans, and durable terminal history ahead of transient one-step helper history so a burst of short helpers cannot push an older long-running or pre-spawn-waiting job out of that bounded primary window. A hosted one-step helper whose current response still requires `read_job` continuation retains stronger private recovery priority for thirty minutes because Machine Bridge explicitly instructed the caller to follow that durable handle. Once Machine Bridge produces a terminal hosted response—during initial settlement or a later `read_job`—the helper drops to the bounded newest-16 transient delivery reserve. The daemon/local inventory's separate `recent_process_recovery` array remains capped at 16 authority-visible handles, prioritizes retained follow-up-required helpers when the primary window omits them, and contains no step output or internal retention state. The hosted Worker projection deliberately removes both those handles and the detailed `jobs` entries, retaining only aggregate inventory; hosted recovery uses the capability returned with the original acceptance. The local array remains recovery inventory, not a polling or MCP replay/session surface. Owner/local listings also include only aggregate recent creation/churn counts; the internal `transient_process` retention class and follow-up marker remain hidden per job.
|
|
17
17
|
|
|
18
18
|
Remote one-step `exec_command`, `run_process`, and `run_local_command` requests still become durable managed jobs before execution. Hosted `start_job` now shares the same short two-second initial-settlement check after its explicit multi-step plan has been durably accepted. A job that reaches terminal state inside that window can therefore return its managed-job result in the original tool response with `follow_up_read_required=false`, eliminating the usual immediate second `read_job` event. A job that remains active keeps the same durable `job_id` and recovery envelope with `follow_up_read_required=true`; execution timeouts, dependency waiting, the pre-spawn resource-admission allowance, finally behavior, and later recovery semantics are unchanged. Local/stdio `start_job` does not add this hosted response wait.
|
|
19
19
|
|
|
@@ -264,7 +264,11 @@ read_job
|
|
|
264
264
|
cancel_job
|
|
265
265
|
```
|
|
266
266
|
|
|
267
|
-
In hosted remote use, successful `start_job`
|
|
267
|
+
In hosted remote use, successful `start_job`, `stage_job`, and durable one-step process acceptance returns the durable `job_id` plus two opaque Worker capabilities: `recovery_key` for reads/dependency references and `control_key` for cancellation. The capabilities are deterministic HMAC outputs bound to the authenticated account version, OAuth client, refresh family, role, job ID, and capability purpose; they are never persisted in daemon job state. A hosted `read_job` requires the matching `recovery_key`, a hosted `cancel_job` requires the matching `control_key`, and every hosted `depends_on` entry requires an exact `dependency_recovery` mapping for that upstream job. The Worker verifies these values before daemon dispatch and removes them from the daemon request. A `job_id` alone is therefore not hosted recovery authority.
|
|
268
|
+
|
|
269
|
+
Hosted `list_jobs` is intentionally aggregate-only. It reports bounded retained/capacity/activity state but omits job IDs, names, and `recent_process_recovery` handles, so independent hosted conversations sharing one owner principal cannot discover each other's recovery identifiers through Machine Bridge inventory. Complete global inventory remains available through local CLI/stdio administration. This protects against Machine Bridge-created cross-conversation discovery, not a host that itself forwards another conversation's valid capability.
|
|
270
|
+
|
|
271
|
+
Hosted acceptance hands execution to durable background ownership without forcing the current assistant response to end. If the current task needs the result, `read_job` with the same `job_id` and `recovery_key` may be used for bounded same-response follow-up until terminal state while calls continue to be accepted. An active relay read defaults to a 40-second server-side long-poll. Terminal settlement returns on the next bounded five-second internal poll; nonterminal status/phase/dependency progress is coalesced for at least 30 seconds by default, and `current_step`-only churn does not wake the hosted call. `wait_ms=0` is an explicit immediate checkpoint, while public hosted `wait_ms` is capped at 60 seconds. Longer jobs continue through another paced read of the same capability-bound job rather than one overlong tool invocation. For a coherent non-interactive sequence, prefer one multi-step managed job or a repository umbrella command to many one-step durable carriers. Do not busy-loop, do not replace server-side pacing with rapid immediate reads, do not substitute repeated `list_jobs` calls for a known-job read, and do not infer or preempt a host/tool deadline from elapsed wall-clock time. Preserve both `job_id` and the corresponding capability across a real host/tool boundary; if a pre-capability job never delivered a hosted capability, use local CLI/stdio administration instead of attempting global hosted discovery. Local explicit-wait inspection retains its operator-driven step-progress behavior; the `current_step` wake suppression is hosted-only.
|
|
268
272
|
|
|
269
273
|
From the local terminal:
|
|
270
274
|
|
package/docs/MULTI_ACCOUNT.md
CHANGED
|
@@ -126,9 +126,9 @@ Interactive processes, retained output sessions, and managed jobs bind to:
|
|
|
126
126
|
- OAuth client ID;
|
|
127
127
|
- refresh-token family ID.
|
|
128
128
|
|
|
129
|
-
|
|
129
|
+
For delegated (non-owner) principals, a different account, client, or token family cannot inspect, continue, send input to, cancel, or terminate the object. The `owner` role is intentionally a local-machine administration role and remains a global object administrator at the daemon boundary; owner CLI/stdio diagnostics can inspect and control retained objects across delegated principals.
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
Hosted managed-job recovery adds a stricter Worker boundary even for owner accounts: detailed `list_jobs` handles are not remotely enumerable, and `read_job`, `cancel_job`, and `depends_on` require purpose-scoped capabilities bound to the accepting account version, OAuth client, refresh family, role, and job. This prevents Machine Bridge's own global owner inventory from becoming cross-conversation recovery authority. It does not claim to distinguish two host conversations if the host itself incorrectly forwards the other conversation's valid capability.
|
|
132
132
|
|
|
133
133
|
## Administrative boundary
|
|
134
134
|
|
package/docs/OPERATIONS.md
CHANGED
|
@@ -93,11 +93,11 @@ After the host path recovers, compare authenticated `server_info`, `machine-mcp
|
|
|
93
93
|
|
|
94
94
|
### Relay interruption messages
|
|
95
95
|
|
|
96
|
-
A reconnect warning proves a transport interruption, not a daemon crash. Compare daemon PID and process start time with `connected_at`, `last_seen_at`, `daemon.relay_transport.last_close_category`, `last_close_code`, `outage_count`, `recent_outages`, `outage_attempts`, `previous_ready_inbound_silence_ms`, `last_connect_milestones_ms`, and the coarse network-route class. In each completed `recent_outages` entry, `disconnected_at` is the first transition into that outage, `last_disconnect_at` is the final failed reconnect/close transition, `ready_at` is verified recovery, and `duration_ms` measures first-disconnect-to-ready time. The bounded `probe_dispatch_pending_at_start`, `probe_dispatch_age_ms_at_start`, `probe_outstanding_at_start`, `probe_age_ms_at_start`, `transport_confirmation_pending_at_start`, and `application_inbound_silence_ms_at_start` fields freeze the liveness phase at that first transition. Use them to test hypotheses such as a failure coinciding with the first five-second protocol Ping, but do not treat that timing correlation as proof that Ping caused the reset. Also distinguish a planned restart from an accidental outage: a current-generation daemon sends `daemon_draining` before relay close, affected calls receive `reason=daemon_planned_drain`, and owner/full `server_info.worker.continuity_evidence` schema 2 durably retains the planned-drain count/time plus bounded socket-disconnect and client-cancellation observations across Worker isolate replacement. Treat `ready_socket_disconnects`, `unplanned_ready_socket_disconnects`, and `last_ready_socket_disconnect` as the qualified durable socket-outage evidence; the broader `socket_disconnects` counters include candidate/probing transport churn. `last_socket_disconnect.role`, `was_ready`, and `connected_at` identify whether the newest first-observed close/error had ever become the authoritative ready channel. Planned-drain intent is serialized into the WebSocket attachment before close so a Durable Object isolate replacement cannot turn a planned shutdown into an unplanned disconnect. Schema-1 disconnect counts are intentionally reset during the schema-2 transition because old isolates could count stale/expired close callbacks more than once. `worker.observability.continuity` remains isolate-local and may reset; use the qualified durable summary for post-incident correlation rather than relying on a later close-1006 inference. For `read_job`, recover with the same returned `job_id`; do not resubmit the job's underlying mutation. `last_connect_milestones_ms` contains only bounded relative timings for the most recent connection attempt phases such as DNS resolution, TCP connect, TLS establishment, HTTP rejection, and WebSocket open; `last_failed_connect_stage`, `last_failed_connect_duration_ms`, `last_failed_connect_milestones_ms`, and `last_failed_connect_http_status` retain the most recent failed attempt even after a later retry succeeds. `last_transport_error_ready` and `last_transport_error_authenticated` distinguish failure of an already-established channel from a pre-readiness connection failure. `last_transport_error_reason` is a strict privacy-safe allowlist (`connection_reset`, `connection_timeout`, `network_unreachable`, bounded DNS/TLS classes, or `unknown`) rather than the raw operating-system message. The signed HTTPS fallback retains its last error class/reason after a later successful poll while resetting the current `http_poll_failures` count, so post-recovery diagnosis can determine whether WSS and HTTPS failed through the same system-network episode. None of these fields contains a hostname, address, DNS answer, certificate, close reason, or proxy endpoint. While no daemon channel is ready, `server_info.daemon.previous_connection` retains only the last verified channel's transport, connected/last-seen/disconnected timestamps, and sanitized relay diagnostics; it excludes policy, tools, account identity, daemon instance/connection identity, call IDs, arguments, and results, and it never participates in routing or authorization. `outage_duration_ms` measures the close-to-ready recovery episode; `previous_ready_inbound_silence_ms` measures how long the preceding ready socket had stopped producing inbound transport proof before it actually closed. The second value is therefore the field that exposes a black-holed OPEN WebSocket whose visible reconnect later completes quickly. A VPN/TUN UI may remain “connected” while its upstream route is unusable. Local OS logs can be compared with the exact `last_disconnected_at` timestamp, but an interface-quality change or tunnel-process correlation is not by itself proof of which product, node, edge, or upstream failed. Correlated failure of an independent HTTPS client at the same timestamp—for example an external API `unexpected EOF` while the relay records WebSocket 1006/`connection_reset`—is stronger evidence of a shared system-network/VPN/TUN episode than of a Machine Bridge event-loop or resource-admission failure; it still does not identify the failing tunnel node or upstream provider. Machine Bridge reports only coarse route/proxy classes and never sends or logs interface names, addresses, DNS answers, proxy credentials, or Worker secrets.
|
|
96
|
+
A reconnect warning proves a transport interruption, not a daemon crash. Compare daemon PID and process start time with `connected_at`, `last_seen_at`, `daemon.relay_transport.last_close_category`, `last_close_code`, `outage_count`, `recent_outages`, `outage_attempts`, `previous_ready_inbound_silence_ms`, `last_connect_milestones_ms`, and the coarse network-route class. In each completed `recent_outages` entry, `disconnected_at` is the first transition into that outage, `last_disconnect_at` is the final failed reconnect/close transition, `ready_at` is verified recovery, and `duration_ms` measures first-disconnect-to-ready time. The bounded `probe_dispatch_pending_at_start`, `probe_dispatch_age_ms_at_start`, `probe_outstanding_at_start`, `probe_age_ms_at_start`, `transport_confirmation_pending_at_start`, and `application_inbound_silence_ms_at_start` fields freeze the liveness phase at that first transition. Use them to test hypotheses such as a failure coinciding with the first five-second protocol Ping, but do not treat that timing correlation as proof that Ping caused the reset. Also distinguish a planned restart from an accidental outage: a current-generation daemon sends `daemon_draining` before relay close, affected calls receive `reason=daemon_planned_drain`, and owner/full `server_info.worker.continuity_evidence` schema 2 durably retains the planned-drain count/time plus bounded socket-disconnect and client-cancellation observations across Worker isolate replacement. Treat `ready_socket_disconnects`, `unplanned_ready_socket_disconnects`, and `last_ready_socket_disconnect` as the qualified durable socket-outage evidence; the broader `socket_disconnects` counters include candidate/probing transport churn. `last_socket_disconnect.role`, `was_ready`, and `connected_at` identify whether the newest first-observed close/error had ever become the authoritative ready channel. Planned-drain intent is serialized into the WebSocket attachment before close so a Durable Object isolate replacement cannot turn a planned shutdown into an unplanned disconnect. Schema-1 disconnect counts are intentionally reset during the schema-2 transition because old isolates could count stale/expired close callbacks more than once. `worker.observability.continuity` remains isolate-local and may reset; use the qualified durable summary for post-incident correlation rather than relying on a later close-1006 inference. For hosted `read_job`, recover with the same returned `job_id` plus its `recovery_key`; do not resubmit the job's underlying mutation. If the job predates capability issuance or its capability was never delivered, use local CLI/stdio administration instead of attempting hosted global discovery. `last_connect_milestones_ms` contains only bounded relative timings for the most recent connection attempt phases such as DNS resolution, TCP connect, TLS establishment, HTTP rejection, and WebSocket open; `last_failed_connect_stage`, `last_failed_connect_duration_ms`, `last_failed_connect_milestones_ms`, and `last_failed_connect_http_status` retain the most recent failed attempt even after a later retry succeeds. `last_transport_error_ready` and `last_transport_error_authenticated` distinguish failure of an already-established channel from a pre-readiness connection failure. `last_transport_error_reason` is a strict privacy-safe allowlist (`connection_reset`, `connection_timeout`, `network_unreachable`, bounded DNS/TLS classes, or `unknown`) rather than the raw operating-system message. The signed HTTPS fallback retains its last error class/reason after a later successful poll while resetting the current `http_poll_failures` count, so post-recovery diagnosis can determine whether WSS and HTTPS failed through the same system-network episode. None of these fields contains a hostname, address, DNS answer, certificate, close reason, or proxy endpoint. While no daemon channel is ready, `server_info.daemon.previous_connection` retains only the last verified channel's transport, connected/last-seen/disconnected timestamps, and sanitized relay diagnostics; it excludes policy, tools, account identity, daemon instance/connection identity, call IDs, arguments, and results, and it never participates in routing or authorization. `outage_duration_ms` measures the close-to-ready recovery episode; `previous_ready_inbound_silence_ms` measures how long the preceding ready socket had stopped producing inbound transport proof before it actually closed. The second value is therefore the field that exposes a black-holed OPEN WebSocket whose visible reconnect later completes quickly. A VPN/TUN UI may remain “connected” while its upstream route is unusable. Local OS logs can be compared with the exact `last_disconnected_at` timestamp, but an interface-quality change or tunnel-process correlation is not by itself proof of which product, node, edge, or upstream failed. Correlated failure of an independent HTTPS client at the same timestamp—for example an external API `unexpected EOF` while the relay records WebSocket 1006/`connection_reset`—is stronger evidence of a shared system-network/VPN/TUN episode than of a Machine Bridge event-loop or resource-admission failure; it still does not identify the failing tunnel node or upstream provider. Machine Bridge reports only coarse route/proxy classes and never sends or logs interface names, addresses, DNS answers, proxy credentials, or Worker secrets.
|
|
97
97
|
|
|
98
98
|
Brief retryable outages recover automatically. On a verified current daemon channel, `server_info.daemon.relay_transport.outage_active=false`; retained fields describe the immediately preceding transport episode rather than claiming a current outage. WebSocket remains preferred and requests a protocol-level probe after five seconds. Calling `ws.ping()` only queues the control frame; it is not treated as remote-probe dispatch until the WebSocket sender's write callback confirms that the Ping actually left the local send queue. The local sender has a separate thirty-second bounded dispatch window, while a confirmed Ping retains its full ten-second Pong deadline. This deliberately prevents compression/backpressure or a slow local socket queue from spending the remote-response budget before any probe was transmitted. A protocol Pong that arrives while a Ping callback is still pending records bidirectional proof for that dispatch round; if the local write callback then completes inside the thirty-second dispatch budget, it does not arm a stale future Pong deadline. Unrelated application inbound is receive-side evidence only and cannot prove the daemon-to-Worker direction. A local queue whose Ping write callback still has not completed after thirty seconds is classified as `relay_transport_send_timeout` even if unrelated inbound traffic continues. One dispatched Ping that reaches the ten-second response deadline does not hard-kill an otherwise ready WSS: the relay runtime enters a fifteen-second `transport_confirmation_pending` window, sends the existing JSON application heartbeat as an independent path check, and prewarms signed HTTPS in standby without taking ownership away from the still-ready WSS. A protocol Pong or the explicit JSON application `pong` clears suspicion and stops standby prewarm; ordinary tool/control traffic does not, because it proves only the Worker-to-daemon receive direction. Only a second-stage confirmation window that receives no application `pong` becomes `relay_transport_timeout` and terminates the WSS. This keeps a true black hole bounded while no longer amplifying a roughly ten-to-fifteen-second persistent-flow stall into an immediate reconnect storm. `heartbeat.probe_dispatch_*`, `heartbeat.transport_confirmation_*`, and bounded sender-backlog fields distinguish local send delay, first-stage response loss, successful second-stage recovery, and confirmed two-stage failure. The separate periodic JSON application heartbeat remains twenty-five seconds with a seventy-five-second application-silence timeout, begins only after verified relay readiness, and the Worker keeps a wider ninety-second WebSocket liveness fallback. This is a detection/recovery bound, not a guarantee that a degraded network can complete another WebSocket handshake inside the same interval. WebSocket connect attempts have a thirty-second outer budget so a degraded but still valid DNS/TCP/TLS/WebSocket upgrade is not misclassified by an unrealistically narrow connection cutoff. The daemon also explicitly disables client `permessage-deflate`: the relay carries bounded control/JSON traffic, while `ws` enables compression by default on clients and compression adds sender-state/CPU overhead that can queue later frames; the stability path does not need that optional negotiation. The fallback still begins independently rather than waiting thirty seconds for WSS. On first-stage WSS liveness suspicion, the same root-certified ephemeral daemon identity prewarms signed HTTPS in standby; if WSS proves live during the second-stage confirmation, that standby poller stops. If the WSS actually disconnects, fallback switches to exact-generation takeover immediately; an in-flight standby request is aborted and replaced rather than being allowed to consume up to its own request deadline before takeover can start. That in-memory session certificate intentionally has a 24-hour maximum lifetime. It is valid for ordinary reconnects during that lifetime, but it is not silently extended: if a later WSS reconnect/authentication attempt discovers that the daemon session has expired, the daemon terminates with `relay_device_session_expired` instead of retrying forever with unusable credentials. Installed launchd/systemd/Windows service supervision restarts the failed daemon and obtains a fresh root-signed session after normal runtime cleanup; the default portable root does this without user interaction. A manually run daemon must be restarted by its operator, and a configured Secure Enclave root retains its existing user-presence requirement when the new daemon start signs the replacement session. Each fallback request has a seven-second deadline, ordinary one-second ready poll cadence, five-second standby-prewarm cadence, bounded one/two/four/five-second retry backoff after request or protocol/session failures, a 750 ms hard minimum request-start interval, and a twelve-second liveness window; a new daemon-backed call waits at most fifteen seconds for some verified daemon channel, and the measured wait is deducted from that call's original execution budget. After an established WSS disappears, the daemon explicitly marks its signed HTTP request as a takeover of the Worker-issued `connection_id` for that exact disconnected WebSocket generation. Once candidate preconditions pass, HTTPS may retire only that targeted same-instance zombie WSS that the Worker has not yet observed closing. If a newer same-instance WSS is already ready before the HTTP request arrives, the old generation no longer matches and the stale takeover remains standby instead of retiring the recovered socket. A takeover request without the exact Worker-issued WebSocket connection ID is invalid rather than being treated as an instance-only legacy takeover. Malformed, stale, wrongly targeted, or different-instance requests cannot preempt a healthy incumbent. During replacement, the daemon reconciles `resume_calls`, processes `ready_ack`, proves local readiness, and only then returns `resume_calls_ack.missing_ids`. A missing ID therefore proves both that the same daemon has no active/unacknowledged-result ownership for that call and that the replacement channel is ready. If the initiating MCP response is still open and at least one second remains in the original execution budget, the Worker may transparently retransmit exactly that same call ID, arguments, authority, and a reduced timeout. `read_job` is stricter: redelivery requires the full ten-second reconciliation headroom to remain, otherwise the Worker declines redelivery and returns retryable recovery failure rather than rewriting the call into an under-budget immediate read. If safe redelivery cannot be accepted, the call falls back to retryable `unavailable` with `side_effects_started=false`. Calls that may have executed, retained terminal results, different-daemon calls, and ambiguous mutations are never automatically replayed. Completed relay results that are still waiting for Worker acknowledgement remain bounded in daemon memory and consume the same recovery-ownership capacity as active calls: 16 total with two control-plane slots reserved for `diagnose_runtime`/`list_roots`. When ordinary recovery ownership reaches 14, another ordinary relay call is rejected before execution with retryable `limit_exceeded` and `side_effects_started=false`; the two reserved diagnostic/recovery calls remain available until total capacity reaches 16. The retained-result implementation also keeps one non-admission emergency ownership slot solely for a violated internal capacity invariant: if an already-executed result reaches retention after the normal 16-entry ceiling is unexpectedly full, that one result remains retained for acknowledgement/reconnect ownership instead of being sent unowned and later misclassified as safe to redeliver. Use of that slot emits an error-level capacity event and may make diagnostics temporarily report ownership above the normal maximum; a second such overflow is not sent. This slot is not usable admission capacity and must never be counted to raise the 16-call execution ceiling. An acknowledgement that is permanently lost cannot pin a result forever: first retention is monotonic and the result expires after the 315-second maximum Worker settlement lifetime on the next live relay heartbeat; the disconnected path still uses the shorter reconnect-grace cleanup. `diagnose_runtime.runtime.relay_result_recovery` exposes only aggregate `active_calls`, `retained_results`, active ownership, and capacity counts—never call IDs, tool arguments, or results. Compare outage intervals with sleep/wake records and `diagnose_runtime.runtime.relay.heartbeat` before classifying them as active network faults; local stdio `server_info.runtime.relay.heartbeat` exposes the same state. On macOS, an authorized, schema-valid remote tool call also enters a bounded idle-sleep guard with a fixed thirty-minute rolling inactivity grace; the service does not depend on shell-only environment overrides that launchd would not persist as configuration, and relay heartbeats do not count as user activity. Authorized relay handlers hold one shared `/usr/bin/caffeinate -i -s -w <daemon-pid>` assertion for their full execution lifetime; `-s` strengthens system-sleep prevention only on AC power while `-i` remains the baseline Idle Sleep request; concurrent handlers share the child, the thirty-minute default inactivity grace begins only after the last one settles, and a new authorized handler cancels any pending release timer so the full grace restarts after that activity settles. A remote `start_process` extends the same assertion only after resource admission succeeds and keeps it until the session child settles, so a long process session is not reduced to the handler grace window. Remote account managed-job runners independently hold `/usr/bin/caffeinate -i -s -w <runner-pid>` after their ownership claim is confirmed and persisted ownership identifies an account-backed job, then retain it through admission, steps, cleanup, and terminal persistence; local managed jobs do not acquire this remote-continuity assertion, and daemon reconnect/replacement does not own the remote runner protection. `diagnose_runtime.runtime.idle_sleep_guard` reports only daemon-side supported/enabled/active/grace/error-class state plus whether the fixed child requests the AC-only system-sleep assertion; it intentionally does not enumerate process-session or job identities. Runtime shutdown terminates process sessions before releasing the daemon guard. None of these assertions claim to prevent explicit sleep or lid-close sleep. A nonzero `event_loop_stall_count` with a large `max_event_loop_lag_ms` means the local daemon was not scheduled promptly; a short stall enters recovery grace, sends a fresh transport probe, and deliberately postpones disconnect. A large stall that aligns with `pmset` Sleep/Wake is suspension evidence. Independently, `relay_outage_analysis` can show either that a close-to-ready interval was itself dominated by system sleep or that the socket failure was first observable at the wake boundary while the same sleep matches the runtime pause; a genuinely awake outage without either evidence remains real transport evidence without identifying which network/host layer caused it. A large `previous_ready_inbound_silence_ms` without a matching local stall remains useful pre-close half-open evidence. Use `--verbose` only when close codes, liveness deadlines, and retry delays are required.
|
|
99
99
|
|
|
100
|
-
A foreground MCP response is not durable delivery. Hosted synchronous calls reserve room for Worker and host settlement instead of occupying the complete interaction window: ordinary daemon-backed tools default to 20 seconds of remote execution plus a separate five-second Worker settlement margin; ordinary configurable browser/application foreground tools also default to 20 seconds, while compound `computer_observe` and `computer_act` default to 30 seconds; all configurable browser/application foreground tools retain their explicit 45-second maximum. Remote `exec_command`, `run_process`, and `run_local_command` no longer keep the child process inside that response lifetime. Each remote process request must carry a unique caller-held `idempotency_key` before dispatch; reuse that same key only when recovering an ambiguous acceptance response. The daemon commits the authorized operation as a principal-bound one-step managed job, launches it with interactive resource-admission priority, and returns a `job_id`
|
|
100
|
+
A foreground MCP response is not durable delivery. Hosted synchronous calls reserve room for Worker and host settlement instead of occupying the complete interaction window: ordinary daemon-backed tools default to 20 seconds of remote execution plus a separate five-second Worker settlement margin; ordinary configurable browser/application foreground tools also default to 20 seconds, while compound `computer_observe` and `computer_act` default to 30 seconds; all configurable browser/application foreground tools retain their explicit 45-second maximum. Remote `exec_command`, `run_process`, and `run_local_command` no longer keep the child process inside that response lifetime. Each remote process request must carry a unique caller-held `idempotency_key` before dispatch; reuse that same key only when recovering an ambiguous acceptance response. The daemon commits the authorized operation as a principal-bound one-step managed job, launches it with interactive resource-admission priority, and returns a `job_id` inside a 10-second acceptance budget; the Worker keeps a separate five-second settlement margin and adds principal-bound `recovery_key`/`control_key` capabilities to the hosted result. Preserve all three together: hosted `read_job` requires the read capability, hosted `cancel_job` requires the control capability, and a bare `job_id` is not remote recovery authority. If that acceptance response is lost to settlement timeout, HTTP response cancellation, or relay reconnect expiry after dispatch, the public error remains non-retryable for generic callers but carries the original key and the explicit recovery action `retry_same_tool_arguments_with_same_idempotency_key`; this reconciles against the retained job instead of authorizing a blind duplicate. The detached child may execute for up to 600 seconds after admission, but the managed runner can separately wait up to thirty minutes for cooperative machine-user resource admission before the child is spawned; the child execution deadline begins only after that admission succeeds. The shared ceiling is exposed machine-readably as `server_info.tool_delivery.managed_job_resource_admission_wait_max_ms`, because the same pre-spawn boundary applies to ordinary durable process jobs and owner `start_job` steps rather than to process tools alone. While the runner is in this pre-spawn state, `read_job.current_phase` is `resource_admission`; no command has started yet. An owner can correlate a long-running status at that phase with `diagnose_runtime.runtime.resource_admission` rather than interpreting it as a slow child process; a delegated non-owner should treat the phase itself as evidence that the child has not spawned, retain the same `job_id`, and avoid blind replay rather than attempting the owner-only machine-wide diagnostic. After admission, the phase returns to `steps`, `finally_steps`, or `recovery-cleanup` as appropriate. Completed step records preserve `duration_ms` as the total orchestration duration. Local/owner reads additionally expose `resource_admission_ms` as the pre-spawn portion so a delayed successful child can be distinguished from slow execution after the fact; delegated non-owner reads omit that machine-user scheduling timing rather than turning shared-host contention into a more precise cross-workload signal. The detached job survives MCP disconnect, relay reconnect, daemon restart, or service replacement. Non-owner process authority is unchanged: automatic durable execution still uses the delegated workspace sandbox and does not grant owner-only `start_job`. If a cached host schema omits a current required field, the Worker rejects before daemon dispatch with a normal no-side-effect tool error and requests a `tools/list` refresh rather than surfacing a protocol-only validation failure. Discovery instructions and tool descriptions both carry orchestration semantics, so `server/discover` and `tools/list` each advertise `ttlMs=0` and every host-visible tool description carries `Tool schema generation N`. `server_info.tool_delivery.tool_schema_generation`, `tool_schema_server_version`, `discovery_ttl_ms`, and `tool_list_ttl_ms` identify the live contract; `host_visible_schema_known_to_server=false` is equally important because a healthy new daemon/Worker cannot prove that an external host discarded an older cached action/tool snapshot. `host_turn_deadline_observable=false` means Machine Bridge cannot pre-compute the external assistant-turn deadline, while `managed_jobs_detached_from_mcp_response=true` records that an accepted durable job is not owned by that response lifetime. After an activation that changes hosted semantics, compare the live `server_info` generation and changed invocation behavior with the governed Workspace Action control snapshot when that product layer is applicable; automation may perform the supported refresh/review path without another conversational approval. Host-internal cache inspection is intentionally excluded from operational release verification. `start_process` remains the explicit daemon-lifetime path when interactive stdin or session-style incremental output is required, but hosted calls use a 10-second execution / 15-second settlement envelope and do not queue behind resource pressure: the first failed admission returns retryable `unavailable`; owner-local callers retain the cooperative wait. Hosted `read_process` supports paced same-response follow-up: each actual output/exit blocking wait lasts at most one second. If another would-block remote read arrives inside the fifteen-second blocking cooldown, the daemon keeps that same MCP call open until output/exit or the cooldown boundary rather than returning an immediate running checkpoint; the Worker reserves enough execution/settlement headroom for that server-side pacing. Results use `status_polling_mode=paced_followup` while the process remains live, plus `blocking_poll_throttled` and `next_blocking_poll_after_ms`; callers must not busy-loop and should respect that cooldown. A new hosted call waits at most fifteen seconds for daemon readiness, but that wait is charged against the call's existing execution budget; an in-flight disconnect likewise never pauses or extends the original absolute deadline. Pending-call reconnect retention is also bounded by the smaller of reconnect grace and that original remaining deadline, and diagnostics distinguish `original call deadline expired during reconnect` from a true full `reconnect grace expired` rather than labeling both cases as the latter. Owner-local stdio/CLI calls retain their synchronous local contract because they do not depend on a hosted response stream. Keep unrelated mutations and verification independently terminal, and never infer task success merely because a durable launch was accepted. For one coherent non-interactive sequence, prefer a repository umbrella command or multi-step `start_job` rather than creating many one-step durable process carriers. If the current task needs the result, hosted `read_job` may follow the known durable `job_id` with its preserved `recovery_key` repeatedly in the same assistant response until terminal state while calls continue to be accepted; active relay reads report `status_polling_mode=bounded_followup` and no longer recommend forced handoff. The normal hosted read is a 40-second server-side long-poll. Terminal settlement returns on the next bounded five-second internal poll; nonterminal status/phase/dependency progress is coalesced for at least 30 seconds by default, and `current_step`-only churn does not wake the hosted call. `wait_ms=0` is the explicit immediate-checkpoint mode, while public hosted `wait_ms` is capped at 60 seconds. The default stays at 40 seconds because live host evidence showed that overlong single requests can outlive the host invocation even though the durable job itself remains healthy; beta.151 reproduced that class with a second explicit 180-second `read_job` returning `mcp_network_error` while generation-18 continuity evidence recorded zero unplanned ready-socket disconnects. The coalescing floor reduces host-visible event density and does not shorten the managed job, the assistant task, or the six-hour managed-step ceiling. Do not busy-loop, do not replace server-side pacing with rapid immediate reads, do not use repeated `list_jobs`, `server_info`, or `diagnose_runtime` calls as substitute polling surfaces, and do not infer or preempt a host/tool deadline from elapsed wall-clock time. Hosted `list_jobs` is aggregate-only and intentionally cannot rediscover lost job IDs/names/recovery handles; detailed global inventory remains local CLI/stdio administration. Return the `job_id`, status, and current phase for later recovery only after an actual host/tool boundary is observed, external input or authorization is required, or the user explicitly requested a checkpoint; only a terminal status is task-completion evidence.
|
|
101
101
|
|
|
102
102
|
The daemon honors `HTTPS_PROXY`/`HTTP_PROXY` and `NO_PROXY` through standard environment-proxy resolution for remote Worker health and relay traffic. `wss:` targets use HTTPS proxy selection and `ws:` targets use HTTP proxy selection. Only HTTP and HTTPS proxy URLs are accepted. Invalid URLs or unsupported protocols fail startup with corrective guidance instead of entering the reconnect loop. Remote-owner `diagnose_runtime.runtime.relay.network_route` reports this value, while local stdio `server_info.runtime.relay.network_route` reports `system-network-stack`, `application-http-proxy`, or `invalid-application-proxy-configuration`. This field describes only Machine Bridge application-level proxy selection: an operating-system VPN/TUN may still intercept `system-network-stack` traffic. `network_route_scope`, outage timestamps/durations, close category/code, transport error class, and next retry timing make that distinction explicit; proxy endpoints and credentials are never returned or logged. The browser-broker CLI health probe is a separate loopback-only path: it accepts only canonical `127.0.0.1`, uses direct Node HTTP with no proxy agent, and does not depend on `NO_PROXY`.
|
|
103
103
|
|
package/docs/PRIVACY.md
CHANGED
|
@@ -11,7 +11,7 @@ npm run privacy:check
|
|
|
11
11
|
npm run privacy:history
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
`privacy:check` scans tracked and unignored new UTF-8 files and relative names for generic/encrypted/algorithm-specific private-key headers, AWS/GitHub/GitLab/npm/Slack/Google/live-payment/API token forms, JWT-shaped bearer values, embedded-credential URLs, absolute user-home paths, non-example email/`user@host` identifiers, credential-shaped filenames, and locally configured private identifiers. A tracked `.npmrc` is parsed: non-secret repository settings such as `engine-strict=true` are allowed, while authentication/identity keys, environment interpolation, and embedded credentials fail closed. Publication-surface symbolic links are rejected rather than followed. Binary, invalid UTF-8, and files above the bounded scanner limit require explicit manual review instead of being silently skipped. Findings report only file, line, and rule; the matched value is never printed.
|
|
14
|
+
`privacy:check` scans tracked and unignored new UTF-8 files and relative names for generic/encrypted/algorithm-specific private-key headers, AWS/GitHub/GitLab/npm/Slack/Google/live-payment/API token forms, current Machine Bridge account-password/OAuth/managed-job capability forms, JWT-shaped bearer values, embedded-credential URLs, absolute user-home paths, non-example email/`user@host` identifiers, credential-shaped filenames, and locally configured private identifiers. Runtime free-form log sanitization and repository publication scanning import the same common credential-pattern source so token alphabet/boundary fixes cannot silently drift between the two defenses. A tracked `.npmrc` is parsed: non-secret repository settings such as `engine-strict=true` are allowed, while authentication/identity keys, environment interpolation, and embedded credentials fail closed. Publication-surface symbolic links are rejected rather than followed. Binary, invalid UTF-8, and files above the bounded scanner limit require explicit manual review instead of being silently skipped. Findings report only file, line, and rule; the matched value is never printed.
|
|
15
15
|
|
|
16
16
|
Operational free-form log sanitization separately redacts compact JWT/JWS-shaped bearer values, including DPoP-proof-shaped compact tokens, before messages are emitted. That sanitizer is deliberately not a generic IP-address anonymizer: remote/client network addresses are omitted or reduced at the source boundary unless an explicit diagnostic contract requires them. This avoids relying on broad IPv4/IPv6 regexes that can corrupt unrelated timestamps, hashes, URLs, or protocol values while still keeping network identity out of routine logs.
|
|
17
17
|
|
|
@@ -101,8 +101,10 @@ MCP `2026-07-28` response streams are request-scoped, not persisted, and not res
|
|
|
101
101
|
|
|
102
102
|
During a brief relay interruption, the same local daemon process may retain an already-completed result envelope in memory until the Worker acknowledges it. This is relay-delivery continuity for an already-dispatched call, not a client-visible protocol session or replay store. Retention is bounded in both count and time: unacknowledged results share the existing 16-call recovery-ownership ceiling with active relay calls, retained results consume ordinary capacity while two control-plane slots remain reserved, the result store independently refuses to grow beyond 16 entries, and a result that never receives Worker acknowledgement expires after the maximum Worker tool-settlement lifetime (315 seconds) when the live relay heartbeat next prunes it. Expiry creates a private per-call replay-safety tombstone so the forgotten completed call cannot later be advertised as daemon-proven missing; unrelated calls retain their own non-execution proof. Tombstone identities are never projected. Diagnostics expose only aggregate counts (`active_calls`, `retained_results`, capacity totals), `automatic_redelivery_safe`, the number of unsafe call tombstones, and whether bounded tombstone capacity forced global fail-closed redelivery disablement. No call IDs, tool names, arguments, or result contents are exposed. A disconnected relay has the separately shorter reconnect-grace cleanup path. Tool arguments and results remain absent from normal operational logs, and durable work that must survive process or machine replacement uses the managed-job subsystem instead.
|
|
103
103
|
|
|
104
|
-
Managed-job capacity remains bounded to 512 retained states plus the existing time-based retention rules
|
|
104
|
+
Managed-job capacity remains bounded to 512 retained states plus the existing time-based retention rules. The daemon/local `list_jobs` inventory still returns at most 50 ordinary records in one response, while the hosted Worker projection removes job IDs, names, and `recent_process_recovery` handles and returns aggregate retained/capacity/activity state only. Completed one-step remote process carriers use an internal `transient_process` retention class solely to order capacity reclamation ahead of explicit managed-job terminal history. Active/staged dependency plans retain a private bounded witness containing only the referenced `job_id`, plan hash, and creation timestamp, and pin those referenced job records until the dependent plan is terminal; those witnesses contain no argv, output, path, credential, or resource value and are not returned in ordinary public status. The retention-class marker is likewise not publicly projected. This expands bounded recovery depth for long workflows without extending the seven-day terminal or 24-hour staged-plan lifetime.
|
|
105
105
|
|
|
106
|
-
Hosted durable acceptance errors may include a recovery descriptor naming `idempotency_key` as the caller-held recovery credential and instructing the caller to reuse the original request arguments. The descriptor deliberately does **not** echo the key value into a second host-visible payload. The local managed-job store likewise derives deterministic identity from the key without persisting the cleartext key.
|
|
106
|
+
Hosted durable acceptance errors may include a recovery descriptor naming `idempotency_key` as the caller-held acceptance-recovery credential and instructing the caller to reuse the original request arguments. The descriptor deliberately does **not** echo the key value into a second host-visible payload. The local managed-job store likewise derives deterministic identity from the key without persisting the cleartext key.
|
|
107
|
+
|
|
108
|
+
Successful hosted managed-job/process acceptance additionally returns `recovery_key` and `control_key` capabilities. These are bearer material and must be treated like access tokens in host transcripts/support artifacts. They are deterministic HMAC outputs over authenticated principal + job + purpose using the Worker OAuth identity key, so the Worker can reissue the same values after an idempotent acceptance replay without storing them. They are not persisted in daemon job state, not forwarded to the daemon after Worker verification, not mirrored into `Mcp-Param-*` routing headers, and ordinary log/privacy sanitizers recognize their `mcp_jr_`/`mcp_jc_` shapes. Hosted inventory never enumerates them or the job handles they protect. A host that incorrectly copies another conversation's still-valid capability has already crossed the boundary that Machine Bridge can observe; the Worker can validate capability ownership/purpose but cannot reconstruct an external host conversation identity that was never authenticated.
|
|
107
109
|
|
|
108
110
|
The signed daemon HTTPS fallback does not change those persistence rules. It carries the same daemon control/tool envelopes over TLS when WSS is unavailable and retains only bounded in-memory transport queues on each side. An unacknowledged envelope may be retransmitted with the same transport sequence after response loss, but duplicate sequences are discarded before business handling; no public MCP replay identifier or persisted fallback result store is created. Fallback authentication headers contain a short-lived root-certified session certificate, one-time nonce, timestamp, body hash, and signature; they are validated but not written to operational logs. Relay diagnosis retains only bounded numeric connect/probe timing, sender-backlog byte counts, and strict allowlisted transport reasons such as `connection_timeout` or `network_unreachable`; it never stores the raw socket error, hostname, address, DNS answer, certificate, VPN interface/node, proxy endpoint, arguments, or results. The fallback may retain its last coarse error class/reason after recovery so a later operator can correlate WSS and HTTPS failure windows, while current health remains represented separately by readiness and the current failure count. Browser broker ping/Pong carries only a bounded sequence number and is not content logging.
|
|
@@ -54,11 +54,13 @@ GitHub-only repository infrastructure changes that do not alter npm package cont
|
|
|
54
54
|
|
|
55
55
|
**The user is not a polling clock.** Once Machine Bridge has durably accepted a job, progress toward the result must not require the user to send `continue`, `继续`, or any equivalent message solely to authorize or trigger another status read. The orchestration layer owns bounded follow-up while tool calls continue to be accepted and the current task still needs the result; it must not infer or preempt a host/tool deadline from elapsed wall-clock time.
|
|
56
56
|
|
|
57
|
-
For a known managed job whose result is required by the current task, an active relay-origin `read_job` uses `status_polling_mode=bounded_followup` with `host_turn_handoff_recommended=false`, and the client may issue another `read_job` in the same assistant response. **Autonomy must not be implemented as a host-side spin loop or as one overlong host call:** active hosted `read_job` uses the advertised 40-second server-side long-poll by default. Terminal settlement returns on the next bounded poll; nonterminal status/phase/dependency progress is coalesced for at least 30 seconds by default, while `current_step`-only churn remains visible in the eventual status but does not wake the host call by itself. `wait_ms=0` is an explicit immediate checkpoint, not the normal wait strategy, while public hosted `wait_ms` is capped at 60 seconds. The default must remain at the empirically safe 40-second interval rather than being raised merely to reduce call count; longer jobs use another paced read of the same `job_id` instead of one overlong host request. Per-call survival and aggregate host-response lifetime are separate constraints: arithmetic such as `duration / wait interval` is useful for estimating interaction density but is **not** evidence that one assistant response can carry every read to terminal state. Process sessions use the corresponding `paced_followup` model: after a blocking read arms the fifteen-second cooldown, another would-block request is paced inside that same MCP call until output/exit or the cooldown boundary rather than returned as a rapid running checkpoint. The managed-job server-side long-poll, nonterminal progress coalescing, remote one-second actual output/exit blocking `read_process` cap, fifteen-second cooldown, `next_blocking_poll_after_ms`, durable ownership, idempotency, and actual request/transport limits are the anti-amplification controls; a mandatory one-read-per-response, rapid immediate-checkpoint loop, later-user-turn boundary, or guessed elapsed-time cutoff is not an acceptable substitute for those controls.
|
|
57
|
+
For a known hosted managed job whose result is required by the current task, the client must preserve the acceptance `job_id` together with its principal-bound `recovery_key`; an active relay-origin `read_job` uses those credentials, reports `status_polling_mode=bounded_followup` with `host_turn_handoff_recommended=false`, and the client may issue another capability-bound `read_job` in the same assistant response. **Autonomy must not be implemented as a host-side spin loop or as one overlong host call:** active hosted `read_job` uses the advertised 40-second server-side long-poll by default. Terminal settlement returns on the next bounded poll; nonterminal status/phase/dependency progress is coalesced for at least 30 seconds by default, while `current_step`-only churn remains visible in the eventual status but does not wake the host call by itself. `wait_ms=0` is an explicit immediate checkpoint, not the normal wait strategy, while public hosted `wait_ms` is capped at 60 seconds. The default must remain at the empirically safe 40-second interval rather than being raised merely to reduce call count; longer jobs use another paced read of the same `job_id` instead of one overlong host request. Per-call survival and aggregate host-response lifetime are separate constraints: arithmetic such as `duration / wait interval` is useful for estimating interaction density but is **not** evidence that one assistant response can carry every read to terminal state. Process sessions use the corresponding `paced_followup` model: after a blocking read arms the fifteen-second cooldown, another would-block request is paced inside that same MCP call until output/exit or the cooldown boundary rather than returned as a rapid running checkpoint. The managed-job server-side long-poll, nonterminal progress coalescing, remote one-second actual output/exit blocking `read_process` cap, fifteen-second cooldown, `next_blocking_poll_after_ms`, durable ownership, idempotency, and actual request/transport limits are the anti-amplification controls; a mandatory one-read-per-response, rapid immediate-checkpoint loop, later-user-turn boundary, or guessed elapsed-time cutoff is not an acceptable substitute for those controls.
|
|
58
58
|
|
|
59
59
|
Host-visible tool-event density must also be controlled before polling begins. A coherent non-interactive workflow that requires several local commands should use a repository-native umbrella command where one exists, otherwise one multi-step `start_job` or the smallest practical number of managed jobs. Do not create a fresh one-step `run_process`/`exec_command`/`run_local_command` carrier for every tiny probe merely because each command is short. This is an orchestration-density rule, not a duration limit: it must never be implemented by shortening the task, reducing the six-hour managed-step ceiling, or handing control back to the user after an arbitrary number of minutes.
|
|
60
60
|
|
|
61
|
-
The transport/runtime must reduce event density structurally as well as through guidance. A hosted one-step process carrier is accepted durably first, then may spend only the documented short initial-settlement window returning a terminal helper result in that same response. If it remains active, the original durable recovery contract wins unchanged. `list_jobs` must prioritize unreadable/active/staged recovery state before terminal helper history, and
|
|
61
|
+
The transport/runtime must reduce event density structurally as well as through guidance. A hosted one-step process carrier is accepted durably first, then may spend only the documented short initial-settlement window returning a terminal helper result in that same response. If it remains active, the original durable recovery contract wins unchanged. The daemon/local `list_jobs` inventory must prioritize unreadable/active/staged recovery state before terminal helper history, but the hosted Worker projection must expose only aggregate inventory and must not enumerate job IDs, names, or recent recovery handles. Hosted reads/dependencies require the acceptance `recovery_key`, cancellation requires the separate `control_key`, and both capability types must be verified before daemon dispatch and omitted from logs/routing metadata. Owner diagnostics must expose bounded event-density, job-churn, and resource-waiter-reason aggregates so a later interruption can be classified from persisted evidence rather than reconstructed from host-visible prose. None of these mechanisms may become an aggregate task-duration ceiling or a user-driven polling requirement.
|
|
62
|
+
|
|
63
|
+
Shared browser focus is likewise not hosted request identity. Hosted browser content/action tools that can otherwise default to the active tab must require an explicit `tab_id` selected from current tab inventory, and hosted browser `computer_observe` must bind its snapshot to that explicit target before later snapshot-bound actions. A concurrent hosted conversation changing the active tab must not retarget an already-specified operation.
|
|
62
64
|
|
|
63
65
|
An active operation may be handed back with its durable recovery identifier only after a real host/tool/runtime boundary is actually observed, external human input/authorization is required, or the user explicitly requested a checkpoint. Elapsed minutes, a guessed host budget, and merely having performed one active status read in the current response are not valid handoff conditions. If a real host/tool boundary ends the response, the next available response must resume the same durable identifier rather than resubmit the underlying operation. A typed `read_job` `not_found` means that Machine Bridge no longer retains that job record; it is not proof that the underlying operation never executed and must never be converted into a blind retry of a side effect. Inventory and diagnostic surfaces such as `list_jobs`, `server_info`, and `diagnose_runtime` are not alternate polling channels and must not be used to evade the authoritative pacing contract.
|
|
64
66
|
|
|
@@ -123,7 +125,7 @@ Any deliberate boundary exception must document the dependency, reason, owner, t
|
|
|
123
125
|
|
|
124
126
|
## 4. Public contracts and generated documentation
|
|
125
127
|
|
|
126
|
-
The MCP tool catalog in `src/shared/tool-catalog.json` is the authoritative
|
|
128
|
+
The MCP tool catalog in `src/shared/tool-catalog.json` is the authoritative base API description for names, daemon availability, annotations, and local/stdio JSON input schemas. The authenticated Worker may deliberately narrow or extend hosted schemas at that boundary; those projections must live in focused Worker helpers, be covered by actual `tools/list`/validator tests, and be documented as hosted-only requirements rather than copied back into the local base schema. The base catalog is rendered into [TOOL_REFERENCE.md](TOOL_REFERENCE.md); CI rejects stale generated documentation.
|
|
127
129
|
|
|
128
130
|
Swagger/OpenAPI is required only if the project later exposes a user-facing HTTP REST API. It is not a substitute for MCP tool schemas or end-to-end MCP protocol tests. Hand-maintained copies of generated contracts are prohibited.
|
|
129
131
|
|