diff --git a/src/auto-reply/reply.triggers.trigger-handling.filters-usage-summary-current-model-provider.test.ts b/src/auto-reply/reply.triggers.trigger-handling.filters-usage-summary-current-model-provider.test.ts index 584799e18db..ef26242d199 100644 --- a/src/auto-reply/reply.triggers.trigger-handling.filters-usage-summary-current-model-provider.test.ts +++ b/src/auto-reply/reply.triggers.trigger-handling.filters-usage-summary-current-model-provider.test.ts @@ -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(); }); }); diff --git a/src/auto-reply/reply.triggers.trigger-handling.targets-active-session-native-stop.test.ts b/src/auto-reply/reply.triggers.trigger-handling.targets-active-session-native-stop.test.ts index 4c40064b269..c6967192457 100644 --- a/src/auto-reply/reply.triggers.trigger-handling.targets-active-session-native-stop.test.ts +++ b/src/auto-reply/reply.triggers.trigger-handling.targets-active-session-native-stop.test.ts @@ -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) => { + 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(); }); });