@codexhost/cli-win32-x64 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,7 +7,7 @@ Run Pi and Claude Code as first-class external harnesses inside Codex Desktop.
7
7
  ## Install
8
8
 
9
9
  ```bash
10
- npm install -g @codexhost/cli@0.1.2
10
+ npm install -g @codexhost/cli@0.1.4
11
11
  ```
12
12
 
13
13
  Do not install this package directly. npm selects it through the optional dependencies of `@codexhost/cli`.
@@ -1 +1 @@
1
- {"schemaVersion":1,"version":"0.1.2","distribution":"npm","target":"windows-x64"}
1
+ {"schemaVersion":1,"version":"0.1.4","distribution":"npm","target":"windows-x64"}
@@ -788,30 +788,12 @@ async function installRendererDraftPrewarmPolicy(inspector, rendererWebContentsI
788
788
  }
789
789
 
790
790
  // packages/desktop-control/src/renderer-control-session.ts
791
- var RendererCompatibilityError = class extends Error {
792
- constructor(capability, reason, cause) {
793
- super(`Renderer compatibility boundary unavailable: ${capability}`, { cause });
794
- this.capability = capability;
795
- this.reason = reason;
796
- this.name = "RendererCompatibilityError";
797
- }
798
- capability;
799
- reason;
800
- };
801
791
  function isRecord4(value) {
802
792
  return typeof value === "object" && value !== null && !Array.isArray(value);
803
793
  }
804
794
  function sleep(milliseconds) {
805
795
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
806
796
  }
807
- async function requireCompatibilityBoundary(capability, reason, operation) {
808
- try {
809
- return await operation();
810
- } catch (error) {
811
- if (error instanceof RendererCompatibilityError) throw error;
812
- throw new RendererCompatibilityError(capability, reason, error);
813
- }
814
- }
815
797
  function sameAgents(actual, expected) {
816
798
  return actual.length === expected.length && actual.every((agent, index) => agent === expected[index]);
817
799
  }
@@ -1040,39 +1022,26 @@ var InstalledRendererControlSession = class {
1040
1022
  );
1041
1023
  const existing = await this.operations.readBinding(this.inspector, selected.id).catch(() => null);
1042
1024
  if (existing !== null) {
1043
- const binding2 = await requireCompatibilityBoundary(
1044
- "agent-routing",
1045
- "agent-routing-structure-unavailable",
1046
- () => validateBindingStatus(existing, this.enabledAgents)
1047
- );
1025
+ const binding2 = validateBindingStatus(existing, this.enabledAgents);
1048
1026
  this.#snapshot = { ...this.#snapshot, renderer: selected, binding: binding2 };
1049
1027
  return this.#snapshot;
1050
1028
  }
1051
- const titlePolicyReadiness = await requireCompatibilityBoundary(
1052
- "title-isolation",
1053
- "title-isolation-structure-unavailable",
1054
- () => waitForRendererTitlePolicyReady(
1055
- () => this.operations.markTitlePolicyReady(this.inspector, selected.id),
1056
- { timeoutMs: this.timeoutMs, pollIntervalMs: this.pollIntervalMs }
1057
- )
1029
+ const titlePolicyReadiness = await waitForRendererTitlePolicyReady(
1030
+ () => this.operations.markTitlePolicyReady(this.inspector, selected.id),
1031
+ { timeoutMs: this.timeoutMs, pollIntervalMs: this.pollIntervalMs }
1058
1032
  );
1059
1033
  await this.operations.execute(this.inspector, selected.id, this.rendererSource);
1060
- const binding = await requireCompatibilityBoundary(
1061
- "agent-routing",
1062
- "agent-routing-structure-unavailable",
1063
- () => waitForBinding(
1064
- this.inspector,
1065
- this.operations,
1066
- selected.id,
1067
- this.enabledAgents,
1068
- this.timeoutMs,
1069
- this.pollIntervalMs
1070
- )
1034
+ const binding = await waitForBinding(
1035
+ this.inspector,
1036
+ this.operations,
1037
+ selected.id,
1038
+ this.enabledAgents,
1039
+ this.timeoutMs,
1040
+ this.pollIntervalMs
1071
1041
  );
1072
- const draftPrewarmPolicy = await requireCompatibilityBoundary(
1073
- "draft-routing",
1074
- "draft-routing-structure-unavailable",
1075
- () => this.operations.installDraftPrewarmPolicy(this.inspector, selected.id)
1042
+ const draftPrewarmPolicy = await this.operations.installDraftPrewarmPolicy(
1043
+ this.inspector,
1044
+ selected.id
1076
1045
  );
1077
1046
  this.#snapshot = {
1078
1047
  ...this.#snapshot,
@@ -1124,41 +1093,28 @@ async function createRendererControlSession(options) {
1124
1093
  const pollIntervalMs = options.pollIntervalMs ?? 250;
1125
1094
  const operations = options.operations ?? defaultOperations;
1126
1095
  const initial = await waitForRenderer(options.inspector, operations, timeoutMs, pollIntervalMs);
1127
- const titlePolicy = await requireCompatibilityBoundary(
1128
- "title-isolation",
1129
- "title-isolation-structure-unavailable",
1130
- () => operations.installTitlePolicy(options.inspector, initial.id)
1131
- );
1096
+ const titlePolicy = await operations.installTitlePolicy(options.inspector, initial.id);
1132
1097
  await operations.reload(options.inspector, initial.id);
1133
1098
  const selected = await waitForRenderer(options.inspector, operations, timeoutMs, pollIntervalMs);
1134
- const titlePolicyReadiness = await requireCompatibilityBoundary(
1135
- "title-isolation",
1136
- "title-isolation-structure-unavailable",
1137
- () => waitForRendererTitlePolicyReady(
1138
- () => operations.markTitlePolicyReady(options.inspector, selected.id),
1139
- {
1140
- timeoutMs,
1141
- pollIntervalMs
1142
- }
1143
- )
1144
- );
1145
- await operations.execute(options.inspector, selected.id, options.rendererSource);
1146
- const binding = await requireCompatibilityBoundary(
1147
- "agent-routing",
1148
- "agent-routing-structure-unavailable",
1149
- () => waitForBinding(
1150
- options.inspector,
1151
- operations,
1152
- selected.id,
1153
- enabledAgents,
1099
+ const titlePolicyReadiness = await waitForRendererTitlePolicyReady(
1100
+ () => operations.markTitlePolicyReady(options.inspector, selected.id),
1101
+ {
1154
1102
  timeoutMs,
1155
1103
  pollIntervalMs
1156
- )
1104
+ }
1105
+ );
1106
+ await operations.execute(options.inspector, selected.id, options.rendererSource);
1107
+ const binding = await waitForBinding(
1108
+ options.inspector,
1109
+ operations,
1110
+ selected.id,
1111
+ enabledAgents,
1112
+ timeoutMs,
1113
+ pollIntervalMs
1157
1114
  );
1158
- const draftPrewarmPolicy = await requireCompatibilityBoundary(
1159
- "draft-routing",
1160
- "draft-routing-structure-unavailable",
1161
- () => operations.installDraftPrewarmPolicy(options.inspector, selected.id)
1115
+ const draftPrewarmPolicy = await operations.installDraftPrewarmPolicy(
1116
+ options.inspector,
1117
+ selected.id
1162
1118
  );
1163
1119
  return new InstalledRendererControlSession(
1164
1120
  options.inspector,
@@ -1203,6 +1159,8 @@ var PRODUCTION_INSTALL_TIMEOUT_MS = 9e4;
1203
1159
  var DESKTOP_CONTROLLER_READINESS_MAX_BYTES = 512;
1204
1160
  var TRANSIENT_INSTALL_ATTEMPTS = 3;
1205
1161
  var TRANSIENT_INSTALL_RETRY_MS = 250;
1162
+ var RECOVERY_RETRY_INITIAL_MS = 3e4;
1163
+ var RECOVERY_RETRY_MAX_MS = 3e5;
1206
1164
  function validCompatibilityIssue(state, issue) {
1207
1165
  const keys = Object.keys(issue);
1208
1166
  if (state === "compatible-with-warning") {
@@ -1217,24 +1175,10 @@ function validCompatibilityIssue(state, issue) {
1217
1175
  "settings-surface"
1218
1176
  ].includes(issue.capability) && issue.reason === "capability-unavailable" && issue.observedIdentity === void 0 && keys.length === 2;
1219
1177
  }
1220
- if (state === "incompatible") {
1221
- const pair = `${issue.capability}:${issue.reason}`;
1222
- return [
1223
- "title-isolation:title-isolation-structure-unavailable",
1224
- "draft-routing:draft-routing-structure-unavailable",
1225
- "agent-routing:agent-routing-structure-unavailable"
1226
- ].includes(pair) && issue.observedIdentity === void 0 && keys.length === 2;
1227
- }
1228
- return state === "detection-failed" && issue.capability === "compatibility-detection" && issue.reason === "inspection-failed" && issue.observedIdentity === void 0 && keys.length === 2;
1178
+ return false;
1229
1179
  }
1230
1180
  function serializeDesktopControllerReadiness(readiness) {
1231
- if (readiness.schemaVersion !== 2 || ![
1232
- "compatible",
1233
- "compatible-with-warning",
1234
- "degraded",
1235
- "incompatible",
1236
- "detection-failed"
1237
- ].includes(readiness.state) || !Array.isArray(readiness.issues) || readiness.issues.length > 1 || readiness.state === "compatible" && readiness.issues.length !== 0 || readiness.state !== "compatible" && (readiness.issues.length !== 1 || !readiness.issues[0] || !validCompatibilityIssue(readiness.state, readiness.issues[0])) || Object.keys(readiness).length !== 3) {
1181
+ if (readiness.schemaVersion !== 2 || !["compatible", "compatible-with-warning", "degraded"].includes(readiness.state) || !Array.isArray(readiness.issues) || readiness.issues.length > 1 || readiness.state === "compatible" && readiness.issues.length !== 0 || readiness.state !== "compatible" && (readiness.issues.length !== 1 || !readiness.issues[0] || !validCompatibilityIssue(readiness.state, readiness.issues[0])) || Object.keys(readiness).length !== 3) {
1238
1182
  throw new Error("Desktop Controller readiness is invalid");
1239
1183
  }
1240
1184
  const line = JSON.stringify(readiness);
@@ -1358,12 +1302,23 @@ async function installProductionSession(options, dependencies) {
1358
1302
  throw new Error("Desktop Controller exhausted Renderer installation attempts");
1359
1303
  }
1360
1304
  async function runDesktopController(options, signal, dependencies = defaultDependencies) {
1305
+ const configuration = `Object.defineProperty(window, "__codexhostProductionConfigV1", { configurable: true, value: { defaultAgent: ${JSON.stringify(options.defaultAgent)} } });`;
1306
+ const now = dependencies.now ?? Date.now;
1361
1307
  let session;
1362
- try {
1308
+ let nextRecoveryAt = 0;
1309
+ let recoveryDelayMs = RECOVERY_RETRY_INITIAL_MS;
1310
+ const recordRecoveryFailure = () => {
1311
+ nextRecoveryAt = now() + recoveryDelayMs;
1312
+ recoveryDelayMs = Math.min(recoveryDelayMs * 2, RECOVERY_RETRY_MAX_MS);
1313
+ };
1314
+ const recordRecoverySuccess = () => {
1315
+ nextRecoveryAt = 0;
1316
+ recoveryDelayMs = RECOVERY_RETRY_INITIAL_MS;
1317
+ };
1318
+ const createSession = async () => {
1363
1319
  const rendererSource = await dependencies.readRenderer(options.rendererPath);
1364
1320
  if (rendererSource.trim().length === 0) throw new Error("production Renderer Bundle is empty");
1365
- const configuration = `Object.defineProperty(window, "__codexhostProductionConfigV1", { configurable: true, value: { defaultAgent: ${JSON.stringify(options.defaultAgent)} } });`;
1366
- session = await installProductionSession(
1321
+ return installProductionSession(
1367
1322
  {
1368
1323
  inspectorEndpoint: options.inspectorEndpoint,
1369
1324
  rendererSource: `${configuration}
@@ -1373,19 +1328,13 @@ ${rendererSource}`,
1373
1328
  },
1374
1329
  dependencies
1375
1330
  );
1376
- } catch (error) {
1377
- dependencies.ready(
1378
- error instanceof RendererCompatibilityError && !isTransientElectronInstallError(error) ? {
1379
- schemaVersion: 2,
1380
- state: "incompatible",
1381
- issues: [{ capability: error.capability, reason: error.reason }]
1382
- } : {
1383
- schemaVersion: 2,
1384
- state: "detection-failed",
1385
- issues: [{ capability: "compatibility-detection", reason: "inspection-failed" }]
1386
- }
1387
- );
1388
- return;
1331
+ };
1332
+ try {
1333
+ session = await createSession();
1334
+ recordRecoverySuccess();
1335
+ } catch {
1336
+ session = void 0;
1337
+ recordRecoveryFailure();
1389
1338
  }
1390
1339
  let operation = Promise.resolve(void 0);
1391
1340
  const useSession = (callback) => {
@@ -1396,22 +1345,45 @@ ${rendererSource}`,
1396
1345
  );
1397
1346
  return next;
1398
1347
  };
1348
+ const resetSession = () => {
1349
+ session?.close();
1350
+ session = void 0;
1351
+ };
1352
+ const ensureSession = async () => {
1353
+ if (!session) session = await createSession();
1354
+ else await session.ensureInstalled();
1355
+ return session;
1356
+ };
1357
+ const recoverSession = async () => {
1358
+ try {
1359
+ const current = await ensureSession();
1360
+ recordRecoverySuccess();
1361
+ return current;
1362
+ } catch (error) {
1363
+ resetSession();
1364
+ recordRecoveryFailure();
1365
+ throw error;
1366
+ }
1367
+ };
1399
1368
  let attachmentServer;
1400
1369
  try {
1401
1370
  attachmentServer = await dependencies.startAttachmentServer({
1402
1371
  port: options.attachmentPort,
1403
1372
  nonce: options.attachmentNonce,
1404
1373
  attach: () => useSession(async () => {
1405
- await session.ensureInstalled();
1406
- await session.activateDesktop();
1374
+ const current = await recoverSession();
1375
+ await current.activateDesktop();
1407
1376
  }),
1408
1377
  compatibilityUpdate: () => useSession(async () => {
1409
- await session.ensureInstalled();
1410
- return session.requestCompatibilityUpdate();
1378
+ const current = await recoverSession();
1379
+ return current.requestCompatibilityUpdate();
1411
1380
  }),
1412
- shutdown: () => useSession(() => session.quitDesktop())
1381
+ shutdown: () => useSession(async () => {
1382
+ const current = await recoverSession();
1383
+ await current.quitDesktop();
1384
+ })
1413
1385
  });
1414
- const issues = session.snapshot.titlePolicy.warnings;
1386
+ const issues = session?.snapshot.titlePolicy.warnings ?? [];
1415
1387
  dependencies.ready({
1416
1388
  schemaVersion: 2,
1417
1389
  state: issues.length === 0 ? "compatible" : "compatible-with-warning",
@@ -1419,12 +1391,19 @@ ${rendererSource}`,
1419
1391
  });
1420
1392
  while (!signal.aborted) {
1421
1393
  await dependencies.sleep(dependencies.monitorIntervalMs);
1422
- if (!signal.aborted) await useSession(() => session.ensureInstalled());
1394
+ if (signal.aborted) continue;
1395
+ await useSession(async () => {
1396
+ if (!session && now() < nextRecoveryAt) return;
1397
+ try {
1398
+ await recoverSession();
1399
+ } catch {
1400
+ }
1401
+ });
1423
1402
  }
1424
1403
  } finally {
1425
1404
  await attachmentServer?.close();
1426
1405
  await operation;
1427
- session.close();
1406
+ resetSession();
1428
1407
  }
1429
1408
  }
1430
1409
 
@@ -50582,7 +50582,7 @@ function isHostApprovalRequestId(value) {
50582
50582
  function isHostQuestionRequestId(value) {
50583
50583
  return typeof value === "number" && Number.isSafeInteger(value) && value >= HOST_QUESTION_REQUEST_ID_MIN && value <= HOST_QUESTION_REQUEST_ID_MAX;
50584
50584
  }
50585
- function classifyCreateRequestRoute(request, defaultAgent) {
50585
+ function classifyCreateRequestRoute(request, defaultAgent2) {
50586
50586
  const route = decodeCreateRoute(request);
50587
50587
  if (!route) return null;
50588
50588
  if (route.harnessId !== "codex") {
@@ -50596,8 +50596,8 @@ function classifyCreateRequestRoute(request, defaultAgent) {
50596
50596
  return {
50597
50597
  requestMethod: "thread/start",
50598
50598
  modelCarrier: "official-model",
50599
- selectedHarness: defaultAgent,
50600
- selectionSource: defaultAgent === "pi" ? "default-agent" : "official-model"
50599
+ selectedHarness: defaultAgent2,
50600
+ selectionSource: defaultAgent2 === "pi" ? "default-agent" : "official-model"
50601
50601
  };
50602
50602
  }
50603
50603
  function requestObject(request) {
@@ -52789,13 +52789,25 @@ async function acquireUpdateOperationLock(stateDirectory) {
52789
52789
  }
52790
52790
  };
52791
52791
  }
52792
+ function processIsAlive(processId) {
52793
+ if (!Number.isSafeInteger(processId) || processId <= 0)
52794
+ return false;
52795
+ try {
52796
+ process.kill(processId, 0);
52797
+ return true;
52798
+ } catch (error52) {
52799
+ return error52.code === "EPERM";
52800
+ }
52801
+ }
52792
52802
  async function recoverUpdateOperationLock(stateDirectory) {
52793
52803
  const lockPath = path9.join(stateDirectory, LOCK_FILE);
52794
52804
  if (!await regularFile(lockPath))
52795
52805
  return;
52806
+ let ownerPid;
52796
52807
  let statusPath;
52797
52808
  try {
52798
52809
  const value = JSON.parse(await readFile3(lockPath, "utf8"));
52810
+ ownerPid = value.ownerPid;
52799
52811
  statusPath = value.statusPath;
52800
52812
  } catch {
52801
52813
  return;
@@ -52804,8 +52816,9 @@ async function recoverUpdateOperationLock(stateDirectory) {
52804
52816
  return;
52805
52817
  try {
52806
52818
  const status = parseUpdateStatus(JSON.parse(await readFile3(statusPath, "utf8")));
52807
- if (TERMINAL_PHASES.has(status.phase))
52819
+ if (TERMINAL_PHASES.has(status.phase) || typeof ownerPid !== "number" || !processIsAlive(ownerPid)) {
52808
52820
  await rm3(lockPath, { force: true });
52821
+ }
52809
52822
  } catch {
52810
52823
  }
52811
52824
  }
@@ -53149,24 +53162,36 @@ function createBackgroundUpdateManager(dependencies = {}) {
53149
53162
  // packages/host-runtime/src/update-coordinator.ts
53150
53163
  var ERROR_MAX_LENGTH = 500;
53151
53164
  var CONTROLLER_TIMEOUT_MS = 5e3;
53152
- async function startCompatibilityUpdate(coordinator) {
53153
- try {
53154
- const check2 = await coordinator.check();
53155
- if (check2.updateAvailable && check2.installationAvailable) {
53156
- void coordinator.start().catch(() => void 0);
53157
- return "update-started";
53158
- }
53159
- if (check2.error === null && check2.latestVersion !== null && check2.currentVersion === check2.latestVersion) {
53160
- return "current";
53161
- }
53162
- } catch {
53163
- }
53164
- return "unavailable";
53165
- }
53165
+ var UPDATER_READY_TIMEOUT_MS = 1e4;
53166
+ var UPDATER_READY_POLL_MS = 20;
53166
53167
  function boundedError(error52) {
53167
53168
  const message3 = error52 instanceof Error ? error52.message : String(error52);
53168
53169
  return message3.slice(0, ERROR_MAX_LENGTH) || "Update operation failed";
53169
53170
  }
53171
+ function delay4(milliseconds) {
53172
+ return new Promise((resolve2) => setTimeout(resolve2, milliseconds));
53173
+ }
53174
+ async function waitForUpdaterReady(manager, statusPath) {
53175
+ const deadline = Date.now() + UPDATER_READY_TIMEOUT_MS;
53176
+ while (Date.now() < deadline) {
53177
+ const status = await manager.readStatus(statusPath);
53178
+ switch (status?.phase) {
53179
+ case "waiting-for-exit":
53180
+ case "installing":
53181
+ case "restarting":
53182
+ case "succeeded":
53183
+ return;
53184
+ case "failed":
53185
+ throw new Error(status.error ?? "Background Updater failed before Desktop shutdown");
53186
+ case "prepared":
53187
+ break;
53188
+ default:
53189
+ throw new Error("Background Updater reported an unexpected startup phase");
53190
+ }
53191
+ await delay4(UPDATER_READY_POLL_MS);
53192
+ }
53193
+ throw new Error("Background Updater did not become ready before Desktop shutdown");
53194
+ }
53170
53195
  function publicStatus(status) {
53171
53196
  return {
53172
53197
  version: status.version,
@@ -53207,7 +53232,8 @@ function requestControllerShutdown(controller) {
53207
53232
  });
53208
53233
  }
53209
53234
  function createHostUpdateCoordinator(options) {
53210
- const manager = options.manager ?? createBackgroundUpdateManager(options.platform ? { platform: options.platform } : {});
53235
+ const platform = options.platform ?? process.platform;
53236
+ const manager = options.manager ?? createBackgroundUpdateManager({ platform });
53211
53237
  const contextPromise = resolveInstalledUpdateContext({
53212
53238
  hostRuntimePath: options.hostRuntimePath,
53213
53239
  ...options.environment ? { environment: options.environment } : {},
@@ -53350,7 +53376,8 @@ function createHostUpdateCoordinator(options) {
53350
53376
  onPrepared
53351
53377
  });
53352
53378
  }
53353
- manager.start(prepared2);
53379
+ if (platform !== "darwin") manager.start(prepared2);
53380
+ await waitForUpdaterReady(manager, prepared2.statusPath);
53354
53381
  shutdownPending = context.controller;
53355
53382
  scheduleShutdown();
53356
53383
  } catch (error52) {
@@ -53387,31 +53414,25 @@ function createHostUpdateCoordinator(options) {
53387
53414
  // packages/host-runtime/src/release-main.ts
53388
53415
  var STOCK_CODEX_PATH_ENV = "CODEXHOST_STOCK_CODEX_PATH";
53389
53416
  var DEFAULT_AGENT_ENV = "CODEXHOST_DEFAULT_AGENT";
53390
- var COMPATIBILITY_UPDATE_ARGUMENT = "--codexhost-compatibility-update";
53391
53417
  var arguments_ = process.argv.slice(2);
53392
53418
  var updateCoordinator = createHostUpdateCoordinator({
53393
53419
  hostRuntimePath: fileURLToPath(import.meta.url),
53394
53420
  environment: process.env
53395
53421
  });
53396
- if (arguments_.length === 1 && arguments_[0] === COMPATIBILITY_UPDATE_ARGUMENT) {
53397
- process.stdout.write(`${await startCompatibilityUpdate(updateCoordinator)}
53398
- `);
53399
- } else {
53400
- const stockCodexPath = process.env[STOCK_CODEX_PATH_ENV];
53401
- if (!stockCodexPath) throw new Error(`${STOCK_CODEX_PATH_ENV} is required`);
53402
- const defaultAgent = process.env[DEFAULT_AGENT_ENV];
53403
- if (defaultAgent !== "codex" && defaultAgent !== "pi") {
53404
- throw new Error(`${DEFAULT_AGENT_ENV} must be 'codex' or 'pi'`);
53405
- }
53406
- const externalAdapters = createExternalHarnessAdapters(process.env);
53407
- const host = new AppServerHost({
53408
- stockCodexPath,
53409
- arguments: arguments_,
53410
- defaultAgent,
53411
- environment: process.env,
53412
- externalAdapters,
53413
- updateCoordinator
53414
- });
53415
- void prefetchClaudeCodeModelCatalog(externalAdapters);
53416
- process.exitCode = await host.run();
53417
- }
53422
+ var stockCodexPath = process.env[STOCK_CODEX_PATH_ENV];
53423
+ if (!stockCodexPath) throw new Error(`${STOCK_CODEX_PATH_ENV} is required`);
53424
+ var defaultAgent = process.env[DEFAULT_AGENT_ENV];
53425
+ if (defaultAgent !== "codex" && defaultAgent !== "pi") {
53426
+ throw new Error(`${DEFAULT_AGENT_ENV} must be 'codex' or 'pi'`);
53427
+ }
53428
+ var externalAdapters = createExternalHarnessAdapters(process.env);
53429
+ var host = new AppServerHost({
53430
+ stockCodexPath,
53431
+ arguments: arguments_,
53432
+ defaultAgent,
53433
+ environment: process.env,
53434
+ externalAdapters,
53435
+ updateCoordinator
53436
+ });
53437
+ void prefetchClaudeCodeModelCatalog(externalAdapters);
53438
+ process.exitCode = await host.run();
@@ -17223,9 +17223,52 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17223
17223
  var THREAD_PERMISSION_MODE_SELECT_METHOD = "codexhost/thread/permission-mode/select";
17224
17224
  var THREAD_OWNERSHIP_LIST_METHOD = "codexhost/thread/ownership/list";
17225
17225
  var THREAD_USAGE_INSPECT_METHOD = "codexhost/thread/usage/inspect";
17226
+ var THREAD_TOKEN_USAGE_UPDATED_METHOD = "thread/tokenUsage/updated";
17226
17227
  var UPDATE_CHECK_METHOD = "codexhost/update/check";
17227
17228
  var UPDATE_START_METHOD = "codexhost/update/start";
17228
17229
  var UPDATE_STATUS_METHOD = "codexhost/update/status";
17230
+ function isRecord4(value) {
17231
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17232
+ }
17233
+ function notifiedThreadId(notification) {
17234
+ if (!isRecord4(notification) || notification.method !== THREAD_TOKEN_USAGE_UPDATED_METHOD) {
17235
+ return null;
17236
+ }
17237
+ const params = notification.params;
17238
+ if (!isRecord4(params)) return null;
17239
+ const parsed = hostThreadIdSchema.safeParse(params.threadId);
17240
+ return parsed.success ? parsed.data : null;
17241
+ }
17242
+ function createThreadUsageSubscriptionRelay() {
17243
+ const listeners = /* @__PURE__ */ new Set();
17244
+ let removeNotificationCallback = null;
17245
+ return {
17246
+ connect(client) {
17247
+ if (removeNotificationCallback || listeners.size === 0) return;
17248
+ try {
17249
+ removeNotificationCallback = client.subscribeThreadUsage?.((update) => {
17250
+ for (const listener of listeners) listener(update);
17251
+ }) ?? null;
17252
+ } catch {
17253
+ removeNotificationCallback = null;
17254
+ }
17255
+ },
17256
+ subscribe(listener) {
17257
+ listeners.add(listener);
17258
+ return () => {
17259
+ listeners.delete(listener);
17260
+ if (listeners.size > 0) return;
17261
+ removeNotificationCallback?.();
17262
+ removeNotificationCallback = null;
17263
+ };
17264
+ },
17265
+ dispose() {
17266
+ removeNotificationCallback?.();
17267
+ removeNotificationCallback = null;
17268
+ listeners.clear();
17269
+ }
17270
+ };
17271
+ }
17229
17272
  function createRendererModelClient(candidates) {
17230
17273
  const managers = candidates.filter(
17231
17274
  (candidate) => typeof candidate.sendRequest === "function"
@@ -17279,6 +17322,29 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17279
17322
  return result;
17280
17323
  },
17281
17324
  inspectThreadUsage,
17325
+ subscribeThreadUsage(listener) {
17326
+ if (typeof manager.addNotificationCallback !== "function") return () => void 0;
17327
+ let disposed = false;
17328
+ const generations = /* @__PURE__ */ new Map();
17329
+ const removeNotificationCallback = manager.addNotificationCallback(
17330
+ THREAD_TOKEN_USAGE_UPDATED_METHOD,
17331
+ (notification) => {
17332
+ const threadId = notifiedThreadId(notification);
17333
+ if (!threadId) return;
17334
+ const generation = (generations.get(threadId) ?? 0) + 1;
17335
+ generations.set(threadId, generation);
17336
+ void inspectThreadUsage({ threadId }).then((update) => {
17337
+ if (!disposed && generations.get(threadId) === generation) listener(update);
17338
+ }).catch(() => void 0);
17339
+ }
17340
+ );
17341
+ return () => {
17342
+ if (disposed) return;
17343
+ disposed = true;
17344
+ generations.clear();
17345
+ removeNotificationCallback();
17346
+ };
17347
+ },
17282
17348
  selectThreadModel,
17283
17349
  selectThreadThinking,
17284
17350
  selectThreadPermissionMode,
@@ -17331,7 +17397,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17331
17397
  function isTransportModelId(model) {
17332
17398
  return isPiTransportModelId(model) || isClaudeTransportModelId(model);
17333
17399
  }
17334
- function isRecord4(value) {
17400
+ function isRecord5(value) {
17335
17401
  return typeof value === "object" && value !== null && !Array.isArray(value);
17336
17402
  }
17337
17403
  function piTransportModelId(model, thinkingOptionId) {
@@ -17402,7 +17468,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17402
17468
  return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.prewarmThreadStart === "function";
17403
17469
  }
17404
17470
  function isActiveRequestManager(value) {
17405
- if (!isRecord4(value) || typeof value.sendRequest !== "function") return false;
17471
+ if (!isRecord5(value) || typeof value.sendRequest !== "function") return false;
17406
17472
  const source = Function.prototype.toString.call(value.sendRequest);
17407
17473
  return source.includes("send-cli-request-for-host") && hasPrewarmMethod(value.requestClient);
17408
17474
  }
@@ -17438,7 +17504,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17438
17504
  let hook = fiber.memoizedState;
17439
17505
  for (let hookIndex = 0; hook && hookIndex < 100; hookIndex += 1) {
17440
17506
  const hookState = hook.memoizedState;
17441
- if (isRecord4(hookState)) {
17507
+ if (isRecord5(hookState)) {
17442
17508
  const requestClient = hookState.requestClient;
17443
17509
  if (isActiveRequestManager(hookState) && matchesCurrentPrewarmSignature(hookState)) {
17444
17510
  targets.add(hookState);
@@ -17455,10 +17521,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17455
17521
  return [...targets];
17456
17522
  }
17457
17523
  function isModelAtomState(value) {
17458
- return isRecord4(value) && isRecord4(value.atom) && typeof value.get === "function" && typeof value.set === "function";
17524
+ return isRecord5(value) && isRecord5(value.atom) && typeof value.get === "function" && typeof value.set === "function";
17459
17525
  }
17460
17526
  function isModelSelection(value) {
17461
- return value === null || isRecord4(value) && "model" in value && "reasoningEffort" in value;
17527
+ return value === null || isRecord5(value) && "model" in value && "reasoningEffort" in value;
17462
17528
  }
17463
17529
  function sameModelPowerSelection(left, right) {
17464
17530
  return left === right || left !== null && right !== null && left.model === right.model && left.reasoningEffort === right.reasoningEffort;
@@ -17470,7 +17536,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17470
17536
  const first = pairs[0];
17471
17537
  if (!first) return null;
17472
17538
  const value = first.optimistic.get();
17473
- if (isRecord4(value) && "modelSettings" in value) return first.optimistic;
17539
+ if (isRecord5(value) && "modelSettings" in value) return first.optimistic;
17474
17540
  if (first.optimistic.atom === first.committed.atom) return null;
17475
17541
  if (!pairs.every(
17476
17542
  (pair) => pair.optimistic.atom === first.optimistic.atom && pair.committed.atom === first.committed.atom && sameTarget2(pair.target, first.target)
@@ -17504,7 +17570,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17504
17570
  let fiber = fiberName ? Object.getOwnPropertyDescriptor(button, fiberName)?.value : null;
17505
17571
  for (let depth = 0; fiber && depth < 60; depth += 1) {
17506
17572
  const props = fiber.memoizedProps;
17507
- if (isRecord4(props) && typeof props.onSelectModel === "function" && typeof props.onSelectReasoningEffort === "function" && "reasoningEffort" in props && isRecord4(props.fallbackPowerSelection)) {
17573
+ if (isRecord5(props) && typeof props.onSelectModel === "function" && typeof props.onSelectReasoningEffort === "function" && "reasoningEffort" in props && isRecord5(props.fallbackPowerSelection)) {
17508
17574
  matches.push({ callback: props.onSelectModel, value: props.reasoningEffort });
17509
17575
  }
17510
17576
  const parent = fiber.return;
@@ -17529,9 +17595,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17529
17595
  let fiber = fiberName ? Object.getOwnPropertyDescriptor(button, fiberName)?.value : null;
17530
17596
  for (let depth = 0; fiber && depth < 60; depth += 1) {
17531
17597
  const props = fiber.memoizedProps;
17532
- if (!control && isRecord4(props) && "model" in props && "reasoningEffort" in props && typeof props.onSelectModel === "function") {
17598
+ if (!control && isRecord5(props) && "model" in props && "reasoningEffort" in props && typeof props.onSelectModel === "function") {
17533
17599
  const selection = { model: props.model, reasoningEffort: props.reasoningEffort };
17534
- const fallbackSelection = isRecord4(props.fallbackPowerSelection) ? props.fallbackPowerSelection : null;
17600
+ const fallbackSelection = isRecord5(props.fallbackPowerSelection) ? props.fallbackPowerSelection : null;
17535
17601
  control = {
17536
17602
  codexSelection: isTransportModelId(selection.model) ? fallbackSelection : selection,
17537
17603
  selection,
@@ -17557,8 +17623,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17557
17623
  let fiber = findComposerFiber(composer);
17558
17624
  for (let depth = 0; fiber && depth < 120; depth += 1) {
17559
17625
  const updateQueue = fiber.updateQueue;
17560
- const memoCache = isRecord4(updateQueue) ? updateQueue.memoCache : null;
17561
- const data = isRecord4(memoCache) && Array.isArray(memoCache.data) ? memoCache.data : [];
17626
+ const memoCache = isRecord5(updateQueue) ? updateQueue.memoCache : null;
17627
+ const data = isRecord5(memoCache) && Array.isArray(memoCache.data) ? memoCache.data : [];
17562
17628
  for (let index = 0; index + 1 < data.length; index += 1) {
17563
17629
  const compact = data[index];
17564
17630
  const resolved = data[index + 1];
@@ -17571,7 +17637,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17571
17637
  } catch {
17572
17638
  continue;
17573
17639
  }
17574
- if (!isRecord4(value) || !("modelSettings" in value)) continue;
17640
+ if (!isRecord5(value) || !("modelSettings" in value)) continue;
17575
17641
  const target = resolved[2] === null || resolved[2].startsWith("client-new-thread:") ? ["default", resolved[2]] : ["conversation", resolved[2]];
17576
17642
  compactPairs.push({ optimistic: compact[1], committed: resolved[3], target });
17577
17643
  }
@@ -17633,7 +17699,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17633
17699
  reasoningEffort: reasoningEffort.value
17634
17700
  };
17635
17701
  }
17636
- if (!isRecord4(current) || !("modelSettings" in current)) return null;
17702
+ if (!isRecord5(current) || !("modelSettings" in current)) return null;
17637
17703
  const picker = findModelPickerControl(composer);
17638
17704
  if (!picker.found) return null;
17639
17705
  return {
@@ -17648,10 +17714,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17648
17714
  };
17649
17715
  }
17650
17716
  function isMainProcessTitlePolicyReady(value) {
17651
- return isRecord4(value) && value.state === "ready";
17717
+ return isRecord5(value) && value.state === "ready";
17652
17718
  }
17653
17719
  function isDraftPrewarmPolicyReady(value) {
17654
- return isRecord4(value) && value.state === "ready" && typeof value.clear === "function";
17720
+ return isRecord5(value) && value.state === "ready" && typeof value.clear === "function";
17655
17721
  }
17656
17722
  async function waitForRendererDraftPrewarmPolicy(target) {
17657
17723
  const deadline = Date.now() + DRAFT_PREWARM_POLICY_WAIT_TIMEOUT_MS;
@@ -17695,9 +17761,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17695
17761
  dispose() {
17696
17762
  }
17697
17763
  });
17764
+ const usageSubscription = createThreadUsageSubscriptionRelay();
17698
17765
  const currentModelClient = () => {
17699
17766
  const client = createRendererModelClient(findActivePrewarmTargets(document));
17700
17767
  if (!client) throw new Error("Renderer Model request manager is unavailable");
17768
+ usageSubscription.connect(client);
17701
17769
  return client;
17702
17770
  };
17703
17771
  const modelControl = Object.freeze({
@@ -17705,6 +17773,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17705
17773
  inspectHarness: (input) => currentModelClient().inspectHarness(input),
17706
17774
  inspectThread: (input) => currentModelClient().inspectThread(input),
17707
17775
  inspectThreadUsage: (input) => currentModelClient().inspectThreadUsage(input),
17776
+ subscribeThreadUsage: (listener) => usageSubscription.subscribe(listener),
17708
17777
  listThreadOwnership: (input) => currentModelClient().listThreadOwnership(input),
17709
17778
  selectThreadModel: (input) => currentModelClient().selectThreadModel(input),
17710
17779
  selectThreadThinking: (input) => currentModelClient().selectThreadThinking(input),
@@ -17783,6 +17852,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17783
17852
  disposed = true;
17784
17853
  observer.disconnect();
17785
17854
  forkControl.dispose();
17855
+ usageSubscription.dispose();
17786
17856
  modelController = null;
17787
17857
  officialSelection = null;
17788
17858
  hasOfficialSelection = false;
@@ -17827,11 +17897,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17827
17897
  return null;
17828
17898
  }
17829
17899
  }
17830
- function isRecord5(value) {
17900
+ function isRecord6(value) {
17831
17901
  return typeof value === "object" && value !== null && !Array.isArray(value);
17832
17902
  }
17833
17903
  function parseExternalConfiguration(value) {
17834
- if (!isRecord5(value)) return void 0;
17904
+ if (!isRecord6(value)) return void 0;
17835
17905
  const model = harnessModelRefSchema.safeParse(value.model);
17836
17906
  if (!model.success) return void 0;
17837
17907
  const thinkingOptionId = harnessThinkingOptionIdSchema.safeParse(value.thinkingOptionId);
@@ -17846,9 +17916,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17846
17916
  const raw = storage.getItem(RENDERER_NEW_THREAD_PREFERENCE_KEY);
17847
17917
  if (!raw) return void 0;
17848
17918
  const parsed = JSON.parse(raw);
17849
- if (!isRecord5(parsed) || parsed.version !== 1) return void 0;
17919
+ if (!isRecord6(parsed) || parsed.version !== 1) return void 0;
17850
17920
  if (!KNOWN_RENDERER_AGENTS.some((agent) => agent === parsed.lastAgent)) return void 0;
17851
- const externalByAgent = isRecord5(parsed.externalByAgent) ? parsed.externalByAgent : {};
17921
+ const externalByAgent = isRecord6(parsed.externalByAgent) ? parsed.externalByAgent : {};
17852
17922
  const pi = parseExternalConfiguration(externalByAgent.pi);
17853
17923
  const claudeCode = parseExternalConfiguration(externalByAgent["claude-code"]);
17854
17924
  return {
@@ -17918,7 +17988,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17918
17988
  var GET_LOCALE_OVERRIDE_URL = "vscode://codex/get-setting";
17919
17989
  var GET_LOCALE_INFO_URL = "vscode://codex/locale-info";
17920
17990
  var DEFAULT_LOCALE_REQUEST_TIMEOUT_MS = 750;
17921
- function isRecord6(value) {
17991
+ function isRecord7(value) {
17922
17992
  return typeof value === "object" && value !== null && !Array.isArray(value);
17923
17993
  }
17924
17994
  function canonicalLocale(value) {
@@ -17940,7 +18010,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17940
18010
  return new DOMException("The operation was aborted", "AbortError");
17941
18011
  }
17942
18012
  function parseLocaleOverride(value) {
17943
- if (!isRecord6(value) || !("value" in value)) {
18013
+ if (!isRecord7(value) || !("value" in value)) {
17944
18014
  throw new Error("Codex locale override response is malformed");
17945
18015
  }
17946
18016
  if (value.value === null) return { value: null };
@@ -17955,7 +18025,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17955
18025
  return locale;
17956
18026
  }
17957
18027
  function parseLocaleInfo(value) {
17958
- if (!isRecord6(value)) throw new Error("Codex locale info response is malformed");
18028
+ if (!isRecord7(value)) throw new Error("Codex locale info response is malformed");
17959
18029
  return {
17960
18030
  ideLocale: optionalLocale(value.ideLocale, "IDE locale"),
17961
18031
  systemLocale: optionalLocale(value.systemLocale, "system locale")
@@ -17984,7 +18054,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17984
18054
  const onAbort = () => settle(() => reject(abortError()));
17985
18055
  const onMessage = (event) => {
17986
18056
  const message2 = event.data;
17987
- if (!isRecord6(message2) || message2.type !== "fetch-response") return;
18057
+ if (!isRecord7(message2) || message2.type !== "fetch-response") return;
17988
18058
  if (message2.requestId !== requestId) return;
17989
18059
  if (message2.responseType !== "success" || typeof message2.status !== "number" || message2.status < 200 || message2.status >= 300 || typeof message2.bodyJsonString !== "string") {
17990
18060
  settle(() => reject(new Error("Codex locale request failed")));
@@ -18130,7 +18200,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18130
18200
  updateSucceeded: "Update installed successfully.",
18131
18201
  updateFailed: "Update failed.",
18132
18202
  updateRetry: "Retry",
18133
- updateViewRelease: "View release notes",
18203
+ updateDownloadFromReleases: "Download from GitHub Releases",
18134
18204
  pageLabels: Object.freeze({
18135
18205
  connections: "Connections",
18136
18206
  "model-pool": "Model Pool",
@@ -18175,7 +18245,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18175
18245
  updateSucceeded: "\u66F4\u65B0\u5B89\u88C5\u6210\u529F\u3002",
18176
18246
  updateFailed: "\u66F4\u65B0\u5931\u8D25\u3002",
18177
18247
  updateRetry: "\u91CD\u8BD5",
18178
- updateViewRelease: "\u67E5\u770B\u66F4\u65B0\u65E5\u5FD7",
18248
+ updateDownloadFromReleases: "\u524D\u5F80 GitHub Releases \u4E0B\u8F7D",
18179
18249
  pageLabels: Object.freeze({
18180
18250
  connections: "\u8FDE\u63A5",
18181
18251
  "model-pool": "\u6A21\u578B\u6C60",
@@ -18521,6 +18591,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18521
18591
  }
18522
18592
 
18523
18593
  // src/settings/pages.ts
18594
+ var CODEXHOST_RELEASES_LATEST_URL = "https://github.com/BytePioneer-AI/codex-host/releases/latest";
18524
18595
  var DEFAULT_RENDERER_SETTINGS_PAGE_IDS = [
18525
18596
  "connections",
18526
18597
  "model-pool",
@@ -18625,7 +18696,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18625
18696
  const panel = document2.createElement("section");
18626
18697
  panel.className = "settings-update-panel";
18627
18698
  panel.setAttribute("aria-live", "polite");
18628
- context.content.append(heading, metadata, panel);
18699
+ const actions = document2.createElement("div");
18700
+ actions.className = "settings-update-actions";
18701
+ const releaseLink = document2.createElement("a");
18702
+ releaseLink.className = "settings-update-link";
18703
+ releaseLink.href = CODEXHOST_RELEASES_LATEST_URL;
18704
+ releaseLink.target = "_blank";
18705
+ releaseLink.rel = "noopener noreferrer";
18706
+ releaseLink.append(
18707
+ messages.updateDownloadFromReleases,
18708
+ createRendererSettingsIcon("external-link", 14)
18709
+ );
18710
+ actions.append(releaseLink);
18711
+ context.content.append(heading, metadata, panel, actions);
18629
18712
  let pollTimer;
18630
18713
  let pollAttempts = 0;
18631
18714
  let pending = false;
@@ -18730,6 +18813,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18730
18813
  const renderCheck = (result, client) => {
18731
18814
  currentVersionValue.textContent = `v${result.currentVersion}`;
18732
18815
  installationValue.textContent = installationLabel(result.installation, messages);
18816
+ if (result.releaseNotesUrl) releaseLink.href = result.releaseNotesUrl;
18733
18817
  const operationMessage = statusMessage(result.status, messages);
18734
18818
  if (isPendingStatus(result.status)) {
18735
18819
  renderPendingStatus(result.status, operationMessage ?? messages.updatePreparing);
@@ -18765,15 +18849,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18765
18849
  update.addEventListener("click", () => start(client));
18766
18850
  panel.append(update);
18767
18851
  }
18768
- if (result.releaseNotesUrl) {
18769
- const notes = document2.createElement("a");
18770
- notes.className = "settings-update-link";
18771
- notes.href = result.releaseNotesUrl;
18772
- notes.target = "_blank";
18773
- notes.rel = "noopener noreferrer";
18774
- notes.append(messages.updateViewRelease, createRendererSettingsIcon("external-link", 14));
18775
- panel.append(notes);
18776
- }
18777
18852
  if (result.error) {
18778
18853
  const error51 = document2.createElement("p");
18779
18854
  error51.className = "settings-update-error";
@@ -18829,7 +18904,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18829
18904
  }
18830
18905
 
18831
18906
  // src/settings/shell.css
18832
- var shell_default = ':host {\n --settings-bg: #181818;\n --settings-sidebar: #1c1c1c;\n --settings-panel: transparent;\n --settings-text: #f5f5f5;\n --settings-muted: #a1a1a1;\n --settings-border: rgb(255 255 255 / 10%);\n --settings-divider: rgb(255 255 255 / 10%);\n --settings-hover: rgb(255 255 255 / 6%);\n --settings-active: rgb(51 156 255 / 12%);\n --settings-focus: #339cff;\n color: var(--settings-text);\n color-scheme: dark;\n font:\n 14px/1.5 system-ui,\n -apple-system,\n BlinkMacSystemFont,\n "Segoe UI",\n sans-serif;\n letter-spacing: 0;\n}\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n letter-spacing: 0;\n}\n\nbutton,\ninput,\nselect {\n font: inherit;\n}\n\nbutton {\n color: inherit;\n}\n\n[hidden] {\n display: none !important;\n}\n\n.codexhost-settings-dialog {\n width: min(1120px, calc(100vw - 32px));\n height: min(780px, calc(100vh - 32px));\n max-width: none;\n max-height: none;\n margin: auto;\n padding: 0;\n overflow: hidden;\n color: var(--settings-text);\n background: var(--settings-bg);\n border: 1px solid var(--settings-border);\n border-radius: 12px;\n box-shadow: 0 24px 64px rgb(0 0 0 / 38%);\n}\n\n.codexhost-settings-dialog::backdrop {\n background: rgb(0 0 0 / 52%);\n}\n\n.settings-frame,\n.settings-layout {\n width: 100%;\n height: 100%;\n min-width: 0;\n min-height: 0;\n}\n\n.settings-frame {\n display: flex;\n flex-direction: column;\n}\n\n.settings-layout {\n flex: 1;\n display: grid;\n grid-template-columns: 240px minmax(0, 1fr);\n background: var(--settings-bg);\n}\n\n.settings-sidebar {\n display: flex;\n min-width: 0;\n min-height: 0;\n flex-direction: column;\n overflow: hidden;\n background: var(--settings-sidebar);\n border-right: 1px solid var(--settings-border);\n padding-top: 28px;\n}\n\n.settings-header {\n display: flex;\n align-items: center;\n min-width: 0;\n height: 64px;\n flex: none;\n padding: 0 16px;\n border-bottom: 1px solid var(--settings-border);\n}\n\n.settings-brand {\n display: flex;\n align-items: center;\n min-width: 0;\n gap: 8px;\n}\n\n.settings-brand__mark {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n flex: none;\n color: var(--settings-text);\n}\n\n.settings-brand__mark .codexhost-settings-icon {\n width: 32px;\n height: 32px;\n object-fit: contain;\n}\n\n.settings-brand__copy {\n display: flex;\n min-width: 0;\n align-items: baseline;\n gap: 0;\n line-height: 20px;\n}\n\n.settings-brand__name {\n overflow: hidden;\n color: var(--settings-text);\n font-size: 14px;\n font-weight: 500;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.settings-brand__title {\n margin-left: 14px;\n padding-left: 14px;\n color: var(--settings-muted);\n font-size: 14px;\n font-weight: 400;\n border-left: 1px solid var(--settings-border);\n}\n\n.settings-nav {\n display: flex;\n min-width: 0;\n min-height: 0;\n flex: 1;\n flex-direction: column;\n gap: 4px;\n padding: 0 12px 16px;\n overflow-y: auto;\n}\n\n.settings-nav-button {\n display: grid;\n grid-template-columns: 16px minmax(0, 1fr);\n align-items: center;\n width: 100%;\n position: relative;\n min-height: 40px;\n flex: none;\n gap: 12px;\n padding: 8px 12px;\n color: var(--settings-text);\n font-size: 14px;\n line-height: 21px;\n text-align: left;\n background: transparent;\n border: 0;\n border-radius: 10px;\n cursor: pointer;\n}\n\n.settings-nav-button .codexhost-settings-icon {\n width: 18px;\n height: 18px;\n opacity: 0.9;\n}\n\n.settings-nav-button:hover {\n background: var(--settings-hover);\n}\n\n.settings-nav-button[aria-current="page"] {\n background: var(--settings-active);\n color: var(--settings-focus);\n}\n\n.settings-nav-button[aria-current="page"]::before {\n position: absolute;\n left: 0;\n width: 3px;\n height: 22px;\n content: "";\n background: var(--settings-focus);\n border-radius: 0 3px 3px 0;\n}\n\n.settings-nav-button span {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.settings-icon-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n flex: none;\n padding: 0;\n color: var(--settings-muted);\n background: transparent;\n border: 0;\n border-radius: 8px;\n cursor: pointer;\n}\n\n.settings-icon-button:hover {\n color: var(--settings-text);\n background: var(--settings-hover);\n}\n\n.settings-icon-button:focus-visible,\n.settings-nav-button:focus-visible {\n outline: 2px solid var(--settings-focus);\n outline-offset: 1px;\n}\n\n.settings-page {\n display: block;\n position: relative;\n min-width: 0;\n min-height: 0;\n overflow-y: auto;\n background: var(--settings-bg);\n scrollbar-gutter: stable;\n}\n\n.settings-page__content {\n width: min(930px, calc(100% - 80px));\n margin-inline: auto;\n}\n\n.settings-page__content {\n min-width: 0;\n padding: 44px 0 56px;\n}\n\n.settings-section-label {\n min-height: auto;\n padding: 0 0 28px;\n color: var(--settings-text);\n font-size: 24px;\n font-weight: 600;\n line-height: 30px;\n}\n\n.settings-status-list,\n.settings-empty {\n overflow: hidden;\n background: var(--settings-panel);\n}\n\n.settings-status-row {\n display: grid;\n grid-template-columns: minmax(170px, 1fr) auto minmax(280px, 1.45fr);\n position: relative;\n align-items: center;\n min-height: 88px;\n gap: 28px;\n padding: 16px 0;\n}\n\n.settings-status-row:not(:last-child)::after {\n content: "";\n position: absolute;\n right: 0;\n bottom: 0;\n left: 0;\n height: 1px;\n background: var(--settings-divider);\n}\n\n.settings-status-row__identity {\n display: inline-flex;\n align-items: center;\n min-width: 0;\n color: var(--settings-text);\n gap: 14px;\n font-size: 15px;\n font-weight: 500;\n line-height: 22px;\n}\n\n.settings-status-row__icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 44px;\n flex: none;\n color: var(--settings-muted);\n background: rgb(255 255 255 / 7%);\n border-radius: 50%;\n}\n\n.settings-status-badge {\n flex: none;\n padding: 5px 12px;\n color: var(--settings-muted);\n font-size: 13px;\n font-weight: 500;\n line-height: 18px;\n background: rgb(255 255 255 / 9%);\n border: 1px solid rgb(255 255 255 / 6%);\n border-radius: 6px;\n}\n\n.settings-status-row__detail {\n min-width: 0;\n color: var(--settings-muted);\n font-size: 13px;\n line-height: 20px;\n}\n\n.settings-empty {\n display: flex;\n align-items: center;\n min-height: 72px;\n padding: 12px 16px;\n}\n\n.settings-empty > div {\n display: grid;\n min-width: 0;\n gap: 2px;\n}\n\n.settings-empty strong {\n color: var(--settings-text);\n font-size: 13px;\n font-weight: 500;\n line-height: 19px;\n}\n\n.settings-empty span {\n color: var(--settings-muted);\n font-size: 13px;\n line-height: 19px;\n}\n\n.settings-page-error {\n padding: 16px;\n color: var(--settings-text);\n font-size: 13px;\n background: var(--settings-panel);\n border: 1px solid var(--settings-border);\n border-radius: 20px;\n}\n\n.settings-update-metadata {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 24px;\n margin-bottom: 24px;\n padding: 0 4px 20px;\n border-bottom: 1px solid var(--settings-divider);\n}\n\n.settings-update-metadata__item {\n display: grid;\n min-width: 0;\n gap: 4px;\n}\n\n.settings-update-metadata__item span {\n color: var(--settings-muted);\n font-size: 12px;\n line-height: 18px;\n}\n\n.settings-update-metadata__item strong {\n min-width: 0;\n overflow-wrap: anywhere;\n color: var(--settings-text);\n font-size: 15px;\n font-weight: 500;\n line-height: 22px;\n}\n\n.settings-update-panel {\n display: grid;\n gap: 16px;\n min-height: 128px;\n padding: 20px;\n color: var(--settings-text);\n background: var(--settings-panel);\n border: 1px solid var(--settings-border);\n border-radius: 8px;\n}\n\n.settings-update-panel > strong,\n.settings-update-panel > span,\n.settings-update-summary,\n.settings-update-error {\n margin: 0;\n overflow-wrap: anywhere;\n font-size: 13px;\n line-height: 20px;\n}\n\n.settings-update-panel > span,\n.settings-update-summary {\n color: var(--settings-muted);\n}\n\n.settings-update-error {\n color: #ef4444;\n}\n\n.settings-update-progress {\n width: 100%;\n height: 8px;\n accent-color: #238be8;\n}\n\n.settings-update-progress-detail {\n color: var(--settings-muted);\n font-size: 12px;\n line-height: 18px;\n}\n\n.settings-update-notes {\n max-height: 240px;\n padding: 14px;\n overflow: auto;\n color: var(--settings-muted);\n font-size: 13px;\n line-height: 20px;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n background: rgb(255 255 255 / 4%);\n border: 1px solid var(--settings-divider);\n border-radius: 6px;\n}\n\n.settings-update-version-row {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n align-items: center;\n gap: 20px;\n min-height: 32px;\n color: var(--settings-muted);\n font-size: 13px;\n}\n\n.settings-update-version-row strong {\n color: var(--settings-text);\n font-size: 15px;\n}\n\n.settings-command-button,\n.settings-update-link {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: fit-content;\n min-height: 36px;\n gap: 8px;\n padding: 8px 12px;\n color: white;\n font: inherit;\n font-size: 13px;\n text-decoration: none;\n background: #1677d2;\n border: 1px solid #238be8;\n border-radius: 6px;\n cursor: pointer;\n}\n\n.settings-command-button--secondary,\n.settings-update-link {\n color: var(--settings-text);\n background: transparent;\n border-color: var(--settings-border);\n}\n\n.settings-command-button:hover,\n.settings-update-link:hover {\n filter: brightness(1.08);\n}\n\n.settings-command-button:focus-visible,\n.settings-update-link:focus-visible {\n outline: 2px solid var(--settings-focus);\n outline-offset: 2px;\n}\n\n.codexhost-settings-icon {\n display: block;\n flex: none;\n stroke: currentColor;\n}\n\n@media (max-width: 720px) {\n .codexhost-settings-dialog {\n width: calc(100vw - 16px);\n height: calc(100vh - 16px);\n border-radius: 10px;\n }\n\n .settings-layout {\n grid-template-columns: minmax(0, 1fr);\n grid-template-rows: auto minmax(0, 1fr);\n }\n\n .settings-sidebar {\n border-right: 0;\n border-bottom: 1px solid var(--settings-border);\n }\n\n .settings-header {\n height: 56px;\n padding-inline: 14px;\n }\n\n .settings-sidebar {\n padding-top: 20px;\n }\n\n .settings-nav {\n flex: none;\n flex-direction: row;\n gap: 4px;\n padding: 7px 8px 8px;\n overflow-x: auto;\n overflow-y: hidden;\n }\n\n .settings-nav-button {\n width: auto;\n min-width: max-content;\n min-height: 34px;\n grid-template-columns: 16px auto;\n border-radius: 10px;\n }\n\n .settings-page__content {\n width: calc(100% - 40px);\n }\n\n .settings-page__content {\n padding-top: 32px;\n padding-bottom: 32px;\n }\n\n .settings-section-label {\n padding-bottom: 20px;\n font-size: 20px;\n line-height: 26px;\n }\n\n .settings-status-row {\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 16px;\n }\n\n .settings-status-row__detail {\n grid-column: 1 / -1;\n margin: -8px 0 0 58px;\n }\n}\n\n@media (forced-colors: active) {\n :host,\n :host([data-theme="dark"]) {\n --settings-bg: Canvas;\n --settings-sidebar: Canvas;\n --settings-panel: Canvas;\n --settings-text: CanvasText;\n --settings-muted: GrayText;\n --settings-border: ButtonBorder;\n --settings-divider: ButtonBorder;\n --settings-hover: Highlight;\n --settings-active: Highlight;\n --settings-focus: Highlight;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n *,\n *::before,\n *::after {\n scroll-behavior: auto !important;\n }\n}\n';
18907
+ var shell_default = ':host {\n --settings-bg: #181818;\n --settings-sidebar: #1c1c1c;\n --settings-panel: transparent;\n --settings-text: #f5f5f5;\n --settings-muted: #a1a1a1;\n --settings-border: rgb(255 255 255 / 10%);\n --settings-divider: rgb(255 255 255 / 10%);\n --settings-hover: rgb(255 255 255 / 6%);\n --settings-active: rgb(51 156 255 / 12%);\n --settings-focus: #339cff;\n color: var(--settings-text);\n color-scheme: dark;\n font:\n 14px/1.5 system-ui,\n -apple-system,\n BlinkMacSystemFont,\n "Segoe UI",\n sans-serif;\n letter-spacing: 0;\n}\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n letter-spacing: 0;\n}\n\nbutton,\ninput,\nselect {\n font: inherit;\n}\n\nbutton {\n color: inherit;\n}\n\n[hidden] {\n display: none !important;\n}\n\n.codexhost-settings-dialog {\n width: min(1120px, calc(100vw - 32px));\n height: min(780px, calc(100vh - 32px));\n max-width: none;\n max-height: none;\n margin: auto;\n padding: 0;\n overflow: hidden;\n color: var(--settings-text);\n background: var(--settings-bg);\n border: 1px solid var(--settings-border);\n border-radius: 12px;\n box-shadow: 0 24px 64px rgb(0 0 0 / 38%);\n}\n\n.codexhost-settings-dialog::backdrop {\n background: rgb(0 0 0 / 52%);\n}\n\n.settings-frame,\n.settings-layout {\n width: 100%;\n height: 100%;\n min-width: 0;\n min-height: 0;\n}\n\n.settings-frame {\n display: flex;\n flex-direction: column;\n}\n\n.settings-layout {\n flex: 1;\n display: grid;\n grid-template-columns: 240px minmax(0, 1fr);\n background: var(--settings-bg);\n}\n\n.settings-sidebar {\n display: flex;\n min-width: 0;\n min-height: 0;\n flex-direction: column;\n overflow: hidden;\n background: var(--settings-sidebar);\n border-right: 1px solid var(--settings-border);\n padding-top: 28px;\n}\n\n.settings-header {\n display: flex;\n align-items: center;\n min-width: 0;\n height: 64px;\n flex: none;\n padding: 0 16px;\n border-bottom: 1px solid var(--settings-border);\n}\n\n.settings-brand {\n display: flex;\n align-items: center;\n min-width: 0;\n gap: 8px;\n}\n\n.settings-brand__mark {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n flex: none;\n color: var(--settings-text);\n}\n\n.settings-brand__mark .codexhost-settings-icon {\n width: 32px;\n height: 32px;\n object-fit: contain;\n}\n\n.settings-brand__copy {\n display: flex;\n min-width: 0;\n align-items: baseline;\n gap: 0;\n line-height: 20px;\n}\n\n.settings-brand__name {\n overflow: hidden;\n color: var(--settings-text);\n font-size: 14px;\n font-weight: 500;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.settings-brand__title {\n margin-left: 14px;\n padding-left: 14px;\n color: var(--settings-muted);\n font-size: 14px;\n font-weight: 400;\n border-left: 1px solid var(--settings-border);\n}\n\n.settings-nav {\n display: flex;\n min-width: 0;\n min-height: 0;\n flex: 1;\n flex-direction: column;\n gap: 4px;\n padding: 0 12px 16px;\n overflow-y: auto;\n}\n\n.settings-nav-button {\n display: grid;\n grid-template-columns: 16px minmax(0, 1fr);\n align-items: center;\n width: 100%;\n position: relative;\n min-height: 40px;\n flex: none;\n gap: 12px;\n padding: 8px 12px;\n color: var(--settings-text);\n font-size: 14px;\n line-height: 21px;\n text-align: left;\n background: transparent;\n border: 0;\n border-radius: 10px;\n cursor: pointer;\n}\n\n.settings-nav-button .codexhost-settings-icon {\n width: 18px;\n height: 18px;\n opacity: 0.9;\n}\n\n.settings-nav-button:hover {\n background: var(--settings-hover);\n}\n\n.settings-nav-button[aria-current="page"] {\n background: var(--settings-active);\n color: var(--settings-focus);\n}\n\n.settings-nav-button[aria-current="page"]::before {\n position: absolute;\n left: 0;\n width: 3px;\n height: 22px;\n content: "";\n background: var(--settings-focus);\n border-radius: 0 3px 3px 0;\n}\n\n.settings-nav-button span {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.settings-icon-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n flex: none;\n padding: 0;\n color: var(--settings-muted);\n background: transparent;\n border: 0;\n border-radius: 8px;\n cursor: pointer;\n}\n\n.settings-icon-button:hover {\n color: var(--settings-text);\n background: var(--settings-hover);\n}\n\n.settings-icon-button:focus-visible,\n.settings-nav-button:focus-visible {\n outline: 2px solid var(--settings-focus);\n outline-offset: 1px;\n}\n\n.settings-page {\n display: block;\n position: relative;\n min-width: 0;\n min-height: 0;\n overflow-y: auto;\n background: var(--settings-bg);\n scrollbar-gutter: stable;\n}\n\n.settings-page__content {\n width: min(930px, calc(100% - 80px));\n margin-inline: auto;\n}\n\n.settings-page__content {\n min-width: 0;\n padding: 44px 0 56px;\n}\n\n.settings-section-label {\n min-height: auto;\n padding: 0 0 28px;\n color: var(--settings-text);\n font-size: 24px;\n font-weight: 600;\n line-height: 30px;\n}\n\n.settings-status-list,\n.settings-empty {\n overflow: hidden;\n background: var(--settings-panel);\n}\n\n.settings-status-row {\n display: grid;\n grid-template-columns: minmax(170px, 1fr) auto minmax(280px, 1.45fr);\n position: relative;\n align-items: center;\n min-height: 88px;\n gap: 28px;\n padding: 16px 0;\n}\n\n.settings-status-row:not(:last-child)::after {\n content: "";\n position: absolute;\n right: 0;\n bottom: 0;\n left: 0;\n height: 1px;\n background: var(--settings-divider);\n}\n\n.settings-status-row__identity {\n display: inline-flex;\n align-items: center;\n min-width: 0;\n color: var(--settings-text);\n gap: 14px;\n font-size: 15px;\n font-weight: 500;\n line-height: 22px;\n}\n\n.settings-status-row__icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 44px;\n flex: none;\n color: var(--settings-muted);\n background: rgb(255 255 255 / 7%);\n border-radius: 50%;\n}\n\n.settings-status-badge {\n flex: none;\n padding: 5px 12px;\n color: var(--settings-muted);\n font-size: 13px;\n font-weight: 500;\n line-height: 18px;\n background: rgb(255 255 255 / 9%);\n border: 1px solid rgb(255 255 255 / 6%);\n border-radius: 6px;\n}\n\n.settings-status-row__detail {\n min-width: 0;\n color: var(--settings-muted);\n font-size: 13px;\n line-height: 20px;\n}\n\n.settings-empty {\n display: flex;\n align-items: center;\n min-height: 72px;\n padding: 12px 16px;\n}\n\n.settings-empty > div {\n display: grid;\n min-width: 0;\n gap: 2px;\n}\n\n.settings-empty strong {\n color: var(--settings-text);\n font-size: 13px;\n font-weight: 500;\n line-height: 19px;\n}\n\n.settings-empty span {\n color: var(--settings-muted);\n font-size: 13px;\n line-height: 19px;\n}\n\n.settings-page-error {\n padding: 16px;\n color: var(--settings-text);\n font-size: 13px;\n background: var(--settings-panel);\n border: 1px solid var(--settings-border);\n border-radius: 20px;\n}\n\n.settings-update-metadata {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 24px;\n margin-bottom: 24px;\n padding: 0 4px 20px;\n border-bottom: 1px solid var(--settings-divider);\n}\n\n.settings-update-metadata__item {\n display: grid;\n min-width: 0;\n gap: 4px;\n}\n\n.settings-update-metadata__item span {\n color: var(--settings-muted);\n font-size: 12px;\n line-height: 18px;\n}\n\n.settings-update-metadata__item strong {\n min-width: 0;\n overflow-wrap: anywhere;\n color: var(--settings-text);\n font-size: 15px;\n font-weight: 500;\n line-height: 22px;\n}\n\n.settings-update-panel {\n display: grid;\n gap: 16px;\n min-height: 128px;\n padding: 20px;\n color: var(--settings-text);\n background: var(--settings-panel);\n border: 1px solid var(--settings-border);\n border-radius: 8px;\n}\n\n.settings-update-panel > strong,\n.settings-update-panel > span,\n.settings-update-summary,\n.settings-update-error {\n margin: 0;\n overflow-wrap: anywhere;\n font-size: 13px;\n line-height: 20px;\n}\n\n.settings-update-panel > span,\n.settings-update-summary {\n color: var(--settings-muted);\n}\n\n.settings-update-error {\n color: #ef4444;\n}\n\n.settings-update-progress {\n width: 100%;\n height: 8px;\n accent-color: #238be8;\n}\n\n.settings-update-progress-detail {\n color: var(--settings-muted);\n font-size: 12px;\n line-height: 18px;\n}\n\n.settings-update-notes {\n max-height: 240px;\n padding: 14px;\n overflow: auto;\n color: var(--settings-muted);\n font-size: 13px;\n line-height: 20px;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n background: rgb(255 255 255 / 4%);\n border: 1px solid var(--settings-divider);\n border-radius: 6px;\n}\n\n.settings-update-version-row {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n align-items: center;\n gap: 20px;\n min-height: 32px;\n color: var(--settings-muted);\n font-size: 13px;\n}\n\n.settings-update-version-row strong {\n color: var(--settings-text);\n font-size: 15px;\n}\n\n.settings-update-actions {\n display: flex;\n min-width: 0;\n margin-top: 16px;\n}\n\n.settings-command-button,\n.settings-update-link {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: fit-content;\n min-height: 36px;\n gap: 8px;\n padding: 8px 12px;\n color: white;\n font: inherit;\n font-size: 13px;\n text-decoration: none;\n background: #1677d2;\n border: 1px solid #238be8;\n border-radius: 6px;\n cursor: pointer;\n}\n\n.settings-command-button--secondary,\n.settings-update-link {\n color: var(--settings-text);\n background: transparent;\n border-color: var(--settings-border);\n}\n\n.settings-command-button:hover,\n.settings-update-link:hover {\n filter: brightness(1.08);\n}\n\n.settings-command-button:focus-visible,\n.settings-update-link:focus-visible {\n outline: 2px solid var(--settings-focus);\n outline-offset: 2px;\n}\n\n.codexhost-settings-icon {\n display: block;\n flex: none;\n stroke: currentColor;\n}\n\n@media (max-width: 720px) {\n .codexhost-settings-dialog {\n width: calc(100vw - 16px);\n height: calc(100vh - 16px);\n border-radius: 10px;\n }\n\n .settings-layout {\n grid-template-columns: minmax(0, 1fr);\n grid-template-rows: auto minmax(0, 1fr);\n }\n\n .settings-sidebar {\n border-right: 0;\n border-bottom: 1px solid var(--settings-border);\n }\n\n .settings-header {\n height: 56px;\n padding-inline: 14px;\n }\n\n .settings-sidebar {\n padding-top: 20px;\n }\n\n .settings-nav {\n flex: none;\n flex-direction: row;\n gap: 4px;\n padding: 7px 8px 8px;\n overflow-x: auto;\n overflow-y: hidden;\n }\n\n .settings-nav-button {\n width: auto;\n min-width: max-content;\n min-height: 34px;\n grid-template-columns: 16px auto;\n border-radius: 10px;\n }\n\n .settings-page__content {\n width: calc(100% - 40px);\n }\n\n .settings-page__content {\n padding-top: 32px;\n padding-bottom: 32px;\n }\n\n .settings-section-label {\n padding-bottom: 20px;\n font-size: 20px;\n line-height: 26px;\n }\n\n .settings-status-row {\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 16px;\n }\n\n .settings-status-row__detail {\n grid-column: 1 / -1;\n margin: -8px 0 0 58px;\n }\n}\n\n@media (forced-colors: active) {\n :host,\n :host([data-theme="dark"]) {\n --settings-bg: Canvas;\n --settings-sidebar: Canvas;\n --settings-panel: Canvas;\n --settings-text: CanvasText;\n --settings-muted: GrayText;\n --settings-border: ButtonBorder;\n --settings-divider: ButtonBorder;\n --settings-hover: Highlight;\n --settings-active: Highlight;\n --settings-focus: Highlight;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n *,\n *::before,\n *::after {\n scroll-behavior: auto !important;\n }\n}\n';
18833
18908
 
18834
18909
  // src/settings/shell.ts
18835
18910
  var SETTINGS_SHELL_ATTRIBUTE = "data-codexhost-settings-shell";
@@ -19265,6 +19340,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
19265
19340
  }
19266
19341
 
19267
19342
  // src/renderer-settings-lifecycle.ts
19343
+ var UPDATE_CHECK_TIMEOUT_MS = 5e3;
19344
+ var UPDATE_RETRY_DELAYS_MS = [1e3, 3e3, 1e4, 3e4];
19268
19345
  function installRendererSettingsLifecycle(ownerWindow = window, options = {}) {
19269
19346
  const lifecycleController = new AbortController();
19270
19347
  let locale = resolveRendererSettingsLocale(ownerWindow.navigator.languages);
@@ -19272,6 +19349,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
19272
19349
  let trigger = null;
19273
19350
  let localeRequest = null;
19274
19351
  let checkedUpdateClient = null;
19352
+ let retryUpdateClient = null;
19353
+ let updateRetryTimer = null;
19354
+ let updateRetryAttempt = 0;
19275
19355
  let updateCheckGeneration = 0;
19276
19356
  let updateAvailable = false;
19277
19357
  let openGeneration = 0;
@@ -19334,16 +19414,65 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
19334
19414
  localeRequest = request;
19335
19415
  return request;
19336
19416
  };
19417
+ const clearUpdateRetry = () => {
19418
+ if (updateRetryTimer === null) return;
19419
+ ownerWindow.clearTimeout(updateRetryTimer);
19420
+ updateRetryTimer = null;
19421
+ };
19422
+ const scheduleUpdateRetry = (client) => {
19423
+ if (disposed || updateRetryTimer !== null) return;
19424
+ const delay = UPDATE_RETRY_DELAYS_MS[updateRetryAttempt];
19425
+ if (delay === void 0) return;
19426
+ updateRetryAttempt += 1;
19427
+ updateRetryTimer = ownerWindow.setTimeout(() => {
19428
+ updateRetryTimer = null;
19429
+ if (disposed || options.getUpdateClient?.() !== client) return;
19430
+ refreshUpdateIndicator();
19431
+ }, delay);
19432
+ };
19433
+ const checkUpdateWithTimeout = (client) => new Promise((resolve, reject) => {
19434
+ const timeout = ownerWindow.setTimeout(
19435
+ () => reject(new Error("Update indicator check timed out")),
19436
+ UPDATE_CHECK_TIMEOUT_MS
19437
+ );
19438
+ void client.checkUpdate().then(
19439
+ (result) => {
19440
+ ownerWindow.clearTimeout(timeout);
19441
+ resolve(result);
19442
+ },
19443
+ (error51) => {
19444
+ ownerWindow.clearTimeout(timeout);
19445
+ reject(error51);
19446
+ }
19447
+ );
19448
+ });
19337
19449
  const refreshUpdateIndicator = () => {
19338
19450
  const client = options.getUpdateClient?.() ?? null;
19339
19451
  if (!client || checkedUpdateClient === client) return;
19452
+ if (retryUpdateClient !== client) {
19453
+ clearUpdateRetry();
19454
+ retryUpdateClient = client;
19455
+ updateRetryAttempt = 0;
19456
+ }
19340
19457
  checkedUpdateClient = client;
19341
19458
  const generation = ++updateCheckGeneration;
19342
- void client.checkUpdate().then((result) => {
19459
+ void checkUpdateWithTimeout(client).then((result) => {
19343
19460
  if (disposed || generation !== updateCheckGeneration) return;
19344
19461
  updateAvailable = result.updateAvailable;
19345
19462
  trigger?.setUpdateAvailable(updateAvailable);
19463
+ if (result.error === null) {
19464
+ updateRetryAttempt = 0;
19465
+ clearUpdateRetry();
19466
+ return;
19467
+ }
19468
+ checkedUpdateClient = null;
19469
+ scheduleUpdateRetry(client);
19346
19470
  }).catch(() => {
19471
+ if (disposed || generation !== updateCheckGeneration || checkedUpdateClient !== client) {
19472
+ return;
19473
+ }
19474
+ checkedUpdateClient = null;
19475
+ scheduleUpdateRetry(client);
19347
19476
  });
19348
19477
  };
19349
19478
  mount();
@@ -19364,6 +19493,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
19364
19493
  openGeneration += 1;
19365
19494
  updateCheckGeneration += 1;
19366
19495
  lifecycleController.abort();
19496
+ clearUpdateRetry();
19367
19497
  trigger?.dispose();
19368
19498
  shell?.dispose();
19369
19499
  trigger = null;
@@ -19513,6 +19643,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
19513
19643
  let adapterDispose = null;
19514
19644
  let applyAdapterAgent = null;
19515
19645
  let modelControl = null;
19646
+ let usageNotificationDispose = null;
19516
19647
  const sidebarAgentIcons = installRendererSidebarAgentIcons({
19517
19648
  getClient: () => modelControl
19518
19649
  });
@@ -19569,6 +19700,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
19569
19700
  mounted.usage
19570
19701
  );
19571
19702
  };
19703
+ const applyThreadUsageUpdate = (update) => {
19704
+ for (const mounted of mountedByComposer.values()) {
19705
+ if (threadIdFromComposerModelTarget(mounted.modelTarget) !== update.threadId) continue;
19706
+ mounted.usageRequestGeneration += 1;
19707
+ mounted.usage = update.usage;
19708
+ usageRefreshAttempts.delete(mounted.composer);
19709
+ renderMounted(mounted);
19710
+ }
19711
+ };
19572
19712
  const refreshThreadUsage = async (mounted) => {
19573
19713
  const threadId = threadIdFromComposerModelTarget(mounted.modelTarget);
19574
19714
  if (!threadId || !modelControl || controller.get(mounted.composer).agent === "codex") {
@@ -19632,6 +19772,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
19632
19772
  return;
19633
19773
  }
19634
19774
  const generation = controller.beginOwnershipRequest(mounted.composer);
19775
+ const usageGeneration = mounted.usageRequestGeneration;
19635
19776
  mounted.ownershipStatus = "loading";
19636
19777
  renderMounted(mounted);
19637
19778
  try {
@@ -19641,7 +19782,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
19641
19782
  return;
19642
19783
  }
19643
19784
  const { agent, model, thinkingOptionId, permissionModeId } = restoredThreadOwnership(inspection);
19644
- mounted.usage = inspection.owner === "external" ? inspection.usage ?? null : null;
19785
+ if (mounted.usageRequestGeneration === usageGeneration) {
19786
+ mounted.usage = inspection.owner === "external" ? inspection.usage ?? null : null;
19787
+ }
19645
19788
  const restored = controller.restore(
19646
19789
  mounted.composer,
19647
19790
  agent,
@@ -20542,15 +20685,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20542
20685
  };
20543
20686
  const mutationObserver = new MutationObserver((mutations) => {
20544
20687
  transferReplacedComposers(mutations);
20545
- for (const mounted of mountedByComposer.values()) {
20546
- const anchor = nativeContextUsageControlForComposer(mounted.composer);
20547
- if (!anchor) continue;
20548
- const changed = mutations.some((mutation) => {
20549
- const target = mutation.target instanceof Element ? mutation.target : mutation.target.parentElement;
20550
- return target === anchor || target === anchor.parentElement || anchor.contains(target);
20551
- });
20552
- if (changed) scheduleThreadUsageRefresh(mounted);
20553
- }
20554
20688
  scheduleScan(mutations.some(mutationMayChangeComposerTarget));
20555
20689
  });
20556
20690
  const onAdapterStatus = () => {
@@ -20622,11 +20756,26 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20622
20756
  return startCompatibilityUpdate(modelControl);
20623
20757
  },
20624
20758
  setAdapter(status, dispose, applyAgent, nextModelControl) {
20759
+ usageNotificationDispose?.();
20760
+ usageNotificationDispose = null;
20625
20761
  adapterDispose?.();
20626
20762
  adapterDispose = dispose ?? null;
20627
20763
  applyAdapterAgent = applyAgent ?? null;
20628
20764
  modelControl = nextModelControl ?? null;
20765
+ try {
20766
+ usageNotificationDispose = modelControl?.subscribeThreadUsage?.(applyThreadUsageUpdate) ?? null;
20767
+ } catch {
20768
+ usageNotificationDispose = null;
20769
+ }
20629
20770
  adapterStatus = status;
20771
+ const installedModelControl = modelControl;
20772
+ queueMicrotask(() => {
20773
+ if (disposed || modelControl !== installedModelControl) return;
20774
+ try {
20775
+ settingsLifecycle.refresh();
20776
+ } catch {
20777
+ }
20778
+ });
20630
20779
  sidebarAgentIcons.refresh();
20631
20780
  void refreshHarnessAvailability();
20632
20781
  const connected = connectedComposers();
@@ -20648,6 +20797,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20648
20797
  dispose() {
20649
20798
  if (disposed) return;
20650
20799
  disposed = true;
20800
+ usageNotificationDispose?.();
20801
+ usageNotificationDispose = null;
20651
20802
  adapterDispose?.();
20652
20803
  adapterDispose = null;
20653
20804
  applyAdapterAgent = null;
package/bin/codexhost.exe CHANGED
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codexhost/cli-win32-x64",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Run Pi and Claude Code as first-class external harnesses inside Codex Desktop.",
5
5
  "type": "module",
6
6
  "files": [