@codexhost/cli-darwin-x64 0.13.0 → 0.13.1

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.
@@ -4894,39 +4894,25 @@ async function regularFile(filePath) {
4894
4894
  throw error51;
4895
4895
  }
4896
4896
  }
4897
- async function isUpdateOperationActive(stateDirectory) {
4897
+ async function discoverActiveUpdateStatus(stateDirectory) {
4898
4898
  if (!path4.isAbsolute(stateDirectory))
4899
4899
  throw new Error("update state directory must be absolute");
4900
- return regularFile(path4.join(stateDirectory, LOCK_FILE));
4901
- }
4902
- async function discoverLatestUpdateStatus(stateDirectory) {
4903
- if (!path4.isAbsolute(stateDirectory))
4904
- throw new Error("update state directory must be absolute");
4905
- let entries;
4900
+ const lockPath = path4.join(stateDirectory, LOCK_FILE);
4901
+ if (!await regularFile(lockPath))
4902
+ return null;
4906
4903
  try {
4907
- entries = await readdir(stateDirectory, { withFileTypes: true });
4908
- } catch (error51) {
4909
- if (error51.code === "ENOENT")
4904
+ const lock = JSON.parse(await readFile3(lockPath, "utf8"));
4905
+ if (typeof lock.statusPath !== "string" || !path4.isAbsolute(lock.statusPath))
4910
4906
  return null;
4911
- throw error51;
4912
- }
4913
- const candidates = [];
4914
- for (const entry of entries) {
4915
- if (!entry.isDirectory() || entry.isSymbolicLink() || !entry.name.startsWith("update-"))
4916
- continue;
4917
- const statusPath = path4.join(stateDirectory, entry.name, STATUS_FILE);
4918
- if (!await regularFile(statusPath))
4919
- continue;
4920
- try {
4921
- candidates.push({
4922
- statusPath,
4923
- status: parseUpdateStatus(JSON.parse(await readFile3(statusPath, "utf8")))
4924
- });
4925
- } catch {
4926
- }
4907
+ if (!await regularFile(lock.statusPath))
4908
+ return null;
4909
+ const status2 = parseUpdateStatus(JSON.parse(await readFile3(lock.statusPath, "utf8")));
4910
+ if (TERMINAL_PHASES.has(status2.phase))
4911
+ return null;
4912
+ return { statusPath: lock.statusPath, status: status2 };
4913
+ } catch {
4914
+ return null;
4927
4915
  }
4928
- candidates.sort((left, right) => right.status.updatedAt - left.status.updatedAt);
4929
- return candidates[0] ?? null;
4930
4916
  }
4931
4917
  async function cleanupTerminalUpdateState(stateDirectory, options2 = {}) {
4932
4918
  const now = Math.floor((options2.now ?? Date.now()) / 1e3);
@@ -5124,6 +5110,48 @@ async function verifyDownloadedArtifact(source, filePath, result) {
5124
5110
  }
5125
5111
  }
5126
5112
 
5113
+ // packages/update-manager/dist/updater-ready.js
5114
+ import { setTimeout as delay } from "node:timers/promises";
5115
+ async function waitForUpdaterReady(child, readStatus, timeoutMs = 1e4) {
5116
+ let failure;
5117
+ const onError = (error51) => {
5118
+ failure = error51;
5119
+ };
5120
+ const onExit = () => {
5121
+ failure = new Error("Background Updater exited before completing handoff");
5122
+ };
5123
+ child.on("error", onError);
5124
+ child.on("exit", onExit);
5125
+ const deadline = performance.now() + timeoutMs;
5126
+ try {
5127
+ while (true) {
5128
+ const status2 = await readStatus();
5129
+ if (status2?.phase === "failed")
5130
+ throw new Error(status2.error ?? "Background Updater failed");
5131
+ if (failure)
5132
+ throw failure;
5133
+ if (child.exitCode != null || child.signalCode != null) {
5134
+ throw new Error("Background Updater exited before completing handoff");
5135
+ }
5136
+ if (status2?.phase === "waiting-for-exit")
5137
+ return;
5138
+ if (status2 && status2.phase !== "prepared") {
5139
+ throw new Error(`Unexpected Updater phase during handoff: ${status2.phase}`);
5140
+ }
5141
+ if (performance.now() >= deadline) {
5142
+ throw new Error("Background Updater did not confirm handoff before the startup timeout");
5143
+ }
5144
+ await delay(20);
5145
+ }
5146
+ } catch (error51) {
5147
+ child.kill();
5148
+ throw error51;
5149
+ } finally {
5150
+ child.removeListener("error", onError);
5151
+ child.removeListener("exit", onExit);
5152
+ }
5153
+ }
5154
+
5127
5155
  // packages/update-manager/dist/update-manager.js
5128
5156
  var REQUEST_SCHEMA_VERSION = 1;
5129
5157
  var WINDOWS_RENAME_RETRY_DELAYS_MS = [10, 30, 70, 150, 300];
@@ -5133,7 +5161,7 @@ function errorMessage(error51) {
5133
5161
  function systemErrorCode(error51) {
5134
5162
  return typeof error51 === "object" && error51 !== null && "code" in error51 ? String(error51.code) : null;
5135
5163
  }
5136
- function delay(milliseconds) {
5164
+ function delay2(milliseconds) {
5137
5165
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
5138
5166
  }
5139
5167
  async function replaceStatusFile(temporaryPath, statusPath) {
@@ -5149,7 +5177,7 @@ async function replaceStatusFile(temporaryPath, statusPath) {
5149
5177
  throw error51;
5150
5178
  }
5151
5179
  retryIndex += 1;
5152
- await delay(retryDelay);
5180
+ await delay2(retryDelay);
5153
5181
  }
5154
5182
  }
5155
5183
  }
@@ -5369,14 +5397,20 @@ function createBackgroundUpdateManager(dependencies2 = {}) {
5369
5397
  app_path: appPath
5370
5398
  }, artifact.artifactPath);
5371
5399
  },
5372
- start(prepared) {
5400
+ async start(prepared) {
5373
5401
  if (!preparedRequests.delete(prepared.requestPath)) {
5374
5402
  throw new Error("background update was not prepared by this manager or was already started");
5375
5403
  }
5376
- const child = spawnUpdater(prepared.helperPath, prepared.requestPath);
5377
- if (child.pid === void 0)
5378
- throw new Error("background Updater did not report a process ID");
5379
- return Object.freeze({ ...prepared, updaterPid: child.pid });
5404
+ try {
5405
+ const child = spawnUpdater(prepared.helperPath, prepared.requestPath);
5406
+ await waitForUpdaterReady(child, () => this.readStatus(prepared.statusPath));
5407
+ if (child.pid === void 0)
5408
+ throw new Error("background Updater did not report a process ID");
5409
+ return Object.freeze({ ...prepared, updaterPid: child.pid });
5410
+ } catch (error51) {
5411
+ await writeFailedStatus(prepared.statusPath, prepared.version, prepared.installation, error51);
5412
+ throw error51;
5413
+ }
5380
5414
  },
5381
5415
  async readStatus(statusPathValue) {
5382
5416
  const statusPath = requireAbsolutePath(statusPathValue, "update status path");
@@ -20071,6 +20105,8 @@ var usageStatisticsSessionSchema = external_exports.strictObject({
20071
20105
  harness,
20072
20106
  sessionId: external_exports.string().min(1).max(512),
20073
20107
  project: project.nullable(),
20108
+ /** Persisted Desktop title for a mapped Session, otherwise its native name; may be absent. */
20109
+ title: external_exports.string().min(1).max(200).optional(),
20074
20110
  /** Models the session used, most requests first. */
20075
20111
  models: external_exports.array(model).max(16),
20076
20112
  firstAtMs: count,
@@ -20137,7 +20173,12 @@ var usageStatisticsResultSchema = external_exports.strictObject({
20137
20173
  * The sessions that spent the most, by cost and by tokens without cache, all filters applied;
20138
20174
  * requests without a native session are not listed here but count everywhere else.
20139
20175
  */
20140
- sessions: external_exports.array(usageStatisticsSessionSchema).max(100)
20176
+ sessions: external_exports.array(usageStatisticsSessionSchema).max(100),
20177
+ /**
20178
+ * The sessions last active in the range, newest first, all filters applied. Optional for older
20179
+ * Hosts; missing is treated as no list rather than no sessions.
20180
+ */
20181
+ recentSessions: external_exports.array(usageStatisticsSessionSchema).max(100).optional()
20141
20182
  });
20142
20183
 
20143
20184
  // packages/shared-contracts/dist/ids.js
@@ -26894,61 +26935,6 @@ function finitePercent(value2) {
26894
26935
  function optionalReset(value2) {
26895
26936
  return nonNegativeSafeInteger(value2);
26896
26937
  }
26897
- function addBreakdown(target, source) {
26898
- if (!source)
26899
- return;
26900
- const fields = [
26901
- ["totalTokens", "totalTokens"],
26902
- ["inputTokens", "inputTokens"],
26903
- ["cachedInputTokens", "cachedInputTokens"],
26904
- ["cacheWriteInputTokens", "cacheWriteInputTokens"],
26905
- ["outputTokens", "outputTokens"],
26906
- ["reasoningOutputTokens", "reasoningOutputTokens"]
26907
- ];
26908
- for (const [sourceField, targetField] of fields) {
26909
- const value2 = nonNegativeSafeInteger(source[sourceField]);
26910
- if (value2 !== void 0)
26911
- target[targetField] = value2;
26912
- }
26913
- }
26914
- function observeCodexTokenUsage(value2) {
26915
- if (!isRecord9(value2) || value2.method !== "thread/tokenUsage/updated")
26916
- return null;
26917
- const params = value2.params;
26918
- if (!isRecord9(params))
26919
- return null;
26920
- const threadId2 = hostThreadIdSchema.safeParse(params.threadId);
26921
- const turnId = hostTurnIdSchema.safeParse(params.turnId);
26922
- if (!threadId2.success || !turnId.success)
26923
- return null;
26924
- const tokenUsage = params.tokenUsage;
26925
- if (!isRecord9(tokenUsage))
26926
- return null;
26927
- const total = isRecord9(tokenUsage.total) ? tokenUsage.total : void 0;
26928
- const last = isRecord9(tokenUsage.last) ? tokenUsage.last : void 0;
26929
- const usage = {};
26930
- addBreakdown(usage, total);
26931
- const contextUsedTokens = nonNegativeSafeInteger(last?.totalTokens);
26932
- const contextWindowTokens = nonNegativeSafeInteger(tokenUsage.modelContextWindow);
26933
- if (contextUsedTokens !== void 0 && contextWindowTokens !== void 0 && contextWindowTokens > 0) {
26934
- usage.contextUsedTokens = contextUsedTokens;
26935
- usage.contextWindowTokens = contextWindowTokens;
26936
- }
26937
- const inputTokens = nonNegativeSafeInteger(last?.inputTokens);
26938
- const cachedInputTokens = nonNegativeSafeInteger(last?.cachedInputTokens);
26939
- if (inputTokens !== void 0 && cachedInputTokens !== void 0 && inputTokens > 0) {
26940
- usage.cacheHitRatePercent = Math.min(100, cachedInputTokens / inputTokens * 100);
26941
- }
26942
- try {
26943
- return {
26944
- threadId: threadId2.data,
26945
- turnId: turnId.data,
26946
- usage: parseHostUsage(usage)
26947
- };
26948
- } catch {
26949
- return null;
26950
- }
26951
- }
26952
26938
  function parseRateLimitWindow(value2) {
26953
26939
  if (!isRecord9(value2))
26954
26940
  return null;
@@ -31922,6 +31908,8 @@ var UsageMeter = class {
31922
31908
  #turnOutputMs = 0;
31923
31909
  #timeToFirstOutputMs;
31924
31910
  #outputTokensPerSecond;
31911
+ #timingUnavailable = false;
31912
+ #timedRequests = /* @__PURE__ */ new Set();
31925
31913
  get metered() {
31926
31914
  return this.#metered;
31927
31915
  }
@@ -31938,12 +31926,53 @@ var UsageMeter = class {
31938
31926
  this.#requests.set(request.requestId, request);
31939
31927
  const duration4 = request.startedAtMs !== void 0 && request.completedAtMs !== void 0 ? request.completedAtMs - request.startedAtMs : 0;
31940
31928
  if (!request.historical && duration4 > 0 && activeTurnId !== null && activeTurnId === this.#turnId) {
31941
- this.#turnOutputTokens += request.outputTokens;
31942
- this.#turnOutputMs += duration4;
31943
- this.#outputTokensPerSecond = this.#turnOutputTokens / (this.#turnOutputMs / 1e3);
31929
+ this.recordOutputTiming(
31930
+ {
31931
+ requestId: request.requestId,
31932
+ outputTokens: request.outputTokens,
31933
+ startedAtMs: request.startedAtMs ?? 0,
31934
+ completedAtMs: request.completedAtMs ?? 0
31935
+ },
31936
+ activeTurnId
31937
+ );
31938
+ }
31939
+ return true;
31940
+ }
31941
+ /** Live timing is independent of history replacement and cannot create billing entries. */
31942
+ recordOutputTiming(request, turnId) {
31943
+ if (turnId !== this.#turnId || this.#timingUnavailable || this.#timedRequests.has(request.requestId))
31944
+ return false;
31945
+ if (!request.requestId || ![request.outputTokens, request.startedAtMs, request.completedAtMs].every(
31946
+ (value2) => Number.isSafeInteger(value2) && value2 >= 0
31947
+ ) || request.completedAtMs <= request.startedAtMs) {
31948
+ this.invalidateOutputTiming(turnId);
31949
+ return true;
31944
31950
  }
31951
+ this.#timedRequests.add(request.requestId);
31952
+ this.#turnOutputTokens += request.outputTokens;
31953
+ this.#turnOutputMs += request.completedAtMs - request.startedAtMs;
31954
+ this.#outputTokensPerSecond = this.#turnOutputTokens / (this.#turnOutputMs / 1e3);
31945
31955
  return true;
31946
31956
  }
31957
+ invalidateOutputTiming(turnId) {
31958
+ if (turnId !== this.#turnId) return;
31959
+ this.#timingUnavailable = true;
31960
+ }
31961
+ /** Discard in-flight timing across connections, but keep the last valid displayed speed. */
31962
+ resetTurnTiming() {
31963
+ this.#turnId = null;
31964
+ this.#turnStartedAtMs = null;
31965
+ this.#timeToFirstOutputMs = void 0;
31966
+ this.#timedRequests.clear();
31967
+ this.#timingUnavailable = false;
31968
+ }
31969
+ /** Replace a read-only session history after recovery, append, rollback or file replacement. */
31970
+ replaceHistory(requests, complete) {
31971
+ this.#requests.clear();
31972
+ this.#invalidRecord = false;
31973
+ for (const request of requests) this.recordRequest({ ...request, historical: true }, null);
31974
+ this.recordHistory(complete);
31975
+ }
31947
31976
  recordHistory(complete) {
31948
31977
  this.#metered = true;
31949
31978
  this.#historyComplete = complete;
@@ -31954,6 +31983,8 @@ var UsageMeter = class {
31954
31983
  this.#turnOutputObserved = false;
31955
31984
  this.#turnOutputTokens = 0;
31956
31985
  this.#turnOutputMs = 0;
31986
+ this.#timedRequests.clear();
31987
+ this.#timingUnavailable = false;
31957
31988
  }
31958
31989
  /** Returns true when this is the Turn's first visible output. */
31959
31990
  outputObserved(turnId, nowMs) {
@@ -31966,7 +31997,6 @@ var UsageMeter = class {
31966
31997
  }
31967
31998
  turnCompleted(turnId) {
31968
31999
  if (turnId !== this.#turnId) return;
31969
- if (this.#turnOutputMs === 0) this.#outputTokensPerSecond = void 0;
31970
32000
  this.#turnId = null;
31971
32001
  this.#turnStartedAtMs = null;
31972
32002
  }
@@ -32001,12 +32031,12 @@ var UsageMeter = class {
32001
32031
  const cacheHitRate = this.#sessionCacheHitRatePercent();
32002
32032
  if (cacheHitRate !== null) usage.sessionCacheHitRatePercent = cacheHitRate;
32003
32033
  }
32004
- if (this.#outputTokensPerSecond !== void 0) {
32005
- usage.outputTokensPerSecond = this.#outputTokensPerSecond;
32006
- }
32007
32034
  } else if (usage.totalCostUsd !== void 0) {
32008
32035
  usage.costSource = "native";
32009
32036
  }
32037
+ if (this.#timingUnavailable) delete usage.outputTokensPerSecond;
32038
+ if (this.#outputTokensPerSecond !== void 0)
32039
+ usage.outputTokensPerSecond = this.#outputTokensPerSecond;
32010
32040
  if (this.#timeToFirstOutputMs !== void 0) {
32011
32041
  usage.timeToFirstOutputMs = this.#timeToFirstOutputMs;
32012
32042
  }
@@ -32905,7 +32935,7 @@ function projectDelegationThreadSnapshot(input) {
32905
32935
  // packages/host-runtime/src/harness-delegation-coordinator.ts
32906
32936
  var IMPLICIT_DEDUPLICATION_MS = 3e4;
32907
32937
  var NATIVE_REF_TIMEOUT_MS = 1e4;
32908
- function delay2(milliseconds) {
32938
+ function delay3(milliseconds) {
32909
32939
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
32910
32940
  }
32911
32941
  function terminal(status2) {
@@ -33295,7 +33325,7 @@ var HarnessDelegationCoordinator = class {
33295
33325
  if (terminal(snapshot2.status)) return { ...snapshot2, timedOut: false };
33296
33326
  const remaining = deadline - Date.now();
33297
33327
  if (remaining <= 0) return { ...snapshot2, timedOut: true };
33298
- await delay2(Math.min(100, remaining));
33328
+ await delay3(Math.min(100, remaining));
33299
33329
  }
33300
33330
  }
33301
33331
  async list(input) {
@@ -35429,15 +35459,21 @@ var HarnessPluginRegistry = class {
35429
35459
  };
35430
35460
 
35431
35461
  // packages/host-runtime/src/harness-plugin-loader.ts
35462
+ function hasValidSessionUsage(value2) {
35463
+ const capability = Reflect.get(value2, "sessionUsage");
35464
+ return capability === void 0 || capability !== null && typeof capability === "object" && ["observe", "read"].every((key) => typeof Reflect.get(capability, key) === "function") && ["requestOptions", "shouldForwardNotification", "reset"].every(
35465
+ (key) => Reflect.get(capability, key) === void 0 || typeof Reflect.get(capability, key) === "function"
35466
+ );
35467
+ }
35432
35468
  function isAdapter(value2) {
35433
35469
  if (!value2 || typeof value2 !== "object") return false;
35434
- return ["inspect", "open", "close"].every((key) => typeof Reflect.get(value2, key) === "function");
35470
+ return hasValidSessionUsage(value2) && ["inspect", "open", "close"].every((key) => typeof Reflect.get(value2, key) === "function");
35435
35471
  }
35436
35472
  function isUsageAdapter(value2) {
35437
35473
  if (!value2 || typeof value2 !== "object" || typeof Reflect.get(value2, "close") !== "function")
35438
35474
  return false;
35439
35475
  const capability = Reflect.get(value2, "usageStatistics");
35440
- return !!capability && typeof capability === "object" && ["listSources", "readSource"].every(
35476
+ return hasValidSessionUsage(value2) && !!capability && typeof capability === "object" && ["listSources", "readSource"].every(
35441
35477
  (key) => typeof Reflect.get(capability, key) === "function"
35442
35478
  ) && !("open" in value2) && !("inspect" in value2);
35443
35479
  }
@@ -35578,11 +35614,12 @@ async function loadHarnessPlugins(options2) {
35578
35614
  }
35579
35615
  if (options2.signal?.aborted) return registry2;
35580
35616
  let next = 0;
35617
+ let batch = pending;
35581
35618
  const loaded = /* @__PURE__ */ new Map();
35582
35619
  const worker = async () => {
35583
35620
  for (; ; ) {
35584
35621
  if (options2.signal?.aborted) return;
35585
- const candidate = pending[next++];
35622
+ const candidate = batch[next++];
35586
35623
  if (!candidate) return;
35587
35624
  if (options2.signal?.aborted) return;
35588
35625
  const { manifest } = candidate;
@@ -35628,10 +35665,19 @@ async function loadHarnessPlugins(options2) {
35628
35665
  loaded.set(candidate, { descriptor, adapter });
35629
35666
  }
35630
35667
  };
35631
- await Promise.all(Array.from({ length: Math.min(4, pending.length) }, () => worker()));
35632
- for (const candidate of pending) {
35633
- const entry = loaded.get(candidate);
35634
- if (entry) await registry2.register(entry.descriptor, entry.adapter);
35668
+ const batches = options2.onUsageAdaptersLoaded ? [
35669
+ pending.filter((c) => c.manifest.kind === "usage"),
35670
+ pending.filter((c) => c.manifest.kind !== "usage")
35671
+ ] : [pending];
35672
+ for (const [index, candidates2] of batches.entries()) {
35673
+ batch = candidates2;
35674
+ next = 0;
35675
+ await Promise.all(Array.from({ length: Math.min(4, batch.length) }, () => worker()));
35676
+ for (const candidate of batch) {
35677
+ const entry = loaded.get(candidate);
35678
+ if (entry) await registry2.register(entry.descriptor, entry.adapter);
35679
+ }
35680
+ if (index === 0) options2.onUsageAdaptersLoaded?.(registry2.usageAdapters);
35635
35681
  }
35636
35682
  return registry2;
35637
35683
  }
@@ -36533,16 +36579,16 @@ var OfficialRuntimeScope = class {
36533
36579
  #scheduleRecovery() {
36534
36580
  if (!this.#recovery || this.#closed || this.#recoveryTimer || this.#starting) return;
36535
36581
  const { delaysMs } = this.#recovery;
36536
- const delay5 = delaysMs[Math.min(this.#recoveryAttempt, delaysMs.length - 1)] ?? 0;
36582
+ const delay6 = delaysMs[Math.min(this.#recoveryAttempt, delaysMs.length - 1)] ?? 0;
36537
36583
  this.#recoveryAttempt++;
36538
36584
  this.#diagnosticOutput.write(
36539
- `codexhost: official Codex is unavailable; restarting in ${String(delay5)}ms
36585
+ `codexhost: official Codex is unavailable; restarting in ${String(delay6)}ms
36540
36586
  `
36541
36587
  );
36542
36588
  const timer = setTimeout(() => {
36543
36589
  this.#recoveryTimer = void 0;
36544
36590
  this.start().catch(() => void 0);
36545
- }, delay5);
36591
+ }, delay6);
36546
36592
  timer.unref();
36547
36593
  this.#recoveryTimer = timer;
36548
36594
  }
@@ -37289,11 +37335,163 @@ async function moveThreadSection(input) {
37289
37335
  }
37290
37336
  }
37291
37337
 
37338
+ // packages/host-runtime/src/observed-session-usage.ts
37339
+ var ObservedSessionUsage = class {
37340
+ constructor(providers, prices, diagnose) {
37341
+ this.providers = providers;
37342
+ this.prices = prices;
37343
+ this.diagnose = diagnose;
37344
+ }
37345
+ providers;
37346
+ prices;
37347
+ diagnose;
37348
+ #states = /* @__PURE__ */ new Map();
37349
+ #pending = /* @__PURE__ */ new Map();
37350
+ #abort = new AbortController();
37351
+ #initializeParams = null;
37352
+ initialized(params) {
37353
+ this.#initializeParams = params;
37354
+ for (const state of this.#states.values()) {
37355
+ state.meter.resetTurnTiming();
37356
+ state.activeTurnId = null;
37357
+ }
37358
+ for (const provider of this.providers()) {
37359
+ try {
37360
+ provider.reset?.();
37361
+ } catch (error51) {
37362
+ this.diagnose(error51);
37363
+ }
37364
+ }
37365
+ }
37366
+ requestOptions(method, params) {
37367
+ if (this.#abort.signal.aborted) return null;
37368
+ let result = null;
37369
+ for (const provider of this.providers()) {
37370
+ try {
37371
+ const patch = provider.requestOptions?.({
37372
+ method,
37373
+ params,
37374
+ initializeParams: this.#initializeParams
37375
+ });
37376
+ if (patch) result = Object.assign(result ?? {}, jsonObjectSchema.parse(patch));
37377
+ } catch (error51) {
37378
+ this.diagnose(error51);
37379
+ }
37380
+ }
37381
+ return result;
37382
+ }
37383
+ shouldForwardNotification(message2) {
37384
+ if (this.#abort.signal.aborted) return true;
37385
+ for (const provider of this.providers()) {
37386
+ try {
37387
+ if (provider.shouldForwardNotification?.(message2) === false) return false;
37388
+ } catch (error51) {
37389
+ this.diagnose(error51);
37390
+ }
37391
+ }
37392
+ return true;
37393
+ }
37394
+ observe(message2, now = Date.now()) {
37395
+ if (this.#abort.signal.aborted) return [];
37396
+ const changed = /* @__PURE__ */ new Set();
37397
+ for (const provider of this.providers()) {
37398
+ try {
37399
+ const observation = provider.observe(message2, now);
37400
+ if (!observation) continue;
37401
+ const state = this.#state(observation.sessionId, provider);
37402
+ if (state.provider !== provider) continue;
37403
+ if (observation.usage) {
37404
+ state.live = parseHostUsage(observation.usage);
37405
+ state.revision++;
37406
+ }
37407
+ const turn = observation.turn;
37408
+ if (turn?.phase === "started" && state.activeTurnId !== turn.id) {
37409
+ state.meter.resetTurnTiming();
37410
+ state.meter.turnStarted(turn.id, now);
37411
+ state.activeTurnId = turn.id;
37412
+ }
37413
+ const timing = observation.requestTiming;
37414
+ if (timing) state.meter.recordOutputTiming(timing, timing.turnId);
37415
+ if (observation.timingUnavailable)
37416
+ state.meter.invalidateOutputTiming(observation.timingUnavailable.turnId);
37417
+ if (turn?.phase === "completed") {
37418
+ state.meter.turnCompleted(turn.id);
37419
+ if (state.activeTurnId === turn.id) state.activeTurnId = null;
37420
+ }
37421
+ const firstOutput = turn?.phase === "output" && state.meter.outputObserved(turn.id, now);
37422
+ if (observation.usage || firstOutput || timing || observation.timingUnavailable || turn?.phase === "started" || turn?.phase === "completed")
37423
+ changed.add(observation.sessionId);
37424
+ } catch (error51) {
37425
+ this.diagnose(error51);
37426
+ }
37427
+ }
37428
+ return [...changed];
37429
+ }
37430
+ async read(sessionId) {
37431
+ if (this.#abort.signal.aborted) return null;
37432
+ let pending = this.#pending.get(sessionId);
37433
+ if (!pending) {
37434
+ pending = this.#refresh(sessionId).finally(() => this.#pending.delete(sessionId));
37435
+ this.#pending.set(sessionId, pending);
37436
+ }
37437
+ await pending;
37438
+ const state = this.#states.get(sessionId);
37439
+ if (!state || this.#abort.signal.aborted) return null;
37440
+ const native = state.live ?? state.history;
37441
+ const result = state.meter.derive(native, await this.prices.lookup());
37442
+ if (result?.unpricedModels?.length) this.prices.missing();
37443
+ return result;
37444
+ }
37445
+ async #refresh(sessionId) {
37446
+ const existing = this.#states.get(sessionId);
37447
+ for (const provider of existing ? [existing.provider] : this.providers()) {
37448
+ const revision = existing?.revision ?? 0;
37449
+ try {
37450
+ const history = await provider.read(sessionId, this.#abort.signal);
37451
+ if (this.#abort.signal.aborted) return;
37452
+ if (!history) {
37453
+ if (existing?.meter.metered) existing.meter.recordHistory(false);
37454
+ continue;
37455
+ }
37456
+ const state = this.#state(sessionId, provider);
37457
+ state.history = history.usage ? parseHostUsage(history.usage) : null;
37458
+ state.meter.replaceHistory(
37459
+ history.requests,
37460
+ history.complete && state.revision === revision
37461
+ );
37462
+ return;
37463
+ } catch (error51) {
37464
+ existing?.meter.recordHistory(false);
37465
+ if (!this.#abort.signal.aborted) this.diagnose(error51);
37466
+ }
37467
+ }
37468
+ }
37469
+ #state(id2, provider) {
37470
+ let state = this.#states.get(id2);
37471
+ if (!state) {
37472
+ state = {
37473
+ provider,
37474
+ meter: new UsageMeter(),
37475
+ live: null,
37476
+ history: null,
37477
+ revision: 0,
37478
+ activeTurnId: null
37479
+ };
37480
+ this.#states.set(id2, state);
37481
+ }
37482
+ return state;
37483
+ }
37484
+ close() {
37485
+ this.#abort.abort();
37486
+ this.#states.clear();
37487
+ }
37488
+ };
37489
+
37292
37490
  // packages/host-runtime/src/app-server-host.ts
37293
37491
  var CONSOLE_REQUEST_TIMEOUT_MS = 12e4;
37294
37492
  var SUBAGENT_TERMINAL_REFRESH_DELAYS_MS = [0, 50, 100, 150];
37295
37493
  var THREAD_USAGE_UPDATED_METHOD = "codexhost/thread/usage/updated";
37296
- function delay3(milliseconds) {
37494
+ function delay4(milliseconds) {
37297
37495
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
37298
37496
  }
37299
37497
  function isRecord18(value2) {
@@ -37490,6 +37688,7 @@ var AppServerHost = class {
37490
37688
  #externalSteering = new ExternalTurnSteering();
37491
37689
  #liveCommandCache = new LiveCommandCatalogCache();
37492
37690
  #modelPrices;
37691
+ #observedSessionUsage;
37493
37692
  #repository;
37494
37693
  #pendingDesktopApprovals = /* @__PURE__ */ new Map();
37495
37694
  #pendingDesktopQuestions = /* @__PURE__ */ new Map();
@@ -37506,7 +37705,6 @@ var AppServerHost = class {
37506
37705
  #activeWorkDrainWaiters = /* @__PURE__ */ new Set();
37507
37706
  #pendingOfficialDelegationThreads = /* @__PURE__ */ new Set();
37508
37707
  #pendingOfficialTerminalStatuses = /* @__PURE__ */ new Map();
37509
- #officialUsageByThread = /* @__PURE__ */ new Map();
37510
37708
  #officialRateLimits = new AccountRateLimits();
37511
37709
  #routeObservationTracker = new RequestRouteObservationTracker();
37512
37710
  #officialServerRequests = /* @__PURE__ */ new Map();
@@ -37524,6 +37722,7 @@ var AppServerHost = class {
37524
37722
  #closeRequested = false;
37525
37723
  #pluginLoadAbort = new AbortController();
37526
37724
  #pluginLoading;
37725
+ #usagePluginsReady;
37527
37726
  #desktopRequests = new DesktopRequestQueue();
37528
37727
  #drainActiveWorkOnInputEnd = false;
37529
37728
  #desktopInputEnded = false;
@@ -37535,6 +37734,13 @@ var AppServerHost = class {
37535
37734
  ...options2
37536
37735
  };
37537
37736
  this.#modelPrices = options2.modelPrices ?? new ModelPriceCatalog();
37737
+ this.#observedSessionUsage = new ObservedSessionUsage(
37738
+ () => [...this.#externalAdapters.values(), ...this.#usageOnlyAdapters].flatMap(
37739
+ (adapter) => adapter.sessionUsage ? [adapter.sessionUsage] : []
37740
+ ),
37741
+ this.#modelPrices,
37742
+ (error51) => this.#diagnose(error51)
37743
+ );
37538
37744
  this.#writer = new OrderedWriter(this.#options.desktopOutput, (value2) => {
37539
37745
  this.#noteDesktopReply(value2);
37540
37746
  return this.#takeConsoleReply(value2);
@@ -37702,18 +37908,29 @@ var AppServerHost = class {
37702
37908
  this.#options.usageStatistics?.attach(
37703
37909
  () => [.../* @__PURE__ */ new Set([...this.#externalAdapters.values(), ...this.#usageOnlyAdapters])].flatMap(
37704
37910
  (adapter) => adapter.usageStatistics ? [{ harness: adapter.harnessId, capability: adapter.usageStatistics }] : []
37705
- )
37911
+ ),
37912
+ () => this.#repository.list()
37706
37913
  );
37707
37914
  }
37708
37915
  #waitForPlugins() {
37709
- return this.#pluginLoading ??= this.#loadInstalledPlugins().catch((error51) => {
37710
- this.#diagnose(`Harness plugin load failed: ${errorMessage4(error51)}`);
37711
- });
37916
+ if (!this.#pluginLoading) {
37917
+ const ready = Promise.withResolvers();
37918
+ const usageReady = () => ready.resolve(void 0);
37919
+ this.#usagePluginsReady = ready.promise;
37920
+ this.#pluginLoading = this.#loadInstalledPlugins(usageReady).catch((error51) => {
37921
+ this.#diagnose(`Harness plugin load failed: ${errorMessage4(error51)}`);
37922
+ }).finally(usageReady);
37923
+ }
37924
+ return this.#pluginLoading;
37712
37925
  }
37713
- async #loadInstalledPlugins() {
37926
+ async #loadInstalledPlugins(usageReady) {
37714
37927
  if (!this.#options.pluginRoots || this.#pluginLoadAbort.signal.aborted) return;
37715
37928
  const plugins = await loadHarnessPlugins({
37716
37929
  roots: this.#options.pluginRoots,
37930
+ onUsageAdaptersLoaded: (adapters) => {
37931
+ if (!this.#pluginLoadAbort.signal.aborted) this.#usageOnlyAdapters = [...adapters.values()];
37932
+ usageReady();
37933
+ },
37717
37934
  launchCommandForPlugin: (id2) => this.#launchSettings.initialCommand(id2),
37718
37935
  context: this.#options.pluginContext ?? {
37719
37936
  environment: this.#options.environment ?? process.env,
@@ -37734,6 +37951,7 @@ var AppServerHost = class {
37734
37951
  for (const [id2, adapter] of plugins.adapters) this.#externalAdapters.set(id2, adapter);
37735
37952
  }
37736
37953
  async #closeAdapters() {
37954
+ this.#observedSessionUsage.close();
37737
37955
  const results = await Promise.allSettled([
37738
37956
  this.#plugins?.close(),
37739
37957
  ...[...new Set(this.#options.externalAdapters?.values())].map(
@@ -37937,7 +38155,13 @@ var AppServerHost = class {
37937
38155
  if (request.method === "initialize") {
37938
38156
  try {
37939
38157
  const nativeGeneration = this.#officialRuntimeScope.gate.phase === "ready" ? this.#officialRuntimeScope.owner.generation : void 0;
37940
- const response = await this.#officialRuntime.initializeProtocol(requestObject(request));
38158
+ void this.#waitForPlugins();
38159
+ await this.#usagePluginsReady;
38160
+ const params = requestObject(request);
38161
+ const options2 = this.#observedSessionUsage.requestOptions("initialize", params);
38162
+ const nativeParams = options2 ? { ...params, ...options2 } : params;
38163
+ const response = await this.#officialRuntime.initializeProtocol(nativeParams);
38164
+ if (!isRecord18(response.error)) this.#observedSessionUsage.initialized(nativeParams);
37941
38165
  await this.#writer.json({ ...response, id: request.id });
37942
38166
  this.#nativeAccountObserver?.initialized(nativeGeneration);
37943
38167
  } catch (error51) {
@@ -38768,6 +38992,15 @@ var AppServerHost = class {
38768
38992
  return;
38769
38993
  }
38770
38994
  try {
38995
+ const options2 = isRecord18(request.params) ? this.#observedSessionUsage.requestOptions(request.method, request.params) : null;
38996
+ if (options2) {
38997
+ const original = parseJsonFrame(frame);
38998
+ if (isRecord18(original))
38999
+ frame = encodeJsonFrame({
39000
+ ...original,
39001
+ params: { ...isRecord18(original.params) ? original.params : {}, ...options2 }
39002
+ });
39003
+ }
38771
39004
  await this.#officialRuntime.sendFrame(frame);
38772
39005
  this.#markDesktopRequestAnswered(request.id);
38773
39006
  } catch {
@@ -38786,6 +39019,7 @@ var AppServerHost = class {
38786
39019
  return;
38787
39020
  }
38788
39021
  const parsed2 = input.value;
39022
+ const observedAtMs = Date.now();
38789
39023
  this.#observeOfficialTurnStartResponse(parsed2);
38790
39024
  let forwarded = parsed2;
38791
39025
  if (isRecord18(parsed2) && typeof parsed2.method === "string" && "id" in parsed2) {
@@ -38800,17 +39034,6 @@ var AppServerHost = class {
38800
39034
  if (accountScopedNotification) {
38801
39035
  if (parsed2.method === "account/updated") this.#officialRateLimits.reset(input.accountId);
38802
39036
  }
38803
- const tokenUsage = observeCodexTokenUsage(parsed2);
38804
- if (tokenUsage) {
38805
- const previous = this.#officialUsageByThread.get(tokenUsage.threadId);
38806
- try {
38807
- this.#officialUsageByThread.set(
38808
- tokenUsage.threadId,
38809
- parseHostUsage({ ...previous ?? {}, ...tokenUsage.usage })
38810
- );
38811
- } catch {
38812
- }
38813
- }
38814
39037
  const rateLimits = observeCodexRateLimits(parsed2);
38815
39038
  if (rateLimits) this.#officialRateLimits.observe(input.accountId, rateLimits);
38816
39039
  try {
@@ -38819,8 +39042,16 @@ var AppServerHost = class {
38819
39042
  this.#diagnose(error51);
38820
39043
  }
38821
39044
  this.#routeObservationTracker.bindOfficialResponse(parsed2);
38822
- if (forwarded === parsed2) await this.#writer.frame(input.frame);
38823
- else await this.#writer.json(forwarded);
39045
+ const privateTelemetry = isRecord18(parsed2) && typeof parsed2.method === "string" && !("id" in parsed2) && !this.#observedSessionUsage.shouldForwardNotification(parsed2);
39046
+ if (!privateTelemetry) {
39047
+ if (forwarded === parsed2) await this.#writer.frame(input.frame);
39048
+ else await this.#writer.json(forwarded);
39049
+ }
39050
+ void (this.#usagePluginsReady ?? Promise.resolve()).then(async () => {
39051
+ for (const threadId2 of this.#observedSessionUsage.observe(parsed2, observedAtMs)) {
39052
+ await this.#writer.json({ method: THREAD_USAGE_UPDATED_METHOD, params: { threadId: threadId2 } });
39053
+ }
39054
+ }).catch((error51) => this.#diagnose(error51));
38824
39055
  this.#nativeAccountObserver?.observe(parsed2);
38825
39056
  const deletedProjectId = observeDeletedProject(parsed2);
38826
39057
  if (deletedProjectId) {
@@ -39761,12 +39992,13 @@ var AppServerHost = class {
39761
39992
  );
39762
39993
  return;
39763
39994
  }
39995
+ await this.#waitForPlugins();
39764
39996
  const accountId = await this.#currentCodexAccountId();
39765
39997
  if (accountId) await this.#refreshOfficialRateLimits(accountId);
39766
39998
  const accountCredits = this.#officialAccountCredits(accountId ?? void 0);
39767
39999
  const result2 = threadUsageInspectionSchema.parse({
39768
40000
  threadId: params.data.threadId,
39769
- usage: this.#officialUsageByThread.get(params.data.threadId) ?? null,
40001
+ usage: await this.#observedSessionUsage.read(params.data.threadId),
39770
40002
  ...accountCredits ? { accountCredits } : {}
39771
40003
  });
39772
40004
  await this.#writer.json(rpcEnvelope(request, { result: jsonValueSchema.parse(result2) }));
@@ -41366,7 +41598,7 @@ var AppServerHost = class {
41366
41598
  }
41367
41599
  if (status2 === "idle" && previousStatus === "active") {
41368
41600
  for (const [index, waitMs] of SUBAGENT_TERMINAL_REFRESH_DELAYS_MS.entries()) {
41369
- if (waitMs > 0) await delay3(waitMs);
41601
+ if (waitMs > 0) await delay4(waitMs);
41370
41602
  await this.#refreshOpenSubagentThread(
41371
41603
  threadId2,
41372
41604
  index === SUBAGENT_TERMINAL_REFRESH_DELAYS_MS.length - 1
@@ -41744,7 +41976,7 @@ import { randomUUID as randomUUID15 } from "node:crypto";
41744
41976
  import { chmod as chmod3, lstat as lstat5, mkdir as mkdir12, open as open9, readFile as readFile16, readdir as readdir5, rename as rename11, rm as rm11 } from "node:fs/promises";
41745
41977
  import { uptime } from "node:os";
41746
41978
  import path26 from "node:path";
41747
- import { setTimeout as delay4 } from "node:timers/promises";
41979
+ import { setTimeout as delay5 } from "node:timers/promises";
41748
41980
  var LOCK_RETRY_COUNT = 200;
41749
41981
  var LOCK_RETRY_DELAY_MS = 25;
41750
41982
  var LOCK_MALFORMED_GRACE_MS = 1e3;
@@ -42000,7 +42232,7 @@ async function withRemoteAppServerSocketInitializationLock(socketPath, action) {
42000
42232
  return await action();
42001
42233
  }
42002
42234
  }
42003
- await delay4(LOCK_RETRY_DELAY_MS);
42235
+ await delay5(LOCK_RETRY_DELAY_MS);
42004
42236
  }
42005
42237
  throw new Error(
42006
42238
  `Remote app-server socket initialization is already in progress at ${socketPath}`
@@ -44462,13 +44694,15 @@ function createHostUpdateCoordinator(options2) {
44462
44694
  return authenticated ?? fetchLatestGitHubRelease({ signal: requestSignal });
44463
44695
  });
44464
44696
  let candidate = null;
44697
+ let currentStatusPath = null;
44465
44698
  async function latestStatus(context) {
44466
- const discovered = await discoverLatestUpdateStatus(context.common.stateDirectory);
44467
- if (!discovered) return null;
44468
- if (discovered.status.phase !== "succeeded" && discovered.status.phase !== "failed" && !await isUpdateOperationActive(context.common.stateDirectory)) {
44469
- return null;
44699
+ if (!currentStatusPath) {
44700
+ const active = await discoverActiveUpdateStatus(context.common.stateDirectory);
44701
+ if (!active) return null;
44702
+ currentStatusPath = active.statusPath;
44470
44703
  }
44471
- return publicStatus(discovered.status);
44704
+ const status2 = await manager.readStatus(currentStatusPath);
44705
+ return status2 ? publicStatus(status2) : null;
44472
44706
  }
44473
44707
  async function installable(context, release) {
44474
44708
  if (context.installation.kind === "npm") return true;
@@ -44552,6 +44786,7 @@ function createHostUpdateCoordinator(options2) {
44552
44786
  if (existing) return { status: existing };
44553
44787
  throw new Error("Another update operation is already active");
44554
44788
  }
44789
+ currentStatusPath = null;
44555
44790
  let resolvePrepared;
44556
44791
  let rejectPrepared;
44557
44792
  const preparedReady = new Promise((resolve, reject) => {
@@ -44565,6 +44800,7 @@ function createHostUpdateCoordinator(options2) {
44565
44800
  }
44566
44801
  const onPrepared = async (info) => {
44567
44802
  await lock.setStatusPath(info.statusPath);
44803
+ currentStatusPath = info.statusPath;
44568
44804
  resolvePrepared(info);
44569
44805
  };
44570
44806
  const prepareAndStart = async () => {
@@ -44594,7 +44830,7 @@ function createHostUpdateCoordinator(options2) {
44594
44830
  onPrepared
44595
44831
  });
44596
44832
  }
44597
- if (platform !== "darwin") manager.start(prepared2);
44833
+ if (platform !== "darwin") await manager.start(prepared2);
44598
44834
  } catch (error51) {
44599
44835
  await lock.release();
44600
44836
  rejectPrepared(error51);
@@ -44631,6 +44867,20 @@ import { setImmediate as yieldToEventLoop } from "node:timers/promises";
44631
44867
  // packages/harness-adapter/dist/usage-statistics.js
44632
44868
  var MAX_SESSION_ID_LENGTH = 512;
44633
44869
  var MAX_CWD_LENGTH = 4096;
44870
+ var MAX_SESSION_TITLE_LENGTH = 200;
44871
+ function usageSessionTitle(value2) {
44872
+ if (typeof value2 !== "string")
44873
+ return void 0;
44874
+ const normalized = value2.replaceAll("\0", "").replaceAll(/\s+/gu, " ").trim();
44875
+ if (!normalized)
44876
+ return void 0;
44877
+ if (normalized.length <= MAX_SESSION_TITLE_LENGTH)
44878
+ return normalized;
44879
+ const characters = [...normalized];
44880
+ if (characters.length <= MAX_SESSION_TITLE_LENGTH)
44881
+ return normalized;
44882
+ return `${characters.slice(0, MAX_SESSION_TITLE_LENGTH - 1).join("").trimEnd()}…`;
44883
+ }
44634
44884
  function sessionIdOk(value2) {
44635
44885
  return typeof value2 === "string" && value2.length > 0 && value2.length <= MAX_SESSION_ID_LENGTH;
44636
44886
  }
@@ -44674,6 +44924,12 @@ function parseHarnessUsageEntry(value2) {
44674
44924
  return null;
44675
44925
  if (entry.cwd !== void 0 && !cwdOk(entry.cwd))
44676
44926
  return null;
44927
+ if (entry.sessionTitle !== void 0) {
44928
+ const sessionTitle = usageSessionTitle(entry.sessionTitle);
44929
+ if (sessionTitle === void 0)
44930
+ return null;
44931
+ entry.sessionTitle = sessionTitle;
44932
+ }
44677
44933
  if (entry.tokensUnknown !== void 0 && entry.tokensUnknown !== true)
44678
44934
  return null;
44679
44935
  if (entry.costUsd !== void 0 && (typeof entry.costUsd !== "number" || !Number.isFinite(entry.costUsd) || entry.costUsd < 0)) {
@@ -44685,8 +44941,31 @@ function parseHarnessUsageEntry(value2) {
44685
44941
  return entry;
44686
44942
  }
44687
44943
 
44944
+ // packages/host-runtime/src/usage-session-titles.ts
44945
+ function withHostSessionTitles(result, records) {
44946
+ const titles = /* @__PURE__ */ new Map();
44947
+ for (const record4 of records) {
44948
+ const ref = record4.nativeSessionRef;
44949
+ const title = usageSessionTitle(record4.title);
44950
+ if (record4.state !== "ready" || !ref || ref.harnessId !== record4.harnessId || !title) continue;
44951
+ const key = `${record4.harnessId}\0${ref.nativeSessionId}`;
44952
+ const previous = titles.get(key);
44953
+ if (!previous || record4.updatedAt > previous.updatedAt)
44954
+ titles.set(key, { title, updatedAt: record4.updatedAt });
44955
+ }
44956
+ const decorate = (sessions) => sessions.map((session) => {
44957
+ const title = titles.get(`${session.harness}\0${session.sessionId}`)?.title;
44958
+ return title ? { ...session, title } : session;
44959
+ });
44960
+ return {
44961
+ ...result,
44962
+ sessions: decorate(result.sessions),
44963
+ ...result.recentSessions ? { recentSessions: decorate(result.recentSessions) } : {}
44964
+ };
44965
+ }
44966
+
44688
44967
  // packages/host-runtime/src/usage-statistics.ts
44689
- var CACHE_VERSION = 6;
44968
+ var CACHE_VERSION = 7;
44690
44969
  var STALE_AFTER_MS = 1e4;
44691
44970
  var WARM_DELAY_MS = 3e3;
44692
44971
  var PERSIST_INTERVAL_MS = 6e4;
@@ -44706,7 +44985,8 @@ function compact(entry) {
44706
44985
  entry.cwd ?? null,
44707
44986
  entry.costUsd ?? null,
44708
44987
  entry.credits ?? null,
44709
- entry.tokensUnknown === true
44988
+ entry.tokensUnknown === true,
44989
+ entry.sessionTitle ?? null
44710
44990
  ];
44711
44991
  }
44712
44992
  function interner() {
@@ -44720,9 +45000,9 @@ function interner() {
44720
45000
  };
44721
45001
  }
44722
45002
  function expand(value2, intern) {
44723
- if (!Array.isArray(value2) || value2.length !== 14) return null;
45003
+ if (!Array.isArray(value2) || value2.length !== 15) return null;
44724
45004
  const [id2, occurredAtMs, model2, input, cached2, written, written1h, output, reasoning] = value2;
44725
- const [sessionId, cwd, costUsd, credits, tokensUnknown] = value2.slice(9);
45005
+ const [sessionId, cwd, costUsd, credits, tokensUnknown, sessionTitle] = value2.slice(9);
44726
45006
  return parseHarnessUsageEntry({
44727
45007
  id: id2,
44728
45008
  occurredAtMs,
@@ -44737,7 +45017,8 @@ function expand(value2, intern) {
44737
45017
  ...cwd !== null ? { cwd: intern(cwd) } : {},
44738
45018
  ...costUsd !== null ? { costUsd } : {},
44739
45019
  ...credits !== null ? { credits } : {},
44740
- ...tokensUnknown === true ? { tokensUnknown } : {}
45020
+ ...tokensUnknown === true ? { tokensUnknown } : {},
45021
+ ...sessionTitle !== null ? { sessionTitle: intern(sessionTitle) } : {}
44741
45022
  });
44742
45023
  }
44743
45024
  function message(error51) {
@@ -44828,6 +45109,7 @@ var UsageStatistics = class {
44828
45109
  #caches = /* @__PURE__ */ new Map();
44829
45110
  #failures = /* @__PURE__ */ new Map();
44830
45111
  #sources = null;
45112
+ #readThreadTitles;
44831
45113
  #loaded = null;
44832
45114
  #refreshing = null;
44833
45115
  #refreshedAtMs = 0;
@@ -44842,9 +45124,10 @@ var UsageStatistics = class {
44842
45124
  this.#options = options2;
44843
45125
  this.#directory = path36.join(options2.directory, `v${CACHE_VERSION}`);
44844
45126
  }
44845
- /** The Harnesses to read; call once their plugins are loaded. */
44846
- attach(sources) {
45127
+ /** Attach native storage and, optionally, fresh persisted Desktop title metadata. */
45128
+ attach(sources, readThreadTitles) {
44847
45129
  this.#sources = sources;
45130
+ this.#readThreadTitles = readThreadTitles;
44848
45131
  this.#scheduleWarm();
44849
45132
  }
44850
45133
  /** Reads every source in the background soon after the sources are attached. */
@@ -44871,11 +45154,19 @@ var UsageStatistics = class {
44871
45154
  }
44872
45155
  const now = this.#now();
44873
45156
  if (!this.#refreshing && now - this.#refreshedAtMs > STALE_AFTER_MS) this.#startRefresh();
44874
- return this.#aggregate(
45157
+ const result = this.#aggregate(
44875
45158
  typeof params === "string" ? { range: params } : params,
44876
45159
  now,
44877
45160
  await this.#options.prices.lookup()
44878
45161
  );
45162
+ if (this.#readThreadTitles && (result.sessions.length || result.recentSessions?.length)) {
45163
+ try {
45164
+ return withHostSessionTitles(result, await this.#readThreadTitles());
45165
+ } catch (error51) {
45166
+ this.#options.diagnose?.(`Usage statistics Thread titles unavailable: ${message(error51)}`);
45167
+ }
45168
+ }
45169
+ return result;
44879
45170
  }
44880
45171
  /** Completes the current refresh, for tests and callers that need a settled result. */
44881
45172
  async settled() {
@@ -45153,6 +45444,10 @@ var UsageStatistics = class {
45153
45444
  sessions.set(key, session);
45154
45445
  }
45155
45446
  session.project ??= project2;
45447
+ if (entry.sessionTitle && (session.titleAtMs === void 0 || entry.occurredAtMs >= session.titleAtMs)) {
45448
+ session.title = entry.sessionTitle;
45449
+ session.titleAtMs = entry.occurredAtMs;
45450
+ }
45156
45451
  if (model2 !== null) session.models.set(model2, (session.models.get(model2) ?? 0) + 1);
45157
45452
  session.firstAtMs = Math.min(session.firstAtMs, entry.occurredAtMs);
45158
45453
  session.lastAtMs = Math.max(session.lastAtMs, entry.occurredAtMs);
@@ -45160,21 +45455,26 @@ var UsageStatistics = class {
45160
45455
  }
45161
45456
  }
45162
45457
  const allSessions = [...sessions.values()];
45163
- const top = /* @__PURE__ */ new Set([
45164
- ...[...allSessions].sort((left, right) => right.totals.costUsd - left.totals.costUsd).slice(0, SESSIONS_PER_MEASURE),
45165
- ...[...allSessions].sort((left, right) => tokensWithoutCache(right.totals) - tokensWithoutCache(left.totals)).slice(0, SESSIONS_PER_MEASURE)
45166
- ]);
45167
- const sessionList = [...top].map((session) => ({
45458
+ const toSession = (session) => ({
45168
45459
  harness: session.harness,
45169
45460
  sessionId: session.sessionId,
45170
45461
  project: session.project,
45462
+ ...session.title ? { title: session.title } : {},
45171
45463
  models: [...session.models].sort((left, right) => right[1] - left[1]).slice(0, MODELS_PER_SESSION).map(([name]) => name),
45172
45464
  firstAtMs: session.firstAtMs,
45173
45465
  lastAtMs: session.lastAtMs,
45174
45466
  ...session.totals
45175
- })).sort(
45467
+ });
45468
+ const top = /* @__PURE__ */ new Set([
45469
+ ...[...allSessions].sort((left, right) => right.totals.costUsd - left.totals.costUsd).slice(0, SESSIONS_PER_MEASURE),
45470
+ ...[...allSessions].sort((left, right) => tokensWithoutCache(right.totals) - tokensWithoutCache(left.totals)).slice(0, SESSIONS_PER_MEASURE)
45471
+ ]);
45472
+ const sessionList = [...top].map(toSession).sort(
45176
45473
  (left, right) => right.costUsd - left.costUsd || tokensWithoutCache(right) - tokensWithoutCache(left)
45177
45474
  );
45475
+ const recentSessions = [...allSessions].sort(
45476
+ (left, right) => right.lastAtMs - left.lastAtMs || right.firstAtMs - left.firstAtMs || left.sessionId.localeCompare(right.sessionId)
45477
+ ).slice(0, SESSIONS_PER_MEASURE).map(toSession);
45178
45478
  if (unlistedModel) this.#options.prices.missing();
45179
45479
  const byName = (left, right) => left === right ? 0 : left === null ? 1 : right === null ? -1 : left.localeCompare(right);
45180
45480
  return {
@@ -45219,7 +45519,8 @@ var UsageStatistics = class {
45219
45519
  byHarness: grouped(byHarness, (name) => ({ harness: name })),
45220
45520
  byModel: grouped(byModel, (name) => ({ model: name })).slice(0, 4096).map((row) => ({ ...row, harnessPricedRequests: harnessPriced.get(row.model) ?? 0 })),
45221
45521
  byProject: grouped(byProject, (name) => ({ project: name })).slice(0, 4096),
45222
- sessions: sessionList
45522
+ sessions: sessionList,
45523
+ recentSessions
45223
45524
  };
45224
45525
  }
45225
45526
  };