@codexhost/cli-win32-arm64 0.10.2 → 0.12.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 +1 -1
- package/THIRD_PARTY_NOTICES.txt +4 -0
- package/app/codexhost-distribution.json +1 -1
- package/app/console-server.mjs +18001 -0
- package/app/console-web.js +27674 -0
- package/app/desktop-controller.mjs +452 -110
- package/app/host-runtime.mjs +6670 -1272
- package/app/plugins/antigravity/plugin.mjs +686 -305
- package/app/plugins/claude-code/plugin.mjs +2283 -1450
- package/app/plugins/codebuddy/plugin.mjs +1272 -506
- package/app/plugins/cursor-cli/manifest.json +1 -1
- package/app/plugins/cursor-cli/plugin.mjs +1295 -497
- package/app/plugins/deepseek-harness/plugin.mjs +4718 -4925
- package/app/plugins/enabled.json +2 -1
- package/app/plugins/grok/plugin.mjs +1012 -369
- package/app/plugins/hermes/plugin.mjs +18587 -22714
- package/app/plugins/kimi-code/plugin.mjs +1283 -355
- package/app/plugins/kiro-cli/plugin.mjs +840 -299
- package/app/plugins/omp/plugin.mjs +1045 -328
- package/app/plugins/opencode/plugin.mjs +5341 -1686
- package/app/plugins/pi/plugin.mjs +1343 -534
- package/app/plugins/qoder/plugin.mjs +913 -258
- package/app/plugins/qoder-cn/plugin.mjs +712 -49
- package/app/plugins/workbuddy/plugin.mjs +531 -44
- package/app/plugins/zcode/assets/icon.svg +8 -0
- package/app/plugins/zcode/manifest.json +14 -0
- package/app/plugins/zcode/plugin.mjs +19202 -0
- package/app/renderer-extension.js +5011 -1899
- package/bin/codexhost.exe +0 -0
- package/libexec/codexhost-node-repl.exe +0 -0
- package/libexec/codexhost-shim.exe +0 -0
- package/libexec/codexhost-updater.exe +0 -0
- package/licenses/OpenCode-v2-Client-LICENSE.txt +21 -0
- package/package.json +1 -1
|
@@ -1,93 +1,71 @@
|
|
|
1
1
|
// packages/desktop-control/src/production-controller.ts
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
|
-
import
|
|
3
|
+
import path2 from "node:path";
|
|
4
4
|
|
|
5
|
-
// packages/desktop-control/src/
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
5
|
+
// packages/desktop-control/src/renderer-local-page.ts
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
async function openRendererLocalPage(execute, url, targets) {
|
|
8
|
+
const conversationId = await execute(
|
|
9
|
+
"window.__codexhostRendererBindingProbeV1?.currentThreadId?.() ?? null"
|
|
10
|
+
);
|
|
11
|
+
if (!conversationId) throw new Error("Open a Codex task before starting verification");
|
|
12
|
+
const browserTabId = `manual:${randomUUID()}`;
|
|
13
|
+
const post = (value) => execute(`window.postMessage(${JSON.stringify(value)}, '*'); null`);
|
|
14
|
+
try {
|
|
15
|
+
await post({
|
|
16
|
+
type: "toggle-browser-panel",
|
|
17
|
+
conversationId,
|
|
18
|
+
browserTabId,
|
|
19
|
+
url,
|
|
20
|
+
open: true,
|
|
21
|
+
active: false,
|
|
22
|
+
source: "browser_use",
|
|
23
|
+
initiator: "codexhost_local_page"
|
|
19
24
|
});
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
throw new Error("attachment nonce must be 32 lowercase hexadecimal characters");
|
|
25
|
+
const deadline = Date.now() + 5e3;
|
|
26
|
+
while (!(await targets()).some((target) => target.url === url)) {
|
|
27
|
+
if (Date.now() >= deadline)
|
|
28
|
+
throw new Error("Codex in-app browser did not load the local page");
|
|
29
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
30
|
+
}
|
|
31
|
+
} catch (error) {
|
|
32
|
+
await post({ type: "close-browser-tab", conversationId, browserTabId });
|
|
33
|
+
throw error;
|
|
30
34
|
}
|
|
31
|
-
const sockets = /* @__PURE__ */ new Set();
|
|
32
|
-
let attachment;
|
|
33
|
-
const server = createServer((socket) => {
|
|
34
|
-
sockets.add(socket);
|
|
35
|
-
socket.once("close", () => sockets.delete(socket));
|
|
36
|
-
socket.setEncoding("utf8");
|
|
37
|
-
socket.setTimeout(5e3, () => socket.destroy());
|
|
38
|
-
let request = "";
|
|
39
|
-
let handled = false;
|
|
40
|
-
socket.on("data", (chunk) => {
|
|
41
|
-
if (handled) return;
|
|
42
|
-
request += chunk;
|
|
43
|
-
if (request.length > MAX_REQUEST_BYTES) {
|
|
44
|
-
handled = true;
|
|
45
|
-
respond(socket, "rejected");
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
const newline = request.indexOf("\n");
|
|
49
|
-
if (newline < 0) return;
|
|
50
|
-
handled = true;
|
|
51
|
-
const line = request.slice(0, newline).replace(/\r$/, "");
|
|
52
|
-
if (line === `ATTACH ${options.nonce}`) {
|
|
53
|
-
if (attachment) {
|
|
54
|
-
respond(socket, "busy");
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
socket.setTimeout(0);
|
|
58
|
-
const current = Promise.resolve().then(() => options.attach());
|
|
59
|
-
attachment = current;
|
|
60
|
-
void current.then(
|
|
61
|
-
() => respond(socket, "ready"),
|
|
62
|
-
() => respond(socket, "failed")
|
|
63
|
-
).finally(() => {
|
|
64
|
-
if (attachment === current) attachment = void 0;
|
|
65
|
-
});
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
respond(socket, "rejected");
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
await new Promise((resolve, reject) => {
|
|
72
|
-
const onError = (error) => reject(error);
|
|
73
|
-
server.once("error", onError);
|
|
74
|
-
server.listen(options.port, "127.0.0.1", () => {
|
|
75
|
-
server.off("error", onError);
|
|
76
|
-
resolve();
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
35
|
let closed = false;
|
|
80
36
|
return {
|
|
37
|
+
async show() {
|
|
38
|
+
if (closed) return;
|
|
39
|
+
const current = await execute(
|
|
40
|
+
"window.__codexhostRendererBindingProbeV1?.currentThreadId?.() ?? null"
|
|
41
|
+
);
|
|
42
|
+
if (current !== conversationId)
|
|
43
|
+
await post({ type: "navigate-to-route", path: `/local/${conversationId}` });
|
|
44
|
+
await post({
|
|
45
|
+
type: "toggle-browser-panel",
|
|
46
|
+
conversationId,
|
|
47
|
+
browserTabId,
|
|
48
|
+
open: true,
|
|
49
|
+
source: "manual",
|
|
50
|
+
initiator: "codexhost_local_page"
|
|
51
|
+
});
|
|
52
|
+
},
|
|
81
53
|
async close() {
|
|
82
54
|
if (closed) return;
|
|
83
55
|
closed = true;
|
|
84
|
-
|
|
85
|
-
await closeServer(server);
|
|
56
|
+
await post({ type: "close-browser-tab", conversationId, browserTabId });
|
|
86
57
|
}
|
|
87
58
|
};
|
|
88
59
|
}
|
|
89
60
|
|
|
90
61
|
// packages/desktop-control/src/cdp-client.ts
|
|
62
|
+
function cdpExceptionMessage(details, fallback) {
|
|
63
|
+
const exception = details.exception;
|
|
64
|
+
if (isRecord(exception) && typeof exception.description === "string" && exception.description) {
|
|
65
|
+
return exception.description;
|
|
66
|
+
}
|
|
67
|
+
return typeof details.text === "string" && details.text ? details.text : fallback;
|
|
68
|
+
}
|
|
91
69
|
function isRecord(value) {
|
|
92
70
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
93
71
|
}
|
|
@@ -120,8 +98,8 @@ function parseTarget(value) {
|
|
|
120
98
|
webSocketDebuggerUrl
|
|
121
99
|
};
|
|
122
100
|
}
|
|
123
|
-
function defaultFetch(url) {
|
|
124
|
-
return fetch(url);
|
|
101
|
+
function defaultFetch(url, init) {
|
|
102
|
+
return fetch(url, init);
|
|
125
103
|
}
|
|
126
104
|
function defaultSocketFactory(url) {
|
|
127
105
|
return new WebSocket(url);
|
|
@@ -148,10 +126,10 @@ function parseResponse(value) {
|
|
|
148
126
|
}
|
|
149
127
|
return response;
|
|
150
128
|
}
|
|
151
|
-
async function listCdpTargets(endpoint, fetchImpl = defaultFetch) {
|
|
129
|
+
async function listCdpTargets(endpoint, fetchImpl = defaultFetch, signal) {
|
|
152
130
|
const baseUrl = loopbackUrl(endpoint, ["http:", "https:"]);
|
|
153
131
|
const targetsUrl = new URL("/json/list", baseUrl).toString();
|
|
154
|
-
const response = await fetchImpl(targetsUrl);
|
|
132
|
+
const response = await fetchImpl(targetsUrl, signal ? { signal } : void 0);
|
|
155
133
|
if (!response.ok) throw new Error(`CDP target discovery failed with HTTP ${response.status}`);
|
|
156
134
|
const value = await response.json();
|
|
157
135
|
if (!Array.isArray(value)) throw new Error("CDP target discovery did not return an array");
|
|
@@ -244,8 +222,7 @@ var CdpClient = class _CdpClient {
|
|
|
244
222
|
});
|
|
245
223
|
if (!isRecord(response)) throw new Error("Runtime.evaluate returned an invalid result");
|
|
246
224
|
if (isRecord(response.exceptionDetails)) {
|
|
247
|
-
|
|
248
|
-
throw new Error(text);
|
|
225
|
+
throw new Error(cdpExceptionMessage(response.exceptionDetails, "Renderer evaluation failed"));
|
|
249
226
|
}
|
|
250
227
|
if (!isRecord(response.result) || !("value" in response.result)) {
|
|
251
228
|
throw new Error("Runtime.evaluate did not return a value");
|
|
@@ -296,16 +273,282 @@ var CdpClient = class _CdpClient {
|
|
|
296
273
|
}
|
|
297
274
|
};
|
|
298
275
|
|
|
276
|
+
// packages/desktop-control/src/controller-attachment-server.ts
|
|
277
|
+
import { createServer } from "node:net";
|
|
278
|
+
|
|
279
|
+
// packages/desktop-control/src/local-page-control.ts
|
|
280
|
+
function isLocalPageUrl(value) {
|
|
281
|
+
try {
|
|
282
|
+
const url = new URL(value);
|
|
283
|
+
return url.protocol === "http:" && url.hostname === "127.0.0.1" && url.port !== "" && url.pathname === "/" && !url.username && !url.password && !url.hash;
|
|
284
|
+
} catch {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
async function serveLocalPage(socket, url, open) {
|
|
289
|
+
let page;
|
|
290
|
+
let buffer = "";
|
|
291
|
+
let operation = Promise.resolve();
|
|
292
|
+
const disconnected = Promise.withResolvers();
|
|
293
|
+
socket.once("close", () => disconnected.resolve(void 0));
|
|
294
|
+
socket.setTimeout(0);
|
|
295
|
+
const receive = (chunk) => {
|
|
296
|
+
buffer += chunk;
|
|
297
|
+
if (buffer.length > 64) {
|
|
298
|
+
socket.destroy();
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
let end;
|
|
302
|
+
while ((end = buffer.indexOf("\n")) >= 0) {
|
|
303
|
+
const command = buffer.slice(0, end);
|
|
304
|
+
buffer = buffer.slice(end + 1);
|
|
305
|
+
operation = operation.then(async () => {
|
|
306
|
+
if (socket.destroyed) return;
|
|
307
|
+
if (command === "SHOW") {
|
|
308
|
+
await page?.show();
|
|
309
|
+
socket.write("ready\n");
|
|
310
|
+
} else if (command === "CLOSE") {
|
|
311
|
+
await page?.close();
|
|
312
|
+
socket.end("closed\n");
|
|
313
|
+
} else socket.destroy();
|
|
314
|
+
}).catch(() => {
|
|
315
|
+
socket.destroy();
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
try {
|
|
320
|
+
page = await open(url);
|
|
321
|
+
if (socket.destroyed) return;
|
|
322
|
+
socket.on("data", receive);
|
|
323
|
+
socket.write("ready\n");
|
|
324
|
+
await disconnected.promise;
|
|
325
|
+
await operation;
|
|
326
|
+
} catch {
|
|
327
|
+
if (!socket.destroyed) socket.end("failed\n");
|
|
328
|
+
} finally {
|
|
329
|
+
socket.off("data", receive);
|
|
330
|
+
await page?.close();
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// packages/desktop-control/src/controller-attachment-server.ts
|
|
335
|
+
var MAX_REQUEST_BYTES = 4096;
|
|
336
|
+
function validPort(value) {
|
|
337
|
+
return Number.isInteger(value) && value >= 1 && value <= 65535;
|
|
338
|
+
}
|
|
339
|
+
function validNonce(value) {
|
|
340
|
+
return /^[0-9a-f]{32}$/.test(value);
|
|
341
|
+
}
|
|
342
|
+
function closeServer(server) {
|
|
343
|
+
return new Promise((resolve, reject) => {
|
|
344
|
+
server.close((error) => {
|
|
345
|
+
if (error) reject(error);
|
|
346
|
+
else resolve();
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
function respond(socket, value) {
|
|
351
|
+
socket.end(`${value}
|
|
352
|
+
`);
|
|
353
|
+
}
|
|
354
|
+
async function startControllerAttachmentServer(options) {
|
|
355
|
+
if (!validPort(options.port)) throw new Error("attachment port must be a valid TCP port");
|
|
356
|
+
if (!validNonce(options.nonce)) {
|
|
357
|
+
throw new Error("attachment nonce must be 32 lowercase hexadecimal characters");
|
|
358
|
+
}
|
|
359
|
+
const sockets = /* @__PURE__ */ new Set();
|
|
360
|
+
const pages = /* @__PURE__ */ new Set();
|
|
361
|
+
let attachment;
|
|
362
|
+
const server = createServer((socket) => {
|
|
363
|
+
sockets.add(socket);
|
|
364
|
+
socket.on("error", () => socket.destroy());
|
|
365
|
+
socket.once("close", () => sockets.delete(socket));
|
|
366
|
+
socket.setEncoding("utf8");
|
|
367
|
+
socket.setTimeout(5e3, () => socket.destroy());
|
|
368
|
+
let request = "";
|
|
369
|
+
let handled = false;
|
|
370
|
+
const receive = (chunk) => {
|
|
371
|
+
if (handled) return;
|
|
372
|
+
request += chunk;
|
|
373
|
+
if (request.length > MAX_REQUEST_BYTES) {
|
|
374
|
+
handled = true;
|
|
375
|
+
respond(socket, "rejected");
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const newline = request.indexOf("\n");
|
|
379
|
+
if (newline < 0) return;
|
|
380
|
+
handled = true;
|
|
381
|
+
const line = request.slice(0, newline).replace(/\r$/, "");
|
|
382
|
+
const prefix = `PAGE ${options.nonce} `;
|
|
383
|
+
if (options.openLocalPage && line.startsWith(prefix)) {
|
|
384
|
+
const url = line.slice(prefix.length);
|
|
385
|
+
if (!isLocalPageUrl(url) || request.slice(newline + 1) !== "") {
|
|
386
|
+
respond(socket, "rejected");
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
socket.off("data", receive);
|
|
390
|
+
const page = serveLocalPage(socket, url, options.openLocalPage).catch(
|
|
391
|
+
() => socket.destroy()
|
|
392
|
+
);
|
|
393
|
+
const completion = page.then(() => void 0);
|
|
394
|
+
pages.add(completion);
|
|
395
|
+
void completion.finally(() => pages.delete(completion));
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (line === `ATTACH ${options.nonce}`) {
|
|
399
|
+
if (attachment) {
|
|
400
|
+
respond(socket, "busy");
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
socket.setTimeout(0);
|
|
404
|
+
const current = Promise.resolve().then(() => options.attach());
|
|
405
|
+
attachment = current;
|
|
406
|
+
void current.then(
|
|
407
|
+
() => respond(socket, "ready"),
|
|
408
|
+
() => respond(socket, "failed")
|
|
409
|
+
).finally(() => {
|
|
410
|
+
if (attachment === current) attachment = void 0;
|
|
411
|
+
});
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
respond(socket, "rejected");
|
|
415
|
+
};
|
|
416
|
+
socket.on("data", receive);
|
|
417
|
+
});
|
|
418
|
+
await new Promise((resolve, reject) => {
|
|
419
|
+
const onError = (error) => reject(error);
|
|
420
|
+
server.once("error", onError);
|
|
421
|
+
server.listen(options.port, "127.0.0.1", () => {
|
|
422
|
+
server.off("error", onError);
|
|
423
|
+
resolve();
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
let closed = false;
|
|
427
|
+
return {
|
|
428
|
+
async close() {
|
|
429
|
+
if (closed) return;
|
|
430
|
+
closed = true;
|
|
431
|
+
const serverClosed = closeServer(server);
|
|
432
|
+
for (const socket of sockets) socket.destroy();
|
|
433
|
+
await Promise.all(pages);
|
|
434
|
+
await serverClosed;
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// packages/desktop-control/src/controller-status.ts
|
|
440
|
+
import { randomBytes } from "node:crypto";
|
|
441
|
+
import { mkdir, rename, rm, writeFile } from "node:fs/promises";
|
|
442
|
+
import os from "node:os";
|
|
443
|
+
import path from "node:path";
|
|
444
|
+
var DESKTOP_CONTROLLER_STATUS_FILE = "desktop-controller-v1.json";
|
|
445
|
+
var ERROR_MAX_LENGTH = 1e3;
|
|
446
|
+
function defaultControllerStatusPath(environment = process.env) {
|
|
447
|
+
const dataDirectory = environment.CODEXHOST_DATA_DIR ? path.resolve(environment.CODEXHOST_DATA_DIR) : path.join(os.homedir(), ".codexhost");
|
|
448
|
+
return path.join(dataDirectory, "diagnostics", DESKTOP_CONTROLLER_STATUS_FILE);
|
|
449
|
+
}
|
|
450
|
+
function errorMessage(error) {
|
|
451
|
+
const parts = [];
|
|
452
|
+
let current = error;
|
|
453
|
+
for (let depth = 0; depth < 4 && current !== void 0; depth += 1) {
|
|
454
|
+
parts.push(current instanceof Error ? current.message : String(current));
|
|
455
|
+
current = current instanceof Error ? current.cause : void 0;
|
|
456
|
+
}
|
|
457
|
+
return parts.join(": ").slice(0, ERROR_MAX_LENGTH) || "Renderer installation failed";
|
|
458
|
+
}
|
|
459
|
+
function createRendererStatusReporter(publish, now = Date.now, pid = process.pid) {
|
|
460
|
+
const startedAt = now();
|
|
461
|
+
let current;
|
|
462
|
+
const update = (next) => {
|
|
463
|
+
if (current && current.state === next.state && current.error === next.error && current.failures === next.failures) {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
current = { ...next, updatedAt: now() };
|
|
467
|
+
publish({ schemaVersion: 1, pid, startedAt, renderer: current });
|
|
468
|
+
};
|
|
469
|
+
return {
|
|
470
|
+
installing() {
|
|
471
|
+
update({
|
|
472
|
+
state: "installing",
|
|
473
|
+
error: current?.error ?? null,
|
|
474
|
+
failures: current?.failures ?? 0,
|
|
475
|
+
lastError: current?.lastError ?? null,
|
|
476
|
+
lastFailedAt: current?.lastFailedAt ?? null,
|
|
477
|
+
lastInstalledAt: current?.lastInstalledAt ?? null
|
|
478
|
+
});
|
|
479
|
+
},
|
|
480
|
+
installed() {
|
|
481
|
+
if (current?.state === "installed") return;
|
|
482
|
+
update({
|
|
483
|
+
state: "installed",
|
|
484
|
+
error: null,
|
|
485
|
+
failures: current?.failures ?? 0,
|
|
486
|
+
lastError: current?.lastError ?? null,
|
|
487
|
+
lastFailedAt: current?.lastFailedAt ?? null,
|
|
488
|
+
lastInstalledAt: now()
|
|
489
|
+
});
|
|
490
|
+
},
|
|
491
|
+
failed(error) {
|
|
492
|
+
const message = errorMessage(error);
|
|
493
|
+
update({
|
|
494
|
+
state: "unavailable",
|
|
495
|
+
error: message,
|
|
496
|
+
failures: (current?.failures ?? 0) + 1,
|
|
497
|
+
lastError: message,
|
|
498
|
+
lastFailedAt: now(),
|
|
499
|
+
lastInstalledAt: current?.lastInstalledAt ?? null
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
async function writeControllerStatusFile(filePath, document) {
|
|
505
|
+
const directory = path.dirname(filePath);
|
|
506
|
+
await mkdir(directory, { recursive: true, mode: 448 });
|
|
507
|
+
const temporaryPath = path.join(
|
|
508
|
+
directory,
|
|
509
|
+
`.${path.basename(filePath)}.${randomBytes(6).toString("hex")}.tmp`
|
|
510
|
+
);
|
|
511
|
+
try {
|
|
512
|
+
await writeFile(temporaryPath, `${JSON.stringify(document)}
|
|
513
|
+
`, {
|
|
514
|
+
encoding: "utf8",
|
|
515
|
+
mode: 384,
|
|
516
|
+
flag: "wx"
|
|
517
|
+
});
|
|
518
|
+
await rename(temporaryPath, filePath);
|
|
519
|
+
} catch (error) {
|
|
520
|
+
await rm(temporaryPath, { force: true });
|
|
521
|
+
throw error;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
function createControllerStatusPublisher(filePath = defaultControllerStatusPath()) {
|
|
525
|
+
let queue = Promise.resolve();
|
|
526
|
+
return (document) => {
|
|
527
|
+
queue = queue.then(() => writeControllerStatusFile(filePath, document)).catch(() => void 0);
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
299
531
|
// packages/desktop-control/src/renderer-react-ownership.ts
|
|
300
532
|
function committedReactAncestors(value) {
|
|
301
|
-
const MAX_VISITED_FIBERS =
|
|
533
|
+
const MAX_VISITED_FIBERS = 2e5;
|
|
302
534
|
const fiber = (value2) => typeof value2 === "object" && value2 !== null ? value2 : null;
|
|
535
|
+
const exhausted = () => {
|
|
536
|
+
const target = globalThis;
|
|
537
|
+
if (typeof target.dispatchEvent === "function" && typeof CustomEvent === "function")
|
|
538
|
+
target.dispatchEvent(
|
|
539
|
+
new CustomEvent("codexhost:react-fiber-walk-limit", {
|
|
540
|
+
detail: { limit: MAX_VISITED_FIBERS }
|
|
541
|
+
})
|
|
542
|
+
);
|
|
543
|
+
return [];
|
|
544
|
+
};
|
|
303
545
|
const first = fiber(value);
|
|
304
546
|
if (!first) return [];
|
|
305
547
|
const previous = [];
|
|
306
548
|
const seen = /* @__PURE__ */ new Set();
|
|
307
549
|
for (let node = first; node; node = fiber(node.return)) {
|
|
308
|
-
if (seen.has(node)
|
|
550
|
+
if (seen.has(node)) return [];
|
|
551
|
+
if (seen.size >= MAX_VISITED_FIBERS) return exhausted();
|
|
309
552
|
seen.add(node);
|
|
310
553
|
previous.push(node);
|
|
311
554
|
}
|
|
@@ -313,8 +556,30 @@ function committedReactAncestors(value) {
|
|
|
313
556
|
if (!rootState || !("current" in rootState)) return previous;
|
|
314
557
|
const current = fiber(rootState.current);
|
|
315
558
|
if (!current) return [];
|
|
316
|
-
const stack = [{ node: current, parent: null }];
|
|
317
559
|
const alternate = fiber(first.alternate);
|
|
560
|
+
const path3 = [current];
|
|
561
|
+
seen.clear();
|
|
562
|
+
seen.add(current);
|
|
563
|
+
for (let index = previous.length - 2; index >= 0; index -= 1) {
|
|
564
|
+
const expected = previous[index];
|
|
565
|
+
const expectedAlternate = fiber(expected?.alternate);
|
|
566
|
+
let child = fiber(path3.at(-1)?.child);
|
|
567
|
+
let matched = false;
|
|
568
|
+
while (child && !seen.has(child) && seen.size < MAX_VISITED_FIBERS) {
|
|
569
|
+
seen.add(child);
|
|
570
|
+
if (child === expected || child === expectedAlternate) {
|
|
571
|
+
matched = true;
|
|
572
|
+
break;
|
|
573
|
+
}
|
|
574
|
+
child = fiber(child.sibling);
|
|
575
|
+
}
|
|
576
|
+
if (!matched || !child) break;
|
|
577
|
+
path3.push(child);
|
|
578
|
+
}
|
|
579
|
+
if (path3.length === previous.length && (path3.at(-1) === first || path3.at(-1) === alternate)) {
|
|
580
|
+
return path3.reverse();
|
|
581
|
+
}
|
|
582
|
+
const stack = [{ node: current, parent: null }];
|
|
318
583
|
seen.clear();
|
|
319
584
|
while (stack.length > 0 && seen.size < MAX_VISITED_FIBERS) {
|
|
320
585
|
const entry = stack.pop();
|
|
@@ -332,7 +597,7 @@ function committedReactAncestors(value) {
|
|
|
332
597
|
const child = fiber(entry.node.child);
|
|
333
598
|
if (child) stack.push({ node: child, parent: entry });
|
|
334
599
|
}
|
|
335
|
-
return [];
|
|
600
|
+
return stack.length > 0 ? exhausted() : [];
|
|
336
601
|
}
|
|
337
602
|
|
|
338
603
|
// packages/desktop-control/src/renderer-host-response-ownership.ts
|
|
@@ -1085,6 +1350,14 @@ function installRendererHostRouting(root, target, discover, resolve, createPolic
|
|
|
1085
1350
|
return typeof hostId === "string" && hostId ? hostId : null;
|
|
1086
1351
|
};
|
|
1087
1352
|
const routing = {
|
|
1353
|
+
knownHostIds() {
|
|
1354
|
+
if (disposed) return [];
|
|
1355
|
+
try {
|
|
1356
|
+
return [...read().hostIds];
|
|
1357
|
+
} catch {
|
|
1358
|
+
return [];
|
|
1359
|
+
}
|
|
1360
|
+
},
|
|
1088
1361
|
forHost(hostId) {
|
|
1089
1362
|
return hostId ? routeFor(hostId) : null;
|
|
1090
1363
|
},
|
|
@@ -1151,7 +1424,12 @@ function directRendererInstaller() {
|
|
|
1151
1424
|
manager, bridge, hostId, target, prewarmed, isCurrent, retainRendererHostResponses,
|
|
1152
1425
|
),
|
|
1153
1426
|
);
|
|
1154
|
-
|
|
1427
|
+
// Publish a default draft policy only when its Host is unambiguous. Host
|
|
1428
|
+
// connections can be ready even when multiple Composers coexist.
|
|
1429
|
+
const composerRoute = routing.forComposer();
|
|
1430
|
+
const connected = composerRoute !== null ||
|
|
1431
|
+
['local', ...(routing.knownHostIds?.() ?? [])].some((hostId) => routing.forHost(hostId) !== null);
|
|
1432
|
+
if (!connected) throw new Error('Renderer Host request manager is unavailable');
|
|
1155
1433
|
return { state: 'ready', reason: 'owned-request-bridge' };
|
|
1156
1434
|
})()`;
|
|
1157
1435
|
}
|
|
@@ -1167,7 +1445,8 @@ async function waitForDraftPrewarmPolicy(evaluate, expression) {
|
|
|
1167
1445
|
} catch (error) {
|
|
1168
1446
|
const message = error instanceof Error ? error.message : String(error);
|
|
1169
1447
|
const remaining = deadline - Date.now();
|
|
1170
|
-
if (!message.includes("Renderer request manager is
|
|
1448
|
+
if (!message.includes("Renderer Host request manager is unavailable") || remaining <= 0)
|
|
1449
|
+
throw error;
|
|
1171
1450
|
await new Promise(
|
|
1172
1451
|
(resolve) => setTimeout(resolve, Math.min(REQUEST_MANAGER_POLL_INTERVAL_MS, remaining))
|
|
1173
1452
|
);
|
|
@@ -1188,6 +1467,33 @@ function isRecord2(value) {
|
|
|
1188
1467
|
function sleep(milliseconds) {
|
|
1189
1468
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
1190
1469
|
}
|
|
1470
|
+
function discoveryTimeoutSignal(timeoutMs, signal) {
|
|
1471
|
+
const timeout = AbortSignal.timeout(timeoutMs);
|
|
1472
|
+
return signal ? AbortSignal.any([timeout, signal]) : timeout;
|
|
1473
|
+
}
|
|
1474
|
+
function abortFailure(signal, fallback) {
|
|
1475
|
+
return signal.reason instanceof Error ? signal.reason : new Error(fallback);
|
|
1476
|
+
}
|
|
1477
|
+
function awaitWithSignal(promise, signal, timeoutMessage) {
|
|
1478
|
+
if (signal.aborted) return Promise.reject(abortFailure(signal, timeoutMessage));
|
|
1479
|
+
return new Promise((resolve, reject) => {
|
|
1480
|
+
const onAbort = () => {
|
|
1481
|
+
signal.removeEventListener("abort", onAbort);
|
|
1482
|
+
reject(abortFailure(signal, timeoutMessage));
|
|
1483
|
+
};
|
|
1484
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1485
|
+
promise.then(
|
|
1486
|
+
(value) => {
|
|
1487
|
+
signal.removeEventListener("abort", onAbort);
|
|
1488
|
+
resolve(value);
|
|
1489
|
+
},
|
|
1490
|
+
(error) => {
|
|
1491
|
+
signal.removeEventListener("abort", onAbort);
|
|
1492
|
+
reject(error);
|
|
1493
|
+
}
|
|
1494
|
+
);
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1191
1497
|
function sameAgents(actual, expected) {
|
|
1192
1498
|
const expectedSet = new Set(expected);
|
|
1193
1499
|
return actual.length === expected.length && new Set(actual).size === actual.length && actual.every((agent) => expectedSet.has(agent));
|
|
@@ -1227,21 +1533,36 @@ function validateBindingStatus(value, expectedAgents) {
|
|
|
1227
1533
|
}
|
|
1228
1534
|
return value;
|
|
1229
1535
|
}
|
|
1230
|
-
async function waitForPrimaryTarget(endpoint, operations, timeoutMs, pollIntervalMs, preferredTargetId) {
|
|
1231
|
-
const
|
|
1536
|
+
async function waitForPrimaryTarget(endpoint, operations, timeoutMs, pollIntervalMs, preferredTargetId, signal) {
|
|
1537
|
+
const discovery = discoveryTimeoutSignal(timeoutMs, signal);
|
|
1232
1538
|
let lastError;
|
|
1233
|
-
while (
|
|
1539
|
+
while (!discovery.aborted) {
|
|
1234
1540
|
try {
|
|
1235
1541
|
const target = selectPrimaryRendererTarget(
|
|
1236
|
-
await
|
|
1542
|
+
await awaitWithSignal(
|
|
1543
|
+
operations.listTargets(endpoint, discovery),
|
|
1544
|
+
discovery,
|
|
1545
|
+
"Renderer CDP target discovery timed out"
|
|
1546
|
+
),
|
|
1237
1547
|
preferredTargetId
|
|
1238
1548
|
);
|
|
1239
1549
|
if (target) return target;
|
|
1240
1550
|
lastError = new Error("Renderer CDP has no primary app://-/index.html page target");
|
|
1241
1551
|
} catch (error) {
|
|
1242
1552
|
lastError = error;
|
|
1553
|
+
if (discovery.aborted) break;
|
|
1554
|
+
}
|
|
1555
|
+
if (discovery.aborted) break;
|
|
1556
|
+
try {
|
|
1557
|
+
await awaitWithSignal(
|
|
1558
|
+
sleep(pollIntervalMs),
|
|
1559
|
+
discovery,
|
|
1560
|
+
"Renderer CDP target discovery timed out"
|
|
1561
|
+
);
|
|
1562
|
+
} catch (error) {
|
|
1563
|
+
lastError = error;
|
|
1564
|
+
break;
|
|
1243
1565
|
}
|
|
1244
|
-
await sleep(pollIntervalMs);
|
|
1245
1566
|
}
|
|
1246
1567
|
const detail = lastError instanceof Error ? `: ${lastError.message}` : "";
|
|
1247
1568
|
throw new Error(`Primary Codex Renderer CDP target did not become ready${detail}`);
|
|
@@ -1253,8 +1574,9 @@ async function evaluateSource(renderer, source) {
|
|
|
1253
1574
|
});
|
|
1254
1575
|
if (!isRecord2(response)) throw new Error("Renderer source evaluation returned an invalid result");
|
|
1255
1576
|
if (isRecord2(response.exceptionDetails)) {
|
|
1256
|
-
|
|
1257
|
-
|
|
1577
|
+
throw new Error(
|
|
1578
|
+
cdpExceptionMessage(response.exceptionDetails, "Renderer source evaluation failed")
|
|
1579
|
+
);
|
|
1258
1580
|
}
|
|
1259
1581
|
}
|
|
1260
1582
|
async function readBinding(renderer) {
|
|
@@ -1382,7 +1704,7 @@ var InstalledRendererCdpControlSession = class {
|
|
|
1382
1704
|
}
|
|
1383
1705
|
};
|
|
1384
1706
|
var defaultOperations = {
|
|
1385
|
-
listTargets: (endpoint) => listCdpTargets(endpoint),
|
|
1707
|
+
listTargets: (endpoint, signal) => listCdpTargets(endpoint, fetch, signal),
|
|
1386
1708
|
connect: (webSocketDebuggerUrl) => CdpClient.connect(webSocketDebuggerUrl),
|
|
1387
1709
|
installDraftPrewarmPolicy: installRendererDraftPrewarmPolicyDirect
|
|
1388
1710
|
};
|
|
@@ -1395,7 +1717,9 @@ async function createRendererCdpControlSession(options) {
|
|
|
1395
1717
|
options.rendererCdpEndpoint,
|
|
1396
1718
|
operations,
|
|
1397
1719
|
timeoutMs,
|
|
1398
|
-
pollIntervalMs
|
|
1720
|
+
pollIntervalMs,
|
|
1721
|
+
void 0,
|
|
1722
|
+
options.signal
|
|
1399
1723
|
);
|
|
1400
1724
|
const installed = await installTarget(
|
|
1401
1725
|
target,
|
|
@@ -1454,6 +1778,7 @@ var defaultDependencies = {
|
|
|
1454
1778
|
process.stdout.write(`${serializeDesktopControllerReadiness(readiness)}
|
|
1455
1779
|
`);
|
|
1456
1780
|
},
|
|
1781
|
+
publishStatus: createControllerStatusPublisher(),
|
|
1457
1782
|
sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
|
|
1458
1783
|
monitorIntervalMs: 500
|
|
1459
1784
|
};
|
|
@@ -1485,8 +1810,8 @@ function parseDesktopControllerArguments(arguments_) {
|
|
|
1485
1810
|
if (argument === "--renderer") {
|
|
1486
1811
|
if (rendererPath !== void 0) throw new Error("--renderer may only be provided once");
|
|
1487
1812
|
if (!value) throw new Error("--renderer requires a value");
|
|
1488
|
-
if (!
|
|
1489
|
-
rendererPath =
|
|
1813
|
+
if (!path2.isAbsolute(value)) throw new Error("--renderer must be an absolute path");
|
|
1814
|
+
rendererPath = path2.normalize(value);
|
|
1490
1815
|
index += 1;
|
|
1491
1816
|
continue;
|
|
1492
1817
|
}
|
|
@@ -1568,15 +1893,22 @@ async function runDesktopController(options, signal, dependencies = defaultDepen
|
|
|
1568
1893
|
let session;
|
|
1569
1894
|
let nextRecoveryAt = 0;
|
|
1570
1895
|
let recoveryDelayMs = RECOVERY_RETRY_INITIAL_MS;
|
|
1571
|
-
const
|
|
1896
|
+
const status = createRendererStatusReporter(
|
|
1897
|
+
(document) => dependencies.publishStatus?.(document),
|
|
1898
|
+
now
|
|
1899
|
+
);
|
|
1900
|
+
const recordRecoveryFailure = (error) => {
|
|
1572
1901
|
nextRecoveryAt = now() + recoveryDelayMs;
|
|
1573
1902
|
recoveryDelayMs = Math.min(recoveryDelayMs * 2, RECOVERY_RETRY_MAX_MS);
|
|
1903
|
+
status.failed(error);
|
|
1574
1904
|
};
|
|
1575
1905
|
const recordRecoverySuccess = () => {
|
|
1576
1906
|
nextRecoveryAt = 0;
|
|
1577
1907
|
recoveryDelayMs = RECOVERY_RETRY_INITIAL_MS;
|
|
1908
|
+
status.installed();
|
|
1578
1909
|
};
|
|
1579
1910
|
const createSession = async () => {
|
|
1911
|
+
status.installing();
|
|
1580
1912
|
startupTrace("reading Renderer bundle");
|
|
1581
1913
|
const rendererSource = await dependencies.readRenderer(options.rendererPath);
|
|
1582
1914
|
if (rendererSource.trim().length === 0) throw new Error("production Renderer Bundle is empty");
|
|
@@ -1603,9 +1935,11 @@ ${rendererSource}`,
|
|
|
1603
1935
|
"hermes",
|
|
1604
1936
|
"qoder",
|
|
1605
1937
|
"qoder-cn",
|
|
1606
|
-
"kimi-code"
|
|
1938
|
+
"kimi-code",
|
|
1939
|
+
"zcode"
|
|
1607
1940
|
],
|
|
1608
|
-
timeoutMs: PRODUCTION_INSTALL_TIMEOUT_MS
|
|
1941
|
+
timeoutMs: PRODUCTION_INSTALL_TIMEOUT_MS,
|
|
1942
|
+
signal
|
|
1609
1943
|
},
|
|
1610
1944
|
dependencies
|
|
1611
1945
|
);
|
|
@@ -1613,14 +1947,6 @@ ${rendererSource}`,
|
|
|
1613
1947
|
return installed;
|
|
1614
1948
|
};
|
|
1615
1949
|
startupTrace("initialization started");
|
|
1616
|
-
try {
|
|
1617
|
-
session = await createSession();
|
|
1618
|
-
recordRecoverySuccess();
|
|
1619
|
-
} catch (error) {
|
|
1620
|
-
startupTrace("initial Renderer Session unavailable", error);
|
|
1621
|
-
session = void 0;
|
|
1622
|
-
recordRecoveryFailure();
|
|
1623
|
-
}
|
|
1624
1950
|
let operation = Promise.resolve(void 0);
|
|
1625
1951
|
const useSession = (callback) => {
|
|
1626
1952
|
const next = operation.then(callback, callback);
|
|
@@ -1646,7 +1972,7 @@ ${rendererSource}`,
|
|
|
1646
1972
|
return current;
|
|
1647
1973
|
} catch (error) {
|
|
1648
1974
|
resetSession();
|
|
1649
|
-
recordRecoveryFailure();
|
|
1975
|
+
recordRecoveryFailure(error);
|
|
1650
1976
|
throw error;
|
|
1651
1977
|
}
|
|
1652
1978
|
};
|
|
@@ -1656,6 +1982,11 @@ ${rendererSource}`,
|
|
|
1656
1982
|
attachmentServer = await dependencies.startAttachmentServer({
|
|
1657
1983
|
port: options.attachmentPort,
|
|
1658
1984
|
nonce: options.attachmentNonce,
|
|
1985
|
+
openLocalPage: (url) => openRendererLocalPage(
|
|
1986
|
+
(expression) => useSession(async () => (await recoverSession()).executeRenderer(expression)),
|
|
1987
|
+
url,
|
|
1988
|
+
() => listCdpTargets(options.rendererCdpEndpoint)
|
|
1989
|
+
),
|
|
1659
1990
|
attach: () => useSession(async () => {
|
|
1660
1991
|
const current = await recoverSession();
|
|
1661
1992
|
await current.activateDesktop();
|
|
@@ -1668,6 +1999,17 @@ ${rendererSource}`,
|
|
|
1668
1999
|
state: "compatible",
|
|
1669
2000
|
issues: []
|
|
1670
2001
|
});
|
|
2002
|
+
await useSession(async () => {
|
|
2003
|
+
if (session) return;
|
|
2004
|
+
try {
|
|
2005
|
+
session = await createSession();
|
|
2006
|
+
recordRecoverySuccess();
|
|
2007
|
+
} catch (error) {
|
|
2008
|
+
startupTrace("initial Renderer Session unavailable", error);
|
|
2009
|
+
session = void 0;
|
|
2010
|
+
recordRecoveryFailure(error);
|
|
2011
|
+
}
|
|
2012
|
+
});
|
|
1671
2013
|
while (!signal.aborted) {
|
|
1672
2014
|
await dependencies.sleep(dependencies.monitorIntervalMs);
|
|
1673
2015
|
if (signal.aborted) continue;
|