machine-bridge-mcp 0.12.2 → 0.14.0

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.
@@ -14,7 +14,7 @@ This approximates a local coding agent without pretending that the MCP server ow
14
14
  - `list_local_commands` returns effective registered commands.
15
15
  - `run_local_command` executes a registered direct-argv command when policy permits.
16
16
 
17
- Both stdio and remote Worker connection initialization attempt `session_bootstrap`. Its instruction text is appended to the MCP `initialize` result. Because a host may reuse one MCP connection across conversations, the explicit tool and per-task `resolve_task_capabilities` call remain necessary to refresh and reapply instructions reliably.
17
+ Both stdio and remote Worker connection initialization attempt `session_bootstrap`. Its instruction text is appended to the MCP `initialize` result. Because a host may reuse one MCP connection across conversations, the explicit tool and per-task `resolve_task_capabilities` call remain necessary to refresh and reapply instructions reliably. `server_info.observability.capability_routing` and `project_overview.capabilityRouting` report whether those calls reached the local runtime, their counts and timestamps, loaded-source flags, selected capability metadata, and a runtime-keyed HMAC task fingerprint. Raw task text is not retained.
18
18
 
19
19
  ## Useful defaults without configuration
20
20
 
@@ -171,9 +171,13 @@ Use `AGENTS.md` for durable facts a new contributor or agent must know: validate
171
171
  Without explicit `skill_roots`, discovery scans:
172
172
 
173
173
  - target-to-root `.agents/skills` directories;
174
+ - target-to-root `.codex/skills` compatibility directories;
174
175
  - under unrestricted policy, `~/.agents/skills`;
176
+ - under unrestricted policy, `CODEX_HOME/skills` (normally `~/.codex/skills`);
175
177
  - under unrestricted policy on Unix-like systems, `/etc/codex/skills`.
176
178
 
179
+ Canonical paths are deduplicated, so a compatibility symlink from `.agents/skills` to `.codex/skills` does not duplicate a skill.
180
+
177
181
  A skill directory contains `SKILL.md` or `skill.md` with simple front matter:
178
182
 
179
183
  ```markdown
@@ -189,15 +193,21 @@ No persistent skill or project-context index is trusted as authoritative. `sessi
189
193
 
190
194
  ## Progressive disclosure and task selection
191
195
 
192
- `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. Hyphens, underscores, dots, and whitespace are normalized for matching, while an explicitly named skill or registered command receives a strong deterministic boost. Thus a task that says “agents progressive disclosure” can match `agents-progressive-disclosure` without relying on generic description words.
196
+ `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.
197
+
198
+ 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
+
200
+ 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.
201
+
202
+ ## Registered and automatic package commands
193
203
 
