@cortexkit/opencode-antigravity-auth 2.0.0 → 2.0.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/dist/cli.js +308 -81
- package/dist/cli.js.map +4 -4
- package/dist/index.js +16634 -15416
- package/dist/index.js.map +4 -4
- package/dist/src/plugin/account-command-oauth.d.ts +34 -0
- package/dist/src/plugin/account-command-oauth.d.ts.map +1 -0
- package/dist/src/plugin/account-command-oauth.js +156 -0
- package/dist/src/plugin/account-command-oauth.js.map +1 -0
- package/dist/src/plugin/auth-loader.d.ts +16 -1
- package/dist/src/plugin/auth-loader.d.ts.map +1 -1
- package/dist/src/plugin/auth-loader.js +113 -32
- package/dist/src/plugin/auth-loader.js.map +1 -1
- package/dist/src/plugin/background-quota-refresh.d.ts +146 -0
- package/dist/src/plugin/background-quota-refresh.d.ts.map +1 -0
- package/dist/src/plugin/background-quota-refresh.js +347 -0
- package/dist/src/plugin/background-quota-refresh.js.map +1 -0
- package/dist/src/plugin/command-data.d.ts +61 -8
- package/dist/src/plugin/command-data.d.ts.map +1 -1
- package/dist/src/plugin/command-data.js +231 -39
- package/dist/src/plugin/command-data.js.map +1 -1
- package/dist/src/plugin/commands.d.ts +12 -9
- package/dist/src/plugin/commands.d.ts.map +1 -1
- package/dist/src/plugin/commands.js +140 -33
- package/dist/src/plugin/commands.js.map +1 -1
- package/dist/src/plugin/config/schema.d.ts +2 -0
- package/dist/src/plugin/config/schema.d.ts.map +1 -1
- package/dist/src/plugin/config/schema.js +24 -0
- package/dist/src/plugin/config/schema.js.map +1 -1
- package/dist/src/plugin/debug.d.ts +7 -0
- package/dist/src/plugin/debug.d.ts.map +1 -1
- package/dist/src/plugin/debug.js +25 -0
- package/dist/src/plugin/debug.js.map +1 -1
- package/dist/src/plugin/fetch-interceptor.d.ts.map +1 -1
- package/dist/src/plugin/fetch-interceptor.js +13 -1
- package/dist/src/plugin/fetch-interceptor.js.map +1 -1
- package/dist/src/plugin/index.d.ts +7 -0
- package/dist/src/plugin/index.d.ts.map +1 -1
- package/dist/src/plugin/index.js +127 -24
- package/dist/src/plugin/index.js.map +1 -1
- package/dist/src/plugin/killswitch.d.ts.map +1 -1
- package/dist/src/plugin/killswitch.js +12 -9
- package/dist/src/plugin/killswitch.js.map +1 -1
- package/dist/src/plugin/oauth-methods.d.ts.map +1 -1
- package/dist/src/plugin/oauth-methods.js +2 -10
- package/dist/src/plugin/oauth-methods.js.map +1 -1
- package/dist/src/plugin/quota.d.ts +42 -1
- package/dist/src/plugin/quota.d.ts.map +1 -1
- package/dist/src/plugin/quota.js +185 -41
- package/dist/src/plugin/quota.js.map +1 -1
- package/dist/src/plugin/ui/auth-menu.d.ts.map +1 -1
- package/dist/src/plugin/ui/auth-menu.js +2 -4
- package/dist/src/plugin/ui/auth-menu.js.map +1 -1
- package/dist/src/plugin/ui/quota-status.d.ts.map +1 -1
- package/dist/src/plugin/ui/quota-status.js +3 -5
- package/dist/src/plugin/ui/quota-status.js.map +1 -1
- package/dist/src/sidebar-state.d.ts +126 -9
- package/dist/src/sidebar-state.d.ts.map +1 -1
- package/dist/src/sidebar-state.js +185 -25
- package/dist/src/sidebar-state.js.map +1 -1
- package/dist/src/tui/command-dialogs.js +101 -3
- package/dist/src/tui/command-dialogs.js.map +1 -1
- package/dist/src/tui.d.ts.map +1 -1
- package/dist/src/tui.js +55 -20
- package/dist/src/tui.js.map +1 -1
- package/package.json +6 -6
- package/src/plugin/command-data.ts +331 -51
- package/src/sidebar-state.ts +306 -32
- package/src/tui/command-dialogs.tsx +136 -3
- package/src/tui-compiled/plugin/command-data.ts +331 -51
- package/src/tui-compiled/sidebar-state.ts +306 -32
- package/src/tui-compiled/tui/command-dialogs.tsx +189 -69
- package/src/tui-compiled/tui.tsx +73 -30
- package/src/tui.tsx +82 -28
|
@@ -52,15 +52,20 @@ import { xdgState } from 'xdg-basedir'
|
|
|
52
52
|
|
|
53
53
|
export const SIDEBAR_STATE_VERSION = 1 as const
|
|
54
54
|
|
|
55
|
-
export type SidebarQuotaKey =
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
| '
|
|
59
|
-
|
|
55
|
+
export type SidebarQuotaKey = 'gemini' | 'non-gemini'
|
|
56
|
+
|
|
57
|
+
export interface SidebarQuotaWindowEntry {
|
|
58
|
+
window: 'weekly' | '5h'
|
|
59
|
+
remainingPercent: number
|
|
60
|
+
resetAt?: number
|
|
61
|
+
}
|
|
60
62
|
|
|
61
63
|
export interface SidebarQuotaEntry {
|
|
64
|
+
/** Most-constrained window's remaining % (derived, for back-compat). */
|
|
62
65
|
remainingPercent: number
|
|
63
66
|
resetAt?: number
|
|
67
|
+
/** Per-window breakdown. Omitted in pre-windows snapshots. */
|
|
68
|
+
windows?: SidebarQuotaWindowEntry[]
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
export interface SidebarAccountState {
|
|
@@ -71,6 +76,11 @@ export interface SidebarAccountState {
|
|
|
71
76
|
current: boolean
|
|
72
77
|
cooldownUntil?: number
|
|
73
78
|
quota: Partial<Record<SidebarQuotaKey, SidebarQuotaEntry>>
|
|
79
|
+
/**
|
|
80
|
+
* Captured plan tier. Absent when unknown — do NOT default to `free-tier`.
|
|
81
|
+
* `id` is the raw upstream string (e.g. `"free-tier"`); never normalised.
|
|
82
|
+
*/
|
|
83
|
+
tier?: { id: string; paidId?: string; capturedAt: number }
|
|
74
84
|
}
|
|
75
85
|
|
|
76
86
|
export interface SidebarRoutingEntry {
|
|
@@ -294,17 +304,13 @@ function normalizeAccount(input: unknown): SidebarAccountState | null {
|
|
|
294
304
|
const quotaRaw = record.quota
|
|
295
305
|
const quota: SidebarAccountState['quota'] = {}
|
|
296
306
|
if (isObject(quotaRaw)) {
|
|
297
|
-
for (const key of [
|
|
298
|
-
'claude',
|
|
299
|
-
'gemini-pro',
|
|
300
|
-
'gemini-flash',
|
|
301
|
-
'gpt-oss',
|
|
302
|
-
] as const) {
|
|
307
|
+
for (const key of ['gemini', 'non-gemini'] as const) {
|
|
303
308
|
const entry = (quotaRaw as Record<string, unknown>)[key]
|
|
304
309
|
const normalized = normalizeQuota(entry)
|
|
305
310
|
if (normalized) quota[key] = normalized
|
|
306
311
|
}
|
|
307
312
|
}
|
|
313
|
+
const tier = normalizeTier(record.tier)
|
|
308
314
|
return {
|
|
309
315
|
id,
|
|
310
316
|
label,
|
|
@@ -313,18 +319,60 @@ function normalizeAccount(input: unknown): SidebarAccountState | null {
|
|
|
313
319
|
current,
|
|
314
320
|
cooldownUntil,
|
|
315
321
|
quota,
|
|
322
|
+
...(tier !== undefined ? { tier } : {}),
|
|
316
323
|
}
|
|
317
324
|
}
|
|
318
325
|
|
|
326
|
+
function normalizeTier(
|
|
327
|
+
input: unknown,
|
|
328
|
+
): { id: string; paidId?: string; capturedAt: number } | undefined {
|
|
329
|
+
if (!isObject(input)) return undefined
|
|
330
|
+
const record = input as Record<string, unknown>
|
|
331
|
+
const id =
|
|
332
|
+
typeof record.id === 'string' && record.id.length > 0 ? record.id : null
|
|
333
|
+
const capturedAt = toFiniteNumber(record.capturedAt)
|
|
334
|
+
if (!id || capturedAt === null) return undefined
|
|
335
|
+
const paidId =
|
|
336
|
+
typeof record.paidId === 'string' && record.paidId.length > 0
|
|
337
|
+
? record.paidId
|
|
338
|
+
: undefined
|
|
339
|
+
return { id, ...(paidId ? { paidId } : {}), capturedAt }
|
|
340
|
+
}
|
|
341
|
+
|
|
319
342
|
function normalizeQuota(input: unknown): SidebarQuotaEntry | null {
|
|
320
343
|
if (!isObject(input)) return null
|
|
321
344
|
const record = input as Record<string, unknown>
|
|
322
345
|
const remaining = toFiniteNumber(record.remainingPercent)
|
|
323
346
|
if (remaining === null) return null
|
|
324
347
|
const resetAt = toFiniteNumber(record.resetAt) ?? undefined
|
|
348
|
+
|
|
349
|
+
// Tolerant: deserialize windows array if present; drop malformed entries.
|
|
350
|
+
const windowsRaw = record.windows
|
|
351
|
+
let windows: SidebarQuotaEntry['windows']
|
|
352
|
+
if (Array.isArray(windowsRaw)) {
|
|
353
|
+
const parsed = windowsRaw
|
|
354
|
+
.filter(isObject)
|
|
355
|
+
.map((w) => {
|
|
356
|
+
const win = (w as Record<string, unknown>).window
|
|
357
|
+
const rp = toFiniteNumber(
|
|
358
|
+
(w as Record<string, unknown>).remainingPercent,
|
|
359
|
+
)
|
|
360
|
+
const ra = toFiniteNumber((w as Record<string, unknown>).resetAt)
|
|
361
|
+
if ((win !== 'weekly' && win !== '5h') || rp === null) return null
|
|
362
|
+
return {
|
|
363
|
+
window: win as 'weekly' | '5h',
|
|
364
|
+
remainingPercent: clampNumber(rp, 0, 100),
|
|
365
|
+
resetAt: ra ?? undefined,
|
|
366
|
+
}
|
|
367
|
+
})
|
|
368
|
+
.filter((v): v is NonNullable<typeof v> => v !== null)
|
|
369
|
+
if (parsed.length > 0) windows = parsed
|
|
370
|
+
}
|
|
371
|
+
|
|
325
372
|
return {
|
|
326
373
|
remainingPercent: clampNumber(remaining, 0, 100),
|
|
327
374
|
resetAt,
|
|
375
|
+
windows,
|
|
328
376
|
}
|
|
329
377
|
}
|
|
330
378
|
|
|
@@ -441,13 +489,236 @@ export interface SidebarAccountRedactionInput {
|
|
|
441
489
|
/** Health score in `[0, 100]`. Defaults to 100 when missing. */
|
|
442
490
|
healthScore?: number
|
|
443
491
|
cachedQuota?: {
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
492
|
+
gemini?: {
|
|
493
|
+
remainingFraction?: number
|
|
494
|
+
resetTime?: string
|
|
495
|
+
windows?: Array<{
|
|
496
|
+
window: 'weekly' | '5h'
|
|
497
|
+
remainingFraction: number
|
|
498
|
+
resetTime: string
|
|
499
|
+
}>
|
|
500
|
+
}
|
|
501
|
+
'non-gemini'?: {
|
|
502
|
+
remainingFraction?: number
|
|
503
|
+
resetTime?: string
|
|
504
|
+
windows?: Array<{
|
|
505
|
+
window: 'weekly' | '5h'
|
|
506
|
+
remainingFraction: number
|
|
507
|
+
resetTime: string
|
|
508
|
+
}>
|
|
509
|
+
}
|
|
510
|
+
// Legacy per-pool keys written by older versions of the plugin.
|
|
511
|
+
// Accepted here so the normalizer can map them to the current pool
|
|
512
|
+
// keys at read time without requiring a disk migration.
|
|
513
|
+
[legacyKey: string]:
|
|
514
|
+
| {
|
|
515
|
+
remainingFraction?: number
|
|
516
|
+
resetTime?: string
|
|
517
|
+
modelCount?: number
|
|
518
|
+
windows?: Array<{
|
|
519
|
+
window: 'weekly' | '5h'
|
|
520
|
+
remainingFraction: number
|
|
521
|
+
resetTime: string
|
|
522
|
+
}>
|
|
523
|
+
}
|
|
524
|
+
| undefined
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Opaque identity stamp that was attached to the persisted quota snapshot.
|
|
528
|
+
* Used together with `currentQuotaAccountId` to detect a stale cache that
|
|
529
|
+
* landed on the wrong account after an index shift or token replacement.
|
|
530
|
+
* PII-safe — it is a 16-char hash, not the refresh token itself.
|
|
531
|
+
*/
|
|
532
|
+
cachedQuotaAccountId?: string
|
|
533
|
+
/**
|
|
534
|
+
* Opaque identity stamp for the account that is currently at this index.
|
|
535
|
+
* The redactor drops `cachedQuota` when `cachedQuotaAccountId` is set
|
|
536
|
+
* AND does not match this value, mirroring `toCommandAccountRow` in the
|
|
537
|
+
* command-data service. Omitted in the persisted sidebar state.
|
|
538
|
+
*/
|
|
539
|
+
currentQuotaAccountId?: string
|
|
540
|
+
/**
|
|
541
|
+
* Captured plan tier from `loadCodeAssist`. Absent when unknown. The `id`
|
|
542
|
+
* is the raw upstream string; `capturedAt` is epoch ms. Not PII — tier
|
|
543
|
+
* metadata survives the redaction boundary unchanged.
|
|
544
|
+
*/
|
|
545
|
+
tier?: { id: string; paidId?: string; capturedAt: number }
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Build a `{ id, capturedAt }` tier object from account fields, or `undefined`
|
|
550
|
+
* when either field is absent. Centralised so every producer (index.ts,
|
|
551
|
+
* background-quota-refresh.ts, command-data.ts) uses the same guard and the
|
|
552
|
+
* same shape -- the same pattern shipped as inline literals across three files
|
|
553
|
+
* and produced six missed-field bugs; one helper ends that.
|
|
554
|
+
*/
|
|
555
|
+
export function toCapturedTier(account: {
|
|
556
|
+
capturedTierId?: string
|
|
557
|
+
capturedPaidTierId?: string
|
|
558
|
+
capturedTierAt?: number
|
|
559
|
+
}): { id: string; paidId?: string; capturedAt: number } | undefined {
|
|
560
|
+
return account.capturedTierId !== undefined &&
|
|
561
|
+
account.capturedTierAt !== undefined
|
|
562
|
+
? {
|
|
563
|
+
id: account.capturedTierId,
|
|
564
|
+
...(account.capturedPaidTierId !== undefined
|
|
565
|
+
? { paidId: account.capturedPaidTierId }
|
|
566
|
+
: {}),
|
|
567
|
+
capturedAt: account.capturedTierAt,
|
|
568
|
+
}
|
|
569
|
+
: undefined
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Map pre-pool legacy quota keys to the current two-pool schema at read time.
|
|
574
|
+
*
|
|
575
|
+
* Legacy mappings (empirically settled from burn tests):
|
|
576
|
+
* `gemini-pro` + `gemini-flash` → `gemini`
|
|
577
|
+
* `claude` + `gpt-oss` → `non-gemini`
|
|
578
|
+
*
|
|
579
|
+
* Where multiple legacy keys collapse into one pool the MIN remainingFraction
|
|
580
|
+
* and earliest resetTime are used (most-constrained-first, matching the
|
|
581
|
+
* window-level rule). No `windows` arrays are invented for migrated entries;
|
|
582
|
+
* the single aggregate bar is the correct render for pre-window snapshots.
|
|
583
|
+
*
|
|
584
|
+
* Non-legacy keys that are already canonical (`gemini`, `non-gemini`) are
|
|
585
|
+
* carried through unchanged. The next real quota refresh overwrites any
|
|
586
|
+
* migrated snapshot with authoritative data.
|
|
587
|
+
*/
|
|
588
|
+
export function normalizeLegacyCachedQuota(
|
|
589
|
+
raw: SidebarAccountRedactionInput['cachedQuota'],
|
|
590
|
+
): SidebarAccountRedactionInput['cachedQuota'] {
|
|
591
|
+
if (!raw) return raw
|
|
592
|
+
|
|
593
|
+
// Pool already carries current keys — fast path when no legacy keys present.
|
|
594
|
+
const hasLegacy =
|
|
595
|
+
'gemini-pro' in raw ||
|
|
596
|
+
'gemini-flash' in raw ||
|
|
597
|
+
'claude' in raw ||
|
|
598
|
+
'gpt-oss' in raw
|
|
599
|
+
if (!hasLegacy) return raw
|
|
600
|
+
|
|
601
|
+
type PoolEntry = NonNullable<
|
|
602
|
+
SidebarAccountRedactionInput['cachedQuota']
|
|
603
|
+
>['gemini']
|
|
604
|
+
|
|
605
|
+
// Pick the earlier of two ISO reset timestamps, preferring a defined
|
|
606
|
+
// value over undefined. Extracted so both branches of minFraction use
|
|
607
|
+
// the same merge rather than the previous asymmetry where the a-wins
|
|
608
|
+
// branch dropped b's possibly-earlier resetTime.
|
|
609
|
+
const earlierResetTime = (
|
|
610
|
+
a: string | undefined,
|
|
611
|
+
b: string | undefined,
|
|
612
|
+
): string | undefined => {
|
|
613
|
+
if (!a) return b
|
|
614
|
+
if (!b) return a
|
|
615
|
+
return a < b ? a : b
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const minFraction = (a: PoolEntry, b: PoolEntry): PoolEntry => {
|
|
619
|
+
if (!a) return b
|
|
620
|
+
if (!b) return a
|
|
621
|
+
const fa = a.remainingFraction ?? 1
|
|
622
|
+
const fb = b.remainingFraction ?? 1
|
|
623
|
+
const winner = fa <= fb ? a : b
|
|
624
|
+
const loser = fa <= fb ? b : a
|
|
625
|
+
// Always merge to the earliest resetTime regardless of which pool wins
|
|
626
|
+
// the fraction comparison — an earlier window expiry from the loser
|
|
627
|
+
// pool must not be silently dropped.
|
|
628
|
+
return {
|
|
629
|
+
...winner,
|
|
630
|
+
resetTime: earlierResetTime(winner.resetTime, loser.resetTime),
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const gemini: PoolEntry =
|
|
635
|
+
minFraction(
|
|
636
|
+
raw.gemini,
|
|
637
|
+
minFraction(
|
|
638
|
+
raw['gemini-pro'] as PoolEntry,
|
|
639
|
+
raw['gemini-flash'] as PoolEntry,
|
|
640
|
+
),
|
|
641
|
+
) ?? raw.gemini
|
|
642
|
+
|
|
643
|
+
const nonGemini: PoolEntry =
|
|
644
|
+
minFraction(
|
|
645
|
+
raw['non-gemini'],
|
|
646
|
+
minFraction(raw.claude as PoolEntry, raw['gpt-oss'] as PoolEntry),
|
|
647
|
+
) ?? raw['non-gemini']
|
|
648
|
+
|
|
649
|
+
return {
|
|
650
|
+
...(gemini !== undefined ? { gemini } : {}),
|
|
651
|
+
...(nonGemini !== undefined ? { 'non-gemini': nonGemini } : {}),
|
|
448
652
|
}
|
|
449
653
|
}
|
|
450
654
|
|
|
655
|
+
/**
|
|
656
|
+
* Project a raw cachedQuota pool entry into the sidebar-safe shape.
|
|
657
|
+
*
|
|
658
|
+
* Centralized seam — every producer (quota.ts pushSidebarQuotaSnapshot,
|
|
659
|
+
* auth-loader.ts materializer, command-data.ts writeSidebar) routes
|
|
660
|
+
* through here so the `windows` array is never dropped by an inline
|
|
661
|
+
* `{ remainingFraction, resetTime }` literal.
|
|
662
|
+
*
|
|
663
|
+
* Tolerant: returns `undefined` when the source is missing or the
|
|
664
|
+
* `remainingFraction` is not a finite number. Legacy entries without
|
|
665
|
+
* `windows` produce `{ remainingPercent, resetAt }` only — the TUI's
|
|
666
|
+
* legacy path then renders a single bar.
|
|
667
|
+
*/
|
|
668
|
+
export function projectQuotaPoolForSidebar(source: {
|
|
669
|
+
remainingFraction?: number
|
|
670
|
+
resetTime?: string
|
|
671
|
+
windows?: ReadonlyArray<{
|
|
672
|
+
window: 'weekly' | '5h'
|
|
673
|
+
remainingFraction: number
|
|
674
|
+
resetTime: string
|
|
675
|
+
}>
|
|
676
|
+
}): SidebarQuotaEntry | undefined {
|
|
677
|
+
const fraction = source.remainingFraction
|
|
678
|
+
if (typeof fraction !== 'number' || !Number.isFinite(fraction))
|
|
679
|
+
return undefined
|
|
680
|
+
const remainingPercent = clampNumber(Math.round(fraction * 100), 0, 100)
|
|
681
|
+
let resetAt: number | undefined
|
|
682
|
+
if (typeof source.resetTime === 'string' && source.resetTime.length > 0) {
|
|
683
|
+
const parsed = Date.parse(source.resetTime)
|
|
684
|
+
if (Number.isFinite(parsed)) resetAt = parsed
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const windows: SidebarQuotaEntry['windows'] = source.windows?.length
|
|
688
|
+
? source.windows.map((w) => ({
|
|
689
|
+
window: w.window,
|
|
690
|
+
remainingPercent: clampNumber(
|
|
691
|
+
Math.round(w.remainingFraction * 100),
|
|
692
|
+
0,
|
|
693
|
+
100,
|
|
694
|
+
),
|
|
695
|
+
resetAt:
|
|
696
|
+
typeof w.resetTime === 'string' && w.resetTime.length > 0
|
|
697
|
+
? (() => {
|
|
698
|
+
const parsed = Date.parse(w.resetTime)
|
|
699
|
+
return Number.isFinite(parsed) ? parsed : undefined
|
|
700
|
+
})()
|
|
701
|
+
: undefined,
|
|
702
|
+
}))
|
|
703
|
+
: undefined
|
|
704
|
+
|
|
705
|
+
return { remainingPercent, resetAt, windows }
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Returns `true` when an account at `index` is the active account for
|
|
710
|
+
* at least one model family. Two accounts CAN both be current — one
|
|
711
|
+
* serving claude, one serving gemini — so this must not collapse to one.
|
|
712
|
+
*/
|
|
713
|
+
export function isAccountCurrent(
|
|
714
|
+
index: number,
|
|
715
|
+
activeIndexByFamily: { claude: number; gemini: number },
|
|
716
|
+
): boolean {
|
|
717
|
+
return (
|
|
718
|
+
index === activeIndexByFamily.claude || index === activeIndexByFamily.gemini
|
|
719
|
+
)
|
|
720
|
+
}
|
|
721
|
+
|
|
451
722
|
/**
|
|
452
723
|
* Convert a live account snapshot into the redacted shape the TUI renders.
|
|
453
724
|
* The redacted `SidebarAccountState` carries no email, refresh token, access
|
|
@@ -467,31 +738,32 @@ export function redactAccountForSidebar(
|
|
|
467
738
|
? source.coolingDownUntil
|
|
468
739
|
: undefined
|
|
469
740
|
const health = clampNumber(
|
|
470
|
-
typeof source.healthScore === 'number' ? source.healthScore :
|
|
741
|
+
typeof source.healthScore === 'number' ? source.healthScore : 100,
|
|
471
742
|
0,
|
|
472
743
|
100,
|
|
473
744
|
)
|
|
474
745
|
|
|
475
746
|
const quota: SidebarAccountState['quota'] = {}
|
|
476
|
-
|
|
747
|
+
// Stamp mismatch: the persisted quota snapshot was captured for a
|
|
748
|
+
// different account than the one currently at this index. Drop the
|
|
749
|
+
// stale cache rather than rendering the wrong account's quota
|
|
750
|
+
// percentages. Mirrors `toCommandAccountRow` in command-data.
|
|
751
|
+
const staleCachedQuota =
|
|
752
|
+
typeof source.cachedQuotaAccountId === 'string' &&
|
|
753
|
+
typeof source.currentQuotaAccountId === 'string' &&
|
|
754
|
+
source.cachedQuotaAccountId !== source.currentQuotaAccountId
|
|
755
|
+
// Normalize legacy per-pool keys to the current two-pool schema before
|
|
756
|
+
// reading. This is non-destructive: the next real quota refresh will
|
|
757
|
+
// overwrite the migrated snapshot with authoritative data.
|
|
758
|
+
const cached = staleCachedQuota
|
|
759
|
+
? undefined
|
|
760
|
+
: normalizeLegacyCachedQuota(source.cachedQuota)
|
|
477
761
|
if (cached) {
|
|
478
|
-
for (const key of [
|
|
479
|
-
'claude',
|
|
480
|
-
'gemini-pro',
|
|
481
|
-
'gemini-flash',
|
|
482
|
-
'gpt-oss',
|
|
483
|
-
] as const) {
|
|
762
|
+
for (const key of ['gemini', 'non-gemini'] as const) {
|
|
484
763
|
const entry = cached[key]
|
|
485
764
|
if (!entry) continue
|
|
486
|
-
const
|
|
487
|
-
if (
|
|
488
|
-
const remainingPercent = clampNumber(Math.round(fraction * 100), 0, 100)
|
|
489
|
-
let resetAt: number | undefined
|
|
490
|
-
if (typeof entry.resetTime === 'string' && entry.resetTime.length > 0) {
|
|
491
|
-
const parsed = Date.parse(entry.resetTime)
|
|
492
|
-
if (Number.isFinite(parsed)) resetAt = parsed
|
|
493
|
-
}
|
|
494
|
-
quota[key] = { remainingPercent, resetAt }
|
|
765
|
+
const projected = projectQuotaPoolForSidebar(entry)
|
|
766
|
+
if (projected) quota[key] = projected
|
|
495
767
|
}
|
|
496
768
|
}
|
|
497
769
|
|
|
@@ -503,6 +775,8 @@ export function redactAccountForSidebar(
|
|
|
503
775
|
current,
|
|
504
776
|
cooldownUntil,
|
|
505
777
|
quota,
|
|
778
|
+
// Tier is plan metadata, not PII — it passes the redaction boundary.
|
|
779
|
+
...(source.tier !== undefined ? { tier: source.tier } : {}),
|
|
506
780
|
}
|
|
507
781
|
}
|
|
508
782
|
|