fix: improve status usage filtering and directives

This commit is contained in:
Peter Steinberger
2026-01-09 03:18:41 +01:00
parent 84f668f9c5
commit 1a295d9460
5 changed files with 83 additions and 8 deletions

View File

@@ -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 doesnt 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.

View File

@@ -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();

View File

@@ -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);

View File

@@ -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<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(
store: Record<string, SessionEntry> | 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;
}

View File

@@ -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: {