machine-bridge-mcp 1.2.2 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.6 - 2026-07-17
4
+
5
+ ### Relay ready-context and implicit service-daemon takeover
6
+
7
+ - Keep end-to-end readiness fail-closed, but stop treating an incomplete inbound relay context that only carries `sessionId` as permanently unready. After a verified ready connection, the runtime consults live relay status when the per-message snapshot omits `ready`; an explicit `ready: false` snapshot still rejects tool calls. `RelayConnection` now always forwards boolean `authenticated` and `ready` with the session generation.
8
+ - Recognize managed service daemons started with only `--daemon-only` (no explicit `--workspace` / `--state-dir` on the process argv) when the lock owner already matches the active workspace state. Partial identity (one of the two path flags) remains rejected so foreign processes cannot be taken over. This allows recovery of source-tree recovery daemons that previously stayed orphaned across CLI upgrades.
9
+ - Add regression coverage for pre-ready vs ready inbound message contexts, sessionId-only dispatch after readiness, explicit `ready: false` fail-closed behavior, and implicit daemon-only stop/takeover.
10
+
11
+ ## 1.2.5 - 2026-07-17
12
+
13
+ ### End-to-end relay readiness and safe daemon handover
14
+
15
+ - Separate authenticated WebSocket transport from verified service readiness. A daemon now becomes externally usable only after the Worker sends a random readiness probe and receives its result through the same local message dispatch, relay-session binding, and `tool_result` delivery path used by real calls. `hello_ack` and heartbeats alone can no longer produce `daemon.connected=true` when result delivery is broken. The local runtime also rejects pre-ready tool calls and a premature `ready_ack` that was not preceded by a successfully delivered probe result.
16
+ - Keep the incumbent verified daemon active while a replacement is authenticated and probed. A failed, malformed, silent, or incompatible candidate is closed without displacing the working connection; only a successful `ready_ack` handover replaces the incumbent. Candidate hello, readiness, and steady-state liveness have independent bounded deadlines enforced by Durable Object alarms.
17
+ - Extract daemon attachment/state transitions into `DaemonSocketRegistry`, expose authenticated/probing/ready counts and readiness timestamps through `server_info`, and add fault-injection coverage for missing acknowledgements, invalid probe results, replacement races, reconnect backoff, session-generation propagation, and full OAuth/MCP/WebSocket routing.
18
+ - Correct capability ranking after the local research-skill rename: generic identity tokens such as `web`, `cli`, and `tool` no longer dominate selection, while concrete Chinese research/search intent maps to `research`, `search`, and `find`. This prevents a browser-form task from selecting a web-research skill without weakening explicit research requests.
19
+ - Remove a duplicate Worker route guard, require awaited alarm rescheduling after inbound activity, retain zero production dependency vulnerabilities, and refresh architecture, operations, logging, testing, upgrade, and audit documentation. State schema 6 and policy revision 5 remain unchanged.
20
+
21
+ ## 1.2.4 - 2026-07-17
22
+
23
+ ### Relay tool_result session context regression
24
+
25
+ - Pass the authenticated relay session generation into every inbound WebSocket `onMessage` callback. The 1.2.2 session-binding change discarded every `tool_result` with `session_ended` because handlers received `sessionId=0` even while heartbeats kept the socket live, so MCP tools timed out despite `daemon.connected=true`.
26
+ - Emit an explicit error when a tool result is discarded because the inbound call context lacked a session id, and include both expected and active session ids in the structured event.
27
+ - Add a regression that proves message dispatch attaches the current authenticated session generation.
28
+
29
+ ## 1.2.3 - 2026-07-17
30
+
31
+ ### Worker daemon false-online liveness
32
+
33
+ - Treat authenticated daemon sockets as live only when inbound traffic is recent. `role=daemon` plus `readyState=OPEN` is no longer enough after Durable Object hibernation or a half-closed transport, which previously left `daemon.connected=true` while every `tool_call` timed out.
34
+ - Persist `lastSeenAt` on daemon attachments, refresh it on `hello`, heartbeats, and `tool_result`, and reclaim silent sockets through the Durable Object alarm as well as on tool send failure or prolonged silence during a timed-out call.
35
+ - Report `daemon.last_seen_at`, `daemon.liveness_timeout_ms`, and `worker.sockets_live` from `server_info` so control-plane counters that reset on DO wake are not mistaken for live authenticated sockets.
36
+ - Add pure liveness helpers and regression coverage for fresh, silent, candidate, and legacy attachments without `lastSeenAt`.
37
+
3
38
  ## 1.2.2 - 2026-07-17
4
39
 
5
40
  ### Relay result lifecycle and human-readable diagnostics
@@ -318,7 +353,7 @@
318
353
 
319
354
  - Register bounded `package.*` commands from safe root `package.json` script names, while preserving explicit manifest override/deletion and never injecting script bodies. Windows uses a fixed `cmd.exe` wrapper for package-manager shims; Unix keeps direct executable argv. Extend default skill discovery to project `.codex/skills` and unrestricted `CODEX_HOME/skills` compatibility roots.
320
355
  - Match installed applications by their actual names for every canonical-full task instead of requiring generic “app/window” words, with a bounded discovery cache to avoid repeated filesystem scans.
321
- - Normalize a bounded set of common English inflections and Chinese workflow intents before skill/command ranking, and weight capability-name matches above incidental description overlap. This fixes Chinese selection of `skill-creator`, `smart-search-cli`, and `skill-installer` and prevents generic “create” wording from preferring unrelated design skills.
356
+ - Normalize a bounded set of common English inflections and Chinese workflow intents before skill/command ranking, and weight capability-name matches above incidental description overlap. This fixes Chinese selection of `skill-creator`, `web-research-cli`, and `skill-installer` and prevents generic “create” wording from preferring unrelated design skills.
322
357
  - Record privacy-preserving bootstrap and task-resolution telemetry in `server_info` and `project_overview`: counts, timestamps, source/load flags, selected capability metadata, and a runtime-keyed task fingerprint rather than raw task text. Suppress weak skill-overlap recommendations and clarify that the MCP host still controls whether the resolver and recommended tools are invoked.
323
358
 
324
359
  ### Process and network lifecycle
package/README.md CHANGED
@@ -49,6 +49,8 @@ Local clients such as Claude Desktop, Cursor, and Codex CLI
49
49
 
50
50
  The remote Worker authenticates and relays calls. It cannot directly read local files or start local processes. File, Git, image, patch, process, diagnostic, and managed-job operations execute in the local runtime.
51
51
 
52
+ Remote readiness is end-to-end: a daemon is advertised only after a Worker probe returns through the same local dispatch and session-bound result path used by real tools. A replacement is verified before it displaces the current healthy daemon.
53
+
52
54
  New users should follow the end-to-end [installation and first-use guide](docs/GETTING_STARTED.md). Before sharing a deployment or connecting several accounts, read [multi-client, multi-account, and tenancy architecture](docs/MULTI_ACCOUNT.md).
53
55
 
54
56
  ## Default behavior and policy profiles
package/SECURITY.md CHANGED
@@ -172,7 +172,7 @@ Public health and metadata do not expose live workspace or daemon status. The da
172
172
 
173
173
  ## Relay and denial of service
174
174
 
