mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 11:20:43 +00:00
fix: honor effective model auth health
This commit is contained in:
@@ -148,6 +148,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Memory/QMD: warn with a manual stale collection removal hint when QMD reports a path/pattern conflict but `collection list` lacks verifiable metadata, avoiding unsafe stderr-only rebinds. Refs #71783. (#72297) Thanks @MonkeyLeeT.
|
||||
- Models/auth: make `openclaw models status --check` and dashboard auth health honor effective auth profile order while keeping stale profiles visible. (#79685) Thanks @nimbleenigma.
|
||||
- Docs/Subagents: correct the listed sub-agent bootstrap context files to include `SOUL.md`, `IDENTITY.md`, and `USER.md`. (#79470) Thanks @lastguru-net.
|
||||
- Backup: keep live backup archives from copying current agent session transcripts, cron run logs, and delivery queues while preserving workspace lock/temp files and keeping `--json` output parseable when volatile files are skipped. Fixes #72249. (#72251) Thanks @abnershang.
|
||||
- OpenAI/Codex: install the Codex runtime plugin from npm during OpenAI onboarding and load it automatically for implicit OpenAI model routes, while preserving manual PI runtime overrides. Fixes #79358.
|
||||
|
||||
@@ -157,6 +157,9 @@ describe("buildAuthHealthSummary", () => {
|
||||
const provider = summary.providers.find((entry) => entry.provider === "openai-codex");
|
||||
expect(provider?.status).toBe("ok");
|
||||
expect(provider?.expiresAt).toBe(now + DEFAULT_OAUTH_WARN_MS + 60_000);
|
||||
expect(provider?.effectiveProfiles?.map((profile) => profile.profileId)).toEqual([
|
||||
"openai-codex:named",
|
||||
]);
|
||||
expect(provider?.profiles.map((profile) => profile.profileId)).toEqual([
|
||||
"openai-codex:default",
|
||||
"openai-codex:named",
|
||||
@@ -188,6 +191,7 @@ describe("buildAuthHealthSummary", () => {
|
||||
|
||||
const provider = summary.providers.find((entry) => entry.provider === "codex-cli");
|
||||
expect(provider?.status).toBe("missing");
|
||||
expect(provider?.effectiveProfiles).toEqual([]);
|
||||
expect(provider?.profiles.map((profile) => profile.profileId)).toEqual(["codex-cli:legacy"]);
|
||||
});
|
||||
|
||||
@@ -403,6 +407,7 @@ describe("buildAuthHealthSummary", () => {
|
||||
{
|
||||
provider: "zai",
|
||||
status: "static",
|
||||
effectiveProfiles: summary.profiles,
|
||||
profiles: summary.profiles,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -10,8 +10,7 @@ import { resolveEffectiveOAuthCredential } from "./auth-profiles/effective-oauth
|
||||
import { resolveAuthProfileOrder } from "./auth-profiles/order.js";
|
||||
import type { AuthProfileCredential, AuthProfileStore } from "./auth-profiles/types.js";
|
||||
import { resolveProviderIdForAuth } from "./provider-auth-aliases.js";
|
||||
import { findNormalizedProviderValue } from "./provider-id.js";
|
||||
import { normalizeProviderId } from "./provider-id.js";
|
||||
import { findNormalizedProviderValue, normalizeProviderId } from "./provider-id.js";
|
||||
|
||||
type AuthProfileSource = "store";
|
||||
|
||||
@@ -36,6 +35,11 @@ export type AuthProviderHealth = {
|
||||
status: AuthProviderHealthStatus;
|
||||
expiresAt?: number;
|
||||
remainingMs?: number;
|
||||
/**
|
||||
* Full credential inventory stays in `profiles`; provider rollups use this
|
||||
* effective subset after auth order, aliases, and explicit exclusions apply.
|
||||
*/
|
||||
effectiveProfiles?: AuthProfileHealth[];
|
||||
profiles: AuthProfileHealth[];
|
||||
};
|
||||
|
||||
@@ -298,8 +302,9 @@ export function buildAuthHealthSummary(params: {
|
||||
};
|
||||
|
||||
for (const provider of providersMap.values()) {
|
||||
const statusProfiles = resolveProviderStatusProfiles(provider);
|
||||
if (statusProfiles.length === 0) {
|
||||
const effectiveProfiles = resolveProviderStatusProfiles(provider);
|
||||
provider.effectiveProfiles = effectiveProfiles;
|
||||
if (effectiveProfiles.length === 0) {
|
||||
provider.status = "missing";
|
||||
provider.expiresAt = undefined;
|
||||
provider.remainingMs = undefined;
|
||||
@@ -311,7 +316,7 @@ export function buildAuthHealthSummary(params: {
|
||||
let hasExpiredOrMissing = false;
|
||||
let hasExpiring = false;
|
||||
let earliestExpiry: number | undefined;
|
||||
for (const profile of statusProfiles) {
|
||||
for (const profile of effectiveProfiles) {
|
||||
if (profile.type === "api_key") {
|
||||
hasApiKeyProfile = true;
|
||||
continue;
|
||||
|
||||
@@ -573,7 +573,7 @@ export async function modelsStatusCommand(
|
||||
if (
|
||||
usage.allowCodexRuntimeFallback &&
|
||||
openAIProviderUsesCodexRuntimeByDefault({ provider: usage.provider, config: cfg }) &&
|
||||
providerAuthMap.has(OPENAI_CODEX_PROVIDER_ID)
|
||||
hasUsableProviderAuth(OPENAI_CODEX_PROVIDER_ID)
|
||||
) {
|
||||
providersInUse.add(OPENAI_CODEX_PROVIDER_ID);
|
||||
}
|
||||
|
||||
@@ -548,6 +548,22 @@ describe("aggregateOAuthStatus", () => {
|
||||
expect(result.status).toBe("ok");
|
||||
});
|
||||
|
||||
it("uses effective OAuth profiles while keeping stale inventory visible", () => {
|
||||
const healthy = oauth("ok", expiring + 10_000_000);
|
||||
const stale = oauth("expired", NOW - 1);
|
||||
const result = aggregateOAuthStatus(
|
||||
{
|
||||
provider: "openai-codex",
|
||||
status: "ok",
|
||||
effectiveProfiles: [healthy],
|
||||
profiles: [stale, healthy],
|
||||
},
|
||||
NOW,
|
||||
);
|
||||
expect(result.status).toBe("ok");
|
||||
expect(result.expiresAt).toBe(healthy.expiresAt);
|
||||
});
|
||||
|
||||
it("falls back to prov.status when no OAuth profiles exist", () => {
|
||||
const result = aggregateOAuthStatus(
|
||||
{
|
||||
|
||||
@@ -106,6 +106,8 @@ function providerDisplayName(provider: string): string {
|
||||
* where a healthy OAuth sits alongside an expired/missing bearer token.
|
||||
* For the dashboard's OAuth-health signal, token profiles are a separate
|
||||
* concern — we want "is OAuth healthy?", not "is every credential healthy?"
|
||||
* It also consumes the provider's effective profile subset when auth order
|
||||
* excludes stale inventory from the runtime credential path.
|
||||
*
|
||||
* `expectsOAuth` surfaces the configured-OAuth-but-no-oauth-profile case as
|
||||
* `missing` instead of silently falling back to the provider's rollup (which
|
||||
@@ -124,7 +126,8 @@ export function aggregateOAuthStatus(
|
||||
expiresAt?: number;
|
||||
remainingMs?: number;
|
||||
} {
|
||||
const oauth = prov.profiles.filter((p) => p.type === "oauth");
|
||||
const profiles = prov.effectiveProfiles ?? prov.profiles;
|
||||
const oauth = profiles.filter((p) => p.type === "oauth");
|
||||
if (oauth.length === 0) {
|
||||
if (expectsOAuth) {
|
||||
return { status: "missing" };
|
||||
|
||||
Reference in New Issue
Block a user