From 4d8a4fbb48b58aef20d1db2f41edbf88dff9a45e Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 14 Feb 2026 17:44:59 +0000 Subject: [PATCH] refactor(test): share runReplyAgent memory flush harness --- ...n-count-flush-compaction-completes.test.ts | 127 ++--------------- ...lush-turn-updates-session-metadata.test.ts | 128 ++--------------- ...h.skips-memory-flush-cli-providers.test.ts | 127 ++--------------- ...ry-flush-sandbox-workspace-is-read.test.ts | 126 ++--------------- ...nfigured-prompts-memory-flush-runs.test.ts | 128 ++--------------- .../agent-runner.memory-flush.test-harness.ts | 130 ++++++++++++++++++ 6 files changed, 178 insertions(+), 588 deletions(-) create mode 100644 src/auto-reply/reply/agent-runner.memory-flush.test-harness.ts diff --git a/src/auto-reply/reply/agent-runner.memory-flush.runreplyagent-memory-flush.increments-compaction-count-flush-compaction-completes.test.ts b/src/auto-reply/reply/agent-runner.memory-flush.runreplyagent-memory-flush.increments-compaction-count-flush-compaction-completes.test.ts index 4279dbff35..6238322328 100644 --- a/src/auto-reply/reply/agent-runner.memory-flush.runreplyagent-memory-flush.increments-compaction-count-flush-compaction-completes.test.ts +++ b/src/auto-reply/reply/agent-runner.memory-flush.runreplyagent-memory-flush.increments-compaction-count-flush-compaction-completes.test.ts @@ -1,128 +1,19 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { describe, expect, it, vi } from "vitest"; -import type { TemplateContext } from "../templating.js"; -import type { FollowupRun, QueueSettings } from "./queue.js"; -import { DEFAULT_MEMORY_FLUSH_PROMPT } from "./memory-flush.js"; -import { createMockTypingController } from "./test-helpers.js"; - -const runEmbeddedPiAgentMock = vi.fn(); -const runCliAgentMock = vi.fn(); - -type EmbeddedRunParams = { - prompt?: string; - extraSystemPrompt?: string; - onAgentEvent?: (evt: { stream?: string; data?: { phase?: string; willRetry?: boolean } }) => void; -}; - -vi.mock("../../agents/model-fallback.js", () => ({ - runWithModelFallback: async ({ - provider, - model, - run, - }: { - provider: string; - model: string; - run: (provider: string, model: string) => Promise; - }) => ({ - result: await run(provider, model), - provider, - model, - }), -})); - -vi.mock("../../agents/cli-runner.js", () => ({ - runCliAgent: (params: unknown) => runCliAgentMock(params), -})); - -vi.mock("../../agents/pi-embedded.js", () => ({ - queueEmbeddedPiMessage: vi.fn().mockReturnValue(false), - runEmbeddedPiAgent: (params: unknown) => runEmbeddedPiAgentMock(params), -})); - -vi.mock("./queue.js", async () => { - const actual = await vi.importActual("./queue.js"); - return { - ...actual, - enqueueFollowupRun: vi.fn(), - scheduleFollowupDrain: vi.fn(), - }; -}); - +import { describe, expect, it } from "vitest"; import { runReplyAgent } from "./agent-runner.js"; - -async function seedSessionStore(params: { - storePath: string; - sessionKey: string; - entry: Record; -}) { - await fs.mkdir(path.dirname(params.storePath), { recursive: true }); - await fs.writeFile( - params.storePath, - JSON.stringify({ [params.sessionKey]: params.entry }, null, 2), - "utf-8", - ); -} - -function createBaseRun(params: { - storePath: string; - sessionEntry: Record; - config?: Record; - runOverrides?: Partial; -}) { - const typing = createMockTypingController(); - const sessionCtx = { - Provider: "whatsapp", - OriginatingTo: "+15550001111", - AccountId: "primary", - MessageSid: "msg", - } as unknown as TemplateContext; - const resolvedQueue = { mode: "interrupt" } as unknown as QueueSettings; - const followupRun = { - prompt: "hello", - summaryLine: "hello", - enqueuedAt: Date.now(), - run: { - agentId: "main", - agentDir: "/tmp/agent", - sessionId: "session", - sessionKey: "main", - messageProvider: "whatsapp", - sessionFile: "/tmp/session.jsonl", - workspaceDir: "/tmp", - config: params.config ?? {}, - skillsSnapshot: {}, - provider: "anthropic", - model: "claude", - thinkLevel: "low", - verboseLevel: "off", - elevatedLevel: "off", - bashElevated: { - enabled: false, - allowed: false, - defaultLevel: "off", - }, - timeoutMs: 1_000, - blockReplyBreak: "message_end", - }, - } as unknown as FollowupRun; - const run = { - ...followupRun.run, - ...params.runOverrides, - config: params.config ?? followupRun.run.config, - }; - - return { - typing, - sessionCtx, - resolvedQueue, - followupRun: { ...followupRun, run }, - }; -} +import { + createBaseRun, + getRunEmbeddedPiAgentMock, + seedSessionStore, + type EmbeddedRunParams, +} from "./agent-runner.memory-flush.test-harness.js"; +import { DEFAULT_MEMORY_FLUSH_PROMPT } from "./memory-flush.js"; describe("runReplyAgent memory flush", () => { it("increments compaction count when flush compaction completes", async () => { + const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); runEmbeddedPiAgentMock.mockReset(); const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-flush-")); const storePath = path.join(tmp, "sessions.json"); diff --git a/src/auto-reply/reply/agent-runner.memory-flush.runreplyagent-memory-flush.runs-memory-flush-turn-updates-session-metadata.test.ts b/src/auto-reply/reply/agent-runner.memory-flush.runreplyagent-memory-flush.runs-memory-flush-turn-updates-session-metadata.test.ts index 0a93669a3a..b513d7168d 100644 --- a/src/auto-reply/reply/agent-runner.memory-flush.runreplyagent-memory-flush.runs-memory-flush-turn-updates-session-metadata.test.ts +++ b/src/auto-reply/reply/agent-runner.memory-flush.runreplyagent-memory-flush.runs-memory-flush-turn-updates-session-metadata.test.ts @@ -1,128 +1,19 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { describe, expect, it, vi } from "vitest"; -import type { TemplateContext } from "../templating.js"; -import type { FollowupRun, QueueSettings } from "./queue.js"; -import { DEFAULT_MEMORY_FLUSH_PROMPT } from "./memory-flush.js"; -import { createMockTypingController } from "./test-helpers.js"; - -const runEmbeddedPiAgentMock = vi.fn(); -const runCliAgentMock = vi.fn(); - -type EmbeddedRunParams = { - prompt?: string; - extraSystemPrompt?: string; - onAgentEvent?: (evt: { stream?: string; data?: { phase?: string; willRetry?: boolean } }) => void; -}; - -vi.mock("../../agents/model-fallback.js", () => ({ - runWithModelFallback: async ({ - provider, - model, - run, - }: { - provider: string; - model: string; - run: (provider: string, model: string) => Promise; - }) => ({ - result: await run(provider, model), - provider, - model, - }), -})); - -vi.mock("../../agents/cli-runner.js", () => ({ - runCliAgent: (params: unknown) => runCliAgentMock(params), -})); - -vi.mock("../../agents/pi-embedded.js", () => ({ - queueEmbeddedPiMessage: vi.fn().mockReturnValue(false), - runEmbeddedPiAgent: (params: unknown) => runEmbeddedPiAgentMock(params), -})); - -vi.mock("./queue.js", async () => { - const actual = await vi.importActual("./queue.js"); - return { - ...actual, - enqueueFollowupRun: vi.fn(), - scheduleFollowupDrain: vi.fn(), - }; -}); - +import { describe, expect, it } from "vitest"; import { runReplyAgent } from "./agent-runner.js"; - -async function seedSessionStore(params: { - storePath: string; - sessionKey: string; - entry: Record; -}) { - await fs.mkdir(path.dirname(params.storePath), { recursive: true }); - await fs.writeFile( - params.storePath, - JSON.stringify({ [params.sessionKey]: params.entry }, null, 2), - "utf-8", - ); -} - -function createBaseRun(params: { - storePath: string; - sessionEntry: Record; - config?: Record; - runOverrides?: Partial; -}) { - const typing = createMockTypingController(); - const sessionCtx = { - Provider: "whatsapp", - OriginatingTo: "+15550001111", - AccountId: "primary", - MessageSid: "msg", - } as unknown as TemplateContext; - const resolvedQueue = { mode: "interrupt" } as unknown as QueueSettings; - const followupRun = { - prompt: "hello", - summaryLine: "hello", - enqueuedAt: Date.now(), - run: { - agentId: "main", - agentDir: "/tmp/agent", - sessionId: "session", - sessionKey: "main", - messageProvider: "whatsapp", - sessionFile: "/tmp/session.jsonl", - workspaceDir: "/tmp", - config: params.config ?? {}, - skillsSnapshot: {}, - provider: "anthropic", - model: "claude", - thinkLevel: "low", - verboseLevel: "off", - elevatedLevel: "off", - bashElevated: { - enabled: false, - allowed: false, - defaultLevel: "off", - }, - timeoutMs: 1_000, - blockReplyBreak: "message_end", - }, - } as unknown as FollowupRun; - const run = { - ...followupRun.run, - ...params.runOverrides, - config: params.config ?? followupRun.run.config, - }; - - return { - typing, - sessionCtx, - resolvedQueue, - followupRun: { ...followupRun, run }, - }; -} +import { + createBaseRun, + getRunEmbeddedPiAgentMock, + seedSessionStore, + type EmbeddedRunParams, +} from "./agent-runner.memory-flush.test-harness.js"; +import { DEFAULT_MEMORY_FLUSH_PROMPT } from "./memory-flush.js"; describe("runReplyAgent memory flush", () => { it("runs a memory flush turn and updates session metadata", async () => { + const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); runEmbeddedPiAgentMock.mockReset(); const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-flush-")); const storePath = path.join(tmp, "sessions.json"); @@ -185,6 +76,7 @@ describe("runReplyAgent memory flush", () => { expect(stored[sessionKey].memoryFlushCompactionCount).toBe(1); }); it("skips memory flush when disabled in config", async () => { + const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); runEmbeddedPiAgentMock.mockReset(); const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-flush-")); const storePath = path.join(tmp, "sessions.json"); diff --git a/src/auto-reply/reply/agent-runner.memory-flush.runreplyagent-memory-flush.skips-memory-flush-cli-providers.test.ts b/src/auto-reply/reply/agent-runner.memory-flush.runreplyagent-memory-flush.skips-memory-flush-cli-providers.test.ts index c73fd89788..0cdd6d207c 100644 --- a/src/auto-reply/reply/agent-runner.memory-flush.runreplyagent-memory-flush.skips-memory-flush-cli-providers.test.ts +++ b/src/auto-reply/reply/agent-runner.memory-flush.runreplyagent-memory-flush.skips-memory-flush-cli-providers.test.ts @@ -1,127 +1,20 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { describe, expect, it, vi } from "vitest"; -import type { TemplateContext } from "../templating.js"; -import type { FollowupRun, QueueSettings } from "./queue.js"; -import { createMockTypingController } from "./test-helpers.js"; - -const runEmbeddedPiAgentMock = vi.fn(); -const runCliAgentMock = vi.fn(); - -type EmbeddedRunParams = { - prompt?: string; - extraSystemPrompt?: string; - onAgentEvent?: (evt: { stream?: string; data?: { phase?: string; willRetry?: boolean } }) => void; -}; - -vi.mock("../../agents/model-fallback.js", () => ({ - runWithModelFallback: async ({ - provider, - model, - run, - }: { - provider: string; - model: string; - run: (provider: string, model: string) => Promise; - }) => ({ - result: await run(provider, model), - provider, - model, - }), -})); - -vi.mock("../../agents/cli-runner.js", () => ({ - runCliAgent: (params: unknown) => runCliAgentMock(params), -})); - -vi.mock("../../agents/pi-embedded.js", () => ({ - queueEmbeddedPiMessage: vi.fn().mockReturnValue(false), - runEmbeddedPiAgent: (params: unknown) => runEmbeddedPiAgentMock(params), -})); - -vi.mock("./queue.js", async () => { - const actual = await vi.importActual("./queue.js"); - return { - ...actual, - enqueueFollowupRun: vi.fn(), - scheduleFollowupDrain: vi.fn(), - }; -}); - +import { describe, expect, it } from "vitest"; import { runReplyAgent } from "./agent-runner.js"; - -async function seedSessionStore(params: { - storePath: string; - sessionKey: string; - entry: Record; -}) { - await fs.mkdir(path.dirname(params.storePath), { recursive: true }); - await fs.writeFile( - params.storePath, - JSON.stringify({ [params.sessionKey]: params.entry }, null, 2), - "utf-8", - ); -} - -function createBaseRun(params: { - storePath: string; - sessionEntry: Record; - config?: Record; - runOverrides?: Partial; -}) { - const typing = createMockTypingController(); - const sessionCtx = { - Provider: "whatsapp", - OriginatingTo: "+15550001111", - AccountId: "primary", - MessageSid: "msg", - } as unknown as TemplateContext; - const resolvedQueue = { mode: "interrupt" } as unknown as QueueSettings; - const followupRun = { - prompt: "hello", - summaryLine: "hello", - enqueuedAt: Date.now(), - run: { - agentId: "main", - agentDir: "/tmp/agent", - sessionId: "session", - sessionKey: "main", - messageProvider: "whatsapp", - sessionFile: "/tmp/session.jsonl", - workspaceDir: "/tmp", - config: params.config ?? {}, - skillsSnapshot: {}, - provider: "anthropic", - model: "claude", - thinkLevel: "low", - verboseLevel: "off", - elevatedLevel: "off", - bashElevated: { - enabled: false, - allowed: false, - defaultLevel: "off", - }, - timeoutMs: 1_000, - blockReplyBreak: "message_end", - }, - } as unknown as FollowupRun; - const run = { - ...followupRun.run, - ...params.runOverrides, - config: params.config ?? followupRun.run.config, - }; - - return { - typing, - sessionCtx, - resolvedQueue, - followupRun: { ...followupRun, run }, - }; -} +import { + createBaseRun, + getRunCliAgentMock, + getRunEmbeddedPiAgentMock, + seedSessionStore, + type EmbeddedRunParams, +} from "./agent-runner.memory-flush.test-harness.js"; describe("runReplyAgent memory flush", () => { it("skips memory flush for CLI providers", async () => { + const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); + const runCliAgentMock = getRunCliAgentMock(); runEmbeddedPiAgentMock.mockReset(); runCliAgentMock.mockReset(); const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-flush-")); diff --git a/src/auto-reply/reply/agent-runner.memory-flush.runreplyagent-memory-flush.skips-memory-flush-sandbox-workspace-is-read.test.ts b/src/auto-reply/reply/agent-runner.memory-flush.runreplyagent-memory-flush.skips-memory-flush-sandbox-workspace-is-read.test.ts index 11d6df87a9..f20e5bbd0f 100644 --- a/src/auto-reply/reply/agent-runner.memory-flush.runreplyagent-memory-flush.skips-memory-flush-sandbox-workspace-is-read.test.ts +++ b/src/auto-reply/reply/agent-runner.memory-flush.runreplyagent-memory-flush.skips-memory-flush-sandbox-workspace-is-read.test.ts @@ -1,127 +1,18 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { describe, expect, it, vi } from "vitest"; -import type { TemplateContext } from "../templating.js"; -import type { FollowupRun, QueueSettings } from "./queue.js"; -import { createMockTypingController } from "./test-helpers.js"; - -const runEmbeddedPiAgentMock = vi.fn(); -const runCliAgentMock = vi.fn(); - -type EmbeddedRunParams = { - prompt?: string; - extraSystemPrompt?: string; - onAgentEvent?: (evt: { stream?: string; data?: { phase?: string; willRetry?: boolean } }) => void; -}; - -vi.mock("../../agents/model-fallback.js", () => ({ - runWithModelFallback: async ({ - provider, - model, - run, - }: { - provider: string; - model: string; - run: (provider: string, model: string) => Promise; - }) => ({ - result: await run(provider, model), - provider, - model, - }), -})); - -vi.mock("../../agents/cli-runner.js", () => ({ - runCliAgent: (params: unknown) => runCliAgentMock(params), -})); - -vi.mock("../../agents/pi-embedded.js", () => ({ - queueEmbeddedPiMessage: vi.fn().mockReturnValue(false), - runEmbeddedPiAgent: (params: unknown) => runEmbeddedPiAgentMock(params), -})); - -vi.mock("./queue.js", async () => { - const actual = await vi.importActual("./queue.js"); - return { - ...actual, - enqueueFollowupRun: vi.fn(), - scheduleFollowupDrain: vi.fn(), - }; -}); - +import { describe, expect, it } from "vitest"; import { runReplyAgent } from "./agent-runner.js"; - -async function seedSessionStore(params: { - storePath: string; - sessionKey: string; - entry: Record; -}) { - await fs.mkdir(path.dirname(params.storePath), { recursive: true }); - await fs.writeFile( - params.storePath, - JSON.stringify({ [params.sessionKey]: params.entry }, null, 2), - "utf-8", - ); -} - -function createBaseRun(params: { - storePath: string; - sessionEntry: Record; - config?: Record; - runOverrides?: Partial; -}) { - const typing = createMockTypingController(); - const sessionCtx = { - Provider: "whatsapp", - OriginatingTo: "+15550001111", - AccountId: "primary", - MessageSid: "msg", - } as unknown as TemplateContext; - const resolvedQueue = { mode: "interrupt" } as unknown as QueueSettings; - const followupRun = { - prompt: "hello", - summaryLine: "hello", - enqueuedAt: Date.now(), - run: { - agentId: "main", - agentDir: "/tmp/agent", - sessionId: "session", - sessionKey: "main", - messageProvider: "whatsapp", - sessionFile: "/tmp/session.jsonl", - workspaceDir: "/tmp", - config: params.config ?? {}, - skillsSnapshot: {}, - provider: "anthropic", - model: "claude", - thinkLevel: "low", - verboseLevel: "off", - elevatedLevel: "off", - bashElevated: { - enabled: false, - allowed: false, - defaultLevel: "off", - }, - timeoutMs: 1_000, - blockReplyBreak: "message_end", - }, - } as unknown as FollowupRun; - const run = { - ...followupRun.run, - ...params.runOverrides, - config: params.config ?? followupRun.run.config, - }; - - return { - typing, - sessionCtx, - resolvedQueue, - followupRun: { ...followupRun, run }, - }; -} +import { + createBaseRun, + getRunEmbeddedPiAgentMock, + seedSessionStore, + type EmbeddedRunParams, +} from "./agent-runner.memory-flush.test-harness.js"; describe("runReplyAgent memory flush", () => { it("skips memory flush when the sandbox workspace is read-only", async () => { + const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); runEmbeddedPiAgentMock.mockReset(); const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-flush-")); const storePath = path.join(tmp, "sessions.json"); @@ -187,6 +78,7 @@ describe("runReplyAgent memory flush", () => { expect(stored[sessionKey].memoryFlushAt).toBeUndefined(); }); it("skips memory flush when the sandbox workspace is none", async () => { + const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); runEmbeddedPiAgentMock.mockReset(); const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-flush-")); const storePath = path.join(tmp, "sessions.json"); diff --git a/src/auto-reply/reply/agent-runner.memory-flush.runreplyagent-memory-flush.uses-configured-prompts-memory-flush-runs.test.ts b/src/auto-reply/reply/agent-runner.memory-flush.runreplyagent-memory-flush.uses-configured-prompts-memory-flush-runs.test.ts index df3de6b375..404e25f884 100644 --- a/src/auto-reply/reply/agent-runner.memory-flush.runreplyagent-memory-flush.uses-configured-prompts-memory-flush-runs.test.ts +++ b/src/auto-reply/reply/agent-runner.memory-flush.runreplyagent-memory-flush.uses-configured-prompts-memory-flush-runs.test.ts @@ -1,128 +1,19 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { describe, expect, it, vi } from "vitest"; -import type { TemplateContext } from "../templating.js"; -import type { FollowupRun, QueueSettings } from "./queue.js"; -import { DEFAULT_MEMORY_FLUSH_PROMPT } from "./memory-flush.js"; -import { createMockTypingController } from "./test-helpers.js"; - -const runEmbeddedPiAgentMock = vi.fn(); -const runCliAgentMock = vi.fn(); - -type EmbeddedRunParams = { - prompt?: string; - extraSystemPrompt?: string; - onAgentEvent?: (evt: { stream?: string; data?: { phase?: string; willRetry?: boolean } }) => void; -}; - -vi.mock("../../agents/model-fallback.js", () => ({ - runWithModelFallback: async ({ - provider, - model, - run, - }: { - provider: string; - model: string; - run: (provider: string, model: string) => Promise; - }) => ({ - result: await run(provider, model), - provider, - model, - }), -})); - -vi.mock("../../agents/cli-runner.js", () => ({ - runCliAgent: (params: unknown) => runCliAgentMock(params), -})); - -vi.mock("../../agents/pi-embedded.js", () => ({ - queueEmbeddedPiMessage: vi.fn().mockReturnValue(false), - runEmbeddedPiAgent: (params: unknown) => runEmbeddedPiAgentMock(params), -})); - -vi.mock("./queue.js", async () => { - const actual = await vi.importActual("./queue.js"); - return { - ...actual, - enqueueFollowupRun: vi.fn(), - scheduleFollowupDrain: vi.fn(), - }; -}); - +import { describe, expect, it } from "vitest"; import { runReplyAgent } from "./agent-runner.js"; - -async function seedSessionStore(params: { - storePath: string; - sessionKey: string; - entry: Record; -}) { - await fs.mkdir(path.dirname(params.storePath), { recursive: true }); - await fs.writeFile( - params.storePath, - JSON.stringify({ [params.sessionKey]: params.entry }, null, 2), - "utf-8", - ); -} - -function createBaseRun(params: { - storePath: string; - sessionEntry: Record; - config?: Record; - runOverrides?: Partial; -}) { - const typing = createMockTypingController(); - const sessionCtx = { - Provider: "whatsapp", - OriginatingTo: "+15550001111", - AccountId: "primary", - MessageSid: "msg", - } as unknown as TemplateContext; - const resolvedQueue = { mode: "interrupt" } as unknown as QueueSettings; - const followupRun = { - prompt: "hello", - summaryLine: "hello", - enqueuedAt: Date.now(), - run: { - agentId: "main", - agentDir: "/tmp/agent", - sessionId: "session", - sessionKey: "main", - messageProvider: "whatsapp", - sessionFile: "/tmp/session.jsonl", - workspaceDir: "/tmp", - config: params.config ?? {}, - skillsSnapshot: {}, - provider: "anthropic", - model: "claude", - thinkLevel: "low", - verboseLevel: "off", - elevatedLevel: "off", - bashElevated: { - enabled: false, - allowed: false, - defaultLevel: "off", - }, - timeoutMs: 1_000, - blockReplyBreak: "message_end", - }, - } as unknown as FollowupRun; - const run = { - ...followupRun.run, - ...params.runOverrides, - config: params.config ?? followupRun.run.config, - }; - - return { - typing, - sessionCtx, - resolvedQueue, - followupRun: { ...followupRun, run }, - }; -} +import { + createBaseRun, + getRunEmbeddedPiAgentMock, + seedSessionStore, + type EmbeddedRunParams, +} from "./agent-runner.memory-flush.test-harness.js"; +import { DEFAULT_MEMORY_FLUSH_PROMPT } from "./memory-flush.js"; describe("runReplyAgent memory flush", () => { it("uses configured prompts for memory flush runs", async () => { + const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); runEmbeddedPiAgentMock.mockReset(); const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-flush-")); const storePath = path.join(tmp, "sessions.json"); @@ -200,6 +91,7 @@ describe("runReplyAgent memory flush", () => { expect(calls[1]?.prompt).toBe("hello"); }); it("skips memory flush after a prior flush in the same compaction cycle", async () => { + const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); runEmbeddedPiAgentMock.mockReset(); const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-flush-")); const storePath = path.join(tmp, "sessions.json"); diff --git a/src/auto-reply/reply/agent-runner.memory-flush.test-harness.ts b/src/auto-reply/reply/agent-runner.memory-flush.test-harness.ts new file mode 100644 index 0000000000..35491c8c54 --- /dev/null +++ b/src/auto-reply/reply/agent-runner.memory-flush.test-harness.ts @@ -0,0 +1,130 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import { vi } from "vitest"; +import type { TemplateContext } from "../templating.js"; +import type { FollowupRun, QueueSettings } from "./queue.js"; +import { createMockTypingController } from "./test-helpers.js"; + +type EmbeddedRunParams = { + prompt?: string; + extraSystemPrompt?: string; + onAgentEvent?: (evt: { stream?: string; data?: { phase?: string; willRetry?: boolean } }) => void; +}; + +const state = vi.hoisted(() => ({ + runEmbeddedPiAgentMock: vi.fn(), + runCliAgentMock: vi.fn(), +})); + +export function getRunEmbeddedPiAgentMock() { + return state.runEmbeddedPiAgentMock; +} + +export function getRunCliAgentMock() { + return state.runCliAgentMock; +} + +export type { EmbeddedRunParams }; + +vi.mock("../../agents/model-fallback.js", () => ({ + runWithModelFallback: async ({ + provider, + model, + run, + }: { + provider: string; + model: string; + run: (provider: string, model: string) => Promise; + }) => ({ + result: await run(provider, model), + provider, + model, + }), +})); + +vi.mock("../../agents/cli-runner.js", () => ({ + runCliAgent: (params: unknown) => state.runCliAgentMock(params), +})); + +vi.mock("../../agents/pi-embedded.js", () => ({ + queueEmbeddedPiMessage: vi.fn().mockReturnValue(false), + runEmbeddedPiAgent: (params: unknown) => state.runEmbeddedPiAgentMock(params), +})); + +vi.mock("./queue.js", async () => { + const actual = await vi.importActual("./queue.js"); + return { + ...actual, + enqueueFollowupRun: vi.fn(), + scheduleFollowupDrain: vi.fn(), + }; +}); + +export async function seedSessionStore(params: { + storePath: string; + sessionKey: string; + entry: Record; +}) { + await fs.mkdir(path.dirname(params.storePath), { recursive: true }); + await fs.writeFile( + params.storePath, + JSON.stringify({ [params.sessionKey]: params.entry }, null, 2), + "utf-8", + ); +} + +export function createBaseRun(params: { + storePath: string; + sessionEntry: Record; + config?: Record; + runOverrides?: Partial; +}) { + const typing = createMockTypingController(); + const sessionCtx = { + Provider: "whatsapp", + OriginatingTo: "+15550001111", + AccountId: "primary", + MessageSid: "msg", + } as unknown as TemplateContext; + const resolvedQueue = { mode: "interrupt" } as unknown as QueueSettings; + const followupRun = { + prompt: "hello", + summaryLine: "hello", + enqueuedAt: Date.now(), + run: { + agentId: "main", + agentDir: "/tmp/agent", + sessionId: "session", + sessionKey: "main", + messageProvider: "whatsapp", + sessionFile: "/tmp/session.jsonl", + workspaceDir: "/tmp", + config: params.config ?? {}, + skillsSnapshot: {}, + provider: "anthropic", + model: "claude", + thinkLevel: "low", + verboseLevel: "off", + elevatedLevel: "off", + bashElevated: { + enabled: false, + allowed: false, + defaultLevel: "off", + }, + timeoutMs: 1_000, + blockReplyBreak: "message_end", + }, + } as unknown as FollowupRun; + const run = { + ...followupRun.run, + ...params.runOverrides, + config: params.config ?? followupRun.run.config, + }; + + return { + typing, + sessionCtx, + resolvedQueue, + followupRun: { ...followupRun, run }, + }; +}