175
- Only one authenticated daemon is active. Candidates have a handshake deadline and cannot displace the current daemon before success. Pending calls are socket-bound, client-request-bound, concurrency-limited, size-limited, and timed out. Duplicate in-flight request IDs are rejected only within the same authenticated MCP session. Initialization returns a stateless HMAC-bound `MCP-Session-Id`; the signature binds the session nonce to the OAuth token identity, so one account can use multiple concurrent chat windows without sharing a cancellation or request-id namespace. Independent sessionless POST requests are not indexed by token and request id. A session id is an integrity/correlation token, not a second authorization credential.
175
+ Only one authenticated daemon is active. Candidates have a handshake deadline and cannot displace the current daemon before success. Authenticated sockets also carry a liveness deadline based on inbound traffic so half-open transports cannot remain the active daemon indefinitely. Pending calls are socket-bound, client-request-bound, concurrency-limited, size-limited, and timed out. Duplicate in-flight request IDs are rejected only within the same authenticated MCP session. Initialization returns a stateless HMAC-bound `MCP-Session-Id`; the signature binds the session nonce to the OAuth token identity, so one account can use multiple concurrent chat windows without sharing a cancellation or request-id namespace. Independent sessionless POST requests are not indexed by token and request id. A session id is an integrity/correlation token, not a second authorization credential.
176
176
 
177
177
  Request bodies, WebSocket messages, tool outputs, traversals, sessions, stdin writes, OAuth records, clients, codes, access tokens, refresh tokens, and failure identities are bounded. Disconnect/replacement terminates active child process trees.
178
178
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Machine Bridge Browser",
4
- "version": "1.2.2",
4
+ "version": "1.2.6",
5
5
  "description": "Connects the current Chromium browser profile to the local Machine Bridge runtime for user-authorized automation.",
6
6
  "permissions": [
7
7
  "tabs",
@@ -30,5 +30,5 @@
30
30
  "action": {
31
31
  "default_title": "Machine Bridge Browser"
32
32
  },
33
- "version_name": "1.2.2"
33
+ "version_name": "1.2.6"
34
34
  }
@@ -196,7 +196,7 @@ No persistent skill or project-context index is trusted as authoritative. `sessi
196
196
 
197
197
  `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 and command names/descriptions/argv, returns matches with scores, and loads the leading skill only when its relevance threshold is met. Under canonical `full`, it also compares the task with installed application names on every call; application discovery is cached briefly and refreshed after a bounded interval.
198
198
 
199
- 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`, `smart-search-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.
199
+ 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.
200
200
 
201
201
  This ranking is deterministic local assistance, not semantic certainty. Weak positive matches remain visible for diagnosis, but only a skill meeting the selection threshold is recommended for loading. The model must still evaluate whether the selected skill applies. Machine Bridge does not execute skill scripts implicitly and does not fabricate a dynamically named MCP tool per skill.
202
202
 
@@ -34,9 +34,9 @@ A canonical workspace receives an independent profile, Worker name, secret set,
34
34
  - `runtime-capabilities.mjs` composes agent, application, and browser capability results;
35
35
  - managed jobs, local resources, application automation, and browser automation remain separate managers.
36
36
 
37
- Architecture tests cap the orchestration module and each extracted service independently and reject a return of low-level process, patch, diagnostic, or capability-scoring logic to `LocalRuntime`. `RelayConnection` owns remote WebSocket transport, authenticated `hello_ack` readiness, heartbeat liveness, reconnect backoff, outage logging, and a monotonically increasing in-memory session generation. Every relayed tool result is bound to the generation that delivered its call; a disconnected generation cannot send a late result over its replacement socket. Stdio mode invokes `LocalRuntime` directly without that adapter.
37
+ Architecture tests cap the orchestration module and each extracted service independently and reject a return of low-level process, patch, diagnostic, or capability-scoring logic to `LocalRuntime`. `RelayConnection` owns remote WebSocket transport, `hello_ack` authentication, end-to-end `relay_probe`/`ready_ack` readiness, heartbeat liveness, reconnect backoff, outage logging, and a monotonically increasing in-memory session generation. Every relayed tool result is bound to the generation that delivered its call; a disconnected generation cannot send a late result over its replacement socket. Inbound WebSocket dispatch captures that generation at receive time and passes it to the runtime handler, so a successful local tool execution can publish only on the same generation. Stdio mode invokes `LocalRuntime` directly without that adapter.
38
38
 
39
- `daemon-process.mjs` owns workspace-daemon inspection and takeover. It distinguishes platform service state from the lock-owning Node process, validates PID and process-start identity, canonicalizes workspace/state paths before comparison, parses bounded process command lines without executing them, and sends `SIGTERM` only to a verified same-workspace `--daemon-only` process. POSIX daemons may ignore that signal and reach the bounded non-escalating timeout; Node's Windows signal mapping terminates the verified process directly. CLI orchestration never treats a missing launchd/systemd job as proof that the process exited.
39
+ `daemon-process.mjs` owns workspace-daemon inspection and takeover. It distinguishes platform service state from the lock-owning Node process, validates PID and process-start identity, canonicalizes workspace/state paths before comparison, parses bounded process command lines without executing them, accepts lock-backed `--daemon-only` recovery processes that omit repeated path flags, and sends `SIGTERM` only to a verified same-workspace service daemon. POSIX daemons may ignore that signal and reach the bounded non-escalating timeout; Node's Windows signal mapping terminates the verified process directly. CLI orchestration never treats a missing launchd/systemd job as proof that the process exited.
40
40
 
41
41
  ### Agent context and capability resolver
42
42
 
@@ -99,7 +99,7 @@ The stdio server implements newline-delimited JSON-RPC over stdin/stdout. It neg
99
99
  All requests for a deployed Worker route to one named Durable Object. It owns:
100
100
 
101
101
  - OAuth clients, authorization codes, hashed access-token records, an independently versioned hashed refresh-token store, and throttling metadata;
102
- - one active authenticated daemon WebSocket plus bounded candidate sockets;
102
+ - one active end-to-end-verified daemon WebSocket plus bounded candidate and probing sockets;
103
103
  - policy/tool metadata attached to the active socket;
104
104
  - a bounded in-memory map of pending daemon calls.
105
105
 
@@ -151,12 +151,13 @@ Remote OAuth binds each code, access token, and refresh token to a named Machine
151
151
  5. The Worker creates a five-minute code bound to client, redirect, resource, normalized scope, and PKCE challenge.
152
152
  6. A valid verifier exchanges the one-time code for an expiring access token and refresh token; only their hashes are stored. A refresh request is bound to the original public client, account, scope, resource, and deployment token version, and atomically replaces the refresh token so replay returns `invalid_grant`.
153
153
  7. The MCP client initializes against the sole current protocol version; an obsolete client must upgrade rather than enter a legacy execution path. The Worker returns a stateless HMAC-bound `MCP-Session-Id`, and later request/cancellation correlation is scoped by OAuth token, MCP session, JSON-RPC id type, and id value. Two clients may therefore reuse the same JSON-RPC id concurrently without collision. Sessionless POSTs remain independent and are not inserted into a token-global cancellation index. When the daemon advertises `session_bootstrap`, the Worker requests bounded local instructions and appends them to the initialization result; failure degrades to static instructions.
