@vybestack/llxprt-code 0.9.1 → 0.9.3

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.
Files changed (67) hide show
  1. package/dist/package.json +3 -3
  2. package/dist/src/auth/BucketFailoverHandlerImpl.d.ts +6 -2
  3. package/dist/src/auth/BucketFailoverHandlerImpl.js +128 -22
  4. package/dist/src/auth/BucketFailoverHandlerImpl.js.map +1 -1
  5. package/dist/src/auth/BucketFailoverHandlerImpl.spec.js +201 -1
  6. package/dist/src/auth/BucketFailoverHandlerImpl.spec.js.map +1 -1
  7. package/dist/src/auth/__tests__/oauth-manager.getToken-bucket-peek.spec.js +2 -1
  8. package/dist/src/auth/__tests__/oauth-manager.getToken-bucket-peek.spec.js.map +1 -1
  9. package/dist/src/auth/oauth-manager.auth-lock.spec.js +6 -1
  10. package/dist/src/auth/oauth-manager.auth-lock.spec.js.map +1 -1
  11. package/dist/src/auth/oauth-manager.d.ts +10 -9
  12. package/dist/src/auth/oauth-manager.failover-wiring.spec.js +74 -1
  13. package/dist/src/auth/oauth-manager.failover-wiring.spec.js.map +1 -1
  14. package/dist/src/auth/oauth-manager.issue1468.spec.js +527 -2
  15. package/dist/src/auth/oauth-manager.issue1468.spec.js.map +1 -1
  16. package/dist/src/auth/oauth-manager.js +184 -67
  17. package/dist/src/auth/oauth-manager.js.map +1 -1
  18. package/dist/src/auth/oauth-manager.logout.spec.js +137 -0
  19. package/dist/src/auth/oauth-manager.logout.spec.js.map +1 -1
  20. package/dist/src/auth/oauth-manager.spec.js +58 -0
  21. package/dist/src/auth/oauth-manager.spec.js.map +1 -1
  22. package/dist/src/config/config.js +4 -0
  23. package/dist/src/config/config.js.map +1 -1
  24. package/dist/src/gemini.js.map +1 -1
  25. package/dist/src/generated/git-commit.d.ts +1 -1
  26. package/dist/src/generated/git-commit.js +1 -1
  27. package/dist/src/providers/aliases/chutes-ai.config +1 -1
  28. package/dist/src/providers/aliases/codex.config +1 -0
  29. package/dist/src/providers/aliases/deepseek.config +9 -0
  30. package/dist/src/providers/aliases/fireworks.config +1 -1
  31. package/dist/src/providers/aliases/gemini.config +1 -1
  32. package/dist/src/providers/aliases/openai.config +1 -1
  33. package/dist/src/providers/aliases/openrouter.config +1 -1
  34. package/dist/src/providers/aliases/synthetic.config +1 -1
  35. package/dist/src/providers/aliases/xai.config +1 -1
  36. package/dist/src/providers/aliases/zai.config +9 -0
  37. package/dist/src/providers/providerAliases.codex.test.js +2 -2
  38. package/dist/src/providers/providerAliases.codex.test.js.map +1 -1
  39. package/dist/src/providers/providerAliases.defaultModels.test.d.ts +6 -0
  40. package/dist/src/providers/providerAliases.defaultModels.test.js +57 -0
  41. package/dist/src/providers/providerAliases.defaultModels.test.js.map +1 -0
  42. package/dist/src/providers/providerManagerInstance.oauthRegistration.test.js +1 -1
  43. package/dist/src/providers/providerManagerInstance.oauthRegistration.test.js.map +1 -1
  44. package/dist/src/ui/AppContainer.js +1 -1
  45. package/dist/src/ui/AppContainer.js.map +1 -1
  46. package/dist/src/ui/commands/diagnosticsCommand.js +16 -7
  47. package/dist/src/ui/commands/diagnosticsCommand.js.map +1 -1
  48. package/dist/src/ui/commands/diagnosticsCommand.spec.js +68 -0
  49. package/dist/src/ui/commands/diagnosticsCommand.spec.js.map +1 -1
  50. package/dist/src/ui/commands/statsCommand.js +63 -4
  51. package/dist/src/ui/commands/statsCommand.js.map +1 -1
  52. package/dist/src/ui/components/AuthDialog.js +1 -1
  53. package/dist/src/ui/components/AuthDialog.js.map +1 -1
  54. package/dist/src/ui/utils/terminalCapabilityManager.d.ts +1 -0
  55. package/dist/src/ui/utils/terminalCapabilityManager.js +25 -1
  56. package/dist/src/ui/utils/terminalCapabilityManager.js.map +1 -1
  57. package/dist/src/ui/utils/terminalCapabilityManager.test.js +34 -1
  58. package/dist/src/ui/utils/terminalCapabilityManager.test.js.map +1 -1
  59. package/dist/src/ui/utils/terminalProtocolCleanup.js +1 -1
  60. package/dist/src/ui/utils/terminalProtocolCleanup.js.map +1 -1
  61. package/dist/src/ui/utils/terminalProtocolCleanup.test.js +2 -2
  62. package/dist/src/ui/utils/terminalProtocolCleanup.test.js.map +1 -1
  63. package/dist/src/utils/sandbox.d.ts +13 -0
  64. package/dist/src/utils/sandbox.js +163 -7
  65. package/dist/src/utils/sandbox.js.map +1 -1
  66. package/dist/tsconfig.build.tsbuildinfo +1 -1
  67. package/package.json +3 -3
