test: consolidate trigger-handling status and heartbeat scenarios

This commit is contained in:
Peter Steinberger 2026-02-23 17:19:25 +00:00
parent a8a4fa5b88
commit f7e45ce947
2 changed files with 136 additions and 172 deletions

View File

@ -122,15 +122,19 @@ describe("trigger handling", () => {
);
});
});
it("emits /status once (no duplicate inline + final)", async () => {
it("reports /status once without invoking the agent", async () => {
await withTempHome(async (home) => {
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
const { blockReplies, replies } = await runCommandAndCollectReplies({
home,
body: "/status",
});
expect(blockReplies.length).toBe(0);
expect(replies.length).toBe(1);
expect(String(replies[0]?.text ?? "")).toContain("Model:");
const text = String(replies[0]?.text ?? "");
expect(text).toContain("Model:");
expect(text).toContain("OpenClaw");
expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled();
});
});
it("sets per-response usage footer via /usage", async () => {
@ -255,39 +259,22 @@ describe("trigger handling", () => {
expect(prompt).not.toContain("/status");
});
});
it("aborts even with timestamp prefix", async () => {
it("handles /stop command variants without invoking the agent", async () => {
await withTempHome(async (home) => {
await expectStopAbortWithoutAgent({
home,
body: "[Dec 5 10:00] stop",
from: "+1000",
});
});
});
it("handles /stop without invoking the agent", async () => {
await withTempHome(async (home) => {
await expectStopAbortWithoutAgent({
home,
body: "/stop",
from: "+1003",
});
for (const testCase of [
{ body: "[Dec 5 10:00] stop", from: "+1000" },
{ body: "/stop", from: "+1003" },
] as const) {
await expectStopAbortWithoutAgent({ home, body: testCase.body, from: testCase.from });
}
});
});
it("shows endpoint default in /model status when not configured", async () => {
await withTempHome(async (home) => {
const cfg = makeCfg(home);
const res = await getReplyFromConfig(modelStatusCtx, {}, cfg);
const text = Array.isArray(res) ? res[0]?.text : res?.text;
expect(normalizeTestText(text ?? "")).toContain("endpoint: default");
});
});
it("includes endpoint details in /model status when configured", async () => {
it("shows model status defaults and configured endpoint details", async () => {
await withTempHome(async (home) => {
const defaultCfg = makeCfg(home);
const cfg = {
...makeCfg(home),
...defaultCfg,
models: {
providers: {
minimax: {
@ -297,20 +284,27 @@ describe("trigger handling", () => {
},
},
} as unknown as OpenClawConfig;
const res = await getReplyFromConfig(modelStatusCtx, {}, cfg);
const defaultStatus = await getReplyFromConfig(modelStatusCtx, {}, defaultCfg);
const configuredStatus = await getReplyFromConfig(modelStatusCtx, {}, cfg);
const text = Array.isArray(res) ? res[0]?.text : res?.text;
const normalized = normalizeTestText(text ?? "");
expect(normalized).toContain(
expect(
normalizeTestText(
(Array.isArray(defaultStatus) ? defaultStatus[0]?.text : defaultStatus?.text) ?? "",
),
).toContain("endpoint: default");
const configuredText = Array.isArray(configuredStatus)
? configuredStatus[0]?.text
: configuredStatus?.text;
expect(normalizeTestText(configuredText ?? "")).toContain(
"[minimax] endpoint: https://api.minimax.io/anthropic api: anthropic-messages auth:",
);
});
});
it("restarts by default", async () => {
it("restarts by default and rejects /restart when disabled", async () => {
await withTempHome(async (home) => {
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
const res = await getReplyFromConfig(
const enabledRes = await getReplyFromConfig(
{
Body: " [Dec 5] /restart",
From: "+1001",
@ -320,17 +314,13 @@ describe("trigger handling", () => {
{},
makeCfg(home),
);
const text = Array.isArray(res) ? res[0]?.text : res?.text;
expect(text?.startsWith("⚙️ Restarting") || text?.startsWith("⚠️ Restart failed")).toBe(true);
expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled();
});
});
const enabledText = Array.isArray(enabledRes) ? enabledRes[0]?.text : enabledRes?.text;
expect(
enabledText?.startsWith("⚙️ Restarting") || enabledText?.startsWith("⚠️ Restart failed"),
).toBe(true);
it("rejects /restart when explicitly disabled", async () => {
await withTempHome(async (home) => {
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
const cfg = { ...makeCfg(home), commands: { restart: false } } as OpenClawConfig;
const res = await getReplyFromConfig(
const disabledCfg = { ...makeCfg(home), commands: { restart: false } } as OpenClawConfig;
const disabledRes = await getReplyFromConfig(
{
Body: "/restart",
From: "+1001",
@ -338,29 +328,11 @@ describe("trigger handling", () => {
CommandAuthorized: true,
},
{},
cfg,
disabledCfg,
);
const text = Array.isArray(res) ? res[0]?.text : res?.text;
expect(text).toContain("/restart is disabled");
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",
From: "+1002",
To: "+2000",
CommandAuthorized: true,
},
{},
makeCfg(home),
);
const text = Array.isArray(res) ? res[0]?.text : res?.text;
expect(text).toContain("OpenClaw");
const disabledText = Array.isArray(disabledRes) ? disabledRes[0]?.text : disabledRes?.text;
expect(disabledText).toContain("/restart is disabled");
expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled();
});
});

View File

@ -1,5 +1,4 @@
import fs from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
@ -91,72 +90,66 @@ describe("trigger handling", () => {
});
});
it("uses heartbeat model override for heartbeat runs", async () => {
it("uses heartbeat override when configured and falls back to stored model override", async () => {
await withTempHome(async (home) => {
const runEmbeddedPiAgentMock = mockEmbeddedOkPayload();
const cfg = makeCfg(home);
await writeStoredModelOverride(cfg);
cfg.agents = {
...cfg.agents,
defaults: {
...cfg.agents?.defaults,
heartbeat: { model: "anthropic/claude-haiku-4-5-20251001" },
const cases = [
{
label: "heartbeat-override",
setup: (cfg: ReturnType<typeof makeCfg>) => {
cfg.agents = {
...cfg.agents,
defaults: {
...cfg.agents?.defaults,
heartbeat: { model: "anthropic/claude-haiku-4-5-20251001" },
},
};
},
expected: { provider: "anthropic", model: "claude-haiku-4-5-20251001" },
},
};
{
label: "stored-override",
setup: () => undefined,
expected: { provider: "openai", model: "gpt-5.2" },
},
] as const;
await getReplyFromConfig(BASE_MESSAGE, { isHeartbeat: true }, cfg);
for (const testCase of cases) {
runEmbeddedPiAgentMock.mockClear();
const cfg = makeCfg(home);
cfg.session = { ...cfg.session, store: join(home, `${testCase.label}.sessions.json`) };
await writeStoredModelOverride(cfg);
testCase.setup(cfg);
await getReplyFromConfig(BASE_MESSAGE, { isHeartbeat: true }, cfg);
const call = runEmbeddedPiAgentMock.mock.calls[0]?.[0];
expect(call?.provider).toBe("anthropic");
expect(call?.model).toBe("claude-haiku-4-5-20251001");
const call = runEmbeddedPiAgentMock.mock.calls[0]?.[0];
expect(call?.provider).toBe(testCase.expected.provider);
expect(call?.model).toBe(testCase.expected.model);
}
});
});
it("keeps stored model override for heartbeat runs when heartbeat model is not configured", async () => {
await withTempHome(async (home) => {
const runEmbeddedPiAgentMock = mockEmbeddedOkPayload();
const cfg = makeCfg(home);
await writeStoredModelOverride(cfg);
await getReplyFromConfig(BASE_MESSAGE, { isHeartbeat: true }, cfg);
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 () => {
it("suppresses or strips HEARTBEAT_OK replies outside heartbeat runs", async () => {
await withTempHome(async (home) => {
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
runEmbeddedPiAgentMock.mockResolvedValue({
payloads: [{ text: HEARTBEAT_TOKEN }],
meta: {
durationMs: 1,
agentMeta: { sessionId: "s", provider: "p", model: "m" },
},
});
const cases = [
{ text: HEARTBEAT_TOKEN, expected: undefined },
{ text: `${HEARTBEAT_TOKEN} hello`, expected: "hello" },
] as const;
const res = await getReplyFromConfig(BASE_MESSAGE, {}, makeCfg(home));
expect(res).toBeUndefined();
expect(runEmbeddedPiAgentMock).toHaveBeenCalledOnce();
});
});
it("strips HEARTBEAT_OK at edges outside heartbeat runs", async () => {
await withTempHome(async (home) => {
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
runEmbeddedPiAgentMock.mockResolvedValue({
payloads: [{ text: `${HEARTBEAT_TOKEN} hello` }],
meta: {
durationMs: 1,
agentMeta: { sessionId: "s", provider: "p", model: "m" },
},
});
const res = await getReplyFromConfig(BASE_MESSAGE, {}, makeCfg(home));
expect(maybeReplyText(res)).toBe("hello");
for (const testCase of cases) {
runEmbeddedPiAgentMock.mockClear();
runEmbeddedPiAgentMock.mockResolvedValue({
payloads: [{ text: testCase.text }],
meta: {
durationMs: 1,
agentMeta: { sessionId: "s", provider: "p", model: "m" },
},
});
const res = await getReplyFromConfig(BASE_MESSAGE, {}, makeCfg(home));
expect(maybeReplyText(res)).toBe(testCase.expected);
expect(runEmbeddedPiAgentMock).toHaveBeenCalledOnce();
}
});
});
@ -187,60 +180,59 @@ describe("trigger handling", () => {
});
});
it("runs /compact as a gated command", async () => {
it("runs /compact for main and non-default agents without invoking the embedded run path", async () => {
await withTempHome(async (home) => {
const storePath = join(tmpdir(), `openclaw-session-test-${Date.now()}.json`);
const cfg = makeCfg(home);
cfg.session = { ...cfg.session, store: storePath };
mockSuccessfulCompaction();
{
const storePath = join(home, "compact-main.sessions.json");
const cfg = makeCfg(home);
cfg.session = { ...cfg.session, store: storePath };
mockSuccessfulCompaction();
const request = {
Body: "/compact focus on decisions",
From: "+1003",
To: "+2000",
};
const res = await getReplyFromConfig(
{
...request,
CommandAuthorized: true,
},
{},
cfg,
);
const text = maybeReplyText(res);
expect(text?.startsWith("⚙️ Compacted")).toBe(true);
expect(getCompactEmbeddedPiSessionMock()).toHaveBeenCalledOnce();
expect(getRunEmbeddedPiAgentMock()).not.toHaveBeenCalled();
const store = loadSessionStore(storePath);
const sessionKey = resolveSessionKey("per-sender", request);
expect(store[sessionKey]?.compactionCount).toBe(1);
});
});
it("runs /compact for non-default agents without transcript path validation failures", async () => {
await withTempHome(async (home) => {
getCompactEmbeddedPiSessionMock().mockClear();
mockSuccessfulCompaction();
const res = await getReplyFromConfig(
{
Body: "/compact",
From: "+1004",
const request = {
Body: "/compact focus on decisions",
From: "+1003",
To: "+2000",
SessionKey: "agent:worker1:telegram:12345",
CommandAuthorized: true,
},
{},
makeCfg(home),
);
};
const res = await getReplyFromConfig(
{
...request,
CommandAuthorized: true,
},
{},
cfg,
);
const text = maybeReplyText(res);
expect(text?.startsWith("⚙️ Compacted")).toBe(true);
expect(getCompactEmbeddedPiSessionMock()).toHaveBeenCalledOnce();
const store = loadSessionStore(storePath);
const sessionKey = resolveSessionKey("per-sender", request);
expect(store[sessionKey]?.compactionCount).toBe(1);
}
{
getCompactEmbeddedPiSessionMock().mockClear();
mockSuccessfulCompaction();
const res = await getReplyFromConfig(
{
Body: "/compact",
From: "+1004",
To: "+2000",
SessionKey: "agent:worker1:telegram:12345",
CommandAuthorized: true,
},
{},
makeCfg(home),
);
const text = maybeReplyText(res);
expect(text?.startsWith("⚙️ Compacted")).toBe(true);
expect(getCompactEmbeddedPiSessionMock()).toHaveBeenCalledOnce();
expect(getCompactEmbeddedPiSessionMock().mock.calls[0]?.[0]?.sessionFile).toContain(
join("agents", "worker1", "sessions"),
);
}
const text = maybeReplyText(res);
expect(text?.startsWith("⚙️ Compacted")).toBe(true);
expect(getCompactEmbeddedPiSessionMock()).toHaveBeenCalledOnce();
expect(getCompactEmbeddedPiSessionMock().mock.calls[0]?.[0]?.sessionFile).toContain(
join("agents", "worker1", "sessions"),
);
expect(getRunEmbeddedPiAgentMock()).not.toHaveBeenCalled();
});
});