diff --git a/CHANGELOG.md b/CHANGELOG.md index 40b2c295e..4660aeccc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ### Fixes - Onboarding: resolve CLI entrypoint when running via `npx` so gateway daemon install works without a build step. - CLI: auto-migrate legacy config entries on command start (same behavior as gateway startup). +- Auth: prioritize OAuth profiles but fall back to API keys when refresh fails; stored profiles now load without explicit auth order. - Linux: auto-attempt lingering during onboarding (try without sudo, fallback to sudo) and prompt on install/restart to keep the gateway alive after logout/idle. Thanks @tobiasbischoff for PR #237. - TUI: migrate key handling to the updated pi-tui Key matcher API. - Logging: redact sensitive tokens in verbose tool summaries by default (configurable patterns). diff --git a/src/agents/auth-profiles.test.ts b/src/agents/auth-profiles.test.ts index 286b52093..493f2c09d 100644 --- a/src/agents/auth-profiles.test.ts +++ b/src/agents/auth-profiles.test.ts @@ -30,12 +30,12 @@ describe("resolveAuthProfileOrder", () => { }, }; - it("returns empty order without explicit config", () => { + it("uses stored profiles when no config exists", () => { const order = resolveAuthProfileOrder({ store, provider: "anthropic", }); - expect(order).toEqual([]); + expect(order).toEqual(["anthropic:default", "anthropic:work"]); }); it("prioritizes preferred profiles", () => { @@ -80,4 +80,29 @@ describe("resolveAuthProfileOrder", () => { }); expect(order).toEqual(["anthropic:work", "anthropic:default"]); }); + + it("prioritizes oauth profiles when order missing", () => { + const mixedStore: AuthProfileStore = { + version: 1, + profiles: { + "anthropic:default": { + type: "api_key", + provider: "anthropic", + key: "sk-default", + }, + "anthropic:oauth": { + type: "oauth", + provider: "anthropic", + access: "access-token", + refresh: "refresh-token", + expires: Date.now() + 60_000, + }, + }, + }; + const order = resolveAuthProfileOrder({ + store: mixedStore, + provider: "anthropic", + }); + expect(order).toEqual(["anthropic:oauth", "anthropic:default"]); + }); }); diff --git a/src/agents/auth-profiles.ts b/src/agents/auth-profiles.ts index 61346566d..f9999f17e 100644 --- a/src/agents/auth-profiles.ts +++ b/src/agents/auth-profiles.ts @@ -258,10 +258,16 @@ export function resolveAuthProfileOrder(params: { .map(([profileId]) => profileId) : []; const lastGood = store.lastGood?.[provider]; - const order = + const baseOrder = configuredOrder ?? - (explicitProfiles.length > 0 ? explicitProfiles : undefined); - if (!order) return []; + (explicitProfiles.length > 0 + ? explicitProfiles + : listProfilesForProvider(store, provider)); + if (baseOrder.length === 0) return []; + const order = + configuredOrder && configuredOrder.length > 0 + ? baseOrder + : orderProfilesByMode(baseOrder, store); const filtered = order.filter((profileId) => { const cred = store.profiles[profileId]; @@ -288,6 +294,20 @@ export function resolveAuthProfileOrder(params: { return deduped; } +function orderProfilesByMode( + order: string[], + store: AuthProfileStore, +): string[] { + const scored = order.map((profileId) => { + const type = store.profiles[profileId]?.type; + const score = type === "oauth" ? 0 : type === "api_key" ? 1 : 2; + return { profileId, score }; + }); + return scored + .sort((a, b) => a.score - b.score) + .map((entry) => entry.profileId); +} + export async function resolveApiKeyForProfile(params: { cfg?: ClawdbotConfig; store: AuthProfileStore; diff --git a/src/agents/pi-embedded-runner.ts b/src/agents/pi-embedded-runner.ts index d04314e71..c3ccbe107 100644 --- a/src/agents/pi-embedded-runner.ts +++ b/src/agents/pi-embedded-runner.ts @@ -374,8 +374,11 @@ export async function compactEmbeddedPiSession(params: { }; } try { - const apiKey = await getApiKeyForModel(model, authStorage); - authStorage.setRuntimeApiKey(model.provider, apiKey); + const apiKey = await getApiKeyForModel({ + model, + cfg: params.config, + }); + authStorage.setRuntimeApiKey(model.provider, apiKey.apiKey); } catch (err) { return { ok: false, diff --git a/src/discord/send.test.ts b/src/discord/send.test.ts index f406e710a..675cb464a 100644 --- a/src/discord/send.test.ts +++ b/src/discord/send.test.ts @@ -108,7 +108,9 @@ describe("sendMessageDiscord", () => { it("adds missing permission hints on 50013", async () => { const { rest, postMock, getMock } = makeRest(); - const perms = new PermissionsBitField([PermissionsBitField.Flags.ViewChannel]); + const perms = new PermissionsBitField([ + PermissionsBitField.Flags.ViewChannel, + ]); const apiError = Object.assign(new Error("Missing Permissions"), { code: 50013, status: 403,