@chinhae/codex-connect-linux-arm64 0.11.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 (65) hide show
  1. package/README.md +49 -0
  2. package/THIRD_PARTY_NOTICES.txt +49 -0
  3. package/app/codexhost-distribution.json +1 -0
  4. package/app/desktop-controller.mjs +1704 -0
  5. package/app/host-runtime.mjs +38189 -0
  6. package/app/plugins/antigravity/assets/icon.svg +1 -0
  7. package/app/plugins/antigravity/manifest.json +12 -0
  8. package/app/plugins/antigravity/plugin.mjs +19508 -0
  9. package/app/plugins/claude-code/assets/icon.svg +3 -0
  10. package/app/plugins/claude-code/manifest.json +12 -0
  11. package/app/plugins/claude-code/plugin.mjs +48709 -0
  12. package/app/plugins/codebuddy/assets/icon.svg +1 -0
  13. package/app/plugins/codebuddy/manifest.json +13 -0
  14. package/app/plugins/codebuddy/plugin.mjs +24584 -0
  15. package/app/plugins/cursor-cli/assets/icon.svg +1 -0
  16. package/app/plugins/cursor-cli/manifest.json +13 -0
  17. package/app/plugins/cursor-cli/plugin.mjs +47697 -0
  18. package/app/plugins/deepseek-harness/manifest.json +11 -0
  19. package/app/plugins/deepseek-harness/plugin.mjs +32044 -0
  20. package/app/plugins/enabled.json +20 -0
  21. package/app/plugins/grok/assets/icon.png +0 -0
  22. package/app/plugins/grok/manifest.json +12 -0
  23. package/app/plugins/grok/plugin.mjs +25032 -0
  24. package/app/plugins/hermes/manifest.json +11 -0
  25. package/app/plugins/hermes/plugin.mjs +24129 -0
  26. package/app/plugins/kimi-code/manifest.json +11 -0
  27. package/app/plugins/kimi-code/plugin.mjs +24226 -0
  28. package/app/plugins/kiro-cli/assets/icon.svg +5 -0
  29. package/app/plugins/kiro-cli/manifest.json +12 -0
  30. package/app/plugins/kiro-cli/plugin.mjs +23628 -0
  31. package/app/plugins/omp/assets/icon.svg +14 -0
  32. package/app/plugins/omp/manifest.json +12 -0
  33. package/app/plugins/omp/plugin.mjs +20632 -0
  34. package/app/plugins/opencode/assets/icon.png +0 -0
  35. package/app/plugins/opencode/manifest.json +12 -0
  36. package/app/plugins/opencode/plugin.mjs +23961 -0
  37. package/app/plugins/pi/assets/icon.svg +4 -0
  38. package/app/plugins/pi/manifest.json +12 -0
  39. package/app/plugins/pi/plugin.mjs +21558 -0
  40. package/app/plugins/qoder/assets/icon.svg +1 -0
  41. package/app/plugins/qoder/manifest.json +12 -0
  42. package/app/plugins/qoder/plugin.mjs +52414 -0
  43. package/app/plugins/qoder-cn/assets/icon.svg +1 -0
  44. package/app/plugins/qoder-cn/manifest.json +12 -0
  45. package/app/plugins/qoder-cn/plugin.mjs +52412 -0
  46. package/app/plugins/workbuddy/assets/icon.svg +29 -0
  47. package/app/plugins/workbuddy/manifest.json +14 -0
  48. package/app/plugins/workbuddy/plugin.mjs +24970 -0
  49. package/app/renderer-extension.js +33953 -0
  50. package/bin/codexhost +0 -0
  51. package/libexec/codexhost-shim +0 -0
  52. package/libexec/codexhost-updater +0 -0
  53. package/licenses/Agent-Client-Protocol-SDK-LICENSE.txt +191 -0
  54. package/licenses/Anthropic-SDK-LICENSE.txt +8 -0
  55. package/licenses/Claude-Agent-SDK-LICENSE.md +1 -0
  56. package/licenses/MCP-SDK-LICENSE.txt +21 -0
  57. package/licenses/OpenCode-SDK-LICENSE.txt +24 -0
  58. package/licenses/Qoder-Agent-SDK-LICENSE.txt +7 -0
  59. package/licenses/QoderCN-Agent-SDK-LICENSE.txt +7 -0
  60. package/licenses/diff-LICENSE.txt +29 -0
  61. package/licenses/lucide-LICENSE.txt +43 -0
  62. package/licenses/tailwindcss-LICENSE.txt +21 -0
  63. package/licenses/ws-LICENSE.txt +20 -0
  64. package/licenses/zod-LICENSE.txt +21 -0
  65. package/package.json +42 -0
@@ -0,0 +1,1704 @@
1
+ // packages/desktop-control/src/production-controller.ts
2
+ import { readFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+
5
+ // packages/desktop-control/src/controller-attachment-server.ts
6
+ import { createServer } from "node:net";
7
+ var MAX_REQUEST_BYTES = 96;
8
+ function validPort(value) {
9
+ return Number.isInteger(value) && value >= 1 && value <= 65535;
10
+ }
11
+ function validNonce(value) {
12
+ return /^[0-9a-f]{32}$/.test(value);
13
+ }
14
+ function closeServer(server) {
15
+ return new Promise((resolve, reject) => {
16
+ server.close((error) => {
17
+ if (error) reject(error);
18
+ else resolve();
19
+ });
20
+ });
21
+ }
22
+ function respond(socket, value) {
23
+ socket.end(`${value}
24
+ `);
25
+ }
26
+ async function startControllerAttachmentServer(options) {
27
+ if (!validPort(options.port)) throw new Error("attachment port must be a valid TCP port");
28
+ if (!validNonce(options.nonce)) {
29
+ throw new Error("attachment nonce must be 32 lowercase hexadecimal characters");
30
+ }
31
+ const sockets = /* @__PURE__ */ new Set();
32
+ 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
+ let closed = false;
80
+ return {
81
+ async close() {
82
+ 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/cdp-client.ts
91
+ function isRecord(value) {
92
+ return typeof value === "object" && value !== null && !Array.isArray(value);
93
+ }
94
+ function loopbackUrl(value, protocols) {
95
+ const url = new URL(value);
96
+ if (!protocols.includes(url.protocol)) {
97
+ throw new Error(`CDP endpoint must use ${protocols.join(" or ")}`);
98
+ }
99
+ if (!["127.0.0.1", "localhost", "[::1]"].includes(url.hostname)) {
100
+ throw new Error("CDP endpoint must use a loopback host");
101
+ }
102
+ return url;
103
+ }
104
+ function optionalText(value) {
105
+ return typeof value === "string" && value.length > 0 ? value : null;
106
+ }
107
+ function parseTarget(value) {
108
+ if (!isRecord(value)) throw new Error("CDP target must be an object");
109
+ const id = optionalText(value.id);
110
+ const type = optionalText(value.type);
111
+ const url = optionalText(value.url);
112
+ const webSocketDebuggerUrl = optionalText(value.webSocketDebuggerUrl);
113
+ if (webSocketDebuggerUrl) loopbackUrl(webSocketDebuggerUrl, ["ws:", "wss:"]);
114
+ if (!id || !type || !url || !webSocketDebuggerUrl) return null;
115
+ return {
116
+ id,
117
+ type,
118
+ title: typeof value.title === "string" ? value.title : "",
119
+ url,
120
+ webSocketDebuggerUrl
121
+ };
122
+ }
123
+ function defaultFetch(url) {
124
+ return fetch(url);
125
+ }
126
+ function defaultSocketFactory(url) {
127
+ return new WebSocket(url);
128
+ }
129
+ function messageText(value) {
130
+ if (typeof value === "string") return value;
131
+ if (value instanceof ArrayBuffer) return new TextDecoder().decode(value);
132
+ if (ArrayBuffer.isView(value)) {
133
+ return new TextDecoder().decode(
134
+ new Uint8Array(value.buffer, value.byteOffset, value.byteLength)
135
+ );
136
+ }
137
+ throw new Error("CDP WebSocket returned a non-text message");
138
+ }
139
+ function parseResponse(value) {
140
+ if (!isRecord(value) || typeof value.id !== "number") return null;
141
+ const response = { id: value.id };
142
+ if ("result" in value) response.result = value.result;
143
+ if (isRecord(value.error)) {
144
+ response.error = {
145
+ ...typeof value.error.code === "number" ? { code: value.error.code } : {},
146
+ ...typeof value.error.message === "string" ? { message: value.error.message } : {}
147
+ };
148
+ }
149
+ return response;
150
+ }
151
+ async function listCdpTargets(endpoint, fetchImpl = defaultFetch) {
152
+ const baseUrl = loopbackUrl(endpoint, ["http:", "https:"]);
153
+ const targetsUrl = new URL("/json/list", baseUrl).toString();
154
+ const response = await fetchImpl(targetsUrl);
155
+ if (!response.ok) throw new Error(`CDP target discovery failed with HTTP ${response.status}`);
156
+ const value = await response.json();
157
+ if (!Array.isArray(value)) throw new Error("CDP target discovery did not return an array");
158
+ return value.map(parseTarget).filter((target) => target !== null);
159
+ }
160
+ var CdpClient = class _CdpClient {
161
+ #commandTimeoutMs;
162
+ #socket;
163
+ #closed = false;
164
+ #nextId = 1;
165
+ #pending = /* @__PURE__ */ new Map();
166
+ #listeners = /* @__PURE__ */ new Map();
167
+ constructor(socket, commandTimeoutMs) {
168
+ this.#socket = socket;
169
+ this.#commandTimeoutMs = commandTimeoutMs;
170
+ socket.addEventListener("message", (event) => this.#onMessage(event));
171
+ socket.addEventListener("error", () => this.#fail(new Error("CDP WebSocket failed")));
172
+ socket.addEventListener("close", () => this.#fail(new Error("CDP WebSocket closed")));
173
+ }
174
+ static async connect(url, options = {}) {
175
+ loopbackUrl(url, ["ws:", "wss:"]);
176
+ const socketFactory = options.socketFactory ?? defaultSocketFactory;
177
+ const socket = socketFactory(url);
178
+ const connectTimeoutMs = options.connectTimeoutMs ?? 1e4;
179
+ await new Promise((resolve, reject) => {
180
+ const timeout = setTimeout(() => {
181
+ cleanup();
182
+ socket.close();
183
+ reject(new Error("CDP WebSocket connection timed out"));
184
+ }, connectTimeoutMs);
185
+ const onOpen = () => {
186
+ cleanup();
187
+ resolve();
188
+ };
189
+ const onError = () => {
190
+ cleanup();
191
+ reject(new Error("CDP WebSocket connection failed"));
192
+ };
193
+ const cleanup = () => {
194
+ clearTimeout(timeout);
195
+ socket.removeEventListener("open", onOpen);
196
+ socket.removeEventListener("error", onError);
197
+ };
198
+ socket.addEventListener("open", onOpen);
199
+ socket.addEventListener("error", onError);
200
+ });
201
+ return new _CdpClient(socket, options.commandTimeoutMs ?? 1e4);
202
+ }
203
+ command(method, params = {}) {
204
+ return this.#send(method, params);
205
+ }
206
+ sessionCommand(sessionId, method, params = {}) {
207
+ if (sessionId.length === 0) return Promise.reject(new Error("CDP session ID is required"));
208
+ return this.#send(method, params, sessionId);
209
+ }
210
+ on(method, listener) {
211
+ const listeners = this.#listeners.get(method) ?? /* @__PURE__ */ new Set();
212
+ listeners.add(listener);
213
+ this.#listeners.set(method, listeners);
214
+ return () => {
215
+ listeners.delete(listener);
216
+ if (listeners.size === 0) this.#listeners.delete(method);
217
+ };
218
+ }
219
+ #send(method, params, sessionId) {
220
+ if (this.#closed) return Promise.reject(new Error("CDP client is closed"));
221
+ const id = this.#nextId++;
222
+ return new Promise((resolve, reject) => {
223
+ const timeout = setTimeout(() => {
224
+ this.#pending.delete(id);
225
+ reject(new Error(`CDP command '${method}' timed out`));
226
+ }, this.#commandTimeoutMs);
227
+ this.#pending.set(id, { resolve, reject, timeout });
228
+ try {
229
+ this.#socket.send(
230
+ JSON.stringify({ id, method, params, ...sessionId ? { sessionId } : {} })
231
+ );
232
+ } catch (error) {
233
+ clearTimeout(timeout);
234
+ this.#pending.delete(id);
235
+ reject(error instanceof Error ? error : new Error(String(error)));
236
+ }
237
+ });
238
+ }
239
+ async evaluate(expression) {
240
+ const response = await this.command("Runtime.evaluate", {
241
+ expression,
242
+ awaitPromise: true,
243
+ returnByValue: true
244
+ });
245
+ if (!isRecord(response)) throw new Error("Runtime.evaluate returned an invalid result");
246
+ if (isRecord(response.exceptionDetails)) {
247
+ const text = typeof response.exceptionDetails.text === "string" ? response.exceptionDetails.text : "Renderer evaluation failed";
248
+ throw new Error(text);
249
+ }
250
+ if (!isRecord(response.result) || !("value" in response.result)) {
251
+ throw new Error("Runtime.evaluate did not return a value");
252
+ }
253
+ return response.result.value;
254
+ }
255
+ close() {
256
+ if (this.#closed) return;
257
+ this.#closed = true;
258
+ this.#fail(new Error("CDP client closed"));
259
+ this.#listeners.clear();
260
+ this.#socket.close();
261
+ }
262
+ #onMessage(event) {
263
+ try {
264
+ const value = JSON.parse(messageText(event.data));
265
+ const response = parseResponse(value);
266
+ if (!response) {
267
+ if (!isRecord(value) || typeof value.method !== "string") return;
268
+ const sessionId = typeof value.sessionId === "string" ? value.sessionId : void 0;
269
+ for (const listener of this.#listeners.get(value.method) ?? []) {
270
+ listener(value.params, sessionId);
271
+ }
272
+ return;
273
+ }
274
+ const pending = this.#pending.get(response.id);
275
+ if (!pending) return;
276
+ clearTimeout(pending.timeout);
277
+ this.#pending.delete(response.id);
278
+ if (response.error) {
279
+ const code = response.error.code === void 0 ? "unknown" : String(response.error.code);
280
+ pending.reject(
281
+ new Error(response.error.message ?? `CDP command failed with error ${code}`)
282
+ );
283
+ } else {
284
+ pending.resolve(response.result);
285
+ }
286
+ } catch (error) {
287
+ this.#fail(error instanceof Error ? error : new Error(String(error)));
288
+ }
289
+ }
290
+ #fail(error) {
291
+ for (const pending of this.#pending.values()) {
292
+ clearTimeout(pending.timeout);
293
+ pending.reject(error);
294
+ }
295
+ this.#pending.clear();
296
+ }
297
+ };
298
+
299
+ // packages/desktop-control/src/renderer-react-ownership.ts
300
+ function committedReactAncestors(value) {
301
+ const MAX_VISITED_FIBERS = 2e4;
302
+ const fiber = (value2) => typeof value2 === "object" && value2 !== null ? value2 : null;
303
+ const first = fiber(value);
304
+ if (!first) return [];
305
+ const previous = [];
306
+ const seen = /* @__PURE__ */ new Set();
307
+ for (let node = first; node; node = fiber(node.return)) {
308
+ if (seen.has(node) || seen.size >= MAX_VISITED_FIBERS) return [];
309
+ seen.add(node);
310
+ previous.push(node);
311
+ }
312
+ const rootState = fiber(previous.at(-1)?.stateNode);
313
+ if (!rootState || !("current" in rootState)) return previous;
314
+ const current = fiber(rootState.current);
315
+ if (!current) return [];
316
+ const stack = [{ node: current, parent: null }];
317
+ const alternate = fiber(first.alternate);
318
+ seen.clear();
319
+ while (stack.length > 0 && seen.size < MAX_VISITED_FIBERS) {
320
+ const entry = stack.pop();
321
+ if (!entry) break;
322
+ if (seen.has(entry.node)) return [];
323
+ seen.add(entry.node);
324
+ if (entry.node === first || entry.node === alternate) {
325
+ const ancestors = [];
326
+ for (let cursor = entry; cursor; cursor = cursor.parent)
327
+ ancestors.push(cursor.node);
328
+ return ancestors;
329
+ }
330
+ const sibling = entry.parent && fiber(entry.node.sibling);
331
+ if (sibling) stack.push({ node: sibling, parent: entry.parent });
332
+ const child = fiber(entry.node.child);
333
+ if (child) stack.push({ node: child, parent: entry });
334
+ }
335
+ return [];
336
+ }
337
+
338
+ // packages/desktop-control/src/renderer-host-response-ownership.ts
339
+ function retainRendererHostResponses(client, hostId, target) {
340
+ if (typeof client.addRequestLifecycleListener !== "function" || typeof target.addEventListener !== "function" || typeof target.removeEventListener !== "function")
341
+ return () => {
342
+ };
343
+ const isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
344
+ const pending = /* @__PURE__ */ new Set();
345
+ let retired = false;
346
+ let unsubscribe = () => {
347
+ };
348
+ const detach = () => {
349
+ target.removeEventListener?.("message", receive);
350
+ unsubscribe();
351
+ };
352
+ const receive = (event) => {
353
+ const { source, origin, data } = event;
354
+ if (source != null && source !== target) return;
355
+ const ownOrigin = target.location?.origin;
356
+ if (source === target && ownOrigin && ownOrigin !== "null" && origin !== ownOrigin) return;
357
+ if (!isRecord3(data) || data.type !== "mcp-response" || data.hostId !== hostId) return;
358
+ const message = data.message;
359
+ if (!isRecord3(message)) return;
360
+ const id = message.id;
361
+ if (typeof id !== "string" && typeof id !== "number" || !pending.has(id)) return;
362
+ const hasError = isRecord3(message.error);
363
+ if (!hasError && !Object.prototype.hasOwnProperty.call(message, "result")) return;
364
+ queueMicrotask(() => {
365
+ if (!pending.has(id)) return;
366
+ if (hasError) client.onError(id, message.error, data.hostMetrics);
367
+ else client.onResult(id, message.result, data.hostMetrics);
368
+ });
369
+ };
370
+ target.addEventListener("message", receive);
371
+ unsubscribe = client.addRequestLifecycleListener((event) => {
372
+ if (!isRecord3(event) || event.hostId !== hostId) return;
373
+ const id = event.id;
374
+ if (typeof id !== "string" && typeof id !== "number") return;
375
+ if (!retired && event.type === "started" && typeof event.method === "string" && event.method.startsWith("codexhost/"))
376
+ pending.add(id);
377
+ else if (event.type === "completed" || event.type === "failed") {
378
+ pending.delete(id);
379
+ if (retired && pending.size === 0) detach();
380
+ }
381
+ });
382
+ return () => {
383
+ retired = true;
384
+ if (pending.size === 0) detach();
385
+ };
386
+ }
387
+
388
+ // packages/desktop-control/src/renderer-draft-prewarm-runtime.ts
389
+ function createDraftPrewarmPolicyBridge(manager, bridge, hostId, target, prewarmedThreadManager, isCurrentManager, retainResponses = retainRendererHostResponses) {
390
+ let disposed = false;
391
+ const originalSend = bridge.sendRequest;
392
+ const originalPrewarm = bridge.prewarmThreadStart;
393
+ const originalOnNotification = manager.onNotification;
394
+ const originalDispatchAppServerResponse = manager.dispatchAppServerResponse;
395
+ let selectedModel = null;
396
+ let prewarmGeneration = 0;
397
+ const isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
398
+ const isUnsupportedPosixBridgeSpawn = (value) => {
399
+ const marker = "AbsolutePathBuf deserialized without a base path";
400
+ if (typeof value === "string") {
401
+ return value.startsWith("Invalid request:") && value.includes(marker);
402
+ }
403
+ if (!isRecord3(value)) return false;
404
+ if (typeof value.message === "string" && value.message.includes(marker)) {
405
+ if (value.code === -32600 || value.message.startsWith("Invalid request:")) return true;
406
+ }
407
+ return value.cause !== value && isUnsupportedPosixBridgeSpawn(value.cause);
408
+ };
409
+ const isRemoteControlHost = hostId.startsWith("remote-control:");
410
+ const retireResponses = isRemoteControlHost ? () => {
411
+ } : retainResponses(bridge, hostId, target);
412
+ const knownExternalThreadIds = /* @__PURE__ */ new Set();
413
+ const knownOfficialThreadIds = /* @__PURE__ */ new Set();
414
+ const threadOwnershipResolutions = /* @__PURE__ */ new Map();
415
+ const createBridgeProcessHandle = () => `codexhost-${typeof crypto?.randomUUID === "function" ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(16).slice(2)}`}`;
416
+ let bridgeProcessHandle = createBridgeProcessHandle();
417
+ const bridgeReadyMethod = "codexhost/remote-control-bridge/ready";
418
+ const bridgeRequests = /* @__PURE__ */ new Map();
419
+ const bridgeServerRequestIdPrefix = "codexhost/remote-control-bridge/server-request/";
420
+ const bridgeServerRequests = /* @__PURE__ */ new Map();
421
+ let nextBridgeServerRequestOrdinal = 1;
422
+ let outputDecoders = {
423
+ stdout: new TextDecoder(),
424
+ stderr: new TextDecoder()
425
+ };
426
+ const outputBuffers = { stdout: "", stderr: "" };
427
+ let bridgeState = "idle";
428
+ let bridgeReadyPromise = null;
429
+ let bridgeReadyResolve = null;
430
+ let bridgeReadyReject = null;
431
+ let bridgeReadyTimeout = null;
432
+ let bridgeInitialization = null;
433
+ let writeTail = Promise.resolve();
434
+ const transportError = (message, cause) => {
435
+ const error = new Error(`codexhost Remote Control bridge: ${message}`);
436
+ if (cause !== void 0) Object.assign(error, { cause });
437
+ return error;
438
+ };
439
+ const utf8Base64 = (value) => {
440
+ const bytes = new TextEncoder().encode(value);
441
+ let binary = "";
442
+ for (let index = 0; index < bytes.length; index += 32768) {
443
+ binary += String.fromCharCode(...bytes.subarray(index, index + 32768));
444
+ }
445
+ return btoa(binary);
446
+ };
447
+ const utf16LeBase64 = (value) => {
448
+ let binary = "";
449
+ for (let index = 0; index < value.length; index += 1) {
450
+ const codeUnit = value.charCodeAt(index);
451
+ binary += String.fromCharCode(codeUnit & 255, codeUnit >>> 8);
452
+ }
453
+ return btoa(binary);
454
+ };
455
+ const failBridge = (cause, terminateProcess = true) => {
456
+ if (bridgeState === "failed" || bridgeState === "disposed") return;
457
+ const failedProcessHandle = bridgeProcessHandle;
458
+ const terminate = terminateProcess && (bridgeState === "starting" || bridgeState === "ready");
459
+ bridgeState = "failed";
460
+ if (bridgeReadyTimeout !== null) globalThis.clearTimeout(bridgeReadyTimeout);
461
+ bridgeReadyTimeout = null;
462
+ const stderr = outputBuffers.stderr.trim().slice(-1e3);
463
+ const error = transportError(
464
+ `${cause instanceof Error ? cause.message : String(cause)}${stderr ? `; ${stderr}` : ""}`,
465
+ cause
466
+ );
467
+ bridgeReadyReject?.(error);
468
+ bridgeReadyReject = null;
469
+ bridgeReadyResolve = null;
470
+ for (const requestId of bridgeRequests.keys()) bridge.onError(requestId, error);
471
+ bridgeRequests.clear();
472
+ if (isRemoteControlHost && terminate) {
473
+ void Promise.resolve(
474
+ originalSend.call(bridge, "process/kill", { processHandle: failedProcessHandle })
475
+ ).catch(() => void 0);
476
+ }
477
+ };
478
+ const resetFailedBridge = () => {
479
+ if (bridgeState !== "failed") return;
480
+ bridgeProcessHandle = createBridgeProcessHandle();
481
+ outputDecoders = { stdout: new TextDecoder(), stderr: new TextDecoder() };
482
+ outputBuffers.stdout = "";
483
+ outputBuffers.stderr = "";
484
+ bridgeReadyPromise = null;
485
+ bridgeReadyResolve = null;
486
+ bridgeReadyReject = null;
487
+ bridgeInitialization = null;
488
+ writeTail = Promise.resolve();
489
+ bridgeServerRequests.clear();
490
+ nextBridgeServerRequestOrdinal = 1;
491
+ bridgeState = "idle";
492
+ };
493
+ const rememberExternalThread = (value) => {
494
+ if (!isRecord3(value) || typeof value.id !== "string") return;
495
+ if (value.modelProvider === "codexhost" || value.cliVersion === "codexhost") {
496
+ knownExternalThreadIds.add(value.id);
497
+ knownOfficialThreadIds.delete(value.id);
498
+ }
499
+ };
500
+ const observeBridgeResult = (request, result) => {
501
+ if (!request || !isRecord3(result)) return;
502
+ if (request.method === "thread/list" && Array.isArray(result.data)) {
503
+ for (const thread of result.data) rememberExternalThread(thread);
504
+ return;
505
+ }
506
+ if (request.method === "thread/start" || request.method === "thread/read" || request.method === "thread/resume") {
507
+ rememberExternalThread(result.thread);
508
+ return;
509
+ }
510
+ if (request.method === "codexhost/thread/inspect" && isRecord3(request.parameters) && typeof request.parameters.threadId === "string") {
511
+ if (result.owner === "external") {
512
+ knownExternalThreadIds.add(request.parameters.threadId);
513
+ knownOfficialThreadIds.delete(request.parameters.threadId);
514
+ } else if (result.owner === "codex") {
515
+ knownOfficialThreadIds.add(request.parameters.threadId);
516
+ knownExternalThreadIds.delete(request.parameters.threadId);
517
+ }
518
+ return;
519
+ }
520
+ if (request.method === "thread/delete" && isRecord3(request.parameters) && typeof request.parameters.threadId === "string") {
521
+ knownExternalThreadIds.delete(request.parameters.threadId);
522
+ knownOfficialThreadIds.delete(request.parameters.threadId);
523
+ }
524
+ };
525
+ const handleBridgeFrame = (value) => {
526
+ if (!isRecord3(value)) {
527
+ failBridge("received a non-object app-server frame");
528
+ return;
529
+ }
530
+ if (value.method === bridgeReadyMethod && value.id === void 0) {
531
+ if (isRecord3(value.params) && value.params.protocolVersion === 1) {
532
+ bridgeState = "ready";
533
+ if (bridgeReadyTimeout !== null) globalThis.clearTimeout(bridgeReadyTimeout);
534
+ bridgeReadyTimeout = null;
535
+ bridgeReadyResolve?.();
536
+ bridgeReadyResolve = null;
537
+ bridgeReadyReject = null;
538
+ } else {
539
+ failBridge("reported an unsupported protocol version");
540
+ }
541
+ return;
542
+ }
543
+ if (typeof value.method === "string" && value.id === void 0) {
544
+ if (value.method === "thread/started" && isRecord3(value.params)) {
545
+ rememberExternalThread(value.params.thread);
546
+ }
547
+ originalOnNotification.call(manager, value.method, value.params);
548
+ return;
549
+ }
550
+ if (typeof value.method === "string" && value.id !== void 0) {
551
+ const outerRequestId = `${bridgeServerRequestIdPrefix}${bridgeProcessHandle}/${nextBridgeServerRequestOrdinal}`;
552
+ nextBridgeServerRequestOrdinal += 1;
553
+ bridgeServerRequests.set(outerRequestId, value.id);
554
+ manager.onRequest({ ...value, id: outerRequestId });
555
+ return;
556
+ }
557
+ if (value.id === void 0) {
558
+ failBridge("received an app-server response without an id");
559
+ return;
560
+ }
561
+ const request = bridgeRequests.get(value.id);
562
+ bridgeRequests.delete(value.id);
563
+ if ("error" in value) {
564
+ bridge.onError(value.id, value.error, value.metrics);
565
+ return;
566
+ }
567
+ observeBridgeResult(request, value.result);
568
+ bridge.onResult(value.id, value.result, value.metrics);
569
+ };
570
+ const consumeBridgeOutput = (stream, base64) => {
571
+ const binary = atob(base64);
572
+ const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
573
+ outputBuffers[stream] += outputDecoders[stream].decode(bytes, { stream: true });
574
+ if (stream === "stderr") {
575
+ outputBuffers.stderr = outputBuffers.stderr.slice(-4e3);
576
+ return;
577
+ }
578
+ while (true) {
579
+ const newline = outputBuffers.stdout.indexOf("\n");
580
+ if (newline < 0) break;
581
+ const line = outputBuffers.stdout.slice(0, newline).trim();
582
+ outputBuffers.stdout = outputBuffers.stdout.slice(newline + 1);
583
+ if (!line) continue;
584
+ try {
585
+ handleBridgeFrame(JSON.parse(line));
586
+ } catch (error) {
587
+ failBridge(
588
+ `emitted invalid JSON: ${error instanceof Error ? error.message : String(error)}`
589
+ );
590
+ return;
591
+ }
592
+ }
593
+ };
594
+ const handleOuterNotification = (method, parameters) => {
595
+ if (method === "process/exited" && isRecord3(parameters) && parameters.processHandle === bridgeProcessHandle) {
596
+ const exitCode = typeof parameters.exitCode === "number" ? parameters.exitCode : "unknown";
597
+ const stderr = typeof parameters.stderr === "string" ? parameters.stderr.trim() : "";
598
+ failBridge(
599
+ `process exited unexpectedly with code ${exitCode}${stderr ? `: ${stderr}` : ""}`,
600
+ false
601
+ );
602
+ return true;
603
+ }
604
+ if (method !== "process/outputDelta" || !isRecord3(parameters) || parameters.processHandle !== bridgeProcessHandle || parameters.stream !== "stdout" && parameters.stream !== "stderr" || typeof parameters.deltaBase64 !== "string") {
605
+ return false;
606
+ }
607
+ if (parameters.capReached === true) {
608
+ failBridge(`${parameters.stream} was truncated`);
609
+ return true;
610
+ }
611
+ consumeBridgeOutput(parameters.stream, parameters.deltaBase64);
612
+ return true;
613
+ };
614
+ const startBridge = () => {
615
+ if (!isRemoteControlHost) return Promise.resolve();
616
+ if (bridgeReadyPromise) return bridgeReadyPromise;
617
+ bridgeState = "starting";
618
+ bridgeReadyPromise = new Promise((resolve, reject) => {
619
+ bridgeReadyResolve = resolve;
620
+ bridgeReadyReject = reject;
621
+ });
622
+ bridgeReadyTimeout = globalThis.setTimeout(
623
+ () => failBridge("startup timed out after 15000ms"),
624
+ 15e3
625
+ );
626
+ const powershellScript = "$ErrorActionPreference = 'Stop'; $descriptorPath = Join-Path $env:LOCALAPPDATA 'codexhost\\remote-control-bridge-v1.json'; $descriptor = Get-Content -LiteralPath $descriptorPath -Raw | ConvertFrom-Json; if ($descriptor.schemaVersion -ne 1) { throw 'Unsupported codexhost Remote Control descriptor' }; $nodePath = [string]$descriptor.nodePath; $runtimePath = [string]$descriptor.runtimePath; if (-not [System.IO.Path]::IsPathRooted($nodePath) -or -not [System.IO.Path]::IsPathRooted($runtimePath)) { throw 'Invalid codexhost Remote Control runtime path' }; $owner = Get-Process -Id ([int]$descriptor.ownerPid) -ErrorAction SilentlyContinue; if ($null -eq $owner) { throw 'CodexHost Remote Control runtime is not running' }; $env:CODEXHOST_REMOTE_CONTROL_BRIDGE_PIPE = [string]$descriptor.pipePath; & $nodePath $runtimePath '--codexhost-remote-control-bridge'; exit $LASTEXITCODE";
627
+ const command = [
628
+ "powershell.exe",
629
+ "-NoLogo",
630
+ "-NoProfile",
631
+ "-NonInteractive",
632
+ "-EncodedCommand",
633
+ utf16LeBase64(powershellScript)
634
+ ];
635
+ const startedProcessHandle = bridgeProcessHandle;
636
+ try {
637
+ const started = originalSend.call(bridge, "process/spawn", {
638
+ command,
639
+ processHandle: startedProcessHandle,
640
+ cwd: "C:\\",
641
+ streamStdin: true,
642
+ streamStdoutStderr: true,
643
+ outputBytesCap: null,
644
+ timeoutMs: null
645
+ });
646
+ void Promise.resolve(started).catch((error) => {
647
+ if (bridgeProcessHandle === startedProcessHandle) failBridge(error, false);
648
+ });
649
+ } catch (error) {
650
+ failBridge(error);
651
+ }
652
+ return bridgeReadyPromise;
653
+ };
654
+ const writeBridgeFrame = (value) => {
655
+ const operation = async () => {
656
+ await startBridge();
657
+ if (bridgeState !== "ready") throw transportError("is not ready");
658
+ await originalSend.call(bridge, "process/writeStdin", {
659
+ processHandle: bridgeProcessHandle,
660
+ deltaBase64: utf8Base64(`${JSON.stringify(value)}
661
+ `)
662
+ });
663
+ };
664
+ const next = writeTail.then(operation, operation);
665
+ writeTail = next.catch(() => void 0);
666
+ return next;
667
+ };
668
+ const enqueueBridgeRequest = (method, parameters, options) => bridge.enqueueRequest(method, parameters, options, (request) => {
669
+ bridgeRequests.set(request.id, { method, parameters });
670
+ void writeBridgeFrame(request).catch((error) => failBridge(error));
671
+ });
672
+ const initializeBridgeProtocol = () => {
673
+ const initialization = enqueueBridgeRequest("initialize", {
674
+ clientInfo: {
675
+ name: "codexhost_remote_control_bridge",
676
+ title: "codexhost Remote Control bridge",
677
+ version: "1"
678
+ },
679
+ capabilities: {
680
+ experimentalApi: true,
681
+ mcpServerOpenaiFormElicitation: true
682
+ }
683
+ });
684
+ return new Promise((resolve, reject) => {
685
+ const timeout = globalThis.setTimeout(() => {
686
+ const message = "initialization timed out after 15000ms";
687
+ failBridge(message);
688
+ reject(transportError(message));
689
+ }, 15e3);
690
+ void Promise.resolve(initialization).then(
691
+ (value) => {
692
+ globalThis.clearTimeout(timeout);
693
+ resolve(value);
694
+ },
695
+ (error) => {
696
+ globalThis.clearTimeout(timeout);
697
+ reject(error);
698
+ }
699
+ );
700
+ });
701
+ };
702
+ const initializeBridge = () => {
703
+ resetFailedBridge();
704
+ if (bridgeInitialization) return bridgeInitialization;
705
+ bridgeInitialization = startBridge().then(() => initializeBridgeProtocol()).then(() => writeBridgeFrame({ method: "initialized", params: {} }));
706
+ return bridgeInitialization;
707
+ };
708
+ const threadIdFromParameters = (parameters) => {
709
+ if (!isRecord3(parameters)) return null;
710
+ const value = parameters.threadId ?? parameters.conversationId;
711
+ return typeof value === "string" ? value : null;
712
+ };
713
+ const isThreadScopedMethod = (method) => method.startsWith("thread/") || method.startsWith("turn/") || method.startsWith("review/");
714
+ const resolveThreadOwnership = (threadId) => {
715
+ if (knownExternalThreadIds.has(threadId)) return Promise.resolve("external");
716
+ if (knownOfficialThreadIds.has(threadId)) return Promise.resolve("codex");
717
+ const pending = threadOwnershipResolutions.get(threadId);
718
+ if (pending) return pending;
719
+ const resolution = initializeBridge().then(
720
+ () => enqueueBridgeRequest("codexhost/thread/ownership/list", {
721
+ threadIds: [threadId]
722
+ })
723
+ ).then((value) => {
724
+ const ownerships = isRecord3(value) && Array.isArray(value.threads) ? value.threads : null;
725
+ const ownership = ownerships?.[0];
726
+ if (!ownerships || !isRecord3(ownership) || ownerships.length !== 1 || ownership.threadId !== threadId || ownership.owner !== "external" && ownership.owner !== "codex") {
727
+ throw transportError("Thread ownership lookup returned an invalid result");
728
+ }
729
+ if (ownership.owner === "external") {
730
+ knownExternalThreadIds.add(threadId);
731
+ knownOfficialThreadIds.delete(threadId);
732
+ return "external";
733
+ }
734
+ knownOfficialThreadIds.add(threadId);
735
+ knownExternalThreadIds.delete(threadId);
736
+ return "codex";
737
+ });
738
+ threadOwnershipResolutions.set(threadId, resolution);
739
+ const clearResolution = () => {
740
+ if (threadOwnershipResolutions.get(threadId) === resolution) {
741
+ threadOwnershipResolutions.delete(threadId);
742
+ }
743
+ };
744
+ void resolution.then(clearResolution, clearResolution);
745
+ return resolution;
746
+ };
747
+ const shouldResolveThreadOwnership = (method, parameters) => {
748
+ if (!isRemoteControlHost || method.startsWith("codexhost/") || !isThreadScopedMethod(method)) {
749
+ return null;
750
+ }
751
+ const threadId = threadIdFromParameters(parameters);
752
+ if (!threadId || knownExternalThreadIds.has(threadId) || knownOfficialThreadIds.has(threadId)) {
753
+ return null;
754
+ }
755
+ return threadId;
756
+ };
757
+ const shouldUseBridge = (method, parameters) => {
758
+ if (!isRemoteControlHost) return false;
759
+ if (method.startsWith("codexhost/")) return true;
760
+ if (method === "thread/list") return true;
761
+ if (method === "thread/start") {
762
+ return isRecord3(parameters) && typeof parameters.model === "string" && parameters.model.startsWith("codexhost/");
763
+ }
764
+ const threadId = threadIdFromParameters(parameters);
765
+ if (threadId && knownExternalThreadIds.has(threadId)) return true;
766
+ if (threadId && knownOfficialThreadIds.has(threadId)) return false;
767
+ return selectedModel !== null && (method.startsWith("thread/") || method.startsWith("turn/") || method.startsWith("review/"));
768
+ };
769
+ const routeThreadStart = (parameters) => {
770
+ if (!isRecord3(parameters) || parameters.ephemeral === true) {
771
+ return parameters;
772
+ }
773
+ return {
774
+ ...parameters,
775
+ ...selectedModel === null ? {} : { model: selectedModel }
776
+ };
777
+ };
778
+ const routedSend = (method, parameters, options) => {
779
+ const routedParameters = method === "thread/start" ? routeThreadStart(parameters) : parameters;
780
+ const sendBridged = () => initializeBridge().then(
781
+ () => enqueueBridgeRequest(method, routedParameters, options)
782
+ );
783
+ const sendDirect = () => options === void 0 ? originalSend.call(bridge, method, routedParameters) : originalSend.call(bridge, method, routedParameters, options);
784
+ const unresolvedThreadId = shouldResolveThreadOwnership(method, routedParameters);
785
+ if (unresolvedThreadId) {
786
+ return resolveThreadOwnership(unresolvedThreadId).then(
787
+ (owner) => owner === "external" ? sendBridged() : sendDirect(),
788
+ (error) => {
789
+ if ((method === "thread/read" || method === "thread/resume") && isUnsupportedPosixBridgeSpawn(error)) {
790
+ return Promise.resolve(sendDirect()).then((result) => {
791
+ knownOfficialThreadIds.add(unresolvedThreadId);
792
+ knownExternalThreadIds.delete(unresolvedThreadId);
793
+ return result;
794
+ });
795
+ }
796
+ throw error;
797
+ }
798
+ );
799
+ }
800
+ return shouldUseBridge(method, routedParameters) ? sendBridged() : sendDirect();
801
+ };
802
+ const publishDraftWorkspace = (parameters) => {
803
+ if (!isRecord3(parameters) || parameters.ephemeral === true) return;
804
+ const cwd = parameters.cwd;
805
+ if (typeof cwd !== "string" || cwd.length === 0) return;
806
+ const drafts = isRecord3(target.__codexhostDraftWorkspacesV1) ? target.__codexhostDraftWorkspacesV1 : {};
807
+ drafts[hostId] = cwd;
808
+ Object.defineProperty(target, "__codexhostDraftWorkspacesV1", {
809
+ configurable: true,
810
+ value: drafts
811
+ });
812
+ if (typeof target.dispatchEvent === "function" && typeof CustomEvent === "function") {
813
+ target.dispatchEvent(
814
+ new CustomEvent("codexhost:draft-workspace", { detail: { hostId, cwd } })
815
+ );
816
+ }
817
+ };
818
+ const routedPrewarm = (parameters, options) => {
819
+ const routedParameters = routeThreadStart(parameters);
820
+ const generation = prewarmGeneration;
821
+ publishDraftWorkspace(routedParameters);
822
+ const pending = shouldUseBridge("thread/start", routedParameters) ? routedSend("thread/start", routedParameters, options) : options === void 0 ? originalPrewarm.call(bridge, routedParameters) : originalPrewarm.call(bridge, routedParameters, options);
823
+ if (isRecord3(parameters) && parameters.ephemeral === true || typeof pending !== "object" && typeof pending !== "function" || pending === null || typeof Reflect.get(pending, "then") !== "function") {
824
+ return pending;
825
+ }
826
+ return Promise.resolve(pending).then((result) => {
827
+ if (disposed || generation !== prewarmGeneration) {
828
+ throw new Error("Renderer draft prewarm was invalidated by a configuration change");
829
+ }
830
+ publishDraftWorkspace(routedParameters);
831
+ return result;
832
+ });
833
+ };
834
+ bridge.sendRequest = routedSend;
835
+ bridge.prewarmThreadStart = routedPrewarm;
836
+ const routedWindowMessage = (event) => {
837
+ const message = event.data;
838
+ if (!isRecord3(message) || message.type !== "mcp-notification" || message.hostId !== hostId || typeof message.method !== "string") {
839
+ return;
840
+ }
841
+ handleOuterNotification(message.method, message.params);
842
+ };
843
+ const observesWindowNotifications = isRemoteControlHost && typeof target.addEventListener === "function" && typeof target.removeEventListener === "function";
844
+ if (observesWindowNotifications) target.addEventListener?.("message", routedWindowMessage);
845
+ const routedOnNotification = (method, parameters) => {
846
+ if (!handleOuterNotification(method, parameters)) {
847
+ originalOnNotification.call(manager, method, parameters);
848
+ }
849
+ };
850
+ const routedDispatchAppServerResponse = (method, response) => {
851
+ if (isRemoteControlHost && typeof response.id === "string") {
852
+ const innerRequestId = bridgeServerRequests.get(response.id);
853
+ if (innerRequestId !== void 0 || bridgeServerRequests.has(response.id)) {
854
+ bridgeServerRequests.delete(response.id);
855
+ void writeBridgeFrame({ ...response, id: innerRequestId }).catch(
856
+ (error) => failBridge(error)
857
+ );
858
+ return void 0;
859
+ }
860
+ if (response.id.startsWith(bridgeServerRequestIdPrefix)) return void 0;
861
+ }
862
+ return originalDispatchAppServerResponse.call(manager, method, response);
863
+ };
864
+ if (!observesWindowNotifications) manager.onNotification = routedOnNotification;
865
+ manager.dispatchAppServerResponse = routedDispatchAppServerResponse;
866
+ const policy = Object.freeze({
867
+ state: "ready",
868
+ hostId,
869
+ owns(candidateManager, candidate, candidateHostId, candidatePrewarmedThreadManager, requiresCurrentManager) {
870
+ return !disposed && candidateManager === manager && candidate === bridge && candidateHostId === hostId && candidatePrewarmedThreadManager === prewarmedThreadManager && requiresCurrentManager === !!isCurrentManager;
871
+ },
872
+ requestTarget() {
873
+ if (disposed || isCurrentManager && !isCurrentManager())
874
+ throw new Error("Renderer request manager is retired");
875
+ return manager;
876
+ },
877
+ select(model) {
878
+ if (disposed || isCurrentManager && !isCurrentManager())
879
+ throw new Error("Renderer request manager is retired");
880
+ if (model !== null && (typeof model !== "string" || !model.startsWith("codexhost/"))) {
881
+ throw new Error("Draft route Model must be a codexhost transport carrier");
882
+ }
883
+ if (selectedModel === model) return false;
884
+ selectedModel = model;
885
+ prewarmGeneration += 1;
886
+ return true;
887
+ },
888
+ clear() {
889
+ if (disposed || isCurrentManager && !isCurrentManager())
890
+ return Promise.reject(new Error("Renderer request manager is retired"));
891
+ prewarmGeneration += 1;
892
+ prewarmedThreadManager.discardAllPrewarmedThreads();
893
+ return Promise.resolve();
894
+ },
895
+ dispose() {
896
+ if (disposed) return;
897
+ disposed = true;
898
+ prewarmGeneration += 1;
899
+ retireResponses();
900
+ if (bridge.sendRequest === routedSend) bridge.sendRequest = originalSend;
901
+ if (bridge.prewarmThreadStart === routedPrewarm) {
902
+ bridge.prewarmThreadStart = originalPrewarm;
903
+ }
904
+ if (observesWindowNotifications) {
905
+ target.removeEventListener?.("message", routedWindowMessage);
906
+ } else if (manager.onNotification === routedOnNotification) {
907
+ manager.onNotification = originalOnNotification;
908
+ }
909
+ if (manager.dispatchAppServerResponse === routedDispatchAppServerResponse) {
910
+ manager.dispatchAppServerResponse = originalDispatchAppServerResponse;
911
+ }
912
+ if (bridgeReadyTimeout !== null) globalThis.clearTimeout(bridgeReadyTimeout);
913
+ bridgeReadyTimeout = null;
914
+ if (isRemoteControlHost && (bridgeState === "starting" || bridgeState === "ready")) {
915
+ void Promise.resolve(
916
+ originalSend.call(bridge, "process/kill", { processHandle: bridgeProcessHandle })
917
+ ).catch(() => void 0);
918
+ }
919
+ bridgeState = "disposed";
920
+ const disposedError = transportError("was disposed");
921
+ bridgeReadyReject?.(disposedError);
922
+ for (const requestId of bridgeRequests.keys()) bridge.onError(requestId, disposedError);
923
+ bridgeRequests.clear();
924
+ bridgeServerRequests.clear();
925
+ knownExternalThreadIds.clear();
926
+ knownOfficialThreadIds.clear();
927
+ threadOwnershipResolutions.clear();
928
+ selectedModel = null;
929
+ }
930
+ });
931
+ return policy;
932
+ }
933
+
934
+ // packages/desktop-control/src/renderer-host-discovery.ts
935
+ function requestManagerFromHookState(value, hostId) {
936
+ const record = (candidate) => typeof candidate === "object" && candidate !== null;
937
+ const manager = (candidate) => {
938
+ if (!record(candidate) || !record(candidate.requestClient) || !record(candidate.prewarmedThreadManager))
939
+ return null;
940
+ const bridge = candidate.requestClient;
941
+ return typeof candidate.sendRequest === "function" && typeof bridge.sendRequest === "function" && typeof bridge.prewarmThreadStart === "function" && typeof bridge.enqueueRequest === "function" && typeof candidate.prewarmedThreadManager.discardAllPrewarmedThreads === "function" ? candidate : null;
942
+ };
943
+ if (!record(value)) return null;
944
+ const direct = manager(value) ?? manager(value.manager);
945
+ if (direct) return direct;
946
+ if (!hostId || typeof value.addManager !== "function" || typeof value.getForHostId !== "function" || typeof value.waitForManagerForHostId !== "function")
947
+ return null;
948
+ return manager(value.getForHostId(hostId));
949
+ }
950
+ function discoverRendererHosts(root, ancestors = committedReactAncestors, managerFrom = requestManagerFromHookState) {
951
+ const editors = [
952
+ ...root.querySelectorAll('[data-codex-composer], [contenteditable="true"][role="textbox"]')
953
+ ];
954
+ const hostIds = /* @__PURE__ */ new Set();
955
+ const managers = /* @__PURE__ */ new Set();
956
+ const registries = /* @__PURE__ */ new Set();
957
+ const fibers = /* @__PURE__ */ new Set();
958
+ for (const editor of editors) {
959
+ let element = editor;
960
+ let fiber;
961
+ while (element && !fiber) {
962
+ const key = Object.getOwnPropertyNames(element).find(
963
+ (name) => name.startsWith("__reactFiber$")
964
+ );
965
+ if (key) fiber = Object.getOwnPropertyDescriptor(element, key)?.value;
966
+ element = element.parentElement ?? null;
967
+ }
968
+ for (const current of ancestors(fiber)) {
969
+ fibers.add(current);
970
+ const props = current.memoizedProps;
971
+ for (const key of ["executionTargetHostId", "permissionsHostId"]) {
972
+ const hostId = props?.[key];
973
+ if (typeof hostId === "string" && hostId) hostIds.add(hostId);
974
+ }
975
+ }
976
+ }
977
+ for (const fiber of fibers) {
978
+ let hook = fiber.memoizedState;
979
+ for (let index = 0; hook && index < 120; index += 1) {
980
+ const value = hook.memoizedState;
981
+ const manager = managerFrom(value);
982
+ if (manager) managers.add(manager);
983
+ if (value && typeof value === "object" && "addManager" in value && typeof value.addManager === "function" && "getForHostId" in value && typeof value.getForHostId === "function" && "waitForManagerForHostId" in value && typeof value.waitForManagerForHostId === "function") {
984
+ registries.add(value);
985
+ }
986
+ hook = hook.next && typeof hook.next === "object" ? hook.next : null;
987
+ }
988
+ }
989
+ return {
990
+ editorCount: editors.length,
991
+ hostIds: [...hostIds],
992
+ managers: [...managers],
993
+ registries: [...registries]
994
+ };
995
+ }
996
+ function resolveRendererHostManager(discovery, hostId, managerFrom = requestManagerFromHookState) {
997
+ if (!hostId) return null;
998
+ const candidates = discovery.registries.length ? discovery.registries.map((registry) => managerFrom(registry.getForHostId(hostId))) : discovery.managers;
999
+ const matching = new Set(
1000
+ candidates.filter(
1001
+ (manager) => manager && (manager.getHostId?.() ?? manager.requestClient.hostId) === hostId && (manager.requestClient.hostId === void 0 || manager.requestClient.hostId === hostId)
1002
+ )
1003
+ );
1004
+ return matching.size === 1 ? matching.values().next().value ?? null : null;
1005
+ }
1006
+
1007
+ // packages/desktop-control/src/renderer-host-routing.ts
1008
+ function installRendererHostRouting(root, target, discover, resolve, createPolicy) {
1009
+ const existing = target.__codexhostHostRoutingV1;
1010
+ if (existing) return existing;
1011
+ target.__codexhostDraftPrewarmPolicyV1?.dispose?.();
1012
+ let disposed = false;
1013
+ let lastDiscovery = null;
1014
+ const routes = /* @__PURE__ */ new Map();
1015
+ const read = (composer) => {
1016
+ const source = composer?.matches?.(
1017
+ '[data-codex-composer], [contenteditable="true"][role="textbox"]'
1018
+ ) ? { querySelectorAll: () => [composer] } : composer ?? root;
1019
+ const discovery = discover(source);
1020
+ if (composer) return discovery;
1021
+ if (discovery.editorCount === 0 && lastDiscovery) return lastDiscovery;
1022
+ lastDiscovery = discovery;
1023
+ return discovery;
1024
+ };
1025
+ const lookup = (hostId, discovery) => {
1026
+ if (disposed) return null;
1027
+ try {
1028
+ return resolve(discovery ?? read(), hostId);
1029
+ } catch {
1030
+ return null;
1031
+ }
1032
+ };
1033
+ const retire = (hostId) => {
1034
+ const entry = routes.get(hostId);
1035
+ routes.delete(hostId);
1036
+ entry?.route.policy.dispose();
1037
+ };
1038
+ const routeFor = (hostId, discovery) => {
1039
+ const manager = lookup(hostId, discovery);
1040
+ const previous = routes.get(hostId);
1041
+ if (manager && previous?.route.manager === manager && previous.bridge === manager.requestClient && previous.prewarmed === manager.prewarmedThreadManager && previous.route.policy.owns(
1042
+ manager,
1043
+ manager.requestClient,
1044
+ hostId,
1045
+ manager.prewarmedThreadManager,
1046
+ true
1047
+ )) {
1048
+ return previous.route;
1049
+ }
1050
+ retire(hostId);
1051
+ if (!manager) return null;
1052
+ const bridge = manager.requestClient;
1053
+ const prewarmed = manager.prewarmedThreadManager;
1054
+ const policy = createPolicy(
1055
+ manager,
1056
+ bridge,
1057
+ hostId,
1058
+ target,
1059
+ prewarmed,
1060
+ () => lookup(hostId) === manager && manager.requestClient === bridge && manager.prewarmedThreadManager === prewarmed
1061
+ );
1062
+ const route = { hostId, manager, policy };
1063
+ routes.set(hostId, { route, bridge, prewarmed });
1064
+ return route;
1065
+ };
1066
+ const publish = (route) => {
1067
+ const policy = route?.policy;
1068
+ if (target.__codexhostDraftPrewarmPolicyV1 === policy) return;
1069
+ Object.defineProperty(target, "__codexhostDraftPrewarmPolicyV1", {
1070
+ configurable: true,
1071
+ value: policy
1072
+ });
1073
+ if (typeof target.dispatchEvent === "function" && typeof CustomEvent === "function") {
1074
+ target.dispatchEvent(new CustomEvent("codexhost:draft-prewarm-policy-changed"));
1075
+ }
1076
+ };
1077
+ const composerHostId = (discovery) => {
1078
+ if (discovery.hostIds.length === 0 && discovery.registries.length > 0) return null;
1079
+ const hostIds = new Set(
1080
+ discovery.hostIds.length ? discovery.hostIds : discovery.managers.map(
1081
+ (manager) => manager.getHostId?.() ?? manager.requestClient.hostId
1082
+ )
1083
+ );
1084
+ const hostId = hostIds.size === 1 ? hostIds.values().next().value : null;
1085
+ return typeof hostId === "string" && hostId ? hostId : null;
1086
+ };
1087
+ const routing = {
1088
+ forHost(hostId) {
1089
+ return hostId ? routeFor(hostId) : null;
1090
+ },
1091
+ hostIdForComposer(composer) {
1092
+ if (disposed) return null;
1093
+ try {
1094
+ return composerHostId(read(composer));
1095
+ } catch {
1096
+ return null;
1097
+ }
1098
+ },
1099
+ forComposer(composer) {
1100
+ if (disposed) return null;
1101
+ let discovery;
1102
+ try {
1103
+ discovery = read(composer);
1104
+ } catch {
1105
+ publish(null);
1106
+ return null;
1107
+ }
1108
+ if (!composer) {
1109
+ for (const [hostId2, entry] of routes) {
1110
+ const manager = lookup(hostId2, discovery);
1111
+ if (manager !== entry.route.manager || manager.requestClient !== entry.bridge || manager.prewarmedThreadManager !== entry.prewarmed)
1112
+ retire(hostId2);
1113
+ }
1114
+ }
1115
+ const hostId = composerHostId(discovery);
1116
+ const route = hostId ? routeFor(hostId, discovery) : null;
1117
+ if (!composer) publish(route);
1118
+ return route;
1119
+ },
1120
+ dispose() {
1121
+ if (disposed) return;
1122
+ disposed = true;
1123
+ for (const hostId of routes.keys()) retire(hostId);
1124
+ lastDiscovery = null;
1125
+ if (target.__codexhostHostRoutingV1 === routing) {
1126
+ delete target.__codexhostHostRoutingV1;
1127
+ publish(null);
1128
+ }
1129
+ }
1130
+ };
1131
+ Object.defineProperty(target, "__codexhostHostRoutingV1", { configurable: true, value: routing });
1132
+ return routing;
1133
+ }
1134
+
1135
+ // packages/desktop-control/src/renderer-draft-prewarm-policy.ts
1136
+ var REQUEST_MANAGER_WAIT_TIMEOUT_MS = 6e4;
1137
+ var REQUEST_MANAGER_POLL_INTERVAL_MS = 25;
1138
+ function directRendererInstaller() {
1139
+ return `(() => {
1140
+ const committedReactAncestors = ${committedReactAncestors.toString()};
1141
+ const requestManagerFromHookState = ${requestManagerFromHookState.toString()};
1142
+ const discoverRendererHosts = ${discoverRendererHosts.toString()};
1143
+ const resolveRendererHostManager = ${resolveRendererHostManager.toString()};
1144
+ const retainRendererHostResponses = ${retainRendererHostResponses.toString()};
1145
+ const createDraftPrewarmPolicyBridge = ${createDraftPrewarmPolicyBridge.toString()};
1146
+ const routing = (${installRendererHostRouting.toString()})(
1147
+ document, window,
1148
+ (root) => discoverRendererHosts(root, committedReactAncestors, requestManagerFromHookState),
1149
+ (discovery, hostId) => resolveRendererHostManager(discovery, hostId, requestManagerFromHookState),
1150
+ (manager, bridge, hostId, target, prewarmed, isCurrent) => createDraftPrewarmPolicyBridge(
1151
+ manager, bridge, hostId, target, prewarmed, isCurrent, retainRendererHostResponses,
1152
+ ),
1153
+ );
1154
+ if (!routing.forComposer()) throw new Error('Renderer request manager is ambiguous');
1155
+ return { state: 'ready', reason: 'owned-request-bridge' };
1156
+ })()`;
1157
+ }
1158
+ async function waitForDraftPrewarmPolicy(evaluate, expression) {
1159
+ const deadline = Date.now() + REQUEST_MANAGER_WAIT_TIMEOUT_MS;
1160
+ while (true) {
1161
+ try {
1162
+ const value = await evaluate(expression);
1163
+ if (typeof value !== "object" || value === null || !("state" in value) || value.state !== "ready" || !("reason" in value) || value.reason !== "owned-request-bridge") {
1164
+ throw new Error("Renderer draft prewarm policy returned an invalid status");
1165
+ }
1166
+ return { state: "ready", reason: "owned-request-bridge" };
1167
+ } catch (error) {
1168
+ const message = error instanceof Error ? error.message : String(error);
1169
+ const remaining = deadline - Date.now();
1170
+ if (!message.includes("Renderer request manager is ambiguous") || remaining <= 0) throw error;
1171
+ await new Promise(
1172
+ (resolve) => setTimeout(resolve, Math.min(REQUEST_MANAGER_POLL_INTERVAL_MS, remaining))
1173
+ );
1174
+ }
1175
+ }
1176
+ }
1177
+ function installRendererDraftPrewarmPolicyDirect(renderer) {
1178
+ return waitForDraftPrewarmPolicy(
1179
+ (expression) => renderer.evaluate(expression),
1180
+ directRendererInstaller()
1181
+ );
1182
+ }
1183
+
1184
+ // packages/desktop-control/src/renderer-cdp-control-session.ts
1185
+ function isRecord2(value) {
1186
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1187
+ }
1188
+ function sleep(milliseconds) {
1189
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
1190
+ }
1191
+ function sameAgents(actual, expected) {
1192
+ const expectedSet = new Set(expected);
1193
+ return actual.length === expected.length && new Set(actual).size === actual.length && actual.every((agent) => expectedSet.has(agent));
1194
+ }
1195
+ function isPrimaryRendererUrl(value) {
1196
+ try {
1197
+ const url = new URL(value);
1198
+ return url.protocol === "app:" && url.hostname === "-" && url.pathname === "/index.html" && url.search === "" && url.hash === "";
1199
+ } catch {
1200
+ return false;
1201
+ }
1202
+ }
1203
+ function selectPrimaryRendererTarget(targets, preferredTargetId) {
1204
+ const candidates = targets.filter(
1205
+ (target) => target.type === "page" && isPrimaryRendererUrl(target.url)
1206
+ );
1207
+ return candidates.find((target) => target.id === preferredTargetId) ?? candidates.at(0) ?? null;
1208
+ }
1209
+ var RendererAdapterReadinessError = class extends Error {
1210
+ constructor(state, reason) {
1211
+ super(`Production Renderer Adapter is ${state}: ${reason}`);
1212
+ this.state = state;
1213
+ this.reason = reason;
1214
+ this.name = "RendererAdapterReadinessError";
1215
+ }
1216
+ state;
1217
+ reason;
1218
+ };
1219
+ function validateBindingStatus(value, expectedAgents) {
1220
+ if (!isRecord2(value) || value.version !== 2 || !Array.isArray(value.enabledAgents) || value.enabledAgents.some((agent) => typeof agent !== "string") || !sameAgents(value.enabledAgents, expectedAgents) || !isRecord2(value.adapter)) {
1221
+ throw new Error("Production Renderer binding returned an invalid status");
1222
+ }
1223
+ if (value.adapter.state !== "ready" || typeof value.adapter.reason !== "string") {
1224
+ const state = typeof value.adapter.state === "string" ? value.adapter.state : "invalid";
1225
+ const reason = typeof value.adapter.reason === "string" ? value.adapter.reason : "unknown";
1226
+ throw new RendererAdapterReadinessError(state, reason);
1227
+ }
1228
+ return value;
1229
+ }
1230
+ async function waitForPrimaryTarget(endpoint, operations, timeoutMs, pollIntervalMs, preferredTargetId) {
1231
+ const deadline = Date.now() + timeoutMs;
1232
+ let lastError;
1233
+ while (Date.now() < deadline) {
1234
+ try {
1235
+ const target = selectPrimaryRendererTarget(
1236
+ await operations.listTargets(endpoint),
1237
+ preferredTargetId
1238
+ );
1239
+ if (target) return target;
1240
+ lastError = new Error("Renderer CDP has no primary app://-/index.html page target");
1241
+ } catch (error) {
1242
+ lastError = error;
1243
+ }
1244
+ await sleep(pollIntervalMs);
1245
+ }
1246
+ const detail = lastError instanceof Error ? `: ${lastError.message}` : "";
1247
+ throw new Error(`Primary Codex Renderer CDP target did not become ready${detail}`);
1248
+ }
1249
+ async function evaluateSource(renderer, source) {
1250
+ const response = await renderer.command("Runtime.evaluate", {
1251
+ expression: source,
1252
+ awaitPromise: true
1253
+ });
1254
+ if (!isRecord2(response)) throw new Error("Renderer source evaluation returned an invalid result");
1255
+ if (isRecord2(response.exceptionDetails)) {
1256
+ const text = typeof response.exceptionDetails.text === "string" ? response.exceptionDetails.text : "Renderer source evaluation failed";
1257
+ throw new Error(text);
1258
+ }
1259
+ }
1260
+ async function readBinding(renderer) {
1261
+ return renderer.evaluate("window.__codexhostRendererBindingProbeV1?.status() ?? null");
1262
+ }
1263
+ async function waitForBinding(renderer, enabledAgents, timeoutMs, pollIntervalMs) {
1264
+ const deadline = Date.now() + timeoutMs;
1265
+ let lastError;
1266
+ while (Date.now() < deadline) {
1267
+ try {
1268
+ const value = await readBinding(renderer);
1269
+ if (value !== null) return validateBindingStatus(value, enabledAgents);
1270
+ } catch (error) {
1271
+ lastError = error;
1272
+ if (error instanceof RendererAdapterReadinessError && error.state !== "installing") {
1273
+ throw error;
1274
+ }
1275
+ }
1276
+ await sleep(pollIntervalMs);
1277
+ }
1278
+ const detail = lastError instanceof Error ? `: ${lastError.message}` : "";
1279
+ throw new Error(`Production Renderer binding did not become ready${detail}`);
1280
+ }
1281
+ async function installTarget(target, rendererSource, enabledAgents, timeoutMs, pollIntervalMs, operations) {
1282
+ const renderer = await operations.connect(target.webSocketDebuggerUrl);
1283
+ try {
1284
+ await renderer.command("Runtime.enable");
1285
+ await renderer.command("Page.enable");
1286
+ await renderer.command("Page.addScriptToEvaluateOnNewDocument", { source: rendererSource });
1287
+ await evaluateSource(renderer, rendererSource);
1288
+ const draftPrewarmPolicy = await operations.installDraftPrewarmPolicy(renderer);
1289
+ const binding = await waitForBinding(renderer, enabledAgents, timeoutMs, pollIntervalMs);
1290
+ return { renderer, snapshot: { target, draftPrewarmPolicy, binding } };
1291
+ } catch (error) {
1292
+ renderer.close();
1293
+ throw error;
1294
+ }
1295
+ }
1296
+ var InstalledRendererCdpControlSession = class {
1297
+ constructor(renderer, rendererCdpEndpoint2, rendererSource, enabledAgents, timeoutMs, pollIntervalMs, operations, currentSnapshot) {
1298
+ this.renderer = renderer;
1299
+ this.rendererCdpEndpoint = rendererCdpEndpoint2;
1300
+ this.rendererSource = rendererSource;
1301
+ this.enabledAgents = enabledAgents;
1302
+ this.timeoutMs = timeoutMs;
1303
+ this.pollIntervalMs = pollIntervalMs;
1304
+ this.operations = operations;
1305
+ this.currentSnapshot = currentSnapshot;
1306
+ }
1307
+ renderer;
1308
+ rendererCdpEndpoint;
1309
+ rendererSource;
1310
+ enabledAgents;
1311
+ timeoutMs;
1312
+ pollIntervalMs;
1313
+ operations;
1314
+ currentSnapshot;
1315
+ #closed = false;
1316
+ get snapshot() {
1317
+ return this.currentSnapshot;
1318
+ }
1319
+ async ensureInstalled() {
1320
+ if (this.#closed) throw new Error("Renderer CDP Control Session is closed");
1321
+ const target = await waitForPrimaryTarget(
1322
+ this.rendererCdpEndpoint,
1323
+ this.operations,
1324
+ this.timeoutMs,
1325
+ this.pollIntervalMs,
1326
+ this.currentSnapshot.target.id
1327
+ );
1328
+ if (target.id !== this.currentSnapshot.target.id) {
1329
+ const replacement = await installTarget(
1330
+ target,
1331
+ this.rendererSource,
1332
+ this.enabledAgents,
1333
+ this.timeoutMs,
1334
+ this.pollIntervalMs,
1335
+ this.operations
1336
+ );
1337
+ this.renderer.close();
1338
+ this.renderer = replacement.renderer;
1339
+ this.currentSnapshot = replacement.snapshot;
1340
+ return this.currentSnapshot;
1341
+ }
1342
+ try {
1343
+ const existing = await readBinding(this.renderer);
1344
+ if (existing === null) await evaluateSource(this.renderer, this.rendererSource);
1345
+ const draftPrewarmPolicy = await this.operations.installDraftPrewarmPolicy(this.renderer);
1346
+ const binding = await waitForBinding(
1347
+ this.renderer,
1348
+ this.enabledAgents,
1349
+ this.timeoutMs,
1350
+ this.pollIntervalMs
1351
+ );
1352
+ this.currentSnapshot = { target, draftPrewarmPolicy, binding };
1353
+ return this.currentSnapshot;
1354
+ } catch {
1355
+ const replacement = await installTarget(
1356
+ target,
1357
+ this.rendererSource,
1358
+ this.enabledAgents,
1359
+ this.timeoutMs,
1360
+ this.pollIntervalMs,
1361
+ this.operations
1362
+ );
1363
+ this.renderer.close();
1364
+ this.renderer = replacement.renderer;
1365
+ this.currentSnapshot = replacement.snapshot;
1366
+ return this.currentSnapshot;
1367
+ }
1368
+ }
1369
+ async activateDesktop() {
1370
+ if (this.#closed) throw new Error("Renderer CDP Control Session is closed");
1371
+ await this.renderer.command("Page.bringToFront");
1372
+ return 1;
1373
+ }
1374
+ executeRenderer(expression) {
1375
+ if (this.#closed) return Promise.reject(new Error("Renderer CDP Control Session is closed"));
1376
+ return this.renderer.evaluate(expression);
1377
+ }
1378
+ close() {
1379
+ if (this.#closed) return;
1380
+ this.#closed = true;
1381
+ this.renderer.close();
1382
+ }
1383
+ };
1384
+ var defaultOperations = {
1385
+ listTargets: (endpoint) => listCdpTargets(endpoint),
1386
+ connect: (webSocketDebuggerUrl) => CdpClient.connect(webSocketDebuggerUrl),
1387
+ installDraftPrewarmPolicy: installRendererDraftPrewarmPolicyDirect
1388
+ };
1389
+ async function createRendererCdpControlSession(options) {
1390
+ const enabledAgents = options.enabledAgents ?? ["codex", "pi"];
1391
+ const timeoutMs = options.timeoutMs ?? 3e4;
1392
+ const pollIntervalMs = options.pollIntervalMs ?? 250;
1393
+ const operations = options.operations ?? defaultOperations;
1394
+ const target = await waitForPrimaryTarget(
1395
+ options.rendererCdpEndpoint,
1396
+ operations,
1397
+ timeoutMs,
1398
+ pollIntervalMs
1399
+ );
1400
+ const installed = await installTarget(
1401
+ target,
1402
+ options.rendererSource,
1403
+ enabledAgents,
1404
+ timeoutMs,
1405
+ pollIntervalMs,
1406
+ operations
1407
+ );
1408
+ return new InstalledRendererCdpControlSession(
1409
+ installed.renderer,
1410
+ options.rendererCdpEndpoint,
1411
+ options.rendererSource,
1412
+ enabledAgents,
1413
+ timeoutMs,
1414
+ pollIntervalMs,
1415
+ operations,
1416
+ installed.snapshot
1417
+ );
1418
+ }
1419
+ function installRendererCdpControlSession(options) {
1420
+ return createRendererCdpControlSession(options);
1421
+ }
1422
+
1423
+ // packages/desktop-control/src/production-controller.ts
1424
+ var PRODUCTION_INSTALL_TIMEOUT_MS = 9e4;
1425
+ var RENDERER_CSP_BOOTSTRAP = "globalThis.__zod_globalConfig ??= {}; globalThis.__zod_globalConfig.jitless = true;";
1426
+ var DESKTOP_CONTROLLER_READINESS_MAX_BYTES = 512;
1427
+ var TRANSIENT_INSTALL_ATTEMPTS = 3;
1428
+ var TRANSIENT_INSTALL_RETRY_MS = 250;
1429
+ var RECOVERY_RETRY_INITIAL_MS = 3e4;
1430
+ var RECOVERY_RETRY_MAX_MS = 3e5;
1431
+ var startupTraceStartedAt = Date.now();
1432
+ function startupTrace(stage, detail) {
1433
+ if (process.env.CODEXHOST_STARTUP_TRACE !== "1") return;
1434
+ const suffix = detail === void 0 ? "" : `: ${detail instanceof Error ? detail.message : String(detail)}`;
1435
+ console.error(
1436
+ `[codexhost startup +${Date.now() - startupTraceStartedAt}ms] controller: ${stage}${suffix}`
1437
+ );
1438
+ }
1439
+ function serializeDesktopControllerReadiness(readiness) {
1440
+ if (readiness.schemaVersion !== 2 || readiness.state !== "compatible" || !Array.isArray(readiness.issues) || readiness.issues.length !== 0 || Object.keys(readiness).length !== 3) {
1441
+ throw new Error("Desktop Controller readiness is invalid");
1442
+ }
1443
+ const line = JSON.stringify(readiness);
1444
+ if (Buffer.byteLength(line, "utf8") > DESKTOP_CONTROLLER_READINESS_MAX_BYTES) {
1445
+ throw new Error("Desktop Controller readiness exceeds its size limit");
1446
+ }
1447
+ return line;
1448
+ }
1449
+ var defaultDependencies = {
1450
+ readRenderer: (filePath) => readFile(filePath, "utf8"),
1451
+ install: installRendererCdpControlSession,
1452
+ startAttachmentServer: startControllerAttachmentServer,
1453
+ ready: (readiness) => {
1454
+ process.stdout.write(`${serializeDesktopControllerReadiness(readiness)}
1455
+ `);
1456
+ },
1457
+ sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
1458
+ monitorIntervalMs: 500
1459
+ };
1460
+ function rendererCdpEndpoint(value) {
1461
+ const url = new URL(value);
1462
+ if (url.protocol !== "http:" || !["127.0.0.1", "localhost", "[::1]"].includes(url.hostname) || !url.port || url.username || url.password || url.pathname !== "/" && url.pathname !== "" || url.search || url.hash) {
1463
+ throw new Error("--renderer-cdp-endpoint must be a loopback HTTP origin with an explicit port");
1464
+ }
1465
+ return url.origin;
1466
+ }
1467
+ function parseDesktopControllerArguments(arguments_) {
1468
+ let endpoint;
1469
+ let rendererPath;
1470
+ let defaultAgent;
1471
+ let attachmentPort;
1472
+ let attachmentNonce;
1473
+ for (let index = 0; index < arguments_.length; index += 1) {
1474
+ const argument = arguments_[index];
1475
+ const value = arguments_[index + 1];
1476
+ if (argument === "--renderer-cdp-endpoint") {
1477
+ if (endpoint !== void 0) {
1478
+ throw new Error("--renderer-cdp-endpoint may only be provided once");
1479
+ }
1480
+ if (!value) throw new Error("--renderer-cdp-endpoint requires a value");
1481
+ endpoint = rendererCdpEndpoint(value);
1482
+ index += 1;
1483
+ continue;
1484
+ }
1485
+ if (argument === "--renderer") {
1486
+ if (rendererPath !== void 0) throw new Error("--renderer may only be provided once");
1487
+ 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);
1490
+ index += 1;
1491
+ continue;
1492
+ }
1493
+ if (argument === "--default-agent") {
1494
+ if (defaultAgent !== void 0) throw new Error("--default-agent may only be provided once");
1495
+ if (value !== "codex" && value !== "pi") {
1496
+ throw new Error("--default-agent must be 'codex' or 'pi'");
1497
+ }
1498
+ defaultAgent = value;
1499
+ index += 1;
1500
+ continue;
1501
+ }
1502
+ if (argument === "--attachment-port") {
1503
+ if (attachmentPort !== void 0) {
1504
+ throw new Error("--attachment-port may only be provided once");
1505
+ }
1506
+ const port = Number(value);
1507
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
1508
+ throw new Error("--attachment-port must be a valid TCP port");
1509
+ }
1510
+ attachmentPort = port;
1511
+ index += 1;
1512
+ continue;
1513
+ }
1514
+ if (argument === "--attachment-nonce") {
1515
+ if (attachmentNonce !== void 0) {
1516
+ throw new Error("--attachment-nonce may only be provided once");
1517
+ }
1518
+ if (value === void 0 || !/^[0-9a-f]{32}$/.test(value)) {
1519
+ throw new Error("--attachment-nonce must be 32 lowercase hexadecimal characters");
1520
+ }
1521
+ attachmentNonce = value;
1522
+ index += 1;
1523
+ continue;
1524
+ }
1525
+ throw new Error(`unknown Desktop Controller option: ${argument}`);
1526
+ }
1527
+ if (endpoint === void 0) throw new Error("--renderer-cdp-endpoint is required");
1528
+ if (rendererPath === void 0) throw new Error("--renderer is required");
1529
+ if (defaultAgent === void 0) throw new Error("--default-agent is required");
1530
+ if (attachmentPort === void 0) throw new Error("--attachment-port is required");
1531
+ if (attachmentNonce === void 0) throw new Error("--attachment-nonce is required");
1532
+ return {
1533
+ rendererCdpEndpoint: endpoint,
1534
+ rendererPath,
1535
+ defaultAgent,
1536
+ attachmentPort,
1537
+ attachmentNonce
1538
+ };
1539
+ }
1540
+ function isTransientRendererInstallError(error) {
1541
+ let current = error;
1542
+ for (let depth = 0; depth < 4; depth += 1) {
1543
+ const message = current instanceof Error ? current.message : String(current);
1544
+ if (message.includes("Execution context was destroyed") || message.includes("Promise was collected")) {
1545
+ return true;
1546
+ }
1547
+ current = current instanceof Error ? current.cause : void 0;
1548
+ if (current === void 0) break;
1549
+ }
1550
+ return false;
1551
+ }
1552
+ async function installProductionSession(options, dependencies) {
1553
+ for (let attempt = 1; attempt <= TRANSIENT_INSTALL_ATTEMPTS; attempt += 1) {
1554
+ try {
1555
+ return await dependencies.install(options);
1556
+ } catch (error) {
1557
+ if (attempt === TRANSIENT_INSTALL_ATTEMPTS || !isTransientRendererInstallError(error)) {
1558
+ throw error;
1559
+ }
1560
+ await dependencies.sleep(TRANSIENT_INSTALL_RETRY_MS);
1561
+ }
1562
+ }
1563
+ throw new Error("Desktop Controller exhausted Renderer installation attempts");
1564
+ }
1565
+ async function runDesktopController(options, signal, dependencies = defaultDependencies) {
1566
+ const configuration = `Object.defineProperty(window, "__codexhostProductionConfigV1", { configurable: true, value: { defaultAgent: ${JSON.stringify(options.defaultAgent)} } });`;
1567
+ const now = dependencies.now ?? Date.now;
1568
+ let session;
1569
+ let nextRecoveryAt = 0;
1570
+ let recoveryDelayMs = RECOVERY_RETRY_INITIAL_MS;
1571
+ const recordRecoveryFailure = () => {
1572
+ nextRecoveryAt = now() + recoveryDelayMs;
1573
+ recoveryDelayMs = Math.min(recoveryDelayMs * 2, RECOVERY_RETRY_MAX_MS);
1574
+ };
1575
+ const recordRecoverySuccess = () => {
1576
+ nextRecoveryAt = 0;
1577
+ recoveryDelayMs = RECOVERY_RETRY_INITIAL_MS;
1578
+ };
1579
+ const createSession = async () => {
1580
+ startupTrace("reading Renderer bundle");
1581
+ const rendererSource = await dependencies.readRenderer(options.rendererPath);
1582
+ if (rendererSource.trim().length === 0) throw new Error("production Renderer Bundle is empty");
1583
+ startupTrace("installing Renderer Session");
1584
+ const installed = await installProductionSession(
1585
+ {
1586
+ rendererCdpEndpoint: options.rendererCdpEndpoint,
1587
+ rendererSource: `${RENDERER_CSP_BOOTSTRAP}
1588
+ ${configuration}
1589
+ ${rendererSource}`,
1590
+ enabledAgents: [
1591
+ "codex",
1592
+ "pi",
1593
+ "claude-code",
1594
+ "deepseek-harness",
1595
+ "opencode",
1596
+ "grok",
1597
+ "omp",
1598
+ "antigravity",
1599
+ "kiro-cli",
1600
+ "codebuddy",
1601
+ "workbuddy",
1602
+ "cursor-cli",
1603
+ "hermes",
1604
+ "qoder",
1605
+ "qoder-cn",
1606
+ "kimi-code"
1607
+ ],
1608
+ timeoutMs: PRODUCTION_INSTALL_TIMEOUT_MS
1609
+ },
1610
+ dependencies
1611
+ );
1612
+ startupTrace("Renderer Session installed");
1613
+ return installed;
1614
+ };
1615
+ 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
+ let operation = Promise.resolve(void 0);
1625
+ const useSession = (callback) => {
1626
+ const next = operation.then(callback, callback);
1627
+ operation = next.then(
1628
+ () => void 0,
1629
+ () => void 0
1630
+ );
1631
+ return next;
1632
+ };
1633
+ const resetSession = () => {
1634
+ session?.close();
1635
+ session = void 0;
1636
+ };
1637
+ const ensureSession = async () => {
1638
+ if (!session) session = await createSession();
1639
+ else await session.ensureInstalled();
1640
+ return session;
1641
+ };
1642
+ const recoverSession = async () => {
1643
+ try {
1644
+ const current = await ensureSession();
1645
+ recordRecoverySuccess();
1646
+ return current;
1647
+ } catch (error) {
1648
+ resetSession();
1649
+ recordRecoveryFailure();
1650
+ throw error;
1651
+ }
1652
+ };
1653
+ let attachmentServer;
1654
+ try {
1655
+ startupTrace("starting attachment server");
1656
+ attachmentServer = await dependencies.startAttachmentServer({
1657
+ port: options.attachmentPort,
1658
+ nonce: options.attachmentNonce,
1659
+ attach: () => useSession(async () => {
1660
+ const current = await recoverSession();
1661
+ await current.activateDesktop();
1662
+ })
1663
+ });
1664
+ startupTrace("attachment server ready");
1665
+ startupTrace("publishing readiness");
1666
+ dependencies.ready({
1667
+ schemaVersion: 2,
1668
+ state: "compatible",
1669
+ issues: []
1670
+ });
1671
+ while (!signal.aborted) {
1672
+ await dependencies.sleep(dependencies.monitorIntervalMs);
1673
+ if (signal.aborted) continue;
1674
+ await useSession(async () => {
1675
+ if (!session && now() < nextRecoveryAt) return;
1676
+ try {
1677
+ await recoverSession();
1678
+ } catch {
1679
+ }
1680
+ });
1681
+ }
1682
+ } finally {
1683
+ await attachmentServer?.close();
1684
+ await operation;
1685
+ resetSession();
1686
+ }
1687
+ }
1688
+
1689
+ // packages/desktop-control/src/release-main.ts
1690
+ var abort = new AbortController();
1691
+ var stop = () => abort.abort();
1692
+ process.once("SIGINT", stop);
1693
+ process.once("SIGTERM", stop);
1694
+ try {
1695
+ await runDesktopController(parseDesktopControllerArguments(process.argv.slice(2)), abort.signal);
1696
+ } catch (error) {
1697
+ console.error(
1698
+ `codexhost Desktop Controller: ${error instanceof Error ? error.message : String(error)}`
1699
+ );
1700
+ process.exitCode = 1;
1701
+ } finally {
1702
+ process.removeListener("SIGINT", stop);
1703
+ process.removeListener("SIGTERM", stop);
1704
+ }