diff --git a/CHANGELOG.md b/CHANGELOG.md index d9a484c4388..a8564789063 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Docs: https://docs.openclaw.ai - OpenShell/SSH: reject malformed generated exec commands before sandbox/session setup so unresolved workflow placeholders fail fast instead of reaching the remote shell. Fixes #72373. Thanks @brokemac79. - Google: stop normalizing `gemini-3.1-flash-lite` to the retired preview endpoint and update Flash Lite alias guidance to the GA model id. Fixes #86151. (#86240) Thanks @SebTardif. - Installer: make Alpine apk installs cover Git, verify the Node runtime floor, try `nodejs-current`, and report Alpine version guidance when repositories only provide older Node packages. +- Agents/status: prefer the active Claude CLI OAuth auth label over an unused Anthropic env API-key label for equivalent runtime aliases. Fixes #80184. (#86570) Thanks @brokemac79. - Agents/media: send direct fallback for generated media still missing after an active requester wake fails. (#85489) Thanks @fuller-stack-dev. - Agents: derive overflow compaction budgets from provider-reported and synthetic over-budget token counts so confirmed context overflows compact before retrying. (#70473) Thanks @fuller-stack-dev. - Agents/Codex: recover Codex context-window prompt errors through overflow compaction and surface reset guidance when recovery is exhausted. (#85542) Thanks @fuller-stack-dev. diff --git a/src/agents/model-runtime-aliases.ts b/src/agents/model-runtime-aliases.ts index b1fc3f8c580..451a8a2eb17 100644 --- a/src/agents/model-runtime-aliases.ts +++ b/src/agents/model-runtime-aliases.ts @@ -1,4 +1,5 @@ import type { OpenClawConfig } from "../config/types.openclaw.js"; +import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; import { normalizeStaticProviderModelId } from "./model-ref-shared.js"; import { resolveModelRuntimePolicy } from "./model-runtime-policy.js"; import { resolveProviderIdForAuth } from "./provider-auth-aliases.js"; @@ -182,6 +183,26 @@ export function areRuntimeModelRefsEquivalent(left: string, right: string): bool ); } +export function shouldPreferActiveRuntimeAliasAuthLabel(params: { + runtimeAliasModelEquivalent: boolean; + selectedAuthLabel?: string; + activeAuthLabel?: string; +}): boolean { + if (!params.runtimeAliasModelEquivalent) { + return false; + } + const selectedAuth = normalizeOptionalLowercaseString(params.selectedAuthLabel); + const activeAuth = normalizeOptionalLowercaseString(params.activeAuthLabel); + if (!activeAuth || activeAuth === "unknown") { + return false; + } + return ( + selectedAuth === "unknown" || + (Boolean(selectedAuth?.startsWith("api-key")) && + (activeAuth.startsWith("oauth") || activeAuth.startsWith("token"))) + ); +} + function resolveConfiguredRuntime(params: { cfg?: OpenClawConfig; provider: string; diff --git a/src/auto-reply/reply/commands-status.test.ts b/src/auto-reply/reply/commands-status.test.ts index 88b04e99c21..9a1e68e6890 100644 --- a/src/auto-reply/reply/commands-status.test.ts +++ b/src/auto-reply/reply/commands-status.test.ts @@ -856,6 +856,52 @@ describe("buildStatusReply subagent summary", () => { ); }); + it("prefers active Claude CLI OAuth over selected env API-key labels for runtime aliases", async () => { + const text = await buildStatusText({ + cfg: { + ...baseCfg, + agents: { + defaults: { + agentRuntime: { id: "claude-cli" }, + }, + }, + }, + sessionEntry: { + sessionId: "sess-status-claude-cli-env-key-shadow", + updatedAt: 0, + providerOverride: "anthropic", + modelOverride: "claude-opus-4-7", + modelProvider: "claude-cli", + model: "claude-opus-4-7", + fallbackNoticeSelectedModel: "anthropic/claude-opus-4-7", + fallbackNoticeActiveModel: "claude-cli/claude-opus-4-7", + fallbackNoticeReason: "selected model unavailable", + }, + sessionKey: "agent:main:main", + parentSessionKey: "agent:main:main", + sessionScope: "per-sender", + statusChannel: "mobilechat", + provider: "anthropic", + model: "claude-opus-4-7", + contextTokens: 32_000, + resolvedHarness: "claude-cli", + resolvedFastMode: false, + resolvedVerboseLevel: "off", + resolvedReasoningLevel: "off", + resolveDefaultThinkingLevel: async () => undefined, + isGroup: false, + defaultGroupActivation: () => "mention", + modelAuthOverride: "api-key (env: ANTHROPIC_API_KEY)", + activeModelAuthOverride: "oauth (claude-cli)", + }); + + const normalized = normalizeTestText(text); + expect(normalized).toContain("Session selected: anthropic/claude-opus-4-7"); + expect(normalized).toContain("oauth (claude-cli)"); + expect(normalized).not.toContain("api-key (env: ANTHROPIC_API_KEY)"); + expect(normalized).not.toContain("Usage:"); + }); + it("uses Codex OAuth context overrides for openai models running on the Codex harness", async () => { registerStatusCodexHarness(); diff --git a/src/auto-reply/status.test.ts b/src/auto-reply/status.test.ts index f6e8ad3a4f5..5c2dc6a79f8 100644 --- a/src/auto-reply/status.test.ts +++ b/src/auto-reply/status.test.ts @@ -995,6 +995,53 @@ describe("buildStatusMessage", () => { expect(normalized).toContain("Context: 36k/1.0m (4%)"); }); + it("prefers active CLI OAuth over selected env API-key labels for runtime aliases", () => { + const text = buildStatusMessage({ + config: { + models: { + providers: { + anthropic: { + models: [ + { + id: "claude-opus-4-7", + cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 }, + }, + ], + }, + }, + }, + } as unknown as OpenClawConfig, + agent: { + model: "anthropic/claude-opus-4-7", + }, + sessionEntry: { + sessionId: "claude-cli-runtime-alias-env-key", + updatedAt: 0, + providerOverride: "anthropic", + modelOverride: "claude-opus-4-7", + modelProvider: "claude-cli", + model: "claude-opus-4-7", + fallbackNoticeSelectedModel: "anthropic/claude-opus-4-7", + fallbackNoticeActiveModel: "claude-cli/claude-opus-4-7", + fallbackNoticeReason: "selected model unavailable", + inputTokens: 29, + outputTokens: 19_000, + }, + sessionKey: "agent:main:main", + sessionScope: "per-sender", + queue: { mode: "collect", depth: 0 }, + modelAuth: "api-key (env: ANTHROPIC_API_KEY)", + activeModelAuth: "oauth (anthropic:claude-cli)", + }); + + const normalized = normalizeTestText(text); + expect(normalized).toContain("Model: anthropic/claude-opus-4-7"); + expect(normalized).toContain("oauth (anthropic:claude-cli)"); + expect(normalized).not.toContain("api-key (env: ANTHROPIC_API_KEY)"); + expect(normalized).not.toContain("Fallback: claude-cli/claude-opus-4-7"); + expect(normalized).not.toContain("Cost:"); + }); + it("keeps an explicit runtime context limit when fallback status already computed one", () => { const text = buildStatusMessage({ config: { diff --git a/src/status/status-message.ts b/src/status/status-message.ts index 72b576c456b..1ef4e0de85c 100644 --- a/src/status/status-message.ts +++ b/src/status/status-message.ts @@ -2,7 +2,10 @@ import fs from "node:fs"; import { resolveContextTokensForModel } from "../agents/context.js"; import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js"; import { resolveModelAuthMode } from "../agents/model-auth.js"; -import { areRuntimeModelRefsEquivalent } from "../agents/model-runtime-aliases.js"; +import { + areRuntimeModelRefsEquivalent, + shouldPreferActiveRuntimeAliasAuthLabel, +} from "../agents/model-runtime-aliases.js"; import { buildModelAliasIndex, resolveConfiguredModelRef, @@ -925,8 +928,15 @@ export function buildStatusMessage(args: StatusArgs): string { activeAuthMode && activeAuthMode !== "unknown" ? (args.activeModelAuth ?? activeAuthMode) : undefined; - const selectedAuthLabelValue = - rawSelectedAuthLabelValue ?? (runtimeAliasModelEquivalent ? activeAuthLabelValue : undefined); + const preferActiveAuthLabel = shouldPreferActiveRuntimeAliasAuthLabel({ + runtimeAliasModelEquivalent, + selectedAuthLabel: rawSelectedAuthLabelValue, + activeAuthLabel: activeAuthLabelValue, + }); + const selectedAuthLabelValue = preferActiveAuthLabel + ? activeAuthLabelValue + : (rawSelectedAuthLabelValue ?? + (runtimeAliasModelEquivalent ? activeAuthLabelValue : undefined)); const fallbackState = resolveActiveFallbackState({ selectedModelRef: selectedModelLabel, activeModelRef: activeModelLabel, diff --git a/src/status/status-text.ts b/src/status/status-text.ts index f80a7262aba..cca41115009 100644 --- a/src/status/status-text.ts +++ b/src/status/status-text.ts @@ -10,7 +10,10 @@ import { import { resolveContextTokensForModel } from "../agents/context.js"; import { resolveFastModeState } from "../agents/fast-mode.js"; import { resolveModelAuthLabel } from "../agents/model-auth-label.js"; -import { areRuntimeModelRefsEquivalent } from "../agents/model-runtime-aliases.js"; +import { + areRuntimeModelRefsEquivalent, + shouldPreferActiveRuntimeAliasAuthLabel, +} from "../agents/model-runtime-aliases.js"; import { resolveDefaultModelForAgent } from "../agents/model-selection.js"; import { listOpenAIAuthProfileProvidersForAgentRuntime } from "../agents/openai-codex-routing.js"; import { @@ -280,10 +283,11 @@ export async function buildStatusText(params: BuildStatusTextParams): Promise