@codexhost/cli-darwin-x64 0.11.0 → 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/app/codexhost-distribution.json +1 -1
- package/app/console-server.mjs +44 -2
- package/app/console-web.js +2216 -492
- package/app/desktop-controller.mjs +391 -189
- package/app/host-runtime.mjs +3917 -625
- package/app/plugins/antigravity/plugin.mjs +15634 -15526
- package/app/plugins/claude-code/plugin.mjs +215 -70
- package/app/plugins/codebuddy/plugin.mjs +1260 -827
- package/app/plugins/cursor-cli/plugin.mjs +529 -370
- package/app/plugins/deepseek-harness/plugin.mjs +25467 -26242
- package/app/plugins/enabled.json +2 -1
- package/app/plugins/grok/plugin.mjs +936 -505
- package/app/plugins/hermes/plugin.mjs +18659 -23012
- package/app/plugins/kimi-code/plugin.mjs +1046 -703
- package/app/plugins/kiro-cli/plugin.mjs +959 -590
- package/app/plugins/omp/plugin.mjs +1038 -576
- package/app/plugins/opencode/plugin.mjs +961 -700
- package/app/plugins/pi/plugin.mjs +15911 -15724
- package/app/plugins/qoder/plugin.mjs +679 -375
- package/app/plugins/qoder-cn/plugin.mjs +345 -39
- package/app/plugins/workbuddy/plugin.mjs +417 -40
- 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 +2495 -486
- package/bin/codexhost +0 -0
- package/libexec/codexhost-shim +0 -0
- package/libexec/codexhost-updater +0 -0
- package/package.json +1 -1
|
@@ -2,182 +2,61 @@
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
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 {
|
|
81
|
-
async
|
|
37
|
+
async show() {
|
|
82
38
|
if (closed) return;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
var DESKTOP_CONTROLLER_STATUS_FILE = "desktop-controller-v1.json";
|
|
96
|
-
var ERROR_MAX_LENGTH = 1e3;
|
|
97
|
-
function defaultControllerStatusPath(environment = process.env) {
|
|
98
|
-
const dataDirectory = environment.CODEXHOST_DATA_DIR ? path.resolve(environment.CODEXHOST_DATA_DIR) : path.join(os.homedir(), ".codexhost");
|
|
99
|
-
return path.join(dataDirectory, "diagnostics", DESKTOP_CONTROLLER_STATUS_FILE);
|
|
100
|
-
}
|
|
101
|
-
function errorMessage(error) {
|
|
102
|
-
const parts = [];
|
|
103
|
-
let current = error;
|
|
104
|
-
for (let depth = 0; depth < 4 && current !== void 0; depth += 1) {
|
|
105
|
-
parts.push(current instanceof Error ? current.message : String(current));
|
|
106
|
-
current = current instanceof Error ? current.cause : void 0;
|
|
107
|
-
}
|
|
108
|
-
return parts.join(": ").slice(0, ERROR_MAX_LENGTH) || "Renderer installation failed";
|
|
109
|
-
}
|
|
110
|
-
function createRendererStatusReporter(publish, now = Date.now, pid = process.pid) {
|
|
111
|
-
const startedAt = now();
|
|
112
|
-
let current;
|
|
113
|
-
const update = (next) => {
|
|
114
|
-
if (current && current.state === next.state && current.error === next.error && current.failures === next.failures) {
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
current = { ...next, updatedAt: now() };
|
|
118
|
-
publish({ schemaVersion: 1, pid, startedAt, renderer: current });
|
|
119
|
-
};
|
|
120
|
-
return {
|
|
121
|
-
installing() {
|
|
122
|
-
update({
|
|
123
|
-
state: "installing",
|
|
124
|
-
error: current?.error ?? null,
|
|
125
|
-
failures: current?.failures ?? 0,
|
|
126
|
-
lastError: current?.lastError ?? null,
|
|
127
|
-
lastFailedAt: current?.lastFailedAt ?? null,
|
|
128
|
-
lastInstalledAt: current?.lastInstalledAt ?? null
|
|
129
|
-
});
|
|
130
|
-
},
|
|
131
|
-
installed() {
|
|
132
|
-
if (current?.state === "installed") return;
|
|
133
|
-
update({
|
|
134
|
-
state: "installed",
|
|
135
|
-
error: null,
|
|
136
|
-
failures: current?.failures ?? 0,
|
|
137
|
-
lastError: current?.lastError ?? null,
|
|
138
|
-
lastFailedAt: current?.lastFailedAt ?? null,
|
|
139
|
-
lastInstalledAt: now()
|
|
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"
|
|
140
51
|
});
|
|
141
52
|
},
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
error: message,
|
|
147
|
-
failures: (current?.failures ?? 0) + 1,
|
|
148
|
-
lastError: message,
|
|
149
|
-
lastFailedAt: now(),
|
|
150
|
-
lastInstalledAt: current?.lastInstalledAt ?? null
|
|
151
|
-
});
|
|
53
|
+
async close() {
|
|
54
|
+
if (closed) return;
|
|
55
|
+
closed = true;
|
|
56
|
+
await post({ type: "close-browser-tab", conversationId, browserTabId });
|
|
152
57
|
}
|
|
153
58
|
};
|
|
154
59
|
}
|
|
155
|
-
async function writeControllerStatusFile(filePath, document) {
|
|
156
|
-
const directory = path.dirname(filePath);
|
|
157
|
-
await mkdir(directory, { recursive: true, mode: 448 });
|
|
158
|
-
const temporaryPath = path.join(
|
|
159
|
-
directory,
|
|
160
|
-
`.${path.basename(filePath)}.${randomBytes(6).toString("hex")}.tmp`
|
|
161
|
-
);
|
|
162
|
-
try {
|
|
163
|
-
await writeFile(temporaryPath, `${JSON.stringify(document)}
|
|
164
|
-
`, {
|
|
165
|
-
encoding: "utf8",
|
|
166
|
-
mode: 384,
|
|
167
|
-
flag: "wx"
|
|
168
|
-
});
|
|
169
|
-
await rename(temporaryPath, filePath);
|
|
170
|
-
} catch (error) {
|
|
171
|
-
await rm(temporaryPath, { force: true });
|
|
172
|
-
throw error;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
function createControllerStatusPublisher(filePath = defaultControllerStatusPath()) {
|
|
176
|
-
let queue = Promise.resolve();
|
|
177
|
-
return (document) => {
|
|
178
|
-
queue = queue.then(() => writeControllerStatusFile(filePath, document)).catch(() => void 0);
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
60
|
|
|
182
61
|
// packages/desktop-control/src/cdp-client.ts
|
|
183
62
|
function cdpExceptionMessage(details, fallback) {
|
|
@@ -219,8 +98,8 @@ function parseTarget(value) {
|
|
|
219
98
|
webSocketDebuggerUrl
|
|
220
99
|
};
|
|
221
100
|
}
|
|
222
|
-
function defaultFetch(url) {
|
|
223
|
-
return fetch(url);
|
|
101
|
+
function defaultFetch(url, init) {
|
|
102
|
+
return fetch(url, init);
|
|
224
103
|
}
|
|
225
104
|
function defaultSocketFactory(url) {
|
|
226
105
|
return new WebSocket(url);
|
|
@@ -247,10 +126,10 @@ function parseResponse(value) {
|
|
|
247
126
|
}
|
|
248
127
|
return response;
|
|
249
128
|
}
|
|
250
|
-
async function listCdpTargets(endpoint, fetchImpl = defaultFetch) {
|
|
129
|
+
async function listCdpTargets(endpoint, fetchImpl = defaultFetch, signal) {
|
|
251
130
|
const baseUrl = loopbackUrl(endpoint, ["http:", "https:"]);
|
|
252
131
|
const targetsUrl = new URL("/json/list", baseUrl).toString();
|
|
253
|
-
const response = await fetchImpl(targetsUrl);
|
|
132
|
+
const response = await fetchImpl(targetsUrl, signal ? { signal } : void 0);
|
|
254
133
|
if (!response.ok) throw new Error(`CDP target discovery failed with HTTP ${response.status}`);
|
|
255
134
|
const value = await response.json();
|
|
256
135
|
if (!Array.isArray(value)) throw new Error("CDP target discovery did not return an array");
|
|
@@ -394,6 +273,261 @@ var CdpClient = class _CdpClient {
|
|
|
394
273
|
}
|
|
395
274
|
};
|
|
396
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
|
+
|
|
397
531
|
// packages/desktop-control/src/renderer-react-ownership.ts
|
|
398
532
|
function committedReactAncestors(value) {
|
|
399
533
|
const MAX_VISITED_FIBERS = 2e5;
|
|
@@ -1216,6 +1350,14 @@ function installRendererHostRouting(root, target, discover, resolve, createPolic
|
|
|
1216
1350
|
return typeof hostId === "string" && hostId ? hostId : null;
|
|
1217
1351
|
};
|
|
1218
1352
|
const routing = {
|
|
1353
|
+
knownHostIds() {
|
|
1354
|
+
if (disposed) return [];
|
|
1355
|
+
try {
|
|
1356
|
+
return [...read().hostIds];
|
|
1357
|
+
} catch {
|
|
1358
|
+
return [];
|
|
1359
|
+
}
|
|
1360
|
+
},
|
|
1219
1361
|
forHost(hostId) {
|
|
1220
1362
|
return hostId ? routeFor(hostId) : null;
|
|
1221
1363
|
},
|
|
@@ -1282,7 +1424,12 @@ function directRendererInstaller() {
|
|
|
1282
1424
|
manager, bridge, hostId, target, prewarmed, isCurrent, retainRendererHostResponses,
|
|
1283
1425
|
),
|
|
1284
1426
|
);
|
|
1285
|
-
|
|
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');
|
|
1286
1433
|
return { state: 'ready', reason: 'owned-request-bridge' };
|
|
1287
1434
|
})()`;
|
|
1288
1435
|
}
|
|
@@ -1298,7 +1445,8 @@ async function waitForDraftPrewarmPolicy(evaluate, expression) {
|
|
|
1298
1445
|
} catch (error) {
|
|
1299
1446
|
const message = error instanceof Error ? error.message : String(error);
|
|
1300
1447
|
const remaining = deadline - Date.now();
|
|
1301
|
-
if (!message.includes("Renderer request manager is
|
|
1448
|
+
if (!message.includes("Renderer Host request manager is unavailable") || remaining <= 0)
|
|
1449
|
+
throw error;
|
|
1302
1450
|
await new Promise(
|
|
1303
1451
|
(resolve) => setTimeout(resolve, Math.min(REQUEST_MANAGER_POLL_INTERVAL_MS, remaining))
|
|
1304
1452
|
);
|
|
@@ -1319,6 +1467,33 @@ function isRecord2(value) {
|
|
|
1319
1467
|
function sleep(milliseconds) {
|
|
1320
1468
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
1321
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
|
+
}
|
|
1322
1497
|
function sameAgents(actual, expected) {
|
|
1323
1498
|
const expectedSet = new Set(expected);
|
|
1324
1499
|
return actual.length === expected.length && new Set(actual).size === actual.length && actual.every((agent) => expectedSet.has(agent));
|
|
@@ -1358,21 +1533,36 @@ function validateBindingStatus(value, expectedAgents) {
|
|
|
1358
1533
|
}
|
|
1359
1534
|
return value;
|
|
1360
1535
|
}
|
|
1361
|
-
async function waitForPrimaryTarget(endpoint, operations, timeoutMs, pollIntervalMs, preferredTargetId) {
|
|
1362
|
-
const
|
|
1536
|
+
async function waitForPrimaryTarget(endpoint, operations, timeoutMs, pollIntervalMs, preferredTargetId, signal) {
|
|
1537
|
+
const discovery = discoveryTimeoutSignal(timeoutMs, signal);
|
|
1363
1538
|
let lastError;
|
|
1364
|
-
while (
|
|
1539
|
+
while (!discovery.aborted) {
|
|
1365
1540
|
try {
|
|
1366
1541
|
const target = selectPrimaryRendererTarget(
|
|
1367
|
-
await
|
|
1542
|
+
await awaitWithSignal(
|
|
1543
|
+
operations.listTargets(endpoint, discovery),
|
|
1544
|
+
discovery,
|
|
1545
|
+
"Renderer CDP target discovery timed out"
|
|
1546
|
+
),
|
|
1368
1547
|
preferredTargetId
|
|
1369
1548
|
);
|
|
1370
1549
|
if (target) return target;
|
|
1371
1550
|
lastError = new Error("Renderer CDP has no primary app://-/index.html page target");
|
|
1372
1551
|
} catch (error) {
|
|
1373
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;
|
|
1374
1565
|
}
|
|
1375
|
-
await sleep(pollIntervalMs);
|
|
1376
1566
|
}
|
|
1377
1567
|
const detail = lastError instanceof Error ? `: ${lastError.message}` : "";
|
|
1378
1568
|
throw new Error(`Primary Codex Renderer CDP target did not become ready${detail}`);
|
|
@@ -1514,7 +1704,7 @@ var InstalledRendererCdpControlSession = class {
|
|
|
1514
1704
|
}
|
|
1515
1705
|
};
|
|
1516
1706
|
var defaultOperations = {
|
|
1517
|
-
listTargets: (endpoint) => listCdpTargets(endpoint),
|
|
1707
|
+
listTargets: (endpoint, signal) => listCdpTargets(endpoint, fetch, signal),
|
|
1518
1708
|
connect: (webSocketDebuggerUrl) => CdpClient.connect(webSocketDebuggerUrl),
|
|
1519
1709
|
installDraftPrewarmPolicy: installRendererDraftPrewarmPolicyDirect
|
|
1520
1710
|
};
|
|
@@ -1527,7 +1717,9 @@ async function createRendererCdpControlSession(options) {
|
|
|
1527
1717
|
options.rendererCdpEndpoint,
|
|
1528
1718
|
operations,
|
|
1529
1719
|
timeoutMs,
|
|
1530
|
-
pollIntervalMs
|
|
1720
|
+
pollIntervalMs,
|
|
1721
|
+
void 0,
|
|
1722
|
+
options.signal
|
|
1531
1723
|
);
|
|
1532
1724
|
const installed = await installTarget(
|
|
1533
1725
|
target,
|
|
@@ -1743,9 +1935,11 @@ ${rendererSource}`,
|
|
|
1743
1935
|
"hermes",
|
|
1744
1936
|
"qoder",
|
|
1745
1937
|
"qoder-cn",
|
|
1746
|
-
"kimi-code"
|
|
1938
|
+
"kimi-code",
|
|
1939
|
+
"zcode"
|
|
1747
1940
|
],
|
|
1748
|
-
timeoutMs: PRODUCTION_INSTALL_TIMEOUT_MS
|
|
1941
|
+
timeoutMs: PRODUCTION_INSTALL_TIMEOUT_MS,
|
|
1942
|
+
signal
|
|
1749
1943
|
},
|
|
1750
1944
|
dependencies
|
|
1751
1945
|
);
|
|
@@ -1753,14 +1947,6 @@ ${rendererSource}`,
|
|
|
1753
1947
|
return installed;
|
|
1754
1948
|
};
|
|
1755
1949
|
startupTrace("initialization started");
|
|
1756
|
-
try {
|
|
1757
|
-
session = await createSession();
|
|
1758
|
-
recordRecoverySuccess();
|
|
1759
|
-
} catch (error) {
|
|
1760
|
-
startupTrace("initial Renderer Session unavailable", error);
|
|
1761
|
-
session = void 0;
|
|
1762
|
-
recordRecoveryFailure(error);
|
|
1763
|
-
}
|
|
1764
1950
|
let operation = Promise.resolve(void 0);
|
|
1765
1951
|
const useSession = (callback) => {
|
|
1766
1952
|
const next = operation.then(callback, callback);
|
|
@@ -1796,6 +1982,11 @@ ${rendererSource}`,
|
|
|
1796
1982
|
attachmentServer = await dependencies.startAttachmentServer({
|
|
1797
1983
|
port: options.attachmentPort,
|
|
1798
1984
|
nonce: options.attachmentNonce,
|
|
1985
|
+
openLocalPage: (url) => openRendererLocalPage(
|
|
1986
|
+
(expression) => useSession(async () => (await recoverSession()).executeRenderer(expression)),
|
|
1987
|
+
url,
|
|
1988
|
+
() => listCdpTargets(options.rendererCdpEndpoint)
|
|
1989
|
+
),
|
|
1799
1990
|
attach: () => useSession(async () => {
|
|
1800
1991
|
const current = await recoverSession();
|
|
1801
1992
|
await current.activateDesktop();
|
|
@@ -1808,6 +1999,17 @@ ${rendererSource}`,
|
|
|
1808
1999
|
state: "compatible",
|
|
1809
2000
|
issues: []
|
|
1810
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
|
+
});
|
|
1811
2013
|
while (!signal.aborted) {
|
|
1812
2014
|
await dependencies.sleep(dependencies.monitorIntervalMs);
|
|
1813
2015
|
if (signal.aborted) continue;
|