mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 11:38:38 +00:00
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);
|
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) => {
|
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 },
|
{ Body: "/verbose on", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||||
{},
|
{},
|
||||||
makeWhatsAppDirectiveConfig(home, { model: "anthropic/claude-opus-4-5" }),
|
makeWhatsAppDirectiveConfig(home, { model: "anthropic/claude-opus-4-5" }),
|
||||||
);
|
);
|
||||||
|
expect(replyText(enabledRes)).toMatch(/^⚙️ Verbose logging enabled\./);
|
||||||
|
|
||||||
const text = replyText(res);
|
const disabledRes = await getReplyFromConfig(
|
||||||
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(
|
|
||||||
{ Body: "/verbose off", From: "+1222", To: "+1222", CommandAuthorized: true },
|
{ Body: "/verbose off", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||||
{},
|
{},
|
||||||
makeWhatsAppDirectiveConfig(
|
makeWhatsAppDirectiveConfig(
|
||||||
@@ -200,7 +194,7 @@ describe("directive behavior", () => {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const text = replyText(res);
|
const text = replyText(disabledRes);
|
||||||
expect(text).toMatch(/Verbose logging disabled\./);
|
expect(text).toMatch(/Verbose logging disabled\./);
|
||||||
const store = loadSessionStore(storePath);
|
const store = loadSessionStore(storePath);
|
||||||
const entry = Object.values(store)[0];
|
const entry = Object.values(store)[0];
|
||||||
@@ -208,59 +202,46 @@ describe("directive behavior", () => {
|
|||||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
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) => {
|
await withTempHome(async (home) => {
|
||||||
const { res } = await runInFlightVerboseToggleCase({
|
for (const testCase of [
|
||||||
home,
|
{
|
||||||
shouldEmitBefore: false,
|
shouldEmitBefore: false,
|
||||||
toggledVerboseLevel: "on",
|
toggledVerboseLevel: "on" as const,
|
||||||
});
|
},
|
||||||
|
{
|
||||||
const texts = replyTexts(res);
|
shouldEmitBefore: true,
|
||||||
expect(texts).toContain("done");
|
toggledVerboseLevel: "off" as const,
|
||||||
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
|
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 () => {
|
it("covers think status and /thinking xhigh support matrix", 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 () => {
|
|
||||||
await withTempHome(async (home) => {
|
await withTempHome(async (home) => {
|
||||||
const text = await runThinkDirectiveAndGetText(home);
|
const text = await runThinkDirectiveAndGetText(home);
|
||||||
expect(text).toContain("Current thinking level: high");
|
expect(text).toContain("Current thinking level: high");
|
||||||
expect(text).toContain("Options: off, minimal, low, medium, high.");
|
expect(text).toContain("Options: off, minimal, low, medium, high.");
|
||||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
|
||||||
});
|
for (const model of ["openai-codex/gpt-5.2-codex", "openai/gpt-5.2"]) {
|
||||||
});
|
const texts = await runThinkingDirective(home, model);
|
||||||
it("accepts /thinking xhigh for codex models", async () => {
|
expect(texts).toContain("Thinking level set to xhigh.");
|
||||||
await withTempHome(async (home) => {
|
}
|
||||||
const texts = await runThinkingDirective(home, "openai-codex/gpt-5.2-codex");
|
|
||||||
expect(texts).toContain("Thinking level set to xhigh.");
|
const unsupportedModelTexts = await runThinkingDirective(home, "openai/gpt-4.1-mini");
|
||||||
});
|
expect(unsupportedModelTexts).toContain(
|
||||||
});
|
|
||||||
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(
|
|
||||||
'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.',
|
'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 () => {
|
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');
|
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) => {
|
await withTempHome(async (home) => {
|
||||||
const res = await getReplyFromConfig(
|
const invalidRes = await getReplyFromConfig(
|
||||||
{
|
{
|
||||||
Body: "/queue collect debounce:bogus cap:zero drop:maybe",
|
Body: "/queue collect debounce:bogus cap:zero drop:maybe",
|
||||||
From: "+1222",
|
From: "+1222",
|
||||||
@@ -344,16 +325,12 @@ describe("directive behavior", () => {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const text = replyText(res);
|
const invalidText = replyText(invalidRes);
|
||||||
expect(text).toContain("Invalid debounce");
|
expect(invalidText).toContain("Invalid debounce");
|
||||||
expect(text).toContain("Invalid cap");
|
expect(invalidText).toContain("Invalid cap");
|
||||||
expect(text).toContain("Invalid drop policy");
|
expect(invalidText).toContain("Invalid drop policy");
|
||||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
|
||||||
});
|
const currentRes = await getReplyFromConfig(
|
||||||
});
|
|
||||||
it("shows current queue settings when /queue has no arguments", async () => {
|
|
||||||
await withTempHome(async (home) => {
|
|
||||||
const res = await getReplyFromConfig(
|
|
||||||
{
|
{
|
||||||
Body: "/queue",
|
Body: "/queue",
|
||||||
From: "+1222",
|
From: "+1222",
|
||||||
@@ -379,7 +356,7 @@ describe("directive behavior", () => {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const text = replyText(res);
|
const text = replyText(currentRes);
|
||||||
expect(text).toContain(
|
expect(text).toContain(
|
||||||
"Current queue settings: mode=collect, debounce=1500ms, cap=9, drop=summarize.",
|
"Current queue settings: mode=collect, debounce=1500ms, cap=9, drop=summarize.",
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ async function runReasoningDefaultCase(params: {
|
|||||||
expectedReasoningLevel: "off" | "on";
|
expectedReasoningLevel: "off" | "on";
|
||||||
thinkingDefault?: "off" | "low" | "medium" | "high";
|
thinkingDefault?: "off" | "low" | "medium" | "high";
|
||||||
}) {
|
}) {
|
||||||
|
vi.mocked(runEmbeddedPiAgent).mockClear();
|
||||||
mockEmbeddedTextResult("done");
|
mockEmbeddedTextResult("done");
|
||||||
mockReasoningCapableCatalog();
|
mockReasoningCapableCatalog();
|
||||||
|
|
||||||
@@ -244,11 +245,11 @@ describe("directive behavior", () => {
|
|||||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
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) => {
|
await withTempHome(async (home) => {
|
||||||
mockEmbeddedTextResult("done");
|
mockEmbeddedTextResult("done");
|
||||||
|
|
||||||
const res = await getReplyFromConfig(
|
const inlineModelRes = await getReplyFromConfig(
|
||||||
{
|
{
|
||||||
Body: "please sync /model openai/gpt-4.1-mini now",
|
Body: "please sync /model openai/gpt-4.1-mini now",
|
||||||
From: "+1004",
|
From: "+1004",
|
||||||
@@ -258,31 +259,47 @@ describe("directive behavior", () => {
|
|||||||
makeDefaultModelConfig(home),
|
makeDefaultModelConfig(home),
|
||||||
);
|
);
|
||||||
|
|
||||||
const texts = replyTexts(res);
|
const texts = replyTexts(inlineModelRes);
|
||||||
expect(texts).toContain("done");
|
expect(texts).toContain("done");
|
||||||
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
|
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
|
||||||
const call = vi.mocked(runEmbeddedPiAgent).mock.calls[0]?.[0];
|
const call = vi.mocked(runEmbeddedPiAgent).mock.calls[0]?.[0];
|
||||||
expect(call?.provider).toBe("anthropic");
|
expect(call?.provider).toBe("anthropic");
|
||||||
expect(call?.model).toBe("claude-opus-4-5");
|
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 withTempHome(async (home) => {
|
||||||
await runReasoningDefaultCase({
|
for (const scenario of [
|
||||||
home,
|
{
|
||||||
expectedThinkLevel: "low",
|
expectedThinkLevel: "low" as const,
|
||||||
expectedReasoningLevel: "off",
|
expectedReasoningLevel: "off" as const,
|
||||||
});
|
},
|
||||||
});
|
{
|
||||||
});
|
expectedThinkLevel: "off" as const,
|
||||||
it("keeps auto-reasoning enabled when thinking is explicitly off", async () => {
|
expectedReasoningLevel: "on" as const,
|
||||||
await withTempHome(async (home) => {
|
thinkingDefault: "off" as const,
|
||||||
await runReasoningDefaultCase({
|
},
|
||||||
home,
|
]) {
|
||||||
expectedThinkLevel: "off",
|
await runReasoningDefaultCase({
|
||||||
expectedReasoningLevel: "on",
|
home,
|
||||||
thinkingDefault: "off",
|
...scenario,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it("passes elevated defaults when sender is approved", async () => {
|
it("passes elevated defaults when sender is approved", async () => {
|
||||||
@@ -384,17 +401,14 @@ describe("directive behavior", () => {
|
|||||||
expect(call?.reasoningLevel).toBe("off");
|
expect(call?.reasoningLevel).toBe("off");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
for (const replyTag of ["[[reply_to_current]]", "[[ reply_to_current ]]"]) {
|
it("handles reply_to_current tags and explicit reply_to precedence", async () => {
|
||||||
it(`strips ${replyTag} and maps reply_to_current to MessageSid`, async () => {
|
await withTempHome(async (home) => {
|
||||||
await withTempHome(async (home) => {
|
for (const replyTag of ["[[reply_to_current]]", "[[ reply_to_current ]]"]) {
|
||||||
const payload = await runReplyToCurrentCase(home, `hello ${replyTag}`);
|
const payload = await runReplyToCurrentCase(home, `hello ${replyTag}`);
|
||||||
expect(payload?.text).toBe("hello");
|
expect(payload?.text).toBe("hello");
|
||||||
expect(payload?.replyToId).toBe("msg-123");
|
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(
|
vi.mocked(runEmbeddedPiAgent).mockResolvedValue(
|
||||||
makeEmbeddedTextResult("hi [[reply_to_current]] [[reply_to:abc-456]]"),
|
makeEmbeddedTextResult("hi [[reply_to_current]] [[reply_to:abc-456]]"),
|
||||||
);
|
);
|
||||||
@@ -415,23 +429,4 @@ describe("directive behavior", () => {
|
|||||||
expect(payload?.replyToId).toBe("abc-456");
|
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();
|
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) => {
|
await withTempHome(async (home) => {
|
||||||
const storePath = path.join(home, "sessions.json");
|
for (const testCase of [
|
||||||
|
|
||||||
await getReplyFromConfig(
|
|
||||||
{ Body: "/model minimax", From: "+1222", To: "+1222", CommandAuthorized: true },
|
|
||||||
{},
|
|
||||||
{
|
{
|
||||||
agents: {
|
body: "/model minimax",
|
||||||
defaults: {
|
storePath: path.join(home, "sessions-global-fuzzy.json"),
|
||||||
model: { primary: "minimax/MiniMax-M2.1" },
|
config: {
|
||||||
workspace: path.join(home, "openclaw"),
|
agents: {
|
||||||
models: {
|
defaults: {
|
||||||
"minimax/MiniMax-M2.1": {},
|
model: { primary: "minimax/MiniMax-M2.1" },
|
||||||
"minimax/MiniMax-M2.1-lightning": {},
|
workspace: path.join(home, "openclaw"),
|
||||||
"lmstudio/minimax-m2.1-gs32": {},
|
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: {
|
body: "/model minimax/m2.1",
|
||||||
defaults: {
|
storePath: path.join(home, "sessions-provider-fuzzy.json"),
|
||||||
model: { primary: "minimax/MiniMax-M2.1" },
|
config: {
|
||||||
workspace: path.join(home, "openclaw"),
|
agents: {
|
||||||
models: {
|
defaults: {
|
||||||
"minimax/MiniMax-M2.1": {},
|
model: { primary: "minimax/MiniMax-M2.1" },
|
||||||
"minimax/MiniMax-M2.1-lightning": {},
|
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: {
|
await getReplyFromConfig(
|
||||||
minimax: {
|
{ Body: testCase.body, From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||||
baseUrl: "https://api.minimax.io/anthropic",
|
{},
|
||||||
apiKey: "sk-test",
|
{
|
||||||
api: "anthropic-messages",
|
...testCase.config,
|
||||||
models: [
|
session: { store: testCase.storePath },
|
||||||
makeModelDefinition("MiniMax-M2.1", "MiniMax M2.1"),
|
} as unknown as OpenClawConfig,
|
||||||
makeModelDefinition("MiniMax-M2.1-lightning", "MiniMax M2.1 Lightning"),
|
);
|
||||||
],
|
assertModelSelection(testCase.storePath);
|
||||||
},
|
}
|
||||||
},
|
|
||||||
},
|
|
||||||
session: { store: storePath },
|
|
||||||
} as unknown as OpenClawConfig,
|
|
||||||
);
|
|
||||||
|
|
||||||
assertModelSelection(storePath);
|
|
||||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -184,9 +184,9 @@ describe("directive behavior", () => {
|
|||||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
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) => {
|
await withTempHome(async (home) => {
|
||||||
const res = await getReplyFromConfig(
|
const deniedRes = await getReplyFromConfig(
|
||||||
{
|
{
|
||||||
Body: "/elevated on",
|
Body: "/elevated on",
|
||||||
From: "+1222",
|
From: "+1222",
|
||||||
@@ -199,93 +199,10 @@ describe("directive behavior", () => {
|
|||||||
{},
|
{},
|
||||||
makeRestrictedElevatedDisabledConfig(home) as unknown as OpenClawConfig,
|
makeRestrictedElevatedDisabledConfig(home) as unknown as OpenClawConfig,
|
||||||
);
|
);
|
||||||
|
const deniedText = replyText(deniedRes);
|
||||||
|
expect(deniedText).toContain("agents.list[].tools.elevated.enabled");
|
||||||
|
|
||||||
const text = replyText(res);
|
const statusRes = await getReplyFromConfig(
|
||||||
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(
|
|
||||||
{
|
{
|
||||||
Body: "/status",
|
Body: "/status",
|
||||||
From: "+1222",
|
From: "+1222",
|
||||||
@@ -298,9 +215,71 @@ describe("directive behavior", () => {
|
|||||||
{},
|
{},
|
||||||
makeRestrictedElevatedDisabledConfig(home) as unknown as OpenClawConfig,
|
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);
|
const deniedText = replyText(deniedRes);
|
||||||
expect(text).not.toContain("elevated");
|
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();
|
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user