machine-bridge-mcp 3.0.0-beta.174 → 3.0.0-beta.185
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 +6 -8
- package/README.md +1 -1
- package/browser-extension/manifest.json +1 -1
- package/docs/AGENT_CONTEXT.md +13 -4
- package/docs/AUDIT.md +10 -2
- package/docs/MANAGED_JOBS.md +2 -2
- package/docs/OPERATIONS.md +7 -1
- package/docs/PRIVACY.md +1 -1
- package/package.json +2 -1
- package/src/local/agent-context-projection.mjs +8 -1
- package/src/local/agent-context.mjs +4 -0
- package/src/local/agent-contract.mjs +21 -1
- package/src/local/application-capability-match.mjs +15 -0
- package/src/local/cli-options.mjs +2 -1
- package/src/local/cli-service.mjs +3 -1
- package/src/local/cli.mjs +3 -35
- package/src/local/daemon-http-relay-connection.mjs +2 -0
- package/src/local/default-instructions.mjs +11 -0
- package/src/local/execution-routing-intent.mjs +108 -0
- package/src/local/execution-routing.mjs +31 -41
- package/src/local/job-runner.mjs +2 -2
- package/src/local/managed-job-dependency-retention.mjs +8 -1
- package/src/local/managed-job-output-redaction.mjs +71 -2
- package/src/local/network-proxy.mjs +34 -10
- package/src/local/resilient-relay-connection.mjs +13 -4
- package/src/local/runtime-capabilities.mjs +3 -8
- package/src/local/service.mjs +7 -3
- package/src/local/workspace-profile-migration.mjs +569 -0
- package/src/shared/server-metadata.json +1 -1
- package/src/worker/index.ts +1 -1
- package/src/worker/managed-job-hosted-authority.ts +5 -2
- package/src/worker/pending-call-reconnect-settlement.ts +13 -0
- package/src/worker/pending-calls.ts +2 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 3.0.0-beta.
|
|
4
|
-
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
- Keep
|
|
8
|
-
-
|
|
9
|
-
- Keep relay recovery observability content-free: normal logs and public diagnostics do not gain call IDs, arguments, results, credentials, proxy endpoints, raw socket errors, or personal paths from this recovery path.
|
|
10
|
-
- Advance package, Worker, and browser-extension identity to `3.0.0-beta.174`; hosted tool schema generation remains 27 because no MCP tool argument/result contract changes in this release.
|
|
3
|
+
## 3.0.0-beta.185 - 2026-09-11
|
|
4
|
+
|
|
5
|
+
- Preserve interrupted workspace-profile migration after a source-profile rename when Windows exposes the same state root through a different long/short or junction-equivalent path: relocated historical proof canonicalizes only the nearest existing ancestor, still requires the exact historical profile hash plus `state.json` suffix, and rejects a different canonical ancestor.
|
|
6
|
+
- Normalize Windows native-path identity during workspace migration so active locks and relocated historical profile state remain comparable across ordinary drive paths and native namespace-prefixed forms without weakening containment checks.
|
|
7
|
+
- Keep hosted validation compatible with CodeQL by replacing dynamic resolver regular-expression construction with fixed token parsing while retaining the same release-carrier and worktree-selection behavior.
|
|
8
|
+
- Invalidate the stale beta.184 acceptance after the packaged migration repair and advance package, Worker, and browser-extension identity to `3.0.0-beta.185`; fresh activation, deployed OAuth canary evidence, and observed live verification are required before recording acceptance.
|
|
11
9
|
|
|
12
10
|
## Historical releases
|
|
13
11
|
|
package/README.md
CHANGED
|
@@ -174,7 +174,7 @@ The shared source of truth is `src/shared/policy-contract.json`. The generated m
|
|
|
174
174
|
|
|
175
175
|
For routine remote health checks, prefer `server_info` with `detail: "summary"`; the empty/default call remains full diagnostics and, for owner/full callers, includes a privacy-bounded durable continuity summary that survives Worker isolate replacement without retaining identities, call IDs, tool arguments, results, endpoints, or close reasons. For routine workspace inventory, `project_overview` also accepts `detail: "summary"`; it preserves policy/tool counts and top-level names/types without repeating exact tool arrays, account identity, routing fingerprints, or per-entry paths/sizes. Its empty/default call likewise remains full for compatibility. For remote calls, `server_info.authorization.effective_policy` and, when exact membership is needed, the full projection's `effective_tools` are authoritative. Daemon policy and tools describe only the local capability ceiling before account-role and host-side filtering.
|
|
176
176
|
|
|
177
|
-
`tools/list` is the authenticated account's current discovery catalog. Discovery instructions and tool descriptions carry execution/orchestration semantics, so both `server/discover` and `tools/list` advertise `ttlMs=0`. Current MCP 2026-07-28 remote discovery also advertises `tools.listChanged=true`: a client that opts into `toolsListChanged` through `subscriptions/listen` receives a correlated acknowledgement and level-trigger `notifications/tools/list_changed` event, then re-fetches `tools/list`. The request-scoped subscription remains open until explicit cancellation or the advertised bounded server lease expires; the lease is a fail-safe for HTTP disconnects that the Worker runtime cannot reliably observe and does not replace the initial level-trigger/refetch contract. Every host-visible tool description carries `Tool schema generation N`; `server_info.tool_delivery` exposes the current `tool_schema_generation`, `tool_schema_server_version`, and `tool_list_ttl_ms`, while explicitly reporting that Machine Bridge cannot observe which schema generation an external host has actually cached. A generation change therefore requires the subscription/refetch path or another host-side schema refresh plus post-activation verification. Discovery is not authority: every `tools/call` is still intersected with the current end-to-end-ready daemon policy and tool ceiling, and fails retryably with `unavailable` when no daemon is ready. `server_info.tool_delivery` also distinguishes the advertised catalog from the currently effective daemon/account intersection. WebSocket is the preferred daemon transport: verified ready traffic resumes immediately, but reconnect attempt history resets only after five seconds of generation-stable ready uptime so a shorter ready/close flap keeps its prior exponential backoff position. It requests a protocol-level Ping after five seconds and gives an actually dispatched Ping its full ten-second Pong deadline, then uses one independent fifteen-second application-confirmation window before a ready WSS may be terminated as a transport black hole. A protocol Pong or explicit application `pong` during that second stage preserves WSS; ordinary tool/control inbound remains receive-side evidence and cannot clear transport suspicion, while local event-loop stalls cancel remote suspicion and use the separate recovery-grace path. The periodic application heartbeat remains twenty-five/seventy-five seconds after end-to-end readiness, and the Worker keeps a wider ninety-second fallback. WSS connect attempts have a thirty-second outer budget. Signed HTTPS is independent of that budget: on first-stage WSS suspicion the same root-certified ephemeral device identity prewarms HTTPS in standby, and a real WSS loss promotes that path to exact-generation takeover while aborting any obsolete standby request. Fallback requests bind the fixed route/origin/server/version, a short-lived nonce, timestamp, and exact body hash; they use a seven-second request deadline, twelve-second liveness window, one-second ordinary poll cadence, and a 750 ms minimum request-start interval. The first authenticated exchange enters probing immediately, so verified readiness requires at most two bounded exchanges rather than a separate challenge round trip. Candidate → probing → verified-ready handover prevents the Worker from dispatching until the daemon has processed `ready_ack` and returned sequenced `https_ready`; a same-instance takeover may retire a Worker-side zombie WSS only after the signed candidate preconditions pass. Both directions use bounded contiguous transport sequences, so a lost HTTP response retransmits the same transport envelope and duplicates are discarded before business handling; this does not restore MCP sessions, recovery GET, `Last-Event-ID`, or public result persistence. Same-instance `resume_calls` / `resume_calls_ack` remains authoritative for in-flight ownership. A planned daemon shutdown is a different boundary: before a ready runtime closes its relay it sends `daemon_draining`; the Worker settles calls still owned by that daemon with structured `daemon_planned_drain` recovery and acknowledges the drain. In-flight `read_job` is explicitly read-only recoverable with `recovery.mode=read_same_job` plus the original `job_id`; resume by reading that same job after reconnect. This does not transfer an executing call to a new daemon process, whose random instance identity remains intentionally distinct, and it cannot revive an external assistant turn that has already ended. The daemon sends `resume_calls_ack.missing_ids` only after replacement readiness, only for IDs absent from both its active-call set and unacknowledged-result ledger, and only while it still has fail-closed proof that missing ownership means the call did not execute locally. If a completed-but-unacknowledged result expires, `diagnose_runtime.runtime.relay_result_recovery.automatic_redelivery_safe` becomes false and missing-ID automatic redelivery is disabled rather than risking duplicate side effects. A safe proven-undelivered call may be retransmitted with the same call ID, arguments, authority, and a reduced timeout inside the original execution deadline; a call that may have executed is never automatically replayed. Separately, if that same daemon already executed a call and its terminal result is delayed by the transport break, the Worker may retain the original in-memory settlement owner for
|
|
177
|
+
`tools/list` is the authenticated account's current discovery catalog. Discovery instructions and tool descriptions carry execution/orchestration semantics, so both `server/discover` and `tools/list` advertise `ttlMs=0`. Current MCP 2026-07-28 remote discovery also advertises `tools.listChanged=true`: a client that opts into `toolsListChanged` through `subscriptions/listen` receives a correlated acknowledgement and level-trigger `notifications/tools/list_changed` event, then re-fetches `tools/list`. The request-scoped subscription remains open until explicit cancellation or the advertised bounded server lease expires; the lease is a fail-safe for HTTP disconnects that the Worker runtime cannot reliably observe and does not replace the initial level-trigger/refetch contract. Every host-visible tool description carries `Tool schema generation N`; `server_info.tool_delivery` exposes the current `tool_schema_generation`, `tool_schema_server_version`, and `tool_list_ttl_ms`, while explicitly reporting that Machine Bridge cannot observe which schema generation an external host has actually cached. A generation change therefore requires the subscription/refetch path or another host-side schema refresh plus post-activation verification. Discovery is not authority: every `tools/call` is still intersected with the current end-to-end-ready daemon policy and tool ceiling, and fails retryably with `unavailable` when no daemon is ready. `server_info.tool_delivery` also distinguishes the advertised catalog from the currently effective daemon/account intersection. WebSocket is the preferred daemon transport: verified ready traffic resumes immediately, but reconnect attempt history resets only after five seconds of generation-stable ready uptime so a shorter ready/close flap keeps its prior exponential backoff position. It requests a protocol-level Ping after five seconds and gives an actually dispatched Ping its full ten-second Pong deadline, then uses one independent fifteen-second application-confirmation window before a ready WSS may be terminated as a transport black hole. A protocol Pong or explicit application `pong` during that second stage preserves WSS; ordinary tool/control inbound remains receive-side evidence and cannot clear transport suspicion, while local event-loop stalls cancel remote suspicion and use the separate recovery-grace path. The periodic application heartbeat remains twenty-five/seventy-five seconds after end-to-end readiness, and the Worker keeps a wider ninety-second fallback. WSS connect attempts have a thirty-second outer budget. Signed HTTPS is independent of that budget: on first-stage WSS suspicion the same root-certified ephemeral device identity prewarms HTTPS in standby, and a real WSS loss promotes that path to exact-generation takeover while aborting any obsolete standby request. Fallback requests bind the fixed route/origin/server/version, a short-lived nonce, timestamp, and exact body hash; they use a seven-second request deadline, twelve-second liveness window, one-second ordinary poll cadence, and a 750 ms minimum request-start interval. The first authenticated exchange enters probing immediately, so verified readiness requires at most two bounded exchanges rather than a separate challenge round trip. Candidate → probing → verified-ready handover prevents the Worker from dispatching until the daemon has processed `ready_ack` and returned sequenced `https_ready`; a same-instance takeover may retire a Worker-side zombie WSS only after the signed candidate preconditions pass. Both directions use bounded contiguous transport sequences, so a lost HTTP response retransmits the same transport envelope and duplicates are discarded before business handling; this does not restore MCP sessions, recovery GET, `Last-Event-ID`, or public result persistence. Same-instance `resume_calls` / `resume_calls_ack` remains authoritative for in-flight ownership. A planned daemon shutdown is a different boundary: before a ready runtime closes its relay it sends `daemon_draining`; the Worker settles calls still owned by that daemon with structured `daemon_planned_drain` recovery and acknowledges the drain. In-flight `read_job` is explicitly read-only recoverable with `recovery.mode=read_same_job` plus the original `job_id`; resume by reading that same job after reconnect. This does not transfer an executing call to a new daemon process, whose random instance identity remains intentionally distinct, and it cannot revive an external assistant turn that has already ended. The daemon sends `resume_calls_ack.missing_ids` only after replacement readiness, only for IDs absent from both its active-call set and unacknowledged-result ledger, and only while it still has fail-closed proof that missing ownership means the call did not execute locally. If a completed-but-unacknowledged result expires, `diagnose_runtime.runtime.relay_result_recovery.automatic_redelivery_safe` becomes false and missing-ID automatic redelivery is disabled rather than risking duplicate side effects. A safe proven-undelivered call may be retransmitted with the same call ID, arguments, authority, and a reduced timeout inside the original execution deadline; a call that may have executed is never automatically replayed. Separately, if that same daemon already executed a call and its terminal result is delayed by the transport break, the Worker may retain the original in-memory settlement owner for one fixed fifteen-second window beyond its original settlement deadline, including when that original settlement deadline was already at the ordinary per-tool ceiling. That delivery-only extension cannot authorize redelivery or new execution, cannot accumulate across repeated handovers, and does not create client-visible replay or durable result state. A new call may wait up to fifteen seconds for a verified daemon channel, but measured recovery time is deducted from that call's original execution budget instead of extending the hosted foreground envelope. Hosted synchronous calls otherwise retain their ordinary 20-second execution plus separate five-second Worker settlement margin; configurable browser/application tools retain 20-second ordinary defaults, compound `computer_observe` / `computer_act` retain 30-second defaults, and the explicit remote maximum remains 45 seconds. Remote `exec_command`, `run_process`, and `run_local_command` require a caller-held `idempotency_key`, commit a principal-bound one-step managed job, and remain recoverable through bounded same-response `read_job` follow-up when the current task needs terminal state. Hosted active `read_job` uses a server-side 40-second long-poll by default and returns earlier on meaningful job progress or terminal state; `wait_ms=0` requests an immediate checkpoint, while every public hosted call is capped at 60 seconds. The default remains 40 seconds: live hosted evidence carried both the default and an explicit 60-second read, while beta.151 later reproduced `mcp_network_error` on a second explicit 180-second read even though the Worker-to-daemon WebSocket remained continuously ready. Longer tasks therefore keep the same `job_id` and use another server-paced read rather than one overlong host request. This keeps long-task waiting inside Machine Bridge within the demonstrated per-call host lifetime; the 40-second interval also bounds interaction density to at most 150 reads for a synthetic unchanged 100-minute job, but that arithmetic does not prove that one assistant response can survive the aggregate duration or call count. If a real host/tool boundary ends a response, preserve the durable identifier and resume the same operation later rather than resubmitting its side effect. `start_process` remains daemon-lifetime interactive state; hosted `read_process` permits paced same-response follow-up, defaults an omitted relay `wait_ms` to the one-second blocking cap, and paces another would-block read inside the fifteen-second cooldown within that same MCP call until output/exit or the cooldown boundary. Active job/process reads do not force a user-turn handoff. Callers must not infer or preempt a host/tool deadline from elapsed wall-clock time: while calls continue to be accepted and the task still needs the result, bounded same-response follow-up may continue. Handoff is reserved for an actual observed host/tool boundary, required external input or authorization, or an explicit user checkpoint, while busy loops and status-surface substitution remain prohibited. The durable process façade preserves account/tool authority and delegated workspace sandbox rather than expanding privileges.
|
|
178
178
|
|
|
179
179
|
`full` is the daemon capability ceiling. An authenticated owner may exercise it without per-operation approval IDs. Delegated reviewer, editor, and operator accounts remain inside immutable role ceilings; out-of-role operations are denied rather than converted into a temporary elevation workflow. Process sessions, retained output, and managed jobs are additionally bound to account, client, and refresh-token family. See [local authorization](docs/LOCAL_AUTHORIZATION.md).
|
|
180
180
|
|
|
@@ -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.185",
|
|
34
34
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxryYkpZhq8+VAQLHcGS9BAHQcyKX8RHGIpIwvtIVRU/rcOcE0bNdnM0aZJ/h6xWQsGDHlhvjT2+1aJaAn/9k8473BRWajzVXld961CdHYVFVHoce2hHiSJ0xydWrHMMZhAm0mN0UzjEpgZ0tMw209efcZHIvSwuxhteZMRy4kyiVjwFlOf5oXFCxRuCJnPj3AK9CmCf4XgEBuPIJ0TZmjGHOOdBvJmbCNnAWXYEo5/mf7MfCGhV4IJ1hNuhpoNQfOFKMUcw9/v/IpT62XpfXdGYTfGYCmCjC+gntK1spbkr2P4/2+sYMQtLpse71mpSNGXfcf3abU55Vpn+gncSxRQIDAQAB"
|
|
35
35
|
}
|
package/docs/AGENT_CONTEXT.md
CHANGED
|
@@ -157,7 +157,16 @@ A project manifest lives at `.machine-bridge/agent.json`:
|
|
|
157
157
|
"argv": ["npm", "run", "check"],
|
|
158
158
|
"cwd": ".",
|
|
159
159
|
"timeout_seconds": 600,
|
|
160
|
-
"allow_extra_args": false
|
|
160
|
+
"allow_extra_args": false,
|
|
161
|
+
"execution_mode": "foreground"
|
|
162
|
+
},
|
|
163
|
+
"long-release": {
|
|
164
|
+
"description": "Run a repository-defined long lifecycle.",
|
|
165
|
+
"argv": ["npm", "run", "release:lifecycle"],
|
|
166
|
+
"cwd": ".",
|
|
167
|
+
"timeout_seconds": 600,
|
|
168
|
+
"execution_mode": "managed_job",
|
|
169
|
+
"managed_job_timeout_seconds": 21600
|
|
161
170
|
}
|
|
162
171
|
}
|
|
163
172
|
}
|
|
@@ -190,13 +199,13 @@ description: Review a release without publishing it.
|
|
|
190
199
|
|
|
191
200
|
The entrypoint requires non-empty `name` and `description`. Invalid bundles are skipped with bounded warnings. Symlinked skill directories are followed after canonical policy validation; symbolic-link entrypoint files are rejected. Traversal, depth, entries, summaries, content, and inventory are bounded.
|
|
192
201
|
|
|
193
|
-
No persistent skill or project-context index is trusted as authoritative. `session_bootstrap`, `agent_context`, and `resolve_task_capabilities` rebuild the relevant context; skill-list/load calls rescan effective roots. The refresh fingerprint binds the target/scope, configuration paths, instruction source/precedence/content identity, skill source identity, and complete registered-command definition including cwd and timeout. A matching `known_refresh_fingerprint` permits response compaction only; task ranking, effective-policy filtering, installed-application matching, and route scoring still run. Newly created or edited files are visible without restarting the daemon or changing the MCP tool catalog.
|
|
202
|
+
No persistent skill or project-context index is trusted as authoritative. `session_bootstrap`, `agent_context`, and `resolve_task_capabilities` rebuild the relevant context; skill-list/load calls rescan effective roots. The refresh fingerprint binds the target/scope, configuration paths, instruction source/precedence/content identity, skill source identity, and complete registered-command definition including cwd, foreground timeout, execution mode, and managed-job timeout. A matching `known_refresh_fingerprint` permits response compaction only; task ranking, effective-policy filtering, installed-application matching, and route scoring still run. Newly created or edited files are visible without restarting the daemon or changing the MCP tool catalog.
|
|
194
203
|
|
|
195
204
|
## Progressive disclosure and task selection
|
|
196
205
|
|
|
197
206
|
`agent_context` returns bounded skill metadata. `load_local_skill` returns full instructions only for one selected bundle. `resolve_task_capabilities` tokenizes the current task, ranks skill names/descriptions, command names/descriptions/argv, and public tool definitions, then scores compatible execution surfaces as sets. It returns a schema-versioned envelope with the primary route, alternatives, ambiguity, fallback routes, ranked tools, and failure-aware guidance. Scores are relative within the current response, not probabilities or values to compare across package versions. Registered commands, direct Bash/argv, interactive sessions, durable jobs, files/Git, browser, applications, protected resources, and diagnostics remain separate choices; the advice does not hide or disable any effective-policy-visible tool.
|
|
198
207
|
|
|
199
|
-
The resolver compares the task with cached installed-application names for every authenticated policy profile. This is an intentional read-only inventory surface: delegated roles may see application names and launcher/bundle identifiers, while external paths remain projected. Action routes are still filtered through the authenticated account/daemon policy intersection, so a delegated reviewer connected to a full daemon can discover an installed application without acquiring launch, Accessibility, browser, shell, or write authority.
|
|
208
|
+
The resolver compares the task with cached installed-application names for every authenticated policy profile. An exact full application name may match directly; weaker lexical name fragments require an application-operation intent such as opening, inspecting, focusing, or operating an application. Repository/version words that merely overlap an installed application name are therefore not application intent. This is an intentional read-only inventory surface: delegated roles may see application names and launcher/bundle identifiers, while external paths remain projected. Action routes are still filtered through the authenticated account/daemon policy intersection, so a delegated reviewer connected to a full daemon can discover an installed application without acquiring launch, Accessibility, browser, shell, or write authority.
|
|
200
209
|
|
|
201
210
|
Matching remains deterministic and local. Hyphens, underscores, dots, and whitespace are normalized; common English inflections are reduced to a small canonical form; and a bounded Chinese/English workflow vocabulary covers creation, improvement, installation, search, current/official documentation, verification, testing, frontend/design, browser/web, email, performance, and security intents. Capability-name token matches receive more weight than incidental words in a long description. This lets Chinese tasks select English-metadata skills such as `skill-creator`, `web-research-cli`, and `skill-installer`, while avoiding the prior tie where generic “create” wording could select `frontend-design`. An explicitly named skill or registered command still receives the strongest deterministic boost.
|
|
202
211
|
|
|
@@ -204,7 +213,7 @@ This ranking is deterministic local assistance, not semantic certainty. Weak pos
|
|
|
204
213
|
|
|
205
214
|
## Registered and automatic package commands
|
|
206
215
|
|
|
207
|
-
`run_local_command` spawns the registered argv directly rather than parsing caller text through a Machine Bridge shell. Manifest commands control working directory, timeout ceiling,
|
|
216
|
+
`run_local_command` spawns the registered argv directly rather than parsing caller text through a Machine Bridge shell. Manifest commands control working directory, foreground timeout ceiling, whether caller arguments are accepted, and execution mode. `execution_mode` defaults to `foreground`. A command whose complete lifecycle may exceed the foreground carrier ceiling can declare `execution_mode: "managed_job"` plus `managed_job_timeout_seconds` from 1 through 21,600; capability resolution then recommends `start_job` for a positive request to execute that matched command. Negated, quoted, explanatory, or read-only references to the same command do not create a task supervisor. The registered command metadata remains planning context rather than extra execution authority.
|
|
208
217
|
|
|
209
218
|
When the project root has a valid, non-symbolic-link `package.json`, safe script names are exposed automatically as `package.<normalized-name>` commands. The manager is selected from the validated `packageManager` field or one unambiguous lockfile; npm is used only when no manager signal exists. Conflicting lockfiles suppress automatic commands until the ambiguity is resolved. Only script names and bounded built-in workflow-intent terms are exposed. Script bodies and dependency values are not injected. Automatic commands accept no caller-supplied extra arguments, use the project root as cwd, and have a bounded timeout. Explicit manifests are applied afterward, so they can override or delete an automatic command by name.
|
|
210
219
|
|
package/docs/AUDIT.md
CHANGED
|
@@ -17,10 +17,18 @@ This file is the current audit summary. Historical findings, closed incidents, a
|
|
|
17
17
|
- Portable JWK device roots now renew the daemon's 24-hour root-certified ephemeral session in-process before expiry. WSS and signed HTTPS share one current-session provider; suspension-delayed authentication renews synchronously at the authentication boundary, while renewal failures retain the old session and preserve the existing fail-closed expiry path. Secure Enclave roots remain non-renewable without user presence.
|
|
18
18
|
- The reviewed Sharp security floor is 0.35.4 in both the main development graph and the private Wrangler toolchain. The beta.171 activation preflight was blocked before Worker/service mutation when a newly published libheif advisory caused npm audit to report three high-severity aggregate findings through Sharp -> Miniflare -> Wrangler; an isolated 0.35.4 override retained Wrangler 4.127.1 and workerd 1.20260828.1 while reducing both current production-only audits to zero vulnerabilities.
|
|
19
19
|
- Process-lock readers now treat only `MBM_IDENTITY_CHANGED` as a bounded atomic-publication race and retry at most four observations. This is narrower than generic activation recovery: persistent identity churn and every other storage/link/permission failure still fail closed. The change is driven by beta.172 live evidence where the exact candidate Worker and service were already verified ready, but the activation subprocess observed the daemon lock while startup-readiness publication atomically replaced that file and therefore returned failure instead of writing the activation record.
|
|
20
|
-
- Same-daemon relay recovery now separates execution authority from terminal-result delivery. A transient transport loss may retain only the original in-memory settlement owner for
|
|
20
|
+
- Same-daemon relay recovery now separates execution authority from terminal-result delivery. A transient transport loss may retain only the original in-memory settlement owner for one fixed fifteen-second window beyond its original settlement deadline, including when that original settlement deadline was already at the ordinary tool ceiling. Safe missing-call redelivery remains bound to the original execution deadline, repeated handovers cannot accumulate grace, cancellation still removes ownership, and no client-visible replay or persistent result store is introduced.
|
|
21
|
+
- Compound-task continuity is now explicit planning guidance rather than an implicit host behavior. Multi-command, multi-project, long-running, or interruption-sensitive non-interactive work is routed toward one durable managed job, recovery guidance requires resuming the same accepted job after reconnect, and built-in working agreements reject directory-name freshness as checkout identity evidence. Direct shell remains an available fallback; durability is ownership semantics, not a claim that the relay transport cannot fail.
|
|
22
|
+
- Workspace relocation now has an explicit offline state-migration invariant. The beta.175 candidate exposed stale global selection after the beta165 worktree was archived; beta.176 then proved that changing selection alone still leaves the deployed profile and machine-service owner keyed to the historical canonical path; beta.177 exposed targetless `service stop` still loading selected-workspace state. The unaccepted beta.178 candidate added the migration path, but owner-machine recovery then exposed two post-verification gaps: read-only inspection can leave an unpopulated destination shell, and a recurring external launchd helper that stopped the daemon before later preflight checks converted a populated-destination rejection into repeated service removal. Beta.179 makes targetless stop provider-global/state-free, accepts only absent or provably unpopulated destination profiles, proves provider/job/lock quiescence before pruning any empty shell, never recursively clears destination state, tolerates only an absent historical owner entry script while validating one that still exists, canonicalizes path aliases through `realpath`, and injects `HOME` into macOS launchd definitions. Recurring external migration helpers and compatibility symlinks are not accepted repairs.
|
|
23
|
+
|
|
24
|
+
- Beta.180 is a controlled, unaccepted continuity candidate. Its target is two-layer fault containment rather than an impossible no-disconnect promise: keep signed HTTPS standby and the fallback proxy transport warm so a transient WSS 1006 can take the bounded exact-generation takeover path without a cold start, and make long/compound/keep-working execution explicitly owned by one durable task supervisor instead of host-fragmented one-step calls. The candidate must preserve no-replay and fail-closed transport ownership, keep short/interactive routing unchanged, and may not be called accepted until frozen-tree verification and live owner-machine activation evidence exist.
|
|
25
|
+
|
|
26
|
+
- Beta.181 supersedes the activated-but-unaccepted beta.180 candidate after live owner-machine routing exposed two semantic classification defects rather than a relay failure: `非交互工作` and conditional `外部输入或授权` wording could exclude task supervision, while a project token could weak-match an unrelated installed application sharing one lexical fragment. The repair narrows interactive intent to explicit process/input contexts, requires lexical-token evidence for partial application matches, and makes an already-positive task-supervisor continuation contract authoritative for primary route selection. beta.180 relay standby/takeover behavior is unchanged.
|
|
27
|
+
- Beta.182 is the reviewed predecessor candidate that added repository-specific durable routing for known long prerelease commands, extracted reconnect result settlement into a focused policy module while retaining the then-existing per-tool settlement ceiling, and recorded the controlled relay A/B. Its packaged bytes remain associated with beta.182 and are not reused after the independent re-review changed shipped source.
|
|
28
|
+
- Beta.183 supersedes beta.182 after that re-review found package-affecting continuity, routing, privacy, and auditability defects. Repository-specific long workflows now request durable task supervision through project-provided registered-command metadata rather than hard-coded script names in generic routing; explicit read-only/negated/hypothetical/interactive/existing-job wording is kept out of new-job creation, and weak application-name matches require UI-operation intent. Worker reconnect settlement keeps execution/redelivery on the original deadline but grants the verified same-daemon terminal-result owner one fixed non-cumulative 15-second delivery-only extension beyond the original settlement deadline. Managed-job output redaction is byte-first and truncation-boundary safe; saturated retention tolerates a stale active-to-terminal dependency-plan deletion race without weakening genuine active-state fail-closed behavior; multiline static module edges are included in the architecture graph; and public worktree resolution no longer depends on maintainer-home tooling. The beta.182 controlled application-proxy A/B remains evidence only for the induced fault branch and does not identify the cause of spontaneous historical 1006 resets.
|
|
21
29
|
|
|
22
30
|
## Residual review requirements
|
|
23
31
|
|
|
24
32
|
A green fast or full suite is necessary but not sufficient security evidence for publication. Release acceptance still requires the package/install/security gates and any hosted or live boundary evidence required by the changed surface. This summary does not authorize deployment or npm publication.
|
|
25
33
|
|
|
26
|
-
|
|
34
|
+
Beta.181 is the current accepted/live baseline. Beta.182 remains the reviewed but superseded predecessor candidate. Beta.183 is the unaccepted replacement candidate and must pass frozen-tree verification plus exact-candidate owner-machine activation before acceptance. The controlled application-proxy A/B establishes only the induced fault branch; the spontaneous historical upstream reset source remains unassigned within the current privacy-bounded evidence. A future relay reset may still occur; continuity success means bounded warm HTTPS takeover plus durable task ownership, not an impossible no-disconnect guarantee. npm publication remains separately gated.
|
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
|
|
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 up to 24 hours 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 pending marker is cleared and the helper competes only for the bounded newest-16 transient delivery reserve during the ordinary 30-minute transient grace. 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
|
|
|
@@ -314,7 +314,7 @@ Never place a secret directly in `argv`, `env`, `stdin`, a temporary file's `con
|
|
|
314
314
|
|
|
315
315
|
Per-workspace jobs are stored below the owner-only profile directory. Active jobs retain an owner-only plan for crash recovery. Plan, status, result, runner identity, and lock updates use flushed atomic replacement or complete-before-visible exclusive claims. Transition/recovery locks contain ownership tokens and process start time and are removed only when their file snapshot still matches. After a terminal status is committed, the full plan is deleted, including argv, stdin, embedded temporary-file content, and resource source paths.
|
|
316
316
|
|
|
317
|
-
Retained public job data contains bounded status and redacted results. The hard capacity is 512 retained-state slots across ordinary job directories plus any recognized internal retired-cleanup entries that have not yet been safely removed; `list_jobs.jobs` still returns at most 50 primary records per call, while `recent_process_recovery` may add at most 16 authority-visible process recovery handles that were omitted from that primary window. Terminal jobs are normally retained for up to seven days from their persisted `finished_at` settlement time, but when capacity is full the oldest safely removable terminal records are evicted to reserve a slot for a new job. Staged drafts expire after 24 hours, dependency-referenced records are protected while an active/staged dependent plan still needs them, and active, staged, unreadable, or abnormal retired state is never evicted merely to make room. One-step process results add a private distinction that never appears in public projections: a terminal whose initiating hosted response still required `read_job` keeps follow-up recovery priority for the
|
|
317
|
+
Retained public job data contains bounded status and redacted results. The hard capacity is 512 retained-state slots across ordinary job directories plus any recognized internal retired-cleanup entries that have not yet been safely removed; `list_jobs.jobs` still returns at most 50 primary records per call, while `recent_process_recovery` may add at most 16 authority-visible process recovery handles that were omitted from that primary window. Terminal jobs are normally retained for up to seven days from their persisted `finished_at` settlement time, but when capacity is full the oldest safely removable terminal records are evicted to reserve a slot for a new job. Staged drafts expire after 24 hours, dependency-referenced records are protected while an active/staged dependent plan still needs them, and active, staged, unreadable, or abnormal retired state is never evicted merely to make room. Dependency protection tolerates one narrow transition race: if a scan observed a job as active/staged but its plan disappears because that same job has already committed a terminal status, the stale snapshot does not block eviction of unrelated safe terminal history; if the refreshed same-job status is still active/staged or cannot be read, pruning remains fail closed. One-step process results add a private distinction that never appears in public projections: a terminal whose initiating hosted response still required `read_job` keeps follow-up recovery priority for the 24-hour pending-result grace, while a terminal whose result has already been delivered competes only for the newest-16 delivery reserve during the ordinary 30-minute transient grace. The hard 512-state cap still wins: protected follow-up results may displace older ordinary durable terminal history, but cannot evict active/staged/unreadable/dependency-pinned state, and if all safely removable records are exhausted creation still returns retryable `limit_exceeded`. `list_jobs.retained` remains the number of visible ordinary jobs even when only 50 are returned. That bounded inventory is recovery-first: unreadable, active, and staged state stays first, durable terminal managed-job results precede ordinary transient one-step process terminals, while the separate 16-handle recovery projection prioritizes retained follow-up-required helpers. Owner/local responses additionally include the coarse capacity summary plus `durable_terminal` and `transient_terminal` counts so a full 512-state store can be distinguished from helper churn without exposing job identities, paths, arguments, output, or the private follow-up marker. Delegated non-owner responses omit the global capacity summary. These counts improve recovery visibility only; Machine Bridge still cannot observe whether an external host consumed a terminal result or rendered a final assistant response. Private runtime copies are removed after the finally phase. Runner stdout/stderr log files contain only runner-level diagnostics; step output is not written to those operational logs.
|
|
318
318
|
|
|
319
319
|
The detached runner records a structured owner record containing PID and process start time. Recovery rejects a reused PID instead of treating an unrelated process as the active runner. Numeric-only runner records are invalid. Initial runner publication uses provisional then committed atomic generations; a claim reader is explicitly coupled to that publication protocol and may therefore retry a transient `MBM_IDENTITY_CHANGED` observation for four 1 ms attempts before failing. Each retry repeats the complete secure read and identity validation; this exception does not apply to generic or destructive file reads. Recovery-lock handoff preserves a random ownership token, and the runner removes only a lock whose PID, token, and file snapshot still match. A recovery runner does not gain authority to write terminal evidence merely by confirming its runner claim: it must first complete recovery-lock handoff. Failure or ambiguity in that bootstrap phase leaves the prior `interrupted` status and plan intact for a later safe retry instead of publishing `recovery_failed` and scrubbing recovery material. The handoff has a 30-second monotonic ownership-settlement budget. Timeout and cancellation terminate the process group/tree, retain a referenced forced-escalation timer, and clean descendants that ignore graceful termination before the runner exits.
|
|
320
320
|
|
package/docs/OPERATIONS.md
CHANGED
|
@@ -97,10 +97,16 @@ A reconnect warning proves a transport interruption, not a daemon crash. Compare
|
|
|
97
97
|
|
|
98
98
|
Brief retryable outages recover automatically. On a verified current daemon channel, `server_info.daemon.relay_transport.outage_active=false`; retained fields describe the immediately preceding transport episode rather than claiming a current outage. WebSocket remains preferred and requests a protocol-level probe after five seconds. Calling `ws.ping()` only queues the control frame; it is not treated as remote-probe dispatch until the WebSocket sender's write callback confirms that the Ping actually left the local send queue. The local sender has a separate thirty-second bounded dispatch window, while a confirmed Ping retains its full ten-second Pong deadline. This deliberately prevents compression/backpressure or a slow local socket queue from spending the remote-response budget before any probe was transmitted. A protocol Pong that arrives while a Ping callback is still pending records bidirectional proof for that dispatch round; if the local write callback then completes inside the thirty-second dispatch budget, it does not arm a stale future Pong deadline. Unrelated application inbound is receive-side evidence only and cannot prove the daemon-to-Worker direction. A local queue whose Ping write callback still has not completed after thirty seconds is classified as `relay_transport_send_timeout` even if unrelated inbound traffic continues. One dispatched Ping that reaches the ten-second response deadline does not hard-kill an otherwise ready WSS: the relay runtime enters a fifteen-second `transport_confirmation_pending` window, sends the existing JSON application heartbeat as an independent path check, and prewarms signed HTTPS in standby without taking ownership away from the still-ready WSS. A protocol Pong or the explicit JSON application `pong` clears suspicion and stops standby prewarm; ordinary tool/control traffic does not, because it proves only the Worker-to-daemon receive direction. Only a second-stage confirmation window that receives no application `pong` becomes `relay_transport_timeout` and terminates the WSS. This keeps a true black hole bounded while no longer amplifying a roughly ten-to-fifteen-second persistent-flow stall into an immediate reconnect storm. `heartbeat.probe_dispatch_*`, `heartbeat.transport_confirmation_*`, and bounded sender-backlog fields distinguish local send delay, first-stage response loss, successful second-stage recovery, and confirmed two-stage failure. The separate periodic JSON application heartbeat remains twenty-five seconds with a seventy-five-second application-silence timeout, begins only after verified relay readiness, and the Worker keeps a wider ninety-second WebSocket liveness fallback. This is a detection/recovery bound, not a guarantee that a degraded network can complete another WebSocket handshake inside the same interval. WebSocket connect attempts have a thirty-second outer budget so a degraded but still valid DNS/TCP/TLS/WebSocket upgrade is not misclassified by an unrealistically narrow connection cutoff. The daemon also explicitly disables client `permessage-deflate`: the relay carries bounded control/JSON traffic, while `ws` enables compression by default on clients and compression adds sender-state/CPU overhead that can queue later frames; the stability path does not need that optional negotiation. The fallback still begins independently rather than waiting thirty seconds for WSS. On first-stage WSS liveness suspicion, the same root-certified ephemeral daemon identity prewarms signed HTTPS in standby; if WSS proves live during the second-stage confirmation, that standby poller stops. If the WSS actually disconnects, fallback switches to exact-generation takeover immediately; an in-flight standby request is aborted and replaced rather than being allowed to consume up to its own request deadline before takeover can start. That in-memory session certificate intentionally has a 24-hour maximum lifetime. For the default portable JWK root, the runtime derives a fresh root-certified ephemeral session ten minutes before expiry, swaps the shared WSS/HTTPS authentication provider, and reconnects the same daemon instance so existing reconnect reconciliation can resume in-flight relay calls without `runtime.stop()`. If the machine was suspended across the renewal timer, the next authentication boundary synchronously renews before signing instead of first attempting an expired certificate. `server_info.runtime.trust.daemon_session` exposes only coarse renewal state such as `automatic_renewal`, `session_generation`, `expires_at`, `renewal_due_at`, and failure class/count; it never exposes session keys or certificate material. A Secure Enclave root does not receive unattended renewal because signing may require user presence. If unattended renewal is unavailable or repeatedly fails until the session is truly expired, the existing fail-closed path still terminates with `relay_device_session_expired` rather than retrying forever with unusable credentials; installed launchd/systemd/Windows supervision may then restart the daemon and obtain a new root-signed session, while a manually run daemon requires operator restart. Ordinary standby/ready fallback requests retain the seven-second deadline; exact-generation takeover requests use a three-second deadline until verified readiness. The ordinary one-second ready poll cadence, five-second standby-prewarm cadence, bounded one/two/four/five-second retry backoff, 750 ms hard minimum request-start interval, and twelve-second liveness window are unchanged. A new daemon-backed call waits at most fifteen seconds for some verified daemon channel, and the measured wait is deducted from that call's original execution budget; the takeover budget is deliberately small enough for one fully timed-out stale request, the first retry backoff, and two further takeover exchanges to fit inside that ceiling. After an established WSS disappears, the daemon explicitly marks its signed HTTP request as a takeover of the Worker-issued `connection_id` for that exact disconnected WebSocket generation. Once candidate preconditions pass, HTTPS may retire only that targeted same-instance zombie WSS that the Worker has not yet observed closing. If a newer same-instance WSS is already ready before the HTTP request arrives, the old generation no longer matches and the stale takeover remains standby instead of retiring the recovered socket. A takeover request without the exact Worker-issued WebSocket connection ID is invalid rather than being treated as an instance-only legacy takeover. Malformed, stale, wrongly targeted, or different-instance requests cannot preempt a healthy incumbent. During replacement, the daemon reconciles `resume_calls`, processes `ready_ack`, proves local readiness, and only then returns `resume_calls_ack.missing_ids`. A missing ID therefore proves both that the same daemon has no active/unacknowledged-result ownership for that call and that the replacement channel is ready. If the initiating MCP response is still open and at least one second remains in the original execution budget, the Worker may transparently retransmit exactly that same call ID, arguments, authority, and a reduced timeout. `read_job` is stricter: redelivery requires the full ten-second reconciliation headroom to remain, otherwise the Worker declines redelivery and returns retryable recovery failure rather than rewriting the call into an under-budget immediate read. If safe redelivery cannot be accepted, the call falls back to retryable `unavailable` with `side_effects_started=false`. Calls that may have executed, retained terminal results, different-daemon calls, and ambiguous mutations are never automatically replayed. Completed relay results that are still waiting for Worker acknowledgement remain bounded in daemon memory and consume the same recovery-ownership capacity as active calls: 16 total with two control-plane slots reserved for `diagnose_runtime`/`list_roots`. When ordinary recovery ownership reaches 14, another ordinary relay call is rejected before execution with retryable `limit_exceeded` and `side_effects_started=false`; the two reserved diagnostic/recovery calls remain available until total capacity reaches 16. The retained-result implementation also keeps one non-admission emergency ownership slot solely for a violated internal capacity invariant: if an already-executed result reaches retention after the normal 16-entry ceiling is unexpectedly full, that one result remains retained for acknowledgement/reconnect ownership instead of being sent unowned and later misclassified as safe to redeliver. Use of that slot emits an error-level capacity event and may make diagnostics temporarily report ownership above the normal maximum; a second such overflow is not sent. This slot is not usable admission capacity and must never be counted to raise the 16-call execution ceiling. An acknowledgement that is permanently lost cannot pin a result forever: first retention is monotonic and the result expires after the 315-second maximum Worker settlement lifetime on the next live relay heartbeat; the disconnected path still uses the shorter reconnect-grace cleanup. `diagnose_runtime.runtime.relay_result_recovery` exposes only aggregate `active_calls`, `retained_results`, active ownership, and capacity counts—never call IDs, tool arguments, or results. Compare outage intervals with sleep/wake records and `diagnose_runtime.runtime.relay.heartbeat` before classifying them as active network faults; local stdio `server_info.runtime.relay.heartbeat` exposes the same state. On macOS, authorized relay activity is protected only after policy/account/operation authorization and argument validation succeed, and relay heartbeats do not count as user activity. The persisted idle-sleep mode is `activity` by default, `ac-continuous`, or `continuous`: `activity` retains the existing shared `/usr/bin/caffeinate -i -s -w <daemon-pid>` assertion plus fixed thirty-minute inactivity grace; `ac-continuous` adds a daemon-lifetime `-s -w <daemon-pid>` assertion while keeping that activity lease; `continuous` holds `-i -s -w <daemon-pid>` for the daemon lifetime and does not arm inactivity grace. A remote `start_process` extends activity ownership only after resource admission and through child settlement. Remote account managed-job runners independently hold the same `-i -s -w <runner-pid>` primitive after runner-claim/account-ownership validation and through admission, steps, cleanup, and terminal persistence. Every assertion retains desired-state recovery after unexpected child failure with fixed 1/5/30-second backoff; explicit release/shutdown disables recovery before terminating the child. `diagnose_runtime.runtime.idle_sleep_guard` reports only coarse mode/activity/recovery ownership and bounded unprotected-duration telemetry; it does not enumerate process-session or job identities. Runtime shutdown terminates process sessions before releasing daemon assertions. None of these modes claims to prevent explicit sleep, lid-close sleep, power loss, or operating-system behavior outside `caffeinate` contracts. A nonzero `event_loop_stall_count` with a large `max_event_loop_lag_ms` means the local daemon was not scheduled promptly; a short stall enters recovery grace, sends a fresh transport probe, and deliberately postpones disconnect. A large stall that aligns with `pmset` Sleep/Wake is suspension evidence. Independently, `relay_outage_analysis` can show either that a close-to-ready interval was itself dominated by system sleep or that the socket failure was first observable at the wake boundary while the same sleep matches the runtime pause; a genuinely awake outage without either evidence remains real transport evidence without identifying which network/host layer caused it. A large `previous_ready_inbound_silence_ms` without a matching local stall remains useful pre-close half-open evidence. Use `--verbose` only when close codes, liveness deadlines, and retry delays are required.
|
|
99
99
|
|
|
100
|
-
A foreground MCP response is not durable delivery. For an active hosted `start_job`, `ui_monitor_candidate=true` plus `ui_monitor_render_tool=render_job_monitor` and a fresh `ui_monitor_id` means the host advertised MCP Apps support, but `start_job` itself remains data-only and does not mount UI. **Job polling ownership is not task ownership.** If the current assistant still has task-local work that depends on this job's terminal result, do not render the monitor merely to shorten the response; preserve `job_id` + `recovery_key` and continue bounded server-paced `read_job` calls in the same response while calls are accepted. Render only when the current task can deliberately transfer this job's status polling without needing its terminal result for remaining same-response work. When intentionally rendering, preserve the start-result ID, call `render_job_monitor` once with the exact accepted `job_id` + `recovery_key` + `ui_monitor_id`; it verifies existing read authority, activates that pre-issued correlation ID, and mounts the Job Monitor resource. Do not depend on the render tool result being visible to the initiating planner. Immediately call `read_job` with the same `job_id`, `recovery_key`, and start-result `ui_monitor_id`. A matching current View claim on an ordinary job reports `ui_monitor_claimed=true`, `status_polling_mode=ui_monitor`, `host_turn_handoff_recommended=false`, and `follow_up_read_required=false`; that proves the View can own subsequent status polling for this job only. The sole exception is a persisted `continuation_mode=task_supervisor` whose reviewed durable plan already encapsulates all remaining noninteractive task work: a later active claimed read may report `host_turn_handoff_recommended=true`, allowing the host turn to end because the supervisor job owns the remaining execution lifecycle. Machine Bridge can prove the persisted declaration, active state, and current View claim, but not conversation-level completeness; that remains an owner/caller semantic assertion. The monitor itself remains status-only. The mounted View then uses app-only `read_job_monitor`, not the model-visible `read_job`, for its fixed 40-second status long-polls. That app-only call verifies the same principal-bound `recovery_key`, strips the recovery/monitor values before daemon dispatch, and uses a 60-second client response watchdog and may be retried by the View for at most six attempts with bounded 1/2/4/8/15/30-second backoff after a missing/transport-failed response or an explicitly retryable tool error; it never re-submits the managed job. Exhausted retries and non-retryable status errors pause the View for manual inspection. An older View claim with another monitor ID is not handoff evidence. Hosted synchronous calls reserve room for Worker and host settlement instead of occupying the complete interaction window: ordinary daemon-backed tools default to 20 seconds of remote execution plus a separate five-second Worker settlement margin; ordinary configurable browser/application foreground tools also default to 20 seconds, while compound `computer_observe` and `computer_act` default to 30 seconds; all configurable browser/application foreground tools retain their explicit 45-second maximum. Remote `exec_command`, `run_process`, and `run_local_command` no longer keep the child process inside that response lifetime. Each remote process request must carry a unique caller-held `idempotency_key` before dispatch; reuse that same key only when recovering an ambiguous acceptance response. The daemon commits the authorized operation as a principal-bound one-step managed job, launches it with interactive resource-admission priority, and returns a `job_id` inside a 10-second acceptance budget; the Worker keeps a separate five-second settlement margin and adds principal-bound `recovery_key`/`control_key` capabilities to the hosted result. Preserve all three together: hosted `read_job` requires the read capability, hosted `cancel_job` requires the control capability, and a bare `job_id` is not remote recovery authority. If that acceptance response is lost to settlement timeout, HTTP response cancellation, or relay reconnect expiry after dispatch, the public error remains non-retryable for generic callers but carries the original key and the explicit recovery action `retry_same_tool_arguments_with_same_idempotency_key`; this reconciles against the retained job instead of authorizing a blind duplicate. The detached child may execute for up to 600 seconds after admission, but the managed runner can separately wait up to thirty minutes for cooperative machine-user resource admission before the child is spawned; the child execution deadline begins only after that admission succeeds. The shared ceiling is exposed machine-readably as `server_info.tool_delivery.managed_job_resource_admission_wait_max_ms`, because the same pre-spawn boundary applies to ordinary durable process jobs and owner `start_job` steps rather than to process tools alone. While the runner is in this pre-spawn state, `read_job.current_phase` is `resource_admission`; no command has started yet. An owner can correlate a long-running status at that phase with `diagnose_runtime.runtime.resource_admission` rather than interpreting it as a slow child process; a delegated non-owner should treat the phase itself as evidence that the child has not spawned, retain the same `job_id`, and avoid blind replay rather than attempting the owner-only machine-wide diagnostic. After admission, the phase returns to `steps`, `finally_steps`, or `recovery-cleanup` as appropriate. Completed step records preserve `duration_ms` as the total orchestration duration. Local/owner reads additionally expose `resource_admission_ms` as the pre-spawn portion so a delayed successful child can be distinguished from slow execution after the fact; delegated non-owner reads omit that machine-user scheduling timing rather than turning shared-host contention into a more precise cross-workload signal. The detached job survives MCP disconnect, relay reconnect, daemon restart, or service replacement. Non-owner process authority is unchanged: automatic durable execution still uses the delegated workspace sandbox and does not grant owner-only `start_job`. If a cached host schema omits a current required field, the Worker rejects before daemon dispatch with a normal no-side-effect tool error and requests a `tools/list` refresh rather than surfacing a protocol-only validation failure. Discovery instructions and tool descriptions both carry orchestration semantics, so `server/discover` and `tools/list` each advertise `ttlMs=0` and every host-visible tool description carries `Tool schema generation N`. `server_info.tool_delivery.tool_schema_generation`, `tool_schema_server_version`, `discovery_ttl_ms`, and `tool_list_ttl_ms` identify the live contract; `host_visible_schema_known_to_server=false` is equally important because a healthy new daemon/Worker cannot prove that an external host discarded an older cached action/tool snapshot. `host_turn_deadline_observable=false` means Machine Bridge cannot pre-compute the external assistant-turn deadline, while `managed_jobs_detached_from_mcp_response=true` records that an accepted durable job is not owned by that response lifetime. After an activation that changes hosted semantics, compare the live `server_info` generation and changed invocation behavior with the governed Workspace Action control snapshot when that product layer is applicable; automation may perform the supported refresh/review path without another conversational approval. Host-internal cache inspection is intentionally excluded from operational release verification. `start_process` remains the explicit daemon-lifetime path when interactive stdin or session-style incremental output is required, but hosted calls use a 10-second execution / 15-second settlement envelope and do not queue behind resource pressure: the first failed admission returns retryable `unavailable`; owner-local callers retain the cooperative wait. Hosted `read_process` supports paced same-response follow-up: each actual output/exit blocking wait lasts at most one second. If another would-block remote read arrives inside the fifteen-second blocking cooldown, the daemon keeps that same MCP call open until output/exit or the cooldown boundary rather than returning an immediate running checkpoint; the Worker reserves enough execution/settlement headroom for that server-side pacing. Results use `status_polling_mode=paced_followup` while the process remains live, plus `blocking_poll_throttled` and `next_blocking_poll_after_ms`; callers must not busy-loop and should respect that cooldown. A new hosted call waits at most fifteen seconds for daemon readiness, but that wait is charged against the call's existing execution budget. For an already-dispatched call, a disconnect never pauses or extends the original execution/redelivery deadline. The Worker may separately retain the same in-memory result-settlement owner for up to fifteen seconds beyond its original settlement deadline, capped by the tool's existing maximum settlement lifetime, solely so an already-executed terminal result from the verified same daemon can still reach the original request. Repeated disconnect/rebind cycles cannot accumulate that delivery grace, cancellation still removes the owner, and the longer settlement deadline is never used to authorize another execution. Reconnect retention is bounded by reconnect grace and that fixed delivery deadline. Diagnostics distinguish `original call deadline expired during reconnect`, `terminal result delivery grace expired during reconnect`, and a true full `reconnect grace expired` rather than collapsing those separate boundaries. Owner-local stdio/CLI calls retain their synchronous local contract because they do not depend on a hosted response stream. Keep unrelated mutations and verification independently terminal, and never infer task success merely because a durable launch was accepted. For one coherent non-interactive sequence, prefer a repository umbrella command or multi-step `start_job` rather than creating many one-step durable process carriers. If the current task needs the result, hosted `read_job` may follow the known durable `job_id` with its preserved `recovery_key` repeatedly in the same assistant response until terminal state while calls continue to be accepted; active relay reads report `status_polling_mode=bounded_followup` and no longer recommend forced handoff. The normal hosted read is a 40-second server-side long-poll. Terminal settlement returns on the next bounded five-second internal poll; nonterminal status/phase/dependency progress is coalesced for at least 30 seconds by default, and `current_step`-only churn does not wake the hosted call. `wait_ms=0` is the explicit immediate-checkpoint mode, while public hosted `wait_ms` is capped at 60 seconds. The default stays at 40 seconds because live host evidence showed that overlong single requests can outlive the host invocation even though the durable job itself remains healthy; beta.151 reproduced that class with a second explicit 180-second `read_job` returning `mcp_network_error` while generation-18 continuity evidence recorded zero unplanned ready-socket disconnects. The coalescing floor reduces host-visible event density and does not shorten the managed job, the assistant task, or the six-hour managed-step ceiling. Do not busy-loop, do not replace server-side pacing with rapid immediate reads, do not use repeated `list_jobs`, `server_info`, or `diagnose_runtime` calls as substitute polling surfaces, and do not infer or preempt a host/tool deadline from elapsed wall-clock time. Hosted `list_jobs` is aggregate-only and intentionally cannot rediscover lost job IDs/names/recovery handles; detailed global inventory remains local CLI/stdio administration. Return the `job_id`, status, and current phase for later recovery only after an actual host/tool boundary is observed, external input or authorization is required, or the user explicitly requested a checkpoint; only a terminal status is task-completion evidence.
|
|
100
|
+
A foreground MCP response is not durable delivery. For an active hosted `start_job`, `ui_monitor_candidate=true` plus `ui_monitor_render_tool=render_job_monitor` and a fresh `ui_monitor_id` means the host advertised MCP Apps support, but `start_job` itself remains data-only and does not mount UI. **Job polling ownership is not task ownership.** If the current assistant still has task-local work that depends on this job's terminal result, do not render the monitor merely to shorten the response; preserve `job_id` + `recovery_key` and continue bounded server-paced `read_job` calls in the same response while calls are accepted. Render only when the current task can deliberately transfer this job's status polling without needing its terminal result for remaining same-response work. When intentionally rendering, preserve the start-result ID, call `render_job_monitor` once with the exact accepted `job_id` + `recovery_key` + `ui_monitor_id`; it verifies existing read authority, activates that pre-issued correlation ID, and mounts the Job Monitor resource. Do not depend on the render tool result being visible to the initiating planner. Immediately call `read_job` with the same `job_id`, `recovery_key`, and start-result `ui_monitor_id`. A matching current View claim on an ordinary job reports `ui_monitor_claimed=true`, `status_polling_mode=ui_monitor`, `host_turn_handoff_recommended=false`, and `follow_up_read_required=false`; that proves the View can own subsequent status polling for this job only. The sole exception is a persisted `continuation_mode=task_supervisor` whose reviewed durable plan already encapsulates all remaining noninteractive task work: a later active claimed read may report `host_turn_handoff_recommended=true`, allowing the host turn to end because the supervisor job owns the remaining execution lifecycle. Machine Bridge can prove the persisted declaration, active state, and current View claim, but not conversation-level completeness; that remains an owner/caller semantic assertion. The monitor itself remains status-only. The mounted View then uses app-only `read_job_monitor`, not the model-visible `read_job`, for its fixed 40-second status long-polls. That app-only call verifies the same principal-bound `recovery_key`, strips the recovery/monitor values before daemon dispatch, and uses a 60-second client response watchdog and may be retried by the View for at most six attempts with bounded 1/2/4/8/15/30-second backoff after a missing/transport-failed response or an explicitly retryable tool error; it never re-submits the managed job. Exhausted retries and non-retryable status errors pause the View for manual inspection. An older View claim with another monitor ID is not handoff evidence. Hosted synchronous calls reserve room for Worker and host settlement instead of occupying the complete interaction window: ordinary daemon-backed tools default to 20 seconds of remote execution plus a separate five-second Worker settlement margin; ordinary configurable browser/application foreground tools also default to 20 seconds, while compound `computer_observe` and `computer_act` default to 30 seconds; all configurable browser/application foreground tools retain their explicit 45-second maximum. Remote `exec_command`, `run_process`, and `run_local_command` no longer keep the child process inside that response lifetime. Each remote process request must carry a unique caller-held `idempotency_key` before dispatch; reuse that same key only when recovering an ambiguous acceptance response. The daemon commits the authorized operation as a principal-bound one-step managed job, launches it with interactive resource-admission priority, and returns a `job_id` inside a 10-second acceptance budget; the Worker keeps a separate five-second settlement margin and adds principal-bound `recovery_key`/`control_key` capabilities to the hosted result. Preserve all three together: hosted `read_job` requires the read capability, hosted `cancel_job` requires the control capability, and a bare `job_id` is not remote recovery authority. If that acceptance response is lost to settlement timeout, HTTP response cancellation, or relay reconnect expiry after dispatch, the public error remains non-retryable for generic callers but carries the original key and the explicit recovery action `retry_same_tool_arguments_with_same_idempotency_key`; this reconciles against the retained job instead of authorizing a blind duplicate. The detached child may execute for up to 600 seconds after admission, but the managed runner can separately wait up to thirty minutes for cooperative machine-user resource admission before the child is spawned; the child execution deadline begins only after that admission succeeds. The shared ceiling is exposed machine-readably as `server_info.tool_delivery.managed_job_resource_admission_wait_max_ms`, because the same pre-spawn boundary applies to ordinary durable process jobs and owner `start_job` steps rather than to process tools alone. While the runner is in this pre-spawn state, `read_job.current_phase` is `resource_admission`; no command has started yet. An owner can correlate a long-running status at that phase with `diagnose_runtime.runtime.resource_admission` rather than interpreting it as a slow child process; a delegated non-owner should treat the phase itself as evidence that the child has not spawned, retain the same `job_id`, and avoid blind replay rather than attempting the owner-only machine-wide diagnostic. After admission, the phase returns to `steps`, `finally_steps`, or `recovery-cleanup` as appropriate. Completed step records preserve `duration_ms` as the total orchestration duration. Local/owner reads additionally expose `resource_admission_ms` as the pre-spawn portion so a delayed successful child can be distinguished from slow execution after the fact; delegated non-owner reads omit that machine-user scheduling timing rather than turning shared-host contention into a more precise cross-workload signal. The detached job survives MCP disconnect, relay reconnect, daemon restart, or service replacement. Non-owner process authority is unchanged: automatic durable execution still uses the delegated workspace sandbox and does not grant owner-only `start_job`. If a cached host schema omits a current required field, the Worker rejects before daemon dispatch with a normal no-side-effect tool error and requests a `tools/list` refresh rather than surfacing a protocol-only validation failure. Discovery instructions and tool descriptions both carry orchestration semantics, so `server/discover` and `tools/list` each advertise `ttlMs=0` and every host-visible tool description carries `Tool schema generation N`. `server_info.tool_delivery.tool_schema_generation`, `tool_schema_server_version`, `discovery_ttl_ms`, and `tool_list_ttl_ms` identify the live contract; `host_visible_schema_known_to_server=false` is equally important because a healthy new daemon/Worker cannot prove that an external host discarded an older cached action/tool snapshot. `host_turn_deadline_observable=false` means Machine Bridge cannot pre-compute the external assistant-turn deadline, while `managed_jobs_detached_from_mcp_response=true` records that an accepted durable job is not owned by that response lifetime. After an activation that changes hosted semantics, compare the live `server_info` generation and changed invocation behavior with the governed Workspace Action control snapshot when that product layer is applicable; automation may perform the supported refresh/review path without another conversational approval. Host-internal cache inspection is intentionally excluded from operational release verification. `start_process` remains the explicit daemon-lifetime path when interactive stdin or session-style incremental output is required, but hosted calls use a 10-second execution / 15-second settlement envelope and do not queue behind resource pressure: the first failed admission returns retryable `unavailable`; owner-local callers retain the cooperative wait. Hosted `read_process` supports paced same-response follow-up: each actual output/exit blocking wait lasts at most one second. If another would-block remote read arrives inside the fifteen-second blocking cooldown, the daemon keeps that same MCP call open until output/exit or the cooldown boundary rather than returning an immediate running checkpoint; the Worker reserves enough execution/settlement headroom for that server-side pacing. Results use `status_polling_mode=paced_followup` while the process remains live, plus `blocking_poll_throttled` and `next_blocking_poll_after_ms`; callers must not busy-loop and should respect that cooldown. A new hosted call waits at most fifteen seconds for daemon readiness, but that wait is charged against the call's existing execution budget. For an already-dispatched call, a disconnect never pauses or extends the original execution/redelivery deadline. The Worker may separately retain the same in-memory result-settlement owner for one fixed fifteen-second window beyond its original settlement deadline, including when that original settlement deadline was already at the ordinary per-tool ceiling, solely so an already-executed terminal result from the verified same daemon can still reach the original request. Repeated disconnect/rebind cycles cannot accumulate that delivery grace, cancellation still removes the owner, and the longer settlement deadline is never used to authorize another execution. Reconnect retention is bounded by reconnect grace and that fixed delivery deadline. Diagnostics distinguish `original call deadline expired during reconnect`, `terminal result delivery grace expired during reconnect`, and a true full `reconnect grace expired` rather than collapsing those separate boundaries. Owner-local stdio/CLI calls retain their synchronous local contract because they do not depend on a hosted response stream. Keep unrelated mutations and verification independently terminal, and never infer task success merely because a durable launch was accepted. For one coherent non-interactive sequence, prefer a repository umbrella command or multi-step `start_job` rather than creating many one-step durable process carriers. If the current task needs the result, hosted `read_job` may follow the known durable `job_id` with its preserved `recovery_key` repeatedly in the same assistant response until terminal state while calls continue to be accepted; active relay reads report `status_polling_mode=bounded_followup` and no longer recommend forced handoff. The normal hosted read is a 40-second server-side long-poll. Terminal settlement returns on the next bounded five-second internal poll; nonterminal status/phase/dependency progress is coalesced for at least 30 seconds by default, and `current_step`-only churn does not wake the hosted call. `wait_ms=0` is the explicit immediate-checkpoint mode, while public hosted `wait_ms` is capped at 60 seconds. The default stays at 40 seconds because live host evidence showed that overlong single requests can outlive the host invocation even though the durable job itself remains healthy; beta.151 reproduced that class with a second explicit 180-second `read_job` returning `mcp_network_error` while generation-18 continuity evidence recorded zero unplanned ready-socket disconnects. The coalescing floor reduces host-visible event density and does not shorten the managed job, the assistant task, or the six-hour managed-step ceiling. Do not busy-loop, do not replace server-side pacing with rapid immediate reads, do not use repeated `list_jobs`, `server_info`, or `diagnose_runtime` calls as substitute polling surfaces, and do not infer or preempt a host/tool deadline from elapsed wall-clock time. Hosted `list_jobs` is aggregate-only and intentionally cannot rediscover lost job IDs/names/recovery handles; detailed global inventory remains local CLI/stdio administration. Return the `job_id`, status, and current phase for later recovery only after an actual host/tool boundary is observed, external input or authorization is required, or the user explicitly requested a checkpoint; only a terminal status is task-completion evidence.
|
|
101
101
|
|
|
102
102
|
When diagnosing the Job Monitor, keep coordination health separate from managed-job execution health. `read_job_monitor` is status-only: absence of command output or step records is intentional, not truncation. A monitor-storage failure after `start_job` acceptance must not be reported as job-acceptance failure; the result should simply omit UI-monitor candidacy and continue through model-visible `read_job`. Likewise, failure to read monitor claim metadata means the model has no proof of UI ownership and should keep normal continuation, while failure to delete monitor metadata during an authority revocation does not invalidate the revocation. The View never displays raw host/runtime exception text and does not accept ambient tool-result notifications as state. If the View pauses with a generic monitor error, recover from the same durable `job_id`/`recovery_key` on the normal read path rather than resubmitting the underlying job. Operational logs for monitor-coordination degradation contain only a bounded operation label plus sanitized error class, not job output, paths, principal IDs, monitor IDs, or capabilities.
|
|
103
103
|
|
|
104
|
+
A transient relay reconnect is not a durable workflow coordinator. The transport can recover a live socket and Machine Bridge can preserve already-accepted managed jobs and bounded same-daemon result ownership, but it cannot infer or launch later conversation steps that were never durably accepted. For one coherent non-interactive objective spanning several commands, repositories/projects, long validation, or explicit interruption recovery, submit the sequence as one repository umbrella command or multi-step managed job before the first side effect. Preserve the returned job ID and recovery capability; after reconnect, inspect/read that same job rather than resubmitting the underlying operation. This turns a relay outage into a temporary control/observation interruption instead of terminating the workflow. It does not claim that WebSocket, proxies, VPN/TUN routes, SSH egress, or the remote Worker are physically unbreakable.
|
|
105
|
+
|
|
106
|
+
Moving or archiving a deployed Machine Bridge worktree is a persisted-state migration, not a filesystem-only operation. Prepare and verify the canonical destination first, stop the machine service, then run `machine-mcp workspace migrate <old-path> <canonical-destination>`. The migration requires an inactive provider, no active managed jobs or state locks in the source profile, exact source path/hash evidence, and an absent or proven unpopulated destination profile (safely pruning empty destination shells created by read-only inspection); it moves the complete profile (including retained jobs and audit state), rewrites the state envelope, selects the destination, and retires only a matching stale machine-service owner (even if the historical worktree's entry script has already been removed from disk). Reinstall/start or activate the service on the destination and verify live ownership before archiving the old worktree. If the old path was already moved, recovery is allowed only when the retained source profile proves that exact historical path/hash. Do not recreate a removed pathname with a compatibility symlink. The beta.175 and beta.176 owner-machine activation failures exposed the two halves of this boundary: first stale global workspace selection, then the still-path-keyed deployed profile/service-owner identity. Targetless `machine-mcp service stop` is intentionally provider-global and does not resolve or load selected-workspace state; use an explicit stop target only when a verified workspace-daemon ownership check is desired. This keeps the offline migration reachable even when the historical selected path has already been moved and prevents provider shutdown from pre-creating the destination profile.
|
|
107
|
+
|
|
108
|
+
The September 10 beta.178 owner-machine recovery exposed a second lifecycle rule: do not implement migration retry/recovery as a recurring external launchd, cron, or scheduler job that begins by stopping the daemon. A recurring helper can turn any later preflight rejection into a self-rearming availability failure. Migration ownership stays inside `machine-mcp workspace migrate` and the normal service/activation lifecycle, or at most one bounded handoff that cannot re-register itself after failure. Even a proven-empty destination shell is not pruned until provider inactivity and source job/lock quiescence have been established; if it becomes populated before the empty-directory removal, migration fails closed. A populated destination profile is never an empty shell and is never merged or recursively cleared. The incident helper was a runaway recovery task; no malicious-intent claim is required for this control.
|
|
109
|
+
|
|
104
110
|
The daemon honors `HTTPS_PROXY`/`HTTP_PROXY` and `NO_PROXY` through standard environment-proxy resolution for remote Worker health and ordinary relay traffic. `wss:` targets use HTTPS proxy selection and `ws:` targets use HTTP proxy selection. `MBM_RELAY_PROXY` is a relay-only primary override for deployments that need a stable egress boundary: when it is non-empty, WebSocket relay construction and, by default, the signed HTTP fallback use that exact HTTP(S) proxy instead of consulting `HTTPS_PROXY`/`HTTP_PROXY` or `NO_PROXY`. `MBM_RELAY_FALLBACK_PROXY` is an optional fallback-only override. When it is non-empty, signed HTTP uses that exact proxy independently of `MBM_RELAY_PROXY`; when the key is present with an empty value, signed HTTP returns to standard `HTTPS_PROXY`/`HTTP_PROXY`/`NO_PROXY` resolution while WebSocket continues using its primary route. A connection failure through a selected route follows normal relay recovery and never silently retries the same transport through an unconfigured direct path. Setting `MBM_RELAY_PROXY` itself explicitly to an empty value continues to restore the standard environment-proxy model for the primary route.
|
|
105
111
|
|
|
106
112
|
To isolate relay continuity from an operating-system VPN/TUN, point `MBM_RELAY_PROXY` at a loopback-only sidecar and configure that sidecar to bind its upstream proxy socket to the intended physical/network interface. If continuity also requires an application-layer route that does not share that sidecar, configure `MBM_RELAY_FALLBACK_PROXY` separately; an empty fallback value is useful only when the standard system/env-proxy route is intentionally the desired alternate path. Machine Bridge deliberately does not implement proxy protocols beyond HTTP CONNECT and does not bind the sidecar's upstream socket itself. Therefore a remote proxy value can still be routed through an operating-system tunnel, and `system-network-stack` does not prove tunnel bypass. DNS for each explicit upstream proxy remains that proxy/sidecar's responsibility. Avoid a second system TUN solely for Machine Bridge, because competing default-route ownership recreates the same failure class at another layer.
|
package/docs/PRIVACY.md
CHANGED
|
@@ -95,7 +95,7 @@ Recovered prerelease activation is also a publication-sensitive evidence boundar
|
|
|
95
95
|
For an accidental publication, remove the value from the current tree and release artifacts, determine whether it is merely identifying metadata or an active credential, and rotate/revoke any credential immediately. Public Git and npm history are immutable in ordinary workflows: replacing the current file does not erase old commits or a published package. A coordinated history rewrite, cache invalidation request, or replacement release may be appropriate, but those actions are disruptive and require an explicit repository-owner decision.
|
|
96
96
|
## Current response delivery
|
|
97
97
|
|
|
98
|
-
MCP `2026-07-28` response streams are request-scoped, not persisted, and not resumable by the client. Closing a public response stream cancels that request and releases its transient Worker ownership. The private Durable Object cancellation control is bounded by a short internal settlement deadline so an unresponsive control fetch cannot keep the public response open indefinitely; that deadline carries only stream-control identity and does not add content logging or durable replay state. Because control and direct requests can arrive in either order, the Durable Object also retains a bounded short-lived in-memory cancellation tombstone when a private cancel arrives before direct dispatch ownership opens. The tombstone contains only the random stream-control key plus expiry bookkeeping; it contains no tool name, arguments, result, path, account credential, or user content. Capacity exhaustion fails closed for that bounded lifetime rather than evicting cancellation evidence and starting an orphaned side effect. The Worker's pending-call registry remains in memory and does not persist tool arguments or terminal results as protocol replay state. After a brief same-daemon relay interruption, that registry may retain the original settlement owner for
|
|
98
|
+
MCP `2026-07-28` response streams are request-scoped, not persisted, and not resumable by the client. Closing a public response stream cancels that request and releases its transient Worker ownership. The private Durable Object cancellation control is bounded by a short internal settlement deadline so an unresponsive control fetch cannot keep the public response open indefinitely; that deadline carries only stream-control identity and does not add content logging or durable replay state. Because control and direct requests can arrive in either order, the Durable Object also retains a bounded short-lived in-memory cancellation tombstone when a private cancel arrives before direct dispatch ownership opens. The tombstone contains only the random stream-control key plus expiry bookkeeping; it contains no tool name, arguments, result, path, account credential, or user content. Capacity exhaustion fails closed for that bounded lifetime rather than evicting cancellation evidence and starting an orphaned side effect. The Worker's pending-call registry remains in memory and does not persist tool arguments or terminal results as protocol replay state. After a brief same-daemon relay interruption, that registry may retain the original settlement owner for one fixed fifteen-second window beyond its original settlement deadline, including when that original deadline was already at the ordinary tool settlement ceiling, so an already-executed terminal result can still reach the initiating request. The extension stores no new content, cannot authorize another execution or redelivery, does not accumulate across handovers, and disappears on normal settlement, cancellation, reconnect expiry, or runtime loss.
|
|
99
99
|
|
|
100
100
|
`toolsListChanged` subscriptions have one additional server-side retention bound because public HTTP disconnect is not a reliable Workers/Durable Object lifecycle signal. Each subscription receives its acknowledgement and level-trigger list-change notification immediately and then remains live for at most the advertised 10-second lease unless explicit cancellation, authority revocation, or an observable request abort releases it sooner. Lease state contains only transient account-scoped stream ownership/capacity bookkeeping; it does not persist tool contents, request arguments, or client receipt evidence. The existing 8-per-account / 32-global active limits and bounded 64-account opened-history remain in force.
|
|
101
101
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "machine-bridge-mcp",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.185",
|
|
4
4
|
"description": "Cross-client MCP bridge for local agent context, structured browser and application automation, files, Git, processes, resources, and durable jobs over stdio or OAuth relay.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -128,6 +128,7 @@
|
|
|
128
128
|
"architecture:test": "node tests/architecture-test.mjs",
|
|
129
129
|
"markdown:test": "node tests/markdown-test.mjs",
|
|
130
130
|
"project-metadata:test": "node tests/project-metadata-test.mjs",
|
|
131
|
+
"worktree-resolver:test": "node tests/versioned-worktree-resolver-test.mjs",
|
|
131
132
|
"numbers:test": "node tests/numbers-test.mjs",
|
|
132
133
|
"records:test": "node tests/records-test.mjs",
|
|
133
134
|
"state-inventory:test": "node tests/state-inventory-test.mjs",
|
|
@@ -30,6 +30,8 @@ import { createHash } from "node:crypto";
|
|
|
30
30
|
* @property {string} cwd
|
|
31
31
|
* @property {number} timeoutSeconds
|
|
32
32
|
* @property {boolean} allowExtraArgs
|
|
33
|
+
* @property {"foreground" | "managed_job"} [executionMode]
|
|
34
|
+
* @property {number | null} [managedJobTimeoutSeconds]
|
|
33
35
|
* @property {string} source
|
|
34
36
|
* @property {string} [sourceType]
|
|
35
37
|
* @property {string} [script]
|
|
@@ -66,7 +68,8 @@ export function capabilityFingerprint(state, skills) {
|
|
|
66
68
|
commands: [...state.commands.values()]
|
|
67
69
|
.map((command) => [
|
|
68
70
|
command.name, command.description, command.argv, command.cwd, command.timeoutSeconds,
|
|
69
|
-
command.allowExtraArgs, command.
|
|
71
|
+
command.allowExtraArgs, command.executionMode || "foreground", command.managedJobTimeoutSeconds || 0,
|
|
72
|
+
command.source, command.sourceType || "", command.script || "",
|
|
70
73
|
])
|
|
71
74
|
.sort((left, right) => String(left[0]).localeCompare(String(right[0]))),
|
|
72
75
|
}));
|
|
@@ -130,6 +133,10 @@ export function publicCommands(commands, displayPath) {
|
|
|
130
133
|
cwd: displayPath(command.cwd),
|
|
131
134
|
timeout_seconds: command.timeoutSeconds,
|
|
132
135
|
allow_extra_args: command.allowExtraArgs,
|
|
136
|
+
execution_mode: command.executionMode || "foreground",
|
|
137
|
+
...((command.executionMode || "foreground") === "managed_job"
|
|
138
|
+
? { managed_job_timeout_seconds: command.managedJobTimeoutSeconds || 600 }
|
|
139
|
+
: {}),
|
|
133
140
|
source: displayPath(command.source),
|
|
134
141
|
source_type: command.sourceType || "agent-config",
|
|
135
142
|
...(command.script ? { package_script: command.script } : {}),
|
|
@@ -250,6 +250,8 @@ export class AgentContextManager {
|
|
|
250
250
|
argv: [...command.argv, ...extraArgs],
|
|
251
251
|
cwd: command.cwd,
|
|
252
252
|
timeoutSeconds: command.timeoutSeconds,
|
|
253
|
+
executionMode: command.executionMode,
|
|
254
|
+
managedJobTimeoutSeconds: command.managedJobTimeoutSeconds,
|
|
253
255
|
source: command.source,
|
|
254
256
|
};
|
|
255
257
|
}
|
|
@@ -363,6 +365,8 @@ export class AgentContextManager {
|
|
|
363
365
|
cwd,
|
|
364
366
|
timeoutSeconds: definition.timeoutSeconds,
|
|
365
367
|
allowExtraArgs: definition.allowExtraArgs,
|
|
368
|
+
executionMode: definition.executionMode,
|
|
369
|
+
managedJobTimeoutSeconds: definition.managedJobTimeoutSeconds,
|
|
366
370
|
source: configPath,
|
|
367
371
|
});
|
|
368
372
|
}
|
|
@@ -15,7 +15,10 @@ const CONFIG_KEYS = new Set([
|
|
|
15
15
|
"version", "builtin_instructions", "automatic_project_context", "model_instructions_file",
|
|
16
16
|
"instruction_files", "instruction_max_bytes", "skill_roots", "commands",
|
|
17
17
|
]);
|
|
18
|
-
const COMMAND_KEYS = new Set([
|
|
18
|
+
const COMMAND_KEYS = new Set([
|
|
19
|
+
"description", "argv", "cwd", "timeout_seconds", "allow_extra_args",
|
|
20
|
+
"execution_mode", "managed_job_timeout_seconds",
|
|
21
|
+
]);
|
|
19
22
|
|
|
20
23
|
/**
|
|
21
24
|
* @typedef {{
|
|
@@ -24,6 +27,8 @@ const COMMAND_KEYS = new Set(["description", "argv", "cwd", "timeout_seconds", "
|
|
|
24
27
|
* cwd: string,
|
|
25
28
|
* timeoutSeconds: number,
|
|
26
29
|
* allowExtraArgs: boolean,
|
|
30
|
+
* executionMode: "foreground" | "managed_job",
|
|
31
|
+
* managedJobTimeoutSeconds: number | null,
|
|
27
32
|
* }} NormalizedCommand
|
|
28
33
|
*/
|
|
29
34
|
|
|
@@ -188,12 +193,27 @@ function normalizeCommand(value, name, configPath) {
|
|
|
188
193
|
if (value.allow_extra_args !== undefined && typeof value.allow_extra_args !== "boolean") {
|
|
189
194
|
throw new Error(`registered command '${name}' allow_extra_args must be boolean: ${configPath}`);
|
|
190
195
|
}
|
|
196
|
+
const executionMode = value.execution_mode === undefined ? "foreground" : requiredString(value.execution_mode, `commands.${name}.execution_mode`);
|
|
197
|
+
if (executionMode !== "foreground" && executionMode !== "managed_job") {
|
|
198
|
+
throw new Error(`registered command '${name}' execution_mode must be foreground or managed_job: ${configPath}`);
|
|
199
|
+
}
|
|
200
|
+
if (value.managed_job_timeout_seconds !== undefined && executionMode !== "managed_job") {
|
|
201
|
+
throw new Error(`registered command '${name}' managed_job_timeout_seconds requires execution_mode=managed_job: ${configPath}`);
|
|
202
|
+
}
|
|
203
|
+
const managedJobTimeoutSeconds = executionMode === "managed_job"
|
|
204
|
+
? clampInteger(value.managed_job_timeout_seconds, 600, 1, 21_600)
|
|
205
|
+
: null;
|
|
206
|
+
if (value.managed_job_timeout_seconds !== undefined && managedJobTimeoutSeconds !== value.managed_job_timeout_seconds) {
|
|
207
|
+
throw new Error(`registered command '${name}' managed_job_timeout_seconds must be an integer from 1 to 21600: ${configPath}`);
|
|
208
|
+
}
|
|
191
209
|
return {
|
|
192
210
|
description,
|
|
193
211
|
argv,
|
|
194
212
|
cwd,
|
|
195
213
|
timeoutSeconds,
|
|
196
214
|
allowExtraArgs: value.allow_extra_args === true,
|
|
215
|
+
executionMode,
|
|
216
|
+
managedJobTimeoutSeconds,
|
|
197
217
|
};
|
|
198
218
|
}
|
|
199
219
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function applicationMatchScore(task, application) {
|
|
2
|
+
const text = String(task || "").toLowerCase();
|
|
3
|
+
const name = String(application?.name || "").toLowerCase();
|
|
4
|
+
const id = String(application?.id || "").toLowerCase();
|
|
5
|
+
if (!name) return 0;
|
|
6
|
+
if (text.includes(name)) return 10 + Math.min(name.length, 20);
|
|
7
|
+
if (!applicationOperationIntent(text)) return 0;
|
|
8
|
+
const taskTokens = new Set(text.split(/[^\p{L}\p{N}]+/u).filter((word) => word.length >= 2));
|
|
9
|
+
const words = name.split(/[^\p{L}\p{N}]+/u).filter((word) => word.length >= 2);
|
|
10
|
+
return words.reduce((score, word) => score + (taskTokens.has(word) ? 2 : 0), id && text.includes(id) ? 5 : 0);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function applicationOperationIntent(task) {
|
|
14
|
+
return /\b(?:open|launch|click|focus|activate|application|app|window|menu)\b|打开|启动|检查应用|操作应用|应用|软件|窗口|菜单/i.test(String(task || ""));
|
|
15
|
+
}
|
|
@@ -49,7 +49,8 @@ const ACCOUNT_POSITIONAL_LIMITS = new Map(Object.entries({ list: 1, clients: 1,
|
|
|
49
49
|
const ACTION_POSITIONAL_RULES = new Map(Object.entries({
|
|
50
50
|
workspace(args) {
|
|
51
51
|
const action = String(args._[0] || "show");
|
|
52
|
-
|
|
52
|
+
const max = action === "migrate" ? 3 : action === "set" || action === "select" ? 2 : 1;
|
|
53
|
+
return { max, tooMany: `workspace ${action} received too many positional arguments`, workspaceConflictAfter: action === "set" || action === "select" ? 1 : undefined };
|
|
53
54
|
},
|
|
54
55
|
"idle-sleep"(args) {
|
|
55
56
|
const action = String(args._[0] || "show");
|
|
@@ -139,7 +139,9 @@ async function serviceRestartAction({ args, context }) {
|
|
|
139
139
|
|
|
140
140
|
async function serviceStopAction({ args, stateRoot, service, context }) {
|
|
141
141
|
const logger = context.structuredLogger(Boolean(args.quiet));
|
|
142
|
-
const state =
|
|
142
|
+
const state = hasExplicitServiceTarget(args)
|
|
143
|
+
? optionalServiceState(args, stateRoot, context)
|
|
144
|
+
: null;
|
|
143
145
|
const status = await service.autostartStatus();
|
|
144
146
|
const before = state ? context.inspectWorkspaceDaemon(state) : null;
|
|
145
147
|
assertExplicitStopTarget(args, status, before);
|
package/src/local/cli.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import { createLocalAdminCommands } from "./cli-local-admin.mjs";
|
|
|
12
12
|
import { createServiceCommand } from "./cli-service.mjs";
|
|
13
13
|
import { createActivateCommand } from "./cli-activate.mjs";
|
|
14
14
|
import { idleSleepCommand } from "./cli-idle-sleep.mjs";
|
|
15
|
+
import { createWorkspaceCommand } from "./workspace-profile-migration.mjs";
|
|
15
16
|
import { generateAccountPassword } from "./account-admin.mjs";
|
|
16
17
|
import { accountAdminClient, createAccountCommand } from "./cli-account-admin.mjs";
|
|
17
18
|
export { resolvePolicy } from "./cli-policy.mjs";
|
|
@@ -43,14 +44,12 @@ import {
|
|
|
43
44
|
ensureWorkerSecrets,
|
|
44
45
|
ensureWorkspaceDirectory,
|
|
45
46
|
expandHome,
|
|
46
|
-
loadGlobalConfig,
|
|
47
47
|
loadState,
|
|
48
48
|
readDaemonLockOwner,
|
|
49
49
|
redactState,
|
|
50
50
|
removeStateRoot,
|
|
51
51
|
validateStateRootForRemoval,
|
|
52
52
|
resolveWorkspace,
|
|
53
|
-
saveGlobalConfig,
|
|
54
53
|
saveState,
|
|
55
54
|
selectedWorkspace,
|
|
56
55
|
setSelectedWorkspace,
|
|
@@ -67,6 +66,7 @@ const activateCommand = createActivateCommand({
|
|
|
67
66
|
assertNodeVersion,
|
|
68
67
|
structuredLogger,
|
|
69
68
|
});
|
|
69
|
+
const workspaceCommand = createWorkspaceCommand({ stateRootFromArgs, ask });
|
|
70
70
|
|
|
71
71
|
const COMMAND_HANDLERS = new Map([
|
|
72
72
|
["start", startCommand],
|
|
@@ -136,39 +136,6 @@ async function chooseWorkspace(args, { promptOnFirstRun, save, allowPositional =
|
|
|
136
136
|
return workspace;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
async function workspaceCommand(args) {
|
|
140
|
-
const action = String(args._[0] || "show");
|
|
141
|
-
const stateRoot = stateRootFromArgs(args);
|
|
142
|
-
if (action === "show") {
|
|
143
|
-
const workspace = selectedWorkspace(stateRoot);
|
|
144
|
-
console.log(workspace || "No workspace selected yet. Run `machine-mcp workspace set` or `mbm workspace set`.");
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
if (action === "set" || action === "select") {
|
|
148
|
-
const raw = args.workspace || args._[1];
|
|
149
|
-
let workspace;
|
|
150
|
-
if (raw && raw !== true) workspace = resolveWorkspace(String(raw));
|
|
151
|
-
else {
|
|
152
|
-
const current = selectedWorkspace(stateRoot) || process.cwd();
|
|
153
|
-
const answer = process.stdin.isTTY ? await ask(`Workspace path [${current}]: `) : current;
|
|
154
|
-
workspace = resolveWorkspace(String(answer || current));
|
|
155
|
-
}
|
|
156
|
-
setSelectedWorkspace(workspace, stateRoot);
|
|
157
|
-
console.log(`Selected workspace: ${workspace}`);
|
|
158
|
-
console.log("Run `machine-mcp` (or `mbm`) to use this workspace.");
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
if (action === "reset") {
|
|
162
|
-
const config = loadGlobalConfig(stateRoot);
|
|
163
|
-
delete config.selectedWorkspace;
|
|
164
|
-
delete config.selectedWorkspaceHash;
|
|
165
|
-
saveGlobalConfig(config, stateRoot);
|
|
166
|
-
console.log("Workspace selection reset. Next start will ask again.");
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
throw new Error(`Unknown workspace action: ${action}`);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
139
|
async function ask(prompt) {
|
|
173
140
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
174
141
|
try {
|
|
@@ -836,6 +803,7 @@ Commands:
|
|
|
836
803
|
client-config Print stdio client configuration snippets
|
|
837
804
|
workspace show Show remembered workspace
|
|
838
805
|
workspace set Re-select workspace; prompts with current/default path
|
|
806
|
+
workspace migrate OLD NEW Offline-migrate deployed profile/state identity before archiving or after an accidental move
|
|
839
807
|
idle-sleep show|set MODE Show/set activity, ac-continuous, or continuous; restart after set
|
|
840
808
|
service status Show autostart status
|
|
841
809
|
service install Install login autostart for remembered/current workspace
|
|
@@ -120,6 +120,8 @@ export class DaemonHttpRelayConnection {
|
|
|
120
120
|
authenticated: this.authenticated,
|
|
121
121
|
ready: this.ready,
|
|
122
122
|
closed: this.closed,
|
|
123
|
+
standby: this.standby === true,
|
|
124
|
+
takeover_pending: this.takeoverWebSocket === true && this.ready !== true,
|
|
123
125
|
network_route: this.networkRoute,
|
|
124
126
|
application_heartbeat_interval_ms: this.pollIntervalMs,
|
|
125
127
|
application_heartbeat_timeout_ms: this.livenessTimeoutMs,
|