@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.
Files changed (34) hide show
  1. package/README.md +1 -1
  2. package/THIRD_PARTY_NOTICES.txt +4 -0
  3. package/app/codexhost-distribution.json +1 -1
  4. package/app/console-server.mjs +18001 -0
  5. package/app/console-web.js +27674 -0
  6. package/app/desktop-controller.mjs +452 -110
  7. package/app/host-runtime.mjs +6670 -1272
  8. package/app/plugins/antigravity/plugin.mjs +686 -305
  9. package/app/plugins/claude-code/plugin.mjs +2283 -1450
  10. package/app/plugins/codebuddy/plugin.mjs +1272 -506
  11. package/app/plugins/cursor-cli/manifest.json +1 -1
  12. package/app/plugins/cursor-cli/plugin.mjs +1295 -497
  13. package/app/plugins/deepseek-harness/plugin.mjs +4718 -4925
  14. package/app/plugins/enabled.json +2 -1
  15. package/app/plugins/grok/plugin.mjs +1012 -369
  16. package/app/plugins/hermes/plugin.mjs +18587 -22714
  17. package/app/plugins/kimi-code/plugin.mjs +1283 -355
  18. package/app/plugins/kiro-cli/plugin.mjs +840 -299
  19. package/app/plugins/omp/plugin.mjs +1045 -328
  20. package/app/plugins/opencode/plugin.mjs +5341 -1686
  21. package/app/plugins/pi/plugin.mjs +1343 -534
  22. package/app/plugins/qoder/plugin.mjs +913 -258
  23. package/app/plugins/qoder-cn/plugin.mjs +712 -49
  24. package/app/plugins/workbuddy/plugin.mjs +531 -44
  25. package/app/plugins/zcode/assets/icon.svg +8 -0
  26. package/app/plugins/zcode/manifest.json +14 -0
  27. package/app/plugins/zcode/plugin.mjs +19202 -0
  28. package/app/renderer-extension.js +5011 -1899
  29. package/bin/codexhost.exe +0 -0
  30. package/libexec/codexhost-node-repl.exe +0 -0
  31. package/libexec/codexhost-shim.exe +0 -0
  32. package/libexec/codexhost-updater.exe +0 -0
  33. package/licenses/OpenCode-v2-Client-LICENSE.txt +21 -0
  34. 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 path from "node:path";
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 {
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
- for (const socket of sockets) socket.destroy();
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
- const text = typeof response.exceptionDetails.text === "string" ? response.exceptionDetails.text : "Renderer evaluation failed";
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 = 2e4;
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) || seen.size >= MAX_VISITED_FIBERS) return [];
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
- 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');
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 ambiguous") || remaining <= 0) throw error;
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 deadline = Date.now() + timeoutMs;
1536
+ async function waitForPrimaryTarget(endpoint, operations, timeoutMs, pollIntervalMs, preferredTargetId, signal) {
1537
+ const discovery = discoveryTimeoutSignal(timeoutMs, signal);
1232
1538
  let lastError;
1233
- while (Date.now() < deadline) {
1539
+ while (!discovery.aborted) {
1234
1540
  try {
1235
1541
  const target = selectPrimaryRendererTarget(
1236
- await operations.listTargets(endpoint),
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
- const text = typeof response.exceptionDetails.text === "string" ? response.exceptionDetails.text : "Renderer source evaluation failed";
1257
- throw new Error(text);
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 (!path.isAbsolute(value)) throw new Error("--renderer must be an absolute path");
1489
- rendererPath = path.normalize(value);
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 recordRecoveryFailure = () => {
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;