chore: log elevated and reasoning toggles

This commit is contained in:
Peter Steinberger
2026-01-12 18:37:44 +00:00
parent 1baf9f6a83
commit ffbcd83d1e
3 changed files with 150 additions and 0 deletions

View File

@@ -29,6 +29,7 @@
- Auto-reply: restore 300-char heartbeat ack limit and keep >300 char replies instead of dropping them; adjust long heartbeat test content accordingly.
- Gateway: `agents.list` now honors explicit `agents.list` config without pulling stray agents from disk; GitHub Copilot CLI auth path uses the updated provider build.
- Google: apply patched pi-ai `google-gemini-cli` function call handling (strips ids) after upgrading to pi-ai 0.43.0.
- Auto-reply: elevated/reasoning toggles now enqueue system events so the model sees the mode change immediately.
## 2026.1.11

View File

@@ -1913,6 +1913,67 @@ describe("directive behavior", () => {
});
});
it("queues a system event when toggling elevated", async () => {
await withTempHome(async (home) => {
drainSystemEvents(MAIN_SESSION_KEY);
const storePath = path.join(home, "sessions.json");
await getReplyFromConfig(
{
Body: "/elevated on",
From: "+1222",
To: "+1222",
Provider: "whatsapp",
},
{},
{
agents: {
defaults: {
model: { primary: "openai/gpt-4.1-mini" },
workspace: path.join(home, "clawd"),
},
},
tools: { elevated: { allowFrom: { whatsapp: ["*"] } } },
whatsapp: { allowFrom: ["*"] },
session: { store: storePath },
},
);
const events = drainSystemEvents(MAIN_SESSION_KEY);
expect(events.some((e) => e.includes("Elevated ON"))).toBe(true);
});
});
it("queues a system event when toggling reasoning", async () => {
await withTempHome(async (home) => {
drainSystemEvents(MAIN_SESSION_KEY);
const storePath = path.join(home, "sessions.json");
await getReplyFromConfig(
{
Body: "/reasoning stream",
From: "+1222",
To: "+1222",
Provider: "whatsapp",
},
{},
{
agents: {
defaults: {
model: { primary: "openai/gpt-4.1-mini" },
workspace: path.join(home, "clawd"),
},
},
whatsapp: { allowFrom: ["*"] },
session: { store: storePath },
},
);
const events = drainSystemEvents(MAIN_SESSION_KEY);
expect(events.some((e) => e.includes("Reasoning STREAM"))).toBe(true);
});
});
it("ignores inline /model and uses the default model", async () => {
await withTempHome(async (home) => {
const storePath = path.join(home, "sessions.json");

View File

@@ -68,6 +68,17 @@ const withOptions = (line: string, options: string) =>
const formatElevatedRuntimeHint = () =>
`${SYSTEM_MARK} Runtime is direct; sandboxing does not apply.`;
const formatElevatedEvent = (level: ElevatedLevel) =>
level === "on"
? "Elevated ON — exec runs on host; set elevated:false to stay sandboxed."
: "Elevated OFF — exec stays in sandbox.";
const formatReasoningEvent = (level: ReasoningLevel) => {
if (level === "stream") return "Reasoning STREAM — emit live <think>.";
if (level === "on") return "Reasoning ON — include <think>.";
return "Reasoning OFF — hide <think>.";
};
function formatElevatedUnavailableText(params: {
runtimeSandboxed: boolean;
failures?: Array<{ gate: string; key: string }>;
@@ -1070,6 +1081,22 @@ export async function handleDirectiveOnly(params: {
}
if (sessionEntry && sessionStore && sessionKey) {
const prevElevatedLevel =
currentElevatedLevel ??
(sessionEntry.elevatedLevel as ElevatedLevel | undefined) ??
(elevatedAllowed ? ("on" as ElevatedLevel) : ("off" as ElevatedLevel));
const prevReasoningLevel =
currentReasoningLevel ??
(sessionEntry.reasoningLevel as ReasoningLevel | undefined) ??
"off";
let elevatedChanged =
directives.hasElevatedDirective &&
directives.elevatedLevel !== undefined &&
elevatedEnabled &&
elevatedAllowed;
let reasoningChanged =
directives.hasReasoningDirective &&
directives.reasoningLevel !== undefined;
if (directives.hasThinkDirective && directives.thinkLevel) {
if (directives.thinkLevel === "off") delete sessionEntry.thinkingLevel;
else sessionEntry.thinkingLevel = directives.thinkLevel;
@@ -1081,11 +1108,18 @@ export async function handleDirectiveOnly(params: {
if (directives.reasoningLevel === "off")
delete sessionEntry.reasoningLevel;
else sessionEntry.reasoningLevel = directives.reasoningLevel;
reasoningChanged =
directives.reasoningLevel !== prevReasoningLevel &&
directives.reasoningLevel !== undefined;
}
if (directives.hasElevatedDirective && directives.elevatedLevel) {
// Unlike other toggles, elevated defaults can be "on".
// Persist "off" explicitly so `/elevated off` actually overrides defaults.
sessionEntry.elevatedLevel = directives.elevatedLevel;
elevatedChanged =
elevatedChanged ||
(directives.elevatedLevel !== prevElevatedLevel &&
directives.elevatedLevel !== undefined);
}
if (modelSelection) {
if (modelSelection.isDefault) {
@@ -1123,6 +1157,22 @@ export async function handleDirectiveOnly(params: {
if (storePath) {
await saveSessionStore(storePath, sessionStore);
}
if (elevatedChanged) {
const nextElevated =
(sessionEntry.elevatedLevel ?? "off") as ElevatedLevel;
enqueueSystemEvent(formatElevatedEvent(nextElevated), {
sessionKey,
contextKey: "mode:elevated",
});
}
if (reasoningChanged) {
const nextReasoning =
(sessionEntry.reasoningLevel ?? "off") as ReasoningLevel;
enqueueSystemEvent(formatReasoningEvent(nextReasoning), {
sessionKey,
contextKey: "mode:reasoning",
});
}
}
const parts: string[] = [];
@@ -1240,6 +1290,20 @@ export async function persistInlineDirectives(params: {
const agentDir = resolveAgentDir(cfg, activeAgentId);
if (sessionEntry && sessionStore && sessionKey) {
const prevElevatedLevel =
(sessionEntry.elevatedLevel as ElevatedLevel | undefined) ??
(agentCfg?.elevatedDefault as ElevatedLevel | undefined) ??
(elevatedAllowed ? ("on" as ElevatedLevel) : ("off" as ElevatedLevel));
const prevReasoningLevel =
(sessionEntry.reasoningLevel as ReasoningLevel | undefined) ?? "off";
let elevatedChanged =
directives.hasElevatedDirective &&
directives.elevatedLevel !== undefined &&
elevatedEnabled &&
elevatedAllowed;
let reasoningChanged =
directives.hasReasoningDirective &&
directives.reasoningLevel !== undefined;
let updated = false;
if (directives.hasThinkDirective && directives.thinkLevel) {
if (directives.thinkLevel === "off") {
@@ -1259,6 +1323,10 @@ export async function persistInlineDirectives(params: {
} else {
sessionEntry.reasoningLevel = directives.reasoningLevel;
}
reasoningChanged =
reasoningChanged ||
(directives.reasoningLevel !== prevReasoningLevel &&
directives.reasoningLevel !== undefined);
updated = true;
}
if (
@@ -1269,6 +1337,10 @@ export async function persistInlineDirectives(params: {
) {
// Persist "off" explicitly so inline `/elevated off` overrides defaults.
sessionEntry.elevatedLevel = directives.elevatedLevel;
elevatedChanged =
elevatedChanged ||
(directives.elevatedLevel !== prevElevatedLevel &&
directives.elevatedLevel !== undefined);
updated = true;
}
const modelDirective =
@@ -1341,6 +1413,22 @@ export async function persistInlineDirectives(params: {
if (storePath) {
await saveSessionStore(storePath, sessionStore);
}
if (elevatedChanged) {
const nextElevated =
(sessionEntry.elevatedLevel ?? "off") as ElevatedLevel;
enqueueSystemEvent(formatElevatedEvent(nextElevated), {
sessionKey,
contextKey: "mode:elevated",
});
}
if (reasoningChanged) {
const nextReasoning =
(sessionEntry.reasoningLevel ?? "off") as ReasoningLevel;
enqueueSystemEvent(formatReasoningEvent(nextReasoning), {
sessionKey,
contextKey: "mode:reasoning",
});
}
}
}