@karlorz/codexhost-cli-win32-arm64 0.13.3-6 → 0.13.3-7

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.
@@ -6,104 +6,6 @@
6
6
  __defProp(target, name, { get: all[name], enumerable: true });
7
7
  };
8
8
 
9
- // ../desktop-control/dist/renderer-react-ownership.js
10
- var REACT_FIBER_WALK_LIMIT_EVENT = "codexhost:react-fiber-walk-limit";
11
- function committedReactAncestors(value) {
12
- const MAX_VISITED_FIBERS = 2e5;
13
- const fiber = (value2) => typeof value2 === "object" && value2 !== null ? value2 : null;
14
- const exhausted = () => {
15
- const target = globalThis;
16
- if (typeof target.dispatchEvent === "function" && typeof CustomEvent === "function")
17
- target.dispatchEvent(new CustomEvent("codexhost:react-fiber-walk-limit", {
18
- detail: { limit: MAX_VISITED_FIBERS }
19
- }));
20
- return [];
21
- };
22
- const first = fiber(value);
23
- if (!first)
24
- return [];
25
- const previous = [];
26
- const seen = /* @__PURE__ */ new Set();
27
- for (let node = first; node; node = fiber(node.return)) {
28
- if (seen.has(node))
29
- return [];
30
- if (seen.size >= MAX_VISITED_FIBERS)
31
- return exhausted();
32
- seen.add(node);
33
- previous.push(node);
34
- }
35
- const rootState = fiber(previous.at(-1)?.stateNode);
36
- if (!rootState || !("current" in rootState))
37
- return previous;
38
- const current = fiber(rootState.current);
39
- if (!current)
40
- return [];
41
- const alternate = fiber(first.alternate);
42
- const path = [current];
43
- seen.clear();
44
- seen.add(current);
45
- for (let index = previous.length - 2; index >= 0; index -= 1) {
46
- const expected = previous[index];
47
- const expectedAlternate = fiber(expected?.alternate);
48
- let child = fiber(path.at(-1)?.child);
49
- let matched = false;
50
- while (child && !seen.has(child) && seen.size < MAX_VISITED_FIBERS) {
51
- seen.add(child);
52
- if (child === expected || child === expectedAlternate) {
53
- matched = true;
54
- break;
55
- }
56
- child = fiber(child.sibling);
57
- }
58
- if (!matched || !child)
59
- break;
60
- path.push(child);
61
- }
62
- if (path.length === previous.length && (path.at(-1) === first || path.at(-1) === alternate)) {
63
- return path.reverse();
64
- }
65
- const stack = [{ node: current, parent: null }];
66
- seen.clear();
67
- while (stack.length > 0 && seen.size < MAX_VISITED_FIBERS) {
68
- const entry = stack.pop();
69
- if (!entry)
70
- break;
71
- if (seen.has(entry.node))
72
- return [];
73
- seen.add(entry.node);
74
- if (entry.node === first || entry.node === alternate) {
75
- const ancestors = [];
76
- for (let cursor = entry; cursor; cursor = cursor.parent)
77
- ancestors.push(cursor.node);
78
- return ancestors;
79
- }
80
- const sibling = entry.parent && fiber(entry.node.sibling);
81
- if (sibling)
82
- stack.push({ node: sibling, parent: entry.parent });
83
- const child = fiber(entry.node.child);
84
- if (child)
85
- stack.push({ node: child, parent: entry });
86
- }
87
- return stack.length > 0 ? exhausted() : [];
88
- }
89
-
90
- // src/renderer-chat-voice-debug.ts
91
- var CHAT_VOICE_DEBUG_GLOBAL = "__codexhostChatVoiceDebug";
92
- var CHAT_VOICE_DEBUG_STORAGE_KEY = "codexhost.chat-voice-debug.v1";
93
- function chatVoiceDebug(event, detail = {}) {
94
- const entry = { t: Date.now(), event, ...detail };
95
- const owner = globalThis;
96
- const log = Array.isArray(owner[CHAT_VOICE_DEBUG_GLOBAL]) ? owner[CHAT_VOICE_DEBUG_GLOBAL] : [];
97
- log.push(entry);
98
- while (log.length > 40) log.shift();
99
- owner[CHAT_VOICE_DEBUG_GLOBAL] = log;
100
- try {
101
- owner.localStorage?.setItem(CHAT_VOICE_DEBUG_STORAGE_KEY, JSON.stringify(log));
102
- } catch {
103
- }
104
- owner.console?.info?.("[codexhost-chat-voice]", event, detail);
105
- }
106
-
107
9
  // ../../node_modules/zod/v4/classic/external.js
108
10
  var external_exports = {};
109
11
  __export(external_exports, {
@@ -14990,6 +14892,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
14990
14892
  "codexhost/harness/installation",
14991
14893
  "codexhost/harness/launch-settings/get",
14992
14894
  "codexhost/harness/launch-settings/set",
14895
+ "codexhost/harness/grok-computer-use/get",
14896
+ "codexhost/harness/grok-computer-use/set",
14993
14897
  "codexhost/harness/web-ui/open",
14994
14898
  "codexhost/harness/accounts/sources",
14995
14899
  "codexhost/harness/accounts/list",
@@ -15530,7 +15434,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15530
15434
  var harnessInspectParamsSchema = external_exports.object({
15531
15435
  harnessId: harnessIdSchema,
15532
15436
  cwd: nonBlankTextSchema2.max(16384).optional(),
15533
- refresh: external_exports.boolean().optional()
15437
+ refresh: external_exports.boolean().optional(),
15438
+ /** `availability` asks only whether the Harness can run; see `InspectHarnessInput.purpose`. */
15439
+ purpose: external_exports.literal("availability").optional()
15534
15440
  }).strict();
15535
15441
  var harnessWebUiOpenParamsSchema = external_exports.object({
15536
15442
  harnessId: harnessIdSchema
@@ -15730,6 +15636,28 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15730
15636
  restartRequired: external_exports.boolean()
15731
15637
  }).strict();
15732
15638
 
15639
+ // ../shared-contracts/dist/grok-computer-use.js
15640
+ var GROK_COMPUTER_USE_GET_METHOD = "codexhost/harness/grok-computer-use/get";
15641
+ var GROK_COMPUTER_USE_SET_METHOD = "codexhost/harness/grok-computer-use/set";
15642
+ var grokComputerUseGetSchema = external_exports.object({}).strict();
15643
+ var grokComputerUseSetSchema = external_exports.object({
15644
+ enabled: external_exports.boolean().optional(),
15645
+ browser: external_exports.boolean().optional()
15646
+ }).strict().refine((value) => value.enabled !== void 0 || value.browser !== void 0, {
15647
+ message: "Expected enabled or browser"
15648
+ });
15649
+ var grokComputerUseSettingsSchema = external_exports.object({
15650
+ enabled: external_exports.boolean(),
15651
+ available: external_exports.boolean(),
15652
+ unavailableReason: external_exports.string().min(1).max(500).optional(),
15653
+ browser: external_exports.boolean().optional(),
15654
+ browserFilesPresent: external_exports.boolean().optional(),
15655
+ browserAvailable: external_exports.boolean().optional(),
15656
+ browserUnavailableReason: external_exports.string().min(1).max(500).optional(),
15657
+ applyFailed: external_exports.boolean().optional(),
15658
+ applyError: external_exports.string().min(1).max(500).optional()
15659
+ }).strict();
15660
+
15733
15661
  // ../shared-contracts/dist/harness-installation.js
15734
15662
  var HARNESS_INSTALLATION_METHOD = "codexhost/harness/installation";
15735
15663
  var harnessInstallationParamsSchema = external_exports.object({
@@ -16157,6 +16085,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16157
16085
 
16158
16086
  // ../shared-contracts/dist/voice-settings.js
16159
16087
  var VOICE_SETTINGS_METHOD = "codexhost/voice/settings";
16088
+ var VOICE_GATEWAY_STATUS_METHOD = "codexhost/voice/gateway-status";
16160
16089
  var voiceRoutesSchema = external_exports.strictObject({
16161
16090
  chatExperience: external_exports.enum(["chatgpt", "codex"]),
16162
16091
  chatConnection: external_exports.enum(["desktop", "gateway"]),
@@ -16206,7 +16135,17 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16206
16135
  var workspaceContractVersionSchema = external_exports.literal(WORKSPACE_CONTRACT_VERSION);
16207
16136
 
16208
16137
  // src/renderer-cpa-live-voice-preference.ts
16209
- var sharedRoutes = /* @__PURE__ */ new WeakMap();
16138
+ var voiceStateOwner = globalThis;
16139
+ var { sharedRoutes, gatewayReady } = voiceStateOwner.__codexhostVoicePreferenceStateV1 ??= {
16140
+ sharedRoutes: /* @__PURE__ */ new WeakMap(),
16141
+ gatewayReady: /* @__PURE__ */ new WeakMap()
16142
+ };
16143
+ function applyVoiceGatewayReadiness(owner, ready) {
16144
+ gatewayReady.set(owner, ready);
16145
+ }
16146
+ function voiceGatewayIsReady(owner) {
16147
+ return gatewayReady.get(owner) === true;
16148
+ }
16210
16149
  var SHARED_VOICE_CACHE = "codexhost.voice-routes-cache.v1";
16211
16150
  function cachedRoutes(owner) {
16212
16151
  const memory = sharedRoutes.get(owner);
@@ -16242,9 +16181,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16242
16181
  return routes ? routes.chatExperience === "chatgpt" : !readFlag(owner, CHAT_LIVE_VOICE_STORAGE_KEY, false);
16243
16182
  }
16244
16183
  function legacyVoiceRoutes(owner) {
16184
+ const chatLegacy = readFlag(owner, CHAT_LIVE_VOICE_STORAGE_KEY, false);
16245
16185
  return {
16246
- chatExperience: readFlag(owner, CHAT_LIVE_VOICE_STORAGE_KEY, false) ? "codex" : "chatgpt",
16247
- chatConnection: readFlag(owner, CHAT_LIVE_VOICE_STORAGE_KEY, false) ? "gateway" : "desktop",
16186
+ chatExperience: chatLegacy ? "codex" : "chatgpt",
16187
+ chatConnection: chatLegacy ? "gateway" : "desktop",
16248
16188
  workConnection: readFlag(owner, CPA_LIVE_VOICE_STORAGE_KEY, true) ? "gateway" : "desktop"
16249
16189
  };
16250
16190
  }
@@ -16406,265 +16346,885 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16406
16346
  return handle;
16407
16347
  }
16408
16348
 
16409
- // src/agent-selection-state.ts
16410
- var DEFAULT_RENDERER_AGENTS = ["codex"];
16411
- function defaultIdFactory(sequence) {
16412
- return `codexhost-composer-${Date.now().toString(36)}-${sequence.toString(36)}`;
16413
- }
16414
- function isDefaultTarget(target) {
16415
- return target?.[0] === "default";
16349
+ // src/renderer-voice-assets.ts
16350
+ function supportsVoiceVersion(profiles, version2) {
16351
+ return version2 === void 0 || profiles.some((profile) => profile.version === version2);
16416
16352
  }
16417
- function isConversationTarget(target) {
16418
- return target?.[0] === "conversation";
16419
- }
16420
- function isIdentifiedTarget(target) {
16421
- return target?.[0] === "conversation" || target?.[0] === "default" && typeof target[1] === "string" && target[1].startsWith("client-new-thread:");
16422
- }
16423
- function sameTarget(left, right) {
16424
- return left.length === right.length && left.every((value, index) => value === right[index]);
16353
+ function selectVoiceAssets(profiles, names, version2) {
16354
+ const present = profiles.filter(
16355
+ (profile2) => [profile2.initial, profile2.shared].some((asset) => auditedAppInitialAssetPresent(names, asset))
16356
+ );
16357
+ const profile = present.length === 1 ? present[0] : void 0;
16358
+ return profile && (version2 === void 0 || profile.version === version2) && [profile.initial, profile.shared].every((asset) => auditedAppInitialAssetPresent(names, asset)) ? profile : void 0;
16425
16359
  }
16426
- var DraftAgentController = class {
16427
- #idFactory;
16428
- #defaultAgent;
16429
- #enabledAgents;
16430
- #targetStates = [];
16431
- #modelRequestGenerations = /* @__PURE__ */ new WeakMap();
16432
- #ownershipRequestGenerations = /* @__PURE__ */ new WeakMap();
16433
- #states = /* @__PURE__ */ new WeakMap();
16434
- #switching = /* @__PURE__ */ new Set();
16435
- #pendingSubmissions = /* @__PURE__ */ new Set();
16436
- #composerSequence = 0;
16437
- #modelRequestSequence = 0;
16438
- #ownershipRequestSequence = 0;
16439
- #lastSubmittedAgent;
16440
- constructor(options = {}) {
16441
- this.#idFactory = options.idFactory ?? defaultIdFactory;
16442
- this.#enabledAgents = new Set(options.enabledAgents ?? DEFAULT_RENDERER_AGENTS);
16443
- if (!this.#enabledAgents.has("codex"))
16444
- throw new Error("Renderer enabled Agents must include Codex");
16445
- this.#defaultAgent = options.defaultAgent ?? "codex";
16446
- this.#lastSubmittedAgent = this.#defaultAgent;
16447
- }
16448
- setEnabledAgents(agents) {
16449
- this.#enabledAgents = /* @__PURE__ */ new Set(["codex", ...agents]);
16450
- }
16451
- get(composer) {
16452
- return this.#state(composer);
16453
- }
16454
- detach(composer, preservePendingSubmission = false) {
16455
- const state = this.#states.get(composer);
16456
- if (state && !preservePendingSubmission) this.#pendingSubmissions.delete(state);
16457
- this.#states.delete(composer);
16360
+
16361
+ // ../desktop-control/dist/renderer-react-ownership.js
16362
+ var REACT_FIBER_WALK_LIMIT_EVENT = "codexhost:react-fiber-walk-limit";
16363
+ function committedReactAncestors(value) {
16364
+ const MAX_VISITED_FIBERS = 2e5;
16365
+ const fiber = (value2) => typeof value2 === "object" && value2 !== null ? value2 : null;
16366
+ const exhausted = () => {
16367
+ const target = globalThis;
16368
+ if (typeof target.dispatchEvent === "function" && typeof CustomEvent === "function")
16369
+ target.dispatchEvent(new CustomEvent("codexhost:react-fiber-walk-limit", {
16370
+ detail: { limit: MAX_VISITED_FIBERS }
16371
+ }));
16372
+ return [];
16373
+ };
16374
+ const first = fiber(value);
16375
+ if (!first)
16376
+ return [];
16377
+ const previous = [];
16378
+ const seen = /* @__PURE__ */ new Set();
16379
+ for (let node = first; node; node = fiber(node.return)) {
16380
+ if (seen.has(node))
16381
+ return [];
16382
+ if (seen.size >= MAX_VISITED_FIBERS)
16383
+ return exhausted();
16384
+ seen.add(node);
16385
+ previous.push(node);
16458
16386
  }
16459
- mount(composer, target, preferredNewThreadAgent) {
16460
- const bound = this.#targetState(target);
16461
- if (bound) {
16462
- this.#states.set(composer, bound);
16463
- return bound;
16387
+ const rootState = fiber(previous.at(-1)?.stateNode);
16388
+ if (!rootState || !("current" in rootState))
16389
+ return previous;
16390
+ const current = fiber(rootState.current);
16391
+ if (!current)
16392
+ return [];
16393
+ const alternate = fiber(first.alternate);
16394
+ const path = [current];
16395
+ seen.clear();
16396
+ seen.add(current);
16397
+ for (let index = previous.length - 2; index >= 0; index -= 1) {
16398
+ const expected = previous[index];
16399
+ const expectedAlternate = fiber(expected?.alternate);
16400
+ let child = fiber(path.at(-1)?.child);
16401
+ let matched = false;
16402
+ while (child && !seen.has(child) && seen.size < MAX_VISITED_FIBERS) {
16403
+ seen.add(child);
16404
+ if (child === expected || child === expectedAlternate) {
16405
+ matched = true;
16406
+ break;
16407
+ }
16408
+ child = fiber(child.sibling);
16464
16409
  }
16465
- const preferredAgent = preferredNewThreadAgent ?? this.#lastSubmittedAgent;
16466
- const state = this.#state(composer, isDefaultTarget(target) ? preferredAgent : "codex");
16467
- if (isIdentifiedTarget(target)) this.#targetStates.push({ target, state });
16468
- return state;
16469
- }
16470
- isSwitching(composer) {
16471
- return this.#switching.has(this.#state(composer));
16472
- }
16473
- beginModelRequest(composer) {
16474
- const state = this.#state(composer);
16475
- const generation = ++this.#modelRequestSequence;
16476
- this.#modelRequestGenerations.set(state, generation);
16477
- return generation;
16478
- }
16479
- invalidateModelRequests(composer) {
16480
- this.beginModelRequest(composer);
16481
- }
16482
- isCurrentModelRequest(composer, generation) {
16483
- return (this.#modelRequestGenerations.get(this.#state(composer)) ?? 0) === generation;
16484
- }
16485
- beginOwnershipRequest(composer) {
16486
- const generation = ++this.#ownershipRequestSequence;
16487
- this.#ownershipRequestGenerations.set(this.#state(composer), generation);
16488
- return generation;
16489
- }
16490
- isCurrentOwnershipRequest(composer, generation) {
16491
- return (this.#ownershipRequestGenerations.get(this.#state(composer)) ?? 0) === generation;
16410
+ if (!matched || !child)
16411
+ break;
16412
+ path.push(child);
16492
16413
  }
16493
- rebindConversation(composer, target) {
16494
- if (!isConversationTarget(target)) return null;
16495
- const previous = this.#state(composer);
16496
- this.#pendingSubmissions.delete(previous);
16497
- this.#modelRequestGenerations.set(previous, ++this.#modelRequestSequence);
16498
- this.#ownershipRequestGenerations.set(previous, ++this.#ownershipRequestSequence);
16499
- let state = this.#targetState(target);
16500
- if (!state) {
16501
- state = {
16502
- agent: "codex",
16503
- phase: "draft",
16504
- composerId: this.#idFactory(++this.#composerSequence)
16505
- };
16506
- this.#targetStates.push({ target, state });
16507
- }
16508
- this.#states.set(composer, state);
16509
- this.#modelRequestGenerations.set(state, ++this.#modelRequestSequence);
16510
- this.#ownershipRequestGenerations.set(state, ++this.#ownershipRequestSequence);
16511
- return state;
16414
+ if (path.length === previous.length && (path.at(-1) === first || path.at(-1) === alternate)) {
16415
+ return path.reverse();
16512
16416
  }
16513
- restore(composer, agent, model2, thinkingOptionId, permissionModeId) {
16514
- const state = this.#state(composer);
16515
- this.#pendingSubmissions.delete(state);
16516
- state.agent = agent;
16517
- state.phase = "locked";
16518
- if (agent !== "codex") {
16519
- state.configurationByAgent = {
16520
- ...state.configurationByAgent,
16521
- [agent]: {
16522
- ...model2 ? { model: model2 } : {},
16523
- ...thinkingOptionId ? { thinkingOptionId } : {},
16524
- ...permissionModeId ? { permissionModeId } : {}
16525
- }
16526
- };
16417
+ const stack = [{ node: current, parent: null }];
16418
+ seen.clear();
16419
+ while (stack.length > 0 && seen.size < MAX_VISITED_FIBERS) {
16420
+ const entry = stack.pop();
16421
+ if (!entry)
16422
+ break;
16423
+ if (seen.has(entry.node))
16424
+ return [];
16425
+ seen.add(entry.node);
16426
+ if (entry.node === first || entry.node === alternate) {
16427
+ const ancestors = [];
16428
+ for (let cursor = entry; cursor; cursor = cursor.parent)
16429
+ ancestors.push(cursor.node);
16430
+ return ancestors;
16527
16431
  }
16528
- return state;
16529
- }
16530
- clearConfiguration(composer) {
16531
- delete this.#state(composer).configurationByAgent;
16532
- }
16533
- modelForAgent(composer, agent) {
16534
- return this.#state(composer).configurationByAgent?.[agent]?.model;
16535
- }
16536
- thinkingOptionForAgent(composer, agent) {
16537
- return this.#state(composer).configurationByAgent?.[agent]?.thinkingOptionId;
16538
- }
16539
- permissionModeForAgent(composer, agent) {
16540
- return this.#state(composer).configurationByAgent?.[agent]?.permissionModeId;
16541
- }
16542
- setExternalPermissionMode(composer, agent, permissionModeId) {
16543
- return this.#configure(composer, agent, { permissionModeId });
16544
- }
16545
- setExternalModel(composer, agent, model2) {
16546
- return this.#configure(composer, agent, { model: model2 });
16432
+ const sibling = entry.parent && fiber(entry.node.sibling);
16433
+ if (sibling)
16434
+ stack.push({ node: sibling, parent: entry.parent });
16435
+ const child = fiber(entry.node.child);
16436
+ if (child)
16437
+ stack.push({ node: child, parent: entry });
16547
16438
  }
16548
- setExternalThinkingOption(composer, agent, thinkingOptionId) {
16549
- const state = this.#configure(composer, agent, {});
16550
- const configuration = state.configurationByAgent?.[agent];
16551
- if (!configuration) return state;
16552
- if (thinkingOptionId) configuration.thinkingOptionId = thinkingOptionId;
16553
- else delete configuration.thinkingOptionId;
16554
- return state;
16439
+ return stack.length > 0 ? exhausted() : [];
16440
+ }
16441
+
16442
+ // src/renderer-chat-voice-debug.ts
16443
+ var CHAT_VOICE_DEBUG_GLOBAL = "__codexhostChatVoiceDebug";
16444
+ var CHAT_VOICE_DEBUG_STORAGE_KEY = "codexhost.chat-voice-debug.v1";
16445
+ function chatVoiceDebug(event, detail = {}) {
16446
+ const entry = { t: Date.now(), event, ...detail };
16447
+ const owner = globalThis;
16448
+ const log = Array.isArray(owner[CHAT_VOICE_DEBUG_GLOBAL]) ? owner[CHAT_VOICE_DEBUG_GLOBAL] : [];
16449
+ log.push(entry);
16450
+ while (log.length > 40) log.shift();
16451
+ owner[CHAT_VOICE_DEBUG_GLOBAL] = log;
16452
+ try {
16453
+ owner.localStorage?.setItem(CHAT_VOICE_DEBUG_STORAGE_KEY, JSON.stringify(log));
16454
+ } catch {
16555
16455
  }
16556
- #configure(composer, agent, patch) {
16557
- const state = this.#state(composer);
16558
- state.configurationByAgent = {
16559
- ...state.configurationByAgent,
16560
- [agent]: { ...state.configurationByAgent?.[agent], ...patch }
16456
+ owner.console?.info?.("[codexhost-chat-voice]", event, detail);
16457
+ }
16458
+
16459
+ // src/renderer-native-chat-voice-state.ts
16460
+ function observeNativeChatVoiceState(scope, initial) {
16461
+ const store = Reflect.apply(initial.SPn, void 0, [
16462
+ scope
16463
+ ]);
16464
+ let last = "";
16465
+ const report = () => {
16466
+ const state = store.getState();
16467
+ const connection = state.server?.connectionState;
16468
+ const detail = {
16469
+ active: state.isVoiceModeActive === true,
16470
+ connectedOnce: state.hasConnectedOnce === true,
16471
+ sessionStarted: state.hasVoiceSessionStarted === true,
16472
+ serverConversationReady: typeof state.earlyServerThreadId === "string" && /^[a-f\d]{8}(?:-[a-f\d]{4}){3}-[a-f\d]{12}$/i.test(state.earlyServerThreadId),
16473
+ connection: typeof connection === "string" && ["disconnected", "connecting", "connected", "reconnecting", "signalReconnecting"].includes(
16474
+ connection
16475
+ ) ? connection : "unknown"
16561
16476
  };
16562
- return state;
16563
- }
16564
- lock(composer) {
16565
- const state = this.#state(composer);
16566
- this.#pendingSubmissions.delete(state);
16567
- state.phase = "locked";
16568
- return state;
16569
- }
16570
- markSubmissionPending(composer) {
16571
- const state = this.#state(composer);
16572
- if (state.phase === "draft") this.#pendingSubmissions.add(state);
16573
- return state;
16574
- }
16575
- isSubmissionPending(composer) {
16576
- return this.#pendingSubmissions.has(this.#state(composer));
16577
- }
16578
- clearPendingSubmission(composer) {
16579
- this.#pendingSubmissions.delete(this.#state(composer));
16580
- }
16581
- recordSubmission(composer) {
16582
- const state = this.#state(composer);
16583
- this.#lastSubmittedAgent = state.agent;
16584
- return state;
16585
- }
16586
- transfer(source, replacement, target = null) {
16587
- const state = this.#states.get(source);
16588
- if (!state) return false;
16589
- const bound = this.#targetState(target);
16590
- if (bound && bound !== state) return false;
16591
- if (source !== replacement) {
16592
- if (this.#states.has(replacement)) return false;
16593
- this.#states.set(replacement, state);
16594
- }
16595
- if (isIdentifiedTarget(target) && !bound) this.#targetStates.push({ target, state });
16596
- if (isConversationTarget(target) && this.#pendingSubmissions.delete(state))
16597
- state.phase = "locked";
16598
- return true;
16599
- }
16600
- async switchAgent(composer, nextAgent, operations) {
16601
- const state = this.#state(composer);
16602
- if (!this.#enabledAgents.has(nextAgent)) return false;
16603
- if (state.phase !== "draft" || this.#switching.has(state)) return false;
16604
- if (state.agent === nextAgent) return true;
16605
- this.#pendingSubmissions.delete(state);
16606
- this.#switching.add(state);
16477
+ const next = JSON.stringify(detail);
16478
+ if (next === last) return;
16479
+ last = next;
16480
+ chatVoiceDebug("native-chat-state", detail);
16481
+ };
16482
+ const unsubscribe = store.subscribe(report);
16483
+ report();
16484
+ return unsubscribe;
16485
+ }
16486
+
16487
+ // src/renderer-native-chat-voice-selection.ts
16488
+ var UUID = /^[a-f\d]{8}(?:-[a-f\d]{4}){3}-[a-f\d]{12}$/i;
16489
+ function nativeChatVoiceDraftSelection(binding, initial, shared, origin, signal) {
16490
+ const draft = binding.props.conversationId;
16491
+ const navigate = initial.Nir;
16492
+ const conversationPath = shared.Vot;
16493
+ if (binding.backendConversationId !== void 0 || !draft.startsWith("local-chatgpt:") || !UUID.test(draft.slice("local-chatgpt:".length)) || origin == null || initial.Ycr == null || initial.n0n == null || initial.Y1n == null || typeof navigate !== "function" || typeof conversationPath !== "function")
16494
+ return () => {
16495
+ };
16496
+ const store = Reflect.apply(initial.SPn, void 0, [
16497
+ binding.scope
16498
+ ]);
16499
+ const stateAtStart = store.getState();
16500
+ const readIntentVersion = typeof initial.HJt === "function" ? () => Reflect.apply(initial.HJt, void 0, [binding.scope]) : void 0;
16501
+ const version2 = readIntentVersion?.();
16502
+ const intentVersion = typeof version2 === "number" && Number.isSafeInteger(version2) && version2 > 0 ? version2 : void 0;
16503
+ let session = stateAtStart.voiceSessionId;
16504
+ let activeSeen = stateAtStart.isVoiceModeActive;
16505
+ let sessionStartedSeen = stateAtStart.hasVoiceSessionStarted;
16506
+ let finished = false;
16507
+ return () => {
16508
+ if (finished) return;
16607
16509
  try {
16608
- if (!operations.applyAgent(nextAgent)) return false;
16609
- try {
16610
- await operations.clearPrewarm();
16611
- } catch (error51) {
16612
- if (!operations.applyAgent(state.agent))
16613
- throw new Error("Draft Agent switch could not restore the prior Agent", { cause: error51 });
16614
- return false;
16510
+ const state = store.getState();
16511
+ if (signal.aborted || binding.scope.get(initial.Ycr) !== origin || intentVersion !== void 0 && readIntentVersion?.() !== intentVersion || activeSeen && (!state.isVoiceModeActive || state.voiceSessionId !== session && (sessionStartedSeen || intentVersion === void 0))) {
16512
+ finished = true;
16513
+ return;
16615
16514
  }
16616
- state.agent = nextAgent;
16617
- return true;
16618
- } finally {
16619
- this.#switching.delete(state);
16515
+ if (!state.isVoiceModeActive) return;
16516
+ if (!activeSeen) {
16517
+ session = state.voiceSessionId;
16518
+ activeSeen = true;
16519
+ }
16520
+ if (session !== state.voiceSessionId) {
16521
+ session = state.voiceSessionId;
16522
+ chatVoiceDebug("native-chat-selection-startup-id-adopted");
16523
+ }
16524
+ sessionStartedSeen ||= state.hasVoiceSessionStarted;
16525
+ if (state.isWorkMode !== false || state.conversationId !== draft) {
16526
+ finished = true;
16527
+ return;
16528
+ }
16529
+ if (!state.hasVoiceSessionStarted || state.server.connectionState !== "connected") return;
16530
+ const backend = binding.scope.get(initial.B1n, draft);
16531
+ if (typeof backend !== "string" || !UUID.test(backend)) return;
16532
+ const mapping = binding.scope.get(initial.n0n, draft);
16533
+ const currentNode = binding.scope.get(initial.Y1n, draft);
16534
+ if (mapping == null || typeof mapping !== "object" || typeof currentNode !== "string" || !mapping[currentNode]?.message)
16535
+ return;
16536
+ finished = true;
16537
+ const path = Reflect.apply(conversationPath, void 0, [draft]);
16538
+ const accepted = Reflect.apply(navigate, void 0, [binding.scope, path]);
16539
+ void Promise.resolve(accepted).then(
16540
+ (result) => chatVoiceDebug(
16541
+ result === false ? "native-chat-selection-declined" : "native-chat-draft-selection-requested"
16542
+ ),
16543
+ () => chatVoiceDebug("native-chat-selection-failed")
16544
+ );
16545
+ } catch {
16546
+ finished = true;
16547
+ chatVoiceDebug("native-chat-selection-failed");
16620
16548
  }
16621
- }
16622
- #targetState(target) {
16623
- if (!isIdentifiedTarget(target)) return null;
16624
- return this.#targetStates.find((candidate) => sameTarget(candidate.target, target))?.state ?? null;
16625
- }
16626
- #state(composer, initialAgent) {
16627
- const existing = this.#states.get(composer);
16628
- if (existing) return existing;
16629
- const created = {
16630
- agent: initialAgent ?? this.#defaultAgent,
16631
- phase: "draft",
16632
- composerId: this.#idFactory(++this.#composerSequence)
16633
- };
16634
- this.#states.set(composer, created);
16635
- return created;
16636
- }
16637
- };
16638
-
16639
- // src/renderer-new-thread-preference.ts
16640
- var RENDERER_NEW_THREAD_PREFERENCE_KEY = "codexhost.new-thread-preference.v1";
16641
- function rendererNewThreadPreferenceStorage(hostId2 = "local") {
16642
- if (!hostId2) return null;
16643
- try {
16644
- const storage = window.localStorage;
16645
- if (hostId2 === "local") return storage;
16646
- const scopedKey = (key) => `${key}.host.${encodeURIComponent(hostId2)}`;
16647
- return {
16648
- getItem: (key) => storage.getItem(scopedKey(key)),
16649
- setItem: (key, value) => storage.setItem(scopedKey(key), value)
16650
- };
16651
- } catch {
16652
- return null;
16653
- }
16549
+ };
16654
16550
  }
16655
- function isRecord2(value) {
16656
- return typeof value === "object" && value !== null && !Array.isArray(value);
16551
+
16552
+ // src/renderer-native-chat-voice-presentation.ts
16553
+ function alignNativeChatVoiceButton(button2, send, doc) {
16554
+ const parent = send.parentElement;
16555
+ if (!parent) return;
16556
+ const layout = doc.defaultView?.getComputedStyle(parent);
16557
+ const gap = layout && /flex|grid/.test(layout.display) ? Number.parseFloat(layout.columnGap) || 0 : 0;
16558
+ const margin = `max(0px, calc(var(--spacing, 0.25rem) * 2 - ${gap}px))`;
16559
+ if (button2.style.marginInlineEnd !== margin) button2.style.marginInlineEnd = margin;
16560
+ if (button2.style.alignSelf !== "center") button2.style.alignSelf = "center";
16657
16561
  }
16658
- function parseExternalConfiguration(value) {
16659
- if (!isRecord2(value)) return void 0;
16660
- const model2 = harnessModelRefSchema.safeParse(value.model);
16661
- if (!model2.success) return void 0;
16662
- const thinkingOptionId = harnessThinkingOptionIdSchema.safeParse(value.thinkingOptionId);
16663
- const permissionModeId = harnessPermissionModeIdSchema.safeParse(value.permissionModeId);
16664
- return {
16665
- model: model2.data,
16666
- ...thinkingOptionId.success ? { thinkingOptionId: thinkingOptionId.data } : {},
16667
- ...permissionModeId.success ? { permissionModeId: permissionModeId.data } : {}
16562
+ function hideNativeChatVoiceHomeHeadline(doc) {
16563
+ const headlines = [
16564
+ ...doc.querySelectorAll('[style*="--vt-splash-screen-headline"]')
16565
+ ].filter(
16566
+ (element) => element.querySelector("h1 [data-headline]") && element.getBoundingClientRect().width > 0 && !element.closest("[hidden], [inert]")
16567
+ );
16568
+ if (headlines.length !== 1) return;
16569
+ const headline = headlines[0];
16570
+ if (!headline) return;
16571
+ const previous = headline.style.getPropertyValue("visibility");
16572
+ const priority = headline.style.getPropertyPriority("visibility");
16573
+ headline.style.setProperty("visibility", "hidden", "important");
16574
+ return () => {
16575
+ if (headline.style.getPropertyValue("visibility") === "hidden" && headline.style.getPropertyPriority("visibility") === "important")
16576
+ headline.style.setProperty("visibility", previous, priority);
16577
+ };
16578
+ }
16579
+
16580
+ // src/renderer-native-chat-voice.ts
16581
+ var HOOK_GLOBAL = "__codexhostNativeChatVoiceV1";
16582
+ var NATIVE_CHAT_VOICE_VERSION = "26.1016.62342";
16583
+ var NATIVE_CHAT_INITIAL_ASSET = "app-initial-dddef47d6ef6.js";
16584
+ var NATIVE_CHAT_SHARED_ASSET = "app-shared-fc3316d48427.js";
16585
+ var START_ASSET = "start-voice-7cbf0a39d53b.js";
16586
+ var PREWARM_ASSET = "voice-prewarm-5d107129ac1c.js";
16587
+ var NATIVE_CHAT_VOICE_ASSETS = [
16588
+ {
16589
+ version: NATIVE_CHAT_VOICE_VERSION,
16590
+ initial: NATIVE_CHAT_INITIAL_ASSET,
16591
+ shared: NATIVE_CHAT_SHARED_ASSET,
16592
+ start: START_ASSET,
16593
+ prewarm: PREWARM_ASSET
16594
+ },
16595
+ {
16596
+ version: "26.1016.61520",
16597
+ initial: "app-initial-c95ddb62696e.js",
16598
+ shared: "app-shared-2eda13eb7044.js",
16599
+ start: "start-voice-18b08f0acdd7.js",
16600
+ prewarm: "voice-prewarm-94271e581c1b.js"
16601
+ }
16602
+ ];
16603
+ var BUTTON = "data-codexhost-chat-voice-start";
16604
+ var CONVERSATION_UUID = /^[a-f\d]{8}(?:-[a-f\d]{4}){3}-[a-f\d]{12}$/i;
16605
+ var record2 = (value) => value !== null && typeof value === "object";
16606
+ function scopeRefs(owner, definition) {
16607
+ const scopes = /* @__PURE__ */ new Map();
16608
+ let hook = owner.memoizedState;
16609
+ const seen = /* @__PURE__ */ new Set();
16610
+ while (record2(hook) && seen.size < 4096 && !seen.has(hook)) {
16611
+ seen.add(hook);
16612
+ const state = hook.memoizedState;
16613
+ const scope = record2(state) ? state.current : void 0;
16614
+ if (record2(scope) && scope.scope === definition && typeof scope.get === "function" && typeof scope.set === "function" && scope.chain instanceof Map && scope.chain.get(definition.id) === scope.node)
16615
+ scopes.set(scope.node, scope);
16616
+ hook = hook.next;
16617
+ }
16618
+ return hook == null ? scopes : /* @__PURE__ */ new Map();
16619
+ }
16620
+ function detectNativeChatVoiceBinding(composer, native) {
16621
+ const key = Object.getOwnPropertyNames(composer).find((key2) => key2.startsWith("__reactFiber$"));
16622
+ if (!key) return;
16623
+ const ancestors = committedReactAncestors(Object.getOwnPropertyDescriptor(composer, key)?.value);
16624
+ for (const owner of ancestors) {
16625
+ const props = owner.memoizedProps;
16626
+ if (!record2(props) || typeof props.isWorkConversation !== "boolean" || typeof props.isTemporaryChat !== "boolean" || typeof props.conversationId !== "string")
16627
+ continue;
16628
+ if (props.isWorkConversation || props.isTemporaryChat || props.shouldBlockExternalEgress !== false || props.readOnly === true || !record2(props.composerSelectionContext) || props.composerSelectionContext.conversationId !== props.conversationId)
16629
+ return;
16630
+ const scopes = scopeRefs(owner, native.ownerScopeDefinition);
16631
+ if (scopes.size !== 1) return;
16632
+ const scope = [...scopes.values()][0];
16633
+ if (!scope) return;
16634
+ const appNode = scope.chain.get(native.scopeDefinition.id);
16635
+ if (!record2(appNode)) return;
16636
+ const backendId = scope.get(native.backendConversationId, props.conversationId);
16637
+ const hasBackendId = typeof backendId === "string" && CONVERSATION_UUID.test(backendId);
16638
+ const isDraft = backendId == null && props.conversationId.startsWith("local-chatgpt:") && CONVERSATION_UUID.test(props.conversationId.slice("local-chatgpt:".length));
16639
+ if (!hasBackendId && !isDraft) return;
16640
+ const provider = ancestors.slice(0, ancestors.indexOf(owner)).find(
16641
+ (fiber) => fiber.tag === 10 && record2(fiber.type) && fiber.type.displayName === native.composerScopeDefinition.__scopeBrand
16642
+ );
16643
+ if (!provider) return;
16644
+ const composerChain = record2(provider.memoizedProps) ? provider.memoizedProps.value : void 0;
16645
+ const composerNode = composerChain instanceof Map ? composerChain.get(native.composerScopeDefinition.id) : void 0;
16646
+ if (!(composerChain instanceof Map) || composerChain.get(native.scopeDefinition.id) !== appNode || composerChain.get(native.ownerScopeDefinition.id) !== scope.node || !record2(composerNode) || composerNode.token !== native.composerScopeDefinition || !record2(composerNode.value) || composerNode.value.kind !== "chatgpt" || !record2(composerNode.value.chatGptSelectionContext) || composerNode.value.chatGptSelectionContext.conversationId !== props.conversationId)
16647
+ return;
16648
+ const composerContext = { context: provider.type, chain: composerChain };
16649
+ const contexts = /* @__PURE__ */ new Set();
16650
+ for (const fiber of ancestors) {
16651
+ let dependency = record2(fiber.dependencies) ? fiber.dependencies.firstContext : void 0;
16652
+ const seenDependencies = /* @__PURE__ */ new Set();
16653
+ while (record2(dependency) && seenDependencies.size < 4096 && !seenDependencies.has(dependency)) {
16654
+ seenDependencies.add(dependency);
16655
+ const context = dependency.context;
16656
+ const chain = dependency.memoizedValue;
16657
+ if (record2(context) && context.displayName === native.scopeDefinition.__scopeBrand && chain instanceof Map && chain.get(native.scopeDefinition.id) === appNode)
16658
+ contexts.add(context);
16659
+ dependency = dependency.next;
16660
+ }
16661
+ }
16662
+ if (contexts.size !== 1) return;
16663
+ return {
16664
+ composer,
16665
+ appNode,
16666
+ props,
16667
+ scope,
16668
+ context: [...contexts][0],
16669
+ composerContext,
16670
+ backendConversationId: hasBackendId ? backendId : void 0
16671
+ };
16672
+ }
16673
+ }
16674
+ function startNativeChatVoiceForRequest(request, target = globalThis) {
16675
+ if (!record2(request) || request.productExperience !== "chat" || !nativeChatVoicePreferenceAllowsStart(target))
16676
+ return;
16677
+ const hook = target[HOOK_GLOBAL];
16678
+ if (!hook || hook.status === "disabled" || hook.status === "unsupported")
16679
+ return Promise.reject(new Error("Native Chat voice is unavailable"));
16680
+ return hook.start();
16681
+ }
16682
+ function installNativeChatVoiceHook(options = {}) {
16683
+ const target = options.target ?? globalThis;
16684
+ const existing = target[HOOK_GLOBAL];
16685
+ if (existing) return existing;
16686
+ const config2 = readCpaLiveVoiceHookConfig(target);
16687
+ const unavailable = (status) => ({
16688
+ status,
16689
+ dispose() {
16690
+ },
16691
+ start: async () => {
16692
+ throw new Error("Native Chat voice is unavailable");
16693
+ }
16694
+ });
16695
+ if (!config2) return unavailable("disabled");
16696
+ if (!supportsVoiceVersion(NATIVE_CHAT_VOICE_ASSETS, config2.desktopVersion))
16697
+ return unavailable("unsupported");
16698
+ const doc = target.document;
16699
+ if (!doc) return unavailable("unsupported");
16700
+ const importModule = options.importModule ?? ((url2) => import(url2));
16701
+ let disposed = false;
16702
+ let timer;
16703
+ let modules;
16704
+ let boundScope;
16705
+ let mounted;
16706
+ let button2;
16707
+ const overlayButtons = /* @__PURE__ */ new Map();
16708
+ let controller;
16709
+ let starting = false;
16710
+ let selectDraft;
16711
+ let restoreHeadline;
16712
+ let renderFailed = false;
16713
+ const started = Date.now();
16714
+ const showHeadline = () => {
16715
+ restoreHeadline?.();
16716
+ restoreHeadline = void 0;
16717
+ };
16718
+ const removeButton = () => {
16719
+ button2?.remove();
16720
+ button2 = void 0;
16721
+ for (const [control, [display, priority]] of overlayButtons)
16722
+ if (control.style.getPropertyValue("display") === "none" && control.style.getPropertyPriority("display") === "important")
16723
+ control.style.setProperty("display", display, priority);
16724
+ overlayButtons.clear();
16725
+ };
16726
+ const release = () => {
16727
+ showHeadline();
16728
+ selectDraft = void 0;
16729
+ controller?.abort();
16730
+ controller = void 0;
16731
+ removeButton();
16732
+ mounted?.dispose();
16733
+ mounted = void 0;
16734
+ boundScope = void 0;
16735
+ };
16736
+ const findBinding = () => {
16737
+ if (!modules || !nativeChatVoicePreferenceAllowsStart(target)) return;
16738
+ const forms = [...doc.querySelectorAll("[data-chatgpt-composer]")].filter(
16739
+ (form2) => form2.getBoundingClientRect().width > 0 && !form2.closest("[hidden], [inert]")
16740
+ );
16741
+ if (forms.length !== 1) return;
16742
+ const form = forms[0];
16743
+ if (!form) return;
16744
+ return detectNativeChatVoiceBinding(form, {
16745
+ scopeDefinition: modules.shared.Qst,
16746
+ ownerScopeDefinition: modules.initial.MOr,
16747
+ backendConversationId: modules.initial.B1n,
16748
+ composerScopeDefinition: modules.initial.CDr
16749
+ });
16750
+ };
16751
+ const handle = {
16752
+ get activeCall() {
16753
+ if (starting) return true;
16754
+ if (!boundScope || !modules || typeof modules.initial.SPn !== "function") return false;
16755
+ try {
16756
+ return modules.initial.SPn(boundScope).getState().isVoiceModeActive;
16757
+ } catch {
16758
+ return false;
16759
+ }
16760
+ },
16761
+ status: "pending",
16762
+ async start() {
16763
+ if (disposed || starting) return;
16764
+ const current = findBinding();
16765
+ if (!current || !modules || current.appNode !== mounted?.node)
16766
+ throw new Error("Select a ChatGPT conversation to start Chat voice");
16767
+ starting = true;
16768
+ controller = new AbortController();
16769
+ const signal = controller.signal;
16770
+ let origin;
16771
+ try {
16772
+ if (modules.initial.Ycr != null) origin = current.scope.get(modules.initial.Ycr);
16773
+ } catch {
16774
+ chatVoiceDebug("native-chat-selection-failed");
16775
+ }
16776
+ selectDraft = void 0;
16777
+ chatVoiceDebug("native-chat-start", {
16778
+ hasConversationId: current.backendConversationId !== void 0
16779
+ });
16780
+ try {
16781
+ await Reflect.apply(modules.start.n, void 0, [
16782
+ current.scope,
16783
+ current.props,
16784
+ { event_source: "composer" },
16785
+ signal
16786
+ ]);
16787
+ chatVoiceDebug("native-chat-start-returned");
16788
+ try {
16789
+ selectDraft = nativeChatVoiceDraftSelection(
16790
+ current,
16791
+ modules.initial,
16792
+ modules.shared,
16793
+ origin,
16794
+ signal
16795
+ );
16796
+ selectDraft();
16797
+ } catch {
16798
+ chatVoiceDebug("native-chat-selection-failed");
16799
+ }
16800
+ } catch {
16801
+ chatVoiceDebug("native-chat-start-failed");
16802
+ throw new Error("Native Chat voice could not start");
16803
+ } finally {
16804
+ starting = false;
16805
+ }
16806
+ },
16807
+ dispose() {
16808
+ disposed = true;
16809
+ if (timer !== void 0) target.clearTimeout(timer);
16810
+ release();
16811
+ if (target[HOOK_GLOBAL] === handle) Reflect.deleteProperty(target, HOOK_GLOBAL);
16812
+ }
16813
+ };
16814
+ target[HOOK_GLOBAL] = handle;
16815
+ const refresh = async () => {
16816
+ if (disposed) return;
16817
+ try {
16818
+ if (renderFailed) throw new Error();
16819
+ if (!modules) {
16820
+ const names = loadedAssetNames(doc, target.performance);
16821
+ const assets = selectVoiceAssets(NATIVE_CHAT_VOICE_ASSETS, names, config2.desktopVersion);
16822
+ if (!assets) {
16823
+ if (Date.now() - started >= 6e4) {
16824
+ handle.status = "unsupported";
16825
+ return;
16826
+ }
16827
+ return;
16828
+ }
16829
+ const [shared, initial, start, prewarm] = await Promise.all(
16830
+ [assets.shared, assets.initial, assets.start, assets.prewarm].map(
16831
+ (asset) => importModule(`app://-/assets/${asset}`)
16832
+ )
16833
+ );
16834
+ if (disposed) return;
16835
+ if (!shared || !initial || !start || !prewarm || typeof start.t !== "function" || typeof start.n !== "function" || typeof initial.oT !== "function" || typeof initial.SPn !== "function" || typeof shared.FBt !== "function" || typeof shared.PNt !== "function" || typeof prewarm.VoiceInstantConnectCacheController !== "function")
16836
+ throw new Error();
16837
+ initial.oT();
16838
+ start.t();
16839
+ if (!record2(shared.Qst) || !record2(initial.MOr) || !initial.B1n || !initial.aT || !record2(initial.CDr))
16840
+ throw new Error();
16841
+ modules = { shared, initial, start, prewarm };
16842
+ }
16843
+ const current = findBinding();
16844
+ selectDraft?.();
16845
+ if (!current) {
16846
+ showHeadline();
16847
+ if (handle.activeCall) return;
16848
+ release();
16849
+ handle.status = "pending";
16850
+ return;
16851
+ }
16852
+ if (mounted && current.appNode !== mounted.node) release();
16853
+ boundScope = current.scope;
16854
+ if (!mounted) {
16855
+ const { scope } = current;
16856
+ const initial = modules.initial;
16857
+ const previous = scope.get(initial.aT);
16858
+ const container = doc.createElement("div");
16859
+ container.setAttribute("data-codexhost-chat-voice-prewarm", "");
16860
+ doc.body.append(container);
16861
+ const react = Reflect.apply(modules.shared.FBt, void 0, []);
16862
+ const reactDom = Reflect.apply(modules.shared.PNt, void 0, []);
16863
+ const root = reactDom.createRoot(container, {
16864
+ onUncaughtError() {
16865
+ renderFailed = true;
16866
+ }
16867
+ });
16868
+ const stopObserving = observeNativeChatVoiceState(scope, initial);
16869
+ let appChain;
16870
+ let composerChain;
16871
+ mounted = {
16872
+ node: current.appNode,
16873
+ render(next) {
16874
+ if (appChain === next.scope.chain && composerChain === next.composerContext.chain)
16875
+ return;
16876
+ appChain = next.scope.chain;
16877
+ composerChain = next.composerContext.chain;
16878
+ root.render(
16879
+ react.createElement(
16880
+ next.context,
16881
+ { value: appChain },
16882
+ react.createElement(
16883
+ next.composerContext.context,
16884
+ { value: composerChain },
16885
+ react.createElement(modules?.prewarm.VoiceInstantConnectCacheController, null)
16886
+ )
16887
+ )
16888
+ );
16889
+ },
16890
+ dispose() {
16891
+ stopObserving();
16892
+ root.unmount();
16893
+ container.remove();
16894
+ if (scope.get(initial.aT) === true) scope.set(initial.aT, previous);
16895
+ chatVoiceDebug("native-chat-unbound");
16896
+ }
16897
+ };
16898
+ scope.set(initial.aT, true);
16899
+ chatVoiceDebug("native-chat-bound", {
16900
+ hasConversationId: current.backendConversationId !== void 0
16901
+ });
16902
+ }
16903
+ mounted.render(current);
16904
+ const voiceState = Reflect.apply(modules.initial.SPn, void 0, [
16905
+ current.scope
16906
+ ]).getState();
16907
+ if (current.props.conversationId.startsWith("local-chatgpt:") && voiceState.isVoiceModeActive && voiceState.isWorkMode === false && voiceState.conversationId === current.props.conversationId)
16908
+ restoreHeadline ??= hideNativeChatVoiceHomeHeadline(doc);
16909
+ else showHeadline();
16910
+ const sends = [
16911
+ ...current.composer.querySelectorAll('button[type="submit"]')
16912
+ ].filter(
16913
+ (control) => control.getBoundingClientRect().width > 0 && !control.closest("[hidden], [inert]")
16914
+ );
16915
+ const send = sends.length === 1 ? sends[0] : void 0;
16916
+ if (!send?.parentElement) removeButton();
16917
+ else {
16918
+ if (!button2 || !current.composer.contains(button2)) {
16919
+ removeButton();
16920
+ button2 = doc.createElement("button");
16921
+ button2.type = "button";
16922
+ button2.setAttribute(BUTTON, "");
16923
+ button2.setAttribute("aria-label", "Start Voice");
16924
+ button2.title = "Start Voice";
16925
+ button2.innerHTML = '<svg class="icon-primary-action text-composer-primary" width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true"><path d="M3 8v4m4-7v10m6-12v14m4-9v4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>';
16926
+ button2.addEventListener("click", () => {
16927
+ void handle.start().catch(() => {
16928
+ if (button2) button2.title = "Chat voice could not start. Try again.";
16929
+ });
16930
+ });
16931
+ }
16932
+ if (button2.nextElementSibling !== send) send.parentElement.insertBefore(button2, send);
16933
+ alignNativeChatVoiceButton(button2, send, doc);
16934
+ }
16935
+ if (button2)
16936
+ for (const control of current.composer.querySelectorAll(
16937
+ 'button[aria-label="Start new voice chat"], button[aria-label="Start voice chat"]'
16938
+ )) {
16939
+ if (!overlayButtons.has(control))
16940
+ overlayButtons.set(control, [
16941
+ control.style.getPropertyValue("display"),
16942
+ control.style.getPropertyPriority("display")
16943
+ ]);
16944
+ control.style.setProperty("display", "none", "important");
16945
+ }
16946
+ if (button2 && send) {
16947
+ button2.disabled = starting || current.props.isStreaming === true || voiceState.isVoiceModeActive;
16948
+ const className = [
16949
+ ...send.className.split(/\s+/).filter((name) => name !== "opacity-50" && name !== "cursor-default"),
16950
+ ...button2.disabled ? ["opacity-50", "cursor-default"] : []
16951
+ ].join(" ");
16952
+ if (button2.className !== className) button2.className = className;
16953
+ }
16954
+ handle.status = "active";
16955
+ } catch {
16956
+ release();
16957
+ handle.status = "unsupported";
16958
+ chatVoiceDebug("native-chat-binding-failed");
16959
+ return;
16960
+ } finally {
16961
+ if (!disposed && handle.status !== "unsupported")
16962
+ timer = target.setTimeout(() => void refresh(), 500);
16963
+ }
16964
+ };
16965
+ void refresh();
16966
+ return handle;
16967
+ }
16968
+
16969
+ // src/agent-selection-state.ts
16970
+ var DEFAULT_RENDERER_AGENTS = ["codex"];
16971
+ function defaultIdFactory(sequence) {
16972
+ return `codexhost-composer-${Date.now().toString(36)}-${sequence.toString(36)}`;
16973
+ }
16974
+ function isDefaultTarget(target) {
16975
+ return target?.[0] === "default";
16976
+ }
16977
+ function isConversationTarget(target) {
16978
+ return target?.[0] === "conversation";
16979
+ }
16980
+ function isIdentifiedTarget(target) {
16981
+ return target?.[0] === "conversation" || target?.[0] === "default" && typeof target[1] === "string" && target[1].startsWith("client-new-thread:");
16982
+ }
16983
+ function sameTarget(left, right) {
16984
+ return left.length === right.length && left.every((value, index) => value === right[index]);
16985
+ }
16986
+ var DraftAgentController = class {
16987
+ #idFactory;
16988
+ #defaultAgent;
16989
+ #enabledAgents;
16990
+ #targetStates = [];
16991
+ #modelRequestGenerations = /* @__PURE__ */ new WeakMap();
16992
+ #ownershipRequestGenerations = /* @__PURE__ */ new WeakMap();
16993
+ #states = /* @__PURE__ */ new WeakMap();
16994
+ #switching = /* @__PURE__ */ new Set();
16995
+ #pendingSubmissions = /* @__PURE__ */ new Set();
16996
+ #composerSequence = 0;
16997
+ #modelRequestSequence = 0;
16998
+ #ownershipRequestSequence = 0;
16999
+ #lastSubmittedAgent;
17000
+ constructor(options = {}) {
17001
+ this.#idFactory = options.idFactory ?? defaultIdFactory;
17002
+ this.#enabledAgents = new Set(options.enabledAgents ?? DEFAULT_RENDERER_AGENTS);
17003
+ if (!this.#enabledAgents.has("codex"))
17004
+ throw new Error("Renderer enabled Agents must include Codex");
17005
+ this.#defaultAgent = options.defaultAgent ?? "codex";
17006
+ this.#lastSubmittedAgent = this.#defaultAgent;
17007
+ }
17008
+ setEnabledAgents(agents) {
17009
+ this.#enabledAgents = /* @__PURE__ */ new Set(["codex", ...agents]);
17010
+ }
17011
+ get(composer) {
17012
+ return this.#state(composer);
17013
+ }
17014
+ detach(composer, preservePendingSubmission = false) {
17015
+ const state = this.#states.get(composer);
17016
+ if (state && !preservePendingSubmission) this.#pendingSubmissions.delete(state);
17017
+ this.#states.delete(composer);
17018
+ }
17019
+ mount(composer, target, preferredNewThreadAgent) {
17020
+ const bound = this.#targetState(target);
17021
+ if (bound) {
17022
+ this.#states.set(composer, bound);
17023
+ return bound;
17024
+ }
17025
+ const preferredAgent = preferredNewThreadAgent ?? this.#lastSubmittedAgent;
17026
+ const state = this.#state(composer, isDefaultTarget(target) ? preferredAgent : "codex");
17027
+ if (isIdentifiedTarget(target)) this.#targetStates.push({ target, state });
17028
+ return state;
17029
+ }
17030
+ isSwitching(composer) {
17031
+ return this.#switching.has(this.#state(composer));
17032
+ }
17033
+ beginModelRequest(composer) {
17034
+ const state = this.#state(composer);
17035
+ const generation = ++this.#modelRequestSequence;
17036
+ this.#modelRequestGenerations.set(state, generation);
17037
+ return generation;
17038
+ }
17039
+ invalidateModelRequests(composer) {
17040
+ this.beginModelRequest(composer);
17041
+ }
17042
+ isCurrentModelRequest(composer, generation) {
17043
+ return (this.#modelRequestGenerations.get(this.#state(composer)) ?? 0) === generation;
17044
+ }
17045
+ beginOwnershipRequest(composer) {
17046
+ const generation = ++this.#ownershipRequestSequence;
17047
+ this.#ownershipRequestGenerations.set(this.#state(composer), generation);
17048
+ return generation;
17049
+ }
17050
+ isCurrentOwnershipRequest(composer, generation) {
17051
+ return (this.#ownershipRequestGenerations.get(this.#state(composer)) ?? 0) === generation;
17052
+ }
17053
+ rebindConversation(composer, target) {
17054
+ if (!isConversationTarget(target)) return null;
17055
+ const previous = this.#state(composer);
17056
+ this.#pendingSubmissions.delete(previous);
17057
+ this.#modelRequestGenerations.set(previous, ++this.#modelRequestSequence);
17058
+ this.#ownershipRequestGenerations.set(previous, ++this.#ownershipRequestSequence);
17059
+ let state = this.#targetState(target);
17060
+ if (!state) {
17061
+ state = {
17062
+ agent: "codex",
17063
+ phase: "draft",
17064
+ composerId: this.#idFactory(++this.#composerSequence)
17065
+ };
17066
+ this.#targetStates.push({ target, state });
17067
+ }
17068
+ this.#states.set(composer, state);
17069
+ this.#modelRequestGenerations.set(state, ++this.#modelRequestSequence);
17070
+ this.#ownershipRequestGenerations.set(state, ++this.#ownershipRequestSequence);
17071
+ return state;
17072
+ }
17073
+ restore(composer, agent, model2, thinkingOptionId, permissionModeId) {
17074
+ const state = this.#state(composer);
17075
+ this.#pendingSubmissions.delete(state);
17076
+ state.agent = agent;
17077
+ state.phase = "locked";
17078
+ if (agent !== "codex") {
17079
+ state.configurationByAgent = {
17080
+ ...state.configurationByAgent,
17081
+ [agent]: {
17082
+ ...model2 ? { model: model2 } : {},
17083
+ ...thinkingOptionId ? { thinkingOptionId } : {},
17084
+ ...permissionModeId ? { permissionModeId } : {}
17085
+ }
17086
+ };
17087
+ }
17088
+ return state;
17089
+ }
17090
+ clearConfiguration(composer) {
17091
+ delete this.#state(composer).configurationByAgent;
17092
+ }
17093
+ modelForAgent(composer, agent) {
17094
+ return this.#state(composer).configurationByAgent?.[agent]?.model;
17095
+ }
17096
+ thinkingOptionForAgent(composer, agent) {
17097
+ return this.#state(composer).configurationByAgent?.[agent]?.thinkingOptionId;
17098
+ }
17099
+ permissionModeForAgent(composer, agent) {
17100
+ return this.#state(composer).configurationByAgent?.[agent]?.permissionModeId;
17101
+ }
17102
+ setExternalPermissionMode(composer, agent, permissionModeId) {
17103
+ return this.#configure(composer, agent, { permissionModeId });
17104
+ }
17105
+ setExternalModel(composer, agent, model2) {
17106
+ return this.#configure(composer, agent, { model: model2 });
17107
+ }
17108
+ setExternalThinkingOption(composer, agent, thinkingOptionId) {
17109
+ const state = this.#configure(composer, agent, {});
17110
+ const configuration = state.configurationByAgent?.[agent];
17111
+ if (!configuration) return state;
17112
+ if (thinkingOptionId) configuration.thinkingOptionId = thinkingOptionId;
17113
+ else delete configuration.thinkingOptionId;
17114
+ return state;
17115
+ }
17116
+ #configure(composer, agent, patch) {
17117
+ const state = this.#state(composer);
17118
+ state.configurationByAgent = {
17119
+ ...state.configurationByAgent,
17120
+ [agent]: { ...state.configurationByAgent?.[agent], ...patch }
17121
+ };
17122
+ return state;
17123
+ }
17124
+ lock(composer) {
17125
+ const state = this.#state(composer);
17126
+ this.#pendingSubmissions.delete(state);
17127
+ state.phase = "locked";
17128
+ return state;
17129
+ }
17130
+ markSubmissionPending(composer) {
17131
+ const state = this.#state(composer);
17132
+ if (state.phase === "draft") this.#pendingSubmissions.add(state);
17133
+ return state;
17134
+ }
17135
+ isSubmissionPending(composer) {
17136
+ return this.#pendingSubmissions.has(this.#state(composer));
17137
+ }
17138
+ clearPendingSubmission(composer) {
17139
+ this.#pendingSubmissions.delete(this.#state(composer));
17140
+ }
17141
+ recordSubmission(composer) {
17142
+ const state = this.#state(composer);
17143
+ this.#lastSubmittedAgent = state.agent;
17144
+ return state;
17145
+ }
17146
+ transfer(source, replacement, target = null) {
17147
+ const state = this.#states.get(source);
17148
+ if (!state) return false;
17149
+ const bound = this.#targetState(target);
17150
+ if (bound && bound !== state) return false;
17151
+ if (source !== replacement) {
17152
+ if (this.#states.has(replacement)) return false;
17153
+ this.#states.set(replacement, state);
17154
+ }
17155
+ if (isIdentifiedTarget(target) && !bound) this.#targetStates.push({ target, state });
17156
+ if (isConversationTarget(target) && this.#pendingSubmissions.delete(state))
17157
+ state.phase = "locked";
17158
+ return true;
17159
+ }
17160
+ async switchAgent(composer, nextAgent, operations) {
17161
+ const state = this.#state(composer);
17162
+ if (!this.#enabledAgents.has(nextAgent)) return false;
17163
+ if (state.phase !== "draft" || this.#switching.has(state)) return false;
17164
+ if (state.agent === nextAgent) return true;
17165
+ this.#pendingSubmissions.delete(state);
17166
+ this.#switching.add(state);
17167
+ try {
17168
+ if (!operations.applyAgent(nextAgent)) return false;
17169
+ try {
17170
+ await operations.clearPrewarm();
17171
+ } catch (error51) {
17172
+ if (!operations.applyAgent(state.agent))
17173
+ throw new Error("Draft Agent switch could not restore the prior Agent", { cause: error51 });
17174
+ return false;
17175
+ }
17176
+ state.agent = nextAgent;
17177
+ return true;
17178
+ } finally {
17179
+ this.#switching.delete(state);
17180
+ }
17181
+ }
17182
+ #targetState(target) {
17183
+ if (!isIdentifiedTarget(target)) return null;
17184
+ return this.#targetStates.find((candidate) => sameTarget(candidate.target, target))?.state ?? null;
17185
+ }
17186
+ #state(composer, initialAgent) {
17187
+ const existing = this.#states.get(composer);
17188
+ if (existing) return existing;
17189
+ const created = {
17190
+ agent: initialAgent ?? this.#defaultAgent,
17191
+ phase: "draft",
17192
+ composerId: this.#idFactory(++this.#composerSequence)
17193
+ };
17194
+ this.#states.set(composer, created);
17195
+ return created;
17196
+ }
17197
+ };
17198
+
17199
+ // src/renderer-new-thread-preference.ts
17200
+ var RENDERER_NEW_THREAD_PREFERENCE_KEY = "codexhost.new-thread-preference.v1";
17201
+ function rendererNewThreadPreferenceStorage(hostId2 = "local") {
17202
+ if (!hostId2) return null;
17203
+ try {
17204
+ const storage = window.localStorage;
17205
+ if (hostId2 === "local") return storage;
17206
+ const scopedKey = (key) => `${key}.host.${encodeURIComponent(hostId2)}`;
17207
+ return {
17208
+ getItem: (key) => storage.getItem(scopedKey(key)),
17209
+ setItem: (key, value) => storage.setItem(scopedKey(key), value)
17210
+ };
17211
+ } catch {
17212
+ return null;
17213
+ }
17214
+ }
17215
+ function isRecord2(value) {
17216
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17217
+ }
17218
+ function parseExternalConfiguration(value) {
17219
+ if (!isRecord2(value)) return void 0;
17220
+ const model2 = harnessModelRefSchema.safeParse(value.model);
17221
+ if (!model2.success) return void 0;
17222
+ const thinkingOptionId = harnessThinkingOptionIdSchema.safeParse(value.thinkingOptionId);
17223
+ const permissionModeId = harnessPermissionModeIdSchema.safeParse(value.permissionModeId);
17224
+ return {
17225
+ model: model2.data,
17226
+ ...thinkingOptionId.success ? { thinkingOptionId: thinkingOptionId.data } : {},
17227
+ ...permissionModeId.success ? { permissionModeId: permissionModeId.data } : {}
16668
17228
  };
16669
17229
  }
16670
17230
  function readPreference(storage) {
@@ -16747,14 +17307,34 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16747
17307
  }
16748
17308
 
16749
17309
  // src/renderer-client-owned-voice.ts
16750
- var CLIENT_OWNED_VOICE_VERSION = "26.1013.40934";
16751
- var CLIENT_OWNED_VOICE_INITIAL_ASSET = "app-initial-90eb42395b7d.js";
16752
- var CLIENT_OWNED_VOICE_SHARED_ASSET = "app-shared-52443cbe9ac4.js";
16753
- var HOOK_GLOBAL = "__codexhostClientOwnedVoiceV1";
17310
+ var CLIENT_OWNED_VOICE_VERSION = "26.1016.62342";
17311
+ var CLIENT_OWNED_VOICE_INITIAL_ASSET = "app-initial-dddef47d6ef6.js";
17312
+ var CLIENT_OWNED_VOICE_SHARED_ASSET = "app-shared-fc3316d48427.js";
17313
+ var CLIENT_OWNED_VOICE_SERVICES = "ist";
17314
+ var CLIENT_OWNED_VOICE_OVERLAY_ASSET = "avatar-overlay-native-page-ef8332c6a2fb.js";
17315
+ var CLIENT_OWNED_VOICE_ASSETS = [
17316
+ {
17317
+ version: CLIENT_OWNED_VOICE_VERSION,
17318
+ initial: CLIENT_OWNED_VOICE_INITIAL_ASSET,
17319
+ shared: CLIENT_OWNED_VOICE_SHARED_ASSET,
17320
+ overlay: CLIENT_OWNED_VOICE_OVERLAY_ASSET
17321
+ },
17322
+ {
17323
+ version: "26.1016.61520",
17324
+ initial: "app-initial-c95ddb62696e.js",
17325
+ shared: "app-shared-2eda13eb7044.js",
17326
+ overlay: "avatar-overlay-native-page-fcc17283519a.js"
17327
+ }
17328
+ ];
17329
+ var SHARED_INIT = "Oit";
17330
+ var SHARED_FETCH = "Eit";
17331
+ var SHARED_BRIDGE = "AFt";
17332
+ var SHARED_RESPONSE_ERROR = "Mit";
17333
+ var HOOK_GLOBAL2 = "__codexhostClientOwnedVoiceV1";
16754
17334
  function object2(value) {
16755
17335
  return value !== null && typeof value === "object";
16756
17336
  }
16757
- function requestHost(bridge, params) {
17337
+ function requestVoiceHost(bridge, params, method = REALTIME_CALL_METHOD) {
16758
17338
  const id = `codexhost-voice:${crypto.randomUUID()}`;
16759
17339
  return new Promise((resolve, reject) => {
16760
17340
  let unsubscribe = () => {
@@ -16777,7 +17357,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16777
17357
  try {
16778
17358
  bridge.dispatchMessage("mcp-request", {
16779
17359
  hostId: "local",
16780
- request: { id, method: REALTIME_CALL_METHOD, params }
17360
+ request: { id, method, params }
16781
17361
  });
16782
17362
  } catch {
16783
17363
  done();
@@ -16802,24 +17382,50 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16802
17382
  if (model2.startsWith("codexhost/")) return true;
16803
17383
  return model2.includes("codex") && !model2.includes("gpt-live");
16804
17384
  }
17385
+ function visibleWorkComposer(owner) {
17386
+ const document2 = owner.document;
17387
+ if (!document2?.querySelectorAll) return false;
17388
+ const work = [...document2.querySelectorAll("[data-codex-composer-root]")].some((element) => {
17389
+ const box = element.getBoundingClientRect?.() ?? { width: 0 };
17390
+ return box.width > 0 && !element.closest?.("[hidden], [inert]");
17391
+ });
17392
+ return work && document2.querySelectorAll("[data-chatgpt-composer]").length === 0;
17393
+ }
17394
+ function selectedWorkTab(owner) {
17395
+ const document2 = owner.document;
17396
+ if (!document2?.querySelectorAll) return false;
17397
+ const controls = document2.querySelectorAll("button, [role='tab'], a");
17398
+ let workSelected = false;
17399
+ let chatSelected = false;
17400
+ for (const element of controls) {
17401
+ const name = (element.textContent ?? "").replace(/\s+/gu, " ").trim();
17402
+ if (name !== "Chat" && name !== "Work") continue;
17403
+ const selected = element.getAttribute("aria-selected") === "true" || element.getAttribute("aria-pressed") === "true" || element.getAttribute("data-state") === "active" || element.getAttribute("data-state") === "on";
17404
+ if (!selected) continue;
17405
+ if (name === "Work") workSelected = true;
17406
+ if (name === "Chat") chatSelected = true;
17407
+ }
17408
+ return workSelected && !chatSelected;
17409
+ }
16805
17410
  function isWorkComposerSurface(owner = globalThis) {
16806
17411
  const storage = owner.localStorage;
16807
- if (!storage) return false;
16808
17412
  try {
16809
- const raw = storage.getItem(HOME_COMPOSER_MODE_STORAGE_KEY);
16810
- if (raw == null || raw === "") return false;
16811
- let value = raw;
16812
- if (raw.startsWith("{") || raw.startsWith('"') || raw.startsWith("[")) {
16813
- try {
16814
- value = JSON.parse(raw);
16815
- } catch {
16816
- value = raw;
17413
+ const raw = storage?.getItem(HOME_COMPOSER_MODE_STORAGE_KEY);
17414
+ if (raw != null && raw !== "") {
17415
+ let value = raw;
17416
+ if (raw.startsWith("{") || raw.startsWith('"') || raw.startsWith("[")) {
17417
+ try {
17418
+ value = JSON.parse(raw);
17419
+ } catch {
17420
+ value = raw;
17421
+ }
16817
17422
  }
17423
+ if (value === "work") return true;
17424
+ if (value === "chat") return false;
16818
17425
  }
16819
- return value === "work";
16820
17426
  } catch {
16821
- return false;
16822
17427
  }
17428
+ return selectedWorkTab(owner) || visibleWorkComposer(owner);
16823
17429
  }
16824
17430
  function selectedComposerModel(owner) {
16825
17431
  const routing = owner.__codexhostHostRoutingV1;
@@ -16844,579 +17450,147 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16844
17450
  if (lastVoiceProductExperience !== "work") return false;
16845
17451
  const storage = owner.localStorage;
16846
17452
  if (!storage) return false;
16847
- try {
16848
- const parsed = JSON.parse(storage.getItem(RENDERER_NEW_THREAD_PREFERENCE_KEY) ?? "");
16849
- return typeof parsed.lastAgent === "string" && parsed.lastAgent !== "" && parsed.lastAgent !== "codex";
16850
- } catch {
16851
- return false;
16852
- }
16853
- }
16854
- function wrapClientOwnedVoiceFetch(client, bridge, workOverride, responseError, chatOverride = () => false, workSurface = () => isWorkComposerSurface(), externalHarness = () => isExternalHarnessComposer()) {
16855
- const original = client.fetch;
16856
- const wrapped = async function(url2, options) {
16857
- if (typeof url2 !== "string" || url2.split("?")[0] !== "/wham/realtime/calls" || options?.method !== "POST")
16858
- return original.call(this, url2, options);
16859
- const chat = lastVoiceLaunchProductExperience() === "chat";
16860
- const model2 = backendModelName(options.headers);
16861
- const work = !chat && (isWorkBackendModel(model2) || workSurface());
16862
- const external = !chat && (model2.startsWith("codexhost/") || externalHarness());
16863
- const gateway = external || (work ? workOverride() : chatOverride());
16864
- chatVoiceDebug("wham-create", {
16865
- work,
16866
- external,
16867
- route: gateway ? "gateway" : "desktop",
16868
- productExperience: lastVoiceLaunchProductExperience() ?? "unset"
16869
- });
16870
- if (!gateway) {
16871
- if (!external && !work)
16872
- chatVoiceDebug("chat-native-passthrough", {
16873
- hasThreadId: new Headers(options.headers).has("Thread-Id")
16874
- });
16875
- return original.call(this, url2, options);
16876
- }
16877
- if (typeof options.body !== "string") throw new Error("Unsupported native voice call request");
16878
- options.signal?.throwIfAborted();
16879
- const requestId2 = crypto.randomUUID();
16880
- let cancelled = false;
16881
- const cancel = () => {
16882
- if (cancelled) return;
16883
- cancelled = true;
16884
- void requestHost(bridge, { operation: "cancel", requestId: requestId2 }).catch(() => void 0);
16885
- };
16886
- options.signal?.addEventListener("abort", cancel, { once: true });
16887
- try {
16888
- const result = realtimeCallResponseSchema.parse(
16889
- await requestHost(bridge, {
16890
- operation: "create",
16891
- requestId: requestId2,
16892
- body: options.body,
16893
- // Drop Desktop OAuth, ChatGPT account identity and cookies at the bridge
16894
- // boundary; the Host allowlist repeats this check before sending.
16895
- headers: filterRealtimeCallHeaders(Object.fromEntries(new Headers(options.headers)))
16896
- })
16897
- );
16898
- if (options.signal?.aborted) {
16899
- const callId = realtimeCallIdFromLocation(result.headers.location ?? "");
16900
- if (result.status >= 200 && result.status < 300 && isRealtimeCallId(callId))
16901
- await requestHost(bridge, { operation: "hangup", callId });
16902
- options.signal.throwIfAborted();
16903
- }
16904
- const response = new Response(result.status === 204 ? null : result.body, {
16905
- status: result.status,
16906
- headers: result.headers
16907
- });
16908
- if (!response.ok) throw await responseError(response, url2);
16909
- return response;
16910
- } catch (error51) {
16911
- cancel();
16912
- throw error51;
16913
- } finally {
16914
- options.signal?.removeEventListener("abort", cancel);
16915
- }
16916
- };
16917
- client.fetch = wrapped;
16918
- return () => {
16919
- if (client.fetch === wrapped) client.fetch = original;
16920
- };
16921
- }
16922
- function installClientOwnedVoiceHook(options = {}) {
16923
- const target = options.target ?? globalThis;
16924
- const existing = target[HOOK_GLOBAL];
16925
- if (existing) return existing;
16926
- const config2 = readCpaLiveVoiceHookConfig(target);
16927
- if (!config2) return { status: "disabled", dispose() {
16928
- } };
16929
- if (config2.desktopVersion !== void 0 && config2.desktopVersion !== CLIENT_OWNED_VOICE_VERSION)
16930
- return { status: "unsupported", dispose() {
16931
- } };
16932
- const importModule = options.importModule ?? ((url2) => import(url2));
16933
- let disposed = false;
16934
- let timer;
16935
- let restore;
16936
- const started = Date.now();
16937
- const handle = {
16938
- status: "pending",
16939
- dispose() {
16940
- disposed = true;
16941
- if (timer !== void 0) target.clearTimeout(timer);
16942
- restore?.();
16943
- if (target[HOOK_GLOBAL] === handle) Reflect.deleteProperty(target, HOOK_GLOBAL);
16944
- }
16945
- };
16946
- target[HOOK_GLOBAL] = handle;
16947
- const attempt = async () => {
16948
- if (disposed) return;
16949
- const names = loadedAssetNames(target.document, target.performance);
16950
- if (auditedAppInitialAssetPresent(names, CLIENT_OWNED_VOICE_INITIAL_ASSET) && auditedAppInitialAssetPresent(names, CLIENT_OWNED_VOICE_SHARED_ASSET)) {
16951
- try {
16952
- const module = await importModule(`app://-/assets/${CLIENT_OWNED_VOICE_SHARED_ASSET}`);
16953
- if (disposed) return;
16954
- if (typeof module.frt !== "function") throw new Error();
16955
- module.frt();
16956
- const fetchClass = module.urt;
16957
- const bridgeClass = module.mPt;
16958
- if (!object2(fetchClass) && typeof fetchClass !== "function") throw new Error();
16959
- if (!object2(bridgeClass) && typeof bridgeClass !== "function") throw new Error();
16960
- const client = Reflect.get(fetchClass, "getInstance").call(fetchClass);
16961
- const bridge = Reflect.get(bridgeClass, "getInstance").call(bridgeClass);
16962
- if (typeof client?.fetch !== "function" || typeof bridge?.dispatchMessage !== "function" || typeof bridge?.subscribe !== "function" || typeof module.grt !== "function")
16963
- throw new Error();
16964
- restore = wrapClientOwnedVoiceFetch(
16965
- client,
16966
- bridge,
16967
- () => cpaLiveVoicePreferenceAllowsInstall(target),
16968
- module.grt,
16969
- () => chatLiveVoicePreferenceAllowsOverride(target),
16970
- () => isWorkComposerSurface(target),
16971
- () => isExternalHarnessComposer(target)
16972
- );
16973
- handle.status = "active";
16974
- } catch {
16975
- handle.status = "unsupported";
16976
- }
16977
- return;
16978
- }
16979
- if (Date.now() - started >= 6e4) {
16980
- handle.status = "unsupported";
16981
- return;
16982
- }
16983
- timer = target.setTimeout(() => void attempt(), 250);
16984
- };
16985
- void attempt();
16986
- return handle;
16987
- }
16988
-
16989
- // src/renderer-native-chat-voice-state.ts
16990
- function observeNativeChatVoiceState(scope, initial) {
16991
- const store = Reflect.apply(initial.ejn, void 0, [
16992
- scope
16993
- ]);
16994
- let last = "";
16995
- const report = () => {
16996
- const state = store.getState();
16997
- const connection = state.server?.connectionState;
16998
- const microphone = initial.sUn == null ? void 0 : scope.get(initial.sUn);
16999
- const detail = {
17000
- active: state.isVoiceModeActive === true,
17001
- connectedOnce: state.hasConnectedOnce === true,
17002
- sessionStarted: state.hasVoiceSessionStarted === true,
17003
- serverConversationReady: typeof state.earlyServerThreadId === "string" && /^[a-f\d]{8}(?:-[a-f\d]{4}){3}-[a-f\d]{12}$/i.test(state.earlyServerThreadId),
17004
- connection: typeof connection === "string" && ["disconnected", "connecting", "connected", "reconnecting", "signalReconnecting"].includes(
17005
- connection
17006
- ) ? connection : "unknown",
17007
- microphone: typeof microphone === "string" && ["unknown", "pending", "granted", "denied"].includes(microphone) ? microphone : "unknown"
17008
- };
17009
- const next = JSON.stringify(detail);
17010
- if (next === last) return;
17011
- last = next;
17012
- chatVoiceDebug("native-chat-state", detail);
17013
- };
17014
- const unsubscribe = store.subscribe(report);
17015
- report();
17016
- return unsubscribe;
17017
- }
17018
-
17019
- // src/renderer-native-chat-voice-selection.ts
17020
- var UUID = /^[a-f\d]{8}(?:-[a-f\d]{4}){3}-[a-f\d]{12}$/i;
17021
- function nativeChatVoiceDraftSelection(binding, initial, shared, origin, signal) {
17022
- const draft = binding.props.conversationId;
17023
- const navigate = initial.uIr;
17024
- const conversationPath = shared.xat;
17025
- if (binding.backendConversationId !== void 0 || !draft.startsWith("local-chatgpt:") || !UUID.test(draft.slice("local-chatgpt:".length)) || origin == null || initial.FFr == null || initial.kZn == null || initial.SZn == null || typeof navigate !== "function" || typeof conversationPath !== "function")
17026
- return () => {
17027
- };
17028
- const store = Reflect.apply(initial.ejn, void 0, [
17029
- binding.scope
17030
- ]);
17031
- let session = store.getState().voiceSessionId;
17032
- let activeSeen = store.getState().isVoiceModeActive;
17033
- let finished = false;
17034
- return () => {
17035
- if (finished) return;
17036
- try {
17037
- const state = store.getState();
17038
- if (signal.aborted || binding.scope.get(initial.FFr) !== origin || activeSeen && (!state.isVoiceModeActive || state.voiceSessionId !== session)) {
17039
- finished = true;
17040
- return;
17041
- }
17042
- if (!state.isVoiceModeActive) return;
17043
- if (!activeSeen) {
17044
- session = state.voiceSessionId;
17045
- activeSeen = true;
17046
- }
17047
- if (state.isWorkMode !== false || state.conversationId !== draft) {
17048
- finished = true;
17049
- return;
17050
- }
17051
- if (!state.hasVoiceSessionStarted || state.server.connectionState !== "connected") return;
17052
- const backend = binding.scope.get(initial.pZn, draft);
17053
- if (typeof backend !== "string" || !UUID.test(backend)) return;
17054
- const mapping = binding.scope.get(initial.kZn, draft);
17055
- const currentNode = binding.scope.get(initial.SZn, draft);
17056
- if (mapping == null || typeof mapping !== "object" || typeof currentNode !== "string" || !mapping[currentNode]?.message)
17057
- return;
17058
- finished = true;
17059
- const path = Reflect.apply(conversationPath, void 0, [draft]);
17060
- const accepted = Reflect.apply(navigate, void 0, [binding.scope, path]);
17061
- void Promise.resolve(accepted).then(
17062
- (result) => chatVoiceDebug(
17063
- result === false ? "native-chat-selection-declined" : "native-chat-draft-selection-requested"
17064
- ),
17065
- () => chatVoiceDebug("native-chat-selection-failed")
17066
- );
17067
- } catch {
17068
- finished = true;
17069
- chatVoiceDebug("native-chat-selection-failed");
17070
- }
17071
- };
17072
- }
17073
-
17074
- // src/renderer-native-chat-voice.ts
17075
- var HOOK_GLOBAL2 = "__codexhostNativeChatVoiceV1";
17076
- var START_ASSET = "start-voice-954090654801.js";
17077
- var PREWARM_ASSET = "voice-prewarm-fb2c91219553.js";
17078
- var BUTTON = "data-codexhost-chat-voice-start";
17079
- var CONVERSATION_UUID = /^[a-f\d]{8}(?:-[a-f\d]{4}){3}-[a-f\d]{12}$/i;
17080
- var record2 = (value) => value !== null && typeof value === "object";
17081
- function scopeRefs(owner, definition) {
17082
- const scopes = /* @__PURE__ */ new Map();
17083
- let hook = owner.memoizedState;
17084
- const seen = /* @__PURE__ */ new Set();
17085
- while (record2(hook) && seen.size < 4096 && !seen.has(hook)) {
17086
- seen.add(hook);
17087
- const state = hook.memoizedState;
17088
- const scope = record2(state) ? state.current : void 0;
17089
- if (record2(scope) && scope.scope === definition && typeof scope.get === "function" && typeof scope.set === "function" && scope.chain instanceof Map && scope.chain.get(definition.id) === scope.node)
17090
- scopes.set(scope.node, scope);
17091
- hook = hook.next;
17092
- }
17093
- return hook == null ? scopes : /* @__PURE__ */ new Map();
17094
- }
17095
- function detectNativeChatVoiceBinding(composer, native) {
17096
- const key = Object.getOwnPropertyNames(composer).find((key2) => key2.startsWith("__reactFiber$"));
17097
- if (!key) return;
17098
- const ancestors = committedReactAncestors(Object.getOwnPropertyDescriptor(composer, key)?.value);
17099
- for (const owner of ancestors) {
17100
- const props = owner.memoizedProps;
17101
- if (!record2(props) || typeof props.isWorkConversation !== "boolean" || typeof props.isTemporaryChat !== "boolean" || typeof props.conversationId !== "string")
17102
- continue;
17103
- if (props.isWorkConversation || props.isTemporaryChat || props.shouldBlockExternalEgress !== false || props.readOnly === true || !record2(props.composerSelectionContext) || props.composerSelectionContext.conversationId !== props.conversationId)
17104
- return;
17105
- const scopes = scopeRefs(owner, native.scopeDefinition);
17106
- if (scopes.size !== 1) return;
17107
- const scope = [...scopes.values()][0];
17108
- if (!scope) return;
17109
- const backendId = scope.get(native.backendConversationId, props.conversationId);
17110
- const hasBackendId = typeof backendId === "string" && CONVERSATION_UUID.test(backendId);
17111
- const isDraft = backendId == null && props.conversationId.startsWith("local-chatgpt:") && CONVERSATION_UUID.test(props.conversationId.slice("local-chatgpt:".length));
17112
- if (!hasBackendId && !isDraft) return;
17113
- const draftScopes = /* @__PURE__ */ new Map();
17114
- if (isDraft) {
17115
- for (const fiber of ancestors)
17116
- for (const [node, ref] of scopeRefs(fiber, native.composerScopeDefinition))
17117
- if (ref.chain.get(native.scopeDefinition.id) === scope.node) draftScopes.set(node, ref);
17453
+ try {
17454
+ const parsed = JSON.parse(storage.getItem(RENDERER_NEW_THREAD_PREFERENCE_KEY) ?? "");
17455
+ return typeof parsed.lastAgent === "string" && parsed.lastAgent !== "" && parsed.lastAgent !== "codex";
17456
+ } catch {
17457
+ return false;
17458
+ }
17459
+ }
17460
+ function wrapClientOwnedVoiceFetch(client, bridge, workOverride, responseError, chatOverride = () => false, workSurface = () => isWorkComposerSurface(), externalHarness = () => isExternalHarnessComposer()) {
17461
+ const original = client.fetch;
17462
+ const wrapped = async function(url2, options) {
17463
+ if (typeof url2 !== "string" || url2.split("?")[0] !== "/wham/realtime/calls" || options?.method !== "POST")
17464
+ return original.call(this, url2, options);
17465
+ const experience = lastVoiceLaunchProductExperience();
17466
+ const chat = experience === "chat";
17467
+ const model2 = backendModelName(options.headers);
17468
+ const work = !chat && (experience === "work" || isWorkBackendModel(model2) || workSurface());
17469
+ const external = !chat && (model2.startsWith("codexhost/") || externalHarness());
17470
+ const gateway = external || (work ? workOverride() : chatOverride());
17471
+ chatVoiceDebug("wham-create", {
17472
+ work,
17473
+ external,
17474
+ route: gateway ? "gateway" : "desktop",
17475
+ productExperience: experience ?? "unset"
17476
+ });
17477
+ if (!gateway) {
17478
+ if (!external && !work)
17479
+ chatVoiceDebug("chat-native-passthrough", {
17480
+ hasThreadId: new Headers(options.headers).has("Thread-Id")
17481
+ });
17482
+ return original.call(this, url2, options);
17118
17483
  }
17119
- const contexts = /* @__PURE__ */ new Set();
17120
- let composerContext;
17121
- for (const fiber of ancestors) {
17122
- let dependency = record2(fiber.dependencies) ? fiber.dependencies.firstContext : void 0;
17123
- const seenDependencies = /* @__PURE__ */ new Set();
17124
- while (record2(dependency) && seenDependencies.size < 4096 && !seenDependencies.has(dependency)) {
17125
- seenDependencies.add(dependency);
17126
- const context = dependency.context;
17127
- const chain = dependency.memoizedValue;
17128
- if (record2(context) && context.displayName === native.scopeDefinition.__scopeBrand && chain instanceof Map && chain.get(native.scopeDefinition.id) === scope.node)
17129
- contexts.add(context);
17130
- if (!composerContext && record2(context) && context.displayName === native.composerScopeDefinition.__scopeBrand && chain instanceof Map && chain.get(native.scopeDefinition.id) === scope.node) {
17131
- const node = chain.get(native.composerScopeDefinition.id);
17132
- if (record2(node) && record2(node.value) && (node.value.kind === "chatgpt" && record2(node.value.chatGptSelectionContext) && node.value.chatGptSelectionContext.conversationId === props.conversationId || isDraft && node.value.kind === "new" && native.draftConversationId != null && draftScopes.get(node)?.get(native.draftConversationId) === props.conversationId))
17133
- composerContext = { context, chain };
17134
- else return;
17135
- }
17136
- dependency = dependency.next;
17484
+ if (typeof options.body !== "string") throw new Error("Unsupported native voice call request");
17485
+ options.signal?.throwIfAborted();
17486
+ const requestId2 = crypto.randomUUID();
17487
+ let cancelled = false;
17488
+ const cancel = () => {
17489
+ if (cancelled) return;
17490
+ cancelled = true;
17491
+ void requestVoiceHost(bridge, { operation: "cancel", requestId: requestId2 }).catch(() => void 0);
17492
+ };
17493
+ options.signal?.addEventListener("abort", cancel, { once: true });
17494
+ try {
17495
+ const result = realtimeCallResponseSchema.parse(
17496
+ await requestVoiceHost(bridge, {
17497
+ operation: "create",
17498
+ requestId: requestId2,
17499
+ body: options.body,
17500
+ // Drop Desktop OAuth, ChatGPT account identity and cookies at the bridge
17501
+ // boundary; the Host allowlist repeats this check before sending.
17502
+ headers: filterRealtimeCallHeaders(Object.fromEntries(new Headers(options.headers)))
17503
+ })
17504
+ );
17505
+ if (options.signal?.aborted) {
17506
+ const callId = realtimeCallIdFromLocation(result.headers.location ?? "");
17507
+ if (result.status >= 200 && result.status < 300 && isRealtimeCallId(callId))
17508
+ await requestVoiceHost(bridge, { operation: "hangup", callId });
17509
+ options.signal.throwIfAborted();
17137
17510
  }
17511
+ const response = new Response(result.status === 204 ? null : result.body, {
17512
+ status: result.status,
17513
+ headers: result.headers
17514
+ });
17515
+ if (!response.ok) throw await responseError(response, url2);
17516
+ return response;
17517
+ } catch (error51) {
17518
+ cancel();
17519
+ throw error51;
17520
+ } finally {
17521
+ options.signal?.removeEventListener("abort", cancel);
17138
17522
  }
17139
- if (contexts.size !== 1 || !composerContext) return;
17140
- return {
17141
- composer,
17142
- props,
17143
- scope,
17144
- context: [...contexts][0],
17145
- composerContext,
17146
- backendConversationId: hasBackendId ? backendId : void 0
17147
- };
17148
- }
17149
- }
17150
- function startNativeChatVoiceForRequest(request, target = globalThis) {
17151
- if (!record2(request) || request.productExperience !== "chat" || !nativeChatVoicePreferenceAllowsStart(target))
17152
- return;
17153
- const hook = target[HOOK_GLOBAL2];
17154
- if (!hook || hook.status === "disabled") return;
17155
- return hook.start();
17523
+ };
17524
+ client.fetch = wrapped;
17525
+ return () => {
17526
+ if (client.fetch === wrapped) client.fetch = original;
17527
+ };
17156
17528
  }
17157
- function installNativeChatVoiceHook(options = {}) {
17529
+ function installClientOwnedVoiceHook(options = {}) {
17158
17530
  const target = options.target ?? globalThis;
17159
17531
  const existing = target[HOOK_GLOBAL2];
17160
17532
  if (existing) return existing;
17161
17533
  const config2 = readCpaLiveVoiceHookConfig(target);
17162
- const unavailable = (status) => ({
17163
- status,
17164
- dispose() {
17165
- },
17166
- start: async () => {
17167
- throw new Error("Native Chat voice is unavailable");
17168
- }
17169
- });
17170
- if (!config2) return unavailable("disabled");
17171
- if (config2.desktopVersion !== void 0 && config2.desktopVersion !== CLIENT_OWNED_VOICE_VERSION)
17172
- return unavailable("unsupported");
17173
- const doc = target.document;
17174
- if (!doc) return unavailable("unsupported");
17534
+ if (!config2) return { status: "disabled", dispose() {
17535
+ } };
17536
+ if (!supportsVoiceVersion(CLIENT_OWNED_VOICE_ASSETS, config2.desktopVersion))
17537
+ return { status: "unsupported", dispose() {
17538
+ } };
17175
17539
  const importModule = options.importModule ?? ((url2) => import(url2));
17176
17540
  let disposed = false;
17177
17541
  let timer;
17178
- let modules;
17179
- let boundScope;
17180
- let mounted;
17181
- let button2;
17182
- const overlayButtons = /* @__PURE__ */ new Map();
17183
- let controller;
17184
- let starting = false;
17185
- let selectDraft;
17186
- let renderFailed = false;
17542
+ let restore;
17187
17543
  const started = Date.now();
17188
- const removeButton = () => {
17189
- button2?.remove();
17190
- button2 = void 0;
17191
- };
17192
- const release = () => {
17193
- selectDraft = void 0;
17194
- controller?.abort();
17195
- controller = void 0;
17196
- removeButton();
17197
- for (const [control, [display, priority]] of overlayButtons)
17198
- if (control.style.getPropertyValue("display") === "none" && control.style.getPropertyPriority("display") === "important")
17199
- control.style.setProperty("display", display, priority);
17200
- overlayButtons.clear();
17201
- mounted?.dispose();
17202
- mounted = void 0;
17203
- boundScope = void 0;
17204
- };
17205
- const findBinding = () => {
17206
- if (!modules || !nativeChatVoicePreferenceAllowsStart(target)) return;
17207
- const forms = [...doc.querySelectorAll("[data-chatgpt-composer]")].filter(
17208
- (form2) => form2.getBoundingClientRect().width > 0 && !form2.closest("[hidden], [inert]")
17209
- );
17210
- if (forms.length !== 1) return;
17211
- const form = forms[0];
17212
- if (!form) return;
17213
- return detectNativeChatVoiceBinding(form, {
17214
- scopeDefinition: modules.shared.Cot,
17215
- backendConversationId: modules.initial.pZn,
17216
- draftConversationId: modules.initial.lXt,
17217
- composerScopeDefinition: modules.initial.xAt.scope
17218
- });
17219
- };
17220
17544
  const handle = {
17221
- get activeCall() {
17222
- if (starting) return true;
17223
- if (!boundScope || !modules || typeof modules.initial.ejn !== "function") return false;
17224
- try {
17225
- return modules.initial.ejn(boundScope).getState().isVoiceModeActive;
17226
- } catch {
17227
- return false;
17228
- }
17229
- },
17230
17545
  status: "pending",
17231
- async start() {
17232
- if (disposed || starting) return;
17233
- const current = findBinding();
17234
- if (!current || !modules || current.scope.node !== mounted?.node)
17235
- throw new Error("Select a ChatGPT conversation to start Chat voice");
17236
- starting = true;
17237
- controller = new AbortController();
17238
- const signal = controller.signal;
17239
- let origin;
17240
- try {
17241
- if (modules.initial.FFr != null) origin = current.scope.get(modules.initial.FFr);
17242
- } catch {
17243
- chatVoiceDebug("native-chat-selection-failed");
17244
- }
17245
- selectDraft = void 0;
17246
- chatVoiceDebug("native-chat-start", {
17247
- hasConversationId: current.backendConversationId !== void 0
17248
- });
17249
- try {
17250
- await Reflect.apply(modules.start.n, void 0, [
17251
- current.scope,
17252
- current.props,
17253
- { event_source: "composer" },
17254
- signal
17255
- ]);
17256
- chatVoiceDebug("native-chat-start-returned");
17257
- try {
17258
- selectDraft = nativeChatVoiceDraftSelection(
17259
- current,
17260
- modules.initial,
17261
- modules.shared,
17262
- origin,
17263
- signal
17264
- );
17265
- selectDraft();
17266
- } catch {
17267
- chatVoiceDebug("native-chat-selection-failed");
17268
- }
17269
- } catch {
17270
- chatVoiceDebug("native-chat-start-failed");
17271
- throw new Error("Native Chat voice could not start");
17272
- } finally {
17273
- starting = false;
17274
- }
17275
- },
17276
17546
  dispose() {
17277
17547
  disposed = true;
17278
17548
  if (timer !== void 0) target.clearTimeout(timer);
17279
- release();
17549
+ restore?.();
17280
17550
  if (target[HOOK_GLOBAL2] === handle) Reflect.deleteProperty(target, HOOK_GLOBAL2);
17281
17551
  }
17282
17552
  };
17283
17553
  target[HOOK_GLOBAL2] = handle;
17284
- const refresh = async () => {
17554
+ const attempt = async () => {
17285
17555
  if (disposed) return;
17286
- try {
17287
- if (renderFailed) throw new Error();
17288
- if (!modules) {
17289
- const names = loadedAssetNames(doc, target.performance);
17290
- if (!auditedAppInitialAssetPresent(names, CLIENT_OWNED_VOICE_INITIAL_ASSET) || !auditedAppInitialAssetPresent(names, CLIENT_OWNED_VOICE_SHARED_ASSET)) {
17291
- if (Date.now() - started >= 6e4) {
17292
- handle.status = "unsupported";
17293
- return;
17294
- }
17295
- return;
17296
- }
17297
- const [shared, initial, start, prewarm] = await Promise.all(
17298
- [
17299
- CLIENT_OWNED_VOICE_SHARED_ASSET,
17300
- CLIENT_OWNED_VOICE_INITIAL_ASSET,
17301
- START_ASSET,
17302
- PREWARM_ASSET
17303
- ].map((asset) => importModule(`app://-/assets/${asset}`))
17304
- );
17556
+ const names = loadedAssetNames(target.document, target.performance);
17557
+ const assets = selectVoiceAssets(CLIENT_OWNED_VOICE_ASSETS, names, config2.desktopVersion);
17558
+ if (assets) {
17559
+ try {
17560
+ const module = await importModule(`app://-/assets/${assets.shared}`);
17305
17561
  if (disposed) return;
17306
- if (!shared || !initial || !start || !prewarm || typeof start.t !== "function" || typeof start.n !== "function" || typeof initial.jw !== "function" || typeof initial.ejn !== "function" || typeof shared.yzt !== "function" || typeof shared.vMt !== "function" || typeof prewarm.VoiceInstantConnectCacheController !== "function")
17307
- throw new Error();
17308
- initial.jw();
17309
- start.t();
17310
- if (!record2(shared.Cot) || !initial.pZn || !initial.lXt || !initial.Aw || !record2(initial.xAt) || !record2(initial.xAt.scope))
17562
+ if (typeof module[SHARED_INIT] !== "function") throw new Error();
17563
+ module[SHARED_INIT]();
17564
+ const fetchClass = module[SHARED_FETCH];
17565
+ const bridgeClass = module[SHARED_BRIDGE];
17566
+ if (!object2(fetchClass) && typeof fetchClass !== "function") throw new Error();
17567
+ if (!object2(bridgeClass) && typeof bridgeClass !== "function") throw new Error();
17568
+ const client = Reflect.get(fetchClass, "getInstance").call(fetchClass);
17569
+ const bridge = Reflect.get(bridgeClass, "getInstance").call(bridgeClass);
17570
+ if (typeof client?.fetch !== "function" || typeof bridge?.dispatchMessage !== "function" || typeof bridge?.subscribe !== "function" || typeof module[SHARED_RESPONSE_ERROR] !== "function")
17311
17571
  throw new Error();
17312
- modules = { shared, initial, start, prewarm };
17313
- }
17314
- const current = findBinding();
17315
- selectDraft?.();
17316
- if (!current) {
17317
- if (handle.activeCall) return;
17318
- release();
17319
- handle.status = "pending";
17320
- return;
17321
- }
17322
- if (mounted && current.scope.node !== mounted.node) release();
17323
- boundScope = current.scope;
17324
- if (!mounted) {
17325
- const { scope } = current;
17326
- const initial = modules.initial;
17327
- const previous = scope.get(initial.Aw);
17328
- const container = doc.createElement("div");
17329
- container.setAttribute("data-codexhost-chat-voice-prewarm", "");
17330
- doc.body.append(container);
17331
- const react = Reflect.apply(modules.shared.yzt, void 0, []);
17332
- const reactDom = Reflect.apply(modules.shared.vMt, void 0, []);
17333
- const root = reactDom.createRoot(container, {
17334
- onUncaughtError() {
17335
- renderFailed = true;
17336
- }
17337
- });
17338
- const stopObserving = observeNativeChatVoiceState(scope, initial);
17339
- let appChain;
17340
- let composerChain;
17341
- mounted = {
17342
- node: scope.node,
17343
- render(next) {
17344
- if (appChain === next.scope.chain && composerChain === next.composerContext.chain)
17345
- return;
17346
- appChain = next.scope.chain;
17347
- composerChain = next.composerContext.chain;
17348
- root.render(
17349
- react.createElement(
17350
- next.context,
17351
- { value: appChain },
17352
- react.createElement(
17353
- next.composerContext.context,
17354
- { value: composerChain },
17355
- react.createElement(modules?.prewarm.VoiceInstantConnectCacheController, null)
17356
- )
17357
- )
17358
- );
17359
- },
17360
- dispose() {
17361
- stopObserving();
17362
- root.unmount();
17363
- container.remove();
17364
- if (scope.get(initial.Aw) === true) scope.set(initial.Aw, previous);
17365
- chatVoiceDebug("native-chat-unbound");
17366
- }
17367
- };
17368
- scope.set(initial.Aw, true);
17369
- chatVoiceDebug("native-chat-bound", {
17370
- hasConversationId: current.backendConversationId !== void 0
17371
- });
17372
- }
17373
- mounted.render(current);
17374
- if (!button2 || !current.composer.contains(button2)) {
17375
- removeButton();
17376
- const dictate = current.composer.querySelector(
17377
- 'button[aria-label="Dictate"]'
17572
+ restore = wrapClientOwnedVoiceFetch(
17573
+ client,
17574
+ bridge,
17575
+ () => cpaLiveVoicePreferenceAllowsInstall(target),
17576
+ module[SHARED_RESPONSE_ERROR],
17577
+ () => chatLiveVoicePreferenceAllowsOverride(target),
17578
+ () => isWorkComposerSurface(target),
17579
+ () => isExternalHarnessComposer(target)
17378
17580
  );
17379
- if (dictate?.parentElement) {
17380
- button2 = doc.createElement("button");
17381
- button2.type = "button";
17382
- button2.className = dictate.className;
17383
- button2.setAttribute(BUTTON, "");
17384
- button2.setAttribute("aria-label", "Start Voice");
17385
- button2.title = "Start Voice";
17386
- button2.innerHTML = '<svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true"><path d="M3 8v4m4-7v10m6-12v14m4-9v4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>';
17387
- button2.addEventListener("click", () => {
17388
- void handle.start().catch(() => {
17389
- if (button2) button2.title = "Chat voice could not start. Try again.";
17390
- });
17391
- });
17392
- dictate.parentElement.append(button2);
17393
- }
17581
+ handle.status = "active";
17582
+ } catch {
17583
+ handle.status = "unsupported";
17394
17584
  }
17395
- if (button2)
17396
- for (const control of current.composer.querySelectorAll(
17397
- 'button[aria-label="Start new voice chat"], button[aria-label="Start voice chat"]'
17398
- )) {
17399
- if (!overlayButtons.has(control))
17400
- overlayButtons.set(control, [
17401
- control.style.getPropertyValue("display"),
17402
- control.style.getPropertyPriority("display")
17403
- ]);
17404
- control.style.setProperty("display", "none", "important");
17405
- }
17406
- if (button2)
17407
- button2.disabled = starting || current.props.isStreaming === true || modules.initial.ejn instanceof Function && modules.initial.ejn(current.scope).getState().isVoiceModeActive;
17408
- handle.status = "active";
17409
- } catch {
17410
- release();
17585
+ return;
17586
+ }
17587
+ if (Date.now() - started >= 6e4) {
17411
17588
  handle.status = "unsupported";
17412
- chatVoiceDebug("native-chat-binding-failed");
17413
17589
  return;
17414
- } finally {
17415
- if (!disposed && handle.status !== "unsupported")
17416
- timer = target.setTimeout(() => void refresh(), 500);
17417
17590
  }
17591
+ timer = target.setTimeout(() => void attempt(), 250);
17418
17592
  };
17419
- void refresh();
17593
+ void attempt();
17420
17594
  return handle;
17421
17595
  }
17422
17596
 
@@ -17439,13 +17613,17 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17439
17613
  if (busy || disposed) return;
17440
17614
  busy = true;
17441
17615
  try {
17616
+ const activeCall = desktopVoiceCallActive(owner);
17442
17617
  let state = voiceSettingsStateSchema.parse(
17443
17618
  await request(VOICE_SETTINGS_METHOD, {
17444
17619
  operation: "get",
17445
- runtime: { id, revision, activeCall: desktopVoiceCallActive(owner) }
17620
+ runtime: { id, revision, activeCall }
17446
17621
  })
17447
17622
  );
17448
17623
  if (disposed) return;
17624
+ const ready = state.gatewayReady ?? await request(VOICE_GATEWAY_STATUS_METHOD, {});
17625
+ if (disposed) return;
17626
+ applyVoiceGatewayReadiness(owner, ready === true);
17449
17627
  if (!state.initialized)
17450
17628
  state = voiceSettingsStateSchema.parse(
17451
17629
  await request(VOICE_SETTINGS_METHOD, {
@@ -17453,7 +17631,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17453
17631
  routes: legacyVoiceRoutes(owner)
17454
17632
  })
17455
17633
  );
17456
- if (!disposed && !desktopVoiceCallActive(owner)) {
17634
+ if (!disposed && !activeCall && state.revision !== revision) {
17457
17635
  applySharedVoiceRoutes(owner, state.settings);
17458
17636
  revision = state.revision;
17459
17637
  }
@@ -17466,6 +17644,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17466
17644
  const timer = owner.setInterval(() => void refresh(), intervalMs);
17467
17645
  return () => {
17468
17646
  disposed = true;
17647
+ applyVoiceGatewayReadiness(owner, false);
17469
17648
  owner.clearInterval(timer);
17470
17649
  };
17471
17650
  }
@@ -18520,6 +18699,242 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18520
18699
  return section;
18521
18700
  }
18522
18701
 
18702
+ // src/settings/preference-ui.ts
18703
+ var nextPreferenceId = 0;
18704
+ function preferenceId(prefix) {
18705
+ nextPreferenceId += 1;
18706
+ return `codexhost-${prefix}-${nextPreferenceId}`;
18707
+ }
18708
+ function createPreferenceGroup(document2, title) {
18709
+ const group = document2.createElement("section");
18710
+ group.className = "mt-7 first-of-type:mt-0";
18711
+ const header = document2.createElement("div");
18712
+ header.className = "mb-2 flex min-h-6 items-center gap-2 px-1";
18713
+ const heading = document2.createElement("h3");
18714
+ heading.id = preferenceId("preference-group");
18715
+ heading.className = "m-0 text-[13px] leading-5 font-semibold text-settings-text";
18716
+ heading.textContent = title;
18717
+ header.append(heading);
18718
+ const card = document2.createElement("div");
18719
+ card.className = "rounded-[10px] border border-settings-border bg-settings-panel";
18720
+ group.setAttribute("aria-labelledby", heading.id);
18721
+ group.append(header, card);
18722
+ return { group, header, card };
18723
+ }
18724
+ function createPreferenceItem(document2, input) {
18725
+ const item = document2.createElement("div");
18726
+ item.className = "flex min-h-[60px] items-center justify-between gap-6 border-t border-settings-divider px-4 py-3 first:border-t-0";
18727
+ const copy = document2.createElement("div");
18728
+ copy.className = "flex min-w-0 flex-col gap-0.5";
18729
+ const heading = document2.createElement("div");
18730
+ heading.className = "flex items-center gap-1.5";
18731
+ const title = document2.createElement("label");
18732
+ title.htmlFor = input.controlId;
18733
+ title.className = "cursor-pointer text-[13px] leading-5 font-medium text-settings-text";
18734
+ title.textContent = input.title;
18735
+ heading.append(title);
18736
+ if (input.help) heading.append(createHelpTooltip(document2, input.help.label, input.help.lines));
18737
+ const description = document2.createElement("div");
18738
+ description.id = preferenceId("preference-description");
18739
+ description.className = "text-xs leading-[18px] text-settings-muted data-[invalid=true]:text-settings-danger";
18740
+ description.textContent = input.description;
18741
+ copy.append(heading, description);
18742
+ item.append(copy);
18743
+ return { item, description };
18744
+ }
18745
+ function createHelpTooltip(document2, label, lines) {
18746
+ const wrapper = document2.createElement("span");
18747
+ wrapper.className = "group relative inline-flex";
18748
+ const button2 = document2.createElement("button");
18749
+ button2.type = "button";
18750
+ button2.className = "inline-flex size-4 cursor-help items-center justify-center rounded-full border-0 bg-transparent p-0 text-settings-subtle transition-colors hover:text-settings-text focus-visible:text-settings-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-settings-focus";
18751
+ button2.setAttribute("aria-label", label);
18752
+ button2.append(createRendererSettingsIcon("help", 14));
18753
+ const tooltip = document2.createElement("div");
18754
+ tooltip.id = preferenceId("preference-tooltip");
18755
+ tooltip.setAttribute("role", "tooltip");
18756
+ tooltip.className = "pointer-events-none invisible absolute top-full left-0 z-20 mt-2 w-80 rounded-lg border border-settings-border bg-settings-panel px-3 py-2.5 text-xs leading-[18px] font-normal text-settings-text opacity-0 shadow-[0_10px_30px_rgb(0_0_0/0.22)] transition-opacity duration-150 group-hover:visible group-hover:opacity-100 group-focus-within:visible group-focus-within:opacity-100";
18757
+ const list = document2.createElement("ul");
18758
+ list.className = "m-0 grid list-disc gap-1 pl-4 marker:text-settings-subtle";
18759
+ for (const line of lines) {
18760
+ const item = document2.createElement("li");
18761
+ item.textContent = line;
18762
+ list.append(item);
18763
+ }
18764
+ tooltip.append(list);
18765
+ button2.setAttribute("aria-describedby", tooltip.id);
18766
+ button2.addEventListener("keydown", (event) => {
18767
+ if (event.key === "Escape") button2.blur();
18768
+ });
18769
+ wrapper.append(button2, tooltip);
18770
+ return wrapper;
18771
+ }
18772
+ function createPreferenceSwitch(document2, id, describedBy) {
18773
+ const input = document2.createElement("input");
18774
+ input.type = "checkbox";
18775
+ input.id = id;
18776
+ input.setAttribute("role", "switch");
18777
+ input.setAttribute("aria-describedby", describedBy);
18778
+ input.className = "relative m-0 h-5 w-9 shrink-0 cursor-pointer appearance-none rounded-full bg-settings-surface-hover ring-1 ring-settings-border transition-colors ring-inset before:absolute before:top-0.5 before:left-0.5 before:size-4 before:rounded-full before:bg-white before:shadow-[0_1px_2px_rgb(0_0_0/0.3)] before:transition-transform checked:bg-settings-focus checked:ring-transparent checked:before:translate-x-4 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-settings-focus";
18779
+ return input;
18780
+ }
18781
+ function createNumberField(document2, input) {
18782
+ const field = document2.createElement("div");
18783
+ field.className = "flex h-8 shrink-0 items-center gap-1.5 rounded-md border border-settings-border bg-settings-surface px-2.5 transition-colors focus-within:border-settings-focus data-[invalid=true]:border-settings-danger";
18784
+ const control = document2.createElement("input");
18785
+ control.type = "number";
18786
+ control.id = input.id;
18787
+ control.min = String(input.min);
18788
+ control.max = String(input.max);
18789
+ control.step = "1";
18790
+ control.setAttribute("aria-describedby", input.describedBy);
18791
+ control.className = "w-14 border-0 bg-transparent p-0 text-right text-[13px] leading-5 text-settings-text tabular-nums outline-none";
18792
+ const unit = document2.createElement("span");
18793
+ unit.className = "text-xs text-settings-muted";
18794
+ unit.setAttribute("aria-hidden", "true");
18795
+ unit.textContent = input.unit;
18796
+ field.append(control, unit);
18797
+ return { field, control };
18798
+ }
18799
+
18800
+ // src/settings/grok-computer-use-controls.ts
18801
+ function createGrokComputerUseControls(document2, messages, settings) {
18802
+ const section = document2.createElement("section");
18803
+ section.className = "settings-grok-computer-use";
18804
+ section.dataset.grokComputerUse = "true";
18805
+ const header = document2.createElement("div");
18806
+ header.className = "settings-grok-computer-use__header";
18807
+ const copy = document2.createElement("div");
18808
+ copy.className = "settings-grok-computer-use__copy";
18809
+ const title = document2.createElement("h3");
18810
+ title.className = "settings-grok-computer-use__title";
18811
+ title.id = "grok-computer-use-title";
18812
+ title.textContent = messages.grokComputerUseTitle;
18813
+ const description = document2.createElement("p");
18814
+ description.className = "settings-grok-computer-use__description";
18815
+ description.id = "grok-computer-use-description";
18816
+ description.textContent = messages.grokComputerUseDescription;
18817
+ copy.append(title, description);
18818
+ const input = createPreferenceSwitch(
18819
+ document2,
18820
+ "grok-computer-use-enabled",
18821
+ "grok-computer-use-description grok-computer-use-help grok-computer-use-status"
18822
+ );
18823
+ input.setAttribute("aria-labelledby", title.id);
18824
+ input.setAttribute("aria-label", messages.grokComputerUseLabel);
18825
+ header.append(copy, input);
18826
+ const help = document2.createElement("p");
18827
+ help.className = "settings-grok-computer-use__help";
18828
+ help.id = "grok-computer-use-help";
18829
+ help.textContent = messages.grokComputerUseHelp;
18830
+ const status = document2.createElement("p");
18831
+ status.className = "settings-grok-computer-use__status";
18832
+ status.id = "grok-computer-use-status";
18833
+ status.setAttribute("role", "status");
18834
+ status.setAttribute("aria-live", "polite");
18835
+ const browserHeader = document2.createElement("div");
18836
+ browserHeader.className = "settings-grok-computer-use__header settings-grok-computer-use__browser";
18837
+ const browserCopy = document2.createElement("div");
18838
+ browserCopy.className = "settings-grok-computer-use__copy";
18839
+ const browserTitle = document2.createElement("h3");
18840
+ browserTitle.className = "settings-grok-computer-use__title";
18841
+ browserTitle.id = "grok-browser-title";
18842
+ browserTitle.textContent = messages.grokBrowserTitle;
18843
+ const browserDescription = document2.createElement("p");
18844
+ browserDescription.className = "settings-grok-computer-use__description";
18845
+ browserDescription.id = "grok-browser-description";
18846
+ browserDescription.textContent = messages.grokBrowserDescription;
18847
+ browserCopy.append(browserTitle, browserDescription);
18848
+ const browserInput = createPreferenceSwitch(
18849
+ document2,
18850
+ "grok-browser-enabled",
18851
+ "grok-browser-description grok-browser-help grok-browser-status"
18852
+ );
18853
+ browserInput.setAttribute("aria-labelledby", browserTitle.id);
18854
+ browserInput.setAttribute("aria-label", messages.grokBrowserLabel);
18855
+ browserHeader.append(browserCopy, browserInput);
18856
+ const browserHelp = document2.createElement("p");
18857
+ browserHelp.className = "settings-grok-computer-use__help";
18858
+ browserHelp.id = "grok-browser-help";
18859
+ browserHelp.textContent = messages.grokBrowserHelp;
18860
+ const browserStatus = document2.createElement("p");
18861
+ browserStatus.className = "settings-grok-computer-use__status";
18862
+ browserStatus.id = "grok-browser-status";
18863
+ browserStatus.setAttribute("role", "status");
18864
+ browserStatus.setAttribute("aria-live", "polite");
18865
+ section.append(header, help, status, browserHeader, browserHelp, browserStatus);
18866
+ let busy = true;
18867
+ let loaded = false;
18868
+ let last;
18869
+ const show = (value) => {
18870
+ last = value;
18871
+ input.checked = value.enabled;
18872
+ input.disabled = busy || !loaded || !value.available && !value.enabled;
18873
+ const browserOn = value.browser === true;
18874
+ const browserFiles = value.browserFilesPresent === true;
18875
+ browserInput.checked = browserOn;
18876
+ browserInput.disabled = busy || !loaded || !browserFiles && !browserOn;
18877
+ if (!browserFiles) {
18878
+ browserStatus.dataset.state = "unavailable";
18879
+ browserStatus.textContent = value.browserUnavailableReason ?? messages.grokBrowserUnavailable;
18880
+ } else if (value.browserAvailable !== true) {
18881
+ browserStatus.dataset.state = browserOn ? "unavailable" : "off";
18882
+ browserStatus.textContent = browserOn ? value.browserUnavailableReason ?? messages.grokBrowserBlocked : messages.grokBrowserOff;
18883
+ } else {
18884
+ browserStatus.dataset.state = browserOn ? "on" : "off";
18885
+ browserStatus.textContent = browserOn ? messages.grokBrowserOn : messages.grokBrowserOff;
18886
+ }
18887
+ if (value.applyFailed) {
18888
+ status.dataset.state = "unavailable";
18889
+ status.textContent = value.applyError ?? messages.grokComputerUseApplyFailed;
18890
+ return;
18891
+ }
18892
+ if (!value.available) {
18893
+ status.dataset.state = "unavailable";
18894
+ status.textContent = value.unavailableReason ?? messages.grokComputerUseUnavailable;
18895
+ return;
18896
+ }
18897
+ status.dataset.state = value.enabled ? "on" : "off";
18898
+ status.textContent = value.enabled ? messages.grokComputerUseOn : messages.grokComputerUseOff;
18899
+ };
18900
+ const save = (update, target) => {
18901
+ if (busy || !loaded) return;
18902
+ busy = true;
18903
+ input.disabled = true;
18904
+ browserInput.disabled = true;
18905
+ target.textContent = messages.grokComputerUseSaving;
18906
+ void settings.set(update).then((value) => {
18907
+ busy = false;
18908
+ show(value);
18909
+ }).catch(() => {
18910
+ busy = false;
18911
+ if (last) show(last);
18912
+ else {
18913
+ input.disabled = false;
18914
+ browserInput.disabled = false;
18915
+ }
18916
+ target.textContent = messages.grokComputerUseSaveError;
18917
+ });
18918
+ };
18919
+ input.addEventListener("change", () => save({ enabled: input.checked }, status));
18920
+ browserInput.addEventListener(
18921
+ "change",
18922
+ () => save({ browser: browserInput.checked }, browserStatus)
18923
+ );
18924
+ status.textContent = messages.grokComputerUseLoading;
18925
+ browserStatus.textContent = messages.grokBrowserOff;
18926
+ void settings.get().then((value) => {
18927
+ loaded = true;
18928
+ busy = false;
18929
+ show(value);
18930
+ }).catch(() => {
18931
+ busy = false;
18932
+ status.textContent = messages.grokComputerUseLoadError;
18933
+ browserStatus.textContent = messages.grokComputerUseLoadError;
18934
+ });
18935
+ return section;
18936
+ }
18937
+
18523
18938
  // src/settings/harness-installation-guides.ts
18524
18939
  function harnessHasInstallCommands(plugin) {
18525
18940
  return (plugin?.installation?.commands.length ?? 0) > 0;
@@ -18702,7 +19117,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18702
19117
  }
18703
19118
 
18704
19119
  // src/settings/harness-version-panel.ts
18705
- function createHarnessVersionPanel(document2, messages, signal, agent, client) {
19120
+ function createHarnessVersionPanel(document2, messages, signal, agent, client, onChange = () => void 0) {
18706
19121
  const panel = document2.createElement("section");
18707
19122
  panel.className = "settings-harness-version";
18708
19123
  panel.dataset.harnessVersion = agent;
@@ -18728,6 +19143,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18728
19143
  panel.append(heading, versions, status, actions, note);
18729
19144
  let state;
18730
19145
  let busy = false;
19146
+ let updating = false;
18731
19147
  let unsupported = false;
18732
19148
  const render = () => {
18733
19149
  current.textContent = `${messages.harnessVersionCurrent}: ${state?.currentVersion ?? "\u2014"}`;
@@ -18741,6 +19157,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18741
19157
  if (busy || unsupported || signal.aborted) return;
18742
19158
  if (action === "update" && (!state?.canUpdate || !state.updateAvailable)) return;
18743
19159
  busy = true;
19160
+ updating = action === "update";
19161
+ onChange();
18744
19162
  status.textContent = action === "check" ? messages.harnessVersionChecking : messages.harnessVersionUpdating;
18745
19163
  render();
18746
19164
  try {
@@ -18755,7 +19173,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18755
19173
  if (state) state = { ...state, canUpdate: false };
18756
19174
  } finally {
18757
19175
  busy = false;
18758
- if (!signal.aborted) render();
19176
+ updating = false;
19177
+ if (!signal.aborted) {
19178
+ render();
19179
+ onChange();
19180
+ }
18759
19181
  }
18760
19182
  };
18761
19183
  update.addEventListener("click", () => void run("update"));
@@ -18785,7 +19207,43 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18785
19207
  } else {
18786
19208
  void run("check");
18787
19209
  }
18788
- return panel;
19210
+ return {
19211
+ element: panel,
19212
+ get state() {
19213
+ return state;
19214
+ },
19215
+ get updating() {
19216
+ return updating;
19217
+ },
19218
+ update: () => void run("update")
19219
+ };
19220
+ }
19221
+
19222
+ // src/settings/connection-versions.ts
19223
+ function createConnectionVersions(document2, messages, signal, diagnostics, onChange) {
19224
+ const panels = /* @__PURE__ */ new Map();
19225
+ const installation = diagnostics?.installation?.bind(diagnostics);
19226
+ return {
19227
+ get(hostId2, agent) {
19228
+ if (!installation || signal.aborted) return void 0;
19229
+ const key = JSON.stringify([hostId2, agent]);
19230
+ let panel = panels.get(key);
19231
+ if (!panel) {
19232
+ panel = createHarnessVersionPanel(
19233
+ document2,
19234
+ messages,
19235
+ signal,
19236
+ agent,
19237
+ {
19238
+ run: (action) => installation(hostId2, agent, action)
19239
+ },
19240
+ onChange
19241
+ );
19242
+ panels.set(key, panel);
19243
+ }
19244
+ return panel;
19245
+ }
19246
+ };
18789
19247
  }
18790
19248
 
18791
19249
  // src/settings/connections-page.ts
@@ -18953,6 +19411,14 @@ ${error51.stderrTail}`] : []
18953
19411
  status.dataset.connectionTone = connectionStatusTone(item.availability, item.error !== null);
18954
19412
  status.setAttribute("role", "cell");
18955
19413
  status.textContent = item.error?.code === "authenticationRequired" ? messages.connectionLoginRequired : item.error?.code === "configurationRequired" ? messages.connectionConfigurationRequired : connectionStatusLabel(item.availability, messages, item.error !== null);
19414
+ const version2 = item.version;
19415
+ if (version2?.state?.updateAvailable && !version2.updating) {
19416
+ const available = document2.createElement("span");
19417
+ available.className = "settings-connection-version-available";
19418
+ available.textContent = `${messages.harnessVersionAvailable}: ${harnessVersionLabel(version2.state, messages.harnessVersion)}`;
19419
+ available.title = `${messages.harnessVersionCurrent}: ${version2.state.currentVersion ?? "\u2014"}`;
19420
+ status.append(available);
19421
+ }
18956
19422
  const action = document2.createElement("div");
18957
19423
  action.className = "settings-connection-row__action";
18958
19424
  action.setAttribute("role", "cell");
@@ -18982,6 +19448,23 @@ ${error51.stderrTail}`] : []
18982
19448
  });
18983
19449
  action.append(viewError);
18984
19450
  }
19451
+ if (version2?.state?.updateAvailable && version2.state.canUpdate) {
19452
+ const update = document2.createElement("button");
19453
+ update.type = "button";
19454
+ update.className = "settings-connection-install-link";
19455
+ update.dataset.connectionAction = "update";
19456
+ update.disabled = version2.updating;
19457
+ update.title = messages.harnessVersionUpdate;
19458
+ update.setAttribute("aria-label", `${messages.harnessVersionUpdate}: ${item.name}`);
19459
+ update.append(createRendererSettingsIcon("download", 17));
19460
+ update.addEventListener("click", (event) => {
19461
+ event.stopPropagation();
19462
+ if (update.disabled) return;
19463
+ version2.update();
19464
+ select();
19465
+ });
19466
+ action.append(update);
19467
+ }
18985
19468
  if (group) {
18986
19469
  const toggle = document2.createElement("button");
18987
19470
  toggle.type = "button";
@@ -19179,6 +19662,16 @@ ${error51.stderrTail}`] : []
19179
19662
  });
19180
19663
  body.append(launchControls);
19181
19664
  }
19665
+ const getGrokComputerUse = diagnostics?.getGrokComputerUse?.bind(diagnostics);
19666
+ const setGrokComputerUse = diagnostics?.setGrokComputerUse?.bind(diagnostics);
19667
+ if (hostId2 === "local" && agent === "grok" && getGrokComputerUse && setGrokComputerUse) {
19668
+ body.append(
19669
+ createGrokComputerUseControls(document2, messages, {
19670
+ get: () => getGrokComputerUse(hostId2),
19671
+ set: (update) => setGrokComputerUse(hostId2, update)
19672
+ })
19673
+ );
19674
+ }
19182
19675
  inspector.append(body);
19183
19676
  return launchControls;
19184
19677
  }
@@ -19273,8 +19766,6 @@ ${error51.stderrTail}`] : []
19273
19766
  let selectedHostId = null;
19274
19767
  let selectedItemKey = null;
19275
19768
  let launchControls = null;
19276
- const versionPanels = /* @__PURE__ */ new Map();
19277
- const updatingVersions = /* @__PURE__ */ new Set();
19278
19769
  const hostNames = /* @__PURE__ */ new Map();
19279
19770
  let latestSnapshot = null;
19280
19771
  let disposeHostScroller = () => void 0;
@@ -19303,6 +19794,13 @@ ${error51.stderrTail}`] : []
19303
19794
  renderFrame = null;
19304
19795
  };
19305
19796
  const diagnostics = getDiagnostics();
19797
+ const versions = createConnectionVersions(
19798
+ document2,
19799
+ messages,
19800
+ context.signal,
19801
+ diagnostics,
19802
+ scheduleRender
19803
+ );
19306
19804
  const installs = diagnostics ? harnessInstallStore(diagnostics) : null;
19307
19805
  const runRefresh = () => {
19308
19806
  if (pending || !diagnostics) return;
@@ -19439,10 +19937,13 @@ ${error51.stderrTail}`] : []
19439
19937
  const agent = item.agentSnapshot?.agent;
19440
19938
  if (!agent) return item;
19441
19939
  const state = installs?.get(selectedHost.hostId, agent);
19940
+ const availability = state?.status ?? item.availability;
19941
+ const version2 = item.agentSnapshot?.availability !== "notInstalled" && availability !== "installing" && availability !== "checking" ? versions.get(selectedHost.hostId, agent) : void 0;
19442
19942
  return {
19443
19943
  ...item,
19444
19944
  ...state ? { availability: state.status } : {},
19445
- ...updatingVersions.has(JSON.stringify([selectedHost.hostId, agent])) ? { availability: "updating" } : {},
19945
+ ...version2 ? { version: version2 } : {},
19946
+ ...version2?.updating ? { availability: "updating" } : {},
19446
19947
  ...state?.error ? { installError: state.error } : {},
19447
19948
  ...diagnostics?.installation && harnessHasInstallCommands(item.agentSnapshot?.plugin) ? {
19448
19949
  install: () => {
@@ -19479,30 +19980,7 @@ ${error51.stderrTail}`] : []
19479
19980
  runRefresh
19480
19981
  );
19481
19982
  launchControls = { hostId: selectedHost.hostId, itemKey: item.key, element };
19482
- const agent = item.agentSnapshot?.agent;
19483
- const installation = diagnostics?.installation?.bind(diagnostics);
19484
- if (agent && item.agentSnapshot?.availability !== "notInstalled" && item.availability !== "installing" && item.availability !== "checking" && installation) {
19485
- const key = JSON.stringify([selectedHost.hostId, agent]);
19486
- let panel = versionPanels.get(key);
19487
- if (!panel) {
19488
- const hostId2 = selectedHost.hostId;
19489
- panel = createHarnessVersionPanel(document2, messages, context.signal, agent, {
19490
- async run(action) {
19491
- if (action !== "update") return installation(hostId2, agent, action);
19492
- updatingVersions.add(key);
19493
- if (diagnostics) render(diagnostics.snapshot());
19494
- try {
19495
- return await installation(hostId2, agent, action);
19496
- } finally {
19497
- updatingVersions.delete(key);
19498
- if (!context.signal.aborted && diagnostics) render(diagnostics.snapshot());
19499
- }
19500
- }
19501
- });
19502
- versionPanels.set(key, panel);
19503
- }
19504
- inspector.append(panel);
19505
- }
19983
+ if (item.version) inspector.append(item.version.element);
19506
19984
  };
19507
19985
  const pinnedItem = items.find((item) => item.key === "renderer-adapter");
19508
19986
  if (pinnedItem) {
@@ -19963,6 +20441,26 @@ ${error51.stderrTail}`] : []
19963
20441
  launchPathLoading: "Loading launch settings\u2026",
19964
20442
  launchPathLoadError: "Could not load launch settings. Reopen this detail panel to retry.",
19965
20443
  launchPathSaveError: "Could not save. Enter an existing absolute installation folder on this Host and check configuration permissions.",
20444
+ grokComputerUseLabel: "Grok Computer Use",
20445
+ grokComputerUseTitle: "Computer Use",
20446
+ grokComputerUseDescription: "Let Grok control Mac apps via ChatGPT Computer Use (cua_repl).",
20447
+ grokComputerUseHelp: "ChatGPT Settings \u2192 Computer use still applies to official Codex threads. Always-allowed apps stay in that list. New Grok threads pick up this switch.",
20448
+ grokComputerUseOn: "On \xB7 available",
20449
+ grokComputerUseOff: "Off",
20450
+ grokComputerUseSaving: "Saving\u2026",
20451
+ grokComputerUseLoading: "Loading Grok Computer Use\u2026",
20452
+ grokComputerUseLoadError: "Could not load Grok Computer Use. Reopen this panel to retry.",
20453
+ grokComputerUseSaveError: "Could not save. Install ChatGPT.app with Computer Use, then try again.",
20454
+ grokComputerUseUnavailable: "ChatGPT Desktop Computer Use files were not found on this Mac.",
20455
+ grokBrowserLabel: "Grok in-app browser",
20456
+ grokBrowserTitle: "In-app browser",
20457
+ grokBrowserDescription: "Let Grok use ChatGPT's built-in browser (Browser Use) through cua_repl. Independent of Computer Use.",
20458
+ grokBrowserHelp: "Official Codex Browser Use is unchanged. Grok fails closed when ChatGPT does not authorize the connection or no in-app browser tab can be bound. The Chrome extension is a separate path.",
20459
+ grokBrowserOn: "On \xB7 browser surface enabled",
20460
+ grokBrowserOff: "Off",
20461
+ grokBrowserUnavailable: "ChatGPT Desktop in-app browser files were not found on this Mac.",
20462
+ grokBrowserBlocked: "Saved as intent. ChatGPT Desktop does not authorize Grok to use the in-app browser.",
20463
+ grokComputerUseApplyFailed: "Could not apply the saved setting to Grok config.",
19966
20464
  connectionOfficialWebsite: "Visit official website",
19967
20465
  connectionOpenHarnessWeb: "Open DeepSeek Harness Web",
19968
20466
  connectionDeepSeekTestedVersions: "Supported DSH versions: 0.1.7-rc.1, 0.1.7-rc.2, 0.2.0-rc.1, 0.2.0-rc.2, 0.2.1-rc.1 and 0.2.1-rc.2. Versions newer than 0.2.1-rc.2 can be tried, but compatibility may be limited; versions older than 0.1.7-rc.1 must be upgraded first.",
@@ -19974,6 +20472,7 @@ ${error51.stderrTail}`] : []
19974
20472
  harnessVersionCurrent: "Current version",
19975
20473
  harnessVersionLatest: "Latest version",
19976
20474
  harnessVersionChecking: "Checking versions\u2026",
20475
+ harnessVersionAvailable: "Update available",
19977
20476
  harnessVersionUpdate: "Update",
19978
20477
  harnessVersionUpdating: "Updating\u2026",
19979
20478
  harnessVersionUpToDate: "Up to date",
@@ -20245,6 +20744,26 @@ ${error51.stderrTail}`] : []
20245
20744
  launchPathLoading: "\u6B63\u5728\u8BFB\u53D6\u542F\u52A8\u8BBE\u7F6E\u2026",
20246
20745
  launchPathLoadError: "\u65E0\u6CD5\u8BFB\u53D6\u542F\u52A8\u8BBE\u7F6E\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u6B64\u8BE6\u60C5\u9762\u677F\u91CD\u8BD5\u3002",
20247
20746
  launchPathSaveError: "\u4FDD\u5B58\u5931\u8D25\u3002\u8BF7\u586B\u5199\u6B64 Host \u4E0A\u5B9E\u9645\u5B58\u5728\u7684\u5B89\u88C5\u76EE\u5F55\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u5E76\u786E\u8BA4\u914D\u7F6E\u76EE\u5F55\u53EF\u5199\u3002",
20747
+ grokComputerUseLabel: "Grok Computer Use",
20748
+ grokComputerUseTitle: "Computer Use",
20749
+ grokComputerUseDescription: "\u8BA9 Grok \u901A\u8FC7 ChatGPT Computer Use\uFF08cua_repl\uFF09\u63A7\u5236 Mac \u5E94\u7528\u3002",
20750
+ grokComputerUseHelp: "ChatGPT \u8BBE\u7F6E \u2192 Computer use \u4ECD\u53EA\u4F5C\u7528\u4E8E\u5B98\u65B9 Codex \u7EBF\u7A0B\u3002Always-allowed \u5E94\u7528\u4ECD\u4FDD\u5B58\u5728\u8BE5\u5217\u8868\u3002\u65B0\u7684 Grok \u7EBF\u7A0B\u4F1A\u8BFB\u53D6\u6B64\u5F00\u5173\u3002",
20751
+ grokComputerUseOn: "\u5DF2\u5F00\u542F \xB7 \u53EF\u7528",
20752
+ grokComputerUseOff: "\u5DF2\u5173\u95ED",
20753
+ grokComputerUseSaving: "\u6B63\u5728\u4FDD\u5B58\u2026",
20754
+ grokComputerUseLoading: "\u6B63\u5728\u8BFB\u53D6 Grok Computer Use\u2026",
20755
+ grokComputerUseLoadError: "\u65E0\u6CD5\u8BFB\u53D6 Grok Computer Use\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u6B64\u9762\u677F\u91CD\u8BD5\u3002",
20756
+ grokComputerUseSaveError: "\u4FDD\u5B58\u5931\u8D25\u3002\u8BF7\u5148\u5B89\u88C5\u5E26 Computer Use \u7684 ChatGPT.app\u3002",
20757
+ grokComputerUseUnavailable: "\u672C\u673A\u672A\u627E\u5230 ChatGPT Desktop \u7684 Computer Use \u6587\u4EF6\u3002",
20758
+ grokBrowserLabel: "Grok \u5185\u7F6E\u6D4F\u89C8\u5668",
20759
+ grokBrowserTitle: "\u5185\u7F6E\u6D4F\u89C8\u5668",
20760
+ grokBrowserDescription: "\u8BA9 Grok \u901A\u8FC7 cua_repl \u4F7F\u7528 ChatGPT \u5185\u7F6E\u6D4F\u89C8\u5668\uFF08Browser Use\uFF09\u3002\u4E0E Computer Use \u76F8\u4E92\u72EC\u7ACB\u3002",
20761
+ grokBrowserHelp: "\u5B98\u65B9 Codex Browser Use \u4E0D\u53D7\u5F71\u54CD\u3002ChatGPT \u4E0D\u6388\u6743\u8FDE\u63A5\u6216\u65E0\u6CD5\u7ED1\u5B9A\u5185\u7F6E\u6D4F\u89C8\u5668\u6807\u7B7E\u9875\u65F6\uFF0CGrok \u4F1A\u76F4\u63A5\u5931\u8D25\u3002Chrome \u6269\u5C55\u662F\u53E6\u4E00\u6761\u8DEF\u5F84\u3002",
20762
+ grokBrowserOn: "\u5DF2\u5F00\u542F \xB7 \u6D4F\u89C8\u5668\u80FD\u529B\u5DF2\u542F\u7528",
20763
+ grokBrowserOff: "\u5DF2\u5173\u95ED",
20764
+ grokBrowserUnavailable: "\u672C\u673A\u672A\u627E\u5230 ChatGPT Desktop \u7684\u5185\u7F6E\u6D4F\u89C8\u5668\u6587\u4EF6\u3002",
20765
+ grokBrowserBlocked: "\u5DF2\u4FDD\u5B58\u4E3A\u610F\u5411\u3002ChatGPT Desktop \u5C1A\u672A\u6388\u6743 Grok \u4F7F\u7528\u5185\u7F6E\u6D4F\u89C8\u5668\u3002",
20766
+ grokComputerUseApplyFailed: "\u65E0\u6CD5\u5C06\u5DF2\u4FDD\u5B58\u7684\u8BBE\u7F6E\u5199\u5165 Grok \u914D\u7F6E\u3002",
20248
20767
  connectionOfficialWebsite: "\u8BBF\u95EE\u5B98\u7F51",
20249
20768
  connectionOpenHarnessWeb: "\u6253\u5F00 DeepSeek Harness Web",
20250
20769
  connectionDeepSeekTestedVersions: "\u652F\u6301 DSH \u7248\u672C\uFF1A0.1.7-rc.1\u30010.1.7-rc.2\u30010.2.0-rc.1\u30010.2.0-rc.2\u30010.2.1-rc.1 \u548C 0.2.1-rc.2\u3002\u9AD8\u4E8E 0.2.1-rc.2 \u7684\u7248\u672C\u53EF\u4EE5\u5C1D\u8BD5\u8FDE\u63A5\uFF0C\u4F46\u9002\u914D\u5EA6\u53EF\u80FD\u6709\u9650\uFF1B\u4F4E\u4E8E 0.1.7-rc.1 \u7684\u7248\u672C\u9700\u8981\u5148\u5347\u7EA7\u3002",
@@ -20256,6 +20775,7 @@ ${error51.stderrTail}`] : []
20256
20775
  harnessVersionCurrent: "\u5F53\u524D\u7248\u672C",
20257
20776
  harnessVersionLatest: "\u6700\u65B0\u7248\u672C",
20258
20777
  harnessVersionChecking: "\u6B63\u5728\u68C0\u67E5\u7248\u672C\u2026",
20778
+ harnessVersionAvailable: "\u6709\u65B0\u7248\u672C",
20259
20779
  harnessVersionUpdate: "\u66F4\u65B0",
20260
20780
  harnessVersionUpdating: "\u6B63\u5728\u66F4\u65B0\u2026",
20261
20781
  harnessVersionUpToDate: "\u5DF2\u662F\u6700\u65B0",
@@ -24791,7 +25311,15 @@ Codex Fast mode prioritizes requests and may increase usage.`
24791
25311
  };
24792
25312
  const inspectHarness = async (input, options) => {
24793
25313
  const params = harnessInspectParamsSchema.parse(input);
24794
- const result = await manager.sendRequest(HARNESS_INSPECT_METHOD, params, options);
25314
+ let result;
25315
+ try {
25316
+ result = await manager.sendRequest(HARNESS_INSPECT_METHOD, params, options);
25317
+ } catch (error51) {
25318
+ if (!params.purpose || !isInvalidParams(error51)) throw error51;
25319
+ const full = { ...params };
25320
+ delete full.purpose;
25321
+ result = await manager.sendRequest(HARNESS_INSPECT_METHOD, full, options);
25322
+ }
24795
25323
  return harnessInspectionSchema.parse(result);
24796
25324
  };
24797
25325
  const inspectHarnessCommands = async (input) => {
@@ -24865,6 +25393,19 @@ Codex Fast mode prioritizes requests and may increase usage.`
24865
25393
  )
24866
25394
  );
24867
25395
  },
25396
+ async getGrokComputerUse() {
25397
+ return grokComputerUseSettingsSchema.parse(
25398
+ await manager.sendRequest(GROK_COMPUTER_USE_GET_METHOD, {})
25399
+ );
25400
+ },
25401
+ async setGrokComputerUse(update) {
25402
+ return grokComputerUseSettingsSchema.parse(
25403
+ await manager.sendRequest(
25404
+ GROK_COMPUTER_USE_SET_METHOD,
25405
+ grokComputerUseSetSchema.parse(update)
25406
+ )
25407
+ );
25408
+ },
24868
25409
  async listLoadedSessions() {
24869
25410
  return loadedSessionsSchema.parse(await manager.sendRequest(LOADED_SESSIONS_METHOD, {}));
24870
25411
  },
@@ -25053,6 +25594,9 @@ Codex Fast mode prioritizes requests and may increase usage.`
25053
25594
  }
25054
25595
  });
25055
25596
  }
25597
+ function isInvalidParams(error51) {
25598
+ return typeof error51 === "object" && error51 !== null && "code" in error51 && error51.code === -32602;
25599
+ }
25056
25600
 
25057
25601
  // src/renderer-external-queue.ts
25058
25602
  function isRecord8(value) {
@@ -27164,104 +27708,6 @@ ${text.includes("## My request:") ? "" : "\n## My request:\n"}`,
27164
27708
  return element;
27165
27709
  }
27166
27710
 
27167
- // src/settings/preference-ui.ts
27168
- var nextPreferenceId = 0;
27169
- function preferenceId(prefix) {
27170
- nextPreferenceId += 1;
27171
- return `codexhost-${prefix}-${nextPreferenceId}`;
27172
- }
27173
- function createPreferenceGroup(document2, title) {
27174
- const group = document2.createElement("section");
27175
- group.className = "mt-7 first-of-type:mt-0";
27176
- const header = document2.createElement("div");
27177
- header.className = "mb-2 flex min-h-6 items-center gap-2 px-1";
27178
- const heading = document2.createElement("h3");
27179
- heading.id = preferenceId("preference-group");
27180
- heading.className = "m-0 text-[13px] leading-5 font-semibold text-settings-text";
27181
- heading.textContent = title;
27182
- header.append(heading);
27183
- const card = document2.createElement("div");
27184
- card.className = "rounded-[10px] border border-settings-border bg-settings-panel";
27185
- group.setAttribute("aria-labelledby", heading.id);
27186
- group.append(header, card);
27187
- return { group, header, card };
27188
- }
27189
- function createPreferenceItem(document2, input) {
27190
- const item = document2.createElement("div");
27191
- item.className = "flex min-h-[60px] items-center justify-between gap-6 border-t border-settings-divider px-4 py-3 first:border-t-0";
27192
- const copy = document2.createElement("div");
27193
- copy.className = "flex min-w-0 flex-col gap-0.5";
27194
- const heading = document2.createElement("div");
27195
- heading.className = "flex items-center gap-1.5";
27196
- const title = document2.createElement("label");
27197
- title.htmlFor = input.controlId;
27198
- title.className = "cursor-pointer text-[13px] leading-5 font-medium text-settings-text";
27199
- title.textContent = input.title;
27200
- heading.append(title);
27201
- if (input.help) heading.append(createHelpTooltip(document2, input.help.label, input.help.lines));
27202
- const description = document2.createElement("div");
27203
- description.id = preferenceId("preference-description");
27204
- description.className = "text-xs leading-[18px] text-settings-muted data-[invalid=true]:text-settings-danger";
27205
- description.textContent = input.description;
27206
- copy.append(heading, description);
27207
- item.append(copy);
27208
- return { item, description };
27209
- }
27210
- function createHelpTooltip(document2, label, lines) {
27211
- const wrapper = document2.createElement("span");
27212
- wrapper.className = "group relative inline-flex";
27213
- const button2 = document2.createElement("button");
27214
- button2.type = "button";
27215
- button2.className = "inline-flex size-4 cursor-help items-center justify-center rounded-full border-0 bg-transparent p-0 text-settings-subtle transition-colors hover:text-settings-text focus-visible:text-settings-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-settings-focus";
27216
- button2.setAttribute("aria-label", label);
27217
- button2.append(createRendererSettingsIcon("help", 14));
27218
- const tooltip = document2.createElement("div");
27219
- tooltip.id = preferenceId("preference-tooltip");
27220
- tooltip.setAttribute("role", "tooltip");
27221
- tooltip.className = "pointer-events-none invisible absolute top-full left-0 z-20 mt-2 w-80 rounded-lg border border-settings-border bg-settings-panel px-3 py-2.5 text-xs leading-[18px] font-normal text-settings-text opacity-0 shadow-[0_10px_30px_rgb(0_0_0/0.22)] transition-opacity duration-150 group-hover:visible group-hover:opacity-100 group-focus-within:visible group-focus-within:opacity-100";
27222
- const list = document2.createElement("ul");
27223
- list.className = "m-0 grid list-disc gap-1 pl-4 marker:text-settings-subtle";
27224
- for (const line of lines) {
27225
- const item = document2.createElement("li");
27226
- item.textContent = line;
27227
- list.append(item);
27228
- }
27229
- tooltip.append(list);
27230
- button2.setAttribute("aria-describedby", tooltip.id);
27231
- button2.addEventListener("keydown", (event) => {
27232
- if (event.key === "Escape") button2.blur();
27233
- });
27234
- wrapper.append(button2, tooltip);
27235
- return wrapper;
27236
- }
27237
- function createPreferenceSwitch(document2, id, describedBy) {
27238
- const input = document2.createElement("input");
27239
- input.type = "checkbox";
27240
- input.id = id;
27241
- input.setAttribute("role", "switch");
27242
- input.setAttribute("aria-describedby", describedBy);
27243
- input.className = "relative m-0 h-5 w-9 shrink-0 cursor-pointer appearance-none rounded-full bg-settings-surface-hover ring-1 ring-settings-border transition-colors ring-inset before:absolute before:top-0.5 before:left-0.5 before:size-4 before:rounded-full before:bg-white before:shadow-[0_1px_2px_rgb(0_0_0/0.3)] before:transition-transform checked:bg-settings-focus checked:ring-transparent checked:before:translate-x-4 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-settings-focus";
27244
- return input;
27245
- }
27246
- function createNumberField(document2, input) {
27247
- const field = document2.createElement("div");
27248
- field.className = "flex h-8 shrink-0 items-center gap-1.5 rounded-md border border-settings-border bg-settings-surface px-2.5 transition-colors focus-within:border-settings-focus data-[invalid=true]:border-settings-danger";
27249
- const control = document2.createElement("input");
27250
- control.type = "number";
27251
- control.id = input.id;
27252
- control.min = String(input.min);
27253
- control.max = String(input.max);
27254
- control.step = "1";
27255
- control.setAttribute("aria-describedby", input.describedBy);
27256
- control.className = "w-14 border-0 bg-transparent p-0 text-right text-[13px] leading-5 text-settings-text tabular-nums outline-none";
27257
- const unit = document2.createElement("span");
27258
- unit.className = "text-xs text-settings-muted";
27259
- unit.setAttribute("aria-hidden", "true");
27260
- unit.textContent = input.unit;
27261
- field.append(control, unit);
27262
- return { field, control };
27263
- }
27264
-
27265
27711
  // src/settings/voice-messages.ts
27266
27712
  function voiceMessages(locale) {
27267
27713
  const en = {
@@ -27283,6 +27729,15 @@ ${text.includes("## My request:") ? "" : "\n## My request:\n"}`,
27283
27729
  configured: "Configured",
27284
27730
  needsSetup: "Needs setup",
27285
27731
  unknown: "Not checked",
27732
+ gatewayStatus: "Gateway configuration",
27733
+ preferencesStatus: "Preferences applied",
27734
+ hookStatus: "Desktop gateway voice integration",
27735
+ preferencesOn: "Applied",
27736
+ preferencesOff: "Not applied yet",
27737
+ hookSupported: "Supported",
27738
+ hookUnsupported: "This Desktop build is not supported for gateway voice",
27739
+ hookPending: "Attaching\u2026",
27740
+ hookDisabled: "Off",
27286
27741
  configure: "Configure",
27287
27742
  setup: "Gateway setup",
27288
27743
  setupHint: "Gateway settings use your local Codex configuration and the selected provider\u2019s credential. End voice before editing configuration. Never paste credentials here.",
@@ -27333,6 +27788,15 @@ ${text.includes("## My request:") ? "" : "\n## My request:\n"}`,
27333
27788
  configured: "\u5DF2\u914D\u7F6E",
27334
27789
  needsSetup: "\u9700\u8981\u914D\u7F6E",
27335
27790
  unknown: "\u672A\u68C0\u67E5",
27791
+ gatewayStatus: "\u7F51\u5173\u914D\u7F6E",
27792
+ preferencesStatus: "\u504F\u597D\u662F\u5426\u5DF2\u5E94\u7528",
27793
+ hookStatus: "Desktop \u7F51\u5173\u8BED\u97F3\u96C6\u6210",
27794
+ preferencesOn: "\u5DF2\u5E94\u7528",
27795
+ preferencesOff: "\u5C1A\u672A\u5E94\u7528",
27796
+ hookSupported: "\u652F\u6301",
27797
+ hookUnsupported: "\u5F53\u524D Desktop \u7248\u672C\u4E0D\u652F\u6301\u7F51\u5173\u8BED\u97F3",
27798
+ hookPending: "\u6B63\u5728\u63A5\u5165\u2026",
27799
+ hookDisabled: "\u5DF2\u5173\u95ED",
27336
27800
  configure: "\u914D\u7F6E",
27337
27801
  setup: "\u7F51\u5173\u914D\u7F6E",
27338
27802
  setupHint: "\u7F51\u5173\u4F7F\u7528\u672C\u5730 Codex \u914D\u7F6E\u53CA\u6240\u9009 Provider \u7684\u51ED\u636E\u3002\u7F16\u8F91\u524D\u8BF7\u7ED3\u675F\u8BED\u97F3\u3002\u8BF7\u52FF\u5728\u6B64\u7C98\u8D34\u51ED\u636E\u3002",
@@ -27532,20 +27996,30 @@ ${text.includes("## My request:") ? "" : "\n## My request:\n"}`,
27532
27996
  });
27533
27997
  configure.id = gateway.item.querySelector("label")?.htmlFor ?? "";
27534
27998
  configure.setAttribute("aria-expanded", String(setup));
27999
+ const gatewayReadyLabel = state.gatewayReady === null ? m.unknown : state.gatewayReady ? m.configured : m.needsSetup;
27535
28000
  const gatewayActions = h(
27536
28001
  doc,
27537
28002
  "div",
27538
28003
  { className: "settings-voice__gateway-actions" },
28004
+ h(doc, "span", {}, gatewayReadyLabel),
28005
+ configure
28006
+ );
28007
+ gateway.item.append(gatewayActions);
28008
+ config2.card.append(gateway.item);
28009
+ const hook = options.desktop ? desktopVoiceHookStatus(doc.defaultView) : "unknown";
28010
+ const hookLabel = hook === "active" ? m.hookSupported : hook === "pending" ? m.hookPending : hook === "unsupported" ? m.hookUnsupported : hook === "disabled" ? m.hookDisabled : m.unknown;
28011
+ const health = h(doc, "div", { className: "settings-voice__health" });
28012
+ health.append(
28013
+ h(doc, "p", {}, `${m.gatewayStatus}: ${gatewayReadyLabel}`),
27539
28014
  h(
27540
28015
  doc,
27541
- "span",
28016
+ "p",
27542
28017
  {},
27543
- state.gatewayReady === null ? m.unknown : state.gatewayReady ? m.configured : m.needsSetup
28018
+ `${m.preferencesStatus}: ${state.routesActive === true ? m.preferencesOn : state.routesActive === false ? m.preferencesOff : m.unknown}`
27544
28019
  ),
27545
- configure
28020
+ h(doc, "p", {}, `${m.hookStatus}: ${hookLabel}`)
27546
28021
  );
27547
- gateway.item.append(gatewayActions);
27548
- config2.card.append(gateway.item);
28022
+ config2.card.append(health);
27549
28023
  if (setup) {
27550
28024
  const details = h(
27551
28025
  doc,
@@ -27675,6 +28149,15 @@ ${text.includes("## My request:") ? "" : "\n## My request:\n"}`,
27675
28149
  }
27676
28150
  });
27677
28151
  }
28152
+ function desktopVoiceHookStatus(win) {
28153
+ if (!win) return "unknown";
28154
+ const value = win.__codexhostClientOwnedVoiceV1;
28155
+ if (!value || typeof value !== "object") return "unknown";
28156
+ const status = value.status;
28157
+ if (status === "active" || status === "pending" || status === "unsupported" || status === "disabled")
28158
+ return status;
28159
+ return "unknown";
28160
+ }
27678
28161
 
27679
28162
  // src/console/usage/model-labels.ts
27680
28163
  function usageModelLabel(model2, labels, harness2) {
@@ -30061,6 +30544,13 @@ ${detail}` : ""}`
30061
30544
  save: "Save",
30062
30545
  useAutomatic: "Use automatic",
30063
30546
  saved: "Saved",
30547
+ grokComputerUse: "Grok Computer Use",
30548
+ grokComputerUseHelp: "Lets Grok control Mac apps through ChatGPT\u2019s Computer Use helper. ChatGPT Settings \u2192 Computer use still applies only to official Codex. New Grok threads pick up this switch.",
30549
+ grokComputerUseUnavailable: "ChatGPT Desktop Computer Use files were not found.",
30550
+ grokBrowser: "Grok in-app browser",
30551
+ grokBrowserHelp: "Lets Grok use ChatGPT\u2019s built-in browser (Browser Use) through cua_repl. Independent of Computer Use; official Codex Browser Use is unchanged. Grok fails closed when the in-app tab cannot be bound.",
30552
+ grokBrowserUnavailable: "ChatGPT Desktop in-app browser files were not found.",
30553
+ grokBrowserBlocked: "Saved as intent. ChatGPT Desktop does not authorize Grok to use the in-app browser.",
30064
30554
  installGuide: "Install guide",
30065
30555
  noHarness: "No Harness plugins are installed.",
30066
30556
  consoleOffline: "The console is not running",
@@ -30250,6 +30740,13 @@ ${detail}` : ""}`
30250
30740
  save: "\u4FDD\u5B58",
30251
30741
  useAutomatic: "\u6062\u590D\u81EA\u52A8\u67E5\u627E",
30252
30742
  saved: "\u5DF2\u4FDD\u5B58",
30743
+ grokComputerUse: "Grok Computer Use",
30744
+ grokComputerUseHelp: "\u8BA9 Grok \u901A\u8FC7 ChatGPT \u7684 Computer Use \u52A9\u624B\u63A7\u5236 Mac \u5E94\u7528\u3002ChatGPT \u8BBE\u7F6E \u2192 Computer use \u4ECD\u53EA\u4F5C\u7528\u4E8E\u5B98\u65B9 Codex\u3002\u65B0\u7684 Grok \u7EBF\u7A0B\u4F1A\u8BFB\u53D6\u6B64\u5F00\u5173\u3002",
30745
+ grokComputerUseUnavailable: "\u672A\u627E\u5230 ChatGPT Desktop \u7684 Computer Use \u6587\u4EF6\u3002",
30746
+ grokBrowser: "Grok \u5185\u7F6E\u6D4F\u89C8\u5668",
30747
+ grokBrowserHelp: "\u8BA9 Grok \u901A\u8FC7 cua_repl \u4F7F\u7528 ChatGPT \u5185\u7F6E\u6D4F\u89C8\u5668\uFF08Browser Use\uFF09\u3002\u4E0E Computer Use \u76F8\u4E92\u72EC\u7ACB\uFF0C\u5B98\u65B9 Codex Browser Use \u4E0D\u53D7\u5F71\u54CD\u3002\u65E0\u6CD5\u7ED1\u5B9A\u5185\u7F6E\u6807\u7B7E\u9875\u65F6 Grok \u4F1A\u76F4\u63A5\u5931\u8D25\u3002",
30748
+ grokBrowserUnavailable: "\u672A\u627E\u5230 ChatGPT Desktop \u7684\u5185\u7F6E\u6D4F\u89C8\u5668\u6587\u4EF6\u3002",
30749
+ grokBrowserBlocked: "\u5DF2\u4FDD\u5B58\u4E3A\u610F\u5411\u3002ChatGPT Desktop \u5C1A\u672A\u6388\u6743 Grok \u4F7F\u7528\u5185\u7F6E\u6D4F\u89C8\u5668\u3002",
30253
30750
  installGuide: "\u5B89\u88C5\u8BF4\u660E",
30254
30751
  noHarness: "\u6CA1\u6709\u5DF2\u5B89\u88C5\u7684 Harness \u63D2\u4EF6\u3002",
30255
30752
  consoleOffline: "\u63A7\u5236\u53F0\u672A\u8FD0\u884C",
@@ -32663,7 +33160,7 @@ ${detail}` : ""}`
32663
33160
  }
32664
33161
  function defaultImportName(source, existing) {
32665
33162
  const base = source.provider === "openai-codex" ? "codex" : "grok";
32666
- const used = new Set(existing.map((record6) => record6.name));
33163
+ const used = new Set(existing.map((record7) => record7.name));
32667
33164
  const valid = (name) => credentialImportNameSchema.safeParse(name).success;
32668
33165
  if (!used.has(base)) return base;
32669
33166
  const local = source.label.includes("@") ? source.label.split("@")[0] ?? "" : "";
@@ -32686,7 +33183,7 @@ ${detail}` : ""}`
32686
33183
  const shown = (text) => accountDisplayText(text, hideEmails);
32687
33184
  const targetButtons = /* @__PURE__ */ new Map();
32688
33185
  const importedRecords = () => snapshot.targets.find((target) => target.harnessId === TARGET_HARNESS_ID)?.imports ?? [];
32689
- const sourceOf = (record6) => snapshot.sources.find((source) => source.id === record6.source.id);
33186
+ const sourceOf = (record7) => snapshot.sources.find((source) => source.id === record7.source.id);
32690
33187
  const piTarget = () => snapshot.targets.find((candidate) => candidate.harnessId === TARGET_HARNESS_ID);
32691
33188
  const compatibleSources = () => {
32692
33189
  const target = piTarget();
@@ -32724,7 +33221,7 @@ ${detail}` : ""}`
32724
33221
  sectionImport.addEventListener("click", () => {
32725
33222
  const sources = compatibleSources();
32726
33223
  const free = sources.find(
32727
- (source2) => !importedRecords().some((record6) => record6.source.id === source2.id)
33224
+ (source2) => !importedRecords().some((record7) => record7.source.id === source2.id)
32728
33225
  );
32729
33226
  const source = free ?? sources[0];
32730
33227
  if (source) openDialog("add", { source, choose: true }, SECTION_IMPORT_KEY);
@@ -32774,30 +33271,30 @@ ${detail}` : ""}`
32774
33271
  mark.append(createRendererAgentIcon(agent ?? "pi", 26, document2, pluginFor(agent ?? "pi")));
32775
33272
  return mark;
32776
33273
  };
32777
- const renderRow = (record6) => {
33274
+ const renderRow = (record7) => {
32778
33275
  const row = document2.createElement("div");
32779
33276
  row.className = "settings-pi-accounts__row";
32780
33277
  row.tabIndex = -1;
32781
- row.dataset.importName = record6.name;
32782
- row.setAttribute("aria-label", shown(record6.source.label));
32783
- const agent = record6.source.harnessId;
33278
+ row.dataset.importName = record7.name;
33279
+ row.setAttribute("aria-label", shown(record7.source.label));
33280
+ const agent = record7.source.harnessId;
32784
33281
  const mark = createMark(agent);
32785
33282
  const identity = document2.createElement("div");
32786
33283
  identity.className = "settings-pi-accounts__identity";
32787
33284
  const label = document2.createElement("strong");
32788
33285
  label.className = "settings-account-email";
32789
- label.textContent = shown(record6.source.label);
33286
+ label.textContent = shown(record7.source.label);
32790
33287
  label.title = label.textContent;
32791
33288
  label.translate = false;
32792
33289
  const meta3 = document2.createElement("div");
32793
33290
  meta3.className = "settings-account-metadata";
32794
- const source = sourceOf(record6);
33291
+ const source = sourceOf(record7);
32795
33292
  const parts = [
32796
33293
  metaPart(rendererAgentLabel(agent, pluginFor(agent)), "settings-pi-accounts__agent"),
32797
- metaPart(`${record6.name}/\u2026`, void 0, true),
33294
+ metaPart(`${record7.name}/\u2026`, void 0, true),
32798
33295
  metaPart(messages.copied)
32799
33296
  ];
32800
- const time3 = formatImportedAt(record6.importedAt);
33297
+ const time3 = formatImportedAt(record7.importedAt);
32801
33298
  if (time3) parts.push(metaPart(time3));
32802
33299
  parts.forEach((part, index) => {
32803
33300
  if (index > 0) {
@@ -32814,23 +33311,23 @@ ${detail}` : ""}`
32814
33311
  reimport.type = "button";
32815
33312
  reimport.className = "settings-account-action";
32816
33313
  reimport.textContent = messages.rowReimport;
32817
- reimport.setAttribute("aria-label", `${messages.reimport}: ${shown(record6.source.label)}`);
33314
+ reimport.setAttribute("aria-label", `${messages.reimport}: ${shown(record7.source.label)}`);
32818
33315
  reimport.disabled = busy || !source;
32819
33316
  reimport.title = messages.reimport;
32820
33317
  reimport.addEventListener("click", () => {
32821
- if (source) openDialog("reimport", { source, record: record6 }, `row:${record6.name}`);
33318
+ if (source) openDialog("reimport", { source, record: record7 }, `row:${record7.name}`);
32822
33319
  });
32823
33320
  const remove = document2.createElement("button");
32824
33321
  remove.type = "button";
32825
33322
  remove.className = "settings-account-action settings-pi-accounts__remove";
32826
33323
  remove.textContent = messages.rowRemove;
32827
- remove.setAttribute("aria-label", `${messages.remove}: ${shown(record6.source.label)}`);
33324
+ remove.setAttribute("aria-label", `${messages.remove}: ${shown(record7.source.label)}`);
32828
33325
  remove.disabled = busy;
32829
- remove.addEventListener("click", () => openDialog("remove", { record: record6 }, `row:${record6.name}`));
33326
+ remove.addEventListener("click", () => openDialog("remove", { record: record7 }, `row:${record7.name}`));
32830
33327
  if (source) actions.append(reimport);
32831
33328
  actions.append(remove);
32832
33329
  row.append(mark, identity, actions);
32833
- rows.set(record6.name, row);
33330
+ rows.set(record7.name, row);
32834
33331
  return row;
32835
33332
  };
32836
33333
  const loginTypeLabel = (type) => type === "oauth" ? messages.typeOauth : type === "api_key" ? messages.typeApiKey : messages.typeOther;
@@ -32983,7 +33480,7 @@ ${detail}` : ""}`
32983
33480
  const label = document2.createElement("strong");
32984
33481
  label.textContent = shown(choice.label);
32985
33482
  label.translate = false;
32986
- const existing = importedRecords().find((record6) => record6.source.id === choice.id);
33483
+ const existing = importedRecords().find((record7) => record7.source.id === choice.id);
32987
33484
  const agent = choice.harnessId;
32988
33485
  const detail = document2.createElement("span");
32989
33486
  detail.textContent = [
@@ -33024,7 +33521,7 @@ ${detail}` : ""}`
33024
33521
  const name = request.action === "list" ? void 0 : request.name;
33025
33522
  state = {
33026
33523
  source: state.source,
33027
- record: importedRecords().find((record6) => record6.name === name) ?? state.record
33524
+ record: importedRecords().find((record7) => record7.name === name) ?? state.record
33028
33525
  };
33029
33526
  show(next);
33030
33527
  } else {
@@ -33038,12 +33535,12 @@ ${detail}` : ""}`
33038
33535
  sourceRadios = [];
33039
33536
  body.replaceChildren();
33040
33537
  controls.replaceChildren();
33041
- const { source, record: record6 } = state;
33538
+ const { source, record: record7 } = state;
33042
33539
  const close = () => modal.close();
33043
33540
  if (mode === "add" || mode === "reimport") {
33044
33541
  title.textContent = mode === "add" ? messages.add : messages.reimport;
33045
33542
  modal.setAttribute("aria-label", title.textContent);
33046
- const active = source ?? record6?.source;
33543
+ const active = source ?? record7?.source;
33047
33544
  const choices = mode === "add" && state.choose ? compatibleSources() : [];
33048
33545
  if (choices.length > 1) body.append(sourcePicker(choices));
33049
33546
  else if (active) body.append(line(messages.source, shown(active.label)));
@@ -33052,8 +33549,8 @@ ${detail}` : ""}`
33052
33549
  input.maxLength = 48;
33053
33550
  input.autocomplete = "off";
33054
33551
  input.spellcheck = false;
33055
- input.value = record6?.name ?? (source ? defaultImportName(source, importedRecords()) : "codex");
33056
- input.readOnly = Boolean(record6);
33552
+ input.value = record7?.name ?? (source ? defaultImportName(source, importedRecords()) : "codex");
33553
+ input.readOnly = Boolean(record7);
33057
33554
  input.addEventListener("input", () => {
33058
33555
  nameEdited = true;
33059
33556
  });
@@ -33087,7 +33584,7 @@ ${detail}` : ""}`
33087
33584
  {
33088
33585
  action: mode === "reimport" ? "reimport" : "import",
33089
33586
  sourceId: source2.id,
33090
- name: record6?.name ?? input.value,
33587
+ name: record7?.name ?? input.value,
33091
33588
  confirmed: true
33092
33589
  },
33093
33590
  "done"
@@ -33097,16 +33594,16 @@ ${detail}` : ""}`
33097
33594
  )
33098
33595
  );
33099
33596
  (mode === "add" ? input : controls.children[0]).focus();
33100
- } else if (mode === "remove" && record6) {
33597
+ } else if (mode === "remove" && record7) {
33101
33598
  title.textContent = messages.remove;
33102
33599
  modal.setAttribute("aria-label", title.textContent);
33103
- body.append(note(messages.removeWarning.replace("{name}", record6.name)));
33600
+ body.append(note(messages.removeWarning.replace("{name}", record7.name)));
33104
33601
  const cancel = command(messages.cancel, close);
33105
33602
  controls.append(
33106
33603
  cancel,
33107
33604
  command(
33108
33605
  messages.remove,
33109
- () => submit({ action: "remove", name: record6.name, confirmed: true }, null),
33606
+ () => submit({ action: "remove", name: record7.name, confirmed: true }, null),
33110
33607
  "danger"
33111
33608
  )
33112
33609
  );
@@ -33114,7 +33611,7 @@ ${detail}` : ""}`
33114
33611
  } else {
33115
33612
  title.textContent = messages.doneTitle;
33116
33613
  modal.setAttribute("aria-label", title.textContent);
33117
- const name = record6?.name;
33614
+ const name = record7?.name;
33118
33615
  if (name) body.append(line(messages.entry, `${name}/\u2026`, true));
33119
33616
  body.append(note(messages.done));
33120
33617
  const done = command(messages.close, close, "primary");
@@ -33161,21 +33658,21 @@ ${detail}` : ""}`
33161
33658
  (candidate) => candidate.harnessId === TARGET_HARNESS_ID && source && candidate.providers.includes(source.provider)
33162
33659
  );
33163
33660
  if (!source || !target) return null;
33164
- const record6 = target.imports.find((candidate) => candidate.source.id === source.id);
33661
+ const record7 = target.imports.find((candidate) => candidate.source.id === source.id);
33165
33662
  const key = `${harnessId}:${accountLabel}:import`;
33166
33663
  const button2 = document2.createElement("button");
33167
33664
  button2.type = "button";
33168
33665
  button2.className = "settings-account-pi-import";
33169
33666
  button2.dataset.accountFocus = key;
33170
33667
  button2.disabled = busy;
33171
- const hint = record6 ? messages.importedHint.replace("{name}", record6.name) : messages.add;
33668
+ const hint = record7 ? messages.importedHint.replace("{name}", record7.name) : messages.add;
33172
33669
  button2.title = hint;
33173
33670
  button2.setAttribute("aria-label", hint);
33174
33671
  button2.append(createRendererAgentIcon("pi", 12, document2, pluginFor("pi")));
33175
- if (record6) {
33672
+ if (record7) {
33176
33673
  button2.dataset.state = "imported";
33177
33674
  const name = document2.createElement("code");
33178
- name.textContent = `${record6.name}/\u2026`;
33675
+ name.textContent = `${record7.name}/\u2026`;
33179
33676
  name.translate = false;
33180
33677
  button2.append(name, createRendererSettingsIcon("check", 12));
33181
33678
  } else {
@@ -33183,7 +33680,7 @@ ${detail}` : ""}`
33183
33680
  button2.append(messages.add);
33184
33681
  }
33185
33682
  button2.addEventListener("click", () => {
33186
- if (!record6) {
33683
+ if (!record7) {
33187
33684
  openDialog("add", { source }, key);
33188
33685
  return;
33189
33686
  }
@@ -33192,7 +33689,7 @@ ${detail}` : ""}`
33192
33689
  renderSection();
33193
33690
  sectionToggle.setAttribute("aria-expanded", "true");
33194
33691
  }
33195
- const row = rows.get(record6.name);
33692
+ const row = rows.get(record7.name);
33196
33693
  row?.scrollIntoView({ block: "nearest", behavior: "smooth" });
33197
33694
  row?.focus();
33198
33695
  });
@@ -35749,6 +36246,17 @@ ${detail}` : ""}`
35749
36246
  line-height: 18px;
35750
36247
  }
35751
36248
 
36249
+ .settings-connection-row__status:has(.settings-connection-version-available) {
36250
+ flex-wrap: wrap;
36251
+ max-width: 160px;
36252
+ }
36253
+
36254
+ .settings-connection-version-available {
36255
+ flex-basis: 100%;
36256
+ color: var(--settings-focus);
36257
+ overflow-wrap: anywhere;
36258
+ }
36259
+
35752
36260
  .settings-connection-row__status::before {
35753
36261
  width: 6px;
35754
36262
  height: 6px;
@@ -36092,6 +36600,69 @@ ${detail}` : ""}`
36092
36600
  gap: 8px;
36093
36601
  }
36094
36602
 
36603
+ .settings-grok-computer-use {
36604
+ display: grid;
36605
+ gap: 10px;
36606
+ min-width: 0;
36607
+ margin-top: 4px;
36608
+ padding: 12px;
36609
+ background: var(--settings-surface);
36610
+ border: 1px solid var(--settings-divider);
36611
+ border-radius: 12px;
36612
+ }
36613
+
36614
+ .settings-grok-computer-use__header {
36615
+ display: flex;
36616
+ align-items: flex-start;
36617
+ justify-content: space-between;
36618
+ gap: 12px;
36619
+ }
36620
+
36621
+ .settings-grok-computer-use__copy {
36622
+ display: grid;
36623
+ gap: 4px;
36624
+ min-width: 0;
36625
+ }
36626
+
36627
+ .settings-grok-computer-use__title {
36628
+ margin: 0;
36629
+ color: var(--settings-text);
36630
+ font-size: 14px;
36631
+ font-weight: 600;
36632
+ line-height: 20px;
36633
+ }
36634
+
36635
+ .settings-grok-computer-use__description {
36636
+ margin: 0;
36637
+ color: var(--settings-muted);
36638
+ font-size: 13px;
36639
+ line-height: 18px;
36640
+ overflow-wrap: anywhere;
36641
+ }
36642
+
36643
+ .settings-grok-computer-use__help,
36644
+ .settings-grok-computer-use__status {
36645
+ margin: 0;
36646
+ color: var(--settings-subtle);
36647
+ font-size: 12px;
36648
+ line-height: 16px;
36649
+ overflow-wrap: anywhere;
36650
+ }
36651
+
36652
+ .settings-grok-computer-use__help {
36653
+ padding-top: 8px;
36654
+ border-top: 1px solid var(--settings-divider);
36655
+ }
36656
+
36657
+ .settings-grok-computer-use__browser {
36658
+ padding-top: 8px;
36659
+ border-top: 1px solid var(--settings-divider);
36660
+ }
36661
+
36662
+ .settings-grok-computer-use__status[data-state="on"] {
36663
+ color: var(--settings-success);
36664
+ }
36665
+
36095
36666
  .settings-connection-install-callout {
36096
36667
  display: grid;
36097
36668
  grid-template-columns: 28px minmax(0, 1fr);
@@ -37341,6 +37912,19 @@ ${detail}` : ""}`
37341
37912
  border-top: 1px solid var(--settings-divider);
37342
37913
  font-size: 13px;
37343
37914
  }
37915
+ .settings-voice__health {
37916
+ display: grid;
37917
+ gap: 4px;
37918
+ margin: 8px 12px 12px;
37919
+ color: var(--settings-subtle);
37920
+ font-size: 12px;
37921
+ line-height: 16px;
37922
+ }
37923
+
37924
+ .settings-voice__health p {
37925
+ margin: 0;
37926
+ }
37927
+
37344
37928
  .settings-voice__footer p,
37345
37929
  .settings-voice__hint {
37346
37930
  margin: 4px 0 0;
@@ -39707,7 +40291,7 @@ th[aria-sort] .console-usage-sort__mark {
39707
40291
  place();
39708
40292
  const observer = ownerWindow ? new ownerWindow.MutationObserver((records) => {
39709
40293
  const titlebar = source?.closest("[data-app-shell-titlebar]");
39710
- if (!source?.isConnected || records.some((record6) => titlebar?.contains(record6.target))) {
40294
+ if (!source?.isConnected || records.some((record7) => titlebar?.contains(record7.target))) {
39711
40295
  place();
39712
40296
  }
39713
40297
  }) : null;
@@ -42474,7 +43058,7 @@ ${usage_default}`;
42474
43058
  let webUiAvailable = false;
42475
43059
  try {
42476
43060
  const inspection = await client.inspectHarness(
42477
- { harnessId: harnessIdSchema.parse(agent), refresh },
43061
+ { harnessId: harnessIdSchema.parse(agent), refresh, purpose: "availability" },
42478
43062
  { priority: "background" }
42479
43063
  );
42480
43064
  status = inspection.status === "ready" ? "ready" : inspection.status;
@@ -42693,6 +43277,18 @@ ${usage_default}`;
42693
43277
  if (!client?.setHarnessLaunchSettings) throw new Error("Launch settings are unavailable");
42694
43278
  return client.setHarnessLaunchSettings({ harnessId: harnessIdSchema.parse(agent), path });
42695
43279
  },
43280
+ async getGrokComputerUse(hostId2) {
43281
+ const client = hostId2 === "local" ? modelClientForHost(hostId2) : null;
43282
+ if (!client?.getGrokComputerUse)
43283
+ throw new Error("Grok Computer Use settings are unavailable");
43284
+ return client.getGrokComputerUse();
43285
+ },
43286
+ async setGrokComputerUse(hostId2, update) {
43287
+ const client = hostId2 === "local" ? modelClientForHost(hostId2) : null;
43288
+ if (!client?.setGrokComputerUse)
43289
+ throw new Error("Grok Computer Use settings are unavailable");
43290
+ return client.setGrokComputerUse(update);
43291
+ },
42696
43292
  async openWebUi(hostId2, agent) {
42697
43293
  const state = hostHarnessAvailabilityState(hostId2);
42698
43294
  const client = hostId2 === "local" ? modelClientForHost(hostId2) : null;
@@ -43335,6 +43931,18 @@ ${usage_default}`;
43335
43931
  // src/renderer-external-voice-launch.ts
43336
43932
  var HOOK_GLOBAL3 = "__codexhostExternalVoiceLaunchV1";
43337
43933
  var SHARED_ASSET = "app-shared-8bd9d0399038.js";
43934
+ var LAUNCH_ASSETS = [
43935
+ ...CLIENT_OWNED_VOICE_ASSETS.map((profile) => ({
43936
+ ...profile,
43937
+ services: CLIENT_OWNED_VOICE_SERVICES
43938
+ })),
43939
+ {
43940
+ version: AUDITED_CPA_LIVE_VOICE_DESKTOP_VERSION,
43941
+ initial: AUDITED_CPA_LIVE_VOICE_APP_INITIAL_ASSET,
43942
+ shared: SHARED_ASSET,
43943
+ services: "fN"
43944
+ }
43945
+ ];
43338
43946
  function isRecord16(value) {
43339
43947
  return typeof value === "object" && value !== null && !Array.isArray(value);
43340
43948
  }
@@ -43373,7 +43981,7 @@ ${usage_default}`;
43373
43981
  const config2 = readCpaLiveVoiceHookConfig(target);
43374
43982
  if (!config2) return { status: "disabled", dispose() {
43375
43983
  } };
43376
- if (config2.auditedDesktopVersion !== AUDITED_CPA_LIVE_VOICE_DESKTOP_VERSION || config2.auditedAppInitialAsset !== AUDITED_CPA_LIVE_VOICE_APP_INITIAL_ASSET || config2.desktopVersion !== void 0 && config2.desktopVersion !== config2.auditedDesktopVersion && config2.desktopVersion !== CLIENT_OWNED_VOICE_VERSION)
43984
+ if (config2.auditedDesktopVersion !== AUDITED_CPA_LIVE_VOICE_DESKTOP_VERSION || config2.auditedAppInitialAsset !== AUDITED_CPA_LIVE_VOICE_APP_INITIAL_ASSET || config2.desktopVersion !== void 0 && config2.desktopVersion !== config2.auditedDesktopVersion && !supportsVoiceVersion(CLIENT_OWNED_VOICE_ASSETS, config2.desktopVersion))
43377
43985
  return { status: "unsupported", dispose() {
43378
43986
  } };
43379
43987
  const importModule = options.importModule ?? ((url2) => import(url2));
@@ -43396,14 +44004,11 @@ ${usage_default}`;
43396
44004
  if (disposed) return;
43397
44005
  try {
43398
44006
  const names = loadedAssetNames(target.document, target.performance);
43399
- const clientOwned = auditedAppInitialAssetPresent(names, CLIENT_OWNED_VOICE_INITIAL_ASSET) && auditedAppInitialAssetPresent(names, CLIENT_OWNED_VOICE_SHARED_ASSET);
43400
- const legacy = auditedAppInitialAssetPresent(names, AUDITED_CPA_LIVE_VOICE_APP_INITIAL_ASSET) && auditedAppInitialAssetPresent(names, SHARED_ASSET);
43401
- if (clientOwned || legacy) {
43402
- module ??= await importModule(
43403
- `app://-/assets/${clientOwned ? CLIENT_OWNED_VOICE_SHARED_ASSET : SHARED_ASSET}`
43404
- );
44007
+ const assets = selectVoiceAssets(LAUNCH_ASSETS, names, config2.desktopVersion);
44008
+ if (assets) {
44009
+ module ??= await importModule(`app://-/assets/${assets.shared}`);
43405
44010
  if (disposed) return;
43406
- const services = clientOwned ? module?.Kat : module?.fN;
44011
+ const services = module?.[assets.services];
43407
44012
  if (isRecord16(services)) {
43408
44013
  const original = services.realtimeVoiceRuntime;
43409
44014
  if (typeof original === "function" || isRecord16(original)) {
@@ -43454,11 +44059,294 @@ ${usage_default}`;
43454
44059
  return handle;
43455
44060
  }
43456
44061
 
44062
+ // src/renderer-voice-capability.ts
44063
+ var GLOBAL = "__codexhostVoiceCapabilityV1";
44064
+ var INFERENCE_GATE = "3683713372";
44065
+ var record6 = (value) => value !== null && typeof value === "object";
44066
+ var capable = (value) => record6(value) && typeof value.isLoading === "boolean" && typeof value.isError === "boolean" && typeof value.isCapable === "boolean";
44067
+ var equal = (a, b) => a.isLoading === b.isLoading && a.isError === b.isError && a.isCapable === b.isCapable;
44068
+ function replayVoiceCapability(store, atom, gate) {
44069
+ const evaluate = (override) => {
44070
+ const visiting = /* @__PURE__ */ new Set();
44071
+ let remaining = 512;
44072
+ let hits = 0;
44073
+ const read = (dependency) => {
44074
+ if (--remaining < 0) throw new Error("Voice capability dependency limit");
44075
+ const value = store.get(dependency);
44076
+ if (dependency === gate) {
44077
+ hits += 1;
44078
+ if (typeof value !== "boolean") throw new Error("Voice inference gate unavailable");
44079
+ return override ? false : value;
44080
+ }
44081
+ if (dependency.write || visiting.has(dependency)) return value;
44082
+ if (typeof dependency.read !== "function") throw new Error("Voice dependency unavailable");
44083
+ visiting.add(dependency);
44084
+ try {
44085
+ return dependency.read(read);
44086
+ } finally {
44087
+ visiting.delete(dependency);
44088
+ }
44089
+ };
44090
+ return { value: read(atom), hits };
44091
+ };
44092
+ try {
44093
+ const native = store.get(atom);
44094
+ if (!capable(native) || native.isLoading || native.isError) return null;
44095
+ const before = evaluate(false);
44096
+ if (!capable(before.value) || !equal(native, before.value)) return null;
44097
+ const after = evaluate(true);
44098
+ return after.hits > 0 && capable(after.value) && !after.value.isLoading && !after.value.isError ? after.value : null;
44099
+ } catch {
44100
+ return null;
44101
+ }
44102
+ }
44103
+ function hooks(fiber) {
44104
+ const result = [];
44105
+ const seen = /* @__PURE__ */ new Set();
44106
+ let hook = fiber.memoizedState;
44107
+ while (record6(hook) && result.length < 4096 && !seen.has(hook)) {
44108
+ seen.add(hook);
44109
+ result.push(hook);
44110
+ hook = hook.next;
44111
+ }
44112
+ return hook == null ? result : [];
44113
+ }
44114
+ function composerFibers(composer, chat = false) {
44115
+ const fibers = /* @__PURE__ */ new Set();
44116
+ const rootKey = Object.getOwnPropertyNames(composer).find(
44117
+ (key) => key.startsWith("__reactFiber$")
44118
+ );
44119
+ const ancestors = rootKey ? committedReactAncestors(Object.getOwnPropertyDescriptor(composer, rootKey)?.value) : [];
44120
+ let root = ancestors[0];
44121
+ if (chat) {
44122
+ const owners = ancestors.filter(
44123
+ ({ memoizedProps: props }) => record6(props) && props.isWorkConversation === false && props.isTemporaryChat === false && props.shouldBlockExternalEgress === false && props.readOnly !== true && typeof props.conversationId === "string" && record6(props.composerSelectionContext) && props.composerSelectionContext.conversationId === props.conversationId
44124
+ );
44125
+ if (owners.length !== 1) return fibers;
44126
+ root = owners[0];
44127
+ }
44128
+ if (!root) return fibers;
44129
+ for (const element of [composer, ...composer.querySelectorAll("*")].slice(0, 2048)) {
44130
+ const key = Object.getOwnPropertyNames(element).find((key2) => key2.startsWith("__reactFiber$"));
44131
+ if (!key) continue;
44132
+ const ancestors2 = committedReactAncestors(Object.getOwnPropertyDescriptor(element, key)?.value);
44133
+ const boundary = ancestors2.indexOf(root);
44134
+ if (boundary < 0) continue;
44135
+ for (const fiber of ancestors2.slice(0, boundary + 1)) if (record6(fiber)) fibers.add(fiber);
44136
+ }
44137
+ return fibers;
44138
+ }
44139
+ function bindComposerVoiceCapability(composer, family, gates, allowed, chat = false) {
44140
+ return bindVoiceCapabilitySubscriptions(
44141
+ () => composerFibers(composer, chat),
44142
+ family,
44143
+ gates,
44144
+ allowed
44145
+ );
44146
+ }
44147
+ function bindVoiceCapabilitySubscriptions(currentFibers, family, gates, allowed, realtimeOnly = false) {
44148
+ const fibers = currentFibers();
44149
+ const restores = [];
44150
+ const bindings = [];
44151
+ try {
44152
+ for (const fiber of fibers) {
44153
+ const list = hooks(fiber);
44154
+ for (const memo of list) {
44155
+ const [signal, deps] = Array.isArray(memo.memoizedState) ? memo.memoizedState : [];
44156
+ if (!record6(signal) || !Array.isArray(deps) || deps.length !== 5 || deps[3] !== family || !(deps[1] instanceof Map) || deps[2] !== void 0)
44157
+ continue;
44158
+ const chain = deps[1];
44159
+ const node = chain.get(family.scope.id);
44160
+ const members = node?.familyBindings?.get(family);
44161
+ if (!node || !(members instanceof Map) || node.store !== signal.store) continue;
44162
+ const matches = [...members].filter(([, member]) => member.cacheKey === deps[0]);
44163
+ if (matches.length !== 1 || typeof matches[0]?.[0] !== "string") continue;
44164
+ const parameter = JSON.parse(matches[0][0]);
44165
+ if (!record6(parameter) || Object.keys(parameter).some((key) => key !== "name" && key !== "hostId") || parameter.name !== "voice.realtime" && parameter.name !== "voice.thread" || realtimeOnly && parameter.name !== "voice.realtime" || parameter.hostId !== void 0 && parameter.hostId !== "local" || parameter.name === "voice.thread" && parameter.hostId !== "local")
44166
+ continue;
44167
+ const subscriptions = list.filter((hook2) => {
44168
+ const state = hook2.memoizedState;
44169
+ return Array.isArray(state) && Array.isArray(state[1]) && state[1].length === 2 && state[1][0] === signal && state[1][1] === void 0 && typeof state[0]?.getSnapshot === "function";
44170
+ });
44171
+ if (subscriptions.length !== 1) throw new Error("Ambiguous voice subscription");
44172
+ const hook = subscriptions[0];
44173
+ const subscriber = hook.memoizedState[0];
44174
+ const instance = hook.next?.queue;
44175
+ const effect = hook.next?.next?.memoizedState;
44176
+ const store = signal.store;
44177
+ const gate = gates.resolve(node, chain, INFERENCE_GATE)?.atom;
44178
+ if (!gate || !instance || !record6(effect) || !Array.isArray(effect.deps) || effect.deps[0] !== subscriber.subscribe || typeof effect.create !== "function" || typeof instance.getSnapshot !== "function" || !capable(store.get(signal.atom)) || !capable(subscriber.getSnapshot()) || !capable(instance.getSnapshot()) || !equal(
44179
+ store.get(signal.atom),
44180
+ subscriber.getSnapshot()
44181
+ ) || !equal(subscriber.getSnapshot(), instance.getSnapshot()) || subscriber.createRender?.() != null)
44182
+ throw new Error("Unsupported voice subscription");
44183
+ for (const [object3, key] of [
44184
+ [store, "sub"],
44185
+ [subscriber, "getSnapshot"],
44186
+ [instance, "getSnapshot"]
44187
+ ])
44188
+ if (Object.getOwnPropertyDescriptor(object3, key)?.writable !== true)
44189
+ throw new Error("Voice subscription is not writable");
44190
+ let notify;
44191
+ const sub = store.sub;
44192
+ try {
44193
+ store.sub = (atom, listener) => {
44194
+ if (atom !== signal.atom || notify) throw new Error("Unexpected voice subscription");
44195
+ notify = listener;
44196
+ return () => {
44197
+ };
44198
+ };
44199
+ effect.create();
44200
+ } finally {
44201
+ store.sub = sub;
44202
+ }
44203
+ if (!notify) throw new Error("Voice listener unavailable");
44204
+ const original = subscriber.getSnapshot;
44205
+ const originalInstance = instance.getSnapshot;
44206
+ let previous;
44207
+ const snapshot = () => {
44208
+ const native = original.call(subscriber);
44209
+ if (!allowed()) return native;
44210
+ const projected = replayVoiceCapability(store, signal.atom, gate);
44211
+ if (!projected || !projected.isCapable) return native;
44212
+ if (!previous || !equal(previous, projected)) previous = projected;
44213
+ return previous;
44214
+ };
44215
+ subscriber.getSnapshot = snapshot;
44216
+ instance.getSnapshot = snapshot;
44217
+ const onChange = notify;
44218
+ restores.push(() => {
44219
+ if (subscriber.getSnapshot === snapshot) subscriber.getSnapshot = original;
44220
+ if (instance.getSnapshot === snapshot) instance.getSnapshot = originalInstance;
44221
+ onChange();
44222
+ });
44223
+ bindings.push({ subscriber, signal, instance });
44224
+ onChange();
44225
+ }
44226
+ }
44227
+ if (!restores.length) throw new Error("Owned voice capability unavailable");
44228
+ if (realtimeOnly && restores.length !== 1) throw new Error("Ambiguous voice starter");
44229
+ } catch (error51) {
44230
+ for (const restore of restores.reverse()) restore();
44231
+ throw error51;
44232
+ }
44233
+ return {
44234
+ dispose() {
44235
+ for (const restore of restores.splice(0).reverse()) restore();
44236
+ },
44237
+ current() {
44238
+ const current = [...currentFibers()].flatMap(hooks);
44239
+ return bindings.every(
44240
+ ({ subscriber, signal, instance }) => current.some(
44241
+ (hook) => Array.isArray(hook.memoizedState) && hook.memoizedState[0] === subscriber && hook.memoizedState[1]?.[0] === signal && hook.next?.queue === instance && (!realtimeOnly || capable(hook.next?.memoizedState) && hook.next.memoizedState.isCapable)
44242
+ )
44243
+ );
44244
+ }
44245
+ };
44246
+ }
44247
+ function installVoiceCapabilityProjection(options = {}) {
44248
+ const target = options.target ?? globalThis;
44249
+ if (target[GLOBAL]) return target[GLOBAL];
44250
+ const config2 = readCpaLiveVoiceHookConfig(target);
44251
+ if (!config2) return { status: "disabled", dispose() {
44252
+ } };
44253
+ if (!supportsVoiceVersion(CLIENT_OWNED_VOICE_ASSETS, config2.desktopVersion))
44254
+ return { status: "unsupported", dispose() {
44255
+ } };
44256
+ let timer;
44257
+ let binding;
44258
+ let composer;
44259
+ let disposed = false;
44260
+ const handle = {
44261
+ status: "pending",
44262
+ dispose() {
44263
+ disposed = true;
44264
+ target.clearTimeout(timer);
44265
+ binding?.dispose();
44266
+ if (target[GLOBAL] === handle) Reflect.deleteProperty(target, GLOBAL);
44267
+ }
44268
+ };
44269
+ target[GLOBAL] = handle;
44270
+ const hookActive = (key) => {
44271
+ const hook = target[key];
44272
+ return record6(hook) && hook.status === "active";
44273
+ };
44274
+ const currentRoute = () => {
44275
+ try {
44276
+ const routing = target.__codexhostHostRoutingV1;
44277
+ const route = routing?.forComposer();
44278
+ return route?.hostId === "local" && routing?.forHost("local") === route ? route : void 0;
44279
+ } catch {
44280
+ return void 0;
44281
+ }
44282
+ };
44283
+ const external = () => {
44284
+ try {
44285
+ return currentRoute()?.policy.selectedModel()?.startsWith("codexhost/") === true;
44286
+ } catch {
44287
+ return false;
44288
+ }
44289
+ };
44290
+ const chatGateway = () => !isWorkComposerSurface(target) && chatLiveVoicePreferenceAllowsOverride(target);
44291
+ const allowed = () => !!readCpaLiveVoiceHookConfig(target) && voiceGatewayIsReady(target) && hookActive("__codexhostClientOwnedVoiceV1") && hookActive("__codexhostExternalVoiceLaunchV1") && (chatGateway() || !!currentRoute() && (isWorkComposerSurface(target) || external() && target.document?.querySelectorAll("[data-chatgpt-composer]").length === 0) && (cpaLiveVoicePreferenceAllowsInstall(target) || external()));
44292
+ const refresh = async () => {
44293
+ if (disposed) return;
44294
+ try {
44295
+ const names = loadedAssetNames(target.document, target.performance);
44296
+ const assets = selectVoiceAssets(CLIENT_OWNED_VOICE_ASSETS, names, config2.desktopVersion);
44297
+ if (!assets) {
44298
+ handle.status = "unsupported";
44299
+ } else if (!allowed()) handle.status = "pending";
44300
+ else {
44301
+ const module = await (options.importModule ?? ((url2) => import(url2)))(
44302
+ `app://-/assets/${assets.initial}`
44303
+ );
44304
+ if (disposed) return;
44305
+ if (typeof module.njr !== "function")
44306
+ throw new Error("Voice capability exports unavailable");
44307
+ const composers = [
44308
+ ...target.document?.querySelectorAll(
44309
+ chatGateway() ? "[data-chatgpt-composer]" : "[data-codex-composer-root]"
44310
+ ) ?? []
44311
+ ].filter(
44312
+ (element) => element.getBoundingClientRect().width > 0 && !element.closest("[hidden], [inert]")
44313
+ );
44314
+ if (composers.length !== 1) throw new Error("Voice composer unavailable");
44315
+ if (composers[0] !== composer || !binding?.current()) {
44316
+ module.njr();
44317
+ binding?.dispose();
44318
+ binding = void 0;
44319
+ composer = composers[0];
44320
+ binding = bindComposerVoiceCapability(
44321
+ composer,
44322
+ module.tjr,
44323
+ module.uBr,
44324
+ allowed,
44325
+ chatGateway()
44326
+ );
44327
+ }
44328
+ handle.status = "active";
44329
+ }
44330
+ } catch {
44331
+ handle.status = "unsupported";
44332
+ }
44333
+ if (handle.status !== "active") {
44334
+ binding?.dispose();
44335
+ binding = void 0;
44336
+ composer = void 0;
44337
+ }
44338
+ if (!disposed) timer = target.setTimeout(() => void refresh(), 1e3);
44339
+ };
44340
+ void refresh();
44341
+ return handle;
44342
+ }
44343
+
43457
44344
  // src/production-entry.ts
43458
44345
  installCpaLiveVoiceHook();
43459
44346
  installClientOwnedVoiceHook();
43460
44347
  installExternalVoiceLaunchHook();
43461
44348
  installNativeChatVoiceHook();
44349
+ installVoiceCapabilityProjection();
43462
44350
  var installBinding = () => {
43463
44351
  installRendererBinding();
43464
44352
  };