@oh-my-pi/pi-ai 18.0.5 → 18.0.6
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/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.0.6] - 2026-08-26
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added the `backgroundIdleMs` option to customize how long background auth-broker activity remains active before automatically parking.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed auth-broker background activity keeping processes alive unnecessarily, so unused broker-backed auth storage now parks automatically and no longer prevents CLI exit.
|
|
14
|
+
|
|
5
15
|
## [18.0.5] - 2026-08-25
|
|
6
16
|
|
|
7
17
|
### Breaking Changes
|
|
@@ -35,6 +35,14 @@ export interface RemoteAuthCredentialStoreOptions {
|
|
|
35
35
|
accountPool?: AuthBrokerAccountPool;
|
|
36
36
|
/** Flush cadence for batched observed-usage reports. Default 10s. */
|
|
37
37
|
observedUsageFlushMs?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Idle window after the last foreground store use before background
|
|
40
|
+
* snapshot sync (SSE stream / long-poll) disconnects and parks. A parked
|
|
41
|
+
* store holds no timers or sockets, so an unclosed store never keeps the
|
|
42
|
+
* process alive longer than one idle window. Sync resumes transparently on
|
|
43
|
+
* the next use. Default 20s.
|
|
44
|
+
*/
|
|
45
|
+
backgroundIdleMs?: number;
|
|
38
46
|
}
|
|
39
47
|
export declare class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
40
48
|
#private;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-ai",
|
|
4
|
-
"version": "18.0.
|
|
4
|
+
"version": "18.0.6",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Stencil Labs, Inc.",
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
"fmt": "biome format --write ."
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@oh-my-pi/omptype": "18.0.
|
|
41
|
-
"@oh-my-pi/pi-catalog": "18.0.
|
|
42
|
-
"@oh-my-pi/pi-utils": "18.0.
|
|
43
|
-
"@oh-my-pi/pi-wire": "18.0.
|
|
40
|
+
"@oh-my-pi/omptype": "18.0.6",
|
|
41
|
+
"@oh-my-pi/pi-catalog": "18.0.6",
|
|
42
|
+
"@oh-my-pi/pi-utils": "18.0.6",
|
|
43
|
+
"@oh-my-pi/pi-wire": "18.0.6"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/bun": "^1.3.14"
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
* runs isn't required.
|
|
9
9
|
*/
|
|
10
10
|
import * as os from "node:os";
|
|
11
|
-
import { scheduler } from "node:timers/promises";
|
|
12
11
|
import { getInstallId, logger } from "@oh-my-pi/pi-utils";
|
|
13
12
|
import {
|
|
14
13
|
type AuthCredential,
|
|
@@ -64,6 +63,8 @@ const MAX_WAIT_MS = 5_000;
|
|
|
64
63
|
const BACKGROUND_WAIT_MS = 30_000;
|
|
65
64
|
const BACKGROUND_BACKOFF_INITIAL_MS = 500;
|
|
66
65
|
const BACKGROUND_BACKOFF_MAX_MS = 30_000;
|
|
66
|
+
/** Idle window after the last foreground store use before background sync parks. */
|
|
67
|
+
const BACKGROUND_IDLE_MS = 20_000;
|
|
67
68
|
|
|
68
69
|
function compareCredentialBlockSnapshots(a: CredentialBlockSnapshot, b: CredentialBlockSnapshot): number {
|
|
69
70
|
const provider = a.providerKey.localeCompare(b.providerKey);
|
|
@@ -236,6 +237,14 @@ export interface RemoteAuthCredentialStoreOptions {
|
|
|
236
237
|
accountPool?: AuthBrokerAccountPool;
|
|
237
238
|
/** Flush cadence for batched observed-usage reports. Default 10s. */
|
|
238
239
|
observedUsageFlushMs?: number;
|
|
240
|
+
/**
|
|
241
|
+
* Idle window after the last foreground store use before background
|
|
242
|
+
* snapshot sync (SSE stream / long-poll) disconnects and parks. A parked
|
|
243
|
+
* store holds no timers or sockets, so an unclosed store never keeps the
|
|
244
|
+
* process alive longer than one idle window. Sync resumes transparently on
|
|
245
|
+
* the next use. Default 20s.
|
|
246
|
+
*/
|
|
247
|
+
backgroundIdleMs?: number;
|
|
239
248
|
}
|
|
240
249
|
|
|
241
250
|
export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
@@ -248,6 +257,11 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
248
257
|
#generation = 0;
|
|
249
258
|
#usageOverlays: Map<string, UsageReport> = new Map();
|
|
250
259
|
#backgroundAbort = new AbortController();
|
|
260
|
+
readonly #backgroundIdleMs: number;
|
|
261
|
+
/** Last foreground store use; background sync parks `#backgroundIdleMs` after this. */
|
|
262
|
+
#lastActivityMs = Date.now();
|
|
263
|
+
/** Present while the background loop is parked; resolved by `#noteActivity` or `close()`. */
|
|
264
|
+
#activityWakeup: PromiseWithResolvers<void> | null = null;
|
|
251
265
|
#cache: Map<string, CacheEntry> = new Map();
|
|
252
266
|
#usageCache?: UsageCacheEntry;
|
|
253
267
|
#usageInflight?: Promise<UsageReport[] | null>;
|
|
@@ -281,6 +295,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
281
295
|
this.#client = opts.client;
|
|
282
296
|
this.#streamSnapshots = opts.streamSnapshots ?? true;
|
|
283
297
|
this.#observedUsageFlushMs = opts.observedUsageFlushMs ?? 10_000;
|
|
298
|
+
this.#backgroundIdleMs = opts.backgroundIdleMs ?? BACKGROUND_IDLE_MS;
|
|
284
299
|
this.#accountPool = opts.accountPool
|
|
285
300
|
? new Map([...opts.accountPool].map(([provider, identities]) => [provider, new Set(identities)]))
|
|
286
301
|
: undefined;
|
|
@@ -294,6 +309,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
294
309
|
}
|
|
295
310
|
|
|
296
311
|
get snapshot(): SnapshotResponse {
|
|
312
|
+
this.#noteActivity();
|
|
297
313
|
return this.#snapshot;
|
|
298
314
|
}
|
|
299
315
|
|
|
@@ -346,49 +362,132 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
346
362
|
}
|
|
347
363
|
}
|
|
348
364
|
|
|
365
|
+
/**
|
|
366
|
+
* Background snapshot sync. Invariant: this loop never keeps the process
|
|
367
|
+
* alive on its own. While the store is in active foreground use it holds a
|
|
368
|
+
* live broker request (SSE stream or long-poll); once the store has been
|
|
369
|
+
* idle for `#backgroundIdleMs` an unref'd watchdog aborts that request and
|
|
370
|
+
* the loop parks on a bare promise — zero timers or sockets — until the
|
|
371
|
+
* next foreground call. Backoff sleeps use unref'd timers for the same
|
|
372
|
+
* reason. A leaked (never-closed) store therefore stops pinning the event
|
|
373
|
+
* loop at most one idle window after its last use.
|
|
374
|
+
*/
|
|
349
375
|
async #runBackground(): Promise<void> {
|
|
350
376
|
let backoffMs = BACKGROUND_BACKOFF_INITIAL_MS;
|
|
351
377
|
while (!this.#closed && !this.#backgroundAbort.signal.aborted) {
|
|
352
|
-
if (this.#
|
|
378
|
+
if (this.#idleRemainingMs() <= 0) {
|
|
379
|
+
this.#activityWakeup ??= Promise.withResolvers<void>();
|
|
380
|
+
await this.#activityWakeup.promise;
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
const watchdog = this.#startIdleWatchdog();
|
|
384
|
+
try {
|
|
385
|
+
if (this.#streamSnapshots && !this.#streamingUnsupported) {
|
|
386
|
+
try {
|
|
387
|
+
await this.#consumeSnapshotStream(watchdog.signal);
|
|
388
|
+
backoffMs = BACKGROUND_BACKOFF_INITIAL_MS;
|
|
389
|
+
} catch (error) {
|
|
390
|
+
if (this.#closed || this.#backgroundAbort.signal.aborted) break;
|
|
391
|
+
if (watchdog.idled()) continue;
|
|
392
|
+
if (error instanceof AuthBrokerStreamUnsupportedError) {
|
|
393
|
+
this.#streamingUnsupported = true;
|
|
394
|
+
logger.debug("auth-broker snapshot stream unsupported; falling back to long-poll");
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
logger.debug("auth-broker snapshot stream failed; backing off", { error: String(error) });
|
|
398
|
+
await this.#backoffWait(backoffMs);
|
|
399
|
+
backoffMs = Math.min(BACKGROUND_BACKOFF_MAX_MS, backoffMs * 2);
|
|
400
|
+
}
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
353
403
|
try {
|
|
354
|
-
await this.#
|
|
404
|
+
const result = await this.#client.fetchSnapshot({
|
|
405
|
+
ifGenerationGt: this.#generation,
|
|
406
|
+
waitMs: BACKGROUND_WAIT_MS,
|
|
407
|
+
signal: watchdog.signal,
|
|
408
|
+
});
|
|
409
|
+
if (result.status === 200) this.#applySnapshot(result.snapshot, result.generation);
|
|
355
410
|
backoffMs = BACKGROUND_BACKOFF_INITIAL_MS;
|
|
356
|
-
continue;
|
|
357
411
|
} catch (error) {
|
|
358
412
|
if (this.#closed || this.#backgroundAbort.signal.aborted) break;
|
|
359
|
-
if (
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
continue;
|
|
363
|
-
}
|
|
364
|
-
logger.debug("auth-broker snapshot stream failed; backing off", { error: String(error) });
|
|
365
|
-
await scheduler.wait(backoffMs, { signal: this.#backgroundAbort.signal }).catch(() => {});
|
|
413
|
+
if (watchdog.idled()) continue;
|
|
414
|
+
logger.debug("auth-broker background snapshot sync failed", { error: String(error) });
|
|
415
|
+
await this.#backoffWait(backoffMs);
|
|
366
416
|
backoffMs = Math.min(BACKGROUND_BACKOFF_MAX_MS, backoffMs * 2);
|
|
367
|
-
continue;
|
|
368
417
|
}
|
|
418
|
+
} finally {
|
|
419
|
+
watchdog.stop();
|
|
369
420
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** Record a foreground store use; wakes the parked background sync. */
|
|
425
|
+
#noteActivity(): void {
|
|
426
|
+
this.#lastActivityMs = Date.now();
|
|
427
|
+
if (this.#activityWakeup) {
|
|
428
|
+
this.#activityWakeup.resolve();
|
|
429
|
+
this.#activityWakeup = null;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
#idleRemainingMs(): number {
|
|
434
|
+
return this.#lastActivityMs + this.#backgroundIdleMs - Date.now();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Abort signal for one background iteration that trips once the store has
|
|
439
|
+
* been idle for `#backgroundIdleMs`. The timer is unref'd: it can only fire
|
|
440
|
+
* while something else keeps the event loop alive — typically our own
|
|
441
|
+
* in-flight broker request, which is exactly what it exists to end.
|
|
442
|
+
*/
|
|
443
|
+
#startIdleWatchdog(): { signal: AbortSignal; idled: () => boolean; stop: () => void } {
|
|
444
|
+
const controller = new AbortController();
|
|
445
|
+
let idled = false;
|
|
446
|
+
let timer: Timer | undefined;
|
|
447
|
+
const arm = (): void => {
|
|
448
|
+
const remainingMs = this.#idleRemainingMs();
|
|
449
|
+
if (remainingMs > 0) {
|
|
450
|
+
timer = setTimeout(arm, remainingMs);
|
|
451
|
+
timer.unref?.();
|
|
452
|
+
return;
|
|
383
453
|
}
|
|
454
|
+
idled = true;
|
|
455
|
+
controller.abort(new AIError.AbortError("auth-broker background sync idle"));
|
|
456
|
+
};
|
|
457
|
+
arm();
|
|
458
|
+
return {
|
|
459
|
+
signal: AbortSignal.any([this.#backgroundAbort.signal, controller.signal]),
|
|
460
|
+
idled: () => idled,
|
|
461
|
+
stop: () => clearTimeout(timer),
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Backoff sleep on an unref'd timer so retry waits never pin the process;
|
|
467
|
+
* in an otherwise-exiting process the timer simply never fires and the
|
|
468
|
+
* suspended loop holds no handles. Wakes early on `close()`.
|
|
469
|
+
*/
|
|
470
|
+
async #backoffWait(ms: number): Promise<void> {
|
|
471
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
472
|
+
const timer = setTimeout(resolve, ms);
|
|
473
|
+
timer.unref?.();
|
|
474
|
+
const onAbort = (): void => {
|
|
475
|
+
clearTimeout(timer);
|
|
476
|
+
resolve();
|
|
477
|
+
};
|
|
478
|
+
this.#backgroundAbort.signal.addEventListener("abort", onAbort, { once: true });
|
|
479
|
+
try {
|
|
480
|
+
await promise;
|
|
481
|
+
} finally {
|
|
482
|
+
this.#backgroundAbort.signal.removeEventListener("abort", onAbort);
|
|
384
483
|
}
|
|
385
484
|
}
|
|
386
485
|
|
|
387
|
-
async #consumeSnapshotStream(): Promise<void> {
|
|
388
|
-
const iterator = this.#client.openSnapshotStream({ signal
|
|
486
|
+
async #consumeSnapshotStream(signal: AbortSignal): Promise<void> {
|
|
487
|
+
const iterator = this.#client.openSnapshotStream({ signal });
|
|
389
488
|
try {
|
|
390
489
|
for await (const event of iterator) {
|
|
391
|
-
if (this.#closed ||
|
|
490
|
+
if (this.#closed || signal.aborted) break;
|
|
392
491
|
this.#streamingActive = true;
|
|
393
492
|
this.#applyStreamEvent(event);
|
|
394
493
|
}
|
|
@@ -469,12 +568,14 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
469
568
|
|
|
470
569
|
/** Re-hydrate the in-memory snapshot from the broker. */
|
|
471
570
|
async refreshSnapshot(): Promise<SnapshotResponse> {
|
|
571
|
+
this.#noteActivity();
|
|
472
572
|
const result = await this.#client.fetchSnapshot();
|
|
473
573
|
if (result.status === 200) this.#applySnapshot(result.snapshot, result.generation);
|
|
474
574
|
return this.#snapshot;
|
|
475
575
|
}
|
|
476
576
|
|
|
477
577
|
listAuthCredentials(provider?: string): StoredAuthCredential[] {
|
|
578
|
+
this.#noteActivity();
|
|
478
579
|
const out: StoredAuthCredential[] = [];
|
|
479
580
|
for (const entry of this.#snapshot.credentials) {
|
|
480
581
|
if (provider !== undefined && entry.provider !== provider) continue;
|
|
@@ -490,10 +591,12 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
490
591
|
|
|
491
592
|
/** Broker-backed disabled tombstones; empty against brokers predating the endpoint. */
|
|
492
593
|
listDisabledCredentials(provider?: string, signal?: AbortSignal): Promise<DisabledCredentialSummary[]> {
|
|
594
|
+
this.#noteActivity();
|
|
493
595
|
return this.#client.listDisabledCredentials(provider, signal);
|
|
494
596
|
}
|
|
495
597
|
|
|
496
598
|
getCredentialBlock(credentialId: number, providerKey: string, blockScope: string): number | undefined {
|
|
599
|
+
this.#noteActivity();
|
|
497
600
|
const nowMs = Date.now();
|
|
498
601
|
this.cleanExpiredCredentialBlocks(nowMs);
|
|
499
602
|
const entry = this.#snapshot.credentials.find(candidate => candidate.id === credentialId);
|
|
@@ -511,6 +614,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
511
614
|
}
|
|
512
615
|
|
|
513
616
|
listCredentialBlocks(credentialIds: readonly number[]): StoredCredentialBlock[] {
|
|
617
|
+
this.#noteActivity();
|
|
514
618
|
const nowMs = Date.now();
|
|
515
619
|
this.cleanExpiredCredentialBlocks(nowMs);
|
|
516
620
|
const ids = new Set(credentialIds);
|
|
@@ -533,6 +637,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
533
637
|
}
|
|
534
638
|
|
|
535
639
|
upsertCredentialBlock(block: StoredCredentialBlock): void {
|
|
640
|
+
this.#noteActivity();
|
|
536
641
|
this.#upsertSnapshotBlock(block);
|
|
537
642
|
this.#invalidateUsageCache();
|
|
538
643
|
this.#credentialBlockReconcileAfter.set(
|
|
@@ -562,6 +667,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
562
667
|
}
|
|
563
668
|
|
|
564
669
|
deleteCredentialBlocks(credentialId: number): void {
|
|
670
|
+
this.#noteActivity();
|
|
565
671
|
this.#deleteSnapshotBlocks(credentialId);
|
|
566
672
|
for (const key of this.#credentialBlockReconcileAfter.keys()) {
|
|
567
673
|
if (key.startsWith(`${credentialId}\0`)) this.#credentialBlockReconcileAfter.delete(key);
|
|
@@ -593,6 +699,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
593
699
|
* authoritative row, so we just mirror it.
|
|
594
700
|
*/
|
|
595
701
|
updateAuthCredential(id: number, credential: AuthCredential): void {
|
|
702
|
+
this.#noteActivity();
|
|
596
703
|
for (const entry of this.#snapshot.credentials) {
|
|
597
704
|
if (entry.id !== id) continue;
|
|
598
705
|
entry.credential = credential as typeof entry.credential;
|
|
@@ -601,6 +708,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
601
708
|
}
|
|
602
709
|
|
|
603
710
|
deleteAuthCredential(id: number, disabledCause: string): void {
|
|
711
|
+
this.#noteActivity();
|
|
604
712
|
this.#removeCredentialById(id);
|
|
605
713
|
// Fire-and-forget: tell the broker to persist the disable.
|
|
606
714
|
this.#client.disableCredential(id, disabledCause).catch(error => {
|
|
@@ -609,6 +717,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
609
717
|
}
|
|
610
718
|
|
|
611
719
|
async deleteAuthCredentialRemote(id: number, disabledCause: string): Promise<boolean> {
|
|
720
|
+
this.#noteActivity();
|
|
612
721
|
const found = this.#snapshot.credentials.some(entry => entry.id === id);
|
|
613
722
|
if (!found) return false;
|
|
614
723
|
await this.#client.disableCredential(id, disabledCause);
|
|
@@ -618,6 +727,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
618
727
|
}
|
|
619
728
|
|
|
620
729
|
tryDisableAuthCredentialIfMatches(id: number, _expectedData: string, disabledCause: string): boolean {
|
|
730
|
+
this.#noteActivity();
|
|
621
731
|
const found = this.#snapshot.credentials.find(entry => entry.id === id);
|
|
622
732
|
if (!found) return false;
|
|
623
733
|
this.deleteAuthCredential(id, disabledCause);
|
|
@@ -625,6 +735,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
625
735
|
}
|
|
626
736
|
|
|
627
737
|
async waitForFreshSnapshot(maxWaitMs: number, opts: { signal?: AbortSignal } = {}): Promise<boolean> {
|
|
738
|
+
this.#noteActivity();
|
|
628
739
|
const previousGeneration = this.#generation;
|
|
629
740
|
const result = await this.#client.fetchSnapshot({
|
|
630
741
|
ifGenerationGt: this.#generation,
|
|
@@ -636,6 +747,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
636
747
|
}
|
|
637
748
|
|
|
638
749
|
async prepareForRequest(credentialId: number, opts: { signal?: AbortSignal } = {}): Promise<boolean> {
|
|
750
|
+
this.#noteActivity();
|
|
639
751
|
const entry = this.#snapshot.credentials.find(candidate => candidate.id === credentialId);
|
|
640
752
|
if (entry?.credential.type !== "oauth" || entry.rotatesInMs === null) return false;
|
|
641
753
|
const remainingMs = this.#snapshotReceivedAt + entry.rotatesInMs - Date.now();
|
|
@@ -644,6 +756,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
644
756
|
}
|
|
645
757
|
|
|
646
758
|
async markCredentialSuspect(credentialId: number, opts: { signal?: AbortSignal } = {}): Promise<void> {
|
|
759
|
+
this.#noteActivity();
|
|
647
760
|
const { entry } = await this.#client.refreshCredential(credentialId, opts.signal);
|
|
648
761
|
if (entry.credential.type !== "oauth") {
|
|
649
762
|
throw new AIError.AuthBrokerError(`Broker returned non-OAuth credential for id=${credentialId}`);
|
|
@@ -682,6 +795,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
682
795
|
* any concurrent peer (refresh, generation bump) stays in sync.
|
|
683
796
|
*/
|
|
684
797
|
async upsertAuthCredentialRemote(provider: string, credential: AuthCredential): Promise<StoredAuthCredential[]> {
|
|
798
|
+
this.#noteActivity();
|
|
685
799
|
const { entries } = await this.#client.uploadCredential(provider, credential);
|
|
686
800
|
this.#applyProviderEntries(provider, entries);
|
|
687
801
|
this.#maybeRefreshSnapshot("upload");
|
|
@@ -866,6 +980,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
866
980
|
}
|
|
867
981
|
|
|
868
982
|
getCache(key: string): string | null {
|
|
983
|
+
this.#noteActivity();
|
|
869
984
|
const entry = this.#cache.get(key);
|
|
870
985
|
if (!entry) return null;
|
|
871
986
|
if (entry.expiresAtSec * 1000 <= Date.now()) {
|
|
@@ -876,6 +991,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
876
991
|
}
|
|
877
992
|
|
|
878
993
|
setCache(key: string, value: string, expiresAtSec: number): void {
|
|
994
|
+
this.#noteActivity();
|
|
879
995
|
this.#cache.set(key, { value, expiresAtSec });
|
|
880
996
|
}
|
|
881
997
|
|
|
@@ -894,6 +1010,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
894
1010
|
}
|
|
895
1011
|
|
|
896
1012
|
async invalidateUsageCache(signal?: AbortSignal): Promise<void> {
|
|
1013
|
+
this.#noteActivity();
|
|
897
1014
|
this.#invalidateUsageCache();
|
|
898
1015
|
await this.#client.notifyUsageStale(signal).catch(err => {
|
|
899
1016
|
logger.warn("auth-broker notification of stale usage failed", { error: String(err) });
|
|
@@ -918,6 +1035,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
918
1035
|
_credential: OAuthCredential,
|
|
919
1036
|
signal?: AbortSignal,
|
|
920
1037
|
): Promise<OAuthCredentials> {
|
|
1038
|
+
this.#noteActivity();
|
|
921
1039
|
const { entry } = await this.#client.refreshCredential(credentialId, signal);
|
|
922
1040
|
if (entry.credential.type !== "oauth") {
|
|
923
1041
|
throw new AIError.AuthBrokerError(`Broker returned non-OAuth credential for id=${credentialId}`);
|
|
@@ -951,6 +1069,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
951
1069
|
* residential laptop is, so all credentials surface every cycle.
|
|
952
1070
|
*/
|
|
953
1071
|
async fetchUsageReports(signal?: AbortSignal): Promise<UsageReport[] | null> {
|
|
1072
|
+
this.#noteActivity();
|
|
954
1073
|
const reports = await this.#raceWithSignal(this.#loadUsageReports(), signal);
|
|
955
1074
|
if (!reports) return null;
|
|
956
1075
|
return this.#filterUsageReports(this.#applyUsageOverlays(reports));
|
|
@@ -970,6 +1089,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
970
1089
|
credential: OAuthCredential,
|
|
971
1090
|
signal?: AbortSignal,
|
|
972
1091
|
): Promise<UsageReport | null> {
|
|
1092
|
+
this.#noteActivity();
|
|
973
1093
|
const reports = await this.#raceWithSignal(this.#loadUsageReports(), signal);
|
|
974
1094
|
const visibleReports = reports ? this.#filterUsageReports(reports) : null;
|
|
975
1095
|
const matched = visibleReports ? matchUsageReport(visibleReports, provider, credential) : null;
|
|
@@ -1014,6 +1134,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
1014
1134
|
}
|
|
1015
1135
|
|
|
1016
1136
|
ingestUsageReport(provider: Provider, credential: OAuthCredential, report: UsageReport): boolean {
|
|
1137
|
+
this.#noteActivity();
|
|
1017
1138
|
const key = usageOverlayKey(provider, credential);
|
|
1018
1139
|
if (!key) return false;
|
|
1019
1140
|
const activeOverlay = this.#getActiveUsageOverlay(provider, credential);
|
|
@@ -1202,6 +1323,8 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
1202
1323
|
if (this.#closed) return;
|
|
1203
1324
|
this.#closed = true;
|
|
1204
1325
|
this.#backgroundAbort.abort();
|
|
1326
|
+
this.#activityWakeup?.resolve();
|
|
1327
|
+
this.#activityWakeup = null;
|
|
1205
1328
|
if (this.#observedUsageTimer !== undefined) {
|
|
1206
1329
|
clearTimeout(this.#observedUsageTimer);
|
|
1207
1330
|
this.#observedUsageTimer = undefined;
|