fix: keep /status usage filtering

This commit is contained in:
Peter Steinberger
2026-01-09 03:30:04 +01:00
parent 151523f47b
commit 3c79d5c711
5 changed files with 58 additions and 76 deletions

View File

@@ -24,6 +24,7 @@
- Auto-reply: block reply ordering fix (duplicate PR superseded by #503). (#483) — thanks @AbhisekBasu1 - 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: avoid splitting outbound chunks inside parentheses. (#499) — thanks @philipp-spiess
- Auto-reply: preserve spacing when stripping inline directives. (#539) — thanks @joshp123 - 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: 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). - 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 - macOS: package ClawdbotKit resources and Swift 6.2 compatibility dylib to avoid launch/tool crashes. (#473) — thanks @gupsammy

View File

@@ -20,6 +20,7 @@ const usageMocks = vi.hoisted(() => ({
providers: [], providers: [],
}), }),
formatUsageSummaryLine: vi.fn().mockReturnValue("📊 Usage: Claude 80% left"), formatUsageSummaryLine: vi.fn().mockReturnValue("📊 Usage: Claude 80% left"),
resolveUsageProviderId: vi.fn((provider: string) => provider.split("/")[0]),
})); }));
vi.mock("../infra/provider-usage.js", () => usageMocks); vi.mock("../infra/provider-usage.js", () => usageMocks);

View File

@@ -346,6 +346,7 @@ export async function getReplyFromConfig(
}; };
} }
} }
const disableElevatedInGroup = isGroup && ctx.WasMentioned !== true;
const hasDirective = const hasDirective =
parsedDirectives.hasThinkDirective || parsedDirectives.hasThinkDirective ||
parsedDirectives.hasVerboseDirective || parsedDirectives.hasVerboseDirective ||

View File

@@ -1,6 +1,11 @@
import crypto from "node:crypto"; import {
import { resolveModelAuthMode } from "../../agents/model-auth.js"; ensureAuthProfileStore,
import { normalizeProviderId } from "../../agents/model-selection.js"; listProfilesForProvider,
} from "../../agents/auth-profiles.js";
import {
getCustomProviderApiKey,
resolveEnvApiKey,
} from "../../agents/model-auth.js";
import { import {
abortEmbeddedPiRun, abortEmbeddedPiRun,
compactEmbeddedPiSession, compactEmbeddedPiSession,
@@ -18,7 +23,7 @@ import { logVerbose } from "../../globals.js";
import { import {
formatUsageSummaryLine, formatUsageSummaryLine,
loadProviderUsageSummary, loadProviderUsageSummary,
type UsageProviderId, resolveUsageProviderId,
} from "../../infra/provider-usage.js"; } from "../../infra/provider-usage.js";
import { import {
scheduleGatewaySigusr1Restart, scheduleGatewaySigusr1Restart,
@@ -49,10 +54,8 @@ import type {
ElevatedLevel, ElevatedLevel,
ReasoningLevel, ReasoningLevel,
ThinkLevel, ThinkLevel,
UsageDisplayLevel,
VerboseLevel, VerboseLevel,
} from "../thinking.js"; } from "../thinking.js";
import { normalizeUsageDisplay } from "../thinking.js";
import type { ReplyPayload } from "../types.js"; import type { ReplyPayload } from "../types.js";
import { isAbortTrigger, setAbortMemory } from "./abort.js"; import { isAbortTrigger, setAbortMemory } from "./abort.js";
import type { InlineDirectives } from "./directive-handling.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 { getFollowupQueueDepth, resolveQueueSettings } from "./queue.js";
import { incrementCompactionCount } from "./session-updates.js"; import { incrementCompactionCount } from "./session-updates.js";
const usageProviderMap: Record<string, UsageProviderId> = {
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( function resolveSessionEntryForKey(
store: Record<string, SessionEntry> | undefined, store: Record<string, SessionEntry> | undefined,
sessionKey: string | undefined, sessionKey: string | undefined,
@@ -105,6 +92,36 @@ export type CommandContext = {
to?: string; 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: { function extractCompactInstructions(params: {
rawBody?: string; rawBody?: string;
ctx: MsgContext; ctx: MsgContext;
@@ -407,13 +424,11 @@ export async function handleCommands(params: {
let usageLine: string | null = null; let usageLine: string | null = null;
try { try {
const usageProvider = resolveUsageProviderId(provider); const usageProvider = resolveUsageProviderId(provider);
if (usageProvider) { const usageSummary = await loadProviderUsageSummary({
const usageSummary = await loadProviderUsageSummary({ timeoutMs: 3500,
timeoutMs: 3500, providers: usageProvider ? [usageProvider] : [],
providers: [usageProvider], });
}); usageLine = formatUsageSummaryLine(usageSummary, { now: Date.now() });
usageLine = formatUsageSummaryLine(usageSummary, { now: Date.now() });
}
} catch { } catch {
usageLine = null; usageLine = null;
} }
@@ -434,7 +449,6 @@ export async function handleCommands(params: {
defaultGroupActivation()) defaultGroupActivation())
: undefined; : undefined;
const statusText = buildStatusMessage({ const statusText = buildStatusMessage({
config: cfg,
agent: { agent: {
...cfg.agent, ...cfg.agent,
model: { model: {
@@ -455,7 +469,7 @@ export async function handleCommands(params: {
resolvedVerbose: resolvedVerboseLevel, resolvedVerbose: resolvedVerboseLevel,
resolvedReasoning: resolvedReasoningLevel, resolvedReasoning: resolvedReasoningLevel,
resolvedElevated: resolvedElevatedLevel, resolvedElevated: resolvedElevatedLevel,
modelAuth: resolveModelAuthMode(provider, cfg), modelAuth: resolveModelAuthLabel(provider, cfg),
usageLine: usageLine ?? undefined, usageLine: usageLine ?? undefined,
queue: { queue: {
mode: queueSettings.mode, mode: queueSettings.mode,
@@ -470,51 +484,6 @@ export async function handleCommands(params: {
return { shouldContinue: false, reply: { text: statusText } }; 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 || "<unknown>"}`,
);
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"; const stopRequested = command.commandBodyNormalized === "/stop";
if (allowTextCommands && stopRequested) { if (allowTextCommands && stopRequested) {
if (!command.isAuthorizedSender) { if (!command.isAuthorizedSender) {

View File

@@ -129,6 +129,16 @@ const usageProviders: UsageProviderId[] = [
"zai", "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([ const ignoredErrors = new Set([
"No credentials", "No credentials",
"No token", "No token",