@@ -29,6 +29,12 @@ function isLoggingWrapperCandidate(provider) {
29
29
  typeof provider === 'object' &&
30
30
  Object.prototype.hasOwnProperty.call(provider, 'wrappedProvider'));
31
31
  }
32
+ function hasRequestMetadata(handler) {
33
+ return (!!handler &&
34
+ typeof handler === 'object' &&
35
+ typeof handler.getRequestMetadata ===
36
+ 'function');
37
+ }
32
38
  /**
33
39
  * @plan PLAN-20251020-STATELESSPROVIDER3.P12
34
40
  * @requirement REQ-SP3-003
@@ -455,8 +461,11 @@ export class OAuthManager {
455
461
  if (!provider) {
456
462
  throw new Error(`Unknown provider: ${providerName}`);
457
463
  }
458
- // Resolve which bucket to act on (explicit bucket > session bucket > default)
459
- const bucketToUse = bucket ?? this.sessionBuckets.get(providerName) ?? 'default';
464
+ const sessionMetadata = await this.getCurrentProfileSessionMetadata(providerName);
465
+ // Resolve which bucket to act on (explicit bucket > current-profile session bucket > default)
466
+ const bucketToUse = bucket ??
467
+ (await this.getCurrentProfileSessionBucket(providerName, sessionMetadata)) ??
468
+ 'default';
460
469
  const tokenForLogout = await this.tokenStore.getToken(providerName, bucketToUse);
461
470
  // Call provider logout if exists (best-effort remote revoke), but ALWAYS clear local token
462
471
  if ('logout' in provider && typeof provider.logout === 'function') {
@@ -471,8 +480,13 @@ export class OAuthManager {
471
480
  }
472
481
  await this.tokenStore.removeToken(providerName, bucketToUse);
473
482
  // If we just logged out the active session bucket, clear the in-memory override.
474
- if (this.sessionBuckets.get(providerName) === bucketToUse) {
475
- this.clearSessionBucket(providerName);
483
+ if ((await this.getCurrentProfileSessionBucket(providerName, sessionMetadata)) === bucketToUse) {
484
+ if (this.getSessionBucket(providerName, sessionMetadata) === bucketToUse) {
485
+ this.clearSessionBucket(providerName, sessionMetadata);
486
+ }
487
+ if (this.getSessionBucket(providerName) === bucketToUse) {
488
+ this.clearSessionBucket(providerName);
489
+ }
476
490
  }
477
491
  // CRITICAL FIX: Clear all provider auth caches after logout
478
492
  // This ensures BaseProvider and specific provider caches are invalidated
@@ -522,8 +536,8 @@ export class OAuthManager {
522
536
  // Lines 42-49: FOR EACH provider IN providers DO
523
537
  for (const provider of providers) {
524
538
  try {
525
- // Line 44: AWAIT this.logout(provider)
526
- await this.logout(provider);
539
+ // Line 44: AWAIT this.logoutAllBuckets(provider)
540
+ await this.logoutAllBuckets(provider);
527
541
  }
528
542
  catch (error) {
529
543
  // Lines 45-47: LOG "Failed to logout from " + provider + ": " + error
@@ -565,6 +579,10 @@ export class OAuthManager {
565
579
  // This fixes subagents created without LoadedSettings: they can reuse existing tokens
566
580
  // instead of forcing unnecessary reauth loops.
567
581
  logger.debug(() => `[FLOW] Attempting to get existing token for ${providerName}...`);
582
+ const explicitBucket = typeof bucket === 'string';
583
+ const requestMetadata = !explicitBucket && bucket && typeof bucket === 'object'
584
+ ? bucket
585
+ : undefined;
568
586
  const token = await this.getOAuthToken(providerName, bucket);
569
587
  // Special handling for different providers
570
588
  // @plan:PLAN-20250823-AUTHFIXES.P15
@@ -585,11 +603,11 @@ export class OAuthManager {
585
603
  // handle API-error-driven failover. This peek loop only reads the token store
586
604
  // directly without mutating any failover state.
587
605
  {
588
- const profileBuckets = await this.getProfileBuckets(providerName);
606
+ const profileBuckets = await this.getProfileBuckets(providerName, requestMetadata);
589
607
  if (profileBuckets.length > 1) {
590
608
  const nowInSeconds = Math.floor(Date.now() / 1000);
591
609
  const thirtySecondsFromNow = nowInSeconds + 30;
592
- const alreadyTriedBucket = this.getSessionBucket(providerName);
610
+ const alreadyTriedBucket = this.getSessionBucket(providerName, requestMetadata);
593
611
  for (const peekBucket of profileBuckets) {
594
612
  if (peekBucket === alreadyTriedBucket)
595
613
  continue;
@@ -597,7 +615,7 @@ export class OAuthManager {
597
615
  const peekToken = await this.tokenStore.getToken(providerName, peekBucket);
598
616
  if (peekToken && peekToken.expiry > thirtySecondsFromNow) {
599
617
  logger.debug(() => `[issue1616] Found valid token in bucket '${peekBucket}' for ${providerName}, switching session`);
600
- this.setSessionBucket(providerName, peekBucket);
618
+ this.setSessionBucket(providerName, peekBucket, requestMetadata);
601
619
  return peekToken.access_token;
602
620
  }
603
621
  }
@@ -620,10 +638,19 @@ export class OAuthManager {
620
638
  return null;
621
639
  }
622
640
  logger.debug(() => `[FLOW] No existing token for ${providerName}, triggering OAuth flow...`);
641
+ const resolvedProfileBuckets = await this.getProfileBuckets(providerName, requestMetadata);
642
+ const scopedSessionBucket = explicitBucket
643
+ ? undefined
644
+ : this.getSessionBucket(providerName, requestMetadata);
645
+ const bucketToCheck = explicitBucket
646
+ ? bucket
647
+ : (scopedSessionBucket ??
648
+ (resolvedProfileBuckets.length === 1
649
+ ? resolvedProfileBuckets[0]
650
+ : undefined));
623
651
  // @fix issue1262 & issue1195: Before triggering OAuth, check disk with lock
624
652
  // Another process or earlier run may have written a valid token that we missed
625
653
  // Use the same locking pattern as PR #1258 to prevent race conditions
626
- const bucketToCheck = typeof bucket === 'string' ? bucket : undefined;
627
654
  const lockAcquired = await this.tokenStore.acquireRefreshLock(providerName, {
628
655
  waitMs: 5000, // Wait up to 5 seconds for lock
629
656
  staleMs: 30000,
@@ -680,7 +707,7 @@ export class OAuthManager {
680
707
  // Authentication is handled at the turn boundary via ensureBucketsAuthenticated().
681
708
  // For single-bucket or non-bucketed profiles, preserve existing auth behavior.
682
709
  try {
683
- const buckets = await this.getProfileBuckets(providerName);
710
+ const buckets = resolvedProfileBuckets;
684
711
  if (buckets.length > 1) {
685
712
  // Multi-bucket: pure lookup only — return null.
686
713
  // ensureBucketsAuthenticated() at turn boundary handles auth,
@@ -700,14 +727,22 @@ export class OAuthManager {
700
727
  logger.debug('Could not get ephemeral setting (runtime not initialized), using default', runtimeError);
701
728
  }
702
729
  if (showPrompt) {
703
- const effectiveBuckets = buckets.length === 1 ? buckets : ['default'];
730
+ const effectiveBuckets = bucketToCheck ? [bucketToCheck] : ['default'];
704
731
  logger.debug(`Single-bucket auth with prompt mode for ${providerName}, bucket: ${effectiveBuckets[0]}`);
705
- await this.authenticateMultipleBuckets(providerName, effectiveBuckets);
732
+ await this.authenticateMultipleBuckets(providerName, effectiveBuckets, requestMetadata);
733
+ const authenticatedBucket = effectiveBuckets[0];
734
+ if (authenticatedBucket) {
735
+ this.setSessionBucket(providerName, authenticatedBucket, requestMetadata);
736
+ }
706
737
  }
707
738
  else {
708
- await this.authenticate(providerName);
739
+ const authenticatedBucket = bucketToCheck ?? 'default';
740
+ await this.authenticate(providerName, authenticatedBucket);
741
+ if (authenticatedBucket) {
742
+ this.setSessionBucket(providerName, authenticatedBucket, requestMetadata);
743
+ }
709
744
  }
710
- const newToken = await this.getOAuthToken(providerName);
745
+ const newToken = await this.getOAuthToken(providerName, explicitBucket ? bucketToCheck : requestMetadata);
711
746
  return newToken ? newToken.access_token : null;
712
747
  }
713
748
  catch (error) {
@@ -756,6 +791,9 @@ export class OAuthManager {
756
791
  throw new Error(`Unknown provider: ${providerName}`);
757
792
  }
758
793
  const explicitBucket = typeof bucket === 'string';
794
+ const requestMetadata = !explicitBucket && bucket && typeof bucket === 'object'
795
+ ? bucket
796
+ : undefined;
759
797
  // Determine the bucket to use: explicit bucket parameter or session bucket override
760
798
  let bucketToUse;
761
799
  if (explicitBucket) {
@@ -767,10 +805,11 @@ export class OAuthManager {
767
805
  let failoverHandler;
768
806
  if (!explicitBucket) {
769
807
  await this.withBucketResolutionLock(providerName, async () => {
770
- if (this.sessionBuckets.has(providerName)) {
771
- bucketToUse = this.sessionBuckets.get(providerName);
808
+ const sessionBucket = this.getSessionBucket(providerName, requestMetadata);
809
+ if (sessionBucket) {
810
+ bucketToUse = sessionBucket;
772
811
  }
773
- profileBuckets = await this.getProfileBuckets(providerName);
812
+ profileBuckets = await this.getProfileBuckets(providerName, requestMetadata);
774
813
  const config = this.getConfig?.();
775
814
  // @fix issue1029 - Enhanced debug logging for failover handler setup
776
815
  logger.debug(() => `[issue1029] getOAuthToken: provider=${providerName}, buckets=${JSON.stringify(profileBuckets)}, hasConfig=${!!config}`);
@@ -779,9 +818,15 @@ export class OAuthManager {
779
818
  const existingBuckets = failoverHandler?.getBuckets?.() ?? [];
780
819
  const sameBuckets = existingBuckets.length === profileBuckets.length &&
781
820
  existingBuckets.every((value, index) => value === profileBuckets[index]);
782
- logger.debug(() => `[issue1029] Failover handler check: hasExisting=${!!failoverHandler}, sameBuckets=${sameBuckets}, existingBuckets=${JSON.stringify(existingBuckets)}`);
783
- if (!failoverHandler || !sameBuckets) {
784
- const handler = new BucketFailoverHandlerImpl(profileBuckets, providerName, this);
821
+ const requestedScopeKey = this.getSessionBucketScopeKey(providerName, requestMetadata);
822
+ const existingRequestMetadata = hasRequestMetadata(failoverHandler)
823
+ ? failoverHandler.getRequestMetadata()
824
+ : undefined;
825
+ const existingScopeKey = this.getSessionBucketScopeKey(providerName, existingRequestMetadata);
826
+ const sameScope = existingScopeKey === requestedScopeKey;
827
+ logger.debug(() => `[issue1029] Failover handler check: hasExisting=${!!failoverHandler}, sameBuckets=${sameBuckets}, sameScope=${sameScope}, existingBuckets=${JSON.stringify(existingBuckets)}`);
828
+ if (!failoverHandler || !sameBuckets || !sameScope) {
829
+ const handler = new BucketFailoverHandlerImpl(profileBuckets, providerName, this, requestMetadata);
785
830
  config.setBucketFailoverHandler(handler);
786
831
  failoverHandler = handler;
787
832
  logger.debug(() => `[issue1029] Created and set new BucketFailoverHandlerImpl on config for ${providerName} with buckets: ${JSON.stringify(profileBuckets)}`);
@@ -802,8 +847,8 @@ export class OAuthManager {
802
847
  bucketToUse = profileBuckets[0];
803
848
  }
804
849
  // Establish a default session bucket for the duration of this CLI session.
805
- if (bucketToUse && !this.sessionBuckets.has(providerName)) {
806
- this.sessionBuckets.set(providerName, bucketToUse);
850
+ if (!sessionBucket && bucketToUse) {
851
+ this.setSessionBucket(providerName, bucketToUse, requestMetadata);
807
852
  }
808
853
  }
809
854
  });
@@ -1326,27 +1371,45 @@ export class OAuthManager {
1326
1371
  * Set session bucket override for a provider
1327
1372
  * Session state is in-memory only and not persisted
1328
1373
  */
1329
- setSessionBucket(provider, bucket) {
1330
- this.sessionBuckets.set(provider, bucket);
1374
+ setSessionBucket(provider, bucket, metadata) {
1375
+ this.sessionBuckets.set(this.getSessionBucketScopeKey(provider, metadata), bucket);
1331
1376
  }
1332
1377
  /**
1333
1378
  * Get session bucket override for a provider
1334
1379
  * Returns undefined if no session override set
1335
1380
  */
1336
- getSessionBucket(provider) {
1337
- return this.sessionBuckets.get(provider);
1381
+ getSessionBucket(provider, metadata) {
1382
+ return this.sessionBuckets.get(this.getSessionBucketScopeKey(provider, metadata));
1383
+ }
1384
+ async getCurrentProfileSessionBucket(provider, metadata) {
1385
+ const scopedSessionBucket = this.getSessionBucket(provider, metadata);
1386
+ if (scopedSessionBucket) {
1387
+ return scopedSessionBucket;
1388
+ }
1389
+ const profileBuckets = await this.getProfileBuckets(provider, metadata);
1390
+ if (profileBuckets.length === 1) {
1391
+ return profileBuckets[0];
1392
+ }
1393
+ const unscopedSessionBucket = this.getSessionBucket(provider);
1394
+ if (unscopedSessionBucket &&
1395
+ (profileBuckets.length === 0 ||
1396
+ profileBuckets.includes(unscopedSessionBucket))) {
1397
+ return unscopedSessionBucket;
1398
+ }
1399
+ return undefined;
1338
1400
  }
1339
1401
  /**
1340
1402
  * Clear session bucket override for a provider
1341
1403
  */
1342
- clearSessionBucket(provider) {
1343
- this.sessionBuckets.delete(provider);
1404
+ clearSessionBucket(provider, metadata) {
1405
+ this.sessionBuckets.delete(this.getSessionBucketScopeKey(provider, metadata));
1344
1406
  }
1345
- /**
1346
- * List all buckets for a provider
1347
- */
1348
- async listBuckets(provider) {
1349
- return this.tokenStore.listBuckets(provider);
1407
+ clearAllSessionBuckets(provider) {
1408
+ for (const key of Array.from(this.sessionBuckets.keys())) {
1409
+ if (key === provider || key.startsWith(`${provider}::`)) {
1410
+ this.sessionBuckets.delete(key);
1411
+ }
1412
+ }
1350
1413
  }
1351
1414
  /**
1352
1415
  * Logout from all buckets for a provider
@@ -1361,14 +1424,18 @@ export class OAuthManager {
1361
1424
  logger.warn(`Failed to logout from bucket ${bucket}:`, error);
1362
1425
  }
1363
1426
  }
1364
- this.clearSessionBucket(provider);
1427
+ this.clearAllSessionBuckets(provider);
1428
+ }
1429
+ async listBuckets(provider) {
1430
+ return this.tokenStore.listBuckets(provider);
1365
1431
  }
1366
1432
  /**
1367
1433
  * Get authentication status with bucket information
1368
1434
  */
1369
1435
  async getAuthStatusWithBuckets(provider) {
1370
1436
  const buckets = await this.tokenStore.listBuckets(provider);
1371
- const sessionBucket = this.sessionBuckets.get(provider);
1437
+ const sessionMetadata = await this.getCurrentProfileSessionMetadata(provider);
1438
+ const sessionBucket = await this.getCurrentProfileSessionBucket(provider, sessionMetadata);
1372
1439
  const statuses = [];
1373
1440
  for (const bucket of buckets) {
1374
1441
  const token = await this.tokenStore.getToken(provider, bucket);
@@ -1407,8 +1474,11 @@ export class OAuthManager {
1407
1474
  if (!provider) {
1408
1475
  return null;
1409
1476
  }
1477
+ const sessionMetadata = await this.getCurrentProfileSessionMetadata('anthropic');
1410
1478
  // Get the token for the specified bucket
1411
- const bucketToUse = bucket ?? this.sessionBuckets.get('anthropic') ?? 'default';
1479
+ const bucketToUse = bucket ??
1480
+ (await this.getCurrentProfileSessionBucket('anthropic', sessionMetadata)) ??
1481
+ 'default';
1412
1482
  const token = await this.tokenStore.getToken('anthropic', bucketToUse);
1413
1483
  if (!token) {
1414
1484
  return null;
@@ -1546,48 +1616,95 @@ export class OAuthManager {
1546
1616
  }
1547
1617
  return result;
1548
1618
  }
1549
- async getProfileBuckets(providerName) {
1619
+ getSessionBucketScopeKey(provider, metadata) {
1620
+ const profileId = typeof metadata?.profileId === 'string' &&
1621
+ metadata.profileId.trim() !== ''
1622
+ ? metadata.profileId.trim()
1623
+ : undefined;
1624
+ return profileId ? `${provider}::${profileId}` : provider;
1625
+ }
1626
+ async getCurrentProfileSessionMetadata(providerName) {
1550
1627
  try {
1551
- // Try to get profile from runtime settings
1552
1628
  const { getCliRuntimeServices } = await import('../runtime/runtimeSettings.js');
1553
1629
  const { settingsService } = getCliRuntimeServices();
1554
- // Get current profile name
1555
1630
  const currentProfileName = typeof settingsService.getCurrentProfileName === 'function'
1556
1631
  ? settingsService.getCurrentProfileName()
1557
- : settingsService.get('currentProfile');
1558
- if (!currentProfileName) {
1559
- return [];
1632
+ : (settingsService.get('currentProfile') ?? null);
1633
+ if (!currentProfileName || currentProfileName.trim() === '') {
1634
+ return undefined;
1560
1635
  }
1561
- // Load the profile to check for auth.buckets
1562
- const profileManager = await createProfileManager();
1563
- const profile = await profileManager.loadProfile(currentProfileName);
1564
- // Issue #1468: Verify the profile's provider matches the requested provider
1565
- // Without this check, buckets from one provider's profile could be returned
1566
- // when requesting buckets for a different provider, causing token storage
1567
- // corruption (e.g., Anthropic tokens saved under codex:bucket keys)
1568
- const profileProvider = 'provider' in profile && typeof profile.provider === 'string'
1569
- ? profile.provider
1570
- : null;
1571
- if (profileProvider !== providerName) {
1572
- logger.debug(`Profile provider '${profileProvider}' does not match requested provider '${providerName}', returning empty buckets`);
1573
- return [];
1636
+ return {
1637
+ providerId: providerName,
1638
+ profileId: currentProfileName.trim(),
1639
+ };
1640
+ }
1641
+ catch (error) {
1642
+ logger.debug(`Could not resolve current profile session metadata for ${providerName}:`, error);
1643
+ return undefined;
1644
+ }
1645
+ }
1646
+ async getProfileBuckets(providerName, metadata) {
1647
+ // Prefer the request-scoped profile identity supplied by the caller.
1648
+ const requestedProfileName = typeof metadata?.profileId === 'string' &&
1649
+ metadata.profileId.trim() !== ''
1650
+ ? metadata.profileId.trim()
1651
+ : null;
1652
+ let currentProfileName = requestedProfileName;
1653
+ if (!currentProfileName) {
1654
+ try {
1655
+ // Fall back to the CLI runtime's current profile only when the request
1656
+ // did not provide an explicit profile identity.
1657
+ const { getCliRuntimeServices } = await import('../runtime/runtimeSettings.js');
1658
+ const { settingsService } = getCliRuntimeServices();
1659
+ currentProfileName =
1660
+ typeof settingsService.getCurrentProfileName === 'function'
1661
+ ? settingsService.getCurrentProfileName()
1662
+ : (settingsService.get('currentProfile') ??
1663
+ null);
1574
1664
  }
1575
- // Check if profile has auth.buckets for this provider
1576
- if ('auth' in profile &&
1577
- profile.auth &&
1578
- typeof profile.auth === 'object' &&
1579
- 'type' in profile.auth &&
1580
- profile.auth.type === 'oauth' &&
1581
- 'buckets' in profile.auth &&
1582
- Array.isArray(profile.auth.buckets)) {
1583
- return profile.auth.buckets;
1665
+ catch (error) {
1666
+ logger.debug(`Could not resolve current profile for ${providerName}:`, error);
1667
+ return [];
1584
1668
  }
1669
+ }
1670
+ if (!currentProfileName) {
1585
1671
  return [];
1586
1672
  }
1673
+ let profile;
1674
+ try {
1675
+ // Load the profile to check for auth.buckets
1676
+ const profileManager = await createProfileManager();
1677
+ profile = await profileManager.loadProfile(currentProfileName);
1678
+ }
1587
1679
  catch (error) {
1588
1680
  logger.debug(`Could not load profile buckets for ${providerName}:`, error);
1681
+ if (requestedProfileName) {
1682
+ throw error;
1683
+ }
1684
+ return [];
1685
+ }
1686
+ // Issue #1468: Verify the profile's provider matches the requested provider
1687
+ // Without this check, buckets from one provider's profile could be returned
1688
+ // when requesting buckets for a different provider, causing token storage
1689
+ // corruption (e.g., Anthropic tokens saved under codex:bucket keys)
1690
+ const profileProvider = 'provider' in profile && typeof profile.provider === 'string'
1691
+ ? profile.provider
1692
+ : null;
1693
+ if (profileProvider !== providerName) {
1694
+ logger.debug(`Profile provider '${profileProvider}' does not match requested provider '${providerName}', returning empty buckets`);
1589
1695
  return [];
1590
1696
  }
1697
+ // Check if profile has auth.buckets for this provider
1698
+ if ('auth' in profile &&
1699
+ profile.auth &&
1700
+ typeof profile.auth === 'object' &&
1701
+ 'type' in profile.auth &&
1702
+ profile.auth.type === 'oauth' &&
1703
+ 'buckets' in profile.auth &&
1704
+ Array.isArray(profile.auth.buckets)) {
1705
+ return profile.auth.buckets;
1706
+ }
1707
+ return [];
1591
1708
  }
1592
1709
  /**
1593
1710
  * Authenticate multiple OAuth buckets sequentially using MultiBucketAuthenticator
@@ -1596,7 +1713,7 @@ export class OAuthManager {
1596
1713
  * Issue 913: This method now supports eager authentication of all buckets upfront,
1597
1714
  * filtering out already-authenticated buckets to avoid unnecessary prompts.
1598
1715
  */
1599
- async authenticateMultipleBuckets(providerName, buckets) {
1716
+ async authenticateMultipleBuckets(providerName, buckets, requestMetadata) {
1600
1717
  const { MultiBucketAuthenticator } = await import('./MultiBucketAuthenticator.js');
1601
1718
  // Get ephemeral settings for timing controls
1602
1719
  const { getEphemeralSetting: getRuntimeEphemeralSetting } = await import('../runtime/runtimeSettings.js');
@@ -1851,7 +1968,7 @@ export class OAuthManager {
1851
1968
  if (buckets.length > 1) {
1852
1969
  const config = this.getConfig?.();
1853
1970
  if (config) {
1854
- const handler = new BucketFailoverHandlerImpl(buckets, providerName, this);
1971
+ const handler = new BucketFailoverHandlerImpl(buckets, providerName, this, requestMetadata);
1855
1972
  config.setBucketFailoverHandler(handler);
1856
1973
  logger.debug('Bucket failover handler configured', {
1857
1974
  provider: providerName,