154
- 8. `tools/list` is derived from the active daemon handshake; without a daemon, only `server_info` is advertised.
155
- 9. `tools/call` receives a random relay call ID and is bound to the current daemon socket, authenticated client request key, incoming HTTP abort signal, and local relay-session generation.
156
- 10. The runtime validates policy and arguments, executes the tool, and returns a bounded result.
157
- 11. The Durable Object accepts a result only from the daemon socket that received that call, and the local runtime sends it only through the relay generation that delivered the call.
158
- 12. A matching cancellation notification or incoming HTTP client disconnect removes the pending indexes, tells the daemon to cancel, and terminates ordinary child processes bound to the call. If local completion races with cancellation, the eventual result is discarded rather than treated as a user-visible delivery failure.
159
- 13. `start_job` is different: after durable acceptance, the detached runner is no longer bound to the relay call or socket. Later cancellation uses `cancel_job` or the local CLI.
154
+ 8. A new daemon first authenticates as a bounded `probing` socket. The Worker sends a random `relay_probe`; the local runtime returns it through the normal session-bound result-delivery path; only the matching result produces `ready_ack`, promotion to the active daemon, and safe replacement of an incumbent connection.
155
+ 9. `tools/list` is derived only from the active end-to-end-verified daemon; without one, only `server_info` is advertised.
156
+ 10. `tools/call` receives a random relay call ID and is bound to the current daemon socket, authenticated client request key, incoming HTTP abort signal, and local relay-session generation.
157
+ 11. The runtime validates policy and arguments, executes the tool, and returns a bounded result.
158
+ 12. The Durable Object accepts a result only from the daemon socket that received that call, and the local runtime sends it only through the relay generation that delivered its call.
159
+ 13. A matching cancellation notification or incoming HTTP client disconnect removes the pending indexes, tells the daemon to cancel, and terminates ordinary child processes bound to the call. If local completion races with cancellation, the eventual result is discarded rather than treated as a user-visible delivery failure.
160
+ 14. `start_job` is different: after durable acceptance, the detached runner is no longer bound to the relay call or socket. Later cancellation uses `cancel_job` or the local CLI.
160
161
 
161
162
  Duplicate in-flight JSON-RPC IDs are rejected only within the same authenticated MCP session. The request key includes OAuth token identity, the HMAC-bound MCP session, JSON-RPC id type, and id value, so separate initialized clients may safely reuse the same numeric id.
162
163
 
@@ -220,13 +221,13 @@ Worker-name mutation is a separate identity transition. Existing state rejects a
220
221
 
221
222
  ## Daemon reconnect and replacement
222
223
 
223
- The local `RelayConnection` treats proxy selection, transport construction, transport open, authenticated readiness, and outage recovery as separate states. The shared proxy module maps WebSocket targets to standard HTTP(S) environment-proxy resolution, honors `NO_PROXY`, rejects non-HTTP(S) proxy schemes, and creates the proxy agent without exposing its URL or credentials. Invalid proxy configuration is a fatal configuration error rather than a retryable outage.
224
+ The local `RelayConnection` treats proxy selection, transport construction, WebSocket open, authentication, end-to-end readiness, and outage recovery as separate states. The shared proxy module maps WebSocket targets to standard HTTP(S) environment-proxy resolution, honors `NO_PROXY`, rejects non-HTTP(S) proxy schemes, and creates the proxy agent without exposing its URL or credentials. Invalid proxy configuration is a fatal configuration error rather than a retryable outage.
224
225
 
225
- The local `RelayConnection` otherwise treats transport construction, transport open, authenticated readiness, and outage recovery as separate states. A connection-attempt deadline terminates sockets stuck in `CONNECTING`; after WebSocket open it sends `hello` and reports readiness only after `hello_ack`. A missing acknowledgement reaches a distinct handshake deadline and the candidate is terminated. Once ready, application heartbeats require inbound activity; a silent half-open socket is terminated and reconnected. Outage reminders run on their own exponential-backoff timer rather than depending on another transport callback.
226
+ A connection-attempt deadline terminates sockets stuck in `CONNECTING`. After open, the daemon sends `hello`; `hello_ack` establishes an authenticated relay generation and starts heartbeats, but does not resolve startup or advertise readiness. The Worker then sends a random `relay_probe`. Its result must traverse the local runtime dispatcher and `sendForSession` on that generation before `ready_ack` marks the connection usable. The daemon rejects ordinary tool calls before that state and rejects a premature readiness acknowledgement without locally recorded probe delivery. Independent handshake and readiness deadlines terminate candidates that authenticate but cannot return results. Once ready, application heartbeats require inbound activity; a silent half-open socket is terminated and reconnected. Outage reminders run on their own exponential-backoff timer rather than depending on another transport callback.
226
227
 
227
228
  Reconnect uses bounded exponential backoff with jitter. Brief self-healing interruptions are debug-only. An unresolved outage is promoted to a rate-limited warning after a grace period, and recovery produces one summary. Raw close codes and reason strings remain debug-only.
228
229
 
229
- The Worker also treats a new connection as a bounded candidate until it authenticates and completes `hello`; a Durable Object alarm enforces the deadline across hibernation. Only a completed candidate replaces the old socket. Daemon messages must be valid JSON objects, duplicate `hello` and unknown message types are rejected with explicit protocol errors, and protocol violations close with standard WebSocket status codes. Pending calls retain their originating socket reference. A stale socket cannot complete or cancel replacement-socket calls. Closing a socket rejects only calls assigned to it. The local relay generation supplies the symmetric boundary: work accepted by an older authenticated connection cannot publish its result through a newer connection even when the call IDs are otherwise well formed.
230
+ The Worker stores socket transitions in `DaemonSocketRegistry`: `candidate` before hello, `probing` after authentication, `daemon` only after the end-to-end result probe, and `expired` after terminal failure. Durable Object alarms enforce separate hello, readiness, and steady-state liveness deadlines across hibernation. A healthy incumbent remains active while a replacement is probed; a malformed, silent, or incompatible replacement is closed without displacing it. Only a verified candidate receives `ready_ack` and then replaces the old socket. Ready daemons stay live only while inbound traffic refreshes `lastSeenAt`; silent half-open or hibernation-restored sockets are reclaimed instead of advertising `daemon.connected` while tool calls time out. Pending calls retain their originating socket reference. A stale socket cannot complete or cancel replacement-socket calls. Closing a socket rejects only calls assigned to it. The local relay generation supplies the symmetric boundary: work accepted by an older authenticated connection cannot publish its result through a newer connection even when the call IDs are otherwise well formed.
230
231
 
231
232
  ## Persistence
232
233
 
@@ -246,7 +247,7 @@ Browser-origin handling separates CORS response sharing from protocol authentica
246
247
 
247
248
  ## Observability
248
249
 
249
- Public health exposes only server identity and version. Authenticated `server_info` exposes bounded runtime status, managed-job counts, resource alias names without paths or values, relay route state without endpoint details, and privacy-preserving capability-routing evidence. It separates the daemon capability ceiling from the authenticated account authority: `daemon.policy`/`daemon.tools` retain the pre-role ceiling, while `authorization.effective_policy`/`authorization.effective_tools` and the top-level `tools` report the role-intersected authority before any host-side filtering. It explicitly reports that the host-exposed subset is unknown to the server. `diagnose_runtime` runs fixed local probes and explicitly reports that its own request reached the daemon.
250
+ Public health exposes only server identity and version. Authenticated `server_info` exposes bounded runtime status, managed-job counts, resource alias names without paths or values, relay route state without endpoint details, authenticated/probing/ready socket counts, end-to-end readiness evidence, and privacy-preserving capability-routing evidence. It separates the daemon capability ceiling from the authenticated account authority: `daemon.policy`/`daemon.tools` retain the pre-role ceiling, while `authorization.effective_policy`/`authorization.effective_tools` and the top-level `tools` report the role-intersected authority before any host-side filtering. It explicitly reports that the host-exposed subset is unknown to the server. `diagnose_runtime` runs fixed local probes and explicitly reports that its own request reached the daemon.
250
251
 