194
- This ranking is deterministic local assistance, not semantic certainty. 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.
204
+ `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, and whether caller arguments are accepted. A caller may reduce but not increase the timeout.
195
205
 
196
- ## Registered commands
206
+ 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.
197
207
 
198
- `run_local_command` uses direct argv spawning rather than a shell. The manifest controls working directory, timeout ceiling, and whether caller arguments are accepted. A caller may reduce but not increase the timeout.
208
+ On Unix-like systems the package-manager executable is spawned directly. On Windows, npm/pnpm/yarn/bun are command shims rather than native executables, so the bridge uses a fixed `cmd.exe /d /s /c` command assembled only from an allowlisted manager and a validated script name; callers still cannot append arguments or shell syntax. This wrapper is transport compatibility, not a trust boundary.
199
209
 
200
- Registered commands are workflow aliases, not an approval boundary or sandbox. Package scripts, interpreters, compilers, and executables retain local-user authority. Use `review` or `edit`, or external VM/container isolation, for untrusted content.
210
+ Directly invoking `npm run`, `pnpm run`, `yarn run`, or `bun run` does not make the repository script itself shell-free: package managers execute repository-controlled script bodies according to their own semantics. All registered commands are workflow aliases, not an approval boundary or sandbox. Package scripts, interpreters, compilers, and executables retain local-user authority. Use `review` or `edit`, or external VM/container isolation, for untrusted content.
201
211
 
202
212
  ## Recommended host workflow
203
213
 
@@ -44,9 +44,9 @@ A canonical workspace receives an independent profile, Worker name, secret set,
44
44
 
45
45
  ### Agent context and capability resolver
46
46
 
47
- `AgentContextManager` discovers the nearest Git/workspace scope, applies the user configuration and hierarchical `.machine-bridge/agent.json` files, selects built-in/user/root-to-target instructions, discovers bounded filesystem skills, and resolves registered commands. `default-instructions.mjs` supplies a versioned in-package working-agreement block and derives a small virtual project-context block from root filenames and bounded metadata. It reads package script names but not bodies, does not inspect dependency values or source contents, executes nothing, and writes no user/repository files. A global `model_instructions_file` is a separate user-designated session source and cannot be overridden by a project.
47
+ `AgentContextManager` discovers the nearest Git/workspace scope, applies the user configuration and hierarchical `.machine-bridge/agent.json` files, selects built-in/user/root-to-target instructions, discovers bounded filesystem skills, and resolves registered commands. `project-package.mjs` owns no-follow package metadata parsing, package-manager selection (including fail-closed conflicting-lockfile handling), script-name normalization, bounded workflow-intent aliases, and automatic `package.*` command construction so instruction rendering and command execution do not duplicate package parsing. `default-instructions.mjs` supplies a versioned in-package working-agreement block and derives a small virtual project-context block from root filenames and bounded metadata. It reads package script names but not bodies, does not inspect dependency values or source contents, executes nothing, and writes no user/repository files. A global `model_instructions_file` is a separate user-designated session source and cannot be overridden by a project.
48
48
 
49
- `session_bootstrap` is requested during both stdio and remote MCP initialization. The Worker delegates this read to the connected daemon with a short bounded timeout; failure falls back to static server instructions rather than blocking initialization indefinitely. `resolve_task_capabilities` performs a fresh deterministic scan, rebuilds automatic project facts, and ranks skill/command metadata for the current task. Application and browser capability metadata is added by `LocalRuntime`.
49
+ `session_bootstrap` is requested during both stdio and remote MCP initialization. The Worker delegates this read to the connected daemon with a short bounded timeout; failure falls back to static server instructions rather than blocking initialization indefinitely. `resolve_task_capabilities` performs a fresh deterministic scan, rebuilds automatic project facts, and ranks skill/command metadata for the current task. Application and browser capability metadata is added by `LocalRuntime`; installed application inventory uses a short bounded cache. `CapabilityObserver` records only counts, timestamps, source flags, selected metadata, match counts, recommended tool names, and a runtime-keyed task fingerprint so operators can verify routing without creating a task-content log.
50
50
 
51
51
  The MCP catalog remains static: local skills and commands do not become dynamically named tools. This avoids stale host catalog caches and keeps Worker/stdio schema parity. Progressive disclosure separates discovery, instruction loading, and execution authority. A refresh fingerprint is descriptive rather than a cache-validity guarantee.
52
52
 
@@ -60,7 +60,7 @@ See [Session instructions, skills, commands, and capability discovery](AGENT_CON
60
60
 
61
61
  `BrowserBridgeManager` owns a loopback HTTP/WebSocket broker. The first runtime for the machine-level state root becomes broker owner; additional workspaces and stdio runtimes authenticate to `/runtime` and proxy through the same extension socket. This preserves one extension pairing while allowing multiple local MCP runtimes.
62
62
 
63
- The packaged Manifest V3 extension runs in the user's existing Chromium profile. Its service worker is transport/orchestration only; a fixed packaged `page-automation.js` module is injected into selected frames to expose DOM operations in the correct execution world. It supports accessible frames, open Shadow DOM roots, structured actions, multi-field forms, resource-backed file inputs, and screenshots. The extension never receives arbitrary caller-provided code. The broker validates loopback hostnames, extension origin, bearer subprotocols, message sizes, concurrency, and timeouts. Pairing material is owner-only and omitted from MCP/log output.
63
+ The packaged Manifest V3 extension runs in the user's existing Chromium profile. Its service worker owns transport, tab lifecycle, bounded waits, and selection between input backends. A fixed packaged `page-automation.js` module is injected into selected frames for semantic snapshots, stable element refs, actionability checks, open-Shadow-DOM traversal, structured DOM operations, multi-field forms, and resource-backed file inputs. A separate fixed `devtools-input.js` module exposes only bounded mouse, keyboard, and text sequences through the Chromium debugger API; callers cannot select CDP methods. Trusted sessions attach for one action and detach in `finally`, with explicit DOM fallback policy. A versioned capability handshake prevents a stale unpacked extension from being treated as compatible; replacement is committed only after validation, so an invalid candidate cannot displace the current working connection. Extension keepalive `ping` messages are an explicit protocol operation. The extension never receives arbitrary caller-provided code. The broker validates loopback hostnames, extension origin, bearer subprotocols, message sizes, concurrency, and timeouts. Pairing material is owner-only and omitted from MCP/log output.
64
64
 
65
65
  See [Local application and browser automation](LOCAL_AUTOMATION.md).
66
66
 
@@ -208,7 +208,9 @@ Managed jobs use the same argv/environment primitives but a different lifecycle.
208
208
 
209
209
  ## Daemon reconnect and replacement
210
210
 
211
- The local `RelayConnection` 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.
211
+ The local `RelayConnection` treats proxy selection, transport construction, transport open, authenticated readiness, and outage recovery as separate states. `network-proxy.mjs` 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.
212
+
213
+ 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.
212
214
 
213
215
  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.
214
216
 
@@ -230,7 +232,7 @@ OAuth metadata is pruned on access. Expired codes/tokens, old throttling records
230
232
 
231
233
  ## Observability
232
234
 
233
- Public health exposes only server identity and version. Authenticated `server_info` exposes bounded runtime status, policy origin/revision, managed-job counts, resource alias names without paths or values, and the daemon/relay-advertised tool counts. 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.
235
+ Public health exposes only server identity and version. Authenticated `server_info` exposes bounded runtime status, policy origin/revision, managed-job counts, resource alias names without paths or values, daemon/relay-advertised tool counts, relay route state without endpoint details, and privacy-preserving capability-routing evidence. 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.
234
236
 
235
237
  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.
236
238
 
package/docs/AUDIT.md CHANGED
@@ -1,6 +1,18 @@
1
1
  # Engineering and security audit
2
2
 
3
- This document records the cross-cutting audit initiated for version 0.12.0 and the 0.12.2 follow-up triggered by real cross-platform CI failures. It complements, but does not replace, the continuously enforced contracts in `SECURITY.md`, `docs/ENGINEERING.md`, and the test suite.
3
+ This document records the cross-cutting audit initiated for version 0.12.0, the 0.12.2 cross-platform CI follow-up, and the 0.13.0 architecture/automatic-routing follow-up. It complements, but does not replace, the continuously enforced contracts in `SECURITY.md`, `docs/ENGINEERING.md`, and the test suite.
4
+
5
+ ## 2026-07-13 architecture and automatic-routing follow-up
6
+
7
+ A new behavior-level review found that the existing global/project instruction path was functioning, but three gaps explained weak user-visible automation: direct `.codex/skills` compatibility depended on user-created symlinks, root package scripts were described but not executable through the registered-command surface, and the runtime provided no proof that the host had called bootstrap or task resolution. The review added direct compatibility roots, bounded automatic package commands, and privacy-preserving routing telemetry while retaining the host/model invocation boundary.
8
+
9
+ The review also reproduced a lifecycle defect not covered by the previous descendant test: when a direct child exited after group `SIGTERM` while a detached-stdio descendant ignored the signal, `runProcess` cleared the pending `SIGKILL` escalation and left the descendant alive. Timeout/cancellation/replacement now use one escalation primitive whose forced phase remains referenced independently of direct-child tracking. A dedicated real-process regression covers that exact ordering.
10
+
11
+ Relay routing now honors standard HTTP(S) proxy environment variables and `NO_PROXY`, rejects invalid/unsupported proxy configuration before reconnect, and exposes only coarse route state. Package metadata parsing and capability observation were extracted from orchestration code, and architecture tests now reject domain-to-adapter imports.
12
+
13
+ A real macOS stdio smoke test used the user's actual global instructions, repository instructions, installed skills, package scripts, and installed applications with an isolated temporary state root. Initialization advertised 49 tools and contained all four instruction layers. A Chinese skill-creation task selected `skill-creator` at score 16 and loaded 22,047 bytes of instructions; 42 automatic package commands were discovered and `package.version-check` executed with exit code 0; 100 applications were discovered, `Chess` matched at score 15, opened in the background, was observed running, then quit cleanly. Capability telemetry recorded bootstrap plus three task resolutions without retaining raw task text.
14
+
15
+ The same review reproduced two daily-use ranking failures before the fix: Chinese tasks did not match English skill metadata, and an English skill-creation request tied several generic “create” descriptions and selected `frontend-design` alphabetically. Cross-platform CI then exposed a separate Windows execution defect: automatic commands spawned `npm` as if it were a native executable, but Windows provides `npm.cmd`, producing `spawn npm ENOENT`. Windows package commands now use one fixed command-shell wrapper built from allowlisted manager and validated script names. Bounded bilingual intent normalization plus capability-name weighting corrected both cases. Residual limits remain external: the MCP host can filter initialization instructions or tools, a package manager can execute arbitrary repository script bodies, HTTP(S) proxy support is not SOCKS support, and OS process-tree guarantees remain platform dependent. In the observed remote ChatGPT path, the 0.12.2 relay advertised 49 tools while the host made only 11 available to this conversation, demonstrating that server-side discovery cannot by itself guarantee host invocation.
4
16
 
5
17
  ## 0.12.2 follow-up findings
6
18
 
package/docs/CLIENTS.md CHANGED
@@ -54,7 +54,7 @@ The MCP specification defines stdio and Streamable HTTP as standard transports.
54
54
 
55
55
  ## Automatic capability selection
56
56
 
57
- MCP initialization and `resolve_task_capabilities` give the host a current view of conservative built-in working agreements, bounded project facts, explicit global/project instructions, skills, registered commands, applications, and browser capability. The resolver rescans rather than relying on a stale dynamic tool list and can return the best matching skill instructions in one call. No instruction file is required for the default layers, and no repository file is written automatically.
57
+ MCP initialization and `resolve_task_capabilities` give the host a current view of conservative built-in working agreements, bounded project facts, explicit global/project instructions, skills, explicit/automatic package commands, applications, and browser capability. The resolver rescans rather than relying on a stale dynamic tool list and can return the best matching skill instructions in one call. No instruction file is required for the default layers, and no repository file is written automatically.
58
58
 
59
59
  The host still owns the agent loop. ChatGPT web may use the recommendation automatically, ask for confirmation, expose only part of the catalog, or ignore server instructions. No MCP implementation can guarantee automatic invocation from the server side. Machine Bridge models that limitation explicitly instead of treating a recommendation as execution.
60
60
 
@@ -46,6 +46,7 @@ CLI, service, release, and deployment orchestration
46
46
  Rules:
47
47
 
48
48
  - Transport lifecycle, domain execution, persistence, and presentation are separate modules.
49
+ - Domain modules must not import CLI, service, stdio, or relay adapters. Architecture tests enforce this dependency direction for agent context, package metadata, default instructions, capability observation, application automation, process sessions, and proxy selection.
49
50
  - Pure classification and normalization functions are exported and tested directly when practical.
50
51
  - Adapters may translate data but should not duplicate policy or schemas.
51
52
  - 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.
@@ -13,15 +13,17 @@ Launching a separate automation browser is predictable but loses the user's ordi
13
13
 
14
14
  The extension works with the current profile and supports:
15
15
 
16
- - listing existing tabs;
16
+ - listing, creating, activating, and closing tabs;
17
17
  - reading the current serialized DOM HTML from the main frame or accessible subframes;
18
- - discovering interactive controls, labels, roles, placeholders, and field metadata across accessible frames and open Shadow DOM roots;
19
- - structured navigation, click, focus, fill, select, check, uncheck, key press, and form submission;
18
+ - discovering interactive controls, stable per-document element references, actionability state, geometry, labels, roles, placeholders, and field metadata across accessible frames and open Shadow DOM roots;
19
+ - waiting for bounded combinations of URL, load state, page text, and element state;
20
+ - structured navigation, click, double-click, hover, focus, fill, type-text, select, check, uncheck, key press, scroll-into-view, and form submission;
21
+ - automatic existence/visibility/enabled/editable/stability/hit-target checks before actions, with fixed DevTools mouse/keyboard input for top-frame interactions and a controlled DOM fallback;
20
22
  - multi-field complex form filling in one operation, bounded to 200 fields and 4 MiB of aggregate text;
21
23
  - populating file inputs from registered local resource files;
22
24
  - visible-tab screenshots.
23
25
 
24
- Current support targets Chrome, Chromium, Microsoft Edge, Brave, Vivaldi, and compatible Chromium browsers. After upgrading Machine Bridge, reload the unpacked extension from the browser extensions page so its packaged scripts match the new runtime. Browser-internal pages, extension stores, some PDF/plugin viewers, cross-origin frames denied to the extension, and pages restricted by enterprise policy may not be scriptable.
26
+ Current support targets Chrome, Chromium, Microsoft Edge, Brave, Vivaldi, and compatible Chromium browsers. After upgrading Machine Bridge, reload the unpacked extension from the browser extensions page so its packaged scripts and permissions match the new runtime. Version 0.14.0 adds the `debugger` permission for fixed trusted-input commands; the extension attaches only for the action and detaches afterward. The broker requires a versioned extension hello with the expected capability set, keeps the last compatible connection active until a replacement completes its handshake, and reports `extension_reload_required` instead of silently routing new tools to stale extension code. Browser-internal pages, extension stores, some PDF/plugin viewers, cross-origin frames denied to the extension, and pages restricted by enterprise policy may not be scriptable.
25
27
 
26
28
  ## One-time browser setup
27
29
 
@@ -56,14 +58,16 @@ The broker is machine-global rather than workspace-global. One local owner liste
56
58
  - `browser_status` reports broker role, extension connection, supported operations, pairing URL, and extension path without returning the pairing token.
57
59
  - `pair_browser_extension` opens the local pairing page and returns setup steps.
58
60
  - `browser_list_tabs` lists current tabs.
61
+ - `browser_manage_tabs` creates, activates, or closes a selected tab.
59
62
  - `browser_get_source` returns bounded current DOM HTML for selected frames.
60
- - `browser_inspect_page` returns bounded interactive-element metadata.
61
- - `browser_action` performs one structured navigation or DOM action. Navigation accepts absolute `http`, `https`, or `file` URLs; script/data schemes are rejected.
63
+ - `browser_inspect_page` returns a bounded semantic snapshot. Each control includes a stable `ref` for the lifetime of that document/frame plus visibility, enabled/editable state, and viewport geometry.
64
+ - `browser_wait` waits until all supplied URL, load-state, page-text, and element-state conditions are true.
65
+ - `browser_action` performs one structured navigation or page action. Navigation accepts absolute `http`, `https`, or `file` URLs; script/data schemes are rejected. Pointer and keyboard actions default to `input_mode: auto`, which attempts fixed trusted DevTools input in the top frame and falls back to DOM behavior. `trusted` forbids fallback; `dom` never attaches the debugger.
62
66
  - `browser_fill_form` fills up to 200 fields and can submit once.
63
67
  - `browser_upload_files` sets a file input from up to eight registered local resources. Caller filenames must be safe single-component names; derived names have controls and separators removed. MIME overrides must be canonical media types.
64
68
  - `browser_screenshot` returns native MCP image content.
65
69
 
66
- Selectors can use CSS, ID, field name, label text, visible text, ARIA/implicit role, placeholder, and a zero-based match index. The fixed page module traverses open Shadow DOM roots; closed shadow roots remain inaccessible. For frame-specific work, inspect all frames first and then pass `frame_id` to an action.
70
+ Selectors can use the `ref` returned by inspection, CSS, ID, field name, label text, visible text, ARIA/implicit role, placeholder, and a zero-based match index. `ref` cannot be combined with other fields and becomes stale after navigation or element replacement. Non-ref selectors that match multiple controls fail explicitly unless `index` disambiguates them. The fixed page module traverses open Shadow DOM roots; closed shadow roots remain inaccessible. For frame-specific work, inspect all frames first and then pass `frame_id` to an action. Trusted input currently targets the top frame because DevTools coordinates are top-level; use `input_mode: dom` for an explicitly selected subframe.
67
71
 
68
72
  ## Sensitive form values and files
69
73
 
@@ -89,7 +93,7 @@ Menu-bar and menu subtrees are not recursively expanded by default. This keeps m
89
93
 
90
94
  ## Capability discovery and automatic selection
91
95
 
92
- `resolve_task_capabilities` rescans instruction files, skills, registered commands, and relevant local automation metadata on every call. It ranks matching skills and commands, optionally loads the best skill, and recommends browser/application tools when the task indicates them.
96
+ `resolve_task_capabilities` rescans instruction files, skills, explicit/automatic package commands, and relevant local automation metadata on every call. It ranks matching skills and commands, optionally loads the best skill, and compares every canonical-full task with cached installed-application names, so a task that directly names an app does not need generic “app/window” wording. Application inventory is refreshed after a bounded cache interval.
93
97
 
94
98
  This is the strongest reliable server-side automation boundary available through MCP: discovery, refresh, ranking, and progressive skill loading are automatic. The MCP host still owns the model loop and decides whether a recommended tool is exposed, approved, or invoked. Machine Bridge cannot force ChatGPT web or another host to make a call that the host declines.
95
99
 
@@ -100,13 +104,13 @@ The browser extension has broad page access because generic source inspection an
100
104
  The implementation reduces avoidable risk as follows:
101
105
 
102
106
  - all browser/app tools are `full`-only;
103
- - no arbitrary evaluation or caller-provided script source is accepted;
107
+ - no arbitrary evaluation, caller-provided script source, or caller-selected DevTools method is accepted; trusted input exposes only fixed `Input.dispatchMouseEvent`, `Input.dispatchKeyEvent`, and `Input.insertText` sequences;
104
108
  - loopback HTTP validates `Host`; extension WebSockets require a random bearer subprotocol and a `chrome-extension://` origin;
