codex-computer-use-mcp 0.3.3 → 0.4.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 +38 -121
- 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/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 -301
- 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 +99 -244
- package/package.json +13 -26
- package/ARCHITECTURE.md +0 -162
- package/CHANGELOG.md +0 -13
- package/CONTRIBUTING.md +0 -33
- package/MIGRATION.md +0 -125
- package/PROOF.md +0 -160
- 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 -27
- package/dist/system.js +0 -215
- 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,93 @@
|
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
app: App,
|
|
22
|
-
click_count: Type.Optional(Type.Integer({ description: "Number of clicks. Defaults to 1" })),
|
|
23
|
-
element_index: Type.Optional(Type.String({ description: "Element index to click" })),
|
|
24
|
-
mouse_button: Type.Optional(StringEnum(["left", "right", "middle"] as const, { description: "Mouse button to click. Defaults to left." })),
|
|
25
|
-
x: Type.Optional(Type.Number({ description: "X coordinate in screenshot pixel coordinates" })),
|
|
26
|
-
y: Type.Optional(Type.Number({ description: "Y coordinate in screenshot pixel coordinates" })),
|
|
27
|
-
}, { additionalProperties: false }),
|
|
28
|
-
perform_secondary_action: Type.Object({
|
|
29
|
-
app: App,
|
|
30
|
-
element_index: Type.String({ description: "Element identifier" }),
|
|
31
|
-
action: Type.String({ description: "Secondary accessibility action name" }),
|
|
32
|
-
}, { additionalProperties: false }),
|
|
33
|
-
set_value: Type.Object({
|
|
34
|
-
app: App,
|
|
35
|
-
element_index: Type.String({ description: "Element identifier" }),
|
|
36
|
-
value: Type.String({ description: "Value to assign" }),
|
|
37
|
-
}, { additionalProperties: false }),
|
|
38
|
-
select_text: Type.Object({
|
|
39
|
-
app: Type.String({ description: "App name or bundle identifier" }),
|
|
40
|
-
element_index: Type.String({ description: "Text element identifier" }),
|
|
41
|
-
text: Type.String({ description: "Target text as shown in the accessibility tree" }),
|
|
42
|
-
prefix: Type.Optional(Type.String({ description: "Optional text immediately before the target, used to disambiguate repeated matches" })),
|
|
43
|
-
selection: Type.Optional(StringEnum(["text", "cursor_before", "cursor_after"] as const, { description: "Whether to select the text or place the cursor before or after it. Defaults to text." })),
|
|
44
|
-
suffix: Type.Optional(Type.String({ description: "Optional text immediately after the target, used to disambiguate repeated matches" })),
|
|
45
|
-
}, { additionalProperties: false }),
|
|
46
|
-
scroll: Type.Object({
|
|
47
|
-
app: App,
|
|
48
|
-
element_index: Type.String({ description: "Element identifier" }),
|
|
49
|
-
direction: Type.String({ description: "Scroll direction: up, down, left, or right" }),
|
|
50
|
-
pages: Type.Optional(Type.Number({ description: "Number of pages to scroll. Fractional values are supported. Defaults to 1" })),
|
|
51
|
-
}, { additionalProperties: false }),
|
|
52
|
-
drag: Type.Object({
|
|
53
|
-
app: App,
|
|
54
|
-
from_x: Type.Number({ description: "Start X coordinate" }),
|
|
55
|
-
from_y: Type.Number({ description: "Start Y coordinate" }),
|
|
56
|
-
to_x: Type.Number({ description: "End X coordinate" }),
|
|
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
|
-
};
|
|
19
|
+
import { DirectSessionExecutor } from "../../dist/session-executor.js";
|
|
20
|
+
import {
|
|
21
|
+
TOOL_INPUT_SCHEMAS,
|
|
22
|
+
TOOL_METADATA,
|
|
23
|
+
COMPUTER_USE_METHODS,
|
|
24
|
+
type DirectMethod,
|
|
25
|
+
type JsonObject,
|
|
26
|
+
} from "../../dist/tools.js";
|
|
62
27
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
28
|
+
const jsonObjectSchema = z.record(z.string(), z.json());
|
|
29
|
+
const toolNames = COMPUTER_USE_METHODS.map((method) => `computer_use_${method}`);
|
|
66
30
|
|
|
67
31
|
function titleFor(method: DirectMethod): string {
|
|
68
32
|
return method.split("_").map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`).join(" ");
|
|
69
33
|
}
|
|
70
34
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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;
|
|
35
|
+
interface PiContentResult {
|
|
36
|
+
content: Array<{ type: "text"; text: string } | { type: "image"; data: string; mimeType: string }>;
|
|
37
|
+
fullOutputPath?: string;
|
|
99
38
|
}
|
|
100
39
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
await session?.close();
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async execute(
|
|
118
|
-
method: DirectMethod,
|
|
119
|
-
params: Record<string, unknown>,
|
|
120
|
-
dependencies: Parameters<typeof executeDirectTool>[1],
|
|
121
|
-
): Promise<Awaited<ReturnType<typeof executeDirectTool>>> {
|
|
122
|
-
const previous = this.queue;
|
|
123
|
-
let release!: () => void;
|
|
124
|
-
this.queue = new Promise<void>((resolve) => { release = resolve; });
|
|
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();
|
|
40
|
+
export async function toPiContent(content: JsonObject[]): Promise<PiContentResult> {
|
|
41
|
+
const textBlocks = content
|
|
42
|
+
.filter((block) => block.type !== "image")
|
|
43
|
+
.map((block) => String(block.text ?? ""));
|
|
44
|
+
const truncations = textBlocks.map((text) => truncateHead(text, {
|
|
45
|
+
maxLines: DEFAULT_MAX_LINES,
|
|
46
|
+
maxBytes: DEFAULT_MAX_BYTES,
|
|
47
|
+
}));
|
|
48
|
+
let fullOutputPath: string | undefined;
|
|
49
|
+
if (truncations.some((result) => result.truncated)) {
|
|
50
|
+
const tempDir = await mkdtemp(path.join(tmpdir(), "pi-computer-use-"));
|
|
51
|
+
fullOutputPath = path.join(tempDir, "output.txt");
|
|
52
|
+
await writeFile(fullOutputPath, textBlocks.join("\n\n"), { encoding: "utf8", mode: 0o600 });
|
|
174
53
|
}
|
|
175
|
-
}
|
|
176
54
|
|
|
177
|
-
|
|
178
|
-
|
|
55
|
+
const result: PiContentResult = { content: [] };
|
|
56
|
+
let textIndex = 0;
|
|
179
57
|
for (const block of content) {
|
|
180
58
|
if (block.type === "image") {
|
|
181
|
-
result.push({ type: "image", data: String(block.data), mimeType: String(block.mimeType) });
|
|
59
|
+
result.content.push({ type: "image", data: String(block.data), mimeType: String(block.mimeType) });
|
|
182
60
|
continue;
|
|
183
61
|
}
|
|
184
|
-
const
|
|
185
|
-
const truncated = truncateHead(original, { maxLines: 2_000, maxBytes: 50 * 1024 });
|
|
62
|
+
const truncated = truncations[textIndex++];
|
|
186
63
|
const suffix = truncated.truncated
|
|
187
|
-
? `\n\n[Official Computer Use text truncated
|
|
64
|
+
? `\n\n[Official Computer Use text truncated: showing ${truncated.outputLines} of ${truncated.totalLines} lines (${formatSize(truncated.outputBytes)} of ${formatSize(truncated.totalBytes)}). Full output saved to: ${fullOutputPath}]`
|
|
188
65
|
: "";
|
|
189
|
-
result.push({ type: "text", text: `${truncated.content}${suffix}` });
|
|
66
|
+
result.content.push({ type: "text", text: `${truncated.content}${suffix}` });
|
|
190
67
|
}
|
|
68
|
+
if (fullOutputPath) result.fullOutputPath = fullOutputPath;
|
|
191
69
|
return result;
|
|
192
70
|
}
|
|
193
71
|
|
|
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;
|
|
72
|
+
interface PiElicitationContext {
|
|
73
|
+
hasUI: boolean;
|
|
74
|
+
ui: {
|
|
75
|
+
select(title: string, options: string[]): Promise<string | undefined>;
|
|
76
|
+
editor(title: string, prefill?: string): Promise<string | undefined>;
|
|
77
|
+
notify(message: string, type?: "info" | "warning" | "error"): void;
|
|
78
|
+
};
|
|
219
79
|
}
|
|
220
80
|
|
|
221
81
|
export async function handleOfficialElicitation(
|
|
222
82
|
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
|
-
} },
|
|
83
|
+
ctx: PiElicitationContext,
|
|
228
84
|
openUrl: (url: string) => Promise<boolean>,
|
|
229
85
|
): Promise<DirectBrokerElicitationResponse> {
|
|
230
86
|
if (!ctx.hasUI) return { action: "cancel" };
|
|
231
|
-
const message =
|
|
87
|
+
const message = request.message ?? "Official Computer Use requests input";
|
|
232
88
|
|
|
233
89
|
if (request.mode === "url") {
|
|
234
|
-
if (
|
|
90
|
+
if (!request.url) return { action: "cancel" };
|
|
235
91
|
const choice = await ctx.ui.select(`${message}\n${request.url}`, ["Open URL", "Decline", "Cancel"]);
|
|
236
92
|
if (choice === "Decline") return { action: "decline" };
|
|
237
93
|
if (choice !== "Open URL") return { action: "cancel" };
|
|
@@ -247,19 +103,21 @@ export async function handleOfficialElicitation(
|
|
|
247
103
|
return { action: "cancel" };
|
|
248
104
|
}
|
|
249
105
|
const openAiForm = request.mode === "openai/form";
|
|
250
|
-
if (request.requestedSchema === undefined || (!openAiForm && !
|
|
106
|
+
if (request.requestedSchema === undefined || (!openAiForm && !jsonObjectSchema.safeParse(request.requestedSchema).success)) {
|
|
107
|
+
return { action: "cancel" };
|
|
108
|
+
}
|
|
251
109
|
const choice = await ctx.ui.select(message, ["Respond", "Decline", "Cancel"]);
|
|
252
110
|
if (choice === "Decline") return { action: "decline" };
|
|
253
111
|
if (choice !== "Respond") return { action: "cancel" };
|
|
254
|
-
let prefill = JSON.stringify(initialFormContent(request.requestedSchema), null, 2);
|
|
255
112
|
const title = `${message}\nSchema: ${JSON.stringify(request.requestedSchema)}`;
|
|
113
|
+
let prefill = "{}";
|
|
256
114
|
while (true) {
|
|
257
115
|
const edited = await ctx.ui.editor(title, prefill);
|
|
258
116
|
if (edited === undefined) return { action: "cancel" };
|
|
259
117
|
prefill = edited;
|
|
260
118
|
try {
|
|
261
|
-
const
|
|
262
|
-
|
|
119
|
+
const parsed = JSON.parse(edited);
|
|
120
|
+
const content = openAiForm ? parsed : jsonObjectSchema.parse(parsed);
|
|
263
121
|
return { action: "accept", content };
|
|
264
122
|
} catch (error) {
|
|
265
123
|
ctx.ui.notify(error instanceof Error ? error.message : "Response must be valid JSON", "error");
|
|
@@ -269,62 +127,59 @@ export async function handleOfficialElicitation(
|
|
|
269
127
|
|
|
270
128
|
export default function directComputerUse(pi: ExtensionAPI) {
|
|
271
129
|
const stateRoot = process.env.CODEX_COMPUTER_USE_HOME || path.join(getAgentDir(), "direct-computer-use");
|
|
272
|
-
const sessionExecutor = new
|
|
130
|
+
const sessionExecutor = new DirectSessionExecutor();
|
|
273
131
|
|
|
274
132
|
pi.registerCommand("computer-use-status", {
|
|
275
|
-
description: "Show
|
|
133
|
+
description: "Show Computer Use status",
|
|
276
134
|
handler: async (_args, ctx) => {
|
|
277
|
-
|
|
278
|
-
ctx.ui.notify(JSON.stringify(status, null, 2), "info");
|
|
135
|
+
ctx.ui.notify(JSON.stringify(getDirectStatus(stateRoot), null, 2), "info");
|
|
279
136
|
},
|
|
280
137
|
});
|
|
281
138
|
|
|
282
|
-
const
|
|
283
|
-
for (const method of methods) {
|
|
139
|
+
for (const method of COMPUTER_USE_METHODS) {
|
|
284
140
|
const piName = `computer_use_${method}`;
|
|
285
|
-
const inspectionPromptMetadata = INSPECTION_METHODS.has(method) ? {
|
|
286
|
-
promptSnippet: `${titleFor(method)} through the official signed macOS Computer Use service`,
|
|
287
|
-
promptGuidelines: [
|
|
288
|
-
`${piName} is a direct typed tool: choose its arguments yourself; it does not invoke a nested planner or model.`,
|
|
289
|
-
`Call computer_use_get_app_state once per assistant turn before interacting with an app.`,
|
|
290
|
-
],
|
|
291
|
-
} : {};
|
|
292
141
|
pi.registerTool({
|
|
293
142
|
name: piName,
|
|
294
143
|
label: titleFor(method),
|
|
295
|
-
description:
|
|
296
|
-
|
|
297
|
-
parameters:
|
|
298
|
-
async execute(_toolCallId, params, signal,
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
)
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
144
|
+
description: TOOL_METADATA[method].description,
|
|
145
|
+
// SAFETY: Pi accepts standard JSON Schema objects; these schemas are generated directly from the tool's Zod parser.
|
|
146
|
+
parameters: TOOL_INPUT_SCHEMAS[method] as any,
|
|
147
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
148
|
+
const parsedParams = jsonObjectSchema.parse(params);
|
|
149
|
+
const response = await sessionExecutor.execute(method, parsedParams, {
|
|
150
|
+
stateRoot,
|
|
151
|
+
signal,
|
|
152
|
+
supportsOpenAiFormElicitation: true,
|
|
153
|
+
onElicitation: (request) => handleOfficialElicitation(
|
|
154
|
+
request,
|
|
155
|
+
ctx,
|
|
156
|
+
async (url) => (await pi.exec("/usr/bin/open", ["--", url], { signal, timeout: 15_000 })).code === 0,
|
|
157
|
+
),
|
|
158
|
+
});
|
|
159
|
+
if (response.isError) {
|
|
160
|
+
const message = response.content
|
|
161
|
+
.filter((block) => block.type === "text")
|
|
162
|
+
.map((block) => String(block.text ?? ""))
|
|
163
|
+
.join("\n")
|
|
164
|
+
.slice(0, 2_000);
|
|
165
|
+
throw new Error(message || "Official Computer Use returned an error");
|
|
166
|
+
}
|
|
167
|
+
const rendered = await toPiContent(response.content);
|
|
168
|
+
const details: JsonObject = {};
|
|
169
|
+
if (response.structuredContent !== undefined) details.officialStructuredContent = response.structuredContent;
|
|
170
|
+
if (rendered.fullOutputPath) details.fullOutputPath = rendered.fullOutputPath;
|
|
171
|
+
return { content: rendered.content, details };
|
|
319
172
|
},
|
|
320
173
|
renderCall(args, theme) {
|
|
321
|
-
const
|
|
322
|
-
const app =
|
|
323
|
-
|
|
174
|
+
const parsed = z.object({ app: z.string().optional() }).safeParse(args);
|
|
175
|
+
const app = parsed.success ? parsed.data.app : undefined;
|
|
176
|
+
const label = theme.fg("toolTitle", theme.bold(piName));
|
|
177
|
+
return new Text(app ? `${label} ${theme.fg("accent", app)}` : label, 0, 0);
|
|
324
178
|
},
|
|
325
179
|
});
|
|
326
180
|
}
|
|
327
181
|
|
|
328
|
-
pi.on("session_start", () =>
|
|
182
|
+
pi.on("session_start", () => pi.setActiveTools([...new Set([...pi.getActiveTools(), ...toolNames])]));
|
|
183
|
+
pi.on("agent_settled", () => sessionExecutor.close());
|
|
329
184
|
pi.on("session_shutdown", () => sessionExecutor.close());
|
|
330
185
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-computer-use-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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"
|