251
252
  Foreground logging defaults to `info`; autostart uses `warn`. Authenticated readiness, persistent degradation, and recovery are user-visible state transitions. Brief relay interruptions, raw transport close details, retry timing, and all per-tool starts/successes/failures/cancellations/durations are debug-only. Unexpected local and Worker infrastructure errors are reduced to classes. Messages, strings, arrays, object depth/key counts, and serialized fields are bounded.
252
253
 
package/docs/AUDIT.md CHANGED
@@ -1,5 +1,27 @@
1
+ # Security and privacy audit notes
2
+
3
+ ## 2026-07-17 version 1.2.6 relay ready-context and daemon-takeover audit
4
+
5
+ Version 1.2.5 introduced fail-closed rejection of ordinary tool calls before end-to-end readiness. That boundary is correct, but an incomplete inbound dispatch object that carried only `sessionId` (the 1.2.4 session-binding shape) evaluated as not ready and converted the first real tool call into a permanent local protocol error. Version 1.2.6 keeps the explicit `ready: false` fail-closed path, requires RelayConnection to forward boolean `authenticated`/`ready`, and allows a live ready relay status to satisfy readiness only when the snapshot omitted the field.
6
+
7
+ Service-daemon takeover previously required both `--workspace` and `--state-dir` on the live process command line. Recovery daemons started with only `--daemon-only` from a source tree therefore remained unverified and blocked upgrade takeover. Identity verification now accepts that implicit form when the lock owner already matches the active workspace and state root, while partial path identity remains rejected.
8
+
9
+ No schema, policy revision, credential, or browser-extension protocol change is included. This source change does not deploy the Worker, replace a running daemon, rotate secrets, publish npm, or mutate live operator state.
10
+
1
11
  # Engineering and security audit
2
12
 
13
+ ## 2026-07-17 version 1.2.5 end-to-end relay-readiness audit
14
+
15
+ The incident sequence disproved the previous meaning of “connected.” Version 1.2.3 correctly stopped treating every hibernation-restored `OPEN` WebSocket as live, and version 1.2.4 restored the missing local relay-session generation. Those changes repaired two concrete defects, but the startup contract still established readiness from `hello_ack` and heartbeat traffic. Both signals exercise transport authentication and one-way control messages; neither proves that an inbound request can traverse the local dispatcher and return on the same authenticated relay generation. A regression in that return path could therefore keep heartbeats healthy while every user tool timed out.
16
+
17
+ Version 1.2.5 replaces that proxy signal with direct evidence. After `hello`, the Worker stores the socket as `probing`, returns `hello_ack`, and sends a random `relay_probe`. The local runtime validates the probe and returns `relay_probe_result` through `deliverRelayToolResult` and `sendForSession`, using the exact session-generation boundary that protects real tool results. Only then does the Worker promote the socket to `daemon`, send `ready_ack`, advertise tools, and allow the local startup promise to resolve. The local side independently records successful probe-result delivery; it rejects `ready_ack` before that evidence and rejects any ordinary `tool_call` while the relay is authenticated but not ready. The probe identifier is random, bounded, short-lived attachment metadata; it contains no tool arguments, paths, commands, output, account data, or credentials.
18
+
19
+ Replacement is now fail-safe rather than eager. The current verified daemon remains authoritative while a candidate authenticates and completes the probe. Malformed or mismatched results close the candidate with a protocol error; missing results reach an independent readiness deadline; neither path rejects calls assigned to the incumbent or changes its advertised policy. Only a verified candidate closes the old socket. Durable Object alarms independently enforce candidate hello, probing readiness, and steady-state liveness deadlines across hibernation. `server_info` distinguishes current authenticated, probing, and ready socket counts and reports `readiness_verified`; control-plane consumers no longer need to infer service health from cumulative socket counters.
20
+
21
+ The review also found a lower-severity routing defect in the user's pending skill rename. The ranking algorithm gave short generic identity words such as `web` the same strong weight as a distinctive capability name, so unrelated browser work could cross the selection threshold. Removing only that weight exposed a hidden dependence on the same generic token for Chinese research requests. The correction suppresses strong identity credit for generic words and adds an explicit Chinese research/search semantic mapping. Positive and negative regressions now encode both sides of the decision boundary.
22
+
23
+ Architecture review extracted daemon attachment mutation from the Worker composition root into `DaemonSocketRegistry`, added an independent line cap and responsibility assertions, removed a duplicate HTTP method guard, and made inbound liveness updates await alarm rescheduling rather than leaving an unobserved promise. Fault injection covers no-ready-ack reconnect, invalid replacement probes, incumbent preservation, verified handover, session-bound probe return, and current observability. The complete package gate, Worker dry run, dependency/signature/privacy checks, and cross-platform CI remain the release boundary. This source change does not deploy the Worker, replace the running daemon, rotate credentials, publish npm, create a tag, or create a GitHub Release; a live installation remains on its old behavior until Worker, daemon, and extension converge on 1.2.5.
24
+
3
25
  ## 2026-07-17 version 1.2.2 relay-lifecycle and logging audit
4
26
 
5
27
  The reported warning was the visible symptom of a call-lifecycle mismatch rather than a failed local tool. The Worker could remove a pending call after MCP cancellation, timeout, daemon disconnect, or client abandonment, while the local runtime still completed its asynchronous operation and entered one unconditional result-delivery path. When the original WebSocket had closed, the daemon emitted one `relay.tool_result.delivery_failed` warning per late completion. More seriously, if a replacement relay had already authenticated, the old result could be written to the new socket; the Worker rejected it because its pending record was socket-bound, but the local transport did not enforce the symmetric session boundary.
@@ -53,7 +53,7 @@ Rules:
53
53
  - Pure classification and normalization functions are exported and tested directly when practical.
54
54
  - Adapters may translate data but should not duplicate policy or schemas.
55
55
  - Every protocol control message emitted by one side must be explicitly accepted, rejected, or version-gated by the other side, with an end-to-end contract test covering the message name and semantics.
56
- - State transitions are explicit; readiness is not inferred from a lower-level event. For example, an open WebSocket is not an authenticated relay until `hello_ack` is received.
56
+ - State transitions are explicit; readiness is not inferred from a lower-level event. An open WebSocket is not authenticated until `hello_ack`, and authenticated transport is not service readiness until an end-to-end result probe has returned on the same relay session. Pre-ready work and premature readiness acknowledgements fail closed.
57
57
  - Every externally controlled input is bounded before expensive allocation, traversal, parsing, storage, or execution.
58
58
  - Externally controlled string keys must not use prototype-chain membership or truthiness on ordinary objects. Use `Map`, `Set`, `Object.hasOwn`, or null-prototype records for command dispatch, enums, ACLs, form fields, registries, and other key-addressed contracts.
59
59
  - Repository text must not contain invisible ASCII controls other than tab, CR, and LF; architecture tests enforce this even when JavaScript syntax remains valid.