105
109
  - runtime-to-broker connections require a separate authenticated subprotocol;
106
110
  - the pairing token is stored owner-only, embedded only in the non-cacheable local pairing page, and omitted from MCP results and logs;
107
111
  - an established extension pairing cannot be silently replaced by another localhost page; replacement requires clicking the extension action on the active pairing page;
108
112
  - arguments, form values, page source, screenshots, and results are not operational log data;
109
- - message, source, form-field, upload, traversal, concurrency, proxy-route, and timeout limits are enforced;
113
+ - message, source, form-field, upload, traversal, concurrency, proxy-route, actionability, and timeout limits are enforced; ambiguous selectors, stale refs, hidden/disabled/edit-blocked controls, moving targets, and obscured pointer targets fail explicitly;
110
114
  - MCP cancellation clears local and broker pending state and propagates a cancellation signal to the extension; an action already delivered to a page or application may still have completed;
111
115
  - resource-backed text and files are not returned after injection.
112
116
 
package/docs/LOGGING.md CHANGED
@@ -39,6 +39,8 @@ A TCP/WebSocket `open` event is only transport availability. The daemon is repor
39
39
 
40
40
  Brief network interruptions are expected on laptop network changes, Worker deployment, proxy rotation, and ordinary internet transport. They are handled as follows:
41
41
 
42
+ - proxy selection records only `direct` or `proxy` at debug level; proxy URLs, usernames, passwords, and headers are never log fields;
43
+ - invalid proxy URLs or unsupported schemes fail fast with a sanitized corrective error rather than repeated reconnect warnings;
42
44
  - the raw close code, close reason, error class, connected duration, and retry delay are debug-only;
43
45
  - a brief interruption that reconnects within the grace period produces no `info` or `warn` line;
44
46
  - an outage that remains unresolved for 10 seconds produces a user-readable warning stating the duration, reconnect attempt count, classified cause, and automatic recovery behavior;
@@ -118,7 +120,7 @@ Each managed job has owner-only runner diagnostic logs. Child-step output is ret
118
120
 
119
121
  Machine Bridge does not classify filenames as sensitive and does not block credential-looking names under canonical `full`. An MCP host, connector, model provider, desktop application, operating system, or endpoint-security layer may independently reject a request before it reaches Machine Bridge.
120
122
 
121
- Use `server_info`, `machine-mcp status`, `machine-mcp doctor`, and `diagnose_runtime` to distinguish local policy from host-side enforcement. Changing the Machine Bridge profile cannot override another layer.
123
+ Use `server_info`, `project_overview`, `machine-mcp status`, `machine-mcp doctor`, and `diagnose_runtime` to distinguish local policy from host-side enforcement. Capability-routing status is returned on demand rather than written as task logs; it stores a runtime-keyed task fingerprint, not raw task text. Changing the Machine Bridge profile cannot override another layer.
122
124
 
123
125
  ## Adding or changing logs
124
126
 
@@ -15,6 +15,9 @@ machine-mcp service status
15
15
  | Result | Interpretation |
16
16
  |---|---|
17
17
  | `server_info` reports full and all relay tools, but the current session UI exposes fewer tools | Host/connector post-relay filtering; Machine Bridge cannot enumerate or override that subset |
18
+ | `capability_routing.bootstrap_observed` is false | The current local runtime has not received `session_bootstrap`; reconnect or inspect host initialization handling |
19
+ | `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 |
20
+ | Task resolution ran but all match counts are zero | The resolver ran successfully but found no sufficiently relevant local skill, command, or application |
18
21
  | No structured result because the host rejects the call | Host/connector approval or safety layer, or transport before daemon delivery |
19
22
  | `mcp-host-to-daemon` passes but `local-filesystem` fails | Local state/runtime permissions, disk policy, sandbox, or endpoint security |
20
23
  | Filesystem passes but `local-process-spawn` fails | Local executable policy, endpoint security, OS permissions, or damaged Node runtime |
@@ -30,6 +33,8 @@ A brief relay interruption is retried automatically and is visible only with `--
30
33
 
31
34
  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.
32
35
 
36
+ The daemon honors `HTTPS_PROXY`/`HTTP_PROXY` and `NO_PROXY` through standard environment-proxy resolution. `wss:` targets use HTTPS proxy selection and `ws:` targets use HTTP proxy selection. Only HTTP and HTTPS proxy URLs are accepted. Invalid URLs or unsupported protocols fail startup with corrective guidance instead of entering the reconnect loop. `server_info.runtime.relay.network_route` reports only `direct`, `proxy`, or `invalid-proxy-configuration`; proxy endpoints and credentials are never returned or logged.
37
+
33
38
  ## Browser extension setup and diagnosis
