codex-computer-use-mcp 0.3.4 → 0.5.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/README.md +39 -117
- package/SECURITY.md +5 -63
- package/dist/audit.d.ts +1 -7
- package/dist/audit.js +4 -12
- package/dist/audit.js.map +1 -1
- package/dist/code-executor.d.ts +23 -0
- package/dist/code-executor.js +216 -0
- package/dist/code-executor.js.map +1 -0
- package/dist/code-worker.d.ts +1 -0
- package/dist/code-worker.js +120 -0
- package/dist/code-worker.js.map +1 -0
- package/dist/direct-broker.d.ts +18 -30
- package/dist/direct-broker.js +87 -161
- package/dist/direct-broker.js.map +1 -1
- package/dist/direct-service.d.ts +13 -21
- package/dist/direct-service.js +82 -299
- package/dist/direct-service.js.map +1 -1
- package/dist/mcp-elicitation.d.ts +1 -2
- package/dist/mcp-elicitation.js +18 -23
- package/dist/mcp-elicitation.js.map +1 -1
- package/dist/mcp-server.js +34 -38
- package/dist/mcp-server.js.map +1 -1
- package/dist/session-executor.d.ts +22 -0
- package/dist/session-executor.js +89 -0
- package/dist/session-executor.js.map +1 -0
- package/dist/tools.d.ts +743 -81
- package/dist/tools.js +55 -178
- package/dist/tools.js.map +1 -1
- package/dist/version.d.ts +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/integrations/pi/index.ts +146 -261
- package/package.json +13 -26
- package/ARCHITECTURE.md +0 -162
- package/CHANGELOG.md +0 -19
- package/CONTRIBUTING.md +0 -33
- package/MIGRATION.md +0 -134
- package/PROOF.md +0 -166
- package/dist/config.d.ts +0 -12
- package/dist/config.js +0 -10
- package/dist/config.js.map +0 -1
- package/dist/lock.d.ts +0 -17
- package/dist/lock.js +0 -137
- package/dist/lock.js.map +0 -1
- package/dist/system.d.ts +0 -25
- package/dist/system.js +0 -213
- package/dist/system.js.map +0 -1
- package/docs/adr/0001-standalone-code-mode-mcp.md +0 -45
- package/integrations/pi/README.md +0 -73
- package/integrations/pi/mcp.json.example +0 -15
- package/npm-shrinkwrap.json +0 -4827
package/integrations/pi/index.ts
CHANGED
|
@@ -1,237 +1,128 @@
|
|
|
1
|
+
import { mkdtemp, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
1
3
|
import path from "node:path";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_MAX_BYTES,
|
|
6
|
+
DEFAULT_MAX_LINES,
|
|
7
|
+
formatSize,
|
|
8
|
+
getAgentDir,
|
|
9
|
+
truncateHead,
|
|
10
|
+
type ExtensionAPI,
|
|
11
|
+
} from "@earendil-works/pi-coding-agent";
|
|
4
12
|
import { Text } from "@earendil-works/pi-tui";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
import { getDirectStatus } from "../../dist/direct-service.js";
|
|
7
15
|
import {
|
|
8
|
-
createOfficialDirectToolSession,
|
|
9
16
|
type DirectBrokerElicitationRequest,
|
|
10
17
|
type DirectBrokerElicitationResponse,
|
|
11
|
-
type OfficialDirectToolSession,
|
|
12
18
|
} from "../../dist/direct-broker.js";
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
to_y: Type.Number({ description: "End Y coordinate" }),
|
|
58
|
-
}, { additionalProperties: false }),
|
|
59
|
-
press_key: Type.Object({ app: App, key: Type.String({ description: "Key or key combination to press" }) }, { additionalProperties: false }),
|
|
60
|
-
type_text: Type.Object({ app: App, text: Type.String({ description: "Literal text to type" }) }, { additionalProperties: false }),
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
function toolDescription(method: DirectMethod): string {
|
|
64
|
-
return OFFICIAL_TOOL_METADATA[method].description;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function titleFor(method: DirectMethod): string {
|
|
68
|
-
return method.split("_").map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`).join(" ");
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const INSPECTION_METHODS = new Set<DirectMethod>(["list_apps", "get_app_state"]);
|
|
72
|
-
export const INSPECTION_TOOL_NAMES = [
|
|
73
|
-
"computer_use_list_apps",
|
|
74
|
-
"computer_use_get_app_state",
|
|
75
|
-
] as const;
|
|
76
|
-
export const INTERACTION_TOOL_NAMES = [
|
|
77
|
-
"computer_use_click",
|
|
78
|
-
"computer_use_perform_secondary_action",
|
|
79
|
-
"computer_use_set_value",
|
|
80
|
-
"computer_use_select_text",
|
|
81
|
-
"computer_use_scroll",
|
|
82
|
-
"computer_use_drag",
|
|
83
|
-
"computer_use_press_key",
|
|
84
|
-
"computer_use_type_text",
|
|
85
|
-
] as const;
|
|
86
|
-
|
|
87
|
-
export const COMPUTER_USE_TOOL_NAMES = [
|
|
88
|
-
...INSPECTION_TOOL_NAMES,
|
|
89
|
-
...INTERACTION_TOOL_NAMES,
|
|
90
|
-
] as const;
|
|
91
|
-
|
|
92
|
-
export function setInitialComputerUseTools(pi: Pick<ExtensionAPI, "getActiveTools" | "setActiveTools">): void {
|
|
93
|
-
pi.setActiveTools([...new Set([...pi.getActiveTools(), ...COMPUTER_USE_TOOL_NAMES])]);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
interface SessionExecutorDependencies {
|
|
97
|
-
createSession?: typeof createOfficialDirectToolSession;
|
|
98
|
-
executeTool?: typeof executeDirectTool;
|
|
19
|
+
import { ComputerUseCodeExecutor } from "../../dist/code-executor.js";
|
|
20
|
+
import { type JsonObject } from "../../dist/tools.js";
|
|
21
|
+
|
|
22
|
+
const jsonObjectSchema = z.record(z.string(), z.json());
|
|
23
|
+
const codeParameters = {
|
|
24
|
+
type: "object",
|
|
25
|
+
additionalProperties: false,
|
|
26
|
+
required: ["code"],
|
|
27
|
+
properties: {
|
|
28
|
+
code: {
|
|
29
|
+
type: "string",
|
|
30
|
+
description: "JavaScript body to execute. Use await sky.<method>(args), emit(value), emitImage(screenshot), and store for state shared across calls.",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
} as const;
|
|
34
|
+
|
|
35
|
+
const codeDescription = `Run JavaScript that composes OpenAI's official signed macOS Computer Use methods in one call. No nested model is used.
|
|
36
|
+
|
|
37
|
+
Available globals:
|
|
38
|
+
- sky.list_apps() -> text app inventory
|
|
39
|
+
- sky.get_app_state({ app, disableDiff? }) -> { app, text, screenshot }
|
|
40
|
+
- sky.click({ app, element_index?, x?, y?, mouse_button?, click_count? })
|
|
41
|
+
- sky.perform_secondary_action({ app, element_index, action })
|
|
42
|
+
- sky.set_value({ app, element_index, value })
|
|
43
|
+
- sky.select_text({ app, element_index, text, prefix?, suffix?, selection? })
|
|
44
|
+
- sky.scroll({ app, element_index, direction, pages? })
|
|
45
|
+
- sky.drag({ app, from_x, from_y, to_x, to_y })
|
|
46
|
+
- sky.press_key({ app, key })
|
|
47
|
+
- sky.type_text({ app, text })
|
|
48
|
+
- emit(value) returns text or JSON to Pi
|
|
49
|
+
- emitImage(state.screenshot) returns a screenshot to Pi
|
|
50
|
+
- store is a persistent JSON object shared across calls
|
|
51
|
+
|
|
52
|
+
get_app_state may return an accessibility-tree diff after the first inspection. Pass disableDiff: true when you need a fresh full tree.
|
|
53
|
+
|
|
54
|
+
Example:
|
|
55
|
+
const state = await sky.get_app_state({ app: "TextEdit" });
|
|
56
|
+
emit(state.text);
|
|
57
|
+
|
|
58
|
+
Batch known actions sequentially, then inspect again before deciding the next step.`;
|
|
59
|
+
|
|
60
|
+
interface PiContentResult {
|
|
61
|
+
content: Array<{ type: "text"; text: string } | { type: "image"; data: string; mimeType: string }>;
|
|
62
|
+
fullOutputPath?: string;
|
|
99
63
|
}
|
|
100
64
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
private async closeSession(): Promise<void> {
|
|
112
|
-
const session = this.session;
|
|
113
|
-
this.session = undefined;
|
|
114
|
-
await session?.close();
|
|
65
|
+
export async function toPiContent(content: JsonObject[]): Promise<PiContentResult> {
|
|
66
|
+
const textBlocks = content.filter((block) => block.type !== "image").map((block) => String(block.text ?? ""));
|
|
67
|
+
const fullText = textBlocks.join("\n\n");
|
|
68
|
+
const aggregate = truncateHead(fullText, { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES });
|
|
69
|
+
if (!aggregate.truncated) {
|
|
70
|
+
return {
|
|
71
|
+
content: content.map((block) => block.type === "image"
|
|
72
|
+
? { type: "image", data: String(block.data), mimeType: String(block.mimeType) }
|
|
73
|
+
: { type: "text", text: String(block.text ?? "") }),
|
|
74
|
+
};
|
|
115
75
|
}
|
|
116
76
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
await previous;
|
|
126
|
-
try {
|
|
127
|
-
const executeTool = this.dependencies.executeTool ?? executeDirectTool;
|
|
128
|
-
if (method === "list_apps") return await executeTool({ method, arguments: params }, dependencies);
|
|
129
|
-
if (method === "get_app_state") {
|
|
130
|
-
await this.closeSession();
|
|
131
|
-
const createSession = this.dependencies.createSession ?? createOfficialDirectToolSession;
|
|
132
|
-
try {
|
|
133
|
-
const response = await executeTool(
|
|
134
|
-
{ method, arguments: params },
|
|
135
|
-
{
|
|
136
|
-
...dependencies,
|
|
137
|
-
callTool: async (directMethod, args, options) => {
|
|
138
|
-
this.session = await createSession({ supportsOpenAiFormElicitation: true });
|
|
139
|
-
return this.session.call(directMethod, args, options);
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
);
|
|
143
|
-
if (response.isError) await this.closeSession();
|
|
144
|
-
return response;
|
|
145
|
-
} catch (error) {
|
|
146
|
-
await this.closeSession();
|
|
147
|
-
throw error;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
const session = this.session;
|
|
151
|
-
try {
|
|
152
|
-
return await executeTool(
|
|
153
|
-
{ method, arguments: params },
|
|
154
|
-
session ? {
|
|
155
|
-
...dependencies,
|
|
156
|
-
callTool: async (directMethod, args, options) => {
|
|
157
|
-
const result = await session.call(directMethod, args, options);
|
|
158
|
-
await this.closeSession();
|
|
159
|
-
return { ...result, brokerCleanupVerified: true };
|
|
160
|
-
},
|
|
161
|
-
} : dependencies,
|
|
162
|
-
);
|
|
163
|
-
} finally {
|
|
164
|
-
await this.closeSession();
|
|
165
|
-
}
|
|
166
|
-
} finally {
|
|
167
|
-
release();
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
async close(): Promise<void> {
|
|
172
|
-
await this.queue;
|
|
173
|
-
await this.closeSession();
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function toPiContent(content: Array<Record<string, unknown>>): Array<{ type: "text"; text: string } | { type: "image"; data: string; mimeType: string }> {
|
|
178
|
-
const result: Array<{ type: "text"; text: string } | { type: "image"; data: string; mimeType: string }> = [];
|
|
77
|
+
const tempDir = await mkdtemp(path.join(tmpdir(), "pi-computer-use-"));
|
|
78
|
+
const fullOutputPath = path.join(tempDir, "output.txt");
|
|
79
|
+
await writeFile(fullOutputPath, fullText, { encoding: "utf8", mode: 0o600 });
|
|
80
|
+
const suffix = `\n\n[Official Computer Use text truncated: showing ${aggregate.outputLines} of ${aggregate.totalLines} lines (${formatSize(aggregate.outputBytes)} of ${formatSize(aggregate.totalBytes)}). Full output saved to: ${fullOutputPath}]`;
|
|
81
|
+
const result: PiContentResult = { content: [], fullOutputPath };
|
|
82
|
+
let textIndex = 0;
|
|
83
|
+
let textOffset = 0;
|
|
84
|
+
let noticeAdded = false;
|
|
179
85
|
for (const block of content) {
|
|
180
86
|
if (block.type === "image") {
|
|
181
|
-
result.push({ type: "image", data: String(block.data), mimeType: String(block.mimeType) });
|
|
87
|
+
result.content.push({ type: "image", data: String(block.data), mimeType: String(block.mimeType) });
|
|
182
88
|
continue;
|
|
183
89
|
}
|
|
184
|
-
const
|
|
185
|
-
const
|
|
186
|
-
const
|
|
187
|
-
|
|
90
|
+
const blockText = String(block.text ?? "");
|
|
91
|
+
const start = textOffset + (textIndex > 0 ? 2 : 0);
|
|
92
|
+
const end = start + blockText.length;
|
|
93
|
+
const retained = aggregate.content.length > start
|
|
94
|
+
? blockText.slice(0, Math.min(end, aggregate.content.length) - start)
|
|
188
95
|
: "";
|
|
189
|
-
|
|
96
|
+
const cutoffHere: boolean = !noticeAdded && aggregate.content.length < end;
|
|
97
|
+
if (retained || cutoffHere) {
|
|
98
|
+
result.content.push({ type: "text", text: `${retained}${cutoffHere ? suffix : ""}` });
|
|
99
|
+
noticeAdded ||= cutoffHere;
|
|
100
|
+
}
|
|
101
|
+
textOffset = end;
|
|
102
|
+
textIndex += 1;
|
|
190
103
|
}
|
|
191
104
|
return result;
|
|
192
105
|
}
|
|
193
106
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
203
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function initialFormContent(schema: unknown): Record<string, unknown> {
|
|
207
|
-
if (!isRecord(schema) || !isRecord(schema.properties)) return {};
|
|
208
|
-
const content: Record<string, unknown> = {};
|
|
209
|
-
for (const [key, rawProperty] of Object.entries(schema.properties)) {
|
|
210
|
-
if (!isRecord(rawProperty)) continue;
|
|
211
|
-
if (rawProperty.default !== undefined) content[key] = rawProperty.default;
|
|
212
|
-
else if (Array.isArray(rawProperty.enum) && rawProperty.enum.length > 0) content[key] = rawProperty.enum[0];
|
|
213
|
-
else if (rawProperty.type === "boolean") content[key] = false;
|
|
214
|
-
else if (rawProperty.type === "number" || rawProperty.type === "integer") content[key] = 0;
|
|
215
|
-
else if (rawProperty.type === "array") content[key] = [];
|
|
216
|
-
else content[key] = "";
|
|
217
|
-
}
|
|
218
|
-
return content;
|
|
107
|
+
interface PiElicitationContext {
|
|
108
|
+
hasUI: boolean;
|
|
109
|
+
ui: {
|
|
110
|
+
select(title: string, options: string[]): Promise<string | undefined>;
|
|
111
|
+
editor(title: string, prefill?: string): Promise<string | undefined>;
|
|
112
|
+
notify(message: string, type?: "info" | "warning" | "error"): void;
|
|
113
|
+
};
|
|
219
114
|
}
|
|
220
115
|
|
|
221
116
|
export async function handleOfficialElicitation(
|
|
222
117
|
request: DirectBrokerElicitationRequest,
|
|
223
|
-
ctx:
|
|
224
|
-
select(title: string, options: string[]): Promise<string | undefined>;
|
|
225
|
-
editor(title: string, prefill?: string): Promise<string | undefined>;
|
|
226
|
-
notify(message: string, type?: "info" | "warning" | "error"): void;
|
|
227
|
-
} },
|
|
118
|
+
ctx: PiElicitationContext,
|
|
228
119
|
openUrl: (url: string) => Promise<boolean>,
|
|
229
120
|
): Promise<DirectBrokerElicitationResponse> {
|
|
230
121
|
if (!ctx.hasUI) return { action: "cancel" };
|
|
231
|
-
const message =
|
|
122
|
+
const message = request.message ?? "Official Computer Use requests input";
|
|
232
123
|
|
|
233
124
|
if (request.mode === "url") {
|
|
234
|
-
if (
|
|
125
|
+
if (!request.url) return { action: "cancel" };
|
|
235
126
|
const choice = await ctx.ui.select(`${message}\n${request.url}`, ["Open URL", "Decline", "Cancel"]);
|
|
236
127
|
if (choice === "Decline") return { action: "decline" };
|
|
237
128
|
if (choice !== "Open URL") return { action: "cancel" };
|
|
@@ -247,19 +138,21 @@ export async function handleOfficialElicitation(
|
|
|
247
138
|
return { action: "cancel" };
|
|
248
139
|
}
|
|
249
140
|
const openAiForm = request.mode === "openai/form";
|
|
250
|
-
if (request.requestedSchema === undefined || (!openAiForm && !
|
|
141
|
+
if (request.requestedSchema === undefined || (!openAiForm && !jsonObjectSchema.safeParse(request.requestedSchema).success)) {
|
|
142
|
+
return { action: "cancel" };
|
|
143
|
+
}
|
|
251
144
|
const choice = await ctx.ui.select(message, ["Respond", "Decline", "Cancel"]);
|
|
252
145
|
if (choice === "Decline") return { action: "decline" };
|
|
253
146
|
if (choice !== "Respond") return { action: "cancel" };
|
|
254
|
-
let prefill = JSON.stringify(initialFormContent(request.requestedSchema), null, 2);
|
|
255
147
|
const title = `${message}\nSchema: ${JSON.stringify(request.requestedSchema)}`;
|
|
148
|
+
let prefill = "{}";
|
|
256
149
|
while (true) {
|
|
257
150
|
const edited = await ctx.ui.editor(title, prefill);
|
|
258
151
|
if (edited === undefined) return { action: "cancel" };
|
|
259
152
|
prefill = edited;
|
|
260
153
|
try {
|
|
261
|
-
const
|
|
262
|
-
|
|
154
|
+
const parsed = JSON.parse(edited);
|
|
155
|
+
const content = openAiForm ? parsed : jsonObjectSchema.parse(parsed);
|
|
263
156
|
return { action: "accept", content };
|
|
264
157
|
} catch (error) {
|
|
265
158
|
ctx.ui.notify(error instanceof Error ? error.message : "Response must be valid JSON", "error");
|
|
@@ -269,62 +162,54 @@ export async function handleOfficialElicitation(
|
|
|
269
162
|
|
|
270
163
|
export default function directComputerUse(pi: ExtensionAPI) {
|
|
271
164
|
const stateRoot = process.env.CODEX_COMPUTER_USE_HOME || path.join(getAgentDir(), "direct-computer-use");
|
|
272
|
-
const
|
|
165
|
+
const codeExecutor = new ComputerUseCodeExecutor();
|
|
273
166
|
|
|
274
167
|
pi.registerCommand("computer-use-status", {
|
|
275
|
-
description: "Show
|
|
168
|
+
description: "Show Computer Use status",
|
|
276
169
|
handler: async (_args, ctx) => {
|
|
277
|
-
|
|
278
|
-
ctx.ui.notify(JSON.stringify(status, null, 2), "info");
|
|
170
|
+
ctx.ui.notify(JSON.stringify(getDirectStatus(stateRoot), null, 2), "info");
|
|
279
171
|
},
|
|
280
172
|
});
|
|
281
173
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
},
|
|
320
|
-
renderCall(args, theme) {
|
|
321
|
-
const renderedArgs = args as Record<string, unknown>;
|
|
322
|
-
const app = typeof renderedArgs.app === "string" ? ` ${renderedArgs.app}` : "";
|
|
323
|
-
return new Text(theme.fg("toolTitle", theme.bold(`${piName} `)) + theme.fg("accent", app.trim()), 0, 0);
|
|
324
|
-
},
|
|
325
|
-
});
|
|
326
|
-
}
|
|
174
|
+
pi.registerTool({
|
|
175
|
+
name: "computer_use",
|
|
176
|
+
label: "Computer Use",
|
|
177
|
+
description: codeDescription,
|
|
178
|
+
promptSnippet: "Run composable JavaScript against OpenAI's official signed macOS Computer Use surface",
|
|
179
|
+
promptGuidelines: [
|
|
180
|
+
"Use computer_use for macOS app UI work, composing known sequential actions in one JavaScript call and emitting only the state needed for the next decision.",
|
|
181
|
+
],
|
|
182
|
+
// SAFETY: Pi accepts this standard JSON Schema object as a custom-tool parameter schema.
|
|
183
|
+
parameters: codeParameters as any,
|
|
184
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
185
|
+
const { code } = z.object({ code: z.string() }).parse(params);
|
|
186
|
+
const result = await codeExecutor.execute(code, {
|
|
187
|
+
stateRoot,
|
|
188
|
+
signal,
|
|
189
|
+
supportsOpenAiFormElicitation: true,
|
|
190
|
+
onElicitation: (request) => handleOfficialElicitation(
|
|
191
|
+
request,
|
|
192
|
+
ctx,
|
|
193
|
+
async (url) => (await pi.exec("/usr/bin/open", ["--", url], { signal, timeout: 15_000 })).code === 0,
|
|
194
|
+
),
|
|
195
|
+
});
|
|
196
|
+
const rendered = await toPiContent(result.content);
|
|
197
|
+
const details: JsonObject = { calls: result.calls };
|
|
198
|
+
if (result.error) details.error = result.error;
|
|
199
|
+
if (rendered.fullOutputPath) details.fullOutputPath = rendered.fullOutputPath;
|
|
200
|
+
return { content: rendered.content, details };
|
|
201
|
+
},
|
|
202
|
+
renderCall(args, theme) {
|
|
203
|
+
const parsed = z.object({ code: z.string() }).safeParse(args);
|
|
204
|
+
const firstLine = parsed.success
|
|
205
|
+
? parsed.data.code.split("\n").map((line) => line.trim()).find(Boolean)?.slice(0, 100)
|
|
206
|
+
: undefined;
|
|
207
|
+
const label = theme.fg("toolTitle", theme.bold("computer_use"));
|
|
208
|
+
return new Text(firstLine ? `${label} ${theme.fg("dim", firstLine)}` : label, 0, 0);
|
|
209
|
+
},
|
|
210
|
+
});
|
|
327
211
|
|
|
328
|
-
pi.on("session_start", () =>
|
|
329
|
-
pi.on("
|
|
212
|
+
pi.on("session_start", () => pi.setActiveTools([...new Set([...pi.getActiveTools(), "computer_use"])]));
|
|
213
|
+
pi.on("agent_settled", () => codeExecutor.close());
|
|
214
|
+
pi.on("session_shutdown", () => codeExecutor.close());
|
|
330
215
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-computer-use-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Expose the official macOS Computer Use tools directly to Pi and MCP clients without a nested model.",
|
|
5
5
|
"author": "Thomas Mustier",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,57 +28,44 @@
|
|
|
28
28
|
},
|
|
29
29
|
"files": [
|
|
30
30
|
"dist",
|
|
31
|
-
"integrations/pi",
|
|
32
|
-
"npm-shrinkwrap.json",
|
|
31
|
+
"integrations/pi/index.ts",
|
|
33
32
|
"README.md",
|
|
34
|
-
"CHANGELOG.md",
|
|
35
|
-
"PROOF.md",
|
|
36
|
-
"ARCHITECTURE.md",
|
|
37
|
-
"MIGRATION.md",
|
|
38
33
|
"SECURITY.md",
|
|
39
|
-
"docs/adr",
|
|
40
|
-
"CONTRIBUTING.md",
|
|
41
34
|
"LICENSE"
|
|
42
35
|
],
|
|
43
36
|
"scripts": {
|
|
44
37
|
"build": "rm -rf dist && tsc -p tsconfig.json",
|
|
45
|
-
"
|
|
38
|
+
"lint": "oxlint --deny-warnings",
|
|
39
|
+
"check": "npm run lint && tsc -p tsconfig.json --noEmit",
|
|
40
|
+
"pretest": "npm run build",
|
|
46
41
|
"test": "node --test test/*.test.ts",
|
|
47
|
-
"
|
|
48
|
-
"prepublishOnly": "npm run check && npm run check:pi && npm test
|
|
49
|
-
"check:pi": "npx tsc --ignoreConfig --noEmit --target ES2022 --module NodeNext --moduleResolution NodeNext --strict --skipLibCheck --types node integrations/pi/index.ts"
|
|
42
|
+
"prepack": "npm run build",
|
|
43
|
+
"prepublishOnly": "npm run check && npm run check:pi && npm test",
|
|
44
|
+
"check:pi": "npm run build && npx tsc --ignoreConfig --noEmit --target ES2022 --module NodeNext --moduleResolution NodeNext --strict --skipLibCheck --types node integrations/pi/index.ts"
|
|
50
45
|
},
|
|
51
46
|
"dependencies": {
|
|
52
47
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
53
|
-
"@types/node": "24.13.3",
|
|
54
|
-
"typescript": "7.0.2",
|
|
55
48
|
"zod": "4.4.3"
|
|
56
49
|
},
|
|
57
50
|
"peerDependencies": {
|
|
58
|
-
"@earendil-works/pi-ai": "*",
|
|
59
51
|
"@earendil-works/pi-coding-agent": "*",
|
|
60
|
-
"@earendil-works/pi-tui": "*"
|
|
61
|
-
"typebox": "*"
|
|
52
|
+
"@earendil-works/pi-tui": "*"
|
|
62
53
|
},
|
|
63
54
|
"peerDependenciesMeta": {
|
|
64
|
-
"@earendil-works/pi-ai": {
|
|
65
|
-
"optional": true
|
|
66
|
-
},
|
|
67
55
|
"@earendil-works/pi-coding-agent": {
|
|
68
56
|
"optional": true
|
|
69
57
|
},
|
|
70
58
|
"@earendil-works/pi-tui": {
|
|
71
59
|
"optional": true
|
|
72
|
-
},
|
|
73
|
-
"typebox": {
|
|
74
|
-
"optional": true
|
|
75
60
|
}
|
|
76
61
|
},
|
|
77
62
|
"devDependencies": {
|
|
78
|
-
"@earendil-works/pi-ai": "0.80.7",
|
|
79
63
|
"@earendil-works/pi-coding-agent": "0.80.7",
|
|
80
64
|
"@earendil-works/pi-tui": "0.80.7",
|
|
81
|
-
"
|
|
65
|
+
"@oxlint/plugins": "1.79.0",
|
|
66
|
+
"@types/node": "24.13.3",
|
|
67
|
+
"oxlint": "1.79.0",
|
|
68
|
+
"typescript": "7.0.2"
|
|
82
69
|
},
|
|
83
70
|
"engines": {
|
|
84
71
|
"node": ">=22"
|