package/docs/LOGGING.md CHANGED
@@ -45,7 +45,7 @@ Autostart installation and daemon startup may report the names of allowlisted pr
45
45
 
46
46
  ## Relay connection event policy
47
47
 
48
- A TCP/WebSocket `open` event is only transport availability. The daemon is reported as connected only after the Worker returns `hello_ack` for the authenticated `hello` message.
48
+ A TCP/WebSocket `open` event is only transport availability, and `hello_ack` proves only authenticated bidirectional control traffic. The daemon is reported as ready only after a random Worker probe returns through the ordinary local dispatcher and session-bound result path and the Worker sends `ready_ack`.
49
49
 
50
50
  Brief network interruptions are expected on laptop network changes, Worker deployment, proxy rotation, and ordinary internet transport. They are handled as follows:
51
51
 
@@ -57,10 +57,10 @@ Brief network interruptions are expected on laptop network changes, Worker deplo
57
57
  - while the outage remains unresolved, reminders are scheduled independently of reconnect callbacks and use exponential backoff capped at 15 minutes;
58
58
  - a WebSocket that remains in `CONNECTING` beyond its deadline is terminated so one stalled network attempt cannot freeze the reconnect loop;
59
59
  - relay identity/version mismatch, authentication rejection, and unexpected protocol messages are non-transient: they produce an immediate actionable error and terminate that daemon instead of entering the reconnect loop;
60
- - a Worker `daemon_hello_timeout` error is transient and follows the normal reconnect path rather than being misclassified as authentication rejection;
60
+ - Worker `daemon_hello_timeout` and `daemon_ready_timeout` errors are transient and follow the normal reconnect path rather than being misclassified as authentication rejection;
61
61
  - recovery after a visible outage produces one information summary with a human-readable duration and attempt count; exact seconds and error classes remain debug-only;
62
- - an authenticated replacement is a distinct warning and permanently stops the older daemon;
63
- - failure to receive `hello_ack` within the handshake deadline terminates the candidate socket and retries;
62
+ - a verified replacement is a distinct warning and permanently stops the older daemon;
63
+ - failure to receive `hello_ack` within the handshake deadline, or `ready_ack` within the independent end-to-end readiness deadline, terminates the candidate socket and retries;
64
64
  - lack of inbound heartbeat activity terminates a half-open socket and reconnects.
65
65
 
66
66
  A WebSocket close code such as `1006` means the transport ended without a normal close handshake. It is useful for debug diagnosis but not useful as the default user message. Default logs therefore describe the effect and recovery behavior rather than printing `{"code":1006,"reason":""}`.
@@ -68,7 +68,7 @@ A WebSocket close code such as `1006` means the transport ended without a normal
68
68
  Examples:
69
69
 
70
70
  ```text
71
- [info] daemon: remote relay connected
71
+ [info] daemon: remote relay connected and end-to-end result delivery verified
72
72
  [warn] daemon: remote relay unavailable for 12 seconds; reconnecting automatically (3 reconnect attempts; connection interrupted).
73
73
  [info] daemon: remote relay connection restored after 18 seconds (4 reconnect attempts)
74
74
  ```
@@ -117,7 +117,7 @@ This is defense in depth, not content classification. Unknown, split, transforme
117
117
 
118
118
  The local execution middleware emits bounded events such as `tool.call.started`, `tool.call.completed`, `tool.call.failed`, `tool.call.slow`, and `tool.call.cancel_requested`. Stable fields include a shortened call ID, tool name, origin, duration, error code, and retryability. The Worker emits JSON events for HTTP failures and daemon socket errors. Structured values still pass through field-name and value redaction; JSON format is not permission to log arguments or results.
119
119
 
120
- `server_info` is the operational metrics surface. Local metrics include lifecycle state, active and maximum calls, oldest-call age, active-process ownership, per-tool duration buckets, and error-code counts. Worker metrics include HTTP status classes, pending internal/request-key indexes, per-tool outcomes, daemon candidate/authenticated/disconnected counts, and protocol-error counts. Metrics contain counts and bounded identifiers, not request arguments or result contents.
120
+ `server_info` is the operational metrics surface. Local metrics include lifecycle state, active and maximum calls, oldest-call age, active-process ownership, per-tool duration buckets, and error-code counts. Worker metrics include HTTP status classes, pending internal/request-key indexes, per-tool outcomes, daemon candidate/authenticated/ready/disconnected event counts, current authenticated/probing/ready socket counts, and protocol-error counts. Metrics contain counts and bounded identifiers, not request arguments or result contents.
121
121
 
122
122
  ## Files
123
123
 
@@ -34,6 +34,7 @@ Run these commands from the same environment used for startup so `HTTPS_PROXY`,
34
34
  |---|---|
35
35
  | `authorization.effective_policy.profile` is `full` and the tool is in `authorization.effective_tools`, but the current session UI exposes fewer tools | Host/connector post-relay filtering; Machine Bridge cannot enumerate or override that subset |
36
36
  | `daemon.policy.profile` is `full` but `authorization.effective_policy.profile` is `review`, `edit`, or `agent` | Expected account-role narrowing; the daemon field is only a capability ceiling and must not be reported as the account permission |
37
+ | `worker.sockets_live.authenticated` is nonzero but `worker.sockets_live.ready` is zero | Transport authentication exists, but the end-to-end result probe has not completed; no daemon tools are advertised and the candidate will be closed at the readiness deadline |
37
38
  | `capability_routing.bootstrap_observed` is false | The current local runtime has not received `session_bootstrap`; reconnect or inspect host initialization handling |
38
39
  | `task_resolution_observed` is false after a substantive task | The host/model did not call `resolve_task_capabilities`; server-side discovery cannot force that host decision |
39
40
  | Task resolution ran but all match counts are zero | The resolver ran successfully but found no sufficiently relevant local skill, command, or application |
@@ -50,13 +51,13 @@ A successful diagnostic result applies only to that probe. An MCP host can still
50
51
 
51
52
  Machine Bridge supports concurrent calls: the Worker admits up to 32 pending daemon calls and the local runtime admits up to 16 active tool calls. These are capacity limits, not a single global execution queue. Each successful MCP initialization receives a signed `MCP-Session-Id`; JSON-RPC ids and cancellation are scoped to that session, so separate chat windows may reuse the same numeric ids safely even when they share one OAuth account and token.
52
53
 
53
- `server_info.worker.pending_calls` reports `active`, `request_keys`, `maximum`, `oldest_ms`, and `by_tool`. A nonzero `active` count means work is in flight, not that the bridge is locked. Calls for simple reads and probes should continue while another independent process call runs. Explicit MCP cancellation, an incoming HTTP client disconnect, timeout, and daemon-socket closure remove the pending record and its session request key. Local results are bound to the authenticated relay session that originated the call, so a result that finishes after cancellation or reconnection is discarded instead of being sent over a replacement socket. Refreshing a chat page is not the recovery mechanism and should not be required.
54
+ `server_info.worker.pending_calls` reports `active`, `request_keys`, `maximum`, `oldest_ms`, and `by_tool`. `worker.sockets_live` separately reports `authenticated`, `probing`, `ready`, and `candidates`; only `ready` sockets contribute to `daemon.connected` and tool advertisement. A nonzero `active` count means work is in flight, not that the bridge is locked. Calls for simple reads and probes should continue while another independent process call runs. Explicit MCP cancellation, an incoming HTTP client disconnect, timeout, and daemon-socket closure remove the pending record and its session request key. Local results are bound to the authenticated relay session that originated the call, so a result that finishes after cancellation or reconnection is discarded instead of being sent over a replacement socket. Refreshing a chat page is not the recovery mechanism and should not be required.
54
55
 
