diff --git a/src/auto-reply/reply.directive.directive-behavior.shows-current-verbose-level-verbose-has-no.test.ts b/src/auto-reply/reply.directive.directive-behavior.shows-current-verbose-level-verbose-has-no.test.ts index bda2c08c975..67b1d71a89c 100644 --- a/src/auto-reply/reply.directive.directive-behavior.shows-current-verbose-level-verbose-has-no.test.ts +++ b/src/auto-reply/reply.directive.directive-behavior.shows-current-verbose-level-verbose-has-no.test.ts @@ -4,7 +4,6 @@ import type { OpenClawConfig } from "../config/config.js"; import { loadSessionStore } from "../config/sessions.js"; import { AUTHORIZED_WHATSAPP_COMMAND, - assertElevatedOffStatusReply, installDirectiveBehaviorE2EHooks, makeElevatedDirectiveConfig, makeRestrictedElevatedDisabledConfig, @@ -179,27 +178,6 @@ describe("directive behavior", () => { expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled(); }); }); - it("persists fast toggles across /status and /fast", async () => { - await withTempHome(async (home) => { - const storePath = sessionStorePath(home); - - const onText = await runCommand(home, "/fast on"); - expect(onText).toContain("Fast mode enabled"); - expect(loadSessionStore(storePath)["agent:main:main"]?.fastMode).toBe(true); - - const statusText = await runCommand(home, "/status"); - const optionsLine = statusText?.split("\n").find((line) => line.trim().startsWith("⚙️")); - expect(optionsLine).toContain("Fast: on"); - - const offText = await runCommand(home, "/fast off"); - expect(offText).toContain("Fast mode disabled"); - expect(loadSessionStore(storePath)["agent:main:main"]?.fastMode).toBe(false); - - const fastText = await runCommand(home, "/fast"); - expect(fastText).toContain("Current fast mode: off"); - expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled(); - }); - }); it("treats /fast status like the no-argument status query", async () => { await withTempHome(async (home) => { const statusText = await runCommand(home, "/fast status", { @@ -217,29 +195,6 @@ describe("directive behavior", () => { expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled(); }); }); - it("persists elevated toggles across /status and /elevated", async () => { - await withTempHome(async (home) => { - const storePath = sessionStorePath(home); - - const offStatusText = replyText(await runElevatedCommand(home, "/elevated off\n/status")); - expect(offStatusText).toContain("Session: agent:main:main"); - assertElevatedOffStatusReply(offStatusText); - - const offLevelText = replyText(await runElevatedCommand(home, "/elevated")); - expect(offLevelText).toContain("Current elevated level: off"); - expect(loadSessionStore(storePath)["agent:main:main"]?.elevatedLevel).toBe("off"); - - await runElevatedCommand(home, "/elevated on"); - const onStatusText = replyText(await runElevatedCommand(home, "/status")); - const optionsLine = onStatusText?.split("\n").find((line) => line.trim().startsWith("⚙️")); - expect(optionsLine).toBeTruthy(); - expect(optionsLine).toContain("elevated"); - - const store = loadSessionStore(storePath); - expect(store["agent:main:main"]?.elevatedLevel).toBe("on"); - expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled(); - }); - }); it("enforces per-agent elevated restrictions and status visibility", async () => { await withTempHome(async (home) => { const deniedRes = await getReplyFromConfig( @@ -258,21 +213,6 @@ describe("directive behavior", () => { const deniedText = replyText(deniedRes); expect(deniedText).toContain("agents.list[].tools.elevated.enabled"); - const statusRes = await getReplyFromConfig( - { - Body: "/status", - From: "+1222", - To: "+1222", - Provider: "whatsapp", - SenderE164: "+1222", - SessionKey: "agent:restricted:main", - CommandAuthorized: true, - }, - {}, - makeRestrictedElevatedDisabledConfig(home) as unknown as OpenClawConfig, - ); - const statusText = replyText(statusRes); - expect(statusText).not.toContain("elevated"); expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled(); }); }); @@ -376,37 +316,4 @@ describe("directive behavior", () => { expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled(); }); }); - it("strips inline elevated directives from the user text (does not persist session override)", async () => { - await withTempHome(async (home) => { - runEmbeddedPiAgentMock.mockResolvedValue({ - payloads: [{ text: "ok" }], - meta: { - durationMs: 1, - agentMeta: { sessionId: "s", provider: "p", model: "m" }, - }, - }); - const storePath = sessionStorePath(home); - - await getReplyFromConfig( - { - Body: "hello there /elevated off", - From: "+1222", - To: "+1222", - Provider: "whatsapp", - SenderE164: "+1222", - }, - {}, - makeElevatedDirectiveConfig(home), - ); - - const store = loadSessionStore(storePath); - expect(store["agent:main:main"]?.elevatedLevel).toBeUndefined(); - - const calls = runEmbeddedPiAgentMock.mock.calls; - expect(calls.length).toBeGreaterThan(0); - const call = calls[0]?.[0]; - expect(call?.prompt).toContain("hello there"); - expect(call?.prompt).not.toContain("/elevated"); - }); - }); }); diff --git a/src/auto-reply/reply/directive-handling.model.test.ts b/src/auto-reply/reply/directive-handling.model.test.ts index 66bd82baa1b..a172a0c53ff 100644 --- a/src/auto-reply/reply/directive-handling.model.test.ts +++ b/src/auto-reply/reply/directive-handling.model.test.ts @@ -622,6 +622,14 @@ describe("handleDirectiveOnly model persist behavior (fixes #1435)", () => { expect(result?.text ?? "").not.toContain("failed"); }); + it("strips inline elevated directives while keeping user text", () => { + const directives = parseInlineDirectives("hello there /elevated off"); + + expect(directives.hasElevatedDirective).toBe(true); + expect(directives.elevatedLevel).toBe("off"); + expect(directives.cleaned).toBe("hello there"); + }); + it("persists thinkingLevel=off (does not clear)", async () => { const directives = parseInlineDirectives("/think off"); const sessionEntry = createSessionEntry({ thinkingLevel: "low" }); @@ -639,6 +647,81 @@ describe("handleDirectiveOnly model persist behavior (fixes #1435)", () => { expect(sessionStore["agent:main:dm:1"]?.thinkingLevel).toBe("off"); }); + it("persists and reports fast-mode directives", async () => { + const sessionEntry = createSessionEntry(); + const sessionStore = { [sessionKey]: sessionEntry }; + + const onReply = await handleDirectiveOnly( + createHandleParams({ + directives: parseInlineDirectives("/fast on"), + sessionEntry, + sessionStore, + }), + ); + expect(onReply?.text).toContain("Fast mode enabled"); + expect(sessionEntry.fastMode).toBe(true); + + const statusReply = await handleDirectiveOnly( + createHandleParams({ + directives: parseInlineDirectives("/fast"), + sessionEntry, + sessionStore, + currentFastMode: sessionEntry.fastMode, + }), + ); + expect(statusReply?.text).toContain("Current fast mode: on"); + + const offReply = await handleDirectiveOnly( + createHandleParams({ + directives: parseInlineDirectives("/fast off"), + sessionEntry, + sessionStore, + currentFastMode: sessionEntry.fastMode, + }), + ); + expect(offReply?.text).toContain("Fast mode disabled"); + expect(sessionEntry.fastMode).toBe(false); + }); + + it("persists and reports elevated-mode directives when allowed", async () => { + const sessionEntry = createSessionEntry(); + const sessionStore = { [sessionKey]: sessionEntry }; + const base = { + elevatedAllowed: true, + elevatedEnabled: true, + sessionEntry, + sessionStore, + } satisfies Partial; + + const onReply = await handleDirectiveOnly( + createHandleParams({ + ...base, + directives: parseInlineDirectives("/elevated on"), + }), + ); + expect(onReply?.text).toContain("Elevated mode set to ask"); + expect(sessionEntry.elevatedLevel).toBe("on"); + + const statusReply = await handleDirectiveOnly( + createHandleParams({ + ...base, + directives: parseInlineDirectives("/elevated"), + currentElevatedLevel: sessionEntry.elevatedLevel, + }), + ); + expect(statusReply?.text).toContain("Current elevated level: on"); + + const offReply = await handleDirectiveOnly( + createHandleParams({ + ...base, + directives: parseInlineDirectives("/elevated off"), + currentElevatedLevel: sessionEntry.elevatedLevel, + }), + ); + expect(offReply?.text).toContain("Elevated mode disabled"); + expect(sessionEntry.elevatedLevel).toBe("off"); + }); + it("blocks internal operator.write exec persistence in directive-only handling", async () => { const directives = parseInlineDirectives( "/exec host=node security=allowlist ask=always node=worker-1",