test: consolidate directive behavior suites for faster runs
This commit is contained in:
@@ -171,24 +171,18 @@ describe("directive behavior", () => {
|
||||
expect(blockReplies.length).toBe(0);
|
||||
});
|
||||
});
|
||||
it("acks verbose directive immediately with system marker", async () => {
|
||||
it("handles standalone verbose directives and persistence", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const res = await getReplyFromConfig(
|
||||
const storePath = sessionStorePath(home);
|
||||
|
||||
const enabledRes = await getReplyFromConfig(
|
||||
{ Body: "/verbose on", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
makeWhatsAppDirectiveConfig(home, { model: "anthropic/claude-opus-4-5" }),
|
||||
);
|
||||
expect(replyText(enabledRes)).toMatch(/^⚙️ Verbose logging enabled\./);
|
||||
|
||||
const text = replyText(res);
|
||||
expect(text).toMatch(/^⚙️ Verbose logging enabled\./);
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("persists verbose off when directive is standalone", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const storePath = sessionStorePath(home);
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
const disabledRes = await getReplyFromConfig(
|
||||
{ Body: "/verbose off", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
makeWhatsAppDirectiveConfig(
|
||||
@@ -200,7 +194,7 @@ describe("directive behavior", () => {
|
||||
),
|
||||
);
|
||||
|
||||
const text = replyText(res);
|
||||
const text = replyText(disabledRes);
|
||||
expect(text).toMatch(/Verbose logging disabled\./);
|
||||
const store = loadSessionStore(storePath);
|
||||
const entry = Object.values(store)[0];
|
||||
@@ -208,59 +202,46 @@ describe("directive behavior", () => {
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("updates tool verbose during an in-flight run (toggle on)", async () => {
|
||||
it("updates tool verbose during in-flight runs for toggle on/off", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const { res } = await runInFlightVerboseToggleCase({
|
||||
home,
|
||||
shouldEmitBefore: false,
|
||||
toggledVerboseLevel: "on",
|
||||
});
|
||||
|
||||
const texts = replyTexts(res);
|
||||
expect(texts).toContain("done");
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
|
||||
for (const testCase of [
|
||||
{
|
||||
shouldEmitBefore: false,
|
||||
toggledVerboseLevel: "on" as const,
|
||||
},
|
||||
{
|
||||
shouldEmitBefore: true,
|
||||
toggledVerboseLevel: "off" as const,
|
||||
seedVerboseOn: true,
|
||||
},
|
||||
]) {
|
||||
vi.mocked(runEmbeddedPiAgent).mockClear();
|
||||
const { res } = await runInFlightVerboseToggleCase({
|
||||
home,
|
||||
...testCase,
|
||||
});
|
||||
const texts = replyTexts(res);
|
||||
expect(texts).toContain("done");
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
|
||||
}
|
||||
});
|
||||
});
|
||||
it("updates tool verbose during an in-flight run (toggle off)", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const { res } = await runInFlightVerboseToggleCase({
|
||||
home,
|
||||
shouldEmitBefore: true,
|
||||
toggledVerboseLevel: "off",
|
||||
seedVerboseOn: true,
|
||||
});
|
||||
|
||||
const texts = replyTexts(res);
|
||||
expect(texts).toContain("done");
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
it("shows current think level when /think has no argument", async () => {
|
||||
it("covers think status and /thinking xhigh support matrix", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const text = await runThinkDirectiveAndGetText(home);
|
||||
expect(text).toContain("Current thinking level: high");
|
||||
expect(text).toContain("Options: off, minimal, low, medium, high.");
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("accepts /thinking xhigh for codex models", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const texts = await runThinkingDirective(home, "openai-codex/gpt-5.2-codex");
|
||||
expect(texts).toContain("Thinking level set to xhigh.");
|
||||
});
|
||||
});
|
||||
it("accepts /thinking xhigh for openai gpt-5.2", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const texts = await runThinkingDirective(home, "openai/gpt-5.2");
|
||||
expect(texts).toContain("Thinking level set to xhigh.");
|
||||
});
|
||||
});
|
||||
it("rejects /thinking xhigh for non-codex models", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const texts = await runThinkingDirective(home, "openai/gpt-4.1-mini");
|
||||
expect(texts).toContain(
|
||||
|
||||
for (const model of ["openai-codex/gpt-5.2-codex", "openai/gpt-5.2"]) {
|
||||
const texts = await runThinkingDirective(home, model);
|
||||
expect(texts).toContain("Thinking level set to xhigh.");
|
||||
}
|
||||
|
||||
const unsupportedModelTexts = await runThinkingDirective(home, "openai/gpt-4.1-mini");
|
||||
expect(unsupportedModelTexts).toContain(
|
||||
'Thinking level "xhigh" is only supported for openai/gpt-5.2, openai-codex/gpt-5.3-codex, openai-codex/gpt-5.3-codex-spark, openai-codex/gpt-5.2-codex, openai-codex/gpt-5.1-codex, github-copilot/gpt-5.2-codex or github-copilot/gpt-5.2.',
|
||||
);
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("keeps reserved command aliases from matching after trimming", async () => {
|
||||
@@ -325,9 +306,9 @@ describe("directive behavior", () => {
|
||||
expect(prompt).toContain('Use the "demo-skill" skill');
|
||||
});
|
||||
});
|
||||
it("errors on invalid queue options", async () => {
|
||||
it("reports invalid queue options and current queue settings", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const res = await getReplyFromConfig(
|
||||
const invalidRes = await getReplyFromConfig(
|
||||
{
|
||||
Body: "/queue collect debounce:bogus cap:zero drop:maybe",
|
||||
From: "+1222",
|
||||
@@ -344,16 +325,12 @@ describe("directive behavior", () => {
|
||||
),
|
||||
);
|
||||
|
||||
const text = replyText(res);
|
||||
expect(text).toContain("Invalid debounce");
|
||||
expect(text).toContain("Invalid cap");
|
||||
expect(text).toContain("Invalid drop policy");
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("shows current queue settings when /queue has no arguments", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const res = await getReplyFromConfig(
|
||||
const invalidText = replyText(invalidRes);
|
||||
expect(invalidText).toContain("Invalid debounce");
|
||||
expect(invalidText).toContain("Invalid cap");
|
||||
expect(invalidText).toContain("Invalid drop policy");
|
||||
|
||||
const currentRes = await getReplyFromConfig(
|
||||
{
|
||||
Body: "/queue",
|
||||
From: "+1222",
|
||||
@@ -379,7 +356,7 @@ describe("directive behavior", () => {
|
||||
),
|
||||
);
|
||||
|
||||
const text = replyText(res);
|
||||
const text = replyText(currentRes);
|
||||
expect(text).toContain(
|
||||
"Current queue settings: mode=collect, debounce=1500ms, cap=9, drop=summarize.",
|
||||
);
|
||||
|
||||
@@ -86,6 +86,7 @@ async function runReasoningDefaultCase(params: {
|
||||
expectedReasoningLevel: "off" | "on";
|
||||
thinkingDefault?: "off" | "low" | "medium" | "high";
|
||||
}) {
|
||||
vi.mocked(runEmbeddedPiAgent).mockClear();
|
||||
mockEmbeddedTextResult("done");
|
||||
mockReasoningCapableCatalog();
|
||||
|
||||
@@ -244,11 +245,11 @@ describe("directive behavior", () => {
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("ignores inline /model and uses the default model", async () => {
|
||||
it("ignores inline /model and /think directives while still running agent content", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
mockEmbeddedTextResult("done");
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
const inlineModelRes = await getReplyFromConfig(
|
||||
{
|
||||
Body: "please sync /model openai/gpt-4.1-mini now",
|
||||
From: "+1004",
|
||||
@@ -258,31 +259,47 @@ describe("directive behavior", () => {
|
||||
makeDefaultModelConfig(home),
|
||||
);
|
||||
|
||||
const texts = replyTexts(res);
|
||||
const texts = replyTexts(inlineModelRes);
|
||||
expect(texts).toContain("done");
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
|
||||
const call = vi.mocked(runEmbeddedPiAgent).mock.calls[0]?.[0];
|
||||
expect(call?.provider).toBe("anthropic");
|
||||
expect(call?.model).toBe("claude-opus-4-5");
|
||||
vi.mocked(runEmbeddedPiAgent).mockClear();
|
||||
|
||||
mockEmbeddedTextResult("done");
|
||||
const inlineThinkRes = await getReplyFromConfig(
|
||||
{
|
||||
Body: "please sync /think:high now",
|
||||
From: "+1004",
|
||||
To: "+2000",
|
||||
},
|
||||
{},
|
||||
makeWhatsAppDirectiveConfig(home, { model: { primary: "anthropic/claude-opus-4-5" } }),
|
||||
);
|
||||
|
||||
expect(replyTexts(inlineThinkRes)).toContain("done");
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
it("defaults thinking to low for reasoning-capable models without auto-enabling reasoning", async () => {
|
||||
it("applies reasoning defaults based on thinkingDefault configuration", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await runReasoningDefaultCase({
|
||||
home,
|
||||
expectedThinkLevel: "low",
|
||||
expectedReasoningLevel: "off",
|
||||
});
|
||||
});
|
||||
});
|
||||
it("keeps auto-reasoning enabled when thinking is explicitly off", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await runReasoningDefaultCase({
|
||||
home,
|
||||
expectedThinkLevel: "off",
|
||||
expectedReasoningLevel: "on",
|
||||
thinkingDefault: "off",
|
||||
});
|
||||
for (const scenario of [
|
||||
{
|
||||
expectedThinkLevel: "low" as const,
|
||||
expectedReasoningLevel: "off" as const,
|
||||
},
|
||||
{
|
||||
expectedThinkLevel: "off" as const,
|
||||
expectedReasoningLevel: "on" as const,
|
||||
thinkingDefault: "off" as const,
|
||||
},
|
||||
]) {
|
||||
await runReasoningDefaultCase({
|
||||
home,
|
||||
...scenario,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
it("passes elevated defaults when sender is approved", async () => {
|
||||
@@ -384,17 +401,14 @@ describe("directive behavior", () => {
|
||||
expect(call?.reasoningLevel).toBe("off");
|
||||
});
|
||||
});
|
||||
for (const replyTag of ["[[reply_to_current]]", "[[ reply_to_current ]]"]) {
|
||||
it(`strips ${replyTag} and maps reply_to_current to MessageSid`, async () => {
|
||||
await withTempHome(async (home) => {
|
||||
it("handles reply_to_current tags and explicit reply_to precedence", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
for (const replyTag of ["[[reply_to_current]]", "[[ reply_to_current ]]"]) {
|
||||
const payload = await runReplyToCurrentCase(home, `hello ${replyTag}`);
|
||||
expect(payload?.text).toBe("hello");
|
||||
expect(payload?.replyToId).toBe("msg-123");
|
||||
});
|
||||
});
|
||||
}
|
||||
it("prefers explicit reply_to id over reply_to_current", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
}
|
||||
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue(
|
||||
makeEmbeddedTextResult("hi [[reply_to_current]] [[reply_to:abc-456]]"),
|
||||
);
|
||||
@@ -415,23 +429,4 @@ describe("directive behavior", () => {
|
||||
expect(payload?.replyToId).toBe("abc-456");
|
||||
});
|
||||
});
|
||||
it("applies inline think and still runs agent content", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
mockEmbeddedTextResult("done");
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{
|
||||
Body: "please sync /think:high now",
|
||||
From: "+1004",
|
||||
To: "+2000",
|
||||
},
|
||||
{},
|
||||
makeWhatsAppDirectiveConfig(home, { model: { primary: "anthropic/claude-opus-4-5" } }),
|
||||
);
|
||||
|
||||
const texts = replyTexts(res);
|
||||
expect(texts).toContain("done");
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -110,87 +110,84 @@ describe("directive behavior", () => {
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("picks the best fuzzy match when multiple models match", async () => {
|
||||
it("picks the best fuzzy match for global and provider-scoped minimax queries", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const storePath = path.join(home, "sessions.json");
|
||||
|
||||
await getReplyFromConfig(
|
||||
{ Body: "/model minimax", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
for (const testCase of [
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "minimax/MiniMax-M2.1" },
|
||||
workspace: path.join(home, "openclaw"),
|
||||
models: {
|
||||
"minimax/MiniMax-M2.1": {},
|
||||
"minimax/MiniMax-M2.1-lightning": {},
|
||||
"lmstudio/minimax-m2.1-gs32": {},
|
||||
body: "/model minimax",
|
||||
storePath: path.join(home, "sessions-global-fuzzy.json"),
|
||||
config: {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "minimax/MiniMax-M2.1" },
|
||||
workspace: path.join(home, "openclaw"),
|
||||
models: {
|
||||
"minimax/MiniMax-M2.1": {},
|
||||
"minimax/MiniMax-M2.1-lightning": {},
|
||||
"lmstudio/minimax-m2.1-gs32": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
models: {
|
||||
mode: "merge",
|
||||
providers: {
|
||||
minimax: {
|
||||
baseUrl: "https://api.minimax.io/anthropic",
|
||||
apiKey: "sk-test",
|
||||
api: "anthropic-messages",
|
||||
models: [makeModelDefinition("MiniMax-M2.1", "MiniMax M2.1")],
|
||||
},
|
||||
lmstudio: {
|
||||
baseUrl: "http://127.0.0.1:1234/v1",
|
||||
apiKey: "lmstudio",
|
||||
api: "openai-responses",
|
||||
models: [makeModelDefinition("minimax-m2.1-gs32", "MiniMax M2.1 GS32")],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
models: {
|
||||
mode: "merge",
|
||||
providers: {
|
||||
minimax: {
|
||||
baseUrl: "https://api.minimax.io/anthropic",
|
||||
apiKey: "sk-test",
|
||||
api: "anthropic-messages",
|
||||
models: [makeModelDefinition("MiniMax-M2.1", "MiniMax M2.1")],
|
||||
},
|
||||
lmstudio: {
|
||||
baseUrl: "http://127.0.0.1:1234/v1",
|
||||
apiKey: "lmstudio",
|
||||
api: "openai-responses",
|
||||
models: [makeModelDefinition("minimax-m2.1-gs32", "MiniMax M2.1 GS32")],
|
||||
},
|
||||
},
|
||||
},
|
||||
session: { store: storePath },
|
||||
} as unknown as OpenClawConfig,
|
||||
);
|
||||
|
||||
assertModelSelection(storePath);
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("picks the best fuzzy match within a provider", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const storePath = path.join(home, "sessions.json");
|
||||
|
||||
await getReplyFromConfig(
|
||||
{ Body: "/model minimax/m2.1", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
},
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "minimax/MiniMax-M2.1" },
|
||||
workspace: path.join(home, "openclaw"),
|
||||
models: {
|
||||
"minimax/MiniMax-M2.1": {},
|
||||
"minimax/MiniMax-M2.1-lightning": {},
|
||||
body: "/model minimax/m2.1",
|
||||
storePath: path.join(home, "sessions-provider-fuzzy.json"),
|
||||
config: {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "minimax/MiniMax-M2.1" },
|
||||
workspace: path.join(home, "openclaw"),
|
||||
models: {
|
||||
"minimax/MiniMax-M2.1": {},
|
||||
"minimax/MiniMax-M2.1-lightning": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
models: {
|
||||
mode: "merge",
|
||||
providers: {
|
||||
minimax: {
|
||||
baseUrl: "https://api.minimax.io/anthropic",
|
||||
apiKey: "sk-test",
|
||||
api: "anthropic-messages",
|
||||
models: [
|
||||
makeModelDefinition("MiniMax-M2.1", "MiniMax M2.1"),
|
||||
makeModelDefinition("MiniMax-M2.1-lightning", "MiniMax M2.1 Lightning"),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
models: {
|
||||
mode: "merge",
|
||||
providers: {
|
||||
minimax: {
|
||||
baseUrl: "https://api.minimax.io/anthropic",
|
||||
apiKey: "sk-test",
|
||||
api: "anthropic-messages",
|
||||
models: [
|
||||
makeModelDefinition("MiniMax-M2.1", "MiniMax M2.1"),
|
||||
makeModelDefinition("MiniMax-M2.1-lightning", "MiniMax M2.1 Lightning"),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
session: { store: storePath },
|
||||
} as unknown as OpenClawConfig,
|
||||
);
|
||||
|
||||
assertModelSelection(storePath);
|
||||
},
|
||||
]) {
|
||||
await getReplyFromConfig(
|
||||
{ Body: testCase.body, From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
{
|
||||
...testCase.config,
|
||||
session: { store: testCase.storePath },
|
||||
} as unknown as OpenClawConfig,
|
||||
);
|
||||
assertModelSelection(testCase.storePath);
|
||||
}
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -184,9 +184,9 @@ describe("directive behavior", () => {
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("rejects per-agent elevated when disabled", async () => {
|
||||
it("enforces per-agent elevated restrictions and status visibility", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const res = await getReplyFromConfig(
|
||||
const deniedRes = await getReplyFromConfig(
|
||||
{
|
||||
Body: "/elevated on",
|
||||
From: "+1222",
|
||||
@@ -199,93 +199,10 @@ describe("directive behavior", () => {
|
||||
{},
|
||||
makeRestrictedElevatedDisabledConfig(home) as unknown as OpenClawConfig,
|
||||
);
|
||||
const deniedText = replyText(deniedRes);
|
||||
expect(deniedText).toContain("agents.list[].tools.elevated.enabled");
|
||||
|
||||
const text = replyText(res);
|
||||
expect(text).toContain("agents.list[].tools.elevated.enabled");
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("requires per-agent allowlist in addition to global", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const res = await getReplyFromConfig(
|
||||
{
|
||||
Body: "/elevated on",
|
||||
From: "+1222",
|
||||
To: "+1222",
|
||||
Provider: "whatsapp",
|
||||
SenderE164: "+1222",
|
||||
SessionKey: "agent:work:main",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
makeWorkElevatedAllowlistConfig(home),
|
||||
);
|
||||
|
||||
const text = replyText(res);
|
||||
expect(text).toContain("agents.list[].tools.elevated.allowFrom.whatsapp");
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("allows elevated when both global and per-agent allowlists match", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const res = await getReplyFromConfig(
|
||||
{
|
||||
...makeCommandMessage("/elevated on", "+1333"),
|
||||
SessionKey: "agent:work:main",
|
||||
},
|
||||
{},
|
||||
makeWorkElevatedAllowlistConfig(home),
|
||||
);
|
||||
|
||||
const text = replyText(res);
|
||||
expect(text).toContain("Elevated mode set to ask");
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("warns when elevated is used in direct runtime", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const res = await getReplyFromConfig(
|
||||
makeCommandMessage("/elevated off"),
|
||||
{},
|
||||
makeAllowlistedElevatedConfig(home, { sandbox: { mode: "off" } }),
|
||||
);
|
||||
|
||||
const text = replyText(res);
|
||||
expect(text).toContain("Elevated mode disabled.");
|
||||
expect(text).toContain("Runtime is direct; sandboxing does not apply.");
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("rejects invalid elevated level", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const res = await getReplyFromConfig(
|
||||
makeCommandMessage("/elevated maybe"),
|
||||
{},
|
||||
makeAllowlistedElevatedConfig(home),
|
||||
);
|
||||
|
||||
const text = replyText(res);
|
||||
expect(text).toContain("Unrecognized elevated level");
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("handles multiple directives in a single message", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const res = await getReplyFromConfig(
|
||||
makeCommandMessage("/elevated off\n/verbose on"),
|
||||
{},
|
||||
makeAllowlistedElevatedConfig(home),
|
||||
);
|
||||
|
||||
const text = replyText(res);
|
||||
expect(text).toContain("Elevated mode disabled.");
|
||||
expect(text).toContain("Verbose logging enabled.");
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("shows elevated off in status when per-agent elevated is disabled", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const res = await getReplyFromConfig(
|
||||
const statusRes = await getReplyFromConfig(
|
||||
{
|
||||
Body: "/status",
|
||||
From: "+1222",
|
||||
@@ -298,9 +215,71 @@ describe("directive behavior", () => {
|
||||
{},
|
||||
makeRestrictedElevatedDisabledConfig(home) as unknown as OpenClawConfig,
|
||||
);
|
||||
const statusText = replyText(statusRes);
|
||||
expect(statusText).not.toContain("elevated");
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("applies per-agent allowlist requirements before allowing elevated", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const deniedRes = await getReplyFromConfig(
|
||||
{
|
||||
...makeCommandMessage("/elevated on", "+1222"),
|
||||
SessionKey: "agent:work:main",
|
||||
},
|
||||
{},
|
||||
makeWorkElevatedAllowlistConfig(home),
|
||||
);
|
||||
|
||||
const text = replyText(res);
|
||||
expect(text).not.toContain("elevated");
|
||||
const deniedText = replyText(deniedRes);
|
||||
expect(deniedText).toContain("agents.list[].tools.elevated.allowFrom.whatsapp");
|
||||
|
||||
const allowedRes = await getReplyFromConfig(
|
||||
{
|
||||
...makeCommandMessage("/elevated on", "+1333"),
|
||||
SessionKey: "agent:work:main",
|
||||
},
|
||||
{},
|
||||
makeWorkElevatedAllowlistConfig(home),
|
||||
);
|
||||
|
||||
const allowedText = replyText(allowedRes);
|
||||
expect(allowedText).toContain("Elevated mode set to ask");
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("handles runtime warning, invalid level, and multi-directive elevated inputs", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
for (const scenario of [
|
||||
{
|
||||
body: "/elevated off",
|
||||
config: makeAllowlistedElevatedConfig(home, { sandbox: { mode: "off" } }),
|
||||
expectedSnippets: [
|
||||
"Elevated mode disabled.",
|
||||
"Runtime is direct; sandboxing does not apply.",
|
||||
],
|
||||
},
|
||||
{
|
||||
body: "/elevated maybe",
|
||||
config: makeAllowlistedElevatedConfig(home),
|
||||
expectedSnippets: ["Unrecognized elevated level"],
|
||||
},
|
||||
{
|
||||
body: "/elevated off\n/verbose on",
|
||||
config: makeAllowlistedElevatedConfig(home),
|
||||
expectedSnippets: ["Elevated mode disabled.", "Verbose logging enabled."],
|
||||
},
|
||||
]) {
|
||||
const res = await getReplyFromConfig(
|
||||
makeCommandMessage(scenario.body),
|
||||
{},
|
||||
scenario.config,
|
||||
);
|
||||
const text = replyText(res);
|
||||
for (const snippet of scenario.expectedSnippets) {
|
||||
expect(text).toContain(snippet);
|
||||
}
|
||||
}
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user