@codexhost/cli-darwin-x64 0.1.0-test.1
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 +42 -0
- package/THIRD_PARTY_NOTICES.txt +25 -0
- package/app/desktop-controller.mjs +1297 -0
- package/app/host-runtime.mjs +51976 -0
- package/app/renderer-extension.js +19661 -0
- package/bin/codexhost +0 -0
- package/libexec/codexhost-shim +0 -0
- package/licenses/Anthropic-SDK-LICENSE.txt +8 -0
- package/licenses/Claude-Agent-SDK-LICENSE.md +1 -0
- package/licenses/MCP-SDK-LICENSE.txt +21 -0
- package/licenses/diff-LICENSE.txt +29 -0
- package/licenses/lucide-LICENSE.txt +43 -0
- package/licenses/zod-LICENSE.txt +21 -0
- package/package.json +42 -0
|
@@ -0,0 +1,1297 @@
|
|
|
1
|
+
// packages/desktop-control/src/production-controller.ts
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
// packages/desktop-control/src/controller-attachment-server.ts
|
|
6
|
+
import { createServer } from "node:net";
|
|
7
|
+
var MAX_REQUEST_BYTES = 96;
|
|
8
|
+
function validPort(value) {
|
|
9
|
+
return Number.isInteger(value) && value >= 1 && value <= 65535;
|
|
10
|
+
}
|
|
11
|
+
function validNonce(value) {
|
|
12
|
+
return /^[0-9a-f]{32}$/.test(value);
|
|
13
|
+
}
|
|
14
|
+
function closeServer(server) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
server.close((error) => {
|
|
17
|
+
if (error) reject(error);
|
|
18
|
+
else resolve();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function respond(socket, value) {
|
|
23
|
+
socket.end(`${value}
|
|
24
|
+
`);
|
|
25
|
+
}
|
|
26
|
+
async function startControllerAttachmentServer(options) {
|
|
27
|
+
if (!validPort(options.port)) throw new Error("attachment port must be a valid TCP port");
|
|
28
|
+
if (!validNonce(options.nonce)) {
|
|
29
|
+
throw new Error("attachment nonce must be 32 lowercase hexadecimal characters");
|
|
30
|
+
}
|
|
31
|
+
const sockets = /* @__PURE__ */ new Set();
|
|
32
|
+
const server = createServer((socket) => {
|
|
33
|
+
sockets.add(socket);
|
|
34
|
+
socket.once("close", () => sockets.delete(socket));
|
|
35
|
+
socket.setEncoding("utf8");
|
|
36
|
+
socket.setTimeout(5e3, () => socket.destroy());
|
|
37
|
+
let request = "";
|
|
38
|
+
let handled = false;
|
|
39
|
+
socket.on("data", (chunk) => {
|
|
40
|
+
if (handled) return;
|
|
41
|
+
request += chunk;
|
|
42
|
+
if (request.length > MAX_REQUEST_BYTES) {
|
|
43
|
+
handled = true;
|
|
44
|
+
respond(socket, "rejected");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const newline = request.indexOf("\n");
|
|
48
|
+
if (newline < 0) return;
|
|
49
|
+
handled = true;
|
|
50
|
+
const line = request.slice(0, newline).replace(/\r$/, "");
|
|
51
|
+
if (line !== `ATTACH ${options.nonce}`) {
|
|
52
|
+
respond(socket, "rejected");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
void options.attach().then(
|
|
56
|
+
() => respond(socket, "ready"),
|
|
57
|
+
() => respond(socket, "failed")
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
await new Promise((resolve, reject) => {
|
|
62
|
+
const onError = (error) => reject(error);
|
|
63
|
+
server.once("error", onError);
|
|
64
|
+
server.listen(options.port, "127.0.0.1", () => {
|
|
65
|
+
server.off("error", onError);
|
|
66
|
+
resolve();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
let closed = false;
|
|
70
|
+
return {
|
|
71
|
+
async close() {
|
|
72
|
+
if (closed) return;
|
|
73
|
+
closed = true;
|
|
74
|
+
for (const socket of sockets) socket.destroy();
|
|
75
|
+
await closeServer(server);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// packages/desktop-control/src/cdp-client.ts
|
|
81
|
+
function isRecord(value) {
|
|
82
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
83
|
+
}
|
|
84
|
+
function nonEmptyString(value, field) {
|
|
85
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
86
|
+
throw new Error(`CDP target '${field}' must be non-empty text`);
|
|
87
|
+
}
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
function loopbackUrl(value, protocols) {
|
|
91
|
+
const url = new URL(value);
|
|
92
|
+
if (!protocols.includes(url.protocol)) {
|
|
93
|
+
throw new Error(`CDP endpoint must use ${protocols.join(" or ")}`);
|
|
94
|
+
}
|
|
95
|
+
if (!["127.0.0.1", "localhost", "[::1]"].includes(url.hostname)) {
|
|
96
|
+
throw new Error("CDP endpoint must use a loopback host");
|
|
97
|
+
}
|
|
98
|
+
return url;
|
|
99
|
+
}
|
|
100
|
+
function parseTarget(value) {
|
|
101
|
+
if (!isRecord(value)) throw new Error("CDP target must be an object");
|
|
102
|
+
const target = {
|
|
103
|
+
id: nonEmptyString(value.id, "id"),
|
|
104
|
+
type: nonEmptyString(value.type, "type"),
|
|
105
|
+
title: typeof value.title === "string" ? value.title : "",
|
|
106
|
+
url: nonEmptyString(value.url, "url"),
|
|
107
|
+
webSocketDebuggerUrl: nonEmptyString(value.webSocketDebuggerUrl, "webSocketDebuggerUrl")
|
|
108
|
+
};
|
|
109
|
+
loopbackUrl(target.webSocketDebuggerUrl, ["ws:", "wss:"]);
|
|
110
|
+
return target;
|
|
111
|
+
}
|
|
112
|
+
function defaultFetch(url) {
|
|
113
|
+
return fetch(url);
|
|
114
|
+
}
|
|
115
|
+
function defaultSocketFactory(url) {
|
|
116
|
+
return new WebSocket(url);
|
|
117
|
+
}
|
|
118
|
+
function messageText(value) {
|
|
119
|
+
if (typeof value === "string") return value;
|
|
120
|
+
if (value instanceof ArrayBuffer) return new TextDecoder().decode(value);
|
|
121
|
+
if (ArrayBuffer.isView(value)) {
|
|
122
|
+
return new TextDecoder().decode(
|
|
123
|
+
new Uint8Array(value.buffer, value.byteOffset, value.byteLength)
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
throw new Error("CDP WebSocket returned a non-text message");
|
|
127
|
+
}
|
|
128
|
+
function parseResponse(value) {
|
|
129
|
+
if (!isRecord(value) || typeof value.id !== "number") return null;
|
|
130
|
+
const response = { id: value.id };
|
|
131
|
+
if ("result" in value) response.result = value.result;
|
|
132
|
+
if (isRecord(value.error)) {
|
|
133
|
+
response.error = {
|
|
134
|
+
...typeof value.error.code === "number" ? { code: value.error.code } : {},
|
|
135
|
+
...typeof value.error.message === "string" ? { message: value.error.message } : {}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return response;
|
|
139
|
+
}
|
|
140
|
+
async function listCdpTargets(endpoint, fetchImpl = defaultFetch) {
|
|
141
|
+
const baseUrl = loopbackUrl(endpoint, ["http:", "https:"]);
|
|
142
|
+
const targetsUrl = new URL("/json/list", baseUrl).toString();
|
|
143
|
+
const response = await fetchImpl(targetsUrl);
|
|
144
|
+
if (!response.ok) throw new Error(`CDP target discovery failed with HTTP ${response.status}`);
|
|
145
|
+
const value = await response.json();
|
|
146
|
+
if (!Array.isArray(value)) throw new Error("CDP target discovery did not return an array");
|
|
147
|
+
return value.map(parseTarget);
|
|
148
|
+
}
|
|
149
|
+
var CdpClient = class _CdpClient {
|
|
150
|
+
#commandTimeoutMs;
|
|
151
|
+
#socket;
|
|
152
|
+
#closed = false;
|
|
153
|
+
#nextId = 1;
|
|
154
|
+
#pending = /* @__PURE__ */ new Map();
|
|
155
|
+
#listeners = /* @__PURE__ */ new Map();
|
|
156
|
+
constructor(socket, commandTimeoutMs) {
|
|
157
|
+
this.#socket = socket;
|
|
158
|
+
this.#commandTimeoutMs = commandTimeoutMs;
|
|
159
|
+
socket.addEventListener("message", (event) => this.#onMessage(event));
|
|
160
|
+
socket.addEventListener("error", () => this.#fail(new Error("CDP WebSocket failed")));
|
|
161
|
+
socket.addEventListener("close", () => this.#fail(new Error("CDP WebSocket closed")));
|
|
162
|
+
}
|
|
163
|
+
static async connect(url, options = {}) {
|
|
164
|
+
loopbackUrl(url, ["ws:", "wss:"]);
|
|
165
|
+
const socketFactory = options.socketFactory ?? defaultSocketFactory;
|
|
166
|
+
const socket = socketFactory(url);
|
|
167
|
+
const connectTimeoutMs = options.connectTimeoutMs ?? 1e4;
|
|
168
|
+
await new Promise((resolve, reject) => {
|
|
169
|
+
const timeout = setTimeout(() => {
|
|
170
|
+
cleanup();
|
|
171
|
+
socket.close();
|
|
172
|
+
reject(new Error("CDP WebSocket connection timed out"));
|
|
173
|
+
}, connectTimeoutMs);
|
|
174
|
+
const onOpen = () => {
|
|
175
|
+
cleanup();
|
|
176
|
+
resolve();
|
|
177
|
+
};
|
|
178
|
+
const onError = () => {
|
|
179
|
+
cleanup();
|
|
180
|
+
reject(new Error("CDP WebSocket connection failed"));
|
|
181
|
+
};
|
|
182
|
+
const cleanup = () => {
|
|
183
|
+
clearTimeout(timeout);
|
|
184
|
+
socket.removeEventListener("open", onOpen);
|
|
185
|
+
socket.removeEventListener("error", onError);
|
|
186
|
+
};
|
|
187
|
+
socket.addEventListener("open", onOpen);
|
|
188
|
+
socket.addEventListener("error", onError);
|
|
189
|
+
});
|
|
190
|
+
return new _CdpClient(socket, options.commandTimeoutMs ?? 1e4);
|
|
191
|
+
}
|
|
192
|
+
command(method, params = {}) {
|
|
193
|
+
return this.#send(method, params);
|
|
194
|
+
}
|
|
195
|
+
sessionCommand(sessionId, method, params = {}) {
|
|
196
|
+
if (sessionId.length === 0) return Promise.reject(new Error("CDP session ID is required"));
|
|
197
|
+
return this.#send(method, params, sessionId);
|
|
198
|
+
}
|
|
199
|
+
on(method, listener) {
|
|
200
|
+
const listeners = this.#listeners.get(method) ?? /* @__PURE__ */ new Set();
|
|
201
|
+
listeners.add(listener);
|
|
202
|
+
this.#listeners.set(method, listeners);
|
|
203
|
+
return () => {
|
|
204
|
+
listeners.delete(listener);
|
|
205
|
+
if (listeners.size === 0) this.#listeners.delete(method);
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
#send(method, params, sessionId) {
|
|
209
|
+
if (this.#closed) return Promise.reject(new Error("CDP client is closed"));
|
|
210
|
+
const id = this.#nextId++;
|
|
211
|
+
return new Promise((resolve, reject) => {
|
|
212
|
+
const timeout = setTimeout(() => {
|
|
213
|
+
this.#pending.delete(id);
|
|
214
|
+
reject(new Error(`CDP command '${method}' timed out`));
|
|
215
|
+
}, this.#commandTimeoutMs);
|
|
216
|
+
this.#pending.set(id, { resolve, reject, timeout });
|
|
217
|
+
try {
|
|
218
|
+
this.#socket.send(
|
|
219
|
+
JSON.stringify({ id, method, params, ...sessionId ? { sessionId } : {} })
|
|
220
|
+
);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
clearTimeout(timeout);
|
|
223
|
+
this.#pending.delete(id);
|
|
224
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
async evaluate(expression) {
|
|
229
|
+
const response = await this.command("Runtime.evaluate", {
|
|
230
|
+
expression,
|
|
231
|
+
awaitPromise: true,
|
|
232
|
+
returnByValue: true
|
|
233
|
+
});
|
|
234
|
+
if (!isRecord(response)) throw new Error("Runtime.evaluate returned an invalid result");
|
|
235
|
+
if (isRecord(response.exceptionDetails)) {
|
|
236
|
+
const text = typeof response.exceptionDetails.text === "string" ? response.exceptionDetails.text : "Renderer evaluation failed";
|
|
237
|
+
throw new Error(text);
|
|
238
|
+
}
|
|
239
|
+
if (!isRecord(response.result) || !("value" in response.result)) {
|
|
240
|
+
throw new Error("Runtime.evaluate did not return a value");
|
|
241
|
+
}
|
|
242
|
+
return response.result.value;
|
|
243
|
+
}
|
|
244
|
+
close() {
|
|
245
|
+
if (this.#closed) return;
|
|
246
|
+
this.#closed = true;
|
|
247
|
+
this.#fail(new Error("CDP client closed"));
|
|
248
|
+
this.#listeners.clear();
|
|
249
|
+
this.#socket.close();
|
|
250
|
+
}
|
|
251
|
+
#onMessage(event) {
|
|
252
|
+
try {
|
|
253
|
+
const value = JSON.parse(messageText(event.data));
|
|
254
|
+
const response = parseResponse(value);
|
|
255
|
+
if (!response) {
|
|
256
|
+
if (!isRecord(value) || typeof value.method !== "string") return;
|
|
257
|
+
const sessionId = typeof value.sessionId === "string" ? value.sessionId : void 0;
|
|
258
|
+
for (const listener of this.#listeners.get(value.method) ?? []) {
|
|
259
|
+
listener(value.params, sessionId);
|
|
260
|
+
}
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const pending = this.#pending.get(response.id);
|
|
264
|
+
if (!pending) return;
|
|
265
|
+
clearTimeout(pending.timeout);
|
|
266
|
+
this.#pending.delete(response.id);
|
|
267
|
+
if (response.error) {
|
|
268
|
+
const code = response.error.code === void 0 ? "unknown" : String(response.error.code);
|
|
269
|
+
pending.reject(
|
|
270
|
+
new Error(response.error.message ?? `CDP command failed with error ${code}`)
|
|
271
|
+
);
|
|
272
|
+
} else {
|
|
273
|
+
pending.resolve(response.result);
|
|
274
|
+
}
|
|
275
|
+
} catch (error) {
|
|
276
|
+
this.#fail(error instanceof Error ? error : new Error(String(error)));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
#fail(error) {
|
|
280
|
+
for (const pending of this.#pending.values()) {
|
|
281
|
+
clearTimeout(pending.timeout);
|
|
282
|
+
pending.reject(error);
|
|
283
|
+
}
|
|
284
|
+
this.#pending.clear();
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
// packages/desktop-control/src/main-process-title-policy.ts
|
|
289
|
+
function isRecord2(value) {
|
|
290
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
291
|
+
}
|
|
292
|
+
function resultRecord(value, command) {
|
|
293
|
+
if (!isRecord2(value)) throw new Error(`${command} returned an invalid result`);
|
|
294
|
+
if (isRecord2(value.exceptionDetails)) {
|
|
295
|
+
const exception = value.exceptionDetails.exception;
|
|
296
|
+
const description = isRecord2(exception) ? exception.description : null;
|
|
297
|
+
const text = value.exceptionDetails.text;
|
|
298
|
+
throw new Error(
|
|
299
|
+
typeof description === "string" ? description : typeof text === "string" ? text : `${command} failed`
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
return value;
|
|
303
|
+
}
|
|
304
|
+
function remoteObjectId(value, label) {
|
|
305
|
+
if (!isRecord2(value) || typeof value.objectId !== "string") {
|
|
306
|
+
throw new Error(`${label} is unavailable`);
|
|
307
|
+
}
|
|
308
|
+
return value.objectId;
|
|
309
|
+
}
|
|
310
|
+
function properties(value, command) {
|
|
311
|
+
const result = resultRecord(value, command).result;
|
|
312
|
+
if (!Array.isArray(result)) throw new Error(`${command} returned invalid properties`);
|
|
313
|
+
return result.filter(isRecord2);
|
|
314
|
+
}
|
|
315
|
+
var ELECTRON_MODULE_EXPRESSION = `(() => {
|
|
316
|
+
const mainModule = process.mainModule;
|
|
317
|
+
if (mainModule != null && typeof mainModule.require === 'function') {
|
|
318
|
+
return mainModule.require('electron');
|
|
319
|
+
}
|
|
320
|
+
const { createRequire } = process.getBuiltinModule('module');
|
|
321
|
+
return createRequire(process.execPath)('electron');
|
|
322
|
+
})()`;
|
|
323
|
+
var CONNECT_APP_HOST_CHANNEL = "codex_desktop:connect-app-host";
|
|
324
|
+
var POLICY_STATE_SYMBOL = "codexhost.main-process-title-policy.v1";
|
|
325
|
+
var SERVICE_OWNER_SYMBOL = "codexhost.main-process-title-policy.owner.v1";
|
|
326
|
+
var RENDERER_READY_EXPRESSION = "(() => { Object.defineProperty(window, '__codexhostMainProcessTitlePolicyV1', { configurable: true, value: { state: 'ready' } }); return 'ready'; })()";
|
|
327
|
+
var INSTALL_POLICY_FUNCTION = `async function () {
|
|
328
|
+
const mainModule = process.mainModule;
|
|
329
|
+
const electron = mainModule != null && typeof mainModule.require === 'function'
|
|
330
|
+
? mainModule.require('electron')
|
|
331
|
+
: process.getBuiltinModule('module').createRequire(process.execPath)('electron');
|
|
332
|
+
let selected = null;
|
|
333
|
+
let largestElementCount = 0;
|
|
334
|
+
for (const contents of electron.webContents.getAllWebContents()) {
|
|
335
|
+
if (contents.isDestroyed() || contents.getType() !== 'window') continue;
|
|
336
|
+
let elementCount = 0;
|
|
337
|
+
try {
|
|
338
|
+
elementCount = await contents.executeJavaScript(
|
|
339
|
+
"document.querySelectorAll('*').length",
|
|
340
|
+
true,
|
|
341
|
+
);
|
|
342
|
+
} catch {}
|
|
343
|
+
if (Number.isInteger(elementCount) && elementCount > largestElementCount) {
|
|
344
|
+
selected = contents;
|
|
345
|
+
largestElementCount = elementCount;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (selected == null || largestElementCount < 50) {
|
|
349
|
+
throw new Error('Populated Renderer unavailable for title policy');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const context = this(selected);
|
|
353
|
+
if (context == null || typeof context.createAppHost !== 'function') {
|
|
354
|
+
throw new Error('WindowContext unavailable for title policy');
|
|
355
|
+
}
|
|
356
|
+
const stateSymbol = Symbol.for(${JSON.stringify(POLICY_STATE_SYMBOL)});
|
|
357
|
+
const ownerSymbol = Symbol.for(${JSON.stringify(SERVICE_OWNER_SYMBOL)});
|
|
358
|
+
globalThis[stateSymbol]?.dispose?.();
|
|
359
|
+
|
|
360
|
+
const originalCreateAppHost = context.createAppHost;
|
|
361
|
+
const sampleHost = originalCreateAppHost.call(context, selected);
|
|
362
|
+
const sampleService = sampleHost?.services?.threadMetadataGeneration;
|
|
363
|
+
const servicePrototype = sampleService == null ? null : Object.getPrototypeOf(sampleService);
|
|
364
|
+
const originalGenerateTitle = servicePrototype?.generateTitle;
|
|
365
|
+
if (
|
|
366
|
+
servicePrototype == null ||
|
|
367
|
+
!['Dhe', 'Nye'].includes(sampleService?.constructor?.name) ||
|
|
368
|
+
typeof originalGenerateTitle !== 'function' ||
|
|
369
|
+
!Function.prototype.toString.call(originalGenerateTitle).includes('Failed to generate thread title')
|
|
370
|
+
) {
|
|
371
|
+
throw new Error('ThreadMetadataGenerationService signature mismatch');
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const counters = {
|
|
375
|
+
codexTitleCalls: 0,
|
|
376
|
+
piTitleSkips: 0,
|
|
377
|
+
externalTitleSkips: 0,
|
|
378
|
+
ambiguousTitleSkips: 0,
|
|
379
|
+
};
|
|
380
|
+
const ownedWebContentsIds = new Set();
|
|
381
|
+
const ownService = (service, contents) => {
|
|
382
|
+
const existingOwner = service[ownerSymbol];
|
|
383
|
+
if (existingOwner != null && existingOwner !== contents) {
|
|
384
|
+
throw new Error('Thread metadata service ownership mismatch');
|
|
385
|
+
}
|
|
386
|
+
if (existingOwner == null) {
|
|
387
|
+
Object.defineProperty(service, ownerSymbol, { value: contents });
|
|
388
|
+
}
|
|
389
|
+
ownedWebContentsIds.add(contents.id);
|
|
390
|
+
};
|
|
391
|
+
ownService(sampleService, selected);
|
|
392
|
+
const wrappedCreateAppHost = function (contents) {
|
|
393
|
+
const host = originalCreateAppHost.call(this, contents);
|
|
394
|
+
const service = host?.services?.threadMetadataGeneration;
|
|
395
|
+
if (service != null) ownService(service, contents);
|
|
396
|
+
return host;
|
|
397
|
+
};
|
|
398
|
+
const wrappedGenerateTitle = async function (params) {
|
|
399
|
+
const owner = this[ownerSymbol];
|
|
400
|
+
if (owner == null || owner.isDestroyed()) {
|
|
401
|
+
counters.ambiguousTitleSkips += 1;
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
let selection = null;
|
|
405
|
+
try {
|
|
406
|
+
selection = await owner.executeJavaScript(
|
|
407
|
+
"window.__codexhostRendererBindingProbeV1?.lockedSelection() ?? null",
|
|
408
|
+
true,
|
|
409
|
+
);
|
|
410
|
+
} catch {}
|
|
411
|
+
if (selection?.phase !== 'locked') {
|
|
412
|
+
counters.ambiguousTitleSkips += 1;
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
if (selection.agent === 'pi') {
|
|
416
|
+
counters.piTitleSkips += 1;
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
if (selection.agent !== 'codex') {
|
|
420
|
+
counters.externalTitleSkips += 1;
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
counters.codexTitleCalls += 1;
|
|
424
|
+
return originalGenerateTitle.call(this, params);
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
context.createAppHost = wrappedCreateAppHost;
|
|
428
|
+
servicePrototype.generateTitle = wrappedGenerateTitle;
|
|
429
|
+
const state = {
|
|
430
|
+
counters,
|
|
431
|
+
ownedWebContentsIds,
|
|
432
|
+
dispose() {
|
|
433
|
+
if (context.createAppHost === wrappedCreateAppHost) {
|
|
434
|
+
context.createAppHost = originalCreateAppHost;
|
|
435
|
+
}
|
|
436
|
+
if (servicePrototype.generateTitle === wrappedGenerateTitle) {
|
|
437
|
+
servicePrototype.generateTitle = originalGenerateTitle;
|
|
438
|
+
}
|
|
439
|
+
if (globalThis[stateSymbol] === state) delete globalThis[stateSymbol];
|
|
440
|
+
},
|
|
441
|
+
};
|
|
442
|
+
globalThis[stateSymbol] = state;
|
|
443
|
+
return {
|
|
444
|
+
state: 'ready',
|
|
445
|
+
reason: 'ready',
|
|
446
|
+
contextClass: context.constructor.name,
|
|
447
|
+
serviceClass: sampleService.constructor.name,
|
|
448
|
+
requiresRendererReload: true,
|
|
449
|
+
};
|
|
450
|
+
}`;
|
|
451
|
+
async function installMainProcessTitlePolicy(inspector) {
|
|
452
|
+
const listenerResponse = resultRecord(
|
|
453
|
+
await inspector.command("Runtime.evaluate", {
|
|
454
|
+
expression: `(${ELECTRON_MODULE_EXPRESSION}).ipcMain.listeners(${JSON.stringify(
|
|
455
|
+
CONNECT_APP_HOST_CHANNEL
|
|
456
|
+
)})[0]`
|
|
457
|
+
}),
|
|
458
|
+
"Runtime.evaluate"
|
|
459
|
+
);
|
|
460
|
+
const listener = listenerResponse.result;
|
|
461
|
+
const listenerId = remoteObjectId(listener, "connect-app-host listener");
|
|
462
|
+
const listenerProperties = resultRecord(
|
|
463
|
+
await inspector.command("Runtime.getProperties", { objectId: listenerId }),
|
|
464
|
+
"Runtime.getProperties"
|
|
465
|
+
);
|
|
466
|
+
const internalProperties = listenerProperties.internalProperties;
|
|
467
|
+
if (!Array.isArray(internalProperties)) {
|
|
468
|
+
throw new Error("connect-app-host listener scopes are unavailable");
|
|
469
|
+
}
|
|
470
|
+
const scopes = internalProperties.find(
|
|
471
|
+
(property) => isRecord2(property) && property.name === "[[Scopes]]"
|
|
472
|
+
);
|
|
473
|
+
const scopesId = remoteObjectId(
|
|
474
|
+
isRecord2(scopes) ? scopes.value : null,
|
|
475
|
+
"connect-app-host listener scopes"
|
|
476
|
+
);
|
|
477
|
+
const scopeProperties = properties(
|
|
478
|
+
await inspector.command("Runtime.getProperties", {
|
|
479
|
+
objectId: scopesId,
|
|
480
|
+
ownProperties: true
|
|
481
|
+
}),
|
|
482
|
+
"Runtime.getProperties"
|
|
483
|
+
);
|
|
484
|
+
const localScope = scopeProperties.find((property) => property.name === "0");
|
|
485
|
+
const localScopeId = remoteObjectId(localScope?.value, "connect-app-host local scope");
|
|
486
|
+
const localProperties = properties(
|
|
487
|
+
await inspector.command("Runtime.getProperties", {
|
|
488
|
+
objectId: localScopeId,
|
|
489
|
+
ownProperties: true
|
|
490
|
+
}),
|
|
491
|
+
"Runtime.getProperties"
|
|
492
|
+
);
|
|
493
|
+
const getContext = localProperties.find(
|
|
494
|
+
(property) => property.name === "f" && typeof property.value?.objectId === "string"
|
|
495
|
+
);
|
|
496
|
+
const getContextId = remoteObjectId(getContext?.value, "getContextForWebContents");
|
|
497
|
+
const installPromise = resultRecord(
|
|
498
|
+
await inspector.command("Runtime.callFunctionOn", {
|
|
499
|
+
objectId: getContextId,
|
|
500
|
+
functionDeclaration: INSTALL_POLICY_FUNCTION
|
|
501
|
+
}),
|
|
502
|
+
"Runtime.callFunctionOn"
|
|
503
|
+
);
|
|
504
|
+
const promiseObjectId = remoteObjectId(
|
|
505
|
+
installPromise.result,
|
|
506
|
+
"Main-process title policy installation promise"
|
|
507
|
+
);
|
|
508
|
+
const installResponse = resultRecord(
|
|
509
|
+
await inspector.command("Runtime.awaitPromise", {
|
|
510
|
+
promiseObjectId,
|
|
511
|
+
returnByValue: true
|
|
512
|
+
}),
|
|
513
|
+
"Runtime.awaitPromise"
|
|
514
|
+
);
|
|
515
|
+
const remoteResult = installResponse.result;
|
|
516
|
+
const value = isRecord2(remoteResult) ? remoteResult.value : null;
|
|
517
|
+
if (!isRecord2(value) || value.state !== "ready" || value.reason !== "ready" || typeof value.contextClass !== "string" || typeof value.serviceClass !== "string" || value.requiresRendererReload !== true) {
|
|
518
|
+
throw new Error("Main-process title policy returned an invalid status");
|
|
519
|
+
}
|
|
520
|
+
return value;
|
|
521
|
+
}
|
|
522
|
+
async function markRendererTitlePolicyReady(inspector) {
|
|
523
|
+
const value = await inspector.evaluate(`(async () => {
|
|
524
|
+
const state = globalThis[Symbol.for(${JSON.stringify(POLICY_STATE_SYMBOL)})];
|
|
525
|
+
if (state == null) throw new Error('Main-process title policy is unavailable');
|
|
526
|
+
const mainModule = process.mainModule;
|
|
527
|
+
const electron = mainModule != null && typeof mainModule.require === 'function'
|
|
528
|
+
? mainModule.require('electron')
|
|
529
|
+
: process.getBuiltinModule('module').createRequire(process.execPath)('electron');
|
|
530
|
+
let selected = null;
|
|
531
|
+
let largestElementCount = 0;
|
|
532
|
+
for (const contents of electron.webContents.getAllWebContents()) {
|
|
533
|
+
if (contents.isDestroyed() || contents.getType() !== 'window') continue;
|
|
534
|
+
let elementCount = 0;
|
|
535
|
+
try {
|
|
536
|
+
const evaluation = contents.executeJavaScript(
|
|
537
|
+
"document.querySelectorAll('*').length",
|
|
538
|
+
true,
|
|
539
|
+
);
|
|
540
|
+
const timeout = new Promise((_, reject) => {
|
|
541
|
+
setTimeout(() => reject(new Error('Renderer readiness inspection timed out')), 2_000);
|
|
542
|
+
});
|
|
543
|
+
elementCount = await Promise.race([evaluation, timeout]);
|
|
544
|
+
} catch {}
|
|
545
|
+
if (Number.isInteger(elementCount) && elementCount > largestElementCount) {
|
|
546
|
+
selected = contents;
|
|
547
|
+
largestElementCount = elementCount;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
if (selected == null || largestElementCount < 50) {
|
|
551
|
+
throw new Error('Populated Renderer unavailable for title policy readiness');
|
|
552
|
+
}
|
|
553
|
+
if (!state.ownedWebContentsIds.has(selected.id)) {
|
|
554
|
+
throw new Error('Renderer metadata service ownership is unavailable');
|
|
555
|
+
}
|
|
556
|
+
const marker = selected.executeJavaScript(
|
|
557
|
+
${JSON.stringify(RENDERER_READY_EXPRESSION)},
|
|
558
|
+
true,
|
|
559
|
+
);
|
|
560
|
+
const markerTimeout = new Promise((_, reject) => {
|
|
561
|
+
setTimeout(() => reject(new Error('Renderer readiness marker timed out')), 2_000);
|
|
562
|
+
});
|
|
563
|
+
await Promise.race([marker, markerTimeout]);
|
|
564
|
+
return { state: 'ready', reason: 'owned-metadata-service' };
|
|
565
|
+
})()`);
|
|
566
|
+
if (!isRecord2(value) || value.state !== "ready" || value.reason !== "owned-metadata-service") {
|
|
567
|
+
throw new Error("Renderer title policy returned an invalid readiness status");
|
|
568
|
+
}
|
|
569
|
+
return value;
|
|
570
|
+
}
|
|
571
|
+
async function readMainProcessTitlePolicyCounters(inspector) {
|
|
572
|
+
const value = await inspector.evaluate(`(() => {
|
|
573
|
+
const state = globalThis[Symbol.for(${JSON.stringify(POLICY_STATE_SYMBOL)})];
|
|
574
|
+
return state == null ? null : { ...state.counters };
|
|
575
|
+
})()`);
|
|
576
|
+
if (value === null) return null;
|
|
577
|
+
if (!isRecord2(value) || !Number.isInteger(value.codexTitleCalls) || !Number.isInteger(value.piTitleSkips) || !Number.isInteger(value.externalTitleSkips) || !Number.isInteger(value.ambiguousTitleSkips)) {
|
|
578
|
+
throw new Error("Main-process title policy returned invalid counters");
|
|
579
|
+
}
|
|
580
|
+
return value;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// packages/desktop-control/src/renderer-draft-prewarm-runtime.ts
|
|
584
|
+
function installDraftPrewarmPolicyBridge(send, hostId, target) {
|
|
585
|
+
let clearInFlight = null;
|
|
586
|
+
const policy = Object.freeze({
|
|
587
|
+
state: "ready",
|
|
588
|
+
clear() {
|
|
589
|
+
if (clearInFlight === null) {
|
|
590
|
+
clearInFlight = Promise.resolve(send("clear-prewarmed-threads-for-host", { hostId })).then(() => void 0).finally(() => {
|
|
591
|
+
clearInFlight = null;
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
return clearInFlight;
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
Object.defineProperty(target, "__codexhostDraftPrewarmPolicyV1", {
|
|
598
|
+
configurable: true,
|
|
599
|
+
value: policy
|
|
600
|
+
});
|
|
601
|
+
return { state: "ready", reason: "owned-request-bridge" };
|
|
602
|
+
}
|
|
603
|
+
async function installDraftPrewarmPolicyInRenderer(contents, findRequestManagerExpression, installRendererPolicyFunction) {
|
|
604
|
+
if (contents === null || contents.isDestroyed() || contents.getType() !== "window") {
|
|
605
|
+
throw new Error("Owned Renderer is unavailable for draft prewarm policy");
|
|
606
|
+
}
|
|
607
|
+
let attachedHere = false;
|
|
608
|
+
try {
|
|
609
|
+
if (!contents.debugger.isAttached()) {
|
|
610
|
+
contents.debugger.attach("1.3");
|
|
611
|
+
attachedHere = true;
|
|
612
|
+
}
|
|
613
|
+
await contents.debugger.sendCommand("Runtime.enable");
|
|
614
|
+
const managerResult = await contents.debugger.sendCommand("Runtime.evaluate", {
|
|
615
|
+
expression: findRequestManagerExpression
|
|
616
|
+
});
|
|
617
|
+
const managerResultId = managerResult.result?.objectId;
|
|
618
|
+
if (typeof managerResultId !== "string") {
|
|
619
|
+
throw new Error("Renderer request manager inspection failed");
|
|
620
|
+
}
|
|
621
|
+
const managerProperties = await contents.debugger.sendCommand("Runtime.getProperties", {
|
|
622
|
+
objectId: managerResultId,
|
|
623
|
+
ownProperties: true
|
|
624
|
+
});
|
|
625
|
+
const candidateCount = managerProperties.result?.find(
|
|
626
|
+
(property) => property.name === "candidateCount"
|
|
627
|
+
)?.value?.value;
|
|
628
|
+
const hostId = managerProperties.result?.find((property) => property.name === "hostId")?.value?.value;
|
|
629
|
+
const sendRequest = managerProperties.result?.find(
|
|
630
|
+
(property) => property.name === "sendRequest"
|
|
631
|
+
)?.value;
|
|
632
|
+
if (candidateCount !== 1 || hostId !== "local" || typeof sendRequest?.objectId !== "string") {
|
|
633
|
+
throw new Error("Renderer request manager is ambiguous");
|
|
634
|
+
}
|
|
635
|
+
const functionProperties = await contents.debugger.sendCommand("Runtime.getProperties", {
|
|
636
|
+
objectId: sendRequest.objectId
|
|
637
|
+
});
|
|
638
|
+
const scopesId = functionProperties.internalProperties?.find(
|
|
639
|
+
(property) => property.name === "[[Scopes]]"
|
|
640
|
+
)?.value?.objectId;
|
|
641
|
+
if (typeof scopesId !== "string") {
|
|
642
|
+
throw new Error("Renderer request bridge scopes are unavailable");
|
|
643
|
+
}
|
|
644
|
+
const scopes = await contents.debugger.sendCommand("Runtime.getProperties", {
|
|
645
|
+
objectId: scopesId,
|
|
646
|
+
ownProperties: true
|
|
647
|
+
});
|
|
648
|
+
const bridgeCandidates = [];
|
|
649
|
+
for (const scope of scopes.result ?? []) {
|
|
650
|
+
const scopeId = scope.value?.objectId;
|
|
651
|
+
if (typeof scopeId !== "string") continue;
|
|
652
|
+
const scopeProperties = await contents.debugger.sendCommand("Runtime.getProperties", {
|
|
653
|
+
objectId: scopeId,
|
|
654
|
+
ownProperties: true
|
|
655
|
+
});
|
|
656
|
+
const bridge2 = scopeProperties.result?.find(
|
|
657
|
+
(property) => (property.name === "Rf" || property.name === "rp") && property.value?.type === "function"
|
|
658
|
+
)?.value;
|
|
659
|
+
if (typeof bridge2?.objectId === "string") {
|
|
660
|
+
bridgeCandidates.push({ objectId: bridge2.objectId });
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
const bridge = bridgeCandidates[0];
|
|
664
|
+
if (bridgeCandidates.length !== 1 || !bridge) {
|
|
665
|
+
throw new Error("Renderer request bridge is ambiguous");
|
|
666
|
+
}
|
|
667
|
+
const signature = await contents.debugger.sendCommand("Runtime.callFunctionOn", {
|
|
668
|
+
objectId: bridge.objectId,
|
|
669
|
+
functionDeclaration: "function(){return {arity:this.length,source:Function.prototype.toString.call(this)}}",
|
|
670
|
+
returnByValue: true
|
|
671
|
+
});
|
|
672
|
+
const signatureValue = signature.result?.value;
|
|
673
|
+
if (signatureValue?.arity !== 2 || typeof signatureValue.source !== "string" || !signatureValue.source.includes(".sendRequest")) {
|
|
674
|
+
throw new Error("Renderer request bridge signature mismatch");
|
|
675
|
+
}
|
|
676
|
+
const installed = await contents.debugger.sendCommand("Runtime.callFunctionOn", {
|
|
677
|
+
objectId: bridge.objectId,
|
|
678
|
+
functionDeclaration: installRendererPolicyFunction,
|
|
679
|
+
arguments: [{ value: hostId }],
|
|
680
|
+
awaitPromise: true,
|
|
681
|
+
returnByValue: true
|
|
682
|
+
});
|
|
683
|
+
return installed.result?.value;
|
|
684
|
+
} finally {
|
|
685
|
+
if (attachedHere && contents.debugger.isAttached()) contents.debugger.detach();
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// packages/desktop-control/src/renderer-draft-prewarm-policy.ts
|
|
690
|
+
function isRecord3(value) {
|
|
691
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
692
|
+
}
|
|
693
|
+
var FIND_REQUEST_MANAGER_EXPRESSION = `(() => {
|
|
694
|
+
const visibleEditors = [...document.querySelectorAll(
|
|
695
|
+
'[data-codex-composer], [contenteditable="true"][role="textbox"]',
|
|
696
|
+
)].filter((editor) => {
|
|
697
|
+
const bounds = editor.getBoundingClientRect();
|
|
698
|
+
return bounds.width > 0 && bounds.height > 0;
|
|
699
|
+
});
|
|
700
|
+
if (visibleEditors.length !== 1) {
|
|
701
|
+
return { candidateCount: 0, hostId: null, sendRequest: null };
|
|
702
|
+
}
|
|
703
|
+
let element = visibleEditors[0];
|
|
704
|
+
let fiber = null;
|
|
705
|
+
while (element != null && fiber == null) {
|
|
706
|
+
const key = Object.getOwnPropertyNames(element).find((name) =>
|
|
707
|
+
name.startsWith('__reactFiber$'),
|
|
708
|
+
);
|
|
709
|
+
if (key != null) fiber = element[key];
|
|
710
|
+
element = element.parentElement;
|
|
711
|
+
}
|
|
712
|
+
const managers = new Set();
|
|
713
|
+
for (let depth = 0; fiber != null && depth < 200; depth += 1, fiber = fiber.return) {
|
|
714
|
+
let hook = fiber.memoizedState;
|
|
715
|
+
for (let index = 0; hook != null && index < 120; index += 1, hook = hook.next) {
|
|
716
|
+
const value = hook.memoizedState;
|
|
717
|
+
if (
|
|
718
|
+
value != null &&
|
|
719
|
+
typeof value === 'object' &&
|
|
720
|
+
value.requestClient != null &&
|
|
721
|
+
typeof value.requestClient.prewarmThreadStart === 'function' &&
|
|
722
|
+
typeof value.sendRequest === 'function' &&
|
|
723
|
+
Function.prototype.toString.call(value.sendRequest).includes(
|
|
724
|
+
'send-cli-request-for-host',
|
|
725
|
+
)
|
|
726
|
+
) {
|
|
727
|
+
managers.add(value);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
const manager = managers.size === 1 ? managers.values().next().value : null;
|
|
732
|
+
return {
|
|
733
|
+
candidateCount: managers.size,
|
|
734
|
+
hostId: manager?.getHostId?.() ?? null,
|
|
735
|
+
sendRequest: manager?.sendRequest ?? null,
|
|
736
|
+
};
|
|
737
|
+
})()`;
|
|
738
|
+
var INSTALL_RENDERER_POLICY_FUNCTION = `function(hostId) {
|
|
739
|
+
return (${installDraftPrewarmPolicyBridge.toString()})(this, hostId, window);
|
|
740
|
+
}`;
|
|
741
|
+
function mainProcessInstaller(rendererWebContentsId) {
|
|
742
|
+
return `async function () {
|
|
743
|
+
const mainModule = process.mainModule;
|
|
744
|
+
const electron = mainModule != null && typeof mainModule.require === 'function'
|
|
745
|
+
? mainModule.require('electron')
|
|
746
|
+
: process.getBuiltinModule('module').createRequire(process.execPath)('electron');
|
|
747
|
+
const contents = electron.webContents.fromId(${rendererWebContentsId});
|
|
748
|
+
return (${installDraftPrewarmPolicyInRenderer.toString()})(
|
|
749
|
+
contents,
|
|
750
|
+
${JSON.stringify(FIND_REQUEST_MANAGER_EXPRESSION)},
|
|
751
|
+
${JSON.stringify(INSTALL_RENDERER_POLICY_FUNCTION)}
|
|
752
|
+
);
|
|
753
|
+
}`;
|
|
754
|
+
}
|
|
755
|
+
async function installRendererDraftPrewarmPolicy(inspector, rendererWebContentsId) {
|
|
756
|
+
if (!Number.isInteger(rendererWebContentsId) || rendererWebContentsId <= 0) {
|
|
757
|
+
throw new Error("Renderer webContents ID must be a positive integer");
|
|
758
|
+
}
|
|
759
|
+
const installer = mainProcessInstaller(rendererWebContentsId);
|
|
760
|
+
const value = await inspector.evaluate(`(${installer})()`);
|
|
761
|
+
if (!isRecord3(value) || value.state !== "ready" || value.reason !== "owned-request-bridge") {
|
|
762
|
+
throw new Error("Renderer draft prewarm policy returned an invalid status");
|
|
763
|
+
}
|
|
764
|
+
return value;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// packages/desktop-control/src/renderer-control-session.ts
|
|
768
|
+
function isRecord4(value) {
|
|
769
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
770
|
+
}
|
|
771
|
+
function safeTargetUrl(value) {
|
|
772
|
+
if (typeof value !== "string" || value.length === 0) return "unknown";
|
|
773
|
+
try {
|
|
774
|
+
const url = new URL(value);
|
|
775
|
+
return url.protocol === "app:" ? `${url.protocol}//${url.host}${url.pathname}` : url.protocol;
|
|
776
|
+
} catch {
|
|
777
|
+
return "unknown";
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
function sleep(milliseconds) {
|
|
781
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
782
|
+
}
|
|
783
|
+
function sameAgents(actual, expected) {
|
|
784
|
+
return actual.length === expected.length && actual.every((agent, index) => agent === expected[index]);
|
|
785
|
+
}
|
|
786
|
+
var RendererAdapterReadinessError = class extends Error {
|
|
787
|
+
constructor(state, reason) {
|
|
788
|
+
super(`Production Renderer Adapter is ${state}: ${reason}`);
|
|
789
|
+
this.state = state;
|
|
790
|
+
this.reason = reason;
|
|
791
|
+
this.name = "RendererAdapterReadinessError";
|
|
792
|
+
}
|
|
793
|
+
state;
|
|
794
|
+
reason;
|
|
795
|
+
};
|
|
796
|
+
function validateBindingStatus(value, expectedAgents) {
|
|
797
|
+
if (!isRecord4(value) || value.version !== 2 || !Array.isArray(value.enabledAgents) || value.enabledAgents.some((agent) => typeof agent !== "string") || !sameAgents(value.enabledAgents, expectedAgents) || !isRecord4(value.adapter)) {
|
|
798
|
+
throw new Error("Production Renderer binding returned an invalid status");
|
|
799
|
+
}
|
|
800
|
+
if (value.adapter.state !== "ready" || typeof value.adapter.reason !== "string") {
|
|
801
|
+
const state = typeof value.adapter.state === "string" ? value.adapter.state : "invalid";
|
|
802
|
+
const reason = typeof value.adapter.reason === "string" ? value.adapter.reason : "unknown";
|
|
803
|
+
throw new RendererAdapterReadinessError(state, reason);
|
|
804
|
+
}
|
|
805
|
+
return value;
|
|
806
|
+
}
|
|
807
|
+
function selectRendererWebContents(contents) {
|
|
808
|
+
const candidates = contents.filter(
|
|
809
|
+
(item) => item.type === "window" && item.surface === "primary" && item.runtime.available && item.runtime.elementCount !== null
|
|
810
|
+
).toSorted(
|
|
811
|
+
(left, right) => (right.runtime.elementCount ?? 0) - (left.runtime.elementCount ?? 0)
|
|
812
|
+
);
|
|
813
|
+
const selected = candidates[0];
|
|
814
|
+
return selected && (selected.runtime.elementCount ?? 0) >= 50 ? selected : null;
|
|
815
|
+
}
|
|
816
|
+
async function waitForRendererTitlePolicyReady(markReadiness, options = {}) {
|
|
817
|
+
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
818
|
+
const pollIntervalMs = options.pollIntervalMs ?? 250;
|
|
819
|
+
const now = options.now ?? Date.now;
|
|
820
|
+
const wait = options.sleep ?? sleep;
|
|
821
|
+
const deadline = now() + timeoutMs;
|
|
822
|
+
let lastError;
|
|
823
|
+
while (now() < deadline) {
|
|
824
|
+
try {
|
|
825
|
+
return await markReadiness();
|
|
826
|
+
} catch (error) {
|
|
827
|
+
lastError = error;
|
|
828
|
+
}
|
|
829
|
+
await wait(pollIntervalMs);
|
|
830
|
+
}
|
|
831
|
+
const detail = lastError instanceof Error ? `: ${lastError.message}` : "";
|
|
832
|
+
throw new Error(`Renderer title policy ownership did not become ready${detail}`);
|
|
833
|
+
}
|
|
834
|
+
async function waitForInspectorTarget(endpoint, options = {}) {
|
|
835
|
+
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
836
|
+
const pollIntervalMs = options.pollIntervalMs ?? 250;
|
|
837
|
+
const deadline = Date.now() + timeoutMs;
|
|
838
|
+
let lastError;
|
|
839
|
+
while (Date.now() < deadline) {
|
|
840
|
+
try {
|
|
841
|
+
const targets = await listCdpTargets(endpoint, options.fetchImpl);
|
|
842
|
+
const inspector = targets.find((target) => target.type === "node");
|
|
843
|
+
if (inspector) return inspector;
|
|
844
|
+
lastError = new Error("Inspector has no Node target");
|
|
845
|
+
} catch (error) {
|
|
846
|
+
lastError = error;
|
|
847
|
+
}
|
|
848
|
+
await sleep(pollIntervalMs);
|
|
849
|
+
}
|
|
850
|
+
const detail = lastError instanceof Error ? `: ${lastError.message}` : "";
|
|
851
|
+
throw new Error(`Electron main-process Inspector did not become ready${detail}`);
|
|
852
|
+
}
|
|
853
|
+
var electronModuleExpression = `(() => {
|
|
854
|
+
const mainModule = process.mainModule;
|
|
855
|
+
if (mainModule != null && typeof mainModule.require === 'function') {
|
|
856
|
+
return mainModule.require('electron');
|
|
857
|
+
}
|
|
858
|
+
const { createRequire } = process.getBuiltinModule('module');
|
|
859
|
+
return createRequire(process.execPath)('electron');
|
|
860
|
+
})()`;
|
|
861
|
+
var webContentsRuntimeExpression = `(() => ({
|
|
862
|
+
elementCount: document.querySelectorAll('*').length,
|
|
863
|
+
editorCandidates: document.querySelectorAll('textarea, [contenteditable="true"], [role="textbox"]').length,
|
|
864
|
+
sendButtonCandidates: [...document.querySelectorAll('button')].filter((button) => button.type === 'submit').length
|
|
865
|
+
}))()`;
|
|
866
|
+
async function inspectElectronWebContents(inspector) {
|
|
867
|
+
const value = await inspector.evaluate(`(async () => {
|
|
868
|
+
const { webContents } = ${electronModuleExpression};
|
|
869
|
+
const result = [];
|
|
870
|
+
for (const contents of webContents.getAllWebContents()) {
|
|
871
|
+
let runtime = { available: false, elementCount: null, editorCandidates: null, sendButtonCandidates: null };
|
|
872
|
+
try {
|
|
873
|
+
const evaluation = contents.executeJavaScript(${JSON.stringify(webContentsRuntimeExpression)}, true);
|
|
874
|
+
const timeout = new Promise((_, reject) => {
|
|
875
|
+
setTimeout(() => reject(new Error('Renderer inspection timed out')), 2_000);
|
|
876
|
+
});
|
|
877
|
+
runtime = { available: true, ...(await Promise.race([evaluation, timeout])) };
|
|
878
|
+
} catch {}
|
|
879
|
+
result.push({
|
|
880
|
+
id: contents.id,
|
|
881
|
+
type: contents.getType(),
|
|
882
|
+
surface: contents.getURL().includes('avatar-overlay') ? 'overlay' : 'primary',
|
|
883
|
+
url: contents.getURL(),
|
|
884
|
+
runtime,
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
return result;
|
|
888
|
+
})()`);
|
|
889
|
+
if (!Array.isArray(value)) throw new Error("Electron webContents inspection returned an array");
|
|
890
|
+
return value.map((item) => {
|
|
891
|
+
if (!isRecord4(item) || !Number.isInteger(item.id) || typeof item.type !== "string" || !["primary", "overlay"].includes(String(item.surface)) || !isRecord4(item.runtime)) {
|
|
892
|
+
throw new Error("Electron webContents inspection returned an invalid item");
|
|
893
|
+
}
|
|
894
|
+
return {
|
|
895
|
+
id: item.id,
|
|
896
|
+
type: item.type,
|
|
897
|
+
surface: item.surface,
|
|
898
|
+
url: safeTargetUrl(item.url),
|
|
899
|
+
runtime: {
|
|
900
|
+
available: item.runtime.available === true,
|
|
901
|
+
elementCount: Number.isInteger(item.runtime.elementCount) ? item.runtime.elementCount : null,
|
|
902
|
+
editorCandidates: Number.isInteger(item.runtime.editorCandidates) ? item.runtime.editorCandidates : null,
|
|
903
|
+
sendButtonCandidates: Number.isInteger(item.runtime.sendButtonCandidates) ? item.runtime.sendButtonCandidates : null
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
async function activateElectronDesktop(inspector) {
|
|
909
|
+
const value = await inspector.evaluate(`(() => {
|
|
910
|
+
const { BrowserWindow } = ${electronModuleExpression};
|
|
911
|
+
const windows = BrowserWindow.getAllWindows().filter((window) => !window.isDestroyed());
|
|
912
|
+
for (const window of windows) {
|
|
913
|
+
if (window.isMinimized()) window.restore();
|
|
914
|
+
window.show();
|
|
915
|
+
window.focus();
|
|
916
|
+
}
|
|
917
|
+
return windows.length;
|
|
918
|
+
})()`);
|
|
919
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
920
|
+
throw new Error("Electron Desktop activation found no live window");
|
|
921
|
+
}
|
|
922
|
+
return value;
|
|
923
|
+
}
|
|
924
|
+
async function executeInWebContents(inspector, rendererWebContentsId, source) {
|
|
925
|
+
return inspector.evaluate(`(async () => {
|
|
926
|
+
const { webContents } = ${electronModuleExpression};
|
|
927
|
+
const contents = webContents.fromId(${rendererWebContentsId});
|
|
928
|
+
if (contents == null || contents.isDestroyed()) throw new Error('Renderer webContents is unavailable');
|
|
929
|
+
const result = await contents.executeJavaScript(${JSON.stringify(source)}, true);
|
|
930
|
+
return result === undefined ? null : result;
|
|
931
|
+
})()`);
|
|
932
|
+
}
|
|
933
|
+
async function reloadRenderer(inspector, rendererWebContentsId) {
|
|
934
|
+
await executeInWebContents(inspector, rendererWebContentsId, "location.reload(); null");
|
|
935
|
+
}
|
|
936
|
+
var defaultOperations = {
|
|
937
|
+
inspect: inspectElectronWebContents,
|
|
938
|
+
installTitlePolicy: installMainProcessTitlePolicy,
|
|
939
|
+
markTitlePolicyReady: markRendererTitlePolicyReady,
|
|
940
|
+
installDraftPrewarmPolicy: installRendererDraftPrewarmPolicy,
|
|
941
|
+
reload: reloadRenderer,
|
|
942
|
+
execute: executeInWebContents,
|
|
943
|
+
readBinding: (inspector, rendererWebContentsId) => executeInWebContents(
|
|
944
|
+
inspector,
|
|
945
|
+
rendererWebContentsId,
|
|
946
|
+
"window.__codexhostRendererBindingProbeV1?.status() ?? null"
|
|
947
|
+
),
|
|
948
|
+
readTitlePolicyCounters: readMainProcessTitlePolicyCounters
|
|
949
|
+
};
|
|
950
|
+
async function waitForRenderer(inspector, operations, timeoutMs, pollIntervalMs) {
|
|
951
|
+
const deadline = Date.now() + timeoutMs;
|
|
952
|
+
let inventory = [];
|
|
953
|
+
while (Date.now() < deadline) {
|
|
954
|
+
inventory = await operations.inspect(inspector);
|
|
955
|
+
const renderer = selectRendererWebContents(inventory);
|
|
956
|
+
if (renderer) return { inventory, renderer };
|
|
957
|
+
await sleep(pollIntervalMs);
|
|
958
|
+
}
|
|
959
|
+
throw new Error(
|
|
960
|
+
`Inspector did not find a populated Electron Renderer (${inventory.length} seen)`
|
|
961
|
+
);
|
|
962
|
+
}
|
|
963
|
+
async function waitForBinding(inspector, operations, rendererWebContentsId, enabledAgents, timeoutMs, pollIntervalMs) {
|
|
964
|
+
const deadline = Date.now() + timeoutMs;
|
|
965
|
+
let lastError;
|
|
966
|
+
while (Date.now() < deadline) {
|
|
967
|
+
try {
|
|
968
|
+
const value = await operations.readBinding(inspector, rendererWebContentsId);
|
|
969
|
+
if (value !== null) return validateBindingStatus(value, enabledAgents);
|
|
970
|
+
} catch (error) {
|
|
971
|
+
lastError = error;
|
|
972
|
+
if (error instanceof RendererAdapterReadinessError && error.state !== "installing") {
|
|
973
|
+
throw error;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
await sleep(pollIntervalMs);
|
|
977
|
+
}
|
|
978
|
+
const detail = lastError instanceof Error ? `: ${lastError.message}` : "";
|
|
979
|
+
throw new Error(`Production Renderer binding did not become ready${detail}`);
|
|
980
|
+
}
|
|
981
|
+
var InstalledRendererControlSession = class {
|
|
982
|
+
constructor(inspector, rendererSource, enabledAgents, timeoutMs, pollIntervalMs, operations, snapshot) {
|
|
983
|
+
this.inspector = inspector;
|
|
984
|
+
this.rendererSource = rendererSource;
|
|
985
|
+
this.enabledAgents = enabledAgents;
|
|
986
|
+
this.timeoutMs = timeoutMs;
|
|
987
|
+
this.pollIntervalMs = pollIntervalMs;
|
|
988
|
+
this.operations = operations;
|
|
989
|
+
this.#snapshot = snapshot;
|
|
990
|
+
}
|
|
991
|
+
inspector;
|
|
992
|
+
rendererSource;
|
|
993
|
+
enabledAgents;
|
|
994
|
+
timeoutMs;
|
|
995
|
+
pollIntervalMs;
|
|
996
|
+
operations;
|
|
997
|
+
#closed = false;
|
|
998
|
+
#snapshot;
|
|
999
|
+
get snapshot() {
|
|
1000
|
+
return this.#snapshot;
|
|
1001
|
+
}
|
|
1002
|
+
async ensureInstalled() {
|
|
1003
|
+
if (this.#closed) throw new Error("Renderer Control Session is closed");
|
|
1004
|
+
const selected = await waitForRenderer(
|
|
1005
|
+
this.inspector,
|
|
1006
|
+
this.operations,
|
|
1007
|
+
this.timeoutMs,
|
|
1008
|
+
this.pollIntervalMs
|
|
1009
|
+
);
|
|
1010
|
+
const existing = await this.operations.readBinding(this.inspector, selected.renderer.id).catch(() => null);
|
|
1011
|
+
if (existing !== null) {
|
|
1012
|
+
const binding2 = validateBindingStatus(existing, this.enabledAgents);
|
|
1013
|
+
this.#snapshot = { ...this.#snapshot, ...selected, binding: binding2 };
|
|
1014
|
+
return this.#snapshot;
|
|
1015
|
+
}
|
|
1016
|
+
const titlePolicyReadiness = await waitForRendererTitlePolicyReady(
|
|
1017
|
+
() => this.operations.markTitlePolicyReady(this.inspector),
|
|
1018
|
+
{ timeoutMs: this.timeoutMs, pollIntervalMs: this.pollIntervalMs }
|
|
1019
|
+
);
|
|
1020
|
+
const draftPrewarmPolicy = await this.operations.installDraftPrewarmPolicy(
|
|
1021
|
+
this.inspector,
|
|
1022
|
+
selected.renderer.id
|
|
1023
|
+
);
|
|
1024
|
+
await this.operations.execute(this.inspector, selected.renderer.id, this.rendererSource);
|
|
1025
|
+
const binding = await waitForBinding(
|
|
1026
|
+
this.inspector,
|
|
1027
|
+
this.operations,
|
|
1028
|
+
selected.renderer.id,
|
|
1029
|
+
this.enabledAgents,
|
|
1030
|
+
this.timeoutMs,
|
|
1031
|
+
this.pollIntervalMs
|
|
1032
|
+
);
|
|
1033
|
+
this.#snapshot = {
|
|
1034
|
+
...this.#snapshot,
|
|
1035
|
+
...selected,
|
|
1036
|
+
titlePolicyReadiness,
|
|
1037
|
+
draftPrewarmPolicy,
|
|
1038
|
+
binding
|
|
1039
|
+
};
|
|
1040
|
+
return this.#snapshot;
|
|
1041
|
+
}
|
|
1042
|
+
activateDesktop() {
|
|
1043
|
+
if (this.#closed) return Promise.reject(new Error("Renderer Control Session is closed"));
|
|
1044
|
+
return activateElectronDesktop(this.inspector);
|
|
1045
|
+
}
|
|
1046
|
+
executeRenderer(expression) {
|
|
1047
|
+
if (this.#closed) return Promise.reject(new Error("Renderer Control Session is closed"));
|
|
1048
|
+
return this.operations.execute(
|
|
1049
|
+
this.inspector,
|
|
1050
|
+
this.#snapshot.renderer.id,
|
|
1051
|
+
expression
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
readTitlePolicyCounters() {
|
|
1055
|
+
if (this.#closed) return Promise.reject(new Error("Renderer Control Session is closed"));
|
|
1056
|
+
return this.operations.readTitlePolicyCounters(this.inspector);
|
|
1057
|
+
}
|
|
1058
|
+
close() {
|
|
1059
|
+
if (this.#closed) return;
|
|
1060
|
+
this.#closed = true;
|
|
1061
|
+
this.inspector.close();
|
|
1062
|
+
}
|
|
1063
|
+
};
|
|
1064
|
+
async function createRendererControlSession(options) {
|
|
1065
|
+
const enabledAgents = options.enabledAgents ?? ["codex", "pi"];
|
|
1066
|
+
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
1067
|
+
const pollIntervalMs = options.pollIntervalMs ?? 250;
|
|
1068
|
+
const operations = options.operations ?? defaultOperations;
|
|
1069
|
+
const initial = await waitForRenderer(options.inspector, operations, timeoutMs, pollIntervalMs);
|
|
1070
|
+
const titlePolicy = await operations.installTitlePolicy(options.inspector);
|
|
1071
|
+
await operations.reload(options.inspector, initial.renderer.id);
|
|
1072
|
+
const selected = await waitForRenderer(options.inspector, operations, timeoutMs, pollIntervalMs);
|
|
1073
|
+
const titlePolicyReadiness = await waitForRendererTitlePolicyReady(
|
|
1074
|
+
() => operations.markTitlePolicyReady(options.inspector),
|
|
1075
|
+
{ timeoutMs, pollIntervalMs }
|
|
1076
|
+
);
|
|
1077
|
+
const draftPrewarmPolicy = await operations.installDraftPrewarmPolicy(
|
|
1078
|
+
options.inspector,
|
|
1079
|
+
selected.renderer.id
|
|
1080
|
+
);
|
|
1081
|
+
await operations.execute(options.inspector, selected.renderer.id, options.rendererSource);
|
|
1082
|
+
const binding = await waitForBinding(
|
|
1083
|
+
options.inspector,
|
|
1084
|
+
operations,
|
|
1085
|
+
selected.renderer.id,
|
|
1086
|
+
enabledAgents,
|
|
1087
|
+
timeoutMs,
|
|
1088
|
+
pollIntervalMs
|
|
1089
|
+
);
|
|
1090
|
+
return new InstalledRendererControlSession(
|
|
1091
|
+
options.inspector,
|
|
1092
|
+
options.rendererSource,
|
|
1093
|
+
enabledAgents,
|
|
1094
|
+
timeoutMs,
|
|
1095
|
+
pollIntervalMs,
|
|
1096
|
+
operations,
|
|
1097
|
+
{
|
|
1098
|
+
...selected,
|
|
1099
|
+
titlePolicy,
|
|
1100
|
+
titlePolicyReadiness,
|
|
1101
|
+
draftPrewarmPolicy,
|
|
1102
|
+
binding
|
|
1103
|
+
}
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
async function installRendererControlSession(options) {
|
|
1107
|
+
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
1108
|
+
const pollIntervalMs = options.pollIntervalMs ?? 250;
|
|
1109
|
+
const target = await waitForInspectorTarget(options.inspectorEndpoint, {
|
|
1110
|
+
timeoutMs,
|
|
1111
|
+
pollIntervalMs
|
|
1112
|
+
});
|
|
1113
|
+
const inspector = await CdpClient.connect(target.webSocketDebuggerUrl);
|
|
1114
|
+
try {
|
|
1115
|
+
await inspector.command("Runtime.enable");
|
|
1116
|
+
return await createRendererControlSession({
|
|
1117
|
+
...options,
|
|
1118
|
+
inspector,
|
|
1119
|
+
timeoutMs,
|
|
1120
|
+
pollIntervalMs
|
|
1121
|
+
});
|
|
1122
|
+
} catch (error) {
|
|
1123
|
+
inspector.close();
|
|
1124
|
+
throw error;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// packages/desktop-control/src/production-controller.ts
|
|
1129
|
+
var PRODUCTION_INSTALL_TIMEOUT_MS = 9e4;
|
|
1130
|
+
var TRANSIENT_INSTALL_ATTEMPTS = 3;
|
|
1131
|
+
var TRANSIENT_INSTALL_RETRY_MS = 250;
|
|
1132
|
+
var defaultDependencies = {
|
|
1133
|
+
readRenderer: (filePath) => readFile(filePath, "utf8"),
|
|
1134
|
+
install: installRendererControlSession,
|
|
1135
|
+
startAttachmentServer: startControllerAttachmentServer,
|
|
1136
|
+
ready: () => {
|
|
1137
|
+
process.stdout.write("ready\n");
|
|
1138
|
+
},
|
|
1139
|
+
sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
|
|
1140
|
+
monitorIntervalMs: 500
|
|
1141
|
+
};
|
|
1142
|
+
function inspectorEndpoint(value) {
|
|
1143
|
+
const url = new URL(value);
|
|
1144
|
+
if (url.protocol !== "http:" || !["127.0.0.1", "localhost", "[::1]"].includes(url.hostname) || !url.port || url.username || url.password || url.pathname !== "/" && url.pathname !== "" || url.search || url.hash) {
|
|
1145
|
+
throw new Error("--inspector-endpoint must be a loopback HTTP origin with an explicit port");
|
|
1146
|
+
}
|
|
1147
|
+
return url.origin;
|
|
1148
|
+
}
|
|
1149
|
+
function parseDesktopControllerArguments(arguments_) {
|
|
1150
|
+
let endpoint;
|
|
1151
|
+
let rendererPath;
|
|
1152
|
+
let defaultAgent;
|
|
1153
|
+
let attachmentPort;
|
|
1154
|
+
let attachmentNonce;
|
|
1155
|
+
for (let index = 0; index < arguments_.length; index += 1) {
|
|
1156
|
+
const argument = arguments_[index];
|
|
1157
|
+
const value = arguments_[index + 1];
|
|
1158
|
+
if (argument === "--inspector-endpoint") {
|
|
1159
|
+
if (endpoint !== void 0) throw new Error("--inspector-endpoint may only be provided once");
|
|
1160
|
+
if (!value) throw new Error("--inspector-endpoint requires a value");
|
|
1161
|
+
endpoint = inspectorEndpoint(value);
|
|
1162
|
+
index += 1;
|
|
1163
|
+
continue;
|
|
1164
|
+
}
|
|
1165
|
+
if (argument === "--renderer") {
|
|
1166
|
+
if (rendererPath !== void 0) throw new Error("--renderer may only be provided once");
|
|
1167
|
+
if (!value) throw new Error("--renderer requires a value");
|
|
1168
|
+
if (!path.isAbsolute(value)) throw new Error("--renderer must be an absolute path");
|
|
1169
|
+
rendererPath = path.normalize(value);
|
|
1170
|
+
index += 1;
|
|
1171
|
+
continue;
|
|
1172
|
+
}
|
|
1173
|
+
if (argument === "--default-agent") {
|
|
1174
|
+
if (defaultAgent !== void 0) throw new Error("--default-agent may only be provided once");
|
|
1175
|
+
if (value !== "codex" && value !== "pi") {
|
|
1176
|
+
throw new Error("--default-agent must be 'codex' or 'pi'");
|
|
1177
|
+
}
|
|
1178
|
+
defaultAgent = value;
|
|
1179
|
+
index += 1;
|
|
1180
|
+
continue;
|
|
1181
|
+
}
|
|
1182
|
+
if (argument === "--attachment-port") {
|
|
1183
|
+
if (attachmentPort !== void 0) {
|
|
1184
|
+
throw new Error("--attachment-port may only be provided once");
|
|
1185
|
+
}
|
|
1186
|
+
const port = Number(value);
|
|
1187
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
1188
|
+
throw new Error("--attachment-port must be a valid TCP port");
|
|
1189
|
+
}
|
|
1190
|
+
attachmentPort = port;
|
|
1191
|
+
index += 1;
|
|
1192
|
+
continue;
|
|
1193
|
+
}
|
|
1194
|
+
if (argument === "--attachment-nonce") {
|
|
1195
|
+
if (attachmentNonce !== void 0) {
|
|
1196
|
+
throw new Error("--attachment-nonce may only be provided once");
|
|
1197
|
+
}
|
|
1198
|
+
if (value === void 0 || !/^[0-9a-f]{32}$/.test(value)) {
|
|
1199
|
+
throw new Error("--attachment-nonce must be 32 lowercase hexadecimal characters");
|
|
1200
|
+
}
|
|
1201
|
+
attachmentNonce = value;
|
|
1202
|
+
index += 1;
|
|
1203
|
+
continue;
|
|
1204
|
+
}
|
|
1205
|
+
throw new Error(`unknown Desktop Controller option: ${argument}`);
|
|
1206
|
+
}
|
|
1207
|
+
if (endpoint === void 0) throw new Error("--inspector-endpoint is required");
|
|
1208
|
+
if (rendererPath === void 0) throw new Error("--renderer is required");
|
|
1209
|
+
if (defaultAgent === void 0) throw new Error("--default-agent is required");
|
|
1210
|
+
if (attachmentPort === void 0) throw new Error("--attachment-port is required");
|
|
1211
|
+
if (attachmentNonce === void 0) throw new Error("--attachment-nonce is required");
|
|
1212
|
+
return {
|
|
1213
|
+
inspectorEndpoint: endpoint,
|
|
1214
|
+
rendererPath,
|
|
1215
|
+
defaultAgent,
|
|
1216
|
+
attachmentPort,
|
|
1217
|
+
attachmentNonce
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
function isTransientElectronInstallError(error) {
|
|
1221
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1222
|
+
return message.includes("Uncaught (in promise)") || message.includes("Execution context was destroyed") || message.includes("Promise was collected");
|
|
1223
|
+
}
|
|
1224
|
+
async function installProductionSession(options, dependencies) {
|
|
1225
|
+
for (let attempt = 1; attempt <= TRANSIENT_INSTALL_ATTEMPTS; attempt += 1) {
|
|
1226
|
+
try {
|
|
1227
|
+
return await dependencies.install(options);
|
|
1228
|
+
} catch (error) {
|
|
1229
|
+
if (attempt === TRANSIENT_INSTALL_ATTEMPTS || !isTransientElectronInstallError(error)) {
|
|
1230
|
+
throw error;
|
|
1231
|
+
}
|
|
1232
|
+
await dependencies.sleep(TRANSIENT_INSTALL_RETRY_MS);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
throw new Error("Desktop Controller exhausted Renderer installation attempts");
|
|
1236
|
+
}
|
|
1237
|
+
async function runDesktopController(options, signal, dependencies = defaultDependencies) {
|
|
1238
|
+
const rendererSource = await dependencies.readRenderer(options.rendererPath);
|
|
1239
|
+
if (rendererSource.trim().length === 0) throw new Error("production Renderer Bundle is empty");
|
|
1240
|
+
const configuration = `Object.defineProperty(window, "__codexhostProductionConfigV1", { configurable: true, value: { defaultAgent: ${JSON.stringify(options.defaultAgent)} } });`;
|
|
1241
|
+
const session = await installProductionSession(
|
|
1242
|
+
{
|
|
1243
|
+
inspectorEndpoint: options.inspectorEndpoint,
|
|
1244
|
+
rendererSource: `${configuration}
|
|
1245
|
+
${rendererSource}`,
|
|
1246
|
+
enabledAgents: ["codex", "pi", "claude-code"],
|
|
1247
|
+
timeoutMs: PRODUCTION_INSTALL_TIMEOUT_MS
|
|
1248
|
+
},
|
|
1249
|
+
dependencies
|
|
1250
|
+
);
|
|
1251
|
+
let operation = Promise.resolve(void 0);
|
|
1252
|
+
const useSession = (callback) => {
|
|
1253
|
+
const next = operation.then(callback, callback);
|
|
1254
|
+
operation = next.then(
|
|
1255
|
+
() => void 0,
|
|
1256
|
+
() => void 0
|
|
1257
|
+
);
|
|
1258
|
+
return next;
|
|
1259
|
+
};
|
|
1260
|
+
let attachmentServer;
|
|
1261
|
+
try {
|
|
1262
|
+
attachmentServer = await dependencies.startAttachmentServer({
|
|
1263
|
+
port: options.attachmentPort,
|
|
1264
|
+
nonce: options.attachmentNonce,
|
|
1265
|
+
attach: () => useSession(async () => {
|
|
1266
|
+
await session.ensureInstalled();
|
|
1267
|
+
await session.activateDesktop();
|
|
1268
|
+
})
|
|
1269
|
+
});
|
|
1270
|
+
dependencies.ready();
|
|
1271
|
+
while (!signal.aborted) {
|
|
1272
|
+
await dependencies.sleep(dependencies.monitorIntervalMs);
|
|
1273
|
+
if (!signal.aborted) await useSession(() => session.ensureInstalled());
|
|
1274
|
+
}
|
|
1275
|
+
} finally {
|
|
1276
|
+
await attachmentServer?.close();
|
|
1277
|
+
await operation;
|
|
1278
|
+
session.close();
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
// packages/desktop-control/src/release-main.ts
|
|
1283
|
+
var abort = new AbortController();
|
|
1284
|
+
var stop = () => abort.abort();
|
|
1285
|
+
process.once("SIGINT", stop);
|
|
1286
|
+
process.once("SIGTERM", stop);
|
|
1287
|
+
try {
|
|
1288
|
+
await runDesktopController(parseDesktopControllerArguments(process.argv.slice(2)), abort.signal);
|
|
1289
|
+
} catch (error) {
|
|
1290
|
+
console.error(
|
|
1291
|
+
`codexhost Desktop Controller: ${error instanceof Error ? error.message : String(error)}`
|
|
1292
|
+
);
|
|
1293
|
+
process.exitCode = 1;
|
|
1294
|
+
} finally {
|
|
1295
|
+
process.removeListener("SIGINT", stop);
|
|
1296
|
+
process.removeListener("SIGTERM", stop);
|
|
1297
|
+
}
|