@narumitw/pi-usage 0.60.11 → 0.61.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.
package/docs/providers.md CHANGED
@@ -28,9 +28,10 @@ The README contains the capability overview and shared security requirements.
28
28
  - Source: the Codex usage and earned-reset endpoints using Pi's resolved runtime authorization
29
29
  - Displayed data: returned duration-based windows, resets, credits, earned usage-limit resets, and additional model buckets
30
30
  - Reset mutation: `POST /wham/rate-limit-reset-credits/consume` with a unique redemption request ID and, when available, the selected opaque credit ID
31
- - Statusline examples: `codex 59% ↻ 2h30m 61% ↻ 2d15m`, `codex fast 59% ↻ 2h30m`, or `codex spark 100% ↻ 2h30m`. Set `codexStatusResetCountdown` to `false` for the legacy `5h` and `wk` labels.
31
+ - Statusline examples: `codex 59% ↻ 2h30m 61% ↻ 2d15m`, `codex fast 59% ↻ 2h30m`, or `codex spark 100% ↻ 2h30m`. Set `codexStatusPercentage` to `"used"` to show consumed percentages, or set `codexStatusResetCountdown` to `false` for the legacy `5h` and `wk` labels.
32
32
 
33
33
  The statusline selects a returned bucket that matches the current Codex model when one is available.
34
+ The percentage preference affects only this compact status and defaults to remaining quota; it does not change the detailed report or returned usage data.
34
35
  Unlike `pi-codex-usage`, this successor intentionally has no Codex CLI fallback because the CLI may be logged into a different account than Pi's active runtime account.
35
36
 
36
37
  Reset redemption is available only when Codex is the current provider.
@@ -45,17 +46,21 @@ Opaque credit and account IDs are never shown or persisted by the extension.
45
46
  - Provider ID: `kimi-coding`
46
47
  - Semantics: Kimi Coding Plan request windows plus a separate Extra Usage booster wallet
47
48
  - Source: `GET https://api.kimi.com/coding/v1/usages` using Pi's freshly resolved runtime Bearer credential
48
- - Displayed plan data: the weekly summary, returned sub-windows, used and remaining request percentages, and valid reset times
49
- - Displayed wallet data: balance, monthly spend, and monthly charge limit
50
- - Statusline examples: `kimi 99% 5h 96% wk` or `kimi 95% 1d`
49
+ - Displayed plan data: count-based weekly summaries and returned sub-windows, plus reported ratio-based five-hour, weekly, or monthly quotas and valid reset times
50
+ - Displayed wallet data: balance, monthly spend, and monthly charge limit when the wallet supplies consistent currency and amounts
51
+ - Statusline examples: `kimi 99% 5h 96% wk`, `kimi 56% 5h 21% mo`, or `kimi 95% 1d`
51
52
 
52
53
  Both Pi API-key credentials and Pi OAuth credentials are accepted because current Pi resolves each form as Bearer authorization for the same official Kimi inference origin.
53
54
  The extension queries the fixed usage endpoint only when both the selected model origin and the effective resolved-auth origin are `https://api.kimi.com`.
54
55
  Custom and proxy origins fail before network access, redirects are rejected, and the credential is never sent to an override from Kimi Code's environment-specific development path.
55
56
 
56
- Plan buckets remain integer request counts and are rendered with their source-defined windows.
57
+ Count-based plan buckets remain integer request counts and are rendered with their source-defined windows.
57
58
  Live responses omit both `used` and `remaining` on untouched rows and may report only `remaining` once usage starts, so a row with a positive `limit` and no counters means zero usage, and when only one counter is present the other is derived from `limit`.
58
- Counters that are present but malformed, unknown units, duplicate windows, invalid timestamps, and malformed rows remain unavailable rather than receiving guessed semantics.
59
+ A monthly-plan response reported in [issue #1390](https://github.com/narumiruna/pi-extensions/issues/1390) supplies `usages.limit_month_total.used_ratio` without a count-based monthly row; the extension shows its remaining percentage and `reset_time` as a monthly bucket without inventing a fixed month duration.
60
+ The same map can supply `limit_5h` and `limit_week` when those windows are not present in `limits` or `usage`; `limit_month_code` is not a second total quota.
61
+ Explicit count-based windows with identifiable durations take precedence, including duplicate or malformed count rows, so ratio rows cannot mask their unavailable state.
62
+ Ratios must be finite numbers between zero and one; unknown map keys, malformed rows, invalid timestamps, and unknown count-based units do not receive guessed semantics.
63
+ The top-level wallet may be `boosterWallet` or `booster_wallet` (the former takes precedence when both are present); its nested fields retain their existing camelCase validation.
59
64
  Booster-wallet `amount` and `amountLeft` values use Kimi's first-party conversion of 1,000,000 fixed-point units per cent, while monthly values already arrive in cents.
60
65
  Wallet values retain their currency and stay separate from plan requests and percentages in reports and the statusline.
61
66
  Wallet fields remain unavailable unless the response supplies one consistent currency; missing monthly values are omitted, and an enabled zero cap is shown as zero.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narumitw/pi-usage",
3
- "version": "0.60.11",
3
+ "version": "0.61.1",
4
4
  "description": "Pi extension that shows current-account usage and DeepSeek API balance for supported providers.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/codex-fast.ts CHANGED
@@ -9,6 +9,7 @@ export const CODEX_FAST_MODEL_IDS: ReadonlySet<string> = new Set([
9
9
  "gpt-5.6-luna",
10
10
  "gpt-5.6-sol",
11
11
  "gpt-5.6-terra",
12
+ "gpt-6-sol",
12
13
  ]);
13
14
 
14
15
  export type CodexFastAvailability =
package/src/format.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { CodexStatusPercentage } from "./settings.js";
1
2
  import type { ProviderUsageState, UsageBucket, UsageDisplayState, UsageModel, UsageReport } from "./types.js";
2
3
 
3
4
  const BAR_SEGMENTS = 20;
@@ -54,10 +55,11 @@ export function formatUsageStatusline(
54
55
  model?: UsageModel,
55
56
  now = Date.now(),
56
57
  showCodexResetCountdown = true,
58
+ codexStatusPercentage: CodexStatusPercentage = "remaining",
57
59
  ): string | undefined {
58
60
  if (report.providerId === "baseten") return formatBasetenStatusline(report);
59
61
  if (report.providerId === "openai-codex") {
60
- return formatCodexStatusline(report, model, now, showCodexResetCountdown);
62
+ return formatCodexStatusline(report, model, now, showCodexResetCountdown, codexStatusPercentage);
61
63
  }
62
64
  if (report.providerId === "deepseek") return formatDeepSeekStatusline(report);
63
65
  if (report.providerId === "fireworks") return formatFireworksStatusline(report);
@@ -280,6 +282,15 @@ function formatOpenCodeZenStatusline(report: UsageReport): string | undefined {
280
282
  function formatKimiCodingReport(lines: string[], report: UsageReport): void {
281
283
  for (const bucket of report.buckets) {
282
284
  const reset = bucket.resetsAt ? ` (resets ${formatReset(bucket.resetsAt)})` : "";
285
+ if (bucket.unit === "percent") {
286
+ const remaining = bucket.remaining === undefined ? undefined : Math.round(clampPercent(bucket.remaining));
287
+ const value =
288
+ bucket.used === undefined || remaining === undefined
289
+ ? "unavailable"
290
+ : `${100 - remaining}% used · ${remaining}% left`;
291
+ lines.push(`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${value}${reset}`);
292
+ continue;
293
+ }
283
294
  if (bucket.used === undefined || bucket.limit === undefined) {
284
295
  lines.push(`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}unavailable${reset}`);
285
296
  continue;
@@ -309,15 +320,18 @@ function formatKimiCodingReport(lines: string[], report: UsageReport): void {
309
320
  function formatKimiCodingStatusline(report: UsageReport): string | undefined {
310
321
  const fiveHour = report.buckets.find((bucket) => bucket.id === "five-hour");
311
322
  const weekly = report.buckets.find((bucket) => bucket.id === "weekly");
312
- const subWindow = fiveHour ?? report.buckets.find((bucket) => bucket.id !== "weekly");
313
- const selected = [subWindow, weekly].filter(
323
+ const monthly = report.buckets.find((bucket) => bucket.id === "monthly");
324
+ const subWindow = fiveHour ?? report.buckets.find((bucket) => bucket.id !== "weekly" && bucket.id !== "monthly");
325
+ const selected = [subWindow, weekly, monthly].filter(
314
326
  (bucket, index, buckets): bucket is UsageBucket => bucket !== undefined && buckets.indexOf(bucket) === index,
315
327
  );
316
328
  const parts = ["kimi"];
317
329
  for (const bucket of selected) {
318
- if (!bucket.limit || bucket.remaining === undefined) continue;
330
+ if (bucket.remaining === undefined || (bucket.unit !== "percent" && !bucket.limit)) continue;
319
331
  const fallback = bucket.id === "weekly" ? "weekly" : "5h";
320
- parts.push(`${percentRemaining(bucket)}% ${formatWindowLabel(bucket.windowMinutes, fallback, true)}`);
332
+ const window = bucket.id === "monthly" ? "mo" : formatWindowLabel(bucket.windowMinutes, fallback, true);
333
+ const remaining = bucket.unit === "percent" ? Math.round(clampPercent(bucket.remaining)) : percentRemaining(bucket);
334
+ parts.push(`${remaining}% ${window}`);
321
335
  }
322
336
  return parts.length > 1 ? parts.join(" ") : undefined;
323
337
  }
@@ -521,6 +535,7 @@ function formatCodexStatusline(
521
535
  model?: UsageModel,
522
536
  now = Date.now(),
523
537
  showResetCountdown = true,
538
+ percentage: CodexStatusPercentage = "remaining",
524
539
  ): string | undefined {
525
540
  const group = selectCodexGroup(report, model);
526
541
  if (!group) return formatCodexCreditsStatus(report);
@@ -529,7 +544,8 @@ function formatCodexStatusline(
529
544
  const parts = [group === "codex" ? "codex" : `codex ${compactLimitLabel(labelBucket?.groupLabel ?? group)}`];
530
545
  for (const bucket of buckets) {
531
546
  if (bucket.remaining === undefined) continue;
532
- const percent = `${clampPercent(bucket.remaining).toFixed(0)}%`;
547
+ const displayedPercentage = percentage === "used" ? (bucket.used ?? 100 - bucket.remaining) : bucket.remaining;
548
+ const percent = `${clampPercent(displayedPercentage).toFixed(0)}%`;
533
549
  const fallback = bucket.id.endsWith(":secondary") ? "weekly" : "5h";
534
550
  const window = formatWindowLabel(bucket.windowMinutes, fallback, true);
535
551
  if (!showResetCountdown) {
package/src/index.ts CHANGED
@@ -65,6 +65,7 @@ export {
65
65
  XAI_ADAPTER,
66
66
  } from "./query.js";
67
67
  export type {
68
+ CodexStatusPercentage,
68
69
  UsageSettings,
69
70
  UsageSettingsRuntime,
70
71
  UsageSettingsState,
@@ -76,5 +76,5 @@ function deepSeekCurrency(value: unknown): DeepSeekCurrency | undefined {
76
76
  }
77
77
 
78
78
  function decimalAmount(value: unknown): value is string {
79
- return typeof value === "string" && value.length <= 64 && /^(?:0|[1-9]\d*)(?:\.\d+)?$/u.test(value);
79
+ return typeof value === "string" && value.length <= 64 && /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/u.test(value);
80
80
  }
@@ -5,6 +5,11 @@ const FIVE_HOUR_WINDOW_MINUTES = 300;
5
5
  const DAILY_WINDOW_MINUTES = 1_440;
6
6
  const WEEKLY_WINDOW_MINUTES = 10_080;
7
7
  const FIXED_POINT_UNITS_PER_CENT = 1_000_000;
8
+ const RATIO_WINDOWS = [
9
+ { key: "limit_5h", id: "five-hour", label: "Five-hour window", windowMinutes: FIVE_HOUR_WINDOW_MINUTES },
10
+ { key: "limit_week", id: "weekly", label: "Weekly window", windowMinutes: WEEKLY_WINDOW_MINUTES },
11
+ { key: "limit_month_total", id: "monthly", label: "Monthly window" },
12
+ ] as const;
8
13
 
9
14
  /**
10
15
  * Source contract revalidated on 2026-08-27.
@@ -24,7 +29,9 @@ export function normalizeKimiCodingUsagePayload(payload: KimiCodingUsagePayload,
24
29
  if (!root) throw new Error("Kimi Coding usage response was not an object.");
25
30
 
26
31
  const candidates: UsageBucket[] = [];
32
+ const explicitWindows = new Set<number>();
27
33
  let omittedWindow = false;
34
+ if (root.usage !== undefined) explicitWindows.add(WEEKLY_WINDOW_MINUTES);
28
35
  const summary = parseUsageRow(root.usage, WEEKLY_WINDOW_MINUTES, "Weekly window");
29
36
  if (summary) candidates.push(summary);
30
37
  else if (root.usage !== undefined) omittedWindow = true;
@@ -33,6 +40,7 @@ export function normalizeKimiCodingUsagePayload(payload: KimiCodingUsagePayload,
33
40
  for (const raw of root.limits) {
34
41
  const item = asObject(raw);
35
42
  const windowMinutes = parseWindowMinutes(item?.window);
43
+ if (windowMinutes !== undefined) explicitWindows.add(windowMinutes);
36
44
  const label = sanitizedLabel(item?.name);
37
45
  const bucket =
38
46
  windowMinutes === undefined
@@ -55,9 +63,24 @@ export function normalizeKimiCodingUsagePayload(payload: KimiCodingUsagePayload,
55
63
  if (rows.length === 1) buckets.push(rows[0] as UsageBucket);
56
64
  else omittedWindow = true;
57
65
  }
58
- buckets.sort((left, right) => (left.windowMinutes ?? 0) - (right.windowMinutes ?? 0));
66
+ const usages = asObject(root.usages);
67
+ if (root.usages !== undefined && !usages) omittedWindow = true;
68
+ if (usages) {
69
+ for (const window of RATIO_WINDOWS) {
70
+ const raw = usages[window.key];
71
+ if (raw === undefined || ("windowMinutes" in window && explicitWindows.has(window.windowMinutes))) continue;
72
+ const bucket = parseRatioWindow(raw, window.id, window.label);
73
+ if (bucket) {
74
+ buckets.push({
75
+ ...bucket,
76
+ ...("windowMinutes" in window ? { windowMinutes: window.windowMinutes } : {}),
77
+ });
78
+ } else omittedWindow = true;
79
+ }
80
+ }
81
+ buckets.sort((left, right) => (left.windowMinutes ?? Infinity) - (right.windowMinutes ?? Infinity));
59
82
 
60
- const metrics = parseBoosterWallet(root.boosterWallet);
83
+ const metrics = parseBoosterWallet(root.boosterWallet === undefined ? root.booster_wallet : root.boosterWallet);
61
84
  if (buckets.length === 0 && metrics.length === 0) {
62
85
  throw new Error("Kimi Coding usage endpoint returned no displayable usage data.");
63
86
  }
@@ -104,6 +127,22 @@ function parseUsageRow(value: unknown, windowMinutes: number, label: string): Us
104
127
  };
105
128
  }
106
129
 
130
+ function parseRatioWindow(value: unknown, id: string, label: string): UsageBucket | undefined {
131
+ const row = asObject(value);
132
+ const ratio = row?.used_ratio;
133
+ if (typeof ratio !== "number" || !Number.isFinite(ratio) || ratio < 0 || ratio > 1) return undefined;
134
+ const resetsAt = asIsoEpochSeconds(row?.reset_time);
135
+ const used = ratio * 100;
136
+ return {
137
+ id,
138
+ label,
139
+ used,
140
+ remaining: 100 - used,
141
+ unit: "percent",
142
+ ...(resetsAt !== undefined ? { resetsAt } : {}),
143
+ };
144
+ }
145
+
107
146
  function parseWindowMinutes(value: unknown): number | undefined {
108
147
  const window = asObject(value);
109
148
  if (!window) return undefined;
package/src/settings.ts CHANGED
@@ -9,15 +9,19 @@ import { isBoundedTargetId } from "./usage-targets.js";
9
9
  export const USAGE_SETTINGS_FILE = "pi-usage.json";
10
10
  export const MAX_USAGE_SETTINGS_BYTES = 64 * 1024;
11
11
 
12
+ export type CodexStatusPercentage = "remaining" | "used";
13
+
12
14
  export interface UsageSettings {
13
15
  codexFastMode: boolean;
14
16
  codexStatusResetCountdown: boolean;
17
+ codexStatusPercentage: CodexStatusPercentage;
15
18
  selectedTargets: Record<string, string>;
16
19
  }
17
20
 
18
21
  export const DEFAULT_USAGE_SETTINGS: Readonly<UsageSettings> = Object.freeze({
19
22
  codexFastMode: false,
20
23
  codexStatusResetCountdown: true,
24
+ codexStatusPercentage: "remaining",
21
25
  selectedTargets: Object.freeze({}),
22
26
  });
23
27
 
@@ -66,6 +70,13 @@ export function normalizeUsageSettings(value: unknown): UsageSettings | undefine
66
70
  if (Object.hasOwn(value, "codexStatusResetCountdown") && typeof value.codexStatusResetCountdown !== "boolean") {
67
71
  return undefined;
68
72
  }
73
+ if (
74
+ Object.hasOwn(value, "codexStatusPercentage") &&
75
+ value.codexStatusPercentage !== "remaining" &&
76
+ value.codexStatusPercentage !== "used"
77
+ ) {
78
+ return undefined;
79
+ }
69
80
  if (Object.hasOwn(value, "fireworksAccountId") && !isFireworksAccountId(value.fireworksAccountId)) {
70
81
  return undefined;
71
82
  }
@@ -82,6 +93,8 @@ export function normalizeUsageSettings(value: unknown): UsageSettings | undefine
82
93
  typeof value.codexStatusResetCountdown === "boolean"
83
94
  ? value.codexStatusResetCountdown
84
95
  : DEFAULT_USAGE_SETTINGS.codexStatusResetCountdown,
96
+ codexStatusPercentage:
97
+ value.codexStatusPercentage === "used" ? "used" : DEFAULT_USAGE_SETTINGS.codexStatusPercentage,
85
98
  selectedTargets: effectiveTargets,
86
99
  };
87
100
  }
package/src/types.ts CHANGED
@@ -214,7 +214,9 @@ export interface ZaiPlanInfo {
214
214
  export type KimiCodingUsagePayload = {
215
215
  usage?: unknown;
216
216
  limits?: unknown;
217
+ usages?: unknown;
217
218
  boosterWallet?: unknown;
219
+ booster_wallet?: unknown;
218
220
  };
219
221
 
220
222
  export type XaiUserPayload = {
@@ -2,12 +2,19 @@ import { stripVTControlCharacters } from "node:util";
2
2
  import { type ExtensionCommandContext, getSettingsListTheme } from "@earendil-works/pi-coding-agent";
3
3
  import { Key, matchesKey, type SettingItem, SettingsList, Text } from "@earendil-works/pi-tui";
4
4
  import { errorMessage } from "./core.js";
5
- import type { UsageSettingsRuntime } from "./settings.js";
5
+ import type { UsageSettings, UsageSettingsRuntime } from "./settings.js";
6
6
 
7
7
  const OFF = "Off";
8
8
  const ON = "On";
9
+ const REMAINING = "Remaining";
10
+ const USED = "Used";
9
11
 
10
- type UsageSettingId = "codexFastMode" | "codexStatusResetCountdown";
12
+ type UsageSettingId = "codexFastMode" | "codexStatusResetCountdown" | "codexStatusPercentage";
13
+
14
+ function settingValueLabel(id: UsageSettingId, value: UsageSettings[UsageSettingId]): string {
15
+ if (id === "codexStatusPercentage") return value === "used" ? USED : REMAINING;
16
+ return value === true ? ON : OFF;
17
+ }
11
18
 
12
19
  export async function showUsageSettings(
13
20
  ctx: ExtensionCommandContext,
@@ -45,6 +52,13 @@ export async function showUsageSettings(
45
52
  currentValue: state.settings.codexStatusResetCountdown ? ON : OFF,
46
53
  values: [OFF, ON],
47
54
  },
55
+ {
56
+ id: "codexStatusPercentage",
57
+ label: "Codex percentage",
58
+ description: "Show remaining or used Codex quota in the statusline.",
59
+ currentValue: settingValueLabel("codexStatusPercentage", state.settings.codexStatusPercentage),
60
+ values: [REMAINING, USED],
61
+ },
48
62
  ];
49
63
  const rule = new HorizontalRule({ ruleStyle: (text) => theme.fg("border", text) });
50
64
 
@@ -55,11 +69,11 @@ export async function showUsageSettings(
55
69
  localController.abort();
56
70
  done(changed);
57
71
  };
58
- const queueUpdate = (id: UsageSettingId, requested: boolean, display: string) => {
72
+ const queueUpdate = <Id extends UsageSettingId>(id: Id, requested: UsageSettings[Id], display: string) => {
59
73
  saveQueue = saveQueue.then(async () => {
60
74
  const previous = settingsRuntime.get().settings[id];
61
75
  if (settingsRuntime.get().kind === "invalid") {
62
- settingsList.updateValue(id, previous ? ON : OFF);
76
+ settingsList.updateValue(id, settingValueLabel(id, previous));
63
77
  if (!signal.aborted && isCurrent()) {
64
78
  ctx.ui.notify("Repair pi-usage.json and reload before changing settings.", "error");
65
79
  tui.requestRender();
@@ -67,10 +81,12 @@ export async function showUsageSettings(
67
81
  return;
68
82
  }
69
83
  try {
70
- await settingsRuntime.update({ [id]: requested }, signal);
84
+ const patch: Partial<UsageSettings> = {};
85
+ patch[id] = requested;
86
+ await settingsRuntime.update(patch, signal);
71
87
  } catch (error) {
72
88
  if (signal.aborted || !isCurrent()) return;
73
- settingsList.updateValue(id, previous ? ON : OFF);
89
+ settingsList.updateValue(id, settingValueLabel(id, previous));
74
90
  ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
75
91
  tui.requestRender();
76
92
  return;
@@ -90,7 +106,11 @@ export async function showUsageSettings(
90
106
  getSettingsListTheme(),
91
107
  (id, value) => {
92
108
  if (closing || signal.aborted || !isCurrent()) return;
93
- queueUpdate(id as UsageSettingId, value !== OFF, value);
109
+ if (id === "codexStatusPercentage") {
110
+ queueUpdate(id, value === USED ? "used" : "remaining", value);
111
+ } else if (id === "codexFastMode" || id === "codexStatusResetCountdown") {
112
+ queueUpdate(id, value === ON, value);
113
+ }
94
114
  },
95
115
  cancel,
96
116
  );
package/src/usage.ts CHANGED
@@ -166,10 +166,17 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
166
166
  }
167
167
  return;
168
168
  }
169
+ const settings = settingsRuntime.get().settings;
169
170
  const showCodexResetCountdown =
170
- outcome.state.report.providerId === "openai-codex" && settingsRuntime.get().settings.codexStatusResetCountdown;
171
+ outcome.state.report.providerId === "openai-codex" && settings.codexStatusResetCountdown;
171
172
  const now = Date.now();
172
- const rawValue = formatUsageStatusline(outcome.state.report, model, now, showCodexResetCountdown);
173
+ const rawValue = formatUsageStatusline(
174
+ outcome.state.report,
175
+ model,
176
+ now,
177
+ showCodexResetCountdown,
178
+ settings.codexStatusPercentage,
179
+ );
173
180
  const value = rawValue ? fastRuntime.decorateStatus(model, rawValue) : undefined;
174
181
  if (!safeSetStatus(ctx, value)) return;
175
182
  if (shouldSchedule && sessionActive) scheduleStatusRefresh(ctx, model);
@@ -998,7 +1005,7 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
998
1005
  () => statusGeneration === menuGeneration && !controller.signal.aborted,
999
1006
  (id) => {
1000
1007
  if (
1001
- id === "codexStatusResetCountdown" &&
1008
+ (id === "codexStatusResetCountdown" || id === "codexStatusPercentage") &&
1002
1009
  stableCurrent &&
1003
1010
  statusGeneration === menuGeneration &&
1004
1011
  !controller.signal.aborted