codex-computer-use-mcp 0.1.0 → 0.3.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.
- package/ARCHITECTURE.md +150 -0
- package/CONTRIBUTING.md +16 -10
- package/MIGRATION.md +94 -0
- package/PROOF.md +128 -54
- package/README.md +97 -115
- package/SECURITY.md +45 -22
- package/dist/audit.d.ts +15 -14
- package/dist/audit.js +1 -1
- package/dist/audit.js.map +1 -1
- package/dist/config.d.ts +8 -8
- package/dist/config.js +8 -103
- package/dist/config.js.map +1 -1
- package/dist/direct-broker.d.ts +71 -0
- package/dist/direct-broker.js +652 -0
- package/dist/direct-broker.js.map +1 -0
- package/dist/direct-service.d.ts +33 -0
- package/dist/direct-service.js +350 -0
- package/dist/direct-service.js.map +1 -0
- package/dist/lock.d.ts +1 -0
- package/dist/lock.js +32 -3
- package/dist/lock.js.map +1 -1
- package/dist/mcp-elicitation.d.ts +8 -0
- package/dist/mcp-elicitation.js +47 -0
- package/dist/mcp-elicitation.js.map +1 -0
- package/dist/mcp-server.js +43 -100
- package/dist/mcp-server.js.map +1 -1
- package/dist/system.d.ts +0 -1
- package/dist/system.js +12 -37
- package/dist/system.js.map +1 -1
- package/dist/tools.d.ts +84 -0
- package/dist/tools.js +262 -0
- package/dist/tools.js.map +1 -0
- package/docs/adr/0001-standalone-code-mode-mcp.md +45 -0
- package/integrations/pi/README.md +51 -27
- package/integrations/pi/index.ts +237 -125
- package/integrations/pi/mcp.json.example +3 -3
- package/npm-shrinkwrap.json +3275 -48
- package/package.json +41 -13
- package/dist/policy.d.ts +0 -42
- package/dist/policy.js +0 -428
- package/dist/policy.js.map +0 -1
- package/dist/prompt.d.ts +0 -62
- package/dist/prompt.js +0 -119
- package/dist/prompt.js.map +0 -1
- package/dist/runner.d.ts +0 -55
- package/dist/runner.js +0 -494
- package/dist/runner.js.map +0 -1
- package/dist/service.d.ts +0 -17
- package/dist/service.js +0 -382
- package/dist/service.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,192 +1,174 @@
|
|
|
1
1
|
# Codex Computer Use MCP
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Version 0.3.0 exposes the official signed macOS Computer Use capabilities as direct typed tools for Pi and MCP clients. The calling agent chooses every tool and argument itself.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The primary path has:
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
- per-app kernel locks;
|
|
13
|
-
- foreground/focus telemetry with fail-closed background guarantees;
|
|
14
|
-
- output schemas, call budgets, timeout and process-group cleanup;
|
|
15
|
-
- sanitized, mode-`0600` metadata audits.
|
|
7
|
+
- no nested model call;
|
|
8
|
+
- no model-generated action plan;
|
|
9
|
+
- no subagent;
|
|
10
|
+
- no prompt sent to Codex;
|
|
11
|
+
- no separate model-token usage.
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
It does use OpenAI's signed `codex app-server` as the official host for the bundled Computer Use MCP client. The current official `mcpServer/tool/call` API requires a loaded thread identifier, so the bridge creates an empty, in-memory, zero-turn context (`ephemeral: true`, `turns: []`, `path: null`). It never calls `turn/start` and fails closed if any `turn/*` or `item/*` model activity appears.
|
|
18
14
|
|
|
19
|
-
|
|
15
|
+
> **Independent project.** This is not an OpenAI product and is not endorsed by OpenAI. The app-server API is marked experimental and fixed ChatGPT bundle paths may change.
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
## Direct tools
|
|
22
18
|
|
|
23
|
-
|
|
19
|
+
Pi registers namespaced tools to avoid collisions with Pi's built-ins. The MCP server exposes the upstream method names.
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
| Pi tool | MCP method | No-permissions | Purpose |
|
|
22
|
+
|---|---|---:|---|
|
|
23
|
+
| `computer_use_list_apps` | `list_apps` | yes | List running and recently used apps |
|
|
24
|
+
| `computer_use_get_app_state` | `get_app_state` | yes | Read the key-window screenshot and accessibility tree |
|
|
25
|
+
| `computer_use_click` | `click` | yes | Click an element or screenshot coordinates |
|
|
26
|
+
| `computer_use_perform_secondary_action` | `perform_secondary_action` | yes | Invoke a named accessibility action |
|
|
27
|
+
| `computer_use_set_value` | `set_value` | yes | Assign an accessibility value |
|
|
28
|
+
| `computer_use_select_text` | `select_text` | yes | Select text or place the cursor |
|
|
29
|
+
| `computer_use_scroll` | `scroll` | yes | Scroll an element |
|
|
30
|
+
| `computer_use_drag` | `drag` | yes | Drag between screenshot coordinates |
|
|
31
|
+
| `computer_use_press_key` | `press_key` | yes | Send a key or key combination |
|
|
32
|
+
| `computer_use_type_text` | `type_text` | yes | Type literal text |
|
|
27
33
|
|
|
28
|
-
|
|
34
|
+
The MCP façade preserves the official signed helper's tool descriptions, input schemas, and annotations exactly. The Pi façade preserves its descriptions and input schemas; Pi's tool API does not expose MCP annotations. In particular, all ten MCP methods have `destructiveHint: false`; the wrapper does not conflate changing UI state with destructive behavior or add argument restrictions absent from the official schemas.
|
|
29
35
|
|
|
30
|
-
|
|
36
|
+
Pi 0.80.7 and newer registers all ten definitions but initially activates only `computer_use_list_apps` and `computer_use_get_app_state`. A successful `get_app_state` call additively activates the eight interaction definitions for the following model request. Other active Pi and extension tools remain active. The lazily activated tools omit `promptSnippet` and `promptGuidelines`, allowing Pi's native deferred-loading path to preserve the stable system-prompt prefix.
|
|
31
37
|
|
|
32
|
-
|
|
33
|
-
- Node.js 22 or newer
|
|
34
|
-
- the official ChatGPT macOS app at `/Applications/ChatGPT.app`
|
|
35
|
-
- a Codex account/session available to the app-bundled Codex CLI
|
|
36
|
-
- required first-party OpenAI app approvals and macOS privacy permissions, completed in their official UI
|
|
38
|
+
As the official contract specifies, Pi—not a nested planner—calls `computer_use_get_app_state` once per assistant turn before interacting with an app, then chooses and executes actions itself.
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
## Authorization policy: durable no-permissions
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
`no-permissions` has one precise meaning here: **the wrapper asks no permission questions and exposes all ten official actions**. It is the only mode and the durable default. There is no safe/full selector, config file, environment override, slash command, CLI switch, per-call elevation, app allowlist, intent classifier, task schema, per-action confirmation, special-case app policy, or method gate.
|
|
41
43
|
|
|
42
|
-
|
|
44
|
+
The app-server runtime uses the official Full access combination: `approvalPolicy: "never"` and `sandbox: "danger-full-access"`. In the pinned Codex host, that maps to a disabled permission profile and automatically accepts empty-schema MCP approval elicitations. Normal Computer Use app-access checks therefore proceed without a per-app prompt, just as they do in Codex Full access. The bridge does not edit the service's persistent per-bundle approval file. If app-server emits an elicitation instead of resolving it under Full access, the bridge still forwards it faithfully to the invoking client; unsupported clients return `cancel`, never a fabricated `accept` or `decline`.
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
npm install -g codex-computer-use-mcp
|
|
46
|
-
codex-computer-use-mcp --status
|
|
47
|
-
```
|
|
46
|
+
No-permissions does **not** bypass:
|
|
48
47
|
|
|
49
|
-
|
|
48
|
+
- macOS Screen Recording, Accessibility, or TCC controls;
|
|
49
|
+
- strict OpenAI Team ID and code-signature checks;
|
|
50
|
+
- exact upstream ten-tool schema verification;
|
|
51
|
+
- canonical app identity resolution and per-user/per-app kernel locks shared across Pi and MCP state roots;
|
|
52
|
+
- focus telemetry, timeouts, verified process-tree cleanup, or private audit logging.
|
|
50
53
|
|
|
51
|
-
|
|
52
|
-
git clone https://github.com/tmustier/codex-computer-use-mcp.git
|
|
53
|
-
cd codex-computer-use-mcp
|
|
54
|
-
npm ci
|
|
55
|
-
npm test
|
|
56
|
-
npm run build
|
|
57
|
-
node dist/mcp-server.js --status
|
|
58
|
-
```
|
|
54
|
+
## Why the signed app-server is required
|
|
59
55
|
|
|
60
|
-
|
|
56
|
+
Calling the signed `SkyComputerUseClient mcp` binary directly from an ordinary Pi/Node parent successfully initializes and lists all ten schemas, but real calls are rejected with:
|
|
61
57
|
|
|
62
|
-
|
|
58
|
+
```text
|
|
59
|
+
Computer Use server error -10000: Sender process is not authenticated
|
|
60
|
+
```
|
|
63
61
|
|
|
64
|
-
|
|
62
|
+
OpenAI's app-server exposes a documented `mcpServer/tool/call` endpoint. That endpoint calls a configured MCP tool directly; no model turn is required. Running it from the signed app-bundled binary preserves the official responsible-process/authentication chain without injection, re-signing, TCC changes, private socket emulation, or credential extraction.
|
|
65
63
|
|
|
66
|
-
|
|
67
|
-
|---|---|---|
|
|
68
|
-
| `list` | List apps visible to official Computer Use | Safe or full |
|
|
69
|
-
| `inspect` | Read one app | Full only |
|
|
70
|
-
| `act` | Perform a concrete app task | Full only |
|
|
71
|
-
| `dictionary_lookup` | Constrained local Apple Dictionary workflow | Full only |
|
|
64
|
+
See [`ARCHITECTURE.md`](ARCHITECTURE.md) for source links and the full restriction inventory.
|
|
72
65
|
|
|
73
|
-
|
|
66
|
+
## Requirements
|
|
74
67
|
|
|
75
|
-
|
|
68
|
+
- macOS
|
|
69
|
+
- Node.js 22 or newer
|
|
70
|
+
- Pi 0.80.7 or newer for native progressive tool disclosure
|
|
71
|
+
- official ChatGPT macOS app at `/Applications/ChatGPT.app`
|
|
72
|
+
- official Computer Use component installed
|
|
76
73
|
|
|
77
|
-
|
|
74
|
+
The direct bridge starts app-server with a new private `CODEX_HOME` containing no account credentials and only one configured MCP server: official Computer Use. It does not inherit the user's Codex MCP servers, plugins, history, memories, API keys, or auth file. It selects a non-websocket dummy model provider bound to unreachable loopback, disables plugin/remote-control features, and never starts a turn; this prevents app-server model prewarm or Responses API traffic.
|
|
78
75
|
|
|
79
|
-
|
|
76
|
+
### Locked-screen limitation
|
|
80
77
|
|
|
81
|
-
|
|
78
|
+
Version 0.3.0 supports direct local calls in an unlocked macOS session. It does not support window or accessibility actions after the Mac locks.
|
|
82
79
|
|
|
83
|
-
|
|
80
|
+
OpenAI's [locked Computer Use](https://developers.openai.com/codex/app/computer-use#use-computer-use-while-your-mac-is-locked) is limited to active, trusted ChatGPT turns started from a connected device. It does not authorize other apps or local processes to unlock the Mac. This package uses local zero-turn dispatch, so targeted calls can fail with official error `-10005` while the Mac is locked. Support for locked local use remains a follow-up and is not part of version 0.3.0.
|
|
84
81
|
|
|
85
|
-
|
|
82
|
+
## Pi integration
|
|
86
83
|
|
|
87
|
-
|
|
84
|
+
Install the exact release from npm:
|
|
88
85
|
|
|
89
|
-
|
|
86
|
+
```bash
|
|
87
|
+
pi install npm:codex-computer-use-mcp@0.3.0
|
|
88
|
+
```
|
|
90
89
|
|
|
91
|
-
|
|
90
|
+
To evaluate a source checkout instead:
|
|
92
91
|
|
|
93
92
|
```bash
|
|
94
|
-
|
|
93
|
+
npm ci
|
|
94
|
+
npm run build
|
|
95
|
+
pi -ne -e /absolute/path/to/codex-computer-use-mcp/integrations/pi/index.ts
|
|
95
96
|
```
|
|
96
97
|
|
|
97
|
-
|
|
98
|
+
Command:
|
|
98
99
|
|
|
99
|
-
```
|
|
100
|
-
|
|
100
|
+
```text
|
|
101
|
+
/computer-use-status
|
|
101
102
|
```
|
|
102
103
|
|
|
103
|
-
|
|
104
|
+
The native Pi adapter is the primary product path. It registers all ten typed tools directly, starts each session with the two inspection tools active, and additively activates the eight interaction tools after successful app inspection. It exposes no mode-changing command or wrapper-generated approval UI. Official app-access approval elicitations are resolved by Codex Full access before they reach Pi. Any elicitation that app-server does emit is shown through Pi's UI, and only the user's choice is returned to the service.
|
|
104
105
|
|
|
105
|
-
## MCP
|
|
106
|
+
## MCP server
|
|
106
107
|
|
|
107
|
-
|
|
108
|
+
Running the binary without arguments starts a stdio MCP server exposing the same ten direct methods plus `computer_use_status`:
|
|
108
109
|
|
|
109
110
|
```bash
|
|
110
|
-
|
|
111
|
+
node dist/mcp-server.js
|
|
111
112
|
```
|
|
112
113
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
### Pi MCP gateway
|
|
116
|
-
|
|
117
|
-
Alternatively add this to `~/.pi/agent/mcp.json`. `directTools: false` keeps the capability intentional instead of injecting it into every turn:
|
|
114
|
+
For Pi's generic MCP gateway, keep `directTools: false` so this powerful surface remains intentional:
|
|
118
115
|
|
|
119
116
|
```json
|
|
120
117
|
{
|
|
121
118
|
"mcpServers": {
|
|
122
|
-
"
|
|
123
|
-
"command": "
|
|
124
|
-
"args": ["
|
|
119
|
+
"computer-use": {
|
|
120
|
+
"command": "node",
|
|
121
|
+
"args": ["/absolute/path/to/codex-computer-use-mcp/dist/mcp-server.js"],
|
|
125
122
|
"lifecycle": "lazy",
|
|
126
|
-
"requestTimeoutMs":
|
|
123
|
+
"requestTimeoutMs": 180000,
|
|
127
124
|
"directTools": false
|
|
128
125
|
}
|
|
129
126
|
}
|
|
130
127
|
}
|
|
131
128
|
```
|
|
132
129
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
### Claude Desktop
|
|
136
|
-
|
|
137
|
-
```json
|
|
138
|
-
{
|
|
139
|
-
"mcpServers": {
|
|
140
|
-
"codex-computer-use": {
|
|
141
|
-
"command": "npx",
|
|
142
|
-
"args": ["-y", "codex-computer-use-mcp@0.1.0"]
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
For a source checkout, replace `npx` and its arguments with `node` and the absolute path to `dist/mcp-server.js`.
|
|
149
|
-
|
|
150
|
-
## How it works
|
|
130
|
+
The generic MCP server exposes the same no-permissions behavior: official Codex Full access, no wrapper permission gate, and all ten methods. App-access approvals are resolved inside the official host. Any standard form or URL elicitation that app-server emits is forwarded as an MCP `elicitation/create` request. The upstream client response is returned unchanged; unsupported or headless clients cancel rather than fabricate a decision.
|
|
151
131
|
|
|
152
|
-
|
|
153
|
-
MCP client
|
|
154
|
-
→ this local server
|
|
155
|
-
→ ChatGPT.app's signed app-bundled Codex CLI
|
|
156
|
-
→ signed SkyComputerUseClient
|
|
157
|
-
→ official Computer Use service
|
|
158
|
-
→ target macOS app
|
|
159
|
-
```
|
|
132
|
+
## Security and privacy
|
|
160
133
|
|
|
161
|
-
|
|
134
|
+
Each call:
|
|
162
135
|
|
|
163
|
-
|
|
136
|
+
1. validates typed arguments;
|
|
137
|
+
2. applies the single durable no-permissions policy with no mode or prompt branch;
|
|
138
|
+
3. resolves a target to a canonical installed bundle ID;
|
|
139
|
+
4. acquires a fixed per-user/per-app kernel lock shared across all supported clients and state roots;
|
|
140
|
+
5. starts global focus telemetry;
|
|
141
|
+
6. verifies fixed OpenAI-signed broker/client binaries;
|
|
142
|
+
7. starts a credential-free isolated app-server process tree with model transport disabled;
|
|
143
|
+
8. requires explicit `ephemeral: true`, `turns: []`, and `path: null` attestation;
|
|
144
|
+
9. verifies the exact upstream ten-tool inventory and schemas;
|
|
145
|
+
10. issues exactly one `mcpServer/tool/call`;
|
|
146
|
+
11. rejects any model-turn notification, including during teardown;
|
|
147
|
+
12. combines partial-preserving ancestry enumeration with private-working-directory ownership recovery, then freezes, terminates, and verifies the app-server plus separately grouped or reparented helpers; finally it removes temporary state, releases the lock, and writes a content-safe audit with separate broker/lease cleanup evidence.
|
|
164
148
|
|
|
165
|
-
|
|
149
|
+
Focus checks are detection/completion criteria, not a preventive macOS sandbox. If the target becomes frontmost, the call is reported as failed even though an individual official action may already have completed.
|
|
166
150
|
|
|
167
|
-
|
|
151
|
+
Tool results may contain visible target-app text or screenshots because that is the purpose of Computer Use. They return only to the invoking Pi/MCP client. Audits never retain arguments, typed values, screenshots, app-state payloads, result text, prompts, credentials, or tokens—only bounded metadata such as method, canonical/hashed app identity, byte counts, content types, outcome, focus, broker version, and zero-turn evidence.
|
|
168
152
|
|
|
169
|
-
|
|
153
|
+
## State and migration
|
|
170
154
|
|
|
171
|
-
|
|
155
|
+
Audit state defaults to `~/.direct-computer-use`; override with `CODEX_COMPUTER_USE_HOME`. Permission policy is not read from that agent-writable path: no-permissions is compiled as the only interface. Legacy `config.json` files are ignored.
|
|
172
156
|
|
|
173
|
-
|
|
174
|
-
- App updates may change bundled paths, CLI flags, plugin schemas, or signing layout.
|
|
175
|
-
- First-party approvals may need completion in an official interactive OpenAI session.
|
|
176
|
-
- Browser-host Computer Use is not exposed; this project targets native macOS apps.
|
|
177
|
-
- In full-permissions mode, a target/method/focus/cleanup failure is detected after streamed events; mutations may already have occurred. Inspect and restore manually before retrying.
|
|
178
|
-
- Secure config-path failures may prevent audit creation because the server refuses to write through an untrusted path.
|
|
157
|
+
See [`MIGRATION.md`](MIGRATION.md) for version 0.1 migration, source acceptance, rollback, and conflict avoidance.
|
|
179
158
|
|
|
180
159
|
## Development
|
|
181
160
|
|
|
182
161
|
```bash
|
|
183
162
|
npm ci
|
|
184
163
|
npm run check
|
|
164
|
+
npm run check:pi
|
|
185
165
|
npm test
|
|
186
166
|
npm run build
|
|
167
|
+
npm audit --omit=dev
|
|
168
|
+
npm pack --dry-run
|
|
187
169
|
```
|
|
188
170
|
|
|
189
|
-
|
|
171
|
+
Registry dependency tarballs are exact-pinned with integrity and the package includes `npm-shrinkwrap.json`.
|
|
190
172
|
|
|
191
173
|
See [`PROOF.md`](PROOF.md), [`SECURITY.md`](SECURITY.md), and [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
|
192
174
|
|
package/SECURITY.md
CHANGED
|
@@ -2,37 +2,60 @@
|
|
|
2
2
|
|
|
3
3
|
## Reporting a vulnerability
|
|
4
4
|
|
|
5
|
-
Do not open a public issue for a vulnerability that could weaken
|
|
5
|
+
Do not open a public issue for a vulnerability that could weaken signing, contract verification, no-permissions dispatch, first-party access handling, app identity, locking, focus telemetry, cleanup, or audit integrity.
|
|
6
6
|
|
|
7
|
-
Use GitHub
|
|
7
|
+
Use GitHub private vulnerability reporting. Include the affected commit/version, the smallest non-sensitive reproduction, expected versus observed behavior, and whether an official action completed before failure.
|
|
8
8
|
|
|
9
|
-
Never include credentials, tokens, private
|
|
9
|
+
Never include credentials, tokens, customer/private content, sensitive screenshots, raw app-state payloads, elicitation contents, or private audit files.
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Architecture boundary
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Raw Pi/Node invocation of the signed Computer Use MCP helper is not an authenticated responsible-process path for real calls. The bridge therefore uses the official signed app-server `mcpServer/tool/call` API.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
The API requires a loaded thread ID. The bridge creates an empty, pathless, ephemeral context solely to own the MCP runtime and requires the response to explicitly attest `ephemeral: true`, `path: null`, and `turns: []` before dispatch. It never calls `turn/start`; any `turn/*` or `item/*` event—including one received during teardown—is a fatal architecture violation. The temporary `CODEX_HOME` contains no auth file, API key, user MCPs, plugins, history, memories, or account configuration. A non-websocket dummy model provider points at unreachable loopback, and plugin/remote-control features are disabled, preventing app-server model prewarm and Responses API traffic.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
- full-permissions mode is broad wrapper authorization, not a preventive per-app sandbox;
|
|
19
|
-
- post-dispatch validation can fail completion and surface cleanup risk, but cannot undo a mutation;
|
|
20
|
-
- official OpenAI app approvals, sensitive-action prompts, and macOS privacy controls remain authoritative.
|
|
17
|
+
This is direct tool dispatch—not model orchestration.
|
|
21
18
|
|
|
22
19
|
## Security invariants
|
|
23
20
|
|
|
24
|
-
1. Only
|
|
25
|
-
2. Both binaries
|
|
26
|
-
3.
|
|
27
|
-
4.
|
|
28
|
-
5.
|
|
29
|
-
6. The
|
|
30
|
-
7.
|
|
31
|
-
8. Same-app work
|
|
32
|
-
9.
|
|
33
|
-
10.
|
|
34
|
-
11.
|
|
21
|
+
1. Only fixed app-bundled Codex and Computer Use client paths are allowed in production.
|
|
22
|
+
2. Both binaries pass strict code-signature verification and OpenAI Team ID `2DC432GLL2` checks before dispatch.
|
|
23
|
+
3. The helper's exact ten method names and input schemas match the pinned expected inventory before every call; release tests also verify its descriptions and annotations.
|
|
24
|
+
4. `no-permissions` is the only wrapper policy: all ten methods are exposed and no wrapper permission prompt is opened.
|
|
25
|
+
5. There is no config file, environment override, command, tool argument, per-call branch, or alternate safe/full route that an agent can select.
|
|
26
|
+
6. App-server uses the official Full access combination, `approvalPolicy: "never"` plus `sandbox: "danger-full-access"`. The pinned Codex host automatically accepts empty-schema MCP approval elicitations, so normal first-party app-access checks proceed without prompts. The wrapper does not synthesize this response or edit persistent per-app approvals. Any elicitation app-server emits is forwarded faithfully; an unavailable client cancels.
|
|
27
|
+
7. Target selectors resolve to canonical installed bundle IDs before dispatch.
|
|
28
|
+
8. Same-app work is excluded across native Pi, generic MCP, and custom state roots with one fixed per-user kernel `lockf` lease namespace.
|
|
29
|
+
9. Target focus events, periodic samples, watcher health, queued ASN resolution, and final state are checked. This is post-action detection, not a preventive OS sandbox.
|
|
30
|
+
10. One direct request emits one official `mcpServer/tool/call`; no model turn, subagent, shell, web, plugin, prompt, or reachable model transport is available.
|
|
31
|
+
11. App-server and helper share one private per-call working directory. Cleanup combines strict ancestry enumeration (preserving partial results) with working-directory ownership recovery, freezes processes to a stable set, kills every owned process, awaits stdio closure, and verifies exit. This still finds a helper reparented by an early app-server exit; enumeration/freeze/exit uncertainty is fatal.
|
|
32
|
+
12. Protocol JSONL is bounded before an unterminated line can exceed 8 MB in memory.
|
|
33
|
+
13. Per-call `CODEX_HOME` and work directories are mode-private and recursively removed.
|
|
34
|
+
14. Only validated text/image result blocks cross to the invoking client. No full-result spill file is written.
|
|
35
|
+
15. Audits contain metadata only, including separate `brokerCleanupVerified` and `appLeaseReleased` evidence; lease-release failure changes the audited outcome before surfacing. Arguments, values, screenshots, app-state text, result content, prompts, approvals, credentials, and tokens are forbidden.
|
|
36
|
+
16. Policy rejections are audited; audit failure is fatal once a secure state path exists.
|
|
37
|
+
17. Runtime and development dependency tarballs are exact-pinned with integrity in `npm-shrinkwrap.json`.
|
|
38
|
+
|
|
39
|
+
## Permission semantics
|
|
40
|
+
|
|
41
|
+
### No-permissions
|
|
42
|
+
|
|
43
|
+
All ten official methods and arbitrary resolvable app targets are available without wrapper permission prompts. The name means “ask no permission,” not “disable tools.” This is broad authorization. It does not imply that actions are reversible or that focus detection can prevent an already dispatched action.
|
|
44
|
+
|
|
45
|
+
The mode is compiled as the sole policy. Agent-writable audit state and legacy configuration files cannot change it, and there is no CLI/slash/tool/environment mode selector.
|
|
46
|
+
|
|
47
|
+
## Elicitations
|
|
48
|
+
|
|
49
|
+
The bridge starts app-server with `approvalPolicy: "never"` and `sandbox: "danger-full-access"`. This is Codex's Full access policy, not a wrapper-side approximation. Codex 0.144.2 maps the sandbox value to a disabled permission profile and automatically accepts empty-schema MCP approval elicitations before they are emitted to the client. That covers normal Computer Use app-access prompts without mutating the service's persistent approval file.
|
|
50
|
+
|
|
51
|
+
If app-server emits a form, OpenAI-form, or URL elicitation, Pi renders it and stdio MCP forwards supported standard modes through `elicitation/create`. The wrapper preserves the user/client response. Missing UI or an unsupported client produces `cancel`, never a fabricated decision.
|
|
52
|
+
|
|
53
|
+
## Visible content
|
|
54
|
+
|
|
55
|
+
Computer Use returns the official app-state text and screenshots to the invoking client. The wrapper does not inspect, classify, redact, or use that content to narrow the actions authorized by the user's request, and it never writes the content to its audit.
|
|
35
56
|
|
|
36
57
|
## Supported versions
|
|
37
58
|
|
|
38
|
-
Only the latest release is supported.
|
|
59
|
+
Only the latest approved release is supported. Version 0.3.0 supports direct local calls in an unlocked macOS session. Targeted local calls while the Mac is locked are not supported.
|
|
60
|
+
|
|
61
|
+
App-server is experimental and bundle paths or schemas can change. Drift fails closed until reviewed.
|
package/dist/audit.d.ts
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
|
+
import type { PermissionMode } from "./config.ts";
|
|
2
|
+
import type { DirectMethod } from "./tools.ts";
|
|
1
3
|
export interface AuditRecord {
|
|
2
4
|
timestamp: string;
|
|
3
5
|
runId: string;
|
|
4
|
-
|
|
5
|
-
permissionMode:
|
|
6
|
+
method: DirectMethod | "invalid_request";
|
|
7
|
+
permissionMode: PermissionMode;
|
|
6
8
|
app: string | null;
|
|
7
9
|
mutating: boolean;
|
|
8
|
-
|
|
9
|
-
userConfirmed: boolean;
|
|
10
|
-
authorization: "none" | "full_permissions_config";
|
|
10
|
+
authorization: "no_permissions_unrestricted" | "none";
|
|
11
11
|
inputBytes: number;
|
|
12
12
|
outcome: string;
|
|
13
13
|
durationMs: number;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
computerUseCalls: number;
|
|
21
|
-
computerUseMethods: string[];
|
|
14
|
+
brokerVersion: string | null;
|
|
15
|
+
clientBuild: string | null;
|
|
16
|
+
directCalls: number;
|
|
17
|
+
modelTurnsStarted: number;
|
|
18
|
+
ephemeralThread: boolean | null;
|
|
19
|
+
elicitationRequests: number;
|
|
22
20
|
backgroundPreserved: boolean | null;
|
|
23
|
-
|
|
21
|
+
brokerCleanupVerified: boolean;
|
|
22
|
+
appLeaseReleased: boolean;
|
|
23
|
+
resultContentTypes: string[];
|
|
24
|
+
resultBytes: number;
|
|
24
25
|
}
|
|
25
26
|
export declare function appendAudit(stateDir: string, record: AuditRecord, fileName?: string): Promise<string>;
|
package/dist/audit.js
CHANGED
|
@@ -17,7 +17,7 @@ async function ensurePrivateDirectory(directory) {
|
|
|
17
17
|
}
|
|
18
18
|
await chmod(directory, 0o700);
|
|
19
19
|
}
|
|
20
|
-
export async function appendAudit(stateDir, record, fileName = "
|
|
20
|
+
export async function appendAudit(stateDir, record, fileName = "direct-computer-use.jsonl") {
|
|
21
21
|
if (path.basename(fileName) !== fileName)
|
|
22
22
|
throw new Error("Audit filename must not contain a path");
|
|
23
23
|
await ensurePrivateDirectory(stateDir);
|
package/dist/audit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,IAAI,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,IAAI,MAAM,WAAW,CAAC;AA4B7B,KAAK,UAAU,sBAAsB,CAAC,SAAiB;IACtD,IAAI,CAAC;QACJ,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,CAAC;QACpC,IAAI,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACvH,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACrB,IAAI,KAAK,EAAE,IAAI,KAAK,QAAQ;YAAE,MAAM,KAAK,CAAC;QAC1C,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACzD,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,CAAC;QACpC,IAAI,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACvH,CAAC;IACD,MAAM,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAChC,QAAgB,EAChB,MAAmB,EACnB,QAAQ,GAAG,2BAA2B;IAEtC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IACpG,MAAM,sBAAsB,CAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC9C,MAAM,sBAAsB,CAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAChD,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,GAAG,SAAS,CAAC,OAAO,GAAG,SAAS,CAAC,QAAQ,GAAG,SAAS,CAAC,UAAU,CAAC;IACjG,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;IACnD,IAAI,CAAC;QACJ,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QACjC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;QAC3E,MAAM,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1B,MAAM,MAAM,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC9D,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;IACrB,CAAC;YAAS,CAAC;QACV,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;IACD,OAAO,SAAS,CAAC;AAClB,CAAC"}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
export type PermissionMode = "no-permissions";
|
|
2
2
|
export interface ExtensionConfig {
|
|
3
|
-
version:
|
|
3
|
+
version: 2;
|
|
4
4
|
permissionMode: PermissionMode;
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* The direct interface has one durable policy: no wrapper permission prompts and
|
|
8
|
+
* no wrapper method restrictions. There is intentionally no file, environment,
|
|
9
|
+
* command, tool argument, or per-call override that can select another mode.
|
|
10
|
+
*/
|
|
6
11
|
export declare const DEFAULT_CONFIG: ExtensionConfig;
|
|
7
|
-
export declare
|
|
8
|
-
constructor(message: string);
|
|
9
|
-
}
|
|
10
|
-
export declare function configPath(stateDir: string): string;
|
|
11
|
-
export declare function loadConfig(stateDir: string): Promise<ExtensionConfig>;
|
|
12
|
-
export declare function saveConfig(stateDir: string, config: ExtensionConfig): Promise<string>;
|
|
12
|
+
export declare function loadConfig(): Promise<ExtensionConfig>;
|
package/dist/config.js
CHANGED
|
@@ -1,105 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
export function configPath(stateDir) {
|
|
12
|
-
return path.join(stateDir, "config.json");
|
|
13
|
-
}
|
|
14
|
-
function parseConfig(value) {
|
|
15
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
16
|
-
throw new ConfigError("Computer Use config must be an object");
|
|
17
|
-
const record = value;
|
|
18
|
-
if (Object.keys(record).some((key) => !["version", "permissionMode"].includes(key))) {
|
|
19
|
-
throw new ConfigError("Computer Use config contains unknown keys");
|
|
20
|
-
}
|
|
21
|
-
if (record.version !== 1)
|
|
22
|
-
throw new ConfigError("Unsupported Computer Use config version");
|
|
23
|
-
if (record.permissionMode !== "safe" && record.permissionMode !== "full-permissions") {
|
|
24
|
-
throw new ConfigError("permissionMode must be safe or full-permissions");
|
|
25
|
-
}
|
|
26
|
-
return { version: 1, permissionMode: record.permissionMode };
|
|
27
|
-
}
|
|
28
|
-
async function inspectStateDirectory(stateDir, create) {
|
|
29
|
-
try {
|
|
30
|
-
const info = await lstat(stateDir);
|
|
31
|
-
if (info.isSymbolicLink() || !info.isDirectory())
|
|
32
|
-
throw new ConfigError("Computer Use state must be a non-symlink directory");
|
|
33
|
-
if ((info.mode & 0o022) !== 0)
|
|
34
|
-
throw new ConfigError("Computer Use state directory must not be group/world writable");
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
catch (error) {
|
|
38
|
-
if (error?.code !== "ENOENT")
|
|
39
|
-
throw error;
|
|
40
|
-
if (!create)
|
|
41
|
-
return false;
|
|
42
|
-
await mkdir(stateDir, { recursive: true, mode: 0o700 });
|
|
43
|
-
const info = await lstat(stateDir);
|
|
44
|
-
if (info.isSymbolicLink() || !info.isDirectory())
|
|
45
|
-
throw new ConfigError("Computer Use state must be a non-symlink directory");
|
|
46
|
-
await chmod(stateDir, 0o700);
|
|
47
|
-
return true;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
export async function loadConfig(stateDir) {
|
|
51
|
-
if (!(await inspectStateDirectory(stateDir, false)))
|
|
52
|
-
return { ...DEFAULT_CONFIG };
|
|
53
|
-
const file = configPath(stateDir);
|
|
54
|
-
let handle;
|
|
55
|
-
try {
|
|
56
|
-
handle = await open(file, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
57
|
-
const info = await handle.stat();
|
|
58
|
-
if (!info.isFile())
|
|
59
|
-
throw new ConfigError("Computer Use config must be a regular non-symlink file");
|
|
60
|
-
if ((info.mode & 0o777) !== 0o600)
|
|
61
|
-
throw new ConfigError("Computer Use config must have mode 0600");
|
|
62
|
-
return parseConfig(JSON.parse(await handle.readFile("utf8")));
|
|
63
|
-
}
|
|
64
|
-
catch (error) {
|
|
65
|
-
if (error?.code === "ENOENT")
|
|
66
|
-
return { ...DEFAULT_CONFIG };
|
|
67
|
-
if (error instanceof ConfigError)
|
|
68
|
-
throw error;
|
|
69
|
-
throw new ConfigError("Could not read the Computer Use config");
|
|
70
|
-
}
|
|
71
|
-
finally {
|
|
72
|
-
await handle?.close().catch(() => undefined);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
export async function saveConfig(stateDir, config) {
|
|
76
|
-
const validated = parseConfig(config);
|
|
77
|
-
await inspectStateDirectory(stateDir, true);
|
|
78
|
-
await chmod(stateDir, 0o700);
|
|
79
|
-
const destination = configPath(stateDir);
|
|
80
|
-
const temporary = path.join(stateDir, `.config.${process.pid}.${Date.now()}.tmp`);
|
|
81
|
-
let handle;
|
|
82
|
-
try {
|
|
83
|
-
const flags = constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW;
|
|
84
|
-
handle = await open(temporary, flags, 0o600);
|
|
85
|
-
await handle.chmod(0o600);
|
|
86
|
-
await handle.writeFile(`${JSON.stringify(validated, null, 2)}\n`, "utf8");
|
|
87
|
-
await handle.sync();
|
|
88
|
-
await handle.close();
|
|
89
|
-
handle = undefined;
|
|
90
|
-
await rename(temporary, destination);
|
|
91
|
-
const directory = await open(stateDir, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
92
|
-
try {
|
|
93
|
-
await directory.sync();
|
|
94
|
-
}
|
|
95
|
-
finally {
|
|
96
|
-
await directory.close();
|
|
97
|
-
}
|
|
98
|
-
return destination;
|
|
99
|
-
}
|
|
100
|
-
finally {
|
|
101
|
-
await handle?.close().catch(() => undefined);
|
|
102
|
-
await rm(temporary, { force: true });
|
|
103
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* The direct interface has one durable policy: no wrapper permission prompts and
|
|
3
|
+
* no wrapper method restrictions. There is intentionally no file, environment,
|
|
4
|
+
* command, tool argument, or per-call override that can select another mode.
|
|
5
|
+
*/
|
|
6
|
+
export const DEFAULT_CONFIG = { version: 2, permissionMode: "no-permissions" };
|
|
7
|
+
export async function loadConfig() {
|
|
8
|
+
return { ...DEFAULT_CONFIG };
|
|
104
9
|
}
|
|
105
10
|
//# sourceMappingURL=config.js.map
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAOA;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAoB,EAAE,OAAO,EAAE,CAAC,EAAE,cAAc,EAAE,gBAAgB,EAAE,CAAC;AAEhG,MAAM,CAAC,KAAK,UAAU,UAAU;IAC/B,OAAO,EAAE,GAAG,cAAc,EAAE,CAAC;AAC9B,CAAC"}
|