34
39
 
35
40
  The full-profile daemon starts the loopback browser broker automatically. Install/pair the packaged extension once:
@@ -71,14 +76,21 @@ machine-mcp service status
71
76
  machine-mcp --verbose
72
77
  ```
73
78
 
74
- Recommended upgrade:
75
-
76
- ```sh
77
- npm install -g --omit=optional --allow-scripts=esbuild,workerd,sharp,fsevents machine-bridge-mcp@latest
79
+ For installation or upgrade, launch a pinned npm 12 CLI from an empty temporary directory. This avoids depending on the old npm version currently on `PATH` and prevents its `npx` bootstrap from validating unrelated nearby project metadata first. On Windows Command Prompt:
80
+
81
+ ```bat
82
+ set "MBM_INSTALL_DIR=%TEMP%\machine-bridge-mcp-install-%RANDOM%-%RANDOM%"
83
+ mkdir "%MBM_INSTALL_DIR%"
84
+ pushd "%MBM_INSTALL_DIR%"
85
+ npx --yes npm@12.0.1 install --global npm@12.0.1
86
+ npx --yes npm@12.0.1 install --global --omit=optional --allow-scripts=esbuild,workerd,sharp,fsevents machine-bridge-mcp@latest
87
+ popd
88
+ rmdir /s /q "%MBM_INSTALL_DIR%"
89
+ npm --version
78
90
  machine-mcp --verbose
79
91
  ```
80
92
 
81
- Keep `--omit=optional` in the install command. Without it npm may resolve optional `fsevents` and warn that its install script was not included in `allowScripts`; Machine Bridge does not require that development-time watcher at runtime.
93
+ `Unknown cli config "--allow-scripts"` proves the package installation ran under npm 11 or older. `Invalid property "node"` or `Invalid property "devEngines.node"` means npm parsed a legacy `devEngines` object; inspect the npm debug log to identify its source rather than assuming it belongs to Machine Bridge. The published package declares both Node.js 26 and npm 12 in `engines`, and `machine-mcp doctor` checks both active versions. Keep `--omit=optional` in the install command. Without it npm may resolve optional `fsevents` and warn that its install script was not included in `allowScripts`; Machine Bridge does not require that development-time watcher at runtime.
82
94
 
83
95
  ## State-root safety and removal
84
96
 
package/docs/TESTING.md CHANGED
@@ -17,11 +17,12 @@ The suite includes:
17
17
  - generated Cloudflare Worker types and strict TypeScript checking, including unused-local and unused-parameter rejection;
18
18
  - recursive syntax validation for every JavaScript file under the shipped/runtime/test roots plus the shell wrapper;
19
19
  - shared tool-catalog schema, annotation, and profile-inventory checks;
20
- - default working-agreement injection without user files, bounded automatic project metadata, script-body non-disclosure, user-global opt-out, repository opt-out rejection, global `model_instructions_file` injection in stdio/remote initialization, hierarchical precedence, live project/skill rescanning and fingerprints, automatic task ranking/loading, command override/removal, direct argv handling, timeout ceilings, and execution-profile denial;
20
+ - default working-agreement injection without user files, bounded automatic project metadata, script-body non-disclosure, user-global opt-out, repository opt-out rejection, global `model_instructions_file` injection in stdio/remote initialization, hierarchical precedence, `.agents/skills` and `.codex/skills` compatibility discovery, live project/skill rescanning and fingerprints, automatic task ranking/loading with English inflection normalization, bounded Chinese/English intent aliases, capability-name weighting, and selected-skill instruction loading; automatic `package.*` commands with English/Chinese workflow-intent matching and Windows command-shim execution, explicit command override/removal, direct argv handling, timeout ceilings, runtime-keyed routing-telemetry privacy, and execution-profile denial;
21
21
  - concurrent complete-before-visible lock claims, atomic replacement under active readers, malformed-lock grace, snapshot/token-safe reclamation, absolute-age expiry, PID-reuse detection, and bounded startup-lock waiting;
22
22
  - foreground takeover of active and orphaned background daemons, legacy lock identification from canonicalized process arguments, foreground-process protection, actual-PID exit waiting, POSIX non-escalating timeout behavior, Windows verified-daemon stop semantics, daemon lock mode/version/process-start metadata, launchd service-target semantics, and silent idempotent duplicate service starts;
23
23
  - fail-closed service lifecycle ordering for provider-stop, all-workspace daemon-stop, and definition removal, including platform/daemon/removal failure injection and normalized macOS/systemd/Windows results;
24
- - machine-level browser-broker ownership/client proxying, authenticated extension origin/subprotocol, non-cacheable local pairing, pairing-token non-disclosure, resource-backed upload routing, broker result redaction, pre-registered runtime-handshake listeners, and bounded socket/HTTP waits;
24
+ - machine-level browser-broker ownership/client proxying, authenticated extension origin/subprotocol, non-cacheable local pairing, pairing-token non-disclosure, resource-backed upload routing, broker result redaction, pre-registered runtime-handshake listeners, bounded socket/HTTP waits, installed-application discovery caching, and name-based task matching;
25
+ - relay environment-proxy direct/bypass/agent selection, unsupported proxy rejection, fail-fast invalid configuration, and route observability without endpoint or credential disclosure;
25
26
  - canonical path and symbolic-link escape tests;
26
27
  - relative-path privacy and error-path redaction tests;
27
28
  - atomic create/update, optimistic hash, exact edit, and patch transaction tests;
@@ -31,7 +32,7 @@ The suite includes:
31
32
  - nested Git repository detection and helper suppression;
32
33
  - author-email privacy in `git_log`;
33
34
  - isolated command HOME/temp/cache behavior;
34
- - one-shot timeout, descendant process-group/tree termination including descendants that ignore graceful shutdown, cancellation, and process-session interaction;
35
+ - one-shot timeout, descendant process-group/tree termination including descendants that ignore graceful shutdown after the direct child has already exited, cancellation, and process-session interaction;
35
36
  - layered fixed runtime diagnostics for filesystem, direct process, shell, managed-job storage, and resource availability;
36
37
  - local resource CLI registration, permission checks, dynamic reload, state-path redaction, and content non-disclosure;
37
38
  - real Ed25519 and RSA generation, idempotent reuse, public/private correspondence, mode enforcement, incomplete/mismatched/symlink rejection, and private-content non-disclosure;
@@ -47,11 +48,11 @@ The suite includes:
47
48
  - shared no-follow bounded-file reads for normal files, over-limit data, directories, and symbolic links;
