diff --git a/CHANGELOG.md b/CHANGELOG.md index fe365ca12..e2fb77924 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ - TUI: refresh status bar after think/verbose/reasoning changes. (#519) — thanks @jdrhyne - Status: show Verbose/Elevated only when enabled. - Status: filter usage summary to the active model provider. +- Status: map model providers to usage sources so unrelated usage doesn’t appear. - Commands: allow /elevated off in groups without a mention; keep /elevated on mention-gated. - Commands: keep multi-directive messages from clearing directive handling. - Commands: warn when /elevated runs in direct (unsandboxed) runtime. diff --git a/src/auto-reply/reply.directive.test.ts b/src/auto-reply/reply.directive.test.ts index 32c25f4d9..46b9eba51 100644 --- a/src/auto-reply/reply.directive.test.ts +++ b/src/auto-reply/reply.directive.test.ts @@ -538,6 +538,39 @@ describe("directive behavior", () => { }); }); + it("handles multiple directives in a single message", async () => { + await withTempHome(async (home) => { + vi.mocked(runEmbeddedPiAgent).mockReset(); + + const res = await getReplyFromConfig( + { + Body: "/elevated off\n/verbose on", + From: "+1222", + To: "+1222", + Provider: "whatsapp", + SenderE164: "+1222", + }, + {}, + { + agent: { + model: "anthropic/claude-opus-4-5", + workspace: path.join(home, "clawd"), + elevated: { + allowFrom: { whatsapp: ["+1222"] }, + }, + }, + whatsapp: { allowFrom: ["+1222"] }, + session: { store: path.join(home, "sessions.json") }, + }, + ); + + const text = Array.isArray(res) ? res[0]?.text : res?.text; + expect(text).toContain("Elevated mode disabled."); + expect(text).toContain("Verbose logging enabled."); + expect(runEmbeddedPiAgent).not.toHaveBeenCalled(); + }); + }); + it("acks queue directive and persists override", async () => { await withTempHome(async (home) => { vi.mocked(runEmbeddedPiAgent).mockReset(); diff --git a/src/auto-reply/reply.ts b/src/auto-reply/reply.ts index de038bdfb..a1fed6633 100644 --- a/src/auto-reply/reply.ts +++ b/src/auto-reply/reply.ts @@ -332,7 +332,11 @@ export async function getReplyFromConfig( let parsedDirectives = parseInlineDirectives(rawBody, { modelAliases: configuredAliases, }); - if (isGroup && ctx.WasMentioned !== true && parsedDirectives.hasElevatedDirective) { + if ( + isGroup && + ctx.WasMentioned !== true && + parsedDirectives.hasElevatedDirective + ) { if (parsedDirectives.elevatedLevel !== "off") { parsedDirectives = { ...parsedDirectives, @@ -358,7 +362,6 @@ export async function getReplyFromConfig( if (noMentions.trim().length > 0) { const directiveOnlyCheck = parseInlineDirectives(noMentions, { modelAliases: configuredAliases, - disableElevated: disableElevatedInGroup, }); if (directiveOnlyCheck.cleaned.trim().length > 0) { parsedDirectives = clearInlineDirectives(parsedDirectives.cleaned); diff --git a/src/auto-reply/reply/commands.ts b/src/auto-reply/reply/commands.ts index 4435ef5c9..fba52f153 100644 --- a/src/auto-reply/reply/commands.ts +++ b/src/auto-reply/reply/commands.ts @@ -6,6 +6,7 @@ import { getCustomProviderApiKey, resolveEnvApiKey, } from "../../agents/model-auth.js"; +import { normalizeProviderId } from "../../agents/model-selection.js"; import { abortEmbeddedPiRun, compactEmbeddedPiSession, @@ -23,6 +24,7 @@ import { logVerbose } from "../../globals.js"; import { formatUsageSummaryLine, loadProviderUsageSummary, + type UsageProviderId, } from "../../infra/provider-usage.js"; import { scheduleGatewaySigusr1Restart, @@ -37,7 +39,6 @@ import { normalizeCommandBody, shouldHandleTextCommands, } from "../commands-registry.js"; -import { normalizeProviderId } from "../../agents/model-selection.js"; import { normalizeGroupActivation, parseActivationCommand, @@ -63,6 +64,22 @@ 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, @@ -423,11 +440,14 @@ export async function handleCommands(params: { } let usageLine: string | null = null; try { - const usageSummary = await loadProviderUsageSummary({ - timeoutMs: 3500, - providers: [normalizeProviderId(provider)], - }); - usageLine = formatUsageSummaryLine(usageSummary, { now: Date.now() }); + const usageProvider = resolveUsageProviderId(provider); + if (usageProvider) { + const usageSummary = await loadProviderUsageSummary({ + timeoutMs: 3500, + providers: [usageProvider], + }); + usageLine = formatUsageSummaryLine(usageSummary, { now: Date.now() }); + } } catch { usageLine = null; } diff --git a/src/auto-reply/status.test.ts b/src/auto-reply/status.test.ts index 7ac76b62c..fe6035c88 100644 --- a/src/auto-reply/status.test.ts +++ b/src/auto-reply/status.test.ts @@ -79,6 +79,24 @@ describe("buildStatusMessage", () => { expect(text).toContain("Queue: collect"); }); + it("shows verbose/elevated labels only when enabled", () => { + const text = buildStatusMessage({ + agent: { model: "anthropic/claude-opus-4-5" }, + sessionEntry: { sessionId: "v1", updatedAt: 0 }, + sessionKey: "agent:main:main", + sessionScope: "per-sender", + resolvedThink: "low", + resolvedVerbose: "on", + resolvedElevated: "on", + queue: { mode: "collect", depth: 0 }, + }); + + expect(text).toContain("Verbose"); + expect(text).toContain("Elevated"); + expect(text).not.toContain("Verbose:"); + expect(text).not.toContain("Elevated:"); + }); + it("prefers model overrides over last-run model", () => { const text = buildStatusMessage({ agent: {