From 3c79d5c711c0bb9c287d66c0593a2b999d97c803 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 9 Jan 2026 03:30:04 +0100 Subject: [PATCH] fix: keep /status usage filtering --- CHANGELOG.md | 1 + src/auto-reply/reply.triggers.test.ts | 1 + src/auto-reply/reply.ts | 1 + src/auto-reply/reply/commands.ts | 121 ++++++++++---------------- src/infra/provider-usage.ts | 10 +++ 5 files changed, 58 insertions(+), 76 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74de86ff3..5e6fcd162 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ - Auto-reply: block reply ordering fix (duplicate PR superseded by #503). (#483) — thanks @AbhisekBasu1 - Auto-reply: avoid splitting outbound chunks inside parentheses. (#499) — thanks @philipp-spiess - Auto-reply: preserve spacing when stripping inline directives. (#539) — thanks @joshp123 +- Auto-reply: fix /status usage summary filtering for the active provider. - Status: show provider prefix in /status model display. (#506) — thanks @mcinteerj - Status: compact /status with session token usage + estimated cost, add `/cost` per-response usage lines (tokens-only for OAuth). - macOS: package ClawdbotKit resources and Swift 6.2 compatibility dylib to avoid launch/tool crashes. (#473) — thanks @gupsammy diff --git a/src/auto-reply/reply.triggers.test.ts b/src/auto-reply/reply.triggers.test.ts index da3a52c33..721139de6 100644 --- a/src/auto-reply/reply.triggers.test.ts +++ b/src/auto-reply/reply.triggers.test.ts @@ -20,6 +20,7 @@ const usageMocks = vi.hoisted(() => ({ providers: [], }), formatUsageSummaryLine: vi.fn().mockReturnValue("📊 Usage: Claude 80% left"), + resolveUsageProviderId: vi.fn((provider: string) => provider.split("/")[0]), })); vi.mock("../infra/provider-usage.js", () => usageMocks); diff --git a/src/auto-reply/reply.ts b/src/auto-reply/reply.ts index a1fed6633..e194e7d47 100644 --- a/src/auto-reply/reply.ts +++ b/src/auto-reply/reply.ts @@ -346,6 +346,7 @@ export async function getReplyFromConfig( }; } } + const disableElevatedInGroup = isGroup && ctx.WasMentioned !== true; const hasDirective = parsedDirectives.hasThinkDirective || parsedDirectives.hasVerboseDirective || diff --git a/src/auto-reply/reply/commands.ts b/src/auto-reply/reply/commands.ts index 14289f6c9..2deb1a753 100644 --- a/src/auto-reply/reply/commands.ts +++ b/src/auto-reply/reply/commands.ts @@ -1,6 +1,11 @@ -import crypto from "node:crypto"; -import { resolveModelAuthMode } from "../../agents/model-auth.js"; -import { normalizeProviderId } from "../../agents/model-selection.js"; +import { + ensureAuthProfileStore, + listProfilesForProvider, +} from "../../agents/auth-profiles.js"; +import { + getCustomProviderApiKey, + resolveEnvApiKey, +} from "../../agents/model-auth.js"; import { abortEmbeddedPiRun, compactEmbeddedPiSession, @@ -18,7 +23,7 @@ import { logVerbose } from "../../globals.js"; import { formatUsageSummaryLine, loadProviderUsageSummary, - type UsageProviderId, + resolveUsageProviderId, } from "../../infra/provider-usage.js"; import { scheduleGatewaySigusr1Restart, @@ -49,10 +54,8 @@ import type { ElevatedLevel, ReasoningLevel, ThinkLevel, - UsageDisplayLevel, VerboseLevel, } from "../thinking.js"; -import { normalizeUsageDisplay } from "../thinking.js"; import type { ReplyPayload } from "../types.js"; import { isAbortTrigger, setAbortMemory } from "./abort.js"; import type { InlineDirectives } from "./directive-handling.js"; @@ -60,22 +63,6 @@ import { stripMentions, stripStructuralPrefixes } from "./mentions.js"; import { getFollowupQueueDepth, resolveQueueSettings } from "./queue.js"; import { incrementCompactionCount } from "./session-updates.js"; -const usageProviderMap: Record = { - anthropic: "anthropic", - "github-copilot": "github-copilot", - "google-antigravity": "google-antigravity", - "google-gemini-cli": "google-gemini-cli", - google: "google-gemini-cli", - openai: "openai-codex", - "openai-codex": "openai-codex", - zai: "zai", -}; - -function resolveUsageProviderId(provider: string): UsageProviderId | undefined { - const normalized = normalizeProviderId(provider); - return usageProviderMap[normalized]; -} - function resolveSessionEntryForKey( store: Record | undefined, sessionKey: string | undefined, @@ -105,6 +92,36 @@ export type CommandContext = { to?: string; }; +function resolveModelAuthLabel( + provider?: string, + cfg?: ClawdbotConfig, +): string | undefined { + const resolved = provider?.trim(); + if (!resolved) return undefined; + + const store = ensureAuthProfileStore(); + const profiles = listProfilesForProvider(store, resolved); + if (profiles.length > 0) { + const modes = new Set( + profiles + .map((id) => store.profiles[id]?.type) + .filter((mode): mode is "api_key" | "oauth" => Boolean(mode)), + ); + if (modes.has("oauth") && modes.has("api_key")) return "mixed"; + if (modes.has("oauth")) return "oauth"; + if (modes.has("api_key")) return "api-key"; + } + + const envKey = resolveEnvApiKey(resolved); + if (envKey?.apiKey) { + return envKey.source.includes("OAUTH_TOKEN") ? "oauth" : "api-key"; + } + + if (getCustomProviderApiKey(cfg, resolved)) return "api-key"; + + return "unknown"; +} + function extractCompactInstructions(params: { rawBody?: string; ctx: MsgContext; @@ -407,13 +424,11 @@ export async function handleCommands(params: { let usageLine: string | null = null; try { const usageProvider = resolveUsageProviderId(provider); - if (usageProvider) { - const usageSummary = await loadProviderUsageSummary({ - timeoutMs: 3500, - providers: [usageProvider], - }); - usageLine = formatUsageSummaryLine(usageSummary, { now: Date.now() }); - } + const usageSummary = await loadProviderUsageSummary({ + timeoutMs: 3500, + providers: usageProvider ? [usageProvider] : [], + }); + usageLine = formatUsageSummaryLine(usageSummary, { now: Date.now() }); } catch { usageLine = null; } @@ -434,7 +449,6 @@ export async function handleCommands(params: { defaultGroupActivation()) : undefined; const statusText = buildStatusMessage({ - config: cfg, agent: { ...cfg.agent, model: { @@ -455,7 +469,7 @@ export async function handleCommands(params: { resolvedVerbose: resolvedVerboseLevel, resolvedReasoning: resolvedReasoningLevel, resolvedElevated: resolvedElevatedLevel, - modelAuth: resolveModelAuthMode(provider, cfg), + modelAuth: resolveModelAuthLabel(provider, cfg), usageLine: usageLine ?? undefined, queue: { mode: queueSettings.mode, @@ -470,51 +484,6 @@ export async function handleCommands(params: { return { shouldContinue: false, reply: { text: statusText } }; } - const costRequested = - command.commandBodyNormalized === "/cost" || - command.commandBodyNormalized.startsWith("/cost "); - if (allowTextCommands && costRequested) { - if (!command.isAuthorizedSender) { - logVerbose( - `Ignoring /cost from unauthorized sender: ${command.senderE164 || ""}`, - ); - return { shouldContinue: false }; - } - const rawArgs = command.commandBodyNormalized.slice("/cost".length).trim(); - const normalized = - rawArgs.length > 0 ? normalizeUsageDisplay(rawArgs) : undefined; - if (rawArgs.length > 0 && !normalized) { - return { - shouldContinue: false, - reply: { text: "⚙️ Usage: /cost on|off" }, - }; - } - const current: UsageDisplayLevel = - sessionEntry?.responseUsage === "on" ? "on" : "off"; - const next = normalized ?? (current === "on" ? "off" : "on"); - if (sessionStore && sessionKey) { - const entry = sessionEntry ?? - sessionStore[sessionKey] ?? { - sessionId: crypto.randomUUID(), - updatedAt: Date.now(), - }; - if (next === "off") delete entry.responseUsage; - else entry.responseUsage = next; - entry.updatedAt = Date.now(); - sessionStore[sessionKey] = entry; - if (storePath) { - await saveSessionStore(storePath, sessionStore); - } - } - return { - shouldContinue: false, - reply: { - text: - next === "on" ? "⚙️ Usage line enabled." : "⚙️ Usage line disabled.", - }, - }; - } - const stopRequested = command.commandBodyNormalized === "/stop"; if (allowTextCommands && stopRequested) { if (!command.isAuthorizedSender) { diff --git a/src/infra/provider-usage.ts b/src/infra/provider-usage.ts index 11cab33cc..bc3e220a3 100644 --- a/src/infra/provider-usage.ts +++ b/src/infra/provider-usage.ts @@ -129,6 +129,16 @@ const usageProviders: UsageProviderId[] = [ "zai", ]; +export function resolveUsageProviderId( + provider?: string | null, +): UsageProviderId | undefined { + if (!provider) return undefined; + const normalized = normalizeProviderId(provider); + return usageProviders.includes(normalized as UsageProviderId) + ? (normalized as UsageProviderId) + : undefined; +} + const ignoredErrors = new Set([ "No credentials", "No token",