mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-21 16:41:56 +00:00
refactor(test): dedupe trigger-handling e2e setup
This commit is contained in:
@@ -1,97 +1,15 @@
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||
|
||||
vi.mock("../agents/pi-embedded.js", () => ({
|
||||
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
|
||||
compactEmbeddedPiSession: vi.fn(),
|
||||
runEmbeddedPiAgent: vi.fn(),
|
||||
queueEmbeddedPiMessage: vi.fn().mockReturnValue(false),
|
||||
resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`,
|
||||
isEmbeddedPiRunActive: vi.fn().mockReturnValue(false),
|
||||
isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false),
|
||||
}));
|
||||
|
||||
const usageMocks = vi.hoisted(() => ({
|
||||
loadProviderUsageSummary: vi.fn().mockResolvedValue({
|
||||
updatedAt: 0,
|
||||
providers: [],
|
||||
}),
|
||||
formatUsageSummaryLine: vi.fn().mockReturnValue("📊 Usage: Claude 80% left"),
|
||||
resolveUsageProviderId: vi.fn((provider: string) => provider.split("/")[0]),
|
||||
}));
|
||||
|
||||
vi.mock("../infra/provider-usage.js", () => usageMocks);
|
||||
|
||||
const modelCatalogMocks = vi.hoisted(() => ({
|
||||
loadModelCatalog: vi.fn().mockResolvedValue([
|
||||
{
|
||||
provider: "anthropic",
|
||||
id: "claude-opus-4-5",
|
||||
name: "Claude Opus 4.5",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{
|
||||
provider: "openrouter",
|
||||
id: "anthropic/claude-opus-4-5",
|
||||
name: "Claude Opus 4.5 (OpenRouter)",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{ provider: "openai", id: "gpt-4.1-mini", name: "GPT-4.1 mini" },
|
||||
{ provider: "openai", id: "gpt-5.2", name: "GPT-5.2" },
|
||||
{ provider: "openai-codex", id: "gpt-5.2", name: "GPT-5.2 (Codex)" },
|
||||
{ provider: "minimax", id: "MiniMax-M2.1", name: "MiniMax M2.1" },
|
||||
]),
|
||||
resetModelCatalogCacheForTest: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../agents/model-catalog.js", () => modelCatalogMocks);
|
||||
|
||||
import { abortEmbeddedPiRun, runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getReplyFromConfig } from "./reply.js";
|
||||
import {
|
||||
getRunEmbeddedPiAgentMock,
|
||||
installTriggerHandlingE2eTestHooks,
|
||||
makeCfg,
|
||||
withTempHome,
|
||||
} from "./reply.triggers.trigger-handling.test-harness.js";
|
||||
|
||||
const _MAIN_SESSION_KEY = "agent:main:main";
|
||||
|
||||
const webMocks = vi.hoisted(() => ({
|
||||
webAuthExists: vi.fn().mockResolvedValue(true),
|
||||
getWebAuthAgeMs: vi.fn().mockReturnValue(120_000),
|
||||
readWebSelfId: vi.fn().mockReturnValue({ e164: "+1999" }),
|
||||
}));
|
||||
|
||||
vi.mock("../web/session.js", () => webMocks);
|
||||
|
||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||
return withTempHomeBase(
|
||||
async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockClear();
|
||||
vi.mocked(abortEmbeddedPiRun).mockClear();
|
||||
return await fn(home);
|
||||
},
|
||||
{ prefix: "openclaw-triggers-" },
|
||||
);
|
||||
}
|
||||
|
||||
function makeCfg(home: string) {
|
||||
return {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "openclaw"),
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
installTriggerHandlingE2eTestHooks();
|
||||
|
||||
describe("trigger handling", () => {
|
||||
it("allows /activation from allowFrom in groups", async () => {
|
||||
@@ -112,12 +30,12 @@ describe("trigger handling", () => {
|
||||
);
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text).toBe("⚙️ Group activation set to mention.");
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
expect(getRunEmbeddedPiAgentMock()).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("injects group activation context into the system prompt", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||
getRunEmbeddedPiAgentMock().mockResolvedValue({
|
||||
payloads: [{ text: "ok" }],
|
||||
meta: {
|
||||
durationMs: 1,
|
||||
@@ -159,15 +77,15 @@ describe("trigger handling", () => {
|
||||
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text).toBe("ok");
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
|
||||
const extra = vi.mocked(runEmbeddedPiAgent).mock.calls[0]?.[0]?.extraSystemPrompt ?? "";
|
||||
expect(getRunEmbeddedPiAgentMock()).toHaveBeenCalledOnce();
|
||||
const extra = getRunEmbeddedPiAgentMock().mock.calls[0]?.[0]?.extraSystemPrompt ?? "";
|
||||
expect(extra).toContain('"chat_type": "group"');
|
||||
expect(extra).toContain("Activation: always-on");
|
||||
});
|
||||
});
|
||||
it("runs a greeting prompt for a bare /new", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||
getRunEmbeddedPiAgentMock().mockResolvedValue({
|
||||
payloads: [{ text: "hello" }],
|
||||
meta: {
|
||||
durationMs: 1,
|
||||
@@ -202,8 +120,8 @@ describe("trigger handling", () => {
|
||||
);
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text).toBe("hello");
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
|
||||
const prompt = vi.mocked(runEmbeddedPiAgent).mock.calls[0]?.[0]?.prompt ?? "";
|
||||
expect(getRunEmbeddedPiAgentMock()).toHaveBeenCalledOnce();
|
||||
const prompt = getRunEmbeddedPiAgentMock().mock.calls[0]?.[0]?.prompt ?? "";
|
||||
expect(prompt).toContain("A new session was started via /new or /reset");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,97 +1,15 @@
|
||||
import fs from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||
|
||||
vi.mock("../agents/pi-embedded.js", () => ({
|
||||
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
|
||||
compactEmbeddedPiSession: vi.fn(),
|
||||
runEmbeddedPiAgent: vi.fn(),
|
||||
queueEmbeddedPiMessage: vi.fn().mockReturnValue(false),
|
||||
resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`,
|
||||
isEmbeddedPiRunActive: vi.fn().mockReturnValue(false),
|
||||
isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false),
|
||||
}));
|
||||
|
||||
const usageMocks = vi.hoisted(() => ({
|
||||
loadProviderUsageSummary: vi.fn().mockResolvedValue({
|
||||
updatedAt: 0,
|
||||
providers: [],
|
||||
}),
|
||||
formatUsageSummaryLine: vi.fn().mockReturnValue("📊 Usage: Claude 80% left"),
|
||||
resolveUsageProviderId: vi.fn((provider: string) => provider.split("/")[0]),
|
||||
}));
|
||||
|
||||
vi.mock("../infra/provider-usage.js", () => usageMocks);
|
||||
|
||||
const modelCatalogMocks = vi.hoisted(() => ({
|
||||
loadModelCatalog: vi.fn().mockResolvedValue([
|
||||
{
|
||||
provider: "anthropic",
|
||||
id: "claude-opus-4-5",
|
||||
name: "Claude Opus 4.5",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{
|
||||
provider: "openrouter",
|
||||
id: "anthropic/claude-opus-4-5",
|
||||
name: "Claude Opus 4.5 (OpenRouter)",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{ provider: "openai", id: "gpt-4.1-mini", name: "GPT-4.1 mini" },
|
||||
{ provider: "openai", id: "gpt-5.2", name: "GPT-5.2" },
|
||||
{ provider: "openai-codex", id: "gpt-5.2", name: "GPT-5.2 (Codex)" },
|
||||
{ provider: "minimax", id: "MiniMax-M2.1", name: "MiniMax M2.1" },
|
||||
]),
|
||||
resetModelCatalogCacheForTest: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../agents/model-catalog.js", () => modelCatalogMocks);
|
||||
|
||||
import { abortEmbeddedPiRun, runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getReplyFromConfig } from "./reply.js";
|
||||
import {
|
||||
getRunEmbeddedPiAgentMock,
|
||||
installTriggerHandlingE2eTestHooks,
|
||||
MAIN_SESSION_KEY,
|
||||
withTempHome,
|
||||
} from "./reply.triggers.trigger-handling.test-harness.js";
|
||||
|
||||
const MAIN_SESSION_KEY = "agent:main:main";
|
||||
|
||||
const webMocks = vi.hoisted(() => ({
|
||||
webAuthExists: vi.fn().mockResolvedValue(true),
|
||||
getWebAuthAgeMs: vi.fn().mockReturnValue(120_000),
|
||||
readWebSelfId: vi.fn().mockReturnValue({ e164: "+1999" }),
|
||||
}));
|
||||
|
||||
vi.mock("../web/session.js", () => webMocks);
|
||||
|
||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||
return withTempHomeBase(
|
||||
async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockClear();
|
||||
vi.mocked(abortEmbeddedPiRun).mockClear();
|
||||
return await fn(home);
|
||||
},
|
||||
{ prefix: "openclaw-triggers-" },
|
||||
);
|
||||
}
|
||||
|
||||
function _makeCfg(home: string) {
|
||||
return {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "openclaw"),
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
installTriggerHandlingE2eTestHooks();
|
||||
|
||||
describe("trigger handling", () => {
|
||||
it("allows approved sender to toggle elevated mode", async () => {
|
||||
@@ -180,7 +98,7 @@ describe("trigger handling", () => {
|
||||
});
|
||||
it("ignores elevated directive in groups when not mentioned", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||
getRunEmbeddedPiAgentMock().mockResolvedValue({
|
||||
payloads: [{ text: "ok" }],
|
||||
meta: {
|
||||
durationMs: 1,
|
||||
@@ -223,7 +141,7 @@ describe("trigger handling", () => {
|
||||
);
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text).toBeUndefined();
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
expect(getRunEmbeddedPiAgentMock()).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,128 +1,34 @@
|
||||
import fs from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||
|
||||
vi.mock("../agents/pi-embedded.js", () => ({
|
||||
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
|
||||
compactEmbeddedPiSession: vi.fn(),
|
||||
runEmbeddedPiAgent: vi.fn(),
|
||||
queueEmbeddedPiMessage: vi.fn().mockReturnValue(false),
|
||||
resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`,
|
||||
isEmbeddedPiRunActive: vi.fn().mockReturnValue(false),
|
||||
isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false),
|
||||
}));
|
||||
|
||||
const usageMocks = vi.hoisted(() => ({
|
||||
loadProviderUsageSummary: vi.fn().mockResolvedValue({
|
||||
updatedAt: 0,
|
||||
providers: [],
|
||||
}),
|
||||
formatUsageSummaryLine: vi.fn().mockReturnValue("📊 Usage: Claude 80% left"),
|
||||
resolveUsageProviderId: vi.fn((provider: string) => provider.split("/")[0]),
|
||||
}));
|
||||
|
||||
vi.mock("../infra/provider-usage.js", () => usageMocks);
|
||||
|
||||
const modelCatalogMocks = vi.hoisted(() => ({
|
||||
loadModelCatalog: vi.fn().mockResolvedValue([
|
||||
{
|
||||
provider: "anthropic",
|
||||
id: "claude-opus-4-5",
|
||||
name: "Claude Opus 4.5",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{
|
||||
provider: "openrouter",
|
||||
id: "anthropic/claude-opus-4-5",
|
||||
name: "Claude Opus 4.5 (OpenRouter)",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{ provider: "openai", id: "gpt-4.1-mini", name: "GPT-4.1 mini" },
|
||||
{ provider: "openai", id: "gpt-5.2", name: "GPT-5.2" },
|
||||
{ provider: "openai-codex", id: "gpt-5.2", name: "GPT-5.2 (Codex)" },
|
||||
{ provider: "minimax", id: "MiniMax-M2.1", name: "MiniMax M2.1" },
|
||||
]),
|
||||
resetModelCatalogCacheForTest: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../agents/model-catalog.js", () => modelCatalogMocks);
|
||||
|
||||
import { abortEmbeddedPiRun, runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { loadSessionStore } from "../config/sessions.js";
|
||||
import { getReplyFromConfig } from "./reply.js";
|
||||
import {
|
||||
installTriggerHandlingE2eTestHooks,
|
||||
MAIN_SESSION_KEY,
|
||||
makeCfg,
|
||||
withTempHome,
|
||||
} from "./reply.triggers.trigger-handling.test-harness.js";
|
||||
|
||||
const MAIN_SESSION_KEY = "agent:main:main";
|
||||
|
||||
const webMocks = vi.hoisted(() => ({
|
||||
webAuthExists: vi.fn().mockResolvedValue(true),
|
||||
getWebAuthAgeMs: vi.fn().mockReturnValue(120_000),
|
||||
readWebSelfId: vi.fn().mockReturnValue({ e164: "+1999" }),
|
||||
}));
|
||||
|
||||
vi.mock("../web/session.js", () => webMocks);
|
||||
|
||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||
return withTempHomeBase(
|
||||
async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockClear();
|
||||
vi.mocked(abortEmbeddedPiRun).mockClear();
|
||||
return await fn(home);
|
||||
},
|
||||
{ prefix: "openclaw-triggers-" },
|
||||
);
|
||||
}
|
||||
|
||||
function _makeCfg(home: string) {
|
||||
return {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "openclaw"),
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
installTriggerHandlingE2eTestHooks();
|
||||
|
||||
describe("trigger handling", () => {
|
||||
it("allows elevated off in groups without mention", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||
payloads: [{ text: "ok" }],
|
||||
meta: {
|
||||
durationMs: 1,
|
||||
agentMeta: { sessionId: "s", provider: "p", model: "m" },
|
||||
},
|
||||
});
|
||||
const baseCfg = makeCfg(home);
|
||||
const cfg = {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "openclaw"),
|
||||
},
|
||||
},
|
||||
...baseCfg,
|
||||
tools: {
|
||||
elevated: {
|
||||
allowFrom: { whatsapp: ["+1000"] },
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
...baseCfg.channels,
|
||||
whatsapp: {
|
||||
allowFrom: ["+1000"],
|
||||
groups: { "*": { requireMention: false } },
|
||||
},
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
@@ -146,27 +52,24 @@ describe("trigger handling", () => {
|
||||
expect(store["agent:main:whatsapp:group:123@g.us"]?.elevatedLevel).toBe("off");
|
||||
});
|
||||
});
|
||||
|
||||
it("allows elevated directive in groups when mentioned", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const baseCfg = makeCfg(home);
|
||||
const cfg = {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "openclaw"),
|
||||
},
|
||||
},
|
||||
...baseCfg,
|
||||
tools: {
|
||||
elevated: {
|
||||
allowFrom: { whatsapp: ["+1000"] },
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
...baseCfg.channels,
|
||||
whatsapp: {
|
||||
allowFrom: ["+1000"],
|
||||
groups: { "*": { requireMention: true } },
|
||||
},
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
@@ -191,26 +94,23 @@ describe("trigger handling", () => {
|
||||
expect(store["agent:main:whatsapp:group:123@g.us"]?.elevatedLevel).toBe("on");
|
||||
});
|
||||
});
|
||||
|
||||
it("allows elevated directive in direct chats without mentions", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const baseCfg = makeCfg(home);
|
||||
const cfg = {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "openclaw"),
|
||||
},
|
||||
},
|
||||
...baseCfg,
|
||||
tools: {
|
||||
elevated: {
|
||||
allowFrom: { whatsapp: ["+1000"] },
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
...baseCfg.channels,
|
||||
whatsapp: {
|
||||
allowFrom: ["+1000"],
|
||||
},
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
|
||||
@@ -1,95 +1,19 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { normalizeTestText } from "../../test/helpers/normalize-text.js";
|
||||
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||
|
||||
vi.mock("../agents/pi-embedded.js", () => ({
|
||||
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
|
||||
compactEmbeddedPiSession: vi.fn(),
|
||||
runEmbeddedPiAgent: vi.fn(),
|
||||
queueEmbeddedPiMessage: vi.fn().mockReturnValue(false),
|
||||
resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`,
|
||||
isEmbeddedPiRunActive: vi.fn().mockReturnValue(false),
|
||||
isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false),
|
||||
}));
|
||||
|
||||
const usageMocks = vi.hoisted(() => ({
|
||||
loadProviderUsageSummary: vi.fn().mockResolvedValue({
|
||||
updatedAt: 0,
|
||||
providers: [],
|
||||
}),
|
||||
formatUsageSummaryLine: vi.fn().mockReturnValue("📊 Usage: Claude 80% left"),
|
||||
formatUsageWindowSummary: vi.fn().mockReturnValue("Claude 80% left"),
|
||||
resolveUsageProviderId: vi.fn((provider: string) => provider.split("/")[0]),
|
||||
}));
|
||||
|
||||
vi.mock("../infra/provider-usage.js", () => usageMocks);
|
||||
|
||||
const modelCatalogMocks = vi.hoisted(() => ({
|
||||
loadModelCatalog: vi.fn().mockResolvedValue([
|
||||
{
|
||||
provider: "anthropic",
|
||||
id: "claude-opus-4-5",
|
||||
name: "Claude Opus 4.5",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{
|
||||
provider: "openrouter",
|
||||
id: "anthropic/claude-opus-4-5",
|
||||
name: "Claude Opus 4.5 (OpenRouter)",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{ provider: "openai", id: "gpt-4.1-mini", name: "GPT-4.1 mini" },
|
||||
{ provider: "openai", id: "gpt-5.2", name: "GPT-5.2" },
|
||||
{ provider: "openai-codex", id: "gpt-5.2", name: "GPT-5.2 (Codex)" },
|
||||
{ provider: "minimax", id: "MiniMax-M2.1", name: "MiniMax M2.1" },
|
||||
]),
|
||||
resetModelCatalogCacheForTest: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../agents/model-catalog.js", () => modelCatalogMocks);
|
||||
|
||||
import { abortEmbeddedPiRun, runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
||||
import { getReplyFromConfig } from "./reply.js";
|
||||
import {
|
||||
getProviderUsageMocks,
|
||||
getRunEmbeddedPiAgentMock,
|
||||
installTriggerHandlingE2eTestHooks,
|
||||
makeCfg,
|
||||
withTempHome,
|
||||
} from "./reply.triggers.trigger-handling.test-harness.js";
|
||||
|
||||
const _MAIN_SESSION_KEY = "agent:main:main";
|
||||
installTriggerHandlingE2eTestHooks();
|
||||
|
||||
const webMocks = vi.hoisted(() => ({
|
||||
webAuthExists: vi.fn().mockResolvedValue(true),
|
||||
getWebAuthAgeMs: vi.fn().mockReturnValue(120_000),
|
||||
readWebSelfId: vi.fn().mockReturnValue({ e164: "+1999" }),
|
||||
}));
|
||||
|
||||
vi.mock("../web/session.js", () => webMocks);
|
||||
|
||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||
return withTempHomeBase(
|
||||
async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockClear();
|
||||
vi.mocked(abortEmbeddedPiRun).mockClear();
|
||||
return await fn(home);
|
||||
},
|
||||
{ prefix: "openclaw-triggers-" },
|
||||
);
|
||||
}
|
||||
|
||||
function makeCfg(home: string) {
|
||||
return {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "openclaw"),
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
}
|
||||
const usageMocks = getProviderUsageMocks();
|
||||
|
||||
async function readSessionStore(home: string): Promise<Record<string, unknown>> {
|
||||
const raw = await readFile(join(home, "sessions.json"), "utf-8");
|
||||
@@ -101,10 +25,6 @@ function pickFirstStoreEntry<T>(store: Record<string, unknown>): T | undefined {
|
||||
return entries[0];
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("trigger handling", () => {
|
||||
it("filters usage summary to the current model provider", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
@@ -193,7 +113,7 @@ describe("trigger handling", () => {
|
||||
expect(blockReplies.length).toBe(0);
|
||||
expect(replies.length).toBe(1);
|
||||
expect(String(replies[0]?.text ?? "")).toContain("Usage footer: tokens");
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
expect(getRunEmbeddedPiAgentMock()).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -255,7 +175,7 @@ describe("trigger handling", () => {
|
||||
const s3 = await readSessionStore(home);
|
||||
expect(pickFirstStoreEntry<{ responseUsage?: string }>(s3)?.responseUsage).toBeUndefined();
|
||||
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
expect(getRunEmbeddedPiAgentMock()).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -281,12 +201,12 @@ describe("trigger handling", () => {
|
||||
const store = await readSessionStore(home);
|
||||
expect(pickFirstStoreEntry<{ responseUsage?: string }>(store)?.responseUsage).toBe("tokens");
|
||||
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
expect(getRunEmbeddedPiAgentMock()).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("sends one inline status and still returns agent reply for mixed text", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||
getRunEmbeddedPiAgentMock().mockResolvedValue({
|
||||
payloads: [{ text: "agent says hi" }],
|
||||
meta: {
|
||||
durationMs: 1,
|
||||
@@ -315,7 +235,7 @@ describe("trigger handling", () => {
|
||||
expect(String(blockReplies[0]?.text ?? "")).toContain("Model:");
|
||||
expect(replies.length).toBe(1);
|
||||
expect(replies[0]?.text).toBe("agent says hi");
|
||||
const prompt = vi.mocked(runEmbeddedPiAgent).mock.calls[0]?.[0]?.prompt ?? "";
|
||||
const prompt = getRunEmbeddedPiAgentMock().mock.calls[0]?.[0]?.prompt ?? "";
|
||||
expect(prompt).not.toContain("/status");
|
||||
});
|
||||
});
|
||||
@@ -333,7 +253,7 @@ describe("trigger handling", () => {
|
||||
);
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text).toBe("⚙️ Agent was aborted.");
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
expect(getRunEmbeddedPiAgentMock()).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("handles /stop without invoking the agent", async () => {
|
||||
@@ -350,7 +270,7 @@ describe("trigger handling", () => {
|
||||
);
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text).toBe("⚙️ Agent was aborted.");
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
expect(getRunEmbeddedPiAgentMock()).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,107 +1,26 @@
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||
|
||||
vi.mock("../agents/pi-embedded.js", () => ({
|
||||
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
|
||||
compactEmbeddedPiSession: vi.fn(),
|
||||
runEmbeddedPiAgent: vi.fn(),
|
||||
queueEmbeddedPiMessage: vi.fn().mockReturnValue(false),
|
||||
resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`,
|
||||
isEmbeddedPiRunActive: vi.fn().mockReturnValue(false),
|
||||
isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false),
|
||||
}));
|
||||
|
||||
const usageMocks = vi.hoisted(() => ({
|
||||
loadProviderUsageSummary: vi.fn().mockResolvedValue({
|
||||
updatedAt: 0,
|
||||
providers: [],
|
||||
}),
|
||||
formatUsageSummaryLine: vi.fn().mockReturnValue("📊 Usage: Claude 80% left"),
|
||||
resolveUsageProviderId: vi.fn((provider: string) => provider.split("/")[0]),
|
||||
}));
|
||||
|
||||
vi.mock("../infra/provider-usage.js", () => usageMocks);
|
||||
|
||||
const modelCatalogMocks = vi.hoisted(() => ({
|
||||
loadModelCatalog: vi.fn().mockResolvedValue([
|
||||
{
|
||||
provider: "anthropic",
|
||||
id: "claude-opus-4-5",
|
||||
name: "Claude Opus 4.5",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{
|
||||
provider: "openrouter",
|
||||
id: "anthropic/claude-opus-4-5",
|
||||
name: "Claude Opus 4.5 (OpenRouter)",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{ provider: "openai", id: "gpt-4.1-mini", name: "GPT-4.1 mini" },
|
||||
{ provider: "openai", id: "gpt-5.2", name: "GPT-5.2" },
|
||||
{ provider: "openai-codex", id: "gpt-5.2", name: "GPT-5.2 (Codex)" },
|
||||
{ provider: "minimax", id: "MiniMax-M2.1", name: "MiniMax M2.1" },
|
||||
]),
|
||||
resetModelCatalogCacheForTest: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../agents/model-catalog.js", () => modelCatalogMocks);
|
||||
|
||||
import { abortEmbeddedPiRun, runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getReplyFromConfig } from "./reply.js";
|
||||
import {
|
||||
getRunEmbeddedPiAgentMock,
|
||||
installTriggerHandlingE2eTestHooks,
|
||||
makeCfg,
|
||||
withTempHome,
|
||||
} from "./reply.triggers.trigger-handling.test-harness.js";
|
||||
|
||||
const _MAIN_SESSION_KEY = "agent:main:main";
|
||||
|
||||
const webMocks = vi.hoisted(() => ({
|
||||
webAuthExists: vi.fn().mockResolvedValue(true),
|
||||
getWebAuthAgeMs: vi.fn().mockReturnValue(120_000),
|
||||
readWebSelfId: vi.fn().mockReturnValue({ e164: "+1999" }),
|
||||
}));
|
||||
|
||||
vi.mock("../web/session.js", () => webMocks);
|
||||
|
||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||
return withTempHomeBase(
|
||||
async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockClear();
|
||||
vi.mocked(abortEmbeddedPiRun).mockClear();
|
||||
return await fn(home);
|
||||
},
|
||||
{ prefix: "openclaw-triggers-" },
|
||||
);
|
||||
}
|
||||
|
||||
function makeCfg(home: string) {
|
||||
return {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "openclaw"),
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
installTriggerHandlingE2eTestHooks();
|
||||
|
||||
describe("trigger handling", () => {
|
||||
it("handles inline /commands and strips it before the agent", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
|
||||
runEmbeddedPiAgentMock.mockResolvedValue({
|
||||
payloads: [{ text: "ok" }],
|
||||
meta: {
|
||||
durationMs: 1,
|
||||
agentMeta: { sessionId: "s", provider: "p", model: "m" },
|
||||
},
|
||||
});
|
||||
|
||||
const blockReplies: Array<{ text?: string }> = [];
|
||||
const res = await getReplyFromConfig(
|
||||
{
|
||||
@@ -117,24 +36,28 @@ describe("trigger handling", () => {
|
||||
},
|
||||
makeCfg(home),
|
||||
);
|
||||
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(blockReplies.length).toBe(1);
|
||||
expect(blockReplies[0]?.text).toContain("Slash commands");
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalled();
|
||||
const prompt = vi.mocked(runEmbeddedPiAgent).mock.calls[0]?.[0]?.prompt ?? "";
|
||||
expect(runEmbeddedPiAgentMock).toHaveBeenCalled();
|
||||
const prompt = runEmbeddedPiAgentMock.mock.calls[0]?.[0]?.prompt ?? "";
|
||||
expect(prompt).not.toContain("/commands");
|
||||
expect(text).toBe("ok");
|
||||
});
|
||||
});
|
||||
|
||||
it("handles inline /whoami and strips it before the agent", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
|
||||
runEmbeddedPiAgentMock.mockResolvedValue({
|
||||
payloads: [{ text: "ok" }],
|
||||
meta: {
|
||||
durationMs: 1,
|
||||
agentMeta: { sessionId: "s", provider: "p", model: "m" },
|
||||
},
|
||||
});
|
||||
|
||||
const blockReplies: Array<{ text?: string }> = [];
|
||||
const res = await getReplyFromConfig(
|
||||
{
|
||||
@@ -151,31 +74,31 @@ describe("trigger handling", () => {
|
||||
},
|
||||
makeCfg(home),
|
||||
);
|
||||
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(blockReplies.length).toBe(1);
|
||||
expect(blockReplies[0]?.text).toContain("Identity");
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalled();
|
||||
const prompt = vi.mocked(runEmbeddedPiAgent).mock.calls[0]?.[0]?.prompt ?? "";
|
||||
expect(runEmbeddedPiAgentMock).toHaveBeenCalled();
|
||||
const prompt = runEmbeddedPiAgentMock.mock.calls[0]?.[0]?.prompt ?? "";
|
||||
expect(prompt).not.toContain("/whoami");
|
||||
expect(text).toBe("ok");
|
||||
});
|
||||
});
|
||||
|
||||
it("drops /status for unauthorized senders", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
|
||||
const baseCfg = makeCfg(home);
|
||||
const cfg = {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "openclaw"),
|
||||
},
|
||||
},
|
||||
...baseCfg,
|
||||
channels: {
|
||||
...baseCfg.channels,
|
||||
whatsapp: {
|
||||
allowFrom: ["+1000"],
|
||||
},
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{
|
||||
Body: "/status",
|
||||
@@ -187,26 +110,26 @@ describe("trigger handling", () => {
|
||||
{},
|
||||
cfg,
|
||||
);
|
||||
|
||||
expect(res).toBeUndefined();
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("drops /whoami for unauthorized senders", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
|
||||
const baseCfg = makeCfg(home);
|
||||
const cfg = {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "openclaw"),
|
||||
},
|
||||
},
|
||||
...baseCfg,
|
||||
channels: {
|
||||
...baseCfg.channels,
|
||||
whatsapp: {
|
||||
allowFrom: ["+1000"],
|
||||
},
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{
|
||||
Body: "/whoami",
|
||||
@@ -218,8 +141,9 @@ describe("trigger handling", () => {
|
||||
{},
|
||||
cfg,
|
||||
);
|
||||
|
||||
expect(res).toBeUndefined();
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,102 +1,21 @@
|
||||
import fs from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||
|
||||
vi.mock("../agents/pi-embedded.js", () => ({
|
||||
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
|
||||
compactEmbeddedPiSession: vi.fn(),
|
||||
runEmbeddedPiAgent: vi.fn(),
|
||||
queueEmbeddedPiMessage: vi.fn().mockReturnValue(false),
|
||||
resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`,
|
||||
isEmbeddedPiRunActive: vi.fn().mockReturnValue(false),
|
||||
isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false),
|
||||
}));
|
||||
|
||||
const usageMocks = vi.hoisted(() => ({
|
||||
loadProviderUsageSummary: vi.fn().mockResolvedValue({
|
||||
updatedAt: 0,
|
||||
providers: [],
|
||||
}),
|
||||
formatUsageSummaryLine: vi.fn().mockReturnValue("📊 Usage: Claude 80% left"),
|
||||
resolveUsageProviderId: vi.fn((provider: string) => provider.split("/")[0]),
|
||||
}));
|
||||
|
||||
vi.mock("../infra/provider-usage.js", () => usageMocks);
|
||||
|
||||
const modelCatalogMocks = vi.hoisted(() => ({
|
||||
loadModelCatalog: vi.fn().mockResolvedValue([
|
||||
{
|
||||
provider: "anthropic",
|
||||
id: "claude-opus-4-5",
|
||||
name: "Claude Opus 4.5",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{
|
||||
provider: "openrouter",
|
||||
id: "anthropic/claude-opus-4-5",
|
||||
name: "Claude Opus 4.5 (OpenRouter)",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{ provider: "openai", id: "gpt-4.1-mini", name: "GPT-4.1 mini" },
|
||||
{ provider: "openai", id: "gpt-5.2", name: "GPT-5.2" },
|
||||
{ provider: "openai-codex", id: "gpt-5.2", name: "GPT-5.2 (Codex)" },
|
||||
{ provider: "minimax", id: "MiniMax-M2.1", name: "MiniMax M2.1" },
|
||||
]),
|
||||
resetModelCatalogCacheForTest: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../agents/model-catalog.js", () => modelCatalogMocks);
|
||||
|
||||
import { abortEmbeddedPiRun, runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getReplyFromConfig } from "./reply.js";
|
||||
import {
|
||||
getRunEmbeddedPiAgentMock,
|
||||
installTriggerHandlingE2eTestHooks,
|
||||
MAIN_SESSION_KEY,
|
||||
makeCfg,
|
||||
withTempHome,
|
||||
} from "./reply.triggers.trigger-handling.test-harness.js";
|
||||
|
||||
const MAIN_SESSION_KEY = "agent:main:main";
|
||||
|
||||
const webMocks = vi.hoisted(() => ({
|
||||
webAuthExists: vi.fn().mockResolvedValue(true),
|
||||
getWebAuthAgeMs: vi.fn().mockReturnValue(120_000),
|
||||
readWebSelfId: vi.fn().mockReturnValue({ e164: "+1999" }),
|
||||
}));
|
||||
|
||||
vi.mock("../web/session.js", () => webMocks);
|
||||
|
||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||
return withTempHomeBase(
|
||||
async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockClear();
|
||||
vi.mocked(abortEmbeddedPiRun).mockClear();
|
||||
return await fn(home);
|
||||
},
|
||||
{ prefix: "openclaw-triggers-" },
|
||||
);
|
||||
}
|
||||
|
||||
function makeCfg(home: string) {
|
||||
return {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "openclaw"),
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
installTriggerHandlingE2eTestHooks();
|
||||
|
||||
describe("trigger handling", () => {
|
||||
it("ignores inline elevated directive for unapproved sender", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||
getRunEmbeddedPiAgentMock().mockResolvedValue({
|
||||
payloads: [{ text: "ok" }],
|
||||
meta: {
|
||||
durationMs: 1,
|
||||
@@ -136,7 +55,7 @@ describe("trigger handling", () => {
|
||||
);
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text).not.toContain("elevated is not available right now");
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalled();
|
||||
expect(getRunEmbeddedPiAgentMock()).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("uses tools.elevated.allowFrom.discord for elevated approval", async () => {
|
||||
@@ -204,12 +123,12 @@ describe("trigger handling", () => {
|
||||
);
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text).toContain("tools.elevated.allowFrom.discord");
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
expect(getRunEmbeddedPiAgentMock()).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("returns a context overflow fallback when the embedded agent throws", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockRejectedValue(new Error("Context window exceeded"));
|
||||
getRunEmbeddedPiAgentMock().mockRejectedValue(new Error("Context window exceeded"));
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{
|
||||
@@ -225,7 +144,7 @@ describe("trigger handling", () => {
|
||||
expect(text).toBe(
|
||||
"⚠️ Context overflow — prompt too large for this model. Try a shorter message or a larger-context model.",
|
||||
);
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
|
||||
expect(getRunEmbeddedPiAgentMock()).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,103 +1,22 @@
|
||||
import fs from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||
|
||||
vi.mock("../agents/pi-embedded.js", () => ({
|
||||
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
|
||||
compactEmbeddedPiSession: vi.fn(),
|
||||
runEmbeddedPiAgent: vi.fn(),
|
||||
queueEmbeddedPiMessage: vi.fn().mockReturnValue(false),
|
||||
resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`,
|
||||
isEmbeddedPiRunActive: vi.fn().mockReturnValue(false),
|
||||
isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false),
|
||||
}));
|
||||
|
||||
const usageMocks = vi.hoisted(() => ({
|
||||
loadProviderUsageSummary: vi.fn().mockResolvedValue({
|
||||
updatedAt: 0,
|
||||
providers: [],
|
||||
}),
|
||||
formatUsageSummaryLine: vi.fn().mockReturnValue("📊 Usage: Claude 80% left"),
|
||||
resolveUsageProviderId: vi.fn((provider: string) => provider.split("/")[0]),
|
||||
}));
|
||||
|
||||
vi.mock("../infra/provider-usage.js", () => usageMocks);
|
||||
|
||||
const modelCatalogMocks = vi.hoisted(() => ({
|
||||
loadModelCatalog: vi.fn().mockResolvedValue([
|
||||
{
|
||||
provider: "anthropic",
|
||||
id: "claude-opus-4-5",
|
||||
name: "Claude Opus 4.5",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{
|
||||
provider: "openrouter",
|
||||
id: "anthropic/claude-opus-4-5",
|
||||
name: "Claude Opus 4.5 (OpenRouter)",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{ provider: "openai", id: "gpt-4.1-mini", name: "GPT-4.1 mini" },
|
||||
{ provider: "openai", id: "gpt-5.2", name: "GPT-5.2" },
|
||||
{ provider: "openai-codex", id: "gpt-5.2", name: "GPT-5.2 (Codex)" },
|
||||
{ provider: "minimax", id: "MiniMax-M2.1", name: "MiniMax M2.1" },
|
||||
]),
|
||||
resetModelCatalogCacheForTest: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../agents/model-catalog.js", () => modelCatalogMocks);
|
||||
|
||||
import { abortEmbeddedPiRun, runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getReplyFromConfig } from "./reply.js";
|
||||
import {
|
||||
getRunEmbeddedPiAgentMock,
|
||||
installTriggerHandlingE2eTestHooks,
|
||||
MAIN_SESSION_KEY,
|
||||
makeCfg,
|
||||
withTempHome,
|
||||
} from "./reply.triggers.trigger-handling.test-harness.js";
|
||||
import { HEARTBEAT_TOKEN } from "./tokens.js";
|
||||
|
||||
const _MAIN_SESSION_KEY = "agent:main:main";
|
||||
|
||||
const webMocks = vi.hoisted(() => ({
|
||||
webAuthExists: vi.fn().mockResolvedValue(true),
|
||||
getWebAuthAgeMs: vi.fn().mockReturnValue(120_000),
|
||||
readWebSelfId: vi.fn().mockReturnValue({ e164: "+1999" }),
|
||||
}));
|
||||
|
||||
vi.mock("../web/session.js", () => webMocks);
|
||||
|
||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||
return withTempHomeBase(
|
||||
async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockClear();
|
||||
vi.mocked(abortEmbeddedPiRun).mockClear();
|
||||
return await fn(home);
|
||||
},
|
||||
{ prefix: "openclaw-triggers-" },
|
||||
);
|
||||
}
|
||||
|
||||
function makeCfg(home: string) {
|
||||
return {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "openclaw"),
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
installTriggerHandlingE2eTestHooks();
|
||||
|
||||
describe("trigger handling", () => {
|
||||
it("includes the error cause when the embedded agent throws", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockRejectedValue(new Error("sandbox is not defined."));
|
||||
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
|
||||
runEmbeddedPiAgentMock.mockRejectedValue(new Error("sandbox is not defined."));
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{
|
||||
@@ -113,12 +32,14 @@ describe("trigger handling", () => {
|
||||
expect(text).toBe(
|
||||
"⚠️ Agent failed before reply: sandbox is not defined.\nLogs: openclaw logs --follow",
|
||||
);
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
|
||||
expect(runEmbeddedPiAgentMock).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
it("uses heartbeat model override for heartbeat runs", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
|
||||
runEmbeddedPiAgentMock.mockResolvedValue({
|
||||
payloads: [{ text: "ok" }],
|
||||
meta: {
|
||||
durationMs: 1,
|
||||
@@ -128,9 +49,9 @@ describe("trigger handling", () => {
|
||||
|
||||
const cfg = makeCfg(home);
|
||||
await fs.writeFile(
|
||||
join(home, "sessions.json"),
|
||||
cfg.session.store,
|
||||
JSON.stringify({
|
||||
[_MAIN_SESSION_KEY]: {
|
||||
[MAIN_SESSION_KEY]: {
|
||||
sessionId: "main",
|
||||
updatedAt: Date.now(),
|
||||
providerOverride: "openai",
|
||||
@@ -157,14 +78,16 @@ describe("trigger handling", () => {
|
||||
cfg,
|
||||
);
|
||||
|
||||
const call = vi.mocked(runEmbeddedPiAgent).mock.calls[0]?.[0];
|
||||
const call = runEmbeddedPiAgentMock.mock.calls[0]?.[0];
|
||||
expect(call?.provider).toBe("anthropic");
|
||||
expect(call?.model).toBe("claude-haiku-4-5-20251001");
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps stored model override for heartbeat runs when heartbeat model is not configured", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
|
||||
runEmbeddedPiAgentMock.mockResolvedValue({
|
||||
payloads: [{ text: "ok" }],
|
||||
meta: {
|
||||
durationMs: 1,
|
||||
@@ -172,10 +95,11 @@ describe("trigger handling", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const cfg = makeCfg(home);
|
||||
await fs.writeFile(
|
||||
join(home, "sessions.json"),
|
||||
cfg.session.store,
|
||||
JSON.stringify({
|
||||
[_MAIN_SESSION_KEY]: {
|
||||
[MAIN_SESSION_KEY]: {
|
||||
sessionId: "main",
|
||||
updatedAt: Date.now(),
|
||||
providerOverride: "openai",
|
||||
@@ -192,17 +116,19 @@ describe("trigger handling", () => {
|
||||
To: "+2000",
|
||||
},
|
||||
{ isHeartbeat: true },
|
||||
makeCfg(home),
|
||||
cfg,
|
||||
);
|
||||
|
||||
const call = vi.mocked(runEmbeddedPiAgent).mock.calls[0]?.[0];
|
||||
const call = runEmbeddedPiAgentMock.mock.calls[0]?.[0];
|
||||
expect(call?.provider).toBe("openai");
|
||||
expect(call?.model).toBe("gpt-5.2");
|
||||
});
|
||||
});
|
||||
|
||||
it("suppresses HEARTBEAT_OK replies outside heartbeat runs", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
|
||||
runEmbeddedPiAgentMock.mockResolvedValue({
|
||||
payloads: [{ text: HEARTBEAT_TOKEN }],
|
||||
meta: {
|
||||
durationMs: 1,
|
||||
@@ -221,12 +147,14 @@ describe("trigger handling", () => {
|
||||
);
|
||||
|
||||
expect(res).toBeUndefined();
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
|
||||
expect(runEmbeddedPiAgentMock).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
it("strips HEARTBEAT_OK at edges outside heartbeat runs", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
|
||||
runEmbeddedPiAgentMock.mockResolvedValue({
|
||||
payloads: [{ text: `${HEARTBEAT_TOKEN} hello` }],
|
||||
meta: {
|
||||
durationMs: 1,
|
||||
@@ -248,8 +176,10 @@ describe("trigger handling", () => {
|
||||
expect(text).toBe("hello");
|
||||
});
|
||||
});
|
||||
|
||||
it("updates group activation when the owner sends /activation", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
|
||||
const cfg = makeCfg(home);
|
||||
const res = await getReplyFromConfig(
|
||||
{
|
||||
@@ -271,7 +201,7 @@ describe("trigger handling", () => {
|
||||
{ groupActivation?: string }
|
||||
>;
|
||||
expect(store["agent:main:whatsapp:group:123@g.us"]?.groupActivation).toBe("always");
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,122 +1,39 @@
|
||||
import fs from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||
|
||||
vi.mock("../agents/pi-embedded.js", () => ({
|
||||
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
|
||||
compactEmbeddedPiSession: vi.fn(),
|
||||
runEmbeddedPiAgent: vi.fn(),
|
||||
queueEmbeddedPiMessage: vi.fn().mockReturnValue(false),
|
||||
resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`,
|
||||
isEmbeddedPiRunActive: vi.fn().mockReturnValue(false),
|
||||
isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false),
|
||||
}));
|
||||
|
||||
const usageMocks = vi.hoisted(() => ({
|
||||
loadProviderUsageSummary: vi.fn().mockResolvedValue({
|
||||
updatedAt: 0,
|
||||
providers: [],
|
||||
}),
|
||||
formatUsageSummaryLine: vi.fn().mockReturnValue("📊 Usage: Claude 80% left"),
|
||||
resolveUsageProviderId: vi.fn((provider: string) => provider.split("/")[0]),
|
||||
}));
|
||||
|
||||
vi.mock("../infra/provider-usage.js", () => usageMocks);
|
||||
|
||||
const modelCatalogMocks = vi.hoisted(() => ({
|
||||
loadModelCatalog: vi.fn().mockResolvedValue([
|
||||
{
|
||||
provider: "anthropic",
|
||||
id: "claude-opus-4-5",
|
||||
name: "Claude Opus 4.5",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{
|
||||
provider: "openrouter",
|
||||
id: "anthropic/claude-opus-4-5",
|
||||
name: "Claude Opus 4.5 (OpenRouter)",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{ provider: "openai", id: "gpt-4.1-mini", name: "GPT-4.1 mini" },
|
||||
{ provider: "openai", id: "gpt-5.2", name: "GPT-5.2" },
|
||||
{ provider: "openai-codex", id: "gpt-5.2", name: "GPT-5.2 (Codex)" },
|
||||
{ provider: "minimax", id: "MiniMax-M2.1", name: "MiniMax M2.1" },
|
||||
]),
|
||||
resetModelCatalogCacheForTest: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../agents/model-catalog.js", () => modelCatalogMocks);
|
||||
|
||||
import { abortEmbeddedPiRun, runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getReplyFromConfig } from "./reply.js";
|
||||
import {
|
||||
getRunEmbeddedPiAgentMock,
|
||||
installTriggerHandlingE2eTestHooks,
|
||||
MAIN_SESSION_KEY,
|
||||
makeCfg,
|
||||
withTempHome,
|
||||
} from "./reply.triggers.trigger-handling.test-harness.js";
|
||||
|
||||
const MAIN_SESSION_KEY = "agent:main:main";
|
||||
|
||||
const webMocks = vi.hoisted(() => ({
|
||||
webAuthExists: vi.fn().mockResolvedValue(true),
|
||||
getWebAuthAgeMs: vi.fn().mockReturnValue(120_000),
|
||||
readWebSelfId: vi.fn().mockReturnValue({ e164: "+1999" }),
|
||||
}));
|
||||
|
||||
vi.mock("../web/session.js", () => webMocks);
|
||||
|
||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||
return withTempHomeBase(
|
||||
async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockClear();
|
||||
vi.mocked(abortEmbeddedPiRun).mockClear();
|
||||
return await fn(home);
|
||||
},
|
||||
{ prefix: "openclaw-triggers-" },
|
||||
);
|
||||
}
|
||||
|
||||
function makeCfg(home: string) {
|
||||
return {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "openclaw"),
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
installTriggerHandlingE2eTestHooks();
|
||||
|
||||
describe("trigger handling", () => {
|
||||
it("keeps inline /status for unauthorized senders", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
|
||||
runEmbeddedPiAgentMock.mockResolvedValue({
|
||||
payloads: [{ text: "ok" }],
|
||||
meta: {
|
||||
durationMs: 1,
|
||||
agentMeta: { sessionId: "s", provider: "p", model: "m" },
|
||||
},
|
||||
});
|
||||
|
||||
const baseCfg = makeCfg(home);
|
||||
const cfg = {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "openclaw"),
|
||||
},
|
||||
},
|
||||
...baseCfg,
|
||||
channels: {
|
||||
...baseCfg.channels,
|
||||
whatsapp: {
|
||||
allowFrom: ["+1000"],
|
||||
},
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{
|
||||
Body: "please /status now",
|
||||
@@ -130,35 +47,35 @@ describe("trigger handling", () => {
|
||||
);
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text).toBe("ok");
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalled();
|
||||
const prompt = vi.mocked(runEmbeddedPiAgent).mock.calls[0]?.[0]?.prompt ?? "";
|
||||
expect(runEmbeddedPiAgentMock).toHaveBeenCalled();
|
||||
const prompt = runEmbeddedPiAgentMock.mock.calls[0]?.[0]?.prompt ?? "";
|
||||
// Not allowlisted: inline /status is treated as plain text and is not stripped.
|
||||
expect(prompt).toContain("/status");
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps inline /help for unauthorized senders", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
|
||||
runEmbeddedPiAgentMock.mockResolvedValue({
|
||||
payloads: [{ text: "ok" }],
|
||||
meta: {
|
||||
durationMs: 1,
|
||||
agentMeta: { sessionId: "s", provider: "p", model: "m" },
|
||||
},
|
||||
});
|
||||
|
||||
const baseCfg = makeCfg(home);
|
||||
const cfg = {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "openclaw"),
|
||||
},
|
||||
},
|
||||
...baseCfg,
|
||||
channels: {
|
||||
...baseCfg.channels,
|
||||
whatsapp: {
|
||||
allowFrom: ["+1000"],
|
||||
},
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{
|
||||
Body: "please /help now",
|
||||
@@ -172,13 +89,15 @@ describe("trigger handling", () => {
|
||||
);
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text).toBe("ok");
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalled();
|
||||
const prompt = vi.mocked(runEmbeddedPiAgent).mock.calls[0]?.[0]?.prompt ?? "";
|
||||
expect(runEmbeddedPiAgentMock).toHaveBeenCalled();
|
||||
const prompt = runEmbeddedPiAgentMock.mock.calls[0]?.[0]?.prompt ?? "";
|
||||
expect(prompt).toContain("/help");
|
||||
});
|
||||
});
|
||||
|
||||
it("returns help without invoking the agent", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
|
||||
const res = await getReplyFromConfig(
|
||||
{
|
||||
Body: "/help",
|
||||
@@ -193,24 +112,21 @@ describe("trigger handling", () => {
|
||||
expect(text).toContain("Help");
|
||||
expect(text).toContain("Session");
|
||||
expect(text).toContain("More: /commands for full list");
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("allows owner to set send policy", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const baseCfg = makeCfg(home);
|
||||
const cfg = {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "openclaw"),
|
||||
},
|
||||
},
|
||||
...baseCfg,
|
||||
channels: {
|
||||
...baseCfg.channels,
|
||||
whatsapp: {
|
||||
allowFrom: ["+1000"],
|
||||
},
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
|
||||
@@ -1,102 +1,21 @@
|
||||
import fs from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||
|
||||
vi.mock("../agents/pi-embedded.js", () => ({
|
||||
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
|
||||
compactEmbeddedPiSession: vi.fn(),
|
||||
runEmbeddedPiAgent: vi.fn(),
|
||||
queueEmbeddedPiMessage: vi.fn().mockReturnValue(false),
|
||||
resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`,
|
||||
isEmbeddedPiRunActive: vi.fn().mockReturnValue(false),
|
||||
isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false),
|
||||
}));
|
||||
|
||||
const usageMocks = vi.hoisted(() => ({
|
||||
loadProviderUsageSummary: vi.fn().mockResolvedValue({
|
||||
updatedAt: 0,
|
||||
providers: [],
|
||||
}),
|
||||
formatUsageSummaryLine: vi.fn().mockReturnValue("📊 Usage: Claude 80% left"),
|
||||
resolveUsageProviderId: vi.fn((provider: string) => provider.split("/")[0]),
|
||||
}));
|
||||
|
||||
vi.mock("../infra/provider-usage.js", () => usageMocks);
|
||||
|
||||
const modelCatalogMocks = vi.hoisted(() => ({
|
||||
loadModelCatalog: vi.fn().mockResolvedValue([
|
||||
{
|
||||
provider: "anthropic",
|
||||
id: "claude-opus-4-5",
|
||||
name: "Claude Opus 4.5",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{
|
||||
provider: "openrouter",
|
||||
id: "anthropic/claude-opus-4-5",
|
||||
name: "Claude Opus 4.5 (OpenRouter)",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{ provider: "openai", id: "gpt-4.1-mini", name: "GPT-4.1 mini" },
|
||||
{ provider: "openai", id: "gpt-5.2", name: "GPT-5.2" },
|
||||
{ provider: "openai-codex", id: "gpt-5.2", name: "GPT-5.2 (Codex)" },
|
||||
{ provider: "minimax", id: "MiniMax-M2.1", name: "MiniMax M2.1" },
|
||||
]),
|
||||
resetModelCatalogCacheForTest: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../agents/model-catalog.js", () => modelCatalogMocks);
|
||||
|
||||
import { abortEmbeddedPiRun, runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveSessionKey } from "../config/sessions.js";
|
||||
import { getReplyFromConfig } from "./reply.js";
|
||||
import {
|
||||
getRunEmbeddedPiAgentMock,
|
||||
installTriggerHandlingE2eTestHooks,
|
||||
makeCfg,
|
||||
withTempHome,
|
||||
} from "./reply.triggers.trigger-handling.test-harness.js";
|
||||
|
||||
const _MAIN_SESSION_KEY = "agent:main:main";
|
||||
|
||||
const webMocks = vi.hoisted(() => ({
|
||||
webAuthExists: vi.fn().mockResolvedValue(true),
|
||||
getWebAuthAgeMs: vi.fn().mockReturnValue(120_000),
|
||||
readWebSelfId: vi.fn().mockReturnValue({ e164: "+1999" }),
|
||||
}));
|
||||
|
||||
vi.mock("../web/session.js", () => webMocks);
|
||||
|
||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||
return withTempHomeBase(
|
||||
async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockClear();
|
||||
vi.mocked(abortEmbeddedPiRun).mockClear();
|
||||
return await fn(home);
|
||||
},
|
||||
{ prefix: "openclaw-triggers-" },
|
||||
);
|
||||
}
|
||||
|
||||
function makeCfg(home: string) {
|
||||
return {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "openclaw"),
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
installTriggerHandlingE2eTestHooks();
|
||||
|
||||
describe("trigger handling", () => {
|
||||
it("reports active auth profile and key snippet in status", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
|
||||
const cfg = makeCfg(home);
|
||||
const agentDir = join(home, ".openclaw", "agents", "main", "agent");
|
||||
await fs.mkdir(agentDir, { recursive: true });
|
||||
@@ -153,21 +72,24 @@ describe("trigger handling", () => {
|
||||
);
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text).toContain("api-key");
|
||||
expect(text).toMatch(/…|\.{3}/);
|
||||
expect(text).toMatch(/\u2026|\.{3}/);
|
||||
expect(text).toContain("(anthropic:work)");
|
||||
expect(text).not.toContain("mixed");
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("strips inline /status and still runs the agent", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
|
||||
runEmbeddedPiAgentMock.mockResolvedValue({
|
||||
payloads: [{ text: "ok" }],
|
||||
meta: {
|
||||
durationMs: 1,
|
||||
agentMeta: { sessionId: "s", provider: "p", model: "m" },
|
||||
},
|
||||
});
|
||||
|
||||
const blockReplies: Array<{ text?: string }> = [];
|
||||
await getReplyFromConfig(
|
||||
{
|
||||
@@ -186,18 +108,20 @@ describe("trigger handling", () => {
|
||||
},
|
||||
makeCfg(home),
|
||||
);
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalled();
|
||||
expect(runEmbeddedPiAgentMock).toHaveBeenCalled();
|
||||
// Allowlisted senders: inline /status runs immediately (like /help) and is
|
||||
// stripped from the prompt; the remaining text continues through the agent.
|
||||
expect(blockReplies.length).toBe(1);
|
||||
expect(String(blockReplies[0]?.text ?? "").length).toBeGreaterThan(0);
|
||||
const prompt = vi.mocked(runEmbeddedPiAgent).mock.calls[0]?.[0]?.prompt ?? "";
|
||||
const prompt = runEmbeddedPiAgentMock.mock.calls[0]?.[0]?.prompt ?? "";
|
||||
expect(prompt).not.toContain("/status");
|
||||
});
|
||||
});
|
||||
|
||||
it("handles inline /help and strips it before the agent", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
|
||||
runEmbeddedPiAgentMock.mockResolvedValue({
|
||||
payloads: [{ text: "ok" }],
|
||||
meta: {
|
||||
durationMs: 1,
|
||||
@@ -222,8 +146,8 @@ describe("trigger handling", () => {
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(blockReplies.length).toBe(1);
|
||||
expect(blockReplies[0]?.text).toContain("Help");
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalled();
|
||||
const prompt = vi.mocked(runEmbeddedPiAgent).mock.calls[0]?.[0]?.prompt ?? "";
|
||||
expect(runEmbeddedPiAgentMock).toHaveBeenCalled();
|
||||
const prompt = runEmbeddedPiAgentMock.mock.calls[0]?.[0]?.prompt ?? "";
|
||||
expect(prompt).not.toContain("/help");
|
||||
expect(text).toBe("ok");
|
||||
});
|
||||
|
||||
@@ -1,108 +1,23 @@
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||
|
||||
vi.mock("../agents/pi-embedded.js", () => ({
|
||||
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
|
||||
compactEmbeddedPiSession: vi.fn(),
|
||||
runEmbeddedPiAgent: vi.fn(),
|
||||
queueEmbeddedPiMessage: vi.fn().mockReturnValue(false),
|
||||
resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`,
|
||||
isEmbeddedPiRunActive: vi.fn().mockReturnValue(false),
|
||||
isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false),
|
||||
}));
|
||||
|
||||
const usageMocks = vi.hoisted(() => ({
|
||||
loadProviderUsageSummary: vi.fn().mockResolvedValue({
|
||||
updatedAt: 0,
|
||||
providers: [],
|
||||
}),
|
||||
formatUsageSummaryLine: vi.fn().mockReturnValue("📊 Usage: Claude 80% left"),
|
||||
resolveUsageProviderId: vi.fn((provider: string) => provider.split("/")[0]),
|
||||
}));
|
||||
|
||||
vi.mock("../infra/provider-usage.js", () => usageMocks);
|
||||
|
||||
const modelCatalogMocks = vi.hoisted(() => ({
|
||||
loadModelCatalog: vi.fn().mockResolvedValue([
|
||||
{
|
||||
provider: "anthropic",
|
||||
id: "claude-opus-4-5",
|
||||
name: "Claude Opus 4.5",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{
|
||||
provider: "openrouter",
|
||||
id: "anthropic/claude-opus-4-5",
|
||||
name: "Claude Opus 4.5 (OpenRouter)",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{ provider: "openai", id: "gpt-4.1-mini", name: "GPT-4.1 mini" },
|
||||
{ provider: "openai", id: "gpt-5.2", name: "GPT-5.2" },
|
||||
{ provider: "openai-codex", id: "gpt-5.2", name: "GPT-5.2 (Codex)" },
|
||||
{ provider: "minimax", id: "MiniMax-M2.1", name: "MiniMax M2.1" },
|
||||
]),
|
||||
resetModelCatalogCacheForTest: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../agents/model-catalog.js", () => modelCatalogMocks);
|
||||
|
||||
import {
|
||||
abortEmbeddedPiRun,
|
||||
compactEmbeddedPiSession,
|
||||
runEmbeddedPiAgent,
|
||||
} from "../agents/pi-embedded.js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { loadSessionStore, resolveSessionKey } from "../config/sessions.js";
|
||||
import { getReplyFromConfig } from "./reply.js";
|
||||
import {
|
||||
getCompactEmbeddedPiSessionMock,
|
||||
getRunEmbeddedPiAgentMock,
|
||||
installTriggerHandlingE2eTestHooks,
|
||||
makeCfg,
|
||||
withTempHome,
|
||||
} from "./reply.triggers.trigger-handling.test-harness.js";
|
||||
|
||||
const _MAIN_SESSION_KEY = "agent:main:main";
|
||||
|
||||
const webMocks = vi.hoisted(() => ({
|
||||
webAuthExists: vi.fn().mockResolvedValue(true),
|
||||
getWebAuthAgeMs: vi.fn().mockReturnValue(120_000),
|
||||
readWebSelfId: vi.fn().mockReturnValue({ e164: "+1999" }),
|
||||
}));
|
||||
|
||||
vi.mock("../web/session.js", () => webMocks);
|
||||
|
||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||
return withTempHomeBase(
|
||||
async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockClear();
|
||||
vi.mocked(abortEmbeddedPiRun).mockClear();
|
||||
return await fn(home);
|
||||
},
|
||||
{ prefix: "openclaw-triggers-" },
|
||||
);
|
||||
}
|
||||
|
||||
function makeCfg(home: string) {
|
||||
return {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "openclaw"),
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
installTriggerHandlingE2eTestHooks();
|
||||
|
||||
describe("trigger handling", () => {
|
||||
it("runs /compact as a gated command", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const storePath = join(tmpdir(), `openclaw-session-test-${Date.now()}.json`);
|
||||
vi.mocked(compactEmbeddedPiSession).mockResolvedValue({
|
||||
getCompactEmbeddedPiSessionMock().mockResolvedValue({
|
||||
ok: true,
|
||||
compacted: true,
|
||||
result: {
|
||||
@@ -139,8 +54,8 @@ describe("trigger handling", () => {
|
||||
);
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text?.startsWith("⚙️ Compacted")).toBe(true);
|
||||
expect(compactEmbeddedPiSession).toHaveBeenCalledOnce();
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
expect(getCompactEmbeddedPiSessionMock()).toHaveBeenCalledOnce();
|
||||
expect(getRunEmbeddedPiAgentMock()).not.toHaveBeenCalled();
|
||||
const store = loadSessionStore(storePath);
|
||||
const sessionKey = resolveSessionKey("per-sender", {
|
||||
Body: "/compact focus on decisions",
|
||||
@@ -152,8 +67,8 @@ describe("trigger handling", () => {
|
||||
});
|
||||
it("runs /compact for non-default agents without transcript path validation failures", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(compactEmbeddedPiSession).mockClear();
|
||||
vi.mocked(compactEmbeddedPiSession).mockResolvedValue({
|
||||
getCompactEmbeddedPiSessionMock().mockClear();
|
||||
getCompactEmbeddedPiSessionMock().mockResolvedValue({
|
||||
ok: true,
|
||||
compacted: true,
|
||||
result: {
|
||||
@@ -177,16 +92,16 @@ describe("trigger handling", () => {
|
||||
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text?.startsWith("⚙️ Compacted")).toBe(true);
|
||||
expect(compactEmbeddedPiSession).toHaveBeenCalledOnce();
|
||||
expect(vi.mocked(compactEmbeddedPiSession).mock.calls[0]?.[0]?.sessionFile).toContain(
|
||||
expect(getCompactEmbeddedPiSessionMock()).toHaveBeenCalledOnce();
|
||||
expect(getCompactEmbeddedPiSessionMock().mock.calls[0]?.[0]?.sessionFile).toContain(
|
||||
join("agents", "worker1", "sessions"),
|
||||
);
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
expect(getRunEmbeddedPiAgentMock()).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("ignores think directives that only appear in the context wrapper", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||
getRunEmbeddedPiAgentMock().mockResolvedValue({
|
||||
payloads: [{ text: "ok" }],
|
||||
meta: {
|
||||
durationMs: 1,
|
||||
@@ -212,8 +127,8 @@ describe("trigger handling", () => {
|
||||
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text).toBe("ok");
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
|
||||
const prompt = vi.mocked(runEmbeddedPiAgent).mock.calls[0]?.[0]?.prompt ?? "";
|
||||
expect(getRunEmbeddedPiAgentMock()).toHaveBeenCalledOnce();
|
||||
const prompt = getRunEmbeddedPiAgentMock().mock.calls[0]?.[0]?.prompt ?? "";
|
||||
expect(prompt).toContain("Give me the status");
|
||||
expect(prompt).not.toContain("/thinking high");
|
||||
expect(prompt).not.toContain("/think high");
|
||||
@@ -221,7 +136,7 @@ describe("trigger handling", () => {
|
||||
});
|
||||
it("does not emit directive acks for heartbeats with /think", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||
getRunEmbeddedPiAgentMock().mockResolvedValue({
|
||||
payloads: [{ text: "ok" }],
|
||||
meta: {
|
||||
durationMs: 1,
|
||||
@@ -242,7 +157,7 @@ describe("trigger handling", () => {
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text).toBe("ok");
|
||||
expect(text).not.toMatch(/Thinking level set/i);
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
|
||||
expect(getRunEmbeddedPiAgentMock()).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,102 +1,19 @@
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||
|
||||
vi.mock("../agents/pi-embedded.js", () => ({
|
||||
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
|
||||
compactEmbeddedPiSession: vi.fn(),
|
||||
runEmbeddedPiAgent: vi.fn(),
|
||||
queueEmbeddedPiMessage: vi.fn().mockReturnValue(false),
|
||||
resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`,
|
||||
isEmbeddedPiRunActive: vi.fn().mockReturnValue(false),
|
||||
isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false),
|
||||
}));
|
||||
|
||||
const usageMocks = vi.hoisted(() => ({
|
||||
loadProviderUsageSummary: vi.fn().mockResolvedValue({
|
||||
updatedAt: 0,
|
||||
providers: [],
|
||||
}),
|
||||
formatUsageSummaryLine: vi.fn().mockReturnValue("📊 Usage: Claude 80% left"),
|
||||
resolveUsageProviderId: vi.fn((provider: string) => provider.split("/")[0]),
|
||||
}));
|
||||
|
||||
vi.mock("../infra/provider-usage.js", () => usageMocks);
|
||||
|
||||
const modelCatalogMocks = vi.hoisted(() => ({
|
||||
loadModelCatalog: vi.fn().mockResolvedValue([
|
||||
{
|
||||
provider: "anthropic",
|
||||
id: "claude-opus-4-5",
|
||||
name: "Claude Opus 4.5",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{
|
||||
provider: "openrouter",
|
||||
id: "anthropic/claude-opus-4-5",
|
||||
name: "Claude Opus 4.5 (OpenRouter)",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{ provider: "openai", id: "gpt-4.1-mini", name: "GPT-4.1 mini" },
|
||||
{ provider: "openai", id: "gpt-5.2", name: "GPT-5.2" },
|
||||
{ provider: "openai-codex", id: "gpt-5.2", name: "GPT-5.2 (Codex)" },
|
||||
{ provider: "minimax", id: "MiniMax-M2.1", name: "MiniMax M2.1" },
|
||||
]),
|
||||
resetModelCatalogCacheForTest: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../agents/model-catalog.js", () => modelCatalogMocks);
|
||||
|
||||
import { abortEmbeddedPiRun, runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getReplyFromConfig } from "./reply.js";
|
||||
import {
|
||||
getRunEmbeddedPiAgentMock,
|
||||
installTriggerHandlingE2eTestHooks,
|
||||
withTempHome,
|
||||
} from "./reply.triggers.trigger-handling.test-harness.js";
|
||||
|
||||
const _MAIN_SESSION_KEY = "agent:main:main";
|
||||
|
||||
const webMocks = vi.hoisted(() => ({
|
||||
webAuthExists: vi.fn().mockResolvedValue(true),
|
||||
getWebAuthAgeMs: vi.fn().mockReturnValue(120_000),
|
||||
readWebSelfId: vi.fn().mockReturnValue({ e164: "+1999" }),
|
||||
}));
|
||||
|
||||
vi.mock("../web/session.js", () => webMocks);
|
||||
|
||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||
return withTempHomeBase(
|
||||
async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockClear();
|
||||
vi.mocked(abortEmbeddedPiRun).mockClear();
|
||||
return await fn(home);
|
||||
},
|
||||
{ prefix: "openclaw-triggers-" },
|
||||
);
|
||||
}
|
||||
|
||||
function _makeCfg(home: string) {
|
||||
return {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "openclaw"),
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
installTriggerHandlingE2eTestHooks();
|
||||
|
||||
describe("trigger handling", () => {
|
||||
it("runs a greeting prompt for a bare /reset", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||
getRunEmbeddedPiAgentMock().mockResolvedValue({
|
||||
payloads: [{ text: "hello" }],
|
||||
meta: {
|
||||
durationMs: 1,
|
||||
@@ -131,8 +48,8 @@ describe("trigger handling", () => {
|
||||
);
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text).toBe("hello");
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
|
||||
const prompt = vi.mocked(runEmbeddedPiAgent).mock.calls[0]?.[0]?.prompt ?? "";
|
||||
expect(getRunEmbeddedPiAgentMock()).toHaveBeenCalledOnce();
|
||||
const prompt = getRunEmbeddedPiAgentMock().mock.calls[0]?.[0]?.prompt ?? "";
|
||||
expect(prompt).toContain("A new session was started via /new or /reset");
|
||||
});
|
||||
});
|
||||
@@ -164,7 +81,7 @@ describe("trigger handling", () => {
|
||||
},
|
||||
);
|
||||
expect(res).toBeUndefined();
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
expect(getRunEmbeddedPiAgentMock()).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("blocks /reset for non-owner senders", async () => {
|
||||
@@ -195,7 +112,7 @@ describe("trigger handling", () => {
|
||||
},
|
||||
);
|
||||
expect(res).toBeUndefined();
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
expect(getRunEmbeddedPiAgentMock()).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,97 +1,14 @@
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { normalizeTestText } from "../../test/helpers/normalize-text.js";
|
||||
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||
|
||||
vi.mock("../agents/pi-embedded.js", () => ({
|
||||
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
|
||||
compactEmbeddedPiSession: vi.fn(),
|
||||
runEmbeddedPiAgent: vi.fn(),
|
||||
queueEmbeddedPiMessage: vi.fn().mockReturnValue(false),
|
||||
resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`,
|
||||
isEmbeddedPiRunActive: vi.fn().mockReturnValue(false),
|
||||
isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false),
|
||||
}));
|
||||
|
||||
const usageMocks = vi.hoisted(() => ({
|
||||
loadProviderUsageSummary: vi.fn().mockResolvedValue({
|
||||
updatedAt: 0,
|
||||
providers: [],
|
||||
}),
|
||||
formatUsageSummaryLine: vi.fn().mockReturnValue("📊 Usage: Claude 80% left"),
|
||||
resolveUsageProviderId: vi.fn((provider: string) => provider.split("/")[0]),
|
||||
}));
|
||||
|
||||
vi.mock("../infra/provider-usage.js", () => usageMocks);
|
||||
|
||||
const modelCatalogMocks = vi.hoisted(() => ({
|
||||
loadModelCatalog: vi.fn().mockResolvedValue([
|
||||
{
|
||||
provider: "anthropic",
|
||||
id: "claude-opus-4-5",
|
||||
name: "Claude Opus 4.5",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{
|
||||
provider: "openrouter",
|
||||
id: "anthropic/claude-opus-4-5",
|
||||
name: "Claude Opus 4.5 (OpenRouter)",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{ provider: "openai", id: "gpt-4.1-mini", name: "GPT-4.1 mini" },
|
||||
{ provider: "openai", id: "gpt-5.2", name: "GPT-5.2" },
|
||||
{ provider: "openai-codex", id: "gpt-5.2", name: "GPT-5.2 (Codex)" },
|
||||
{ provider: "minimax", id: "MiniMax-M2.1", name: "MiniMax M2.1" },
|
||||
]),
|
||||
resetModelCatalogCacheForTest: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../agents/model-catalog.js", () => modelCatalogMocks);
|
||||
|
||||
import { abortEmbeddedPiRun, runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
||||
import { getReplyFromConfig } from "./reply.js";
|
||||
import {
|
||||
getRunEmbeddedPiAgentMock,
|
||||
installTriggerHandlingE2eTestHooks,
|
||||
makeCfg,
|
||||
withTempHome,
|
||||
} from "./reply.triggers.trigger-handling.test-harness.js";
|
||||
|
||||
const _MAIN_SESSION_KEY = "agent:main:main";
|
||||
|
||||
const webMocks = vi.hoisted(() => ({
|
||||
webAuthExists: vi.fn().mockResolvedValue(true),
|
||||
getWebAuthAgeMs: vi.fn().mockReturnValue(120_000),
|
||||
readWebSelfId: vi.fn().mockReturnValue({ e164: "+1999" }),
|
||||
}));
|
||||
|
||||
vi.mock("../web/session.js", () => webMocks);
|
||||
|
||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||
return withTempHomeBase(
|
||||
async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockClear();
|
||||
vi.mocked(abortEmbeddedPiRun).mockClear();
|
||||
return await fn(home);
|
||||
},
|
||||
{ prefix: "openclaw-triggers-" },
|
||||
);
|
||||
}
|
||||
|
||||
function makeCfg(home: string) {
|
||||
return {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "openclaw"),
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
installTriggerHandlingE2eTestHooks();
|
||||
|
||||
describe("trigger handling", () => {
|
||||
it("shows endpoint default in /model status when not configured", async () => {
|
||||
@@ -153,6 +70,7 @@ describe("trigger handling", () => {
|
||||
});
|
||||
it("rejects /restart by default", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
|
||||
const res = await getReplyFromConfig(
|
||||
{
|
||||
Body: " [Dec 5] /restart",
|
||||
@@ -165,11 +83,12 @@ describe("trigger handling", () => {
|
||||
);
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text).toContain("/restart is disabled");
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("restarts when enabled", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
|
||||
const cfg = { ...makeCfg(home), commands: { restart: true } };
|
||||
const res = await getReplyFromConfig(
|
||||
{
|
||||
@@ -183,11 +102,12 @@ describe("trigger handling", () => {
|
||||
);
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text?.startsWith("⚙️ Restarting") || text?.startsWith("⚠️ Restart failed")).toBe(true);
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("reports status without invoking the agent", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
|
||||
const res = await getReplyFromConfig(
|
||||
{
|
||||
Body: "/status",
|
||||
@@ -200,7 +120,7 @@ describe("trigger handling", () => {
|
||||
);
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text).toContain("OpenClaw");
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,98 +1,14 @@
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { normalizeTestText } from "../../test/helpers/normalize-text.js";
|
||||
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||
|
||||
vi.mock("../agents/pi-embedded.js", () => ({
|
||||
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
|
||||
compactEmbeddedPiSession: vi.fn(),
|
||||
runEmbeddedPiAgent: vi.fn(),
|
||||
queueEmbeddedPiMessage: vi.fn().mockReturnValue(false),
|
||||
resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`,
|
||||
isEmbeddedPiRunActive: vi.fn().mockReturnValue(false),
|
||||
isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false),
|
||||
}));
|
||||
|
||||
const usageMocks = vi.hoisted(() => ({
|
||||
loadProviderUsageSummary: vi.fn().mockResolvedValue({
|
||||
updatedAt: 0,
|
||||
providers: [],
|
||||
}),
|
||||
formatUsageSummaryLine: vi.fn().mockReturnValue("📊 Usage: Claude 80% left"),
|
||||
resolveUsageProviderId: vi.fn((provider: string) => provider.split("/")[0]),
|
||||
}));
|
||||
|
||||
vi.mock("../infra/provider-usage.js", () => usageMocks);
|
||||
|
||||
const modelCatalogMocks = vi.hoisted(() => ({
|
||||
loadModelCatalog: vi.fn().mockResolvedValue([
|
||||
{
|
||||
provider: "anthropic",
|
||||
id: "claude-opus-4-5",
|
||||
name: "Claude Opus 4.5",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{
|
||||
provider: "openrouter",
|
||||
id: "anthropic/claude-opus-4-5",
|
||||
name: "Claude Opus 4.5 (OpenRouter)",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{ provider: "openai", id: "gpt-4.1-mini", name: "GPT-4.1 mini" },
|
||||
{ provider: "openai", id: "gpt-5.2", name: "GPT-5.2" },
|
||||
{ provider: "openai-codex", id: "gpt-5.2", name: "GPT-5.2 (Codex)" },
|
||||
{ provider: "minimax", id: "MiniMax-M2.1", name: "MiniMax M2.1" },
|
||||
]),
|
||||
resetModelCatalogCacheForTest: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../agents/model-catalog.js", () => modelCatalogMocks);
|
||||
|
||||
import { abortEmbeddedPiRun, runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
||||
import { loadSessionStore } from "../config/sessions.js";
|
||||
import { getReplyFromConfig } from "./reply.js";
|
||||
import {
|
||||
installTriggerHandlingE2eTestHooks,
|
||||
makeCfg,
|
||||
withTempHome,
|
||||
} from "./reply.triggers.trigger-handling.test-harness.js";
|
||||
|
||||
const _MAIN_SESSION_KEY = "agent:main:main";
|
||||
|
||||
const webMocks = vi.hoisted(() => ({
|
||||
webAuthExists: vi.fn().mockResolvedValue(true),
|
||||
getWebAuthAgeMs: vi.fn().mockReturnValue(120_000),
|
||||
readWebSelfId: vi.fn().mockReturnValue({ e164: "+1999" }),
|
||||
}));
|
||||
|
||||
vi.mock("../web/session.js", () => webMocks);
|
||||
|
||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||
return withTempHomeBase(
|
||||
async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockClear();
|
||||
vi.mocked(abortEmbeddedPiRun).mockClear();
|
||||
return await fn(home);
|
||||
},
|
||||
{ prefix: "openclaw-triggers-" },
|
||||
);
|
||||
}
|
||||
|
||||
function makeCfg(home: string) {
|
||||
return {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "openclaw"),
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
installTriggerHandlingE2eTestHooks();
|
||||
|
||||
describe("trigger handling", () => {
|
||||
it("shows a /model summary and points to /models", async () => {
|
||||
|
||||
@@ -1,99 +1,19 @@
|
||||
import fs from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||
|
||||
vi.mock("../agents/pi-embedded.js", () => ({
|
||||
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
|
||||
compactEmbeddedPiSession: vi.fn(),
|
||||
runEmbeddedPiAgent: vi.fn(),
|
||||
queueEmbeddedPiMessage: vi.fn().mockReturnValue(false),
|
||||
resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`,
|
||||
isEmbeddedPiRunActive: vi.fn().mockReturnValue(false),
|
||||
isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false),
|
||||
}));
|
||||
|
||||
const usageMocks = vi.hoisted(() => ({
|
||||
loadProviderUsageSummary: vi.fn().mockResolvedValue({
|
||||
updatedAt: 0,
|
||||
providers: [],
|
||||
}),
|
||||
formatUsageSummaryLine: vi.fn().mockReturnValue("📊 Usage: Claude 80% left"),
|
||||
resolveUsageProviderId: vi.fn((provider: string) => provider.split("/")[0]),
|
||||
}));
|
||||
|
||||
vi.mock("../infra/provider-usage.js", () => usageMocks);
|
||||
|
||||
const modelCatalogMocks = vi.hoisted(() => ({
|
||||
loadModelCatalog: vi.fn().mockResolvedValue([
|
||||
{
|
||||
provider: "anthropic",
|
||||
id: "claude-opus-4-5",
|
||||
name: "Claude Opus 4.5",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{
|
||||
provider: "openrouter",
|
||||
id: "anthropic/claude-opus-4-5",
|
||||
name: "Claude Opus 4.5 (OpenRouter)",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{ provider: "openai", id: "gpt-4.1-mini", name: "GPT-4.1 mini" },
|
||||
{ provider: "openai", id: "gpt-5.2", name: "GPT-5.2" },
|
||||
{ provider: "openai-codex", id: "gpt-5.2", name: "GPT-5.2 (Codex)" },
|
||||
{ provider: "minimax", id: "MiniMax-M2.1", name: "MiniMax M2.1" },
|
||||
]),
|
||||
resetModelCatalogCacheForTest: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../agents/model-catalog.js", () => modelCatalogMocks);
|
||||
|
||||
import { abortEmbeddedPiRun, runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { loadSessionStore } from "../config/sessions.js";
|
||||
import { getReplyFromConfig } from "./reply.js";
|
||||
import {
|
||||
getAbortEmbeddedPiRunMock,
|
||||
getRunEmbeddedPiAgentMock,
|
||||
installTriggerHandlingE2eTestHooks,
|
||||
MAIN_SESSION_KEY,
|
||||
makeCfg,
|
||||
withTempHome,
|
||||
} from "./reply.triggers.trigger-handling.test-harness.js";
|
||||
import { enqueueFollowupRun, getFollowupQueueDepth, type FollowupRun } from "./reply/queue.js";
|
||||
|
||||
const MAIN_SESSION_KEY = "agent:main:main";
|
||||
|
||||
const webMocks = vi.hoisted(() => ({
|
||||
webAuthExists: vi.fn().mockResolvedValue(true),
|
||||
getWebAuthAgeMs: vi.fn().mockReturnValue(120_000),
|
||||
readWebSelfId: vi.fn().mockReturnValue({ e164: "+1999" }),
|
||||
}));
|
||||
|
||||
vi.mock("../web/session.js", () => webMocks);
|
||||
|
||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||
return withTempHomeBase(
|
||||
async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockClear();
|
||||
vi.mocked(abortEmbeddedPiRun).mockClear();
|
||||
return await fn(home);
|
||||
},
|
||||
{ prefix: "openclaw-triggers-" },
|
||||
);
|
||||
}
|
||||
|
||||
function makeCfg(home: string) {
|
||||
return {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "openclaw"),
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
installTriggerHandlingE2eTestHooks();
|
||||
|
||||
describe("trigger handling", () => {
|
||||
it("targets the active session for native /stop", async () => {
|
||||
@@ -160,7 +80,7 @@ describe("trigger handling", () => {
|
||||
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text).toBe("⚙️ Agent was aborted.");
|
||||
expect(vi.mocked(abortEmbeddedPiRun)).toHaveBeenCalledWith(targetSessionId);
|
||||
expect(getAbortEmbeddedPiRunMock()).toHaveBeenCalledWith(targetSessionId);
|
||||
const store = loadSessionStore(cfg.session.store);
|
||||
expect(store[targetSessionKey]?.abortedLastRun).toBe(true);
|
||||
expect(getFollowupQueueDepth(targetSessionKey)).toBe(0);
|
||||
@@ -212,7 +132,7 @@ describe("trigger handling", () => {
|
||||
expect(store[targetSessionKey]?.modelOverride).toBe("gpt-4.1-mini");
|
||||
expect(store[slashSessionKey]).toBeUndefined();
|
||||
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||
getRunEmbeddedPiAgentMock().mockResolvedValue({
|
||||
payloads: [{ text: "ok" }],
|
||||
meta: {
|
||||
durationMs: 5,
|
||||
@@ -233,8 +153,8 @@ describe("trigger handling", () => {
|
||||
cfg,
|
||||
);
|
||||
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
|
||||
expect(vi.mocked(runEmbeddedPiAgent).mock.calls[0]?.[0]).toEqual(
|
||||
expect(getRunEmbeddedPiAgentMock()).toHaveBeenCalledOnce();
|
||||
expect(getRunEmbeddedPiAgentMock().mock.calls[0]?.[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
provider: "openai",
|
||||
model: "gpt-4.1-mini",
|
||||
|
||||
134
src/auto-reply/reply.triggers.trigger-handling.test-harness.ts
Normal file
134
src/auto-reply/reply.triggers.trigger-handling.test-harness.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import { join } from "node:path";
|
||||
import { afterEach, vi } from "vitest";
|
||||
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||
|
||||
const piEmbeddedMocks = vi.hoisted(() => ({
|
||||
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
|
||||
compactEmbeddedPiSession: vi.fn(),
|
||||
runEmbeddedPiAgent: vi.fn(),
|
||||
queueEmbeddedPiMessage: vi.fn().mockReturnValue(false),
|
||||
isEmbeddedPiRunActive: vi.fn().mockReturnValue(false),
|
||||
isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false),
|
||||
}));
|
||||
|
||||
export function getAbortEmbeddedPiRunMock() {
|
||||
return piEmbeddedMocks.abortEmbeddedPiRun;
|
||||
}
|
||||
|
||||
export function getCompactEmbeddedPiSessionMock() {
|
||||
return piEmbeddedMocks.compactEmbeddedPiSession;
|
||||
}
|
||||
|
||||
export function getRunEmbeddedPiAgentMock() {
|
||||
return piEmbeddedMocks.runEmbeddedPiAgent;
|
||||
}
|
||||
|
||||
export function getQueueEmbeddedPiMessageMock() {
|
||||
return piEmbeddedMocks.queueEmbeddedPiMessage;
|
||||
}
|
||||
|
||||
vi.mock("../agents/pi-embedded.js", () => ({
|
||||
abortEmbeddedPiRun: (...args: unknown[]) => piEmbeddedMocks.abortEmbeddedPiRun(...args),
|
||||
compactEmbeddedPiSession: (...args: unknown[]) =>
|
||||
piEmbeddedMocks.compactEmbeddedPiSession(...args),
|
||||
runEmbeddedPiAgent: (...args: unknown[]) => piEmbeddedMocks.runEmbeddedPiAgent(...args),
|
||||
queueEmbeddedPiMessage: (...args: unknown[]) => piEmbeddedMocks.queueEmbeddedPiMessage(...args),
|
||||
resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`,
|
||||
isEmbeddedPiRunActive: (...args: unknown[]) => piEmbeddedMocks.isEmbeddedPiRunActive(...args),
|
||||
isEmbeddedPiRunStreaming: (...args: unknown[]) =>
|
||||
piEmbeddedMocks.isEmbeddedPiRunStreaming(...args),
|
||||
}));
|
||||
|
||||
const providerUsageMocks = vi.hoisted(() => ({
|
||||
loadProviderUsageSummary: vi.fn().mockResolvedValue({
|
||||
updatedAt: 0,
|
||||
providers: [],
|
||||
}),
|
||||
formatUsageSummaryLine: vi.fn().mockReturnValue("📊 Usage: Claude 80% left"),
|
||||
formatUsageWindowSummary: vi.fn().mockReturnValue("Claude 80% left"),
|
||||
resolveUsageProviderId: vi.fn((provider: string) => provider.split("/")[0]),
|
||||
}));
|
||||
|
||||
export function getProviderUsageMocks() {
|
||||
return providerUsageMocks;
|
||||
}
|
||||
|
||||
vi.mock("../infra/provider-usage.js", () => providerUsageMocks);
|
||||
|
||||
const modelCatalogMocks = vi.hoisted(() => ({
|
||||
loadModelCatalog: vi.fn().mockResolvedValue([
|
||||
{
|
||||
provider: "anthropic",
|
||||
id: "claude-opus-4-5",
|
||||
name: "Claude Opus 4.5",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{
|
||||
provider: "openrouter",
|
||||
id: "anthropic/claude-opus-4-5",
|
||||
name: "Claude Opus 4.5 (OpenRouter)",
|
||||
contextWindow: 200000,
|
||||
},
|
||||
{ provider: "openai", id: "gpt-4.1-mini", name: "GPT-4.1 mini" },
|
||||
{ provider: "openai", id: "gpt-5.2", name: "GPT-5.2" },
|
||||
{ provider: "openai-codex", id: "gpt-5.2", name: "GPT-5.2 (Codex)" },
|
||||
{ provider: "minimax", id: "MiniMax-M2.1", name: "MiniMax M2.1" },
|
||||
]),
|
||||
resetModelCatalogCacheForTest: vi.fn(),
|
||||
}));
|
||||
|
||||
export function getModelCatalogMocks() {
|
||||
return modelCatalogMocks;
|
||||
}
|
||||
|
||||
vi.mock("../agents/model-catalog.js", () => modelCatalogMocks);
|
||||
|
||||
const webSessionMocks = vi.hoisted(() => ({
|
||||
webAuthExists: vi.fn().mockResolvedValue(true),
|
||||
getWebAuthAgeMs: vi.fn().mockReturnValue(120_000),
|
||||
readWebSelfId: vi.fn().mockReturnValue({ e164: "+1999" }),
|
||||
}));
|
||||
|
||||
export function getWebSessionMocks() {
|
||||
return webSessionMocks;
|
||||
}
|
||||
|
||||
vi.mock("../web/session.js", () => webSessionMocks);
|
||||
|
||||
export const MAIN_SESSION_KEY = "agent:main:main";
|
||||
|
||||
export async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||
return withTempHomeBase(
|
||||
async (home) => {
|
||||
// Avoid cross-test leakage if a test doesn't touch these mocks.
|
||||
piEmbeddedMocks.runEmbeddedPiAgent.mockClear();
|
||||
piEmbeddedMocks.abortEmbeddedPiRun.mockClear();
|
||||
piEmbeddedMocks.compactEmbeddedPiSession.mockClear();
|
||||
return await fn(home);
|
||||
},
|
||||
{ prefix: "openclaw-triggers-" },
|
||||
);
|
||||
}
|
||||
|
||||
export function makeCfg(home: string) {
|
||||
return {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "openclaw"),
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
}
|
||||
|
||||
export function installTriggerHandlingE2eTestHooks() {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user