@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.
@@ -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/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();
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
- 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");
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 close() {
37
+ async show() {
82
38
  if (closed) return;
83
- closed = true;
84
- for (const socket of sockets) socket.destroy();
85
- await closeServer(server);
86
- }
87
- };
88
- }
89
-
90
- // packages/desktop-control/src/controller-status.ts
91
- import { randomBytes } from "node:crypto";
92
- import { mkdir, rename, rm, writeFile } from "node:fs/promises";
93
- import os from "node:os";
94
- import path from "node:path";
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
- failed(error) {
143
- const message = errorMessage(error);
144
- update({
145
- state: "unavailable",
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
- if (!routing.forComposer()) throw new Error('Renderer request manager is ambiguous');
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 ambiguous") || remaining <= 0) throw error;
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 deadline = Date.now() + timeoutMs;
1536
+ async function waitForPrimaryTarget(endpoint, operations, timeoutMs, pollIntervalMs, preferredTargetId, signal) {
1537
+ const discovery = discoveryTimeoutSignal(timeoutMs, signal);
1363
1538
  let lastError;
1364
- while (Date.now() < deadline) {
1539
+ while (!discovery.aborted) {
1365
1540
  try {
1366
1541
  const target = selectPrimaryRendererTarget(
1367
- await operations.listTargets(endpoint),
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;