@codexhost/cli-win32-x64 0.1.3 → 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.3
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.3","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
+ }
1157
1105
  );
1158
- const draftPrewarmPolicy = await requireCompatibilityBoundary(
1159
- "draft-routing",
1160
- "draft-routing-structure-unavailable",
1161
- () => operations.installDraftPrewarmPolicy(options.inspector, selected.id)
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
1114
+ );
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);
@@ -1344,15 +1288,12 @@ function isTransientElectronInstallError(error) {
1344
1288
  }
1345
1289
  return false;
1346
1290
  }
1347
- function shouldRetryInstallError(error) {
1348
- return !(error instanceof RendererCompatibilityError) || isTransientElectronInstallError(error);
1349
- }
1350
1291
  async function installProductionSession(options, dependencies) {
1351
1292
  for (let attempt = 1; attempt <= TRANSIENT_INSTALL_ATTEMPTS; attempt += 1) {
1352
1293
  try {
1353
1294
  return await dependencies.install(options);
1354
1295
  } catch (error) {
1355
- if (attempt === TRANSIENT_INSTALL_ATTEMPTS || !shouldRetryInstallError(error)) {
1296
+ if (attempt === TRANSIENT_INSTALL_ATTEMPTS || !isTransientElectronInstallError(error)) {
1356
1297
  throw error;
1357
1298
  }
1358
1299
  await dependencies.sleep(TRANSIENT_INSTALL_RETRY_MS);
@@ -1361,12 +1302,23 @@ async function installProductionSession(options, dependencies) {
1361
1302
  throw new Error("Desktop Controller exhausted Renderer installation attempts");
1362
1303
  }
1363
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;
1364
1307
  let session;
1365
- 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 () => {
1366
1319
  const rendererSource = await dependencies.readRenderer(options.rendererPath);
1367
1320
  if (rendererSource.trim().length === 0) throw new Error("production Renderer Bundle is empty");
1368
- const configuration = `Object.defineProperty(window, "__codexhostProductionConfigV1", { configurable: true, value: { defaultAgent: ${JSON.stringify(options.defaultAgent)} } });`;
1369
- session = await installProductionSession(
1321
+ return installProductionSession(
1370
1322
  {
1371
1323
  inspectorEndpoint: options.inspectorEndpoint,
1372
1324
  rendererSource: `${configuration}
@@ -1376,19 +1328,13 @@ ${rendererSource}`,
1376
1328
  },
1377
1329
  dependencies
1378
1330
  );
1379
- } catch (error) {
1380
- dependencies.ready(
1381
- error instanceof RendererCompatibilityError && !isTransientElectronInstallError(error) ? {
1382
- schemaVersion: 2,
1383
- state: "incompatible",
1384
- issues: [{ capability: error.capability, reason: error.reason }]
1385
- } : {
1386
- schemaVersion: 2,
1387
- state: "detection-failed",
1388
- issues: [{ capability: "compatibility-detection", reason: "inspection-failed" }]
1389
- }
1390
- );
1391
- return;
1331
+ };
1332
+ try {
1333
+ session = await createSession();
1334
+ recordRecoverySuccess();
1335
+ } catch {
1336
+ session = void 0;
1337
+ recordRecoveryFailure();
1392
1338
  }
1393
1339
  let operation = Promise.resolve(void 0);
1394
1340
  const useSession = (callback) => {
@@ -1399,22 +1345,45 @@ ${rendererSource}`,
1399
1345
  );
1400
1346
  return next;
1401
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
+ };
1402
1368
  let attachmentServer;
1403
1369
  try {
1404
1370
  attachmentServer = await dependencies.startAttachmentServer({
1405
1371
  port: options.attachmentPort,
1406
1372
  nonce: options.attachmentNonce,
1407
1373
  attach: () => useSession(async () => {
1408
- await session.ensureInstalled();
1409
- await session.activateDesktop();
1374
+ const current = await recoverSession();
1375
+ await current.activateDesktop();
1410
1376
  }),
1411
1377
  compatibilityUpdate: () => useSession(async () => {
1412
- await session.ensureInstalled();
1413
- return session.requestCompatibilityUpdate();
1378
+ const current = await recoverSession();
1379
+ return current.requestCompatibilityUpdate();
1414
1380
  }),
1415
- shutdown: () => useSession(() => session.quitDesktop())
1381
+ shutdown: () => useSession(async () => {
1382
+ const current = await recoverSession();
1383
+ await current.quitDesktop();
1384
+ })
1416
1385
  });
1417
- const issues = session.snapshot.titlePolicy.warnings;
1386
+ const issues = session?.snapshot.titlePolicy.warnings ?? [];
1418
1387
  dependencies.ready({
1419
1388
  schemaVersion: 2,
1420
1389
  state: issues.length === 0 ? "compatible" : "compatible-with-warning",
@@ -1422,12 +1391,19 @@ ${rendererSource}`,
1422
1391
  });
1423
1392
  while (!signal.aborted) {
1424
1393
  await dependencies.sleep(dependencies.monitorIntervalMs);
1425
- 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
+ });
1426
1402
  }
1427
1403
  } finally {
1428
1404
  await attachmentServer?.close();
1429
1405
  await operation;
1430
- session.close();
1406
+ resetSession();
1431
1407
  }
1432
1408
  }
