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 - TUI: refresh status bar after think/verbose/reasoning changes. (#519) — thanks @jdrhyne
- Status: show Verbose/Elevated only when enabled. - Status: show Verbose/Elevated only when enabled.
- Status: filter usage summary to the active model provider. - 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: allow /elevated off in groups without a mention; keep /elevated on mention-gated.
- Commands: keep multi-directive messages from clearing directive handling. - Commands: keep multi-directive messages from clearing directive handling.
- Commands: warn when /elevated runs in direct (unsandboxed) runtime. - 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 () => { it("acks queue directive and persists override", async () => {
await withTempHome(async (home) => { await withTempHome(async (home) => {
vi.mocked(runEmbeddedPiAgent).mockReset(); vi.mocked(runEmbeddedPiAgent).mockReset();

View File

@@ -332,7 +332,11 @@ export async function getReplyFromConfig(
let parsedDirectives = parseInlineDirectives(rawBody, { let parsedDirectives = parseInlineDirectives(rawBody, {
modelAliases: configuredAliases, modelAliases: configuredAliases,
}); });
if (isGroup && ctx.WasMentioned !== true && parsedDirectives.hasElevatedDirective) { if (
isGroup &&
ctx.WasMentioned !== true &&
parsedDirectives.hasElevatedDirective
) {
if (parsedDirectives.elevatedLevel !== "off") { if (parsedDirectives.elevatedLevel !== "off") {
parsedDirectives = { parsedDirectives = {
...parsedDirectives, ...parsedDirectives,
@@ -358,7 +362,6 @@ export async function getReplyFromConfig(
if (noMentions.trim().length > 0) { if (noMentions.trim().length > 0) {
const directiveOnlyCheck = parseInlineDirectives(noMentions, { const directiveOnlyCheck = parseInlineDirectives(noMentions, {
modelAliases: configuredAliases, modelAliases: configuredAliases,
disableElevated: disableElevatedInGroup,
}); });
if (directiveOnlyCheck.cleaned.trim().length > 0) { if (directiveOnlyCheck.cleaned.trim().length > 0) {
parsedDirectives = clearInlineDirectives(parsedDirectives.cleaned); parsedDirectives = clearInlineDirectives(parsedDirectives.cleaned);

View File

@@ -6,6 +6,7 @@ import {
getCustomProviderApiKey, getCustomProviderApiKey,
resolveEnvApiKey, resolveEnvApiKey,
} from "../../agents/model-auth.js"; } from "../../agents/model-auth.js";
import { normalizeProviderId } from "../../agents/model-selection.js";
import { import {
abortEmbeddedPiRun, abortEmbeddedPiRun,
compactEmbeddedPiSession, compactEmbeddedPiSession,
@@ -23,6 +24,7 @@ import { logVerbose } from "../../globals.js";
import { import {
formatUsageSummaryLine, formatUsageSummaryLine,
loadProviderUsageSummary, loadProviderUsageSummary,
type UsageProviderId,
} from "../../infra/provider-usage.js"; } from "../../infra/provider-usage.js";
import { import {
scheduleGatewaySigusr1Restart, scheduleGatewaySigusr1Restart,
@@ -37,7 +39,6 @@ import {
normalizeCommandBody, normalizeCommandBody,
shouldHandleTextCommands, shouldHandleTextCommands,
} from "../commands-registry.js"; } from "../commands-registry.js";
import { normalizeProviderId } from "../../agents/model-selection.js";
import { import {
normalizeGroupActivation, normalizeGroupActivation,
parseActivationCommand, parseActivationCommand,
@@ -63,6 +64,22 @@ 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,
@@ -423,11 +440,14 @@ export async function handleCommands(params: {
} }
let usageLine: string | null = null; let usageLine: string | null = null;
try { try {
const usageSummary = await loadProviderUsageSummary({ const usageProvider = resolveUsageProviderId(provider);
timeoutMs: 3500, if (usageProvider) {
providers: [normalizeProviderId(provider)], const usageSummary = await loadProviderUsageSummary({
}); timeoutMs: 3500,
usageLine = formatUsageSummaryLine(usageSummary, { now: Date.now() }); providers: [usageProvider],
});
usageLine = formatUsageSummaryLine(usageSummary, { now: Date.now() });
}
} catch { } catch {
usageLine = null; usageLine = null;
} }

View File

@@ -79,6 +79,24 @@ describe("buildStatusMessage", () => {
expect(text).toContain("Queue: collect"); 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", () => { it("prefers model overrides over last-run model", () => {
const text = buildStatusMessage({ const text = buildStatusMessage({
agent: { agent: {