chore: log elevated and reasoning toggles
This commit is contained in:
@@ -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.
|
- 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.
|
- 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.
|
- 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
|
## 2026.1.11
|
||||||
|
|
||||||
|
|||||||
@@ -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 () => {
|
it("ignores inline /model and uses the default model", async () => {
|
||||||
await withTempHome(async (home) => {
|
await withTempHome(async (home) => {
|
||||||
const storePath = path.join(home, "sessions.json");
|
const storePath = path.join(home, "sessions.json");
|
||||||
|
|||||||
@@ -68,6 +68,17 @@ const withOptions = (line: string, options: string) =>
|
|||||||
const formatElevatedRuntimeHint = () =>
|
const formatElevatedRuntimeHint = () =>
|
||||||
`${SYSTEM_MARK} Runtime is direct; sandboxing does not apply.`;
|
`${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: {
|
function formatElevatedUnavailableText(params: {
|
||||||
runtimeSandboxed: boolean;
|
runtimeSandboxed: boolean;
|
||||||
failures?: Array<{ gate: string; key: string }>;
|
failures?: Array<{ gate: string; key: string }>;
|
||||||
@@ -1070,6 +1081,22 @@ export async function handleDirectiveOnly(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sessionEntry && sessionStore && sessionKey) {
|
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.hasThinkDirective && directives.thinkLevel) {
|
||||||
if (directives.thinkLevel === "off") delete sessionEntry.thinkingLevel;
|
if (directives.thinkLevel === "off") delete sessionEntry.thinkingLevel;
|
||||||
else sessionEntry.thinkingLevel = directives.thinkLevel;
|
else sessionEntry.thinkingLevel = directives.thinkLevel;
|
||||||
@@ -1081,11 +1108,18 @@ export async function handleDirectiveOnly(params: {
|
|||||||
if (directives.reasoningLevel === "off")
|
if (directives.reasoningLevel === "off")
|
||||||
delete sessionEntry.reasoningLevel;
|
delete sessionEntry.reasoningLevel;
|
||||||
else sessionEntry.reasoningLevel = directives.reasoningLevel;
|
else sessionEntry.reasoningLevel = directives.reasoningLevel;
|
||||||
|
reasoningChanged =
|
||||||
|
directives.reasoningLevel !== prevReasoningLevel &&
|
||||||
|
directives.reasoningLevel !== undefined;
|
||||||
}
|
}
|
||||||
if (directives.hasElevatedDirective && directives.elevatedLevel) {
|
if (directives.hasElevatedDirective && directives.elevatedLevel) {
|
||||||
// Unlike other toggles, elevated defaults can be "on".
|
// Unlike other toggles, elevated defaults can be "on".
|
||||||
// Persist "off" explicitly so `/elevated off` actually overrides defaults.
|
// Persist "off" explicitly so `/elevated off` actually overrides defaults.
|
||||||
sessionEntry.elevatedLevel = directives.elevatedLevel;
|
sessionEntry.elevatedLevel = directives.elevatedLevel;
|
||||||
|
elevatedChanged =
|
||||||
|
elevatedChanged ||
|
||||||
|
(directives.elevatedLevel !== prevElevatedLevel &&
|
||||||
|
directives.elevatedLevel !== undefined);
|
||||||
}
|
}
|
||||||
if (modelSelection) {
|
if (modelSelection) {
|
||||||
if (modelSelection.isDefault) {
|
if (modelSelection.isDefault) {
|
||||||
@@ -1123,6 +1157,22 @@ export async function handleDirectiveOnly(params: {
|
|||||||
if (storePath) {
|
if (storePath) {
|
||||||
await saveSessionStore(storePath, sessionStore);
|
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[] = [];
|
const parts: string[] = [];
|
||||||
@@ -1240,6 +1290,20 @@ export async function persistInlineDirectives(params: {
|
|||||||
const agentDir = resolveAgentDir(cfg, activeAgentId);
|
const agentDir = resolveAgentDir(cfg, activeAgentId);
|
||||||
|
|
||||||
if (sessionEntry && sessionStore && sessionKey) {
|
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;
|
let updated = false;
|
||||||
if (directives.hasThinkDirective && directives.thinkLevel) {
|
if (directives.hasThinkDirective && directives.thinkLevel) {
|
||||||
if (directives.thinkLevel === "off") {
|
if (directives.thinkLevel === "off") {
|
||||||
@@ -1259,6 +1323,10 @@ export async function persistInlineDirectives(params: {
|
|||||||
} else {
|
} else {
|
||||||
sessionEntry.reasoningLevel = directives.reasoningLevel;
|
sessionEntry.reasoningLevel = directives.reasoningLevel;
|
||||||
}
|
}
|
||||||
|
reasoningChanged =
|
||||||
|
reasoningChanged ||
|
||||||
|
(directives.reasoningLevel !== prevReasoningLevel &&
|
||||||
|
directives.reasoningLevel !== undefined);
|
||||||
updated = true;
|
updated = true;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@@ -1269,6 +1337,10 @@ export async function persistInlineDirectives(params: {
|
|||||||
) {
|
) {
|
||||||
// Persist "off" explicitly so inline `/elevated off` overrides defaults.
|
// Persist "off" explicitly so inline `/elevated off` overrides defaults.
|
||||||
sessionEntry.elevatedLevel = directives.elevatedLevel;
|
sessionEntry.elevatedLevel = directives.elevatedLevel;
|
||||||
|
elevatedChanged =
|
||||||
|
elevatedChanged ||
|
||||||
|
(directives.elevatedLevel !== prevElevatedLevel &&
|
||||||
|
directives.elevatedLevel !== undefined);
|
||||||
updated = true;
|
updated = true;
|
||||||
}
|
}
|
||||||
const modelDirective =
|
const modelDirective =
|
||||||
@@ -1341,6 +1413,22 @@ export async function persistInlineDirectives(params: {
|
|||||||
if (storePath) {
|
if (storePath) {
|
||||||
await saveSessionStore(storePath, sessionStore);
|
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",
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user