55
56
  `server_info.worker.observability.calls.unmatched_results` counts results that reached the Worker after their pending record was already removed. A small increase can accompany cancellation or timeout races, especially during mixed-version upgrade convergence; sustained growth together with old pending calls indicates incompatible components or a lifecycle defect. The counter contains no tool arguments or result data.
56
57
 
57
58
  ### Relay interruption messages
58
59
 
59
- A brief relay interruption is retried automatically and is visible only with `--verbose`. Default logs do not print raw WebSocket values such as `code=1006` with an empty reason. If a transient outage persists for 10 seconds, the daemon emits a readable duration/cause/reconnect summary; later reminders use autonomous exponential backoff capped at 15 minutes, and recovery produces one readable summary. Each transport connection attempt also has a deadline, so a socket stuck in `CONNECTING` cannot freeze retries. Identity/version mismatch, authentication rejection, and unexpected protocol messages are not retried as ordinary network faults: the daemon emits an immediate actionable error and exits, requiring upgrade/redeployment or credential repair. A Worker-side daemon handshake timeout remains retryable.
60
+ A brief relay interruption is retried automatically and is visible only with `--verbose`. Default logs do not print raw WebSocket values such as `code=1006` with an empty reason. If a transient outage persists for 10 seconds, the daemon emits a readable duration/cause/reconnect summary; later reminders use autonomous exponential backoff capped at 15 minutes, and recovery produces one readable summary. Each transport connection attempt also has a deadline, so a socket stuck in `CONNECTING` cannot freeze retries. Identity/version mismatch, authentication rejection, and unexpected protocol messages are not retried as ordinary network faults: the daemon emits an immediate actionable error and exits, requiring upgrade/redeployment or credential repair. Worker-side hello and end-to-end readiness timeouts remain retryable. Authentication is not reported as usable service readiness until a session-bound probe result returns.
60
61
 
61
62
  Use `--verbose` only when close codes, close reasons, heartbeat timeouts, and retry delays are needed for diagnosis. A close code of 1006 means the transport ended without a normal close handshake; it does not by itself identify the cause.
62
63
 
@@ -94,7 +95,7 @@ Application UI inspection/actions require Accessibility permission for the Node/
94
95
 
95
96
  `machine-mcp` is a foreground command. It remains attached to the terminal, defaults to `info` logging, and stops on `Ctrl+C`. `machine-mcp service start` launches the installed platform service in the background and returns to the shell; that service uses `warn` logging.
96
97
 
97
- A global npm install changes the CLI files on disk but does not replace an already running Node process. Startup and other state-changing CLI operations use a token/process-identity lock and wait up to 30 seconds for a normal concurrent startup to finish; duration limits use monotonic elapsed time, so NTP or manual wall-clock correction does not lengthen or shorten the wait; a short launchd/systemd overlap is therefore serialized rather than reported immediately as an error. On a normal foreground start, Machine Bridge unloads the platform service and then independently examines the workspace daemon lock. This second path handles a detached/orphan `--daemon-only` process that launchd, systemd, or Task Scheduler no longer tracks. Only current lock records containing service mode, version, PID, process start time, entrypoint, workspace, and state root are eligible for takeover. Before sending `SIGTERM`, Machine Bridge verifies PID and process start time plus the live command line, entrypoint, canonical workspace, canonical state root, and daemon-only flag. It waits up to 15 seconds for both the PID and lock to disappear and does not add a later forced-kill escalation. On POSIX, a daemon that handles and ignores `SIGTERM` therefore reaches the bounded timeout and remains alive. Node maps the supported termination signals to immediate process termination on Windows, so the same verified-daemon stop normally completes rather than exercising the POSIX timeout branch. A foreground or unverifiable process is left untouched; stop a foreground instance with `Ctrl+C`.
98
+ A global npm install changes the CLI files on disk but does not replace an already running Node process. Startup and other state-changing CLI operations use a token/process-identity lock and wait up to 30 seconds for a normal concurrent startup to finish; duration limits use monotonic elapsed time, so NTP or manual wall-clock correction does not lengthen or shorten the wait; a short launchd/systemd overlap is therefore serialized rather than reported immediately as an error. On a normal foreground start, Machine Bridge unloads the platform service and then independently examines the workspace daemon lock. This second path handles a detached/orphan `--daemon-only` process that launchd, systemd, or Task Scheduler no longer tracks. Only current lock records containing service mode, version, PID, process start time, entrypoint, workspace, and state root are eligible for takeover. Before sending `SIGTERM`, Machine Bridge verifies PID and process start time plus the live command line, entrypoint, and daemon-only flag. Explicit `--workspace` and `--state-dir` must both match the active state when present; a recovery daemon started with only `--daemon-only` is accepted when the lock owner already records that workspace and state root. Partial path identity (only one of the two flags) is rejected. It waits up to 15 seconds for both the PID and lock to disappear and does not add a later forced-kill escalation. On POSIX, a daemon that handles and ignores `SIGTERM` therefore reaches the bounded timeout and remains alive. Node maps the supported termination signals to immediate process termination on Windows, so the same verified-daemon stop normally completes rather than exercising the POSIX timeout branch. A foreground or unverifiable process is left untouched; stop a foreground instance with `Ctrl+C`.
98
99
 
99
100
  `machine-mcp service status [WORKSPACE]` reports two independent layers: the platform service (`active`) and `workspace_daemon`, plus `effective_active` and `orphaned_workspace_daemon` summary flags. On macOS it is possible for launchd to report inactive while a prior Node process remains alive with parent PID 1; that is an orphan-daemon condition, not proof that the daemon stopped. `service stop` unloads the provider when present and then terminates only a verified service-style workspace daemon. `service uninstall` and full uninstall are ordered fail-closed operations: provider stop → verified daemon stop(s) → definition removal. A failed or ambiguous stop leaves definitions and state intact. If takeover reaches its deadline, run:
100
101
 
@@ -230,7 +231,9 @@ Claude and Copilot Studio call the public Worker from their cloud connectivity l
230
231
 
231
232
  ## Reconnect and replacement
232
233
 
233
- The daemon sends heartbeats and reconnects with bounded exponential backoff and jitter. A new socket remains a candidate until it authenticates and sends a valid `hello`; only then does it replace the previous daemon.
234
+ The daemon sends heartbeats and reconnects with bounded exponential backoff and jitter. A new socket progresses through candidate, authenticated/probing, and ready states. After `hello_ack`, the Worker sends a random probe that must return through the exact local dispatcher and relay-session result path used by real calls. Only the matching result produces `ready_ack`, `daemon.connected=true`, and tool advertisement. A healthy incumbent remains active until that proof succeeds, so a bad replacement cannot create an avoidable outage.
235
+
236
+ The Worker tracks inbound `lastSeenAt` and reclaims ready or probing sockets that go silent past their applicable liveness/readiness windows. Diagnose with `daemon.readiness_verified`, `worker.sockets_live.ready`, `worker.sockets_live.probing`, and `daemon.last_seen_at`; cumulative `worker.observability.sockets.authenticated` is an event counter, not current liveness. A state with no ready daemon is unavailable by construction rather than falsely online. Restart the daemon only after inspecting the classified timeout/protocol error; refreshing an MCP client does not repair the local transport.
234
237
 
