mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-17 20:21:13 +00:00
test(auto-reply): move directive event coverage lower
This commit is contained in:
@@ -1,16 +1,11 @@
|
||||
import "./reply.directive.directive-behavior.e2e-mocks.js";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { loadSessionStore } from "../config/sessions.js";
|
||||
import type { ModelDefinitionConfig } from "../config/types.models.js";
|
||||
import { drainSystemEvents } from "../infra/system-events.js";
|
||||
import {
|
||||
assertModelSelection,
|
||||
installDirectiveBehaviorE2EHooks,
|
||||
MAIN_SESSION_KEY,
|
||||
makeWhatsAppDirectiveConfig,
|
||||
replyText,
|
||||
sessionStorePath,
|
||||
withTempHome,
|
||||
@@ -31,18 +26,6 @@ function makeModelDefinition(id: string, name: string): ModelDefinitionConfig {
|
||||
};
|
||||
}
|
||||
|
||||
function makeModelSwitchConfig(home: string) {
|
||||
return withFullRuntimeReplyConfig(
|
||||
makeWhatsAppDirectiveConfig(home, {
|
||||
model: { primary: "openai/gpt-4.1-mini" },
|
||||
models: {
|
||||
"openai/gpt-4.1-mini": {},
|
||||
"anthropic/claude-opus-4-6": { alias: "Opus" },
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function makeMoonshotConfig(home: string, storePath: string) {
|
||||
return withFullRuntimeReplyConfig({
|
||||
agents: {
|
||||
@@ -251,97 +234,4 @@ describe("directive behavior", () => {
|
||||
expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("stores auth profile overrides on /model directive", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const storePath = sessionStorePath(home);
|
||||
const authDir = path.join(home, ".openclaw", "agents", "main", "agent");
|
||||
await fs.mkdir(authDir, { recursive: true, mode: 0o700 });
|
||||
await fs.writeFile(
|
||||
path.join(authDir, "auth-profiles.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
version: 1,
|
||||
profiles: {
|
||||
"anthropic:work": {
|
||||
type: "api_key",
|
||||
provider: "anthropic",
|
||||
key: "sk-test-1234567890",
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{ Body: "/model Opus@anthropic:work", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
makeModelSwitchConfig(home),
|
||||
);
|
||||
|
||||
const text = replyText(res);
|
||||
expect(text).toContain("Auth profile set to anthropic:work");
|
||||
const store = loadSessionStore(storePath);
|
||||
const entry = store["agent:main:main"];
|
||||
expect(entry.authProfileOverride).toBe("anthropic:work");
|
||||
expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("queues system events for model, elevated, and reasoning directives", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
drainSystemEvents(MAIN_SESSION_KEY);
|
||||
await getReplyFromConfig(
|
||||
{ Body: "/model Opus", From: "+1222", To: "+1222", CommandAuthorized: true },
|
||||
{},
|
||||
makeModelSwitchConfig(home),
|
||||
);
|
||||
|
||||
let events = drainSystemEvents(MAIN_SESSION_KEY);
|
||||
expect(events).toContain("Model switched to Opus (anthropic/claude-opus-4-6).");
|
||||
|
||||
drainSystemEvents(MAIN_SESSION_KEY);
|
||||
|
||||
await getReplyFromConfig(
|
||||
{
|
||||
Body: "/elevated on",
|
||||
From: "+1222",
|
||||
To: "+1222",
|
||||
Provider: "whatsapp",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
withFullRuntimeReplyConfig(
|
||||
makeWhatsAppDirectiveConfig(
|
||||
home,
|
||||
{ model: { primary: "openai/gpt-4.1-mini" } },
|
||||
{ tools: { elevated: { allowFrom: { whatsapp: ["*"] } } } },
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
events = drainSystemEvents(MAIN_SESSION_KEY);
|
||||
expect(events.some((e) => e.includes("Elevated ASK"))).toBe(true);
|
||||
|
||||
drainSystemEvents(MAIN_SESSION_KEY);
|
||||
|
||||
await getReplyFromConfig(
|
||||
{
|
||||
Body: "/reasoning stream",
|
||||
From: "+1222",
|
||||
To: "+1222",
|
||||
Provider: "whatsapp",
|
||||
CommandAuthorized: true,
|
||||
},
|
||||
{},
|
||||
withFullRuntimeReplyConfig(
|
||||
makeWhatsAppDirectiveConfig(home, { model: { primary: "openai/gpt-4.1-mini" } }),
|
||||
),
|
||||
);
|
||||
|
||||
events = drainSystemEvents(MAIN_SESSION_KEY);
|
||||
expect(events.some((e) => e.includes("Reasoning STREAM"))).toBe(true);
|
||||
expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
import type { ModelAliasIndex } from "../../agents/model-selection.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import type { SessionEntry } from "../../config/sessions.js";
|
||||
import { enqueueSystemEvent } from "../../infra/system-events.js";
|
||||
import type { ElevatedLevel } from "../thinking.js";
|
||||
import { handleDirectiveOnly } from "./directive-handling.impl.js";
|
||||
import {
|
||||
@@ -119,6 +120,7 @@ beforeEach(() => {
|
||||
]);
|
||||
vi.mocked(resolveAgentDir).mockReset().mockReturnValue(TEST_AGENT_DIR);
|
||||
vi.mocked(resolveSessionAgentId).mockReset().mockReturnValue("main");
|
||||
vi.mocked(enqueueSystemEvent).mockClear();
|
||||
liveModelSwitchMocks.requestLiveSessionModelSwitch.mockReset().mockReturnValue(false);
|
||||
queueMocks.refreshQueuedFollowupSession.mockReset();
|
||||
});
|
||||
@@ -153,6 +155,21 @@ function createGptAliasIndex(): ModelAliasIndex {
|
||||
};
|
||||
}
|
||||
|
||||
function createOpusAliasIndex(): ModelAliasIndex {
|
||||
return {
|
||||
byAlias: new Map([
|
||||
[
|
||||
"opus",
|
||||
{
|
||||
alias: "Opus",
|
||||
ref: { provider: "anthropic", model: "claude-opus-4-6" },
|
||||
},
|
||||
],
|
||||
]),
|
||||
byKey: new Map([["anthropic/claude-opus-4-6", ["Opus"]]]),
|
||||
};
|
||||
}
|
||||
|
||||
function resolveModelSelectionForCommand(params: {
|
||||
command: string;
|
||||
allowedModelKeys: Set<string>;
|
||||
@@ -685,6 +702,55 @@ describe("handleDirectiveOnly model persist behavior (fixes #1435)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("persists auth profile overrides for alias model directives", async () => {
|
||||
setAuthProfiles({
|
||||
"anthropic:work": {
|
||||
type: "api_key",
|
||||
provider: "anthropic",
|
||||
key: "sk-test",
|
||||
},
|
||||
});
|
||||
const sessionEntry = createSessionEntry();
|
||||
const sessionStore = { [sessionKey]: sessionEntry };
|
||||
|
||||
const result = await handleDirectiveOnly(
|
||||
createHandleParams({
|
||||
directives: parseInlineDirectives("/model Opus@anthropic:work"),
|
||||
aliasIndex: createOpusAliasIndex(),
|
||||
defaultProvider: "openai",
|
||||
defaultModel: "gpt-4o",
|
||||
provider: "openai",
|
||||
model: "gpt-4o",
|
||||
initialModelLabel: "openai/gpt-4o",
|
||||
sessionEntry,
|
||||
sessionStore,
|
||||
formatModelSwitchEvent: (label, alias) =>
|
||||
alias ? `Model switched to ${alias} (${label}).` : `Model switched to ${label}.`,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result?.text).toContain("Model set to Opus (anthropic/claude-opus-4-6).");
|
||||
expect(result?.text).toContain("Auth profile set to anthropic:work.");
|
||||
expect(sessionEntry.providerOverride).toBe("anthropic");
|
||||
expect(sessionEntry.modelOverride).toBe("claude-opus-4-6");
|
||||
expect(sessionEntry.authProfileOverride).toBe("anthropic:work");
|
||||
expect(sessionEntry.authProfileOverrideSource).toBe("user");
|
||||
expect(queueMocks.refreshQueuedFollowupSession).toHaveBeenCalledWith({
|
||||
key: sessionKey,
|
||||
nextProvider: "anthropic",
|
||||
nextModel: "claude-opus-4-6",
|
||||
nextAuthProfileId: "anthropic:work",
|
||||
nextAuthProfileIdSource: "user",
|
||||
});
|
||||
expect(enqueueSystemEvent).toHaveBeenCalledWith(
|
||||
"Model switched to Opus (anthropic/claude-opus-4-6).",
|
||||
{
|
||||
sessionKey,
|
||||
contextKey: "model:anthropic/claude-opus-4-6",
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("shows no model message when no /model directive", async () => {
|
||||
const directives = parseInlineDirectives("hello world");
|
||||
const sessionEntry = createSessionEntry();
|
||||
@@ -799,6 +865,44 @@ describe("handleDirectiveOnly model persist behavior (fixes #1435)", () => {
|
||||
expect(sessionEntry.elevatedLevel).toBe("off");
|
||||
});
|
||||
|
||||
it("queues system events for elevated and reasoning mode directives", async () => {
|
||||
const sessionEntry = createSessionEntry();
|
||||
const sessionStore = { [sessionKey]: sessionEntry };
|
||||
|
||||
await handleDirectiveOnly(
|
||||
createHandleParams({
|
||||
directives: parseInlineDirectives("/elevated on"),
|
||||
elevatedAllowed: true,
|
||||
elevatedEnabled: true,
|
||||
sessionEntry,
|
||||
sessionStore,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(enqueueSystemEvent).toHaveBeenCalledWith(
|
||||
"Elevated ASK - exec runs on host; approvals may still apply.",
|
||||
{
|
||||
sessionKey,
|
||||
contextKey: "mode:elevated",
|
||||
},
|
||||
);
|
||||
|
||||
vi.mocked(enqueueSystemEvent).mockClear();
|
||||
|
||||
await handleDirectiveOnly(
|
||||
createHandleParams({
|
||||
directives: parseInlineDirectives("/reasoning stream"),
|
||||
sessionEntry,
|
||||
sessionStore,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(enqueueSystemEvent).toHaveBeenCalledWith("Reasoning STREAM - emit live <think>.", {
|
||||
sessionKey,
|
||||
contextKey: "mode:reasoning",
|
||||
});
|
||||
});
|
||||
|
||||
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",
|
||||
|
||||
Reference in New Issue
Block a user