48
49
  - CLI parsing, policy profiles, and client configuration boundaries;
49
50
  - 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;
50
- - live local Worker OAuth registration, consent, PKCE, token replay rejection, throttling, CORS, protocol negotiation, 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.
51
+ - live local Worker OAuth registration, consent, URL-constructed `303` callbacks including the ChatGPT redirect URI and encoded state, PKCE, token replay rejection, throttling, CORS, protocol negotiation, 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.
51
52
 
52
53
  ## Opt-in live desktop and browser validation
53
54
 
54
- The normal suite uses deterministic mocks for macOS Accessibility and an authenticated in-process extension peer for browser routing. Before a release that changes local UI automation, run the macOS live smoke test on a machine where the invoking Node/terminal process has Accessibility permission:
55
+ The normal suite uses deterministic mocks for macOS Accessibility and an authenticated in-process extension peer for browser routing. Browser contract tests cover versioned extension handshakes, stale-extension rejection, compatible replacement without premature displacement, clean rejection of in-flight direct and proxied requests during replacement, keepalive handling, strict action/value validation, stable-ref validation, combined waits, tab commands, fixed trusted-input CDP sequences, attach/detach cleanup, semantic-ref stability, deterministic scrolling, obscured-target waiting, and stale-reference failure. Before a release that changes local UI automation, run the macOS live smoke test on a machine where the invoking Node/terminal process has Accessibility permission:
55
56
 
56
57
  ```sh
57
58
  npm run app-automation:live-test
@@ -59,7 +60,7 @@ npm run app-automation:live-test
59
60
 
60
61
  It opens Calculator, activates it through the fixed JXA helper, verifies structured JSON output, inspects main-window Accessibility controls with menu recursion disabled, clicks the `One` control, and quits the application. It is intentionally excluded from CI because macOS TCC permission and a graphical login session are host state.
61
62
 
62
- For browser changes, perform an isolated-profile smoke test with the packaged unpacked extension: pair it to a temporary source runtime, navigate a local no-store page, inspect controls, fill multiple fields, click a button, verify the live DOM, and capture a screenshot. A Playwright persistent Chromium context is an acceptable harness because it loads the real MV3 service worker while keeping the user's daily browser profile untouched.
63
+ For browser changes, perform an isolated-profile smoke test with the packaged unpacked extension: pair it to a temporary source runtime, navigate a local no-store page, inspect controls and reuse a returned `ref`, wait for an element state, fill multiple fields, exercise `input_mode: trusted` for click/key/text, verify the live DOM, create/activate/close a tab, and capture a screenshot. A Playwright persistent Chromium context is an acceptable harness because it loads the real MV3 service worker while keeping the user's daily browser profile untouched.
63
64
 
64
65
  ## Additional release checks
65
66
 
@@ -96,7 +97,7 @@ Run `npm run privacy:check` before committing and before packaging. Run and revi
96
97
 
97
98
  ## Package manifest
98
99
 
99
- `npm run package:test` executes a real silent `npm pack --dry-run --json`, requires clean parseable JSON, rejects sensitive local artifacts and credential-like file classes, validates every packaged mode as `0644` or `0755`, and verifies that privacy/engineering guidance, runtime/relay/secure-file/lock/service/browser/app modules, all package-script helpers, the packaged browser extension, contribution discipline, and privacy/release-impact checkers are present. `npm run install:test` packs the real tarball, installs it into an isolated global prefix with the documented npm 12 options, rejects blocked-script warnings, confirms optional `fsevents` is absent, and runs the installed CLI.
100
+ `npm run package:test` executes a real silent `npm pack --dry-run --json`, requires clean parseable JSON, rejects sensitive local artifacts and credential-like file classes, validates every packaged mode as `0644` or `0755`, and verifies that privacy/engineering guidance, runtime/relay/secure-file/lock/service/browser/app modules, all package-script helpers, the packaged browser extension, contribution discipline, and privacy/release-impact checkers are present. `npm run install:test` requires npm 12, installs the real tarball from a package-free directory into an isolated global prefix with the documented options, verifies the packaged npm engine requirement, rejects blocked-script warnings, confirms optional `fsevents` is absent, and runs the installed CLI. The test runs on Linux, macOS, and Windows CI.
100
101
 
101
102
  The stdio integration test also sends an oversized line, verifies bounded rejection, and confirms that the next valid request is still processed.
102
103
 
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "machine-bridge-mcp",
3
- "version": "0.12.2",
3
+ "version": "0.14.0",
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",
7
7
  "engines": {
8
- "node": ">=26.0.0"
8
+ "node": ">=26.0.0",
9
+ "npm": ">=12.0.0"
9
10
  },
10
11
  "bin": {
11
12
  "machine-bridge-mcp": "bin/machine-mcp.mjs",
@@ -42,7 +43,7 @@
42
43
  "worker:types": "wrangler types src/worker/worker-configuration.d.ts",
43
44
  "typecheck": "npm run worker:types && tsc -p tsconfig.json --noEmit",
44
45
  "syntax": "node scripts/syntax-check.mjs",
45
- "check": "npm run privacy:check && npm run privacy:test && npm run release-impact:check && npm run release-impact:test && npm run release-state:test && npm run release-ci:test && npm run network-retry:test && npm run relay:test && npm run secure-file:test && npm run architecture:test && npm run typecheck && npm run syntax && npm run process-lock:test && npm run service-lifecycle:test && npm run catalog:test && npm run agent-context:test && npm run browser-bridge:test && npm run app-automation:test && npm run self-test && npm run atomic-fs:test && npm run package:test && npm run ssh-key:test && npm run full-access:test && npm run managed-jobs:test && npm run stdio:integration-test && npm run worker:integration-test",
46
+ "check": "npm run privacy:check && npm run privacy:test && npm run release-impact:check && npm run release-impact:test && npm run release-state:test && npm run release-ci:test && npm run network-retry:test && npm run relay:test && npm run secure-file:test && npm run architecture:test && npm run typecheck && npm run syntax && npm run process-lock:test && npm run service-lifecycle:test && npm run catalog:test && npm run agent-context:test && npm run browser-command:test && npm run browser-devtools-input:test && npm run browser-page-automation:test && npm run browser-bridge:test && npm run app-automation:test && npm run self-test && npm run atomic-fs:test && npm run package:test && npm run ssh-key:test && npm run full-access:test && npm run managed-jobs:test && npm run stdio:integration-test && npm run worker:integration-test",
46
47
  "worker:dry-run": "wrangler deploy --dry-run",
47
48
  "worker:integration-test": "node tests/worker-integration-test.mjs",
48
49
  "release:check": "node scripts/github-release.mjs --check",
@@ -72,9 +73,14 @@
72
73
  "app-automation:test": "node tests/app-automation-test.mjs",
73
74
  "process-lock:test": "node tests/process-lock-test.mjs",
74
75
  "service-lifecycle:test": "node tests/service-lifecycle-test.mjs",
75
- "app-automation:live-test": "node tests/app-automation-test.mjs --live-macos"
76
+ "app-automation:live-test": "node tests/app-automation-test.mjs --live-macos",
77
+ "browser-command:test": "node tests/browser-command-test.mjs",
78
+ "browser-devtools-input:test": "node tests/browser-devtools-input-test.mjs",
79
+ "browser-page-automation:test": "node tests/browser-page-automation-test.mjs"
76
80
  },
77
81
  "dependencies": {
82
+ "https-proxy-agent": "9.1.0",
83
+ "proxy-from-env": "2.1.0",
78
84
  "wrangler": "4.110.0",
79
85
  "ws": "8.21.0"
80
86
  },
@@ -3,6 +3,7 @@ import { constants as fsConstants } from "node:fs";
3
3
  import { lstat, open, opendir, realpath, stat } from "node:fs/promises";
4
4
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
5
5
  import { createBuiltinInstruction, discoverAutomaticProjectInstruction } from "./default-instructions.mjs";
6
+ import { automaticPackageCommands, readProjectPackageMetadata } from "./project-package.mjs";
6
7
 
7
8
  const CONFIG_RELATIVE_PATH = join(".machine-bridge", "agent.json");
8
9
  const GLOBAL_CONFIG_RELATIVE_PATH = join(".config", "machine-bridge-mcp", "agent.json");
@@ -24,6 +25,35 @@ const MAX_COMMAND_ARGV = 128;
24
25
  const MAX_COMMAND_ARGUMENT_BYTES = 256 * 1024;
25
26
  const COMMAND_NAME_PATTERN = /^[a-z][a-z0-9._-]{0,63}$/;
26
27
  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", "及", "和", "的", "了", "在", "用", "使用", "进行", "根据"]);
28
+ const ENGLISH_TOKEN_CANONICAL = new Map([
29
+ ["created", "create"], ["creating", "create"], ["creation", "create"], ["creator", "create"],
30
+ ["improved", "improve"], ["improving", "improve"], ["improvement", "improve"],
31
+ ["installed", "install"], ["installing", "install"], ["installation", "install"], ["installer", "install"],
32
+ ["searched", "search"], ["searching", "search"], ["finder", "find"],
33
+ ["documentation", "docs"], ["document", "docs"], ["documents", "docs"],
34
+ ["validated", "verify"], ["validate", "verify"], ["validation", "verify"], ["verification", "verify"],
35
+ ["factcheck", "verify"], ["fact-check", "verify"], ["testing", "test"], ["tests", "test"],
36
+ ["building", "build"], ["built", "build"], ["deployment", "deploy"], ["deployed", "deploy"],
37
+ ]);
38
+ const HAN_TOKEN_ALIASES = Object.freeze([
39
+ [/创建|新建|编写/u, ["create", "creator"]],
40
+ [/改进|优化|更新|维护/u, ["improve", "update"]],
41
+ [/技能/u, ["skill"]],
42
+ [/安装/u, ["install", "installer"]],
43
+ [/部署/u, ["deploy", "deployment"]],
44
+ [/查找|搜索|检索/u, ["search", "find"]],
45
+ [/最新|当前/u, ["latest", "current"]],
46
+ [/官方/u, ["official"]],
47
+ [/文档|资料/u, ["docs", "documentation"]],
48
+ [/事实核查|核查|验证|校验/u, ["verify", "factcheck"]],
49
+ [/测试/u, ["test"]],
50
+ [/前端/u, ["frontend"]],
51
+ [/设计/u, ["design"]],
52
+ [/邮件|邮箱/u, ["email"]],
53
+ [/浏览器|网页|网站/u, ["browser", "web"]],
54
+ [/性能/u, ["performance"]],
55
+ [/安全|漏洞/u, ["security", "audit"]],
56
+ ]);
27
57
  const CONFIG_KEYS = new Set(["version", "builtin_instructions", "automatic_project_context", "model_instructions_file", "instruction_files", "instruction_max_bytes", "skill_roots", "commands"]);
28
58
  const COMMAND_KEYS = new Set(["description", "argv", "cwd", "timeout_seconds", "allow_extra_args"]);
29
59
 
@@ -117,7 +147,7 @@ export class AgentContextManager {
117
147
  .sort((a, b) => b.score - a.score || a.skill.name.localeCompare(b.skill.name))
118
148
  .slice(0, maxSkills);
119
149
  const commandMatches = [...state.commands.values()]
120
- .map((command) => ({ command, score: relevanceScore(task, `${command.name} ${command.description} ${command.argv.join(" ")}`, command.name) }))
150
+ .map((command) => ({ command, score: relevanceScore(task, commandMatchText(command), command.name) }))
121
151
  .filter((item) => item.score > 0)
122
152
  .sort((a, b) => b.score - a.score || a.command.name.localeCompare(b.command.name))
123
153
  .slice(0, 20);
@@ -127,7 +157,11 @@ export class AgentContextManager {
127
157
  const content = await readRegularUtf8(selected.entrypoint, MAX_SKILL_ENTRY_BYTES, "skill entrypoint");
128
158
  selectedSkill = { ...publicSkill(selected, this.displayPath), instructions: content.text };
129
159
  }
130
- const recommendedTools = recommendTools(task, commandMatches.length > 0, skillMatches.length > 0);
160
+ const recommendedTools = recommendTools(task, {
161
+ commandsAvailable: state.commands.size > 0,
162
+ commandRelevant: (commandMatches[0]?.score || 0) >= 3,
163
+ skillRelevant: Boolean(selected),
164
+ });
131
165
  const refresh = capabilityFingerprint(state, discovered.skills);
132
166
  return {
133
167
  task,
@@ -239,7 +273,7 @@ export class AgentContextManager {
239
273
  scopeRoot,
240
274
  instructionFiles: [...DEFAULT_INSTRUCTION_FILES],
241
275
  instructionMaxBytes: DEFAULT_INSTRUCTION_MAX_BYTES,
242
- skillRoots: defaultSkillRoots(directories, this.home, this.policy.unrestrictedPaths === true),
276
+ skillRoots: defaultSkillRoots(directories, this.home, this.codexHome, this.policy.unrestrictedPaths === true),
243
277
  commands: new Map(),
244
278
  builtinInstructionsEnabled: true,
245
279
  automaticProjectContextEnabled: true,
@@ -253,6 +287,9 @@ export class AgentContextManager {
253
287
  instructionsTruncated: false,
254
288
  };
255
289
 
290
+ const packageMetadata = await readProjectPackageMetadata(scopeRoot, () => this.throwIfCancelled(context));
291
+ state.commands = automaticPackageCommands(packageMetadata, scopeRoot);
292
+
256
293
  if (this.home) {
257
294
  const globalConfig = join(this.home, GLOBAL_CONFIG_RELATIVE_PATH);
258
295
  const config = await readOptionalConfig(globalConfig, this.home, false);
@@ -488,9 +525,14 @@ function directoriesBetween(root, leaf) {
488
525
  return result.reverse();
489
526
  }
490
527
 
491
- function defaultSkillRoots(directories, home, unrestricted) {
492
- const roots = [...directories].reverse().map((directory) => resolve(directory, ".agents", "skills"));
528
+ function defaultSkillRoots(directories, home, codexHome, unrestricted) {
529
+ const roots = [];
530
+ for (const directory of [...directories].reverse()) {
531
+ roots.push(resolve(directory, ".agents", "skills"));
532
+ roots.push(resolve(directory, ".codex", "skills"));
533
+ }
493
534
  if (unrestricted && home) roots.push(resolve(home, ".agents", "skills"));
535
+ if (unrestricted && codexHome) roots.push(resolve(codexHome, "skills"));
494
536
  if (unrestricted && process.platform !== "win32") roots.push(resolve("/etc/codex/skills"));
495
537
  return [...new Set(roots)];
496
538
  }
@@ -718,13 +760,19 @@ function capabilityFingerprint(state, skills) {
718
760
  }));
719
761
  }
720
762
 
763
+ function commandMatchText(command) {
764
+ return `${command.name} ${command.description} ${command.argv.join(" ")} ${command.searchTerms || ""}`;
765
+ }
766
+
721
767
  function relevanceScore(task, candidate, identity = "") {
722
768
  const taskTokens = tokenize(task);
723
769
  const candidateTokens = tokenize(candidate);
724
770
  if (!taskTokens.size || !candidateTokens.size) return 0;
771
+ const identityTokens = tokenize(identity);
725
772
  let score = 0;
726
773
  for (const token of taskTokens) {
727
774
  if (candidateTokens.has(token)) score += token.length >= 6 ? 2 : 1;
775
+ if (identityTokens.has(token)) score += token.length >= 6 ? 4 : 3;
728
776
  }
729
777
  const taskComparable = comparableText(task);
730
778
  const candidateComparable = comparableText(candidate);
@@ -747,33 +795,40 @@ function tokenize(value) {
747
795
  const tokens = new Set();
748
796
  for (const raw of text.match(/[a-z0-9_][a-z0-9_.-]{1,}/g) || []) {
749
797
  const token = raw.replace(/^[.-]+|[.-]+$/g, "");
750
- if (token.length >= 2 && !TOKEN_STOP_WORDS.has(token)) tokens.add(token);
751
- for (const part of token.split(/[._-]+/)) {
752
- if (part.length >= 2 && !TOKEN_STOP_WORDS.has(part)) tokens.add(part);
753
- }
798
+ addToken(tokens, token);
799
+ for (const part of token.split(/[._-]+/)) addToken(tokens, part);
754
800
  }
755
801
  for (const sequence of text.match(/[\p{Script=Han}]{1,}/gu) || []) {
756
- if (!TOKEN_STOP_WORDS.has(sequence)) tokens.add(sequence);
802
+ addToken(tokens, sequence);
757
803
  const minimumSize = sequence.length === 1 ? 1 : 2;
758
804
  for (let size = minimumSize; size <= Math.min(3, sequence.length); size += 1) {
759
- for (let index = 0; index + size <= sequence.length; index += 1) {
760
- const token = sequence.slice(index, index + size);
761
- if (!TOKEN_STOP_WORDS.has(token)) tokens.add(token);
762
- }
805
+ for (let index = 0; index + size <= sequence.length; index += 1) addToken(tokens, sequence.slice(index, index + size));
763
806
  }
764
807
  }
808
+ for (const [pattern, aliases] of HAN_TOKEN_ALIASES) {
809
+ if (!pattern.test(text)) continue;
810
+ for (const alias of aliases) addToken(tokens, alias);
811
+ }
765
812
  return tokens;
766
813
  }
767
814
 
768
- function recommendTools(task, hasCommands, hasSkills) {
815
+ function addToken(tokens, raw) {
816
+ const token = String(raw || "").replace(/^[.-]+|[.-]+$/g, "");
817
+ if (token.length < 2 || TOKEN_STOP_WORDS.has(token)) return;
818
+ tokens.add(token);
819
+ const canonical = ENGLISH_TOKEN_CANONICAL.get(token);
820
+ if (canonical) tokens.add(canonical);
821
+ }
822
+
823
+ function recommendTools(task, { commandsAvailable, commandRelevant, skillRelevant }) {
769
824
  const lower = task.toLowerCase();
770
825
  const tools = ["agent_context"];
771
- if (hasSkills) tools.push("load_local_skill");
772
- if (hasCommands) tools.push("run_local_command");
773
- if (/browser|chrome|edge|brave|网页|浏览器|表单|网站/.test(lower)) tools.push("browser_status", "browser_list_tabs", "browser_inspect_page", "browser_action", "browser_fill_form");
826
+ if (skillRelevant) tools.push("load_local_skill");
827
+ if (commandRelevant) tools.push("run_local_command");
828
+ if (/browser|chrome|edge|brave|网页|浏览器|表单|网站/.test(lower)) tools.push("browser_status", "browser_list_tabs", "browser_manage_tabs", "browser_inspect_page", "browser_wait", "browser_action", "browser_fill_form");
774
829
  if (/app|application|gui|window|应用|软件|窗口|界面/.test(lower)) tools.push("list_local_applications", "inspect_local_application", "operate_local_application");
775
830
  if (/git|commit|branch|diff|仓库|提交|分支/.test(lower)) tools.push("git_status", "git_diff");
776
- if (/test|build|lint|command|terminal|测试|构建|命令|终端/.test(lower)) tools.push(hasCommands ? "run_local_command" : "run_process");
831
+ if (/test|build|lint|command|terminal|测试|构建|命令|终端/.test(lower)) tools.push(commandsAvailable ? "run_local_command" : "run_process");
777
832
  if (/file|code|source|edit|write|文件|代码|源码|修改|写入/.test(lower)) tools.push("read_file", "search_text", "edit_file", "apply_patch");
778
833
  return [...new Set(tools)];
779
834
  }
@@ -828,6 +883,8 @@ function publicCommands(commands, displayPath) {
828
883
  timeout_seconds: command.timeoutSeconds,
829
884
  allow_extra_args: command.allowExtraArgs,
830
885
  source: displayPath(command.source),
886
+ source_type: command.sourceType || "agent-config",
887
+ ...(command.script ? { package_script: command.script } : {}),
831
888
  }));
832
889
  }
833
890