235
238
  Pending calls are bound to the socket that received them. Results from another socket are ignored. A lost or replaced socket rejects only its own pending calls and terminates locally tracked child process trees. Process sessions are in-memory and do not survive daemon restart or replacement.
236
239
 
package/docs/TESTING.md CHANGED
@@ -49,7 +49,7 @@ The suite includes:
49
49
  - guarded state-root removal, unsafe state-root/workspace overlap rejection before creation, all-profile lock/daemon scanning, strict current-schema validation, corrupt-JSON isolation, and policy-origin persistence;
50
50
  - no filename-based sensitive-file denial under unrestricted policy;
51
51
  - log redaction, control-character handling, message/field bounds, suppression of both successful and failed per-tool events outside debug, service warning-level configuration, current-schema reset, and bounded tail trimming;
52
- - deterministic relay connection lifecycle coverage for transport construction/error/deadline, pre-handshake `welcome` validation, authenticated `hello_ack` readiness, identity/version mismatch, retryable Worker handshake errors, fatal protocol errors, autonomous outage-reminder backoff, handshake and heartbeat timeout, brief-outage suppression, sustained-outage escalation, recovery summaries, and supersession;
52
+ - deterministic relay connection lifecycle coverage for transport construction/error/deadline, pre-handshake `welcome` validation, separate `hello_ack` authentication and `ready_ack` end-to-end readiness, session-bound probe return, pre-ready tool rejection, premature-ready rejection, identity/version mismatch, retryable Worker hello/readiness errors, fatal protocol errors, autonomous outage-reminder backoff, handshake/readiness/heartbeat timeout, brief-outage suppression, sustained-outage escalation, recovery summaries, and supersession;
53
53
  - shared no-follow bounded-file reads for normal files, over-limit data, directories, and symbolic links;
54
54
  - owner-only directory enforcement rejecting final symlinks, failing closed on POSIX chmod errors, verifying `0700`, and retaining Windows portability; Worker temporary-secret lifecycle coverage for process-start-bound names, valid stale-owner reclamation, ambiguous-owner retention, `0600` mode, deletion failures, and simultaneous deployment/cleanup failures;
55
55
  - SARIF security-gate behavior for unknown findings, exact accepted rule/path matches, path mismatch rejection, rationale quality, and exception expiry;
@@ -60,7 +60,7 @@ The suite includes:
60
60
  - independently injected service CLI status/install/start/stop/uninstall/remove paths, including provider failure, no selected workspace, no deployed Worker, aliases, and default output/exit adapters;
61
61
  - CLI parsing, policy profiles, and client configuration boundaries;
62
62
  - live stdio MCP initialization with session instructions, capability resolution, discovery, calls, rich content, sessions, cancellation, managed-job acceptance, and a detached job/finally phase that survives stdio shutdown;
63
- - live local Worker OAuth registration, the unauthenticated `resource_metadata` challenge, protected-resource and authorization-server discovery, Streamable transport metadata, consent, URL-constructed `303` callbacks including the ChatGPT and hosted Claude redirect URIs with encoded state, PKCE, `offline_access`, form-encoded authorization-code and refresh-token exchanges, access/refresh rotation, stale refresh replay rejection, account-version refresh revocation, authorization-code replay rejection, pending-registration throttling that excludes already authorized DCR clients, exact built-in ChatGPT/Grok browser origins, additive custom origins, unrelated-origin preflight rejection, no CORS response sharing for unrelated or opaque origins, opaque-origin authorization-form routing, exact per-request redirect-origin CSP with narrowly scoped Microsoft regional-consent and final Copilot Studio handoff exceptions, accessible credential-error rendering, protocol negotiation, HMAC-bound MCP session issuance, two-session same-id concurrency, sessionless same-id independence, session-scoped cancellation isolation, same-session duplicate rejection, daemon-backed session bootstrap, dynamic tool advertisement, rich content, daemon replacement, cancellation, malformed daemon JSON/non-object rejection, duplicate hello rejection, and unknown-message closure. The metadata/refresh contract is the path used by Claude DCR and Copilot Studio Dynamic discovery. The same integration runs an `editor` account against a canonical `full` daemon and proves that `server_info` and remote `project_overview` report effective `edit` authority while retaining the full daemon ceiling only in explicitly scoped fields.
63
+ - live local Worker OAuth registration, the unauthenticated `resource_metadata` challenge, protected-resource and authorization-server discovery, Streamable transport metadata, consent, URL-constructed `303` callbacks including the ChatGPT and hosted Claude redirect URIs with encoded state, PKCE, `offline_access`, form-encoded authorization-code and refresh-token exchanges, access/refresh rotation, stale refresh replay rejection, account-version refresh revocation, authorization-code replay rejection, pending-registration throttling that excludes already authorized DCR clients, exact built-in ChatGPT/Grok browser origins, additive custom origins, unrelated-origin preflight rejection, no CORS response sharing for unrelated or opaque origins, opaque-origin authorization-form routing, exact per-request redirect-origin CSP with narrowly scoped Microsoft regional-consent and final Copilot Studio handoff exceptions, accessible credential-error rendering, protocol negotiation, HMAC-bound MCP session issuance, two-session same-id concurrency, sessionless same-id independence, session-scoped cancellation isolation, same-session duplicate rejection, daemon-backed session bootstrap, dynamic tool advertisement, rich content, candidate/probing/ready transitions, invalid readiness-result rejection, incumbent preservation until verified handover, daemon replacement, cancellation, malformed daemon JSON/non-object rejection, duplicate hello rejection, and unknown-message closure. The metadata/refresh contract is the path used by Claude DCR and Copilot Studio Dynamic discovery. The same integration runs an `editor` account against a canonical `full` daemon and proves that `server_info` and remote `project_overview` report effective `edit` authority while retaining the full daemon ceiling only in explicitly scoped fields.
64
64
  - local runtime proof that one blocked tool handler does not serialize an independent handler, plus relay fault injection proving an undeliverable terminal result interrupts the ambiguous socket and enters reconnect backoff.
65
65
  - a real headless-Chrome OAuth navigation regression with four cases: `form-action 'self'` blocks the first cross-origin callback, allowing only the registered callback blocks the regional redirect, allowing the registered and regional callbacks blocks the final Copilot Studio redirect, and the complete policy preserves `code` and `state` through all three cross-origin hops. Linux CI fails if Chrome is unavailable; other environments skip only this browser executable check while retaining the Worker CSP assertions.
66
66
 
@@ -108,7 +108,7 @@ GitHub Actions executes the main suite on Linux, macOS, and Windows using the pi
108
108
  - Every permission-expanding feature needs a denial test. Browser/application features also require a token/value/content non-disclosure assertion.
109
109
  - Every bounded resource needs an over-limit test.
110
110
  - Every multi-stage mutation needs a no-partial-commit test.
111
- - Every remote call correlation change needs daemon replacement and cancellation coverage.
111
+ - Every remote call correlation or readiness change needs daemon replacement, incumbent-preservation, timeout, malformed-result, and cancellation coverage.
112
112
  - Every durable workflow needs disconnect, cancellation, cleanup-failure, dead-runner, and plan-scrubbing coverage.
113
113
  - Secret-bearing resource tests must assert absence of raw, path, base64, and hex forms from MCP-visible results.
114
114
  - Logs and public metadata should be tested for absence of sensitive fields, arguments, outputs, and routine success noise—not only presence of expected fields.
package/docs/UPGRADING.md CHANGED
@@ -4,9 +4,9 @@
4
4
 
5
5
  Machine Bridge does not retain parallel implementations for obsolete MCP protocol dates, policy revisions, state schemas, lock formats, or browser-extension protocols. The supported path is a direct upgrade from the immediately preceding published package while its state already uses the current schema.
6
6
 
7
- Version 1.2.2 keeps local state schema version 6 and policy revision 5 unchanged. Existing 1.2.1 workspaces, named accounts, resource registrations, managed-job history, Worker identity, and browser pairing state are reused without a schema conversion. The package change affects relay call/result lifetime, Worker request cancellation, logging presentation, and observability; it does not alter the stored authority model.
7
+ Version 1.2.6 keeps local state schema version 6 and policy revision 5 unchanged. Existing 1.2.5 workspaces, named accounts, resource registrations, managed-job history, Worker identity, and browser pairing state are reused without a schema conversion. The package change hardens relay ready-context propagation and verified service-daemon takeover for recovery daemons; it does not alter stored authority or credential records.
8
8
 
9
- Version 1.2.2 requires the Worker, local daemon, and browser extension to converge on the same package version. Normal startup redeploys the versioned Worker when required. The Worker configuration now enables Cloudflare request-signal delivery and signal passthrough to the Durable Object; an old Worker therefore does not provide the new HTTP-disconnect cancellation path even when the local daemon has already been upgraded.
9
+ Version 1.2.6 still requires the Worker, local daemon, and browser extension to converge on the same package version. The 1.2.5 candidate → probing → ready negotiation remains mandatory; mixed components fail closed. Normal startup redeploys the versioned Worker when required and can now stop a verified same-workspace service daemon even when its process argv used only `--daemon-only` without repeating `--workspace` and `--state-dir`. Reload the unpacked extension after convergence even though this release does not change its browser protocol.
10
10
 
11
11
  Version 1.2.0 could accept prototype-shaped account roles through malformed administration input. On the first 1.2.1 or later Worker access, such an account is preserved for recovery but repaired fail-closed: its role becomes `reviewer`, it is disabled, its account version advances, and its authorization codes and tokens are removed. An operator can then assign a valid role, enable the account, and rotate its password through the normal account administration flow. A local policy record with an unknown profile label is normalized to `custom` while retaining its explicit capability fields; an invalid explicit `--profile` is rejected.
12
12
 
@@ -29,6 +29,6 @@ Machine Bridge never treats an unreadable or foreign-schema state file as empty
29
29
 
30
30
  ## Rollback
31
31
 
32
- Rollback is supported only when the older package understands every persisted schema and protocol already written by the newer package. Version 1.2.1 does not advance local state or policy schemas, so rollback to 1.2.0 remains structurally possible. A repaired malformed account remains a valid disabled reviewer record that 1.2.0 can read. The preferred recovery is still to fix forward because the browser extension and deployed Worker must match the running package exactly, and rollback would restore the defective input validation.
32
+ Rollback is supported only when the older package understands every persisted schema and protocol already written by the newer package. Version 1.2.6 does not advance local state or policy schemas, so a complete rollback to 1.2.5 is structurally possible. The Worker, daemon, and extension must be rolled back together; restoring only one component recreates an unavailable mixed-version system. The preferred recovery is still to fix forward so incomplete ready-context dispatch and orphan recovery-daemon takeover remain corrected.
33
33
 
34
34
  Never roll back by copying only selected state files or changing version fields. Restore one complete verified state backup, package version, Worker build, and browser extension as a single operational unit.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "machine-bridge-mcp",
3
- "version": "1.2.2",
3
+ "version": "1.2.6",
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",
@@ -1,5 +1,6 @@
1
1
  // @ts-check
2
2
  const TOKEN_STOP_WORDS = new Set(["the", "and", "for", "with", "from", "this", "that", "use", "using", "into", "of", "to", "a", "an", "in", "on", "is", "are", "or", "及", "和", "的", "了", "在", "用", "使用", "进行", "根据"]);
3
+ const GENERIC_IDENTITY_TOKENS = new Set(["app", "application", "cli", "local", "tool", "web"]);
3
4
  const ENGLISH_TOKEN_CANONICAL = new Map([
4
5
  ["created", "create"], ["creating", "create"], ["creation", "create"], ["creator", "create"],
5
6
  ["improved", "improve"], ["improving", "improve"], ["improvement", "improve"],
@@ -17,7 +18,7 @@ const HAN_TOKEN_ALIASES = Object.freeze([
17
18
  [/技能/u, ["skill"]],
18
19
  [/安装/u, ["install", "installer"]],
19
20
  [/部署/u, ["deploy", "deployment"]],
20
- [/查找|搜索|检索/u, ["search", "find"]],
21
+ [/查找|搜索|检索|调研|研究/u, ["search", "find", "research"]],
21
22
  [/最新|当前/u, ["latest", "current"]],
22
23
  [/官方/u, ["official"]],
23
24
  [/文档|资料/u, ["docs", "documentation"]],
@@ -47,7 +48,7 @@ export function relevanceScore(task, candidate, identity = "") {
47
48
  let score = 0;
48
49
  for (const token of taskTokens) {
49
50
  if (candidateTokens.has(token)) score += token.length >= 6 ? 2 : 1;
50
- if (identityTokens.has(token)) score += token.length >= 6 ? 4 : 3;
51
+ if (identityTokens.has(token) && !GENERIC_IDENTITY_TOKENS.has(token)) score += token.length >= 6 ? 4 : 3;
51
52
  }
52
53
  const taskComparable = comparableText(task);
53
54
  const candidateComparable = comparableText(candidate);
@@ -168,11 +168,18 @@ function inspectWorkspaceDaemonOwner(state, owner) {
168
168
  const name = path.basename(value);
169
169
  return name === entryName || name === "machine-mcp" || name === "machine-mcp.mjs";
170
170
  });
171
- const matches = argv.includes("--daemon-only")
171
+ const explicitIdentity = Boolean(workspaceArg && stateRootArg)
172
172
  && sameCanonicalPath(workspaceArg, state.workspace.path)
173
- && sameCanonicalPath(stateRootArg, state.paths.stateRoot)
174
- && entryMatches;
175
- return { verified_service_daemon: matches, reason: matches ? "service_command" : "command_mismatch" };
173
+ && sameCanonicalPath(stateRootArg, state.paths.stateRoot);
174
+ const implicitIdentity = !workspaceArg && !stateRootArg;
175
+ const commonIdentity = argv.includes("--daemon-only") && entryMatches;
176
+ if (commonIdentity && explicitIdentity) {
177
+ return { verified_service_daemon: true, reason: "service_command" };
178
+ }
179
+ if (commonIdentity && implicitIdentity) {
180
+ return { verified_service_daemon: true, reason: "implicit_service_command" };
181
+ }
182
+ return { verified_service_daemon: false, reason: "command_mismatch" };
176
183
  }
177
184
 
178
185
  function commandFlagValue(argv, name) {