mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:50:43 +00:00
fix: refresh stale codex auth profile routing
Summary:
- Promotes fresh Codex OAuth relogin profiles ahead of stale per-agent auth order entries.
- Repairs invalidated per-agent Codex order and session overrides toward healthy relogin profiles.
- Adds focused regression coverage for auth order, invalidated profile repair, and session override re-resolution.
Verification:
- pnpm test src/agents/auth-profiles/profiles.test.ts src/agents/auth-profiles.ensureauthprofilestore.test.ts src/agents/auth-profiles/session-override.test.ts src/commands/models/auth.test.ts -- --reporter=verbose
- pnpm exec oxfmt --check --threads=1 CHANGELOG.md src/agents/auth-profiles.ensureauthprofilestore.test.ts src/agents/auth-profiles/persisted.ts src/agents/auth-profiles/profiles.test.ts src/agents/auth-profiles/profiles.ts src/agents/auth-profiles/session-override.test.ts src/agents/auth-profiles/session-override.ts src/commands/models/auth.test.ts src/commands/models/auth.ts
- git diff --check origin/main...HEAD
- pnpm check:changed via Blacksmith Testbox tbx_01kqscwvkywnt72qx1t8a07tp8
- GitHub CI on 1a6f93a372, with checks-node-core-runtime-infra-state rerun passing after an unrelated stale-lock timing failure
This commit is contained in:
@@ -68,6 +68,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Plugins/install: remove the previous managed plugin directory when a reinstall switches sources, so stale ClawHub and npm copies no longer keep duplicate plugin ids in discovery after the new install wins. Thanks @vincentkoc.
|
||||
- Plugins/install: let official plugin reinstall recovery repair source-only installed runtime shadows, so `openclaw plugins install npm:@openclaw/discord --force` can replace the bad package instead of stopping at stale config validation. Thanks @vincentkoc.
|
||||
- Plugins/commands: allow the official ClawHub Codex plugin package to keep reserved `/codex` command ownership, matching the existing npm-managed Codex package behavior. Thanks @vincentkoc.
|
||||
- Auth/OpenAI Codex: rewrite invalidated per-agent Codex auth-order and session profile overrides toward a healthy relogin profile, so revoked OAuth accounts do not stay pinned after signing in again. Thanks @BunsDev.
|
||||
- Plugins/commands: scope QQBot framework slash commands to the QQBot channel so `/bot-*` command handlers and native specs do not leak onto unrelated chat surfaces. Thanks @vincentkoc.
|
||||
- fix: harden backend message action gateway routing [AI]. (#76374) Thanks @pgondhi987.
|
||||
- Gate QQBot streaming command auth [AI]. (#76375) Thanks @pgondhi987.
|
||||
|
||||
@@ -573,6 +573,94 @@ describe("ensureAuthProfileStore", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("rewrites invalidated per-agent Codex order to the main agent's healthy relogin profile", () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-codex-relogin-"));
|
||||
const previousAgentDir = process.env.OPENCLAW_AGENT_DIR;
|
||||
const previousPiAgentDir = process.env.PI_CODING_AGENT_DIR;
|
||||
try {
|
||||
const mainDir = path.join(root, "main-agent");
|
||||
const agentDir = path.join(root, "agent-x");
|
||||
fs.mkdirSync(mainDir, { recursive: true });
|
||||
fs.mkdirSync(agentDir, { recursive: true });
|
||||
|
||||
process.env.OPENCLAW_AGENT_DIR = mainDir;
|
||||
process.env.PI_CODING_AGENT_DIR = mainDir;
|
||||
|
||||
const now = Date.now();
|
||||
const healthyProfileId = "openai-codex:bunsthedev@gmail.com";
|
||||
const staleProfileId = "openai-codex:val@viewdue.ai";
|
||||
saveAuthProfileStore(
|
||||
{
|
||||
version: AUTH_STORE_VERSION,
|
||||
profiles: {
|
||||
[healthyProfileId]: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
access: "healthy-access",
|
||||
refresh: "healthy-refresh",
|
||||
expires: now + 60 * 60 * 1000,
|
||||
email: "bunsthedev@gmail.com",
|
||||
},
|
||||
},
|
||||
order: {
|
||||
"openai-codex": [healthyProfileId],
|
||||
},
|
||||
lastGood: {
|
||||
"openai-codex": healthyProfileId,
|
||||
},
|
||||
},
|
||||
mainDir,
|
||||
);
|
||||
saveAuthProfileStore(
|
||||
{
|
||||
version: AUTH_STORE_VERSION,
|
||||
profiles: {
|
||||
[staleProfileId]: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
access: "stale-access",
|
||||
refresh: "stale-refresh",
|
||||
expires: now + 30 * 60 * 1000,
|
||||
email: "val@viewdue.ai",
|
||||
},
|
||||
},
|
||||
order: {
|
||||
"openai-codex": [staleProfileId],
|
||||
},
|
||||
lastGood: {
|
||||
"openai-codex": staleProfileId,
|
||||
},
|
||||
usageStats: {
|
||||
[staleProfileId]: {
|
||||
cooldownUntil: now + 60_000,
|
||||
cooldownReason: "auth",
|
||||
failureCounts: { auth: 1 },
|
||||
errorCount: 1,
|
||||
lastFailureAt: now - 1_000,
|
||||
},
|
||||
},
|
||||
},
|
||||
agentDir,
|
||||
);
|
||||
clearRuntimeAuthProfileStoreSnapshots();
|
||||
|
||||
const store = loadAuthProfileStoreForRuntime(agentDir, { readOnly: true });
|
||||
|
||||
expect(store.profiles[healthyProfileId]).toMatchObject({
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
access: "healthy-access",
|
||||
});
|
||||
expect(store.profiles[staleProfileId]).toBeUndefined();
|
||||
expect(store.order?.["openai-codex"]).toEqual([healthyProfileId]);
|
||||
expect(store.lastGood?.["openai-codex"]).toBe(healthyProfileId);
|
||||
expect(store.usageStats?.[staleProfileId]).toBeUndefined();
|
||||
} finally {
|
||||
restoreAgentDirEnv({ previousAgentDir, previousPiAgentDir });
|
||||
fs.rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "mode/apiKey aliases map to type/key",
|
||||
|
||||
@@ -18,10 +18,12 @@ import {
|
||||
} from "./state.js";
|
||||
import type {
|
||||
AuthProfileCredential,
|
||||
AuthProfileFailureReason,
|
||||
AuthProfileSecretsStore,
|
||||
AuthProfileStore,
|
||||
OAuthCredential,
|
||||
OAuthCredentials,
|
||||
ProfileUsageStats,
|
||||
} from "./types.js";
|
||||
|
||||
export type LegacyAuthStore = Record<string, AuthProfileCredential>;
|
||||
@@ -213,6 +215,107 @@ function isNewerUsableOAuthCredential(
|
||||
);
|
||||
}
|
||||
|
||||
const AUTH_INVALIDATION_REASONS = new Set<AuthProfileFailureReason>([
|
||||
"auth",
|
||||
"auth_permanent",
|
||||
"session_expired",
|
||||
]);
|
||||
|
||||
function hasAuthInvalidationSignal(stats: ProfileUsageStats | undefined): boolean {
|
||||
if (!stats) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
(stats.cooldownReason && AUTH_INVALIDATION_REASONS.has(stats.cooldownReason)) ||
|
||||
(stats.disabledReason && AUTH_INVALIDATION_REASONS.has(stats.disabledReason))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return Object.entries(stats.failureCounts ?? {}).some(
|
||||
([reason, count]) =>
|
||||
AUTH_INVALIDATION_REASONS.has(reason as AuthProfileFailureReason) &&
|
||||
typeof count === "number" &&
|
||||
count > 0,
|
||||
);
|
||||
}
|
||||
|
||||
function isProfileReferencedByAuthState(store: AuthProfileStore, profileId: string): boolean {
|
||||
if (Object.values(store.order ?? {}).some((profileIds) => profileIds.includes(profileId))) {
|
||||
return true;
|
||||
}
|
||||
return Object.values(store.lastGood ?? {}).some((value) => value === profileId);
|
||||
}
|
||||
|
||||
function resolveProviderAuthStateValue<T>(
|
||||
values: Record<string, T> | undefined,
|
||||
providerKey: string,
|
||||
): T | undefined {
|
||||
if (!values) {
|
||||
return undefined;
|
||||
}
|
||||
for (const [key, value] of Object.entries(values)) {
|
||||
if (normalizeProviderId(key) === providerKey) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function findMainStoreOAuthReplacementForInvalidatedProfile(params: {
|
||||
base: AuthProfileStore;
|
||||
override: AuthProfileStore;
|
||||
profileId: string;
|
||||
credential: OAuthCredential;
|
||||
}): string | undefined {
|
||||
const providerKey = normalizeProviderId(params.credential.provider);
|
||||
if (
|
||||
providerKey !== "openai-codex" ||
|
||||
!isProfileReferencedByAuthState(params.override, params.profileId) ||
|
||||
!hasAuthInvalidationSignal(params.override.usageStats?.[params.profileId])
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const candidates = Object.entries(params.base.profiles)
|
||||
.flatMap(([profileId, credential]): Array<[string, OAuthCredential]> => {
|
||||
if (
|
||||
profileId === params.profileId ||
|
||||
credential.type !== "oauth" ||
|
||||
normalizeProviderId(credential.provider) !== providerKey ||
|
||||
!hasUsableOAuthCredential(credential)
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
return [[profileId, credential]];
|
||||
})
|
||||
.toSorted(([leftId, leftCredential], [rightId, rightCredential]) => {
|
||||
const leftExpires = Number.isFinite(leftCredential.expires) ? leftCredential.expires : 0;
|
||||
const rightExpires = Number.isFinite(rightCredential.expires) ? rightCredential.expires : 0;
|
||||
if (rightExpires !== leftExpires) {
|
||||
return rightExpires - leftExpires;
|
||||
}
|
||||
return leftId.localeCompare(rightId);
|
||||
});
|
||||
if (candidates.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const candidateIds = new Set(candidates.map(([profileId]) => profileId));
|
||||
const orderedProfileId = resolveProviderAuthStateValue(params.base.order, providerKey)?.find(
|
||||
(profileId) => candidateIds.has(profileId),
|
||||
);
|
||||
if (orderedProfileId) {
|
||||
return orderedProfileId;
|
||||
}
|
||||
|
||||
const lastGoodProfileId = resolveProviderAuthStateValue(params.base.lastGood, providerKey);
|
||||
if (lastGoodProfileId && candidateIds.has(lastGoodProfileId)) {
|
||||
return lastGoodProfileId;
|
||||
}
|
||||
|
||||
return candidates.length === 1 ? candidates[0]?.[0] : undefined;
|
||||
}
|
||||
|
||||
function findMainStoreOAuthReplacement(params: {
|
||||
base: AuthProfileStore;
|
||||
legacyProfileId: string;
|
||||
@@ -343,14 +446,21 @@ function reconcileMainStoreOAuthProfileDrift(params: {
|
||||
}): AuthProfileStore {
|
||||
const replacements = new Map<string, string>();
|
||||
for (const [profileId, credential] of Object.entries(params.override.profiles)) {
|
||||
if (credential.type !== "oauth" || !isLegacyDefaultOAuthProfile(profileId, credential)) {
|
||||
if (credential.type !== "oauth") {
|
||||
continue;
|
||||
}
|
||||
const replacementProfileId = findMainStoreOAuthReplacement({
|
||||
base: params.base,
|
||||
legacyProfileId: profileId,
|
||||
legacyCredential: credential,
|
||||
});
|
||||
const replacementProfileId = isLegacyDefaultOAuthProfile(profileId, credential)
|
||||
? findMainStoreOAuthReplacement({
|
||||
base: params.base,
|
||||
legacyProfileId: profileId,
|
||||
legacyCredential: credential,
|
||||
})
|
||||
: findMainStoreOAuthReplacementForInvalidatedProfile({
|
||||
base: params.base,
|
||||
override: params.override,
|
||||
profileId,
|
||||
credential,
|
||||
});
|
||||
if (replacementProfileId) {
|
||||
replacements.set(profileId, replacementProfileId);
|
||||
}
|
||||
|
||||
56
src/agents/auth-profiles/profiles.test.ts
Normal file
56
src/agents/auth-profiles/profiles.test.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { AUTH_STORE_VERSION } from "./constants.js";
|
||||
import { promoteAuthProfileInOrder } from "./profiles.js";
|
||||
import { loadAuthProfileStoreForRuntime, saveAuthProfileStore } from "./store.js";
|
||||
|
||||
describe("promoteAuthProfileInOrder", () => {
|
||||
it("moves a relogin profile to the front of an existing per-agent provider order", async () => {
|
||||
const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-order-promote-"));
|
||||
try {
|
||||
const newProfileId = "openai-codex:bunsthedev@gmail.com";
|
||||
const staleProfileId = "openai-codex:val@viewdue.ai";
|
||||
saveAuthProfileStore(
|
||||
{
|
||||
version: AUTH_STORE_VERSION,
|
||||
profiles: {
|
||||
[newProfileId]: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
access: "new-access",
|
||||
refresh: "new-refresh",
|
||||
expires: Date.now() + 60 * 60 * 1000,
|
||||
},
|
||||
[staleProfileId]: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
access: "stale-access",
|
||||
refresh: "stale-refresh",
|
||||
expires: Date.now() + 30 * 60 * 1000,
|
||||
},
|
||||
},
|
||||
order: {
|
||||
"openai-codex": [staleProfileId],
|
||||
},
|
||||
},
|
||||
agentDir,
|
||||
);
|
||||
|
||||
const updated = await promoteAuthProfileInOrder({
|
||||
agentDir,
|
||||
provider: "openai-codex",
|
||||
profileId: newProfileId,
|
||||
});
|
||||
|
||||
expect(updated?.order?.["openai-codex"]).toEqual([newProfileId, staleProfileId]);
|
||||
expect(loadAuthProfileStoreForRuntime(agentDir).order?.["openai-codex"]).toEqual([
|
||||
newProfileId,
|
||||
staleProfileId,
|
||||
]);
|
||||
} finally {
|
||||
fs.rmSync(agentDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
import { normalizeStringEntries } from "../../shared/string-normalization.js";
|
||||
import { normalizeSecretInput } from "../../utils/normalize-secret-input.js";
|
||||
import { resolveProviderIdForAuth } from "../provider-auth-aliases.js";
|
||||
import { normalizeProviderId } from "../provider-id.js";
|
||||
import { findNormalizedProviderKey, normalizeProviderId } from "../provider-id.js";
|
||||
import { dedupeProfileIds, listProfilesForProvider } from "./profile-list.js";
|
||||
import {
|
||||
ensureAuthProfileStoreForLocalUpdate,
|
||||
@@ -41,6 +41,41 @@ export async function setAuthProfileOrder(params: {
|
||||
});
|
||||
}
|
||||
|
||||
export async function promoteAuthProfileInOrder(params: {
|
||||
agentDir?: string;
|
||||
provider: string;
|
||||
profileId: string;
|
||||
}): Promise<AuthProfileStore | null> {
|
||||
const providerKey = resolveProviderIdForAuth(params.provider);
|
||||
return await updateAuthProfileStoreWithLock({
|
||||
agentDir: params.agentDir,
|
||||
updater: (store) => {
|
||||
const profile = store.profiles[params.profileId];
|
||||
if (!profile || resolveProviderIdForAuth(profile.provider) !== providerKey) {
|
||||
return false;
|
||||
}
|
||||
const orderKey =
|
||||
findNormalizedProviderKey(store.order, providerKey) ?? normalizeProviderId(providerKey);
|
||||
const existing = store.order?.[orderKey];
|
||||
if (!existing || existing.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const next = dedupeProfileIds([
|
||||
params.profileId,
|
||||
...existing.filter((profileId) => profileId !== params.profileId),
|
||||
]);
|
||||
if (
|
||||
next.length === existing.length &&
|
||||
next.every((profileId, idx) => profileId === existing[idx])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
store.order = { ...store.order, [orderKey]: next };
|
||||
return true;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function upsertAuthProfile(params: {
|
||||
profileId: string;
|
||||
credential: AuthProfileCredential;
|
||||
|
||||
@@ -19,7 +19,7 @@ const authStoreMocks = vi.hoisted(() => {
|
||||
state,
|
||||
ensureAuthProfileStore: vi.fn(() => state.store),
|
||||
hasAnyAuthProfileStoreSource: vi.fn(() => state.hasSource),
|
||||
isProfileInCooldown: vi.fn(() => false),
|
||||
isProfileInCooldown: vi.fn((_store: AuthProfileStore, _profileId: string) => false),
|
||||
reset() {
|
||||
state.hasSource = false;
|
||||
state.store = { version: 1, profiles: {} };
|
||||
@@ -246,4 +246,55 @@ describe("resolveSessionAuthProfileOverride", () => {
|
||||
expect(sessionEntry.authProfileOverride).toBe(TEST_PRIMARY_PROFILE_ID);
|
||||
});
|
||||
});
|
||||
|
||||
it("re-resolves a stale user session override when the selected profile becomes unusable", async () => {
|
||||
await withAuthState(async (state) => {
|
||||
const agentDir = state.agentDir();
|
||||
await fs.mkdir(agentDir, { recursive: true });
|
||||
authStoreMocks.state.hasSource = true;
|
||||
authStoreMocks.state.store = createAuthStoreWithProfiles({
|
||||
profiles: {
|
||||
[TEST_PRIMARY_PROFILE_ID]: {
|
||||
type: "api_key",
|
||||
provider: "openai-codex",
|
||||
key: "sk-stale",
|
||||
},
|
||||
[TEST_SECONDARY_PROFILE_ID]: {
|
||||
type: "api_key",
|
||||
provider: "openai-codex",
|
||||
key: "sk-healthy",
|
||||
},
|
||||
},
|
||||
order: {
|
||||
"openai-codex": [TEST_SECONDARY_PROFILE_ID, TEST_PRIMARY_PROFILE_ID],
|
||||
},
|
||||
});
|
||||
authStoreMocks.isProfileInCooldown.mockImplementation(
|
||||
(_store: AuthProfileStore, profileId: string) => profileId === TEST_PRIMARY_PROFILE_ID,
|
||||
);
|
||||
|
||||
const sessionEntry: SessionEntry = {
|
||||
sessionId: "s1",
|
||||
updatedAt: Date.now(),
|
||||
authProfileOverride: TEST_PRIMARY_PROFILE_ID,
|
||||
authProfileOverrideSource: "user",
|
||||
};
|
||||
const sessionStore = { "agent:main:main": sessionEntry };
|
||||
|
||||
const resolved = await resolveSessionAuthProfileOverride({
|
||||
cfg: {} as OpenClawConfig,
|
||||
provider: "openai-codex",
|
||||
agentDir,
|
||||
sessionEntry,
|
||||
sessionStore,
|
||||
sessionKey: "agent:main:main",
|
||||
storePath: undefined,
|
||||
isNewSession: false,
|
||||
});
|
||||
|
||||
expect(resolved).toBe(TEST_SECONDARY_PROFILE_ID);
|
||||
expect(sessionEntry.authProfileOverride).toBe(TEST_SECONDARY_PROFILE_ID);
|
||||
expect(sessionEntry.authProfileOverrideSource).toBe("auto");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -136,12 +136,21 @@ export async function resolveSessionAuthProfileOverride(params: {
|
||||
typeof sessionEntry.authProfileOverrideCompactionCount === "number"
|
||||
? sessionEntry.authProfileOverrideCompactionCount
|
||||
: compactionCount;
|
||||
const replacementForUnusableCurrent =
|
||||
current && isProfileInCooldown(store, current)
|
||||
? order.find((profileId) => profileId !== current && !isProfileInCooldown(store, profileId))
|
||||
: undefined;
|
||||
if (replacementForUnusableCurrent) {
|
||||
current = undefined;
|
||||
}
|
||||
if (source === "user" && current && !isNewSession) {
|
||||
return current;
|
||||
}
|
||||
|
||||
let next = current;
|
||||
if (isNewSession) {
|
||||
if (replacementForUnusableCurrent) {
|
||||
next = replacementForUnusableCurrent;
|
||||
} else if (isNewSession) {
|
||||
next = current ? pickNextAvailable(current) : pickFirstAvailable();
|
||||
} else if (current && compactionCount > storedCompaction) {
|
||||
next = pickNextAvailable(current);
|
||||
|
||||
@@ -23,11 +23,13 @@ const mocks = vi.hoisted(() => ({
|
||||
isRemoteEnvironment: vi.fn(() => false),
|
||||
loadAuthProfileStoreForRuntime: vi.fn(),
|
||||
listProfilesForProvider: vi.fn(),
|
||||
promoteAuthProfileInOrder: vi.fn(),
|
||||
clearAuthProfileCooldown: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../agents/auth-profiles/profiles.js", () => ({
|
||||
listProfilesForProvider: mocks.listProfilesForProvider,
|
||||
promoteAuthProfileInOrder: mocks.promoteAuthProfileInOrder,
|
||||
upsertAuthProfile: mocks.upsertAuthProfile,
|
||||
}));
|
||||
|
||||
@@ -278,6 +280,7 @@ describe("modelsAuthLoginCommand", () => {
|
||||
mocks.clackSelect.mockReset();
|
||||
mocks.clackText.mockReset();
|
||||
mocks.upsertAuthProfile.mockReset();
|
||||
mocks.promoteAuthProfileInOrder.mockReset();
|
||||
|
||||
mocks.resolveDefaultAgentId.mockReturnValue("main");
|
||||
mocks.resolveAgentDir.mockReturnValue("/tmp/openclaw/agents/main");
|
||||
@@ -391,6 +394,11 @@ describe("modelsAuthLoginCommand", () => {
|
||||
}),
|
||||
agentDir: "/tmp/openclaw/agents/main",
|
||||
});
|
||||
expect(mocks.promoteAuthProfileInOrder).toHaveBeenCalledWith({
|
||||
agentDir: "/tmp/openclaw/agents/main",
|
||||
provider: "openai-codex",
|
||||
profileId: "openai-codex:user@example.com",
|
||||
});
|
||||
expect(lastUpdatedConfig?.auth?.profiles?.["openai-codex:user@example.com"]).toMatchObject({
|
||||
provider: "openai-codex",
|
||||
mode: "oauth",
|
||||
|
||||
@@ -11,7 +11,11 @@ import {
|
||||
resolveDefaultAgentId,
|
||||
} from "../../agents/agent-scope.js";
|
||||
import { externalCliDiscoveryForProviderAuth } from "../../agents/auth-profiles.js";
|
||||
import { listProfilesForProvider, upsertAuthProfile } from "../../agents/auth-profiles/profiles.js";
|
||||
import {
|
||||
listProfilesForProvider,
|
||||
promoteAuthProfileInOrder,
|
||||
upsertAuthProfile,
|
||||
} from "../../agents/auth-profiles/profiles.js";
|
||||
import { loadAuthProfileStoreForRuntime } from "../../agents/auth-profiles/store.js";
|
||||
import type { AuthProfileCredential } from "../../agents/auth-profiles/types.js";
|
||||
import { clearAuthProfileCooldown } from "../../agents/auth-profiles/usage.js";
|
||||
@@ -247,6 +251,11 @@ async function persistProviderAuthResult(params: {
|
||||
credential: profile.credential,
|
||||
agentDir: params.agentDir,
|
||||
});
|
||||
await promoteAuthProfileInOrder({
|
||||
agentDir: params.agentDir,
|
||||
provider: profile.credential.provider,
|
||||
profileId: profile.profileId,
|
||||
});
|
||||
}
|
||||
|
||||
await updateConfig((cfg) => {
|
||||
|
||||
Reference in New Issue
Block a user