1433
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) {
@@ -53164,20 +53164,6 @@ var ERROR_MAX_LENGTH = 500;
53164
53164
  var CONTROLLER_TIMEOUT_MS = 5e3;
53165
53165
  var UPDATER_READY_TIMEOUT_MS = 1e4;
53166
53166
  var UPDATER_READY_POLL_MS = 20;
53167
- async function startCompatibilityUpdate(coordinator) {
53168
- try {
53169
- const check2 = await coordinator.check();
53170
- if (check2.updateAvailable && check2.installationAvailable) {
53171
- void coordinator.start().catch(() => void 0);
53172
- return "update-started";
53173
- }
53174
- if (check2.error === null && check2.latestVersion !== null && check2.currentVersion === check2.latestVersion) {
53175
- return "current";
53176
- }
53177
- } catch {
53178
- }
53179
- return "unavailable";
53180
- }
53181
53167
  function boundedError(error52) {
53182
53168
  const message3 = error52 instanceof Error ? error52.message : String(error52);
53183
53169
  return message3.slice(0, ERROR_MAX_LENGTH) || "Update operation failed";
@@ -53428,31 +53414,25 @@ function createHostUpdateCoordinator(options) {
53428
53414
  // packages/host-runtime/src/release-main.ts
53429
53415
  var STOCK_CODEX_PATH_ENV = "CODEXHOST_STOCK_CODEX_PATH";
53430
53416
  var DEFAULT_AGENT_ENV = "CODEXHOST_DEFAULT_AGENT";
53431
- var COMPATIBILITY_UPDATE_ARGUMENT = "--codexhost-compatibility-update";
53432
53417
  var arguments_ = process.argv.slice(2);
53433
53418
  var updateCoordinator = createHostUpdateCoordinator({
53434
53419
  hostRuntimePath: fileURLToPath(import.meta.url),
53435
53420
  environment: process.env
53436
53421
  });
53437
- if (arguments_.length === 1 && arguments_[0] === COMPATIBILITY_UPDATE_ARGUMENT) {
53438
- process.stdout.write(`${await startCompatibilityUpdate(updateCoordinator)}
53439
- `);
53440
- } else {
53441
- const stockCodexPath = process.env[STOCK_CODEX_PATH_ENV];
53442
- if (!stockCodexPath) throw new Error(`${STOCK_CODEX_PATH_ENV} is required`);
53443
- const defaultAgent = process.env[DEFAULT_AGENT_ENV];
53444
- if (defaultAgent !== "codex" && defaultAgent !== "pi") {
53445
- throw new Error(`${DEFAULT_AGENT_ENV} must be 'codex' or 'pi'`);
53446
- }
53447
- const externalAdapters = createExternalHarnessAdapters(process.env);
53448
- const host = new AppServerHost({
53449
- stockCodexPath,
53450
- arguments: arguments_,
53451
- defaultAgent,
53452
- environment: process.env,
53453
- externalAdapters,
53454
- updateCoordinator
53455
- });
53456
- void prefetchClaudeCodeModelCatalog(externalAdapters);
53457
- process.exitCode = await host.run();
53458
- }
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")));
@@ -19573,6 +19643,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
19573
19643
  let adapterDispose = null;
19574
19644
  let applyAdapterAgent = null;
19575
19645
  let modelControl = null;
19646
+ let usageNotificationDispose = null;
19576
19647
  const sidebarAgentIcons = installRendererSidebarAgentIcons({
19577
19648
  getClient: () => modelControl
19578
19649
  });
@@ -19629,6 +19700,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
19629
19700
  mounted.usage
19630
19701
  );
19631
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
+ };
19632
19712
  const refreshThreadUsage = async (mounted) => {
19633
19713
  const threadId = threadIdFromComposerModelTarget(mounted.modelTarget);
19634
19714
  if (!threadId || !modelControl || controller.get(mounted.composer).agent === "codex") {
@@ -19692,6 +19772,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
19692
19772
  return;
19693
19773
  }
19694
19774
  const generation = controller.beginOwnershipRequest(mounted.composer);
19775
+ const usageGeneration = mounted.usageRequestGeneration;
19695
19776
  mounted.ownershipStatus = "loading";
19696
19777
  renderMounted(mounted);
19697
19778
  try {
@@ -19701,7 +19782,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
19701
19782
  return;
19702
19783
  }
19703
19784
  const { agent, model, thinkingOptionId, permissionModeId } = restoredThreadOwnership(inspection);
19704
- 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
+ }
19705
19788
  const restored = controller.restore(
19706
19789
  mounted.composer,
19707
19790
  agent,
@@ -20602,15 +20685,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20602
20685
  };
20603
20686
  const mutationObserver = new MutationObserver((mutations) => {
20604
20687
  transferReplacedComposers(mutations);
20605
- for (const mounted of mountedByComposer.values()) {
20606
- const anchor = nativeContextUsageControlForComposer(mounted.composer);
20607
- if (!anchor) continue;
20608
- const changed = mutations.some((mutation) => {
20609
- const target = mutation.target instanceof Element ? mutation.target : mutation.target.parentElement;
20610
- return target === anchor || target === anchor.parentElement || anchor.contains(target);
20611
- });
20612
- if (changed) scheduleThreadUsageRefresh(mounted);
20613
- }
20614
20688
  scheduleScan(mutations.some(mutationMayChangeComposerTarget));
20615
20689
  });
20616
20690
  const onAdapterStatus = () => {
@@ -20682,10 +20756,17 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20682
20756
  return startCompatibilityUpdate(modelControl);
20683
20757
  },
20684
20758
  setAdapter(status, dispose, applyAgent, nextModelControl) {
20759
+ usageNotificationDispose?.();
20760
+ usageNotificationDispose = null;
20685
20761
  adapterDispose?.();
20686
20762
  adapterDispose = dispose ?? null;
20687
20763
  applyAdapterAgent = applyAgent ?? null;
20688
20764
  modelControl = nextModelControl ?? null;
20765
+ try {
20766
+ usageNotificationDispose = modelControl?.subscribeThreadUsage?.(applyThreadUsageUpdate) ?? null;
20767
+ } catch {
20768
+ usageNotificationDispose = null;
20769
+ }
20689
20770
  adapterStatus = status;
20690
20771
  const installedModelControl = modelControl;
20691
20772
  queueMicrotask(() => {
@@ -20716,6 +20797,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20716
20797
  dispose() {
20717
20798
  if (disposed) return;
20718
20799
  disposed = true;
20800
+ usageNotificationDispose?.();
20801
+ usageNotificationDispose = null;
20719
20802
  adapterDispose?.();
20720
20803
  adapterDispose = null;
20721
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.3",
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": [