diff --git a/src/auto-reply/reply.media-note.test.ts b/src/auto-reply/reply.media-note.test.ts index 2f171790db7..29867be788a 100644 --- a/src/auto-reply/reply.media-note.test.ts +++ b/src/auto-reply/reply.media-note.test.ts @@ -1,91 +1,30 @@ -import path from "node:path"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; -import type { OpenClawConfig } from "../config/config.js"; -import { - createReplyRuntimeMocks, - installReplyRuntimeMocks, - makeEmbeddedTextResult, - resetReplyRuntimeMocks, -} from "./reply.test-harness.js"; - -let getReplyFromConfig: typeof import("./reply.js").getReplyFromConfig; -const agentMocks = createReplyRuntimeMocks(); - -installReplyRuntimeMocks(agentMocks); - -async function withTempHome(fn: (home: string) => Promise): Promise { - return withTempHomeBase( - async (home) => { - agentMocks.runEmbeddedPiAgent.mockClear(); - return await fn(home); - }, - { - env: { - OPENCLAW_BUNDLED_SKILLS_DIR: (home) => path.join(home, "bundled-skills"), - }, - prefix: "openclaw-media-note-", - }, - ); -} - -function makeCfg(home: string) { - return { - agents: { - defaults: { - model: "anthropic/claude-opus-4-6", - workspace: path.join(home, "openclaw"), - }, - }, - channels: { whatsapp: { allowFrom: ["*"] } }, - session: { store: path.join(home, "sessions.json") }, - } as unknown as OpenClawConfig; -} +import { describe, expect, it } from "vitest"; +import { finalizeInboundContext } from "./reply/inbound-context.js"; +import { buildReplyPromptBodies } from "./reply/prompt-prelude.js"; describe("getReplyFromConfig media note plumbing", () => { - beforeEach(async () => { - vi.resetModules(); - vi.stubEnv("OPENCLAW_TEST_FAST", "1"); - resetReplyRuntimeMocks(agentMocks); - ({ getReplyFromConfig } = await import("./reply.js")); - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - it("includes all MediaPaths in the agent prompt", async () => { - await withTempHome(async (home) => { - let seenPrompt: string | undefined; - agentMocks.runEmbeddedPiAgent.mockImplementation(async (params) => { - seenPrompt = params.prompt; - return makeEmbeddedTextResult("ok"); - }); - - const cfg = makeCfg(home); - const res = await getReplyFromConfig( - { - Body: "hello", - From: "+1001", - To: "+2000", - MediaPaths: ["/tmp/a.png", "/tmp/b.png"], - MediaUrls: ["/tmp/a.png", "/tmp/b.png"], - }, - {}, - cfg, - ); - - const text = Array.isArray(res) ? res[0]?.text : res?.text; - expect(text).toBe("ok"); - expect(seenPrompt).toBeTruthy(); - expect(seenPrompt).toContain("[media attached: 2 files]"); - const idxA = seenPrompt?.indexOf("[media attached 1/2: /tmp/a.png"); - const idxB = seenPrompt?.indexOf("[media attached 2/2: /tmp/b.png"); - expect(typeof idxA).toBe("number"); - expect(typeof idxB).toBe("number"); - expect((idxA ?? -1) >= 0).toBe(true); - expect((idxB ?? -1) >= 0).toBe(true); - expect((idxA ?? 0) < (idxB ?? 0)).toBe(true); + it("includes all MediaPaths in the agent prompt", () => { + const sessionCtx = finalizeInboundContext({ + Body: "hello", + BodyForAgent: "hello", + From: "+1001", + To: "+2000", + MediaPaths: ["/tmp/a.png", "/tmp/b.png"], + MediaUrls: ["/tmp/a.png", "/tmp/b.png"], }); + const prompt = buildReplyPromptBodies({ + ctx: sessionCtx, + sessionCtx, + effectiveBaseBody: sessionCtx.BodyForAgent, + prefixedBody: sessionCtx.BodyForAgent, + }).prefixedCommandBody; + + expect(prompt).toContain("[media attached: 2 files]"); + const idxA = prompt.indexOf("[media attached 1/2: /tmp/a.png"); + const idxB = prompt.indexOf("[media attached 2/2: /tmp/b.png"); + expect(idxA).toBeGreaterThanOrEqual(0); + expect(idxB).toBeGreaterThanOrEqual(0); + expect(idxA).toBeLessThan(idxB); + expect(prompt).toContain("hello"); }); }); diff --git a/src/auto-reply/reply.raw-body.test.ts b/src/auto-reply/reply.raw-body.test.ts index edc83d62f1f..dda7695e553 100644 --- a/src/auto-reply/reply.raw-body.test.ts +++ b/src/auto-reply/reply.raw-body.test.ts @@ -1,67 +1,42 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import type { OpenClawConfig } from "../config/config.js"; -import { - createReplyRuntimeMocks, - createTempHomeHarness, - installReplyRuntimeMocks, - makeEmbeddedTextResult, - makeReplyConfig, - resetReplyRuntimeMocks, -} from "./reply.test-harness.js"; -let getReplyFromConfig: typeof import("./reply.js").getReplyFromConfig; -const agentMocks = createReplyRuntimeMocks(); - -const { withTempHome } = createTempHomeHarness({ prefix: "openclaw-rawbody-" }); - -installReplyRuntimeMocks(agentMocks); +import { describe, expect, it } from "vitest"; +import { parseInlineDirectives } from "./reply/directive-handling.parse.js"; +import { finalizeInboundContext } from "./reply/inbound-context.js"; +import { buildInboundUserContextPrefix } from "./reply/inbound-meta.js"; +import { buildReplyPromptBodies } from "./reply/prompt-prelude.js"; describe("RawBody directive parsing", () => { - beforeEach(async () => { - vi.resetModules(); - vi.stubEnv("OPENCLAW_TEST_FAST", "1"); - resetReplyRuntimeMocks(agentMocks); - ({ getReplyFromConfig } = await import("./reply.js")); - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - it("handles directives and history in the prompt", async () => { - await withTempHome(async (home) => { - agentMocks.runEmbeddedPiAgent.mockResolvedValue(makeEmbeddedTextResult("ok")); - - const groupMessageCtx = { - Body: "/think:high status please", - BodyForAgent: "/think:high status please", - RawBody: "/think:high status please", - InboundHistory: [{ sender: "Peter", body: "hello", timestamp: 1700000000000 }], - From: "+1222", - To: "+1222", - ChatType: "group", - GroupSubject: "Ops", - SenderName: "Jake McInteer", - SenderE164: "+6421807830", - CommandAuthorized: true, - }; - - const res = await getReplyFromConfig( - groupMessageCtx, - {}, - makeReplyConfig(home) as OpenClawConfig, - ); - - const text = Array.isArray(res) ? res[0]?.text : res?.text; - expect(text).toBe("ok"); - expect(agentMocks.runEmbeddedPiAgent).toHaveBeenCalledOnce(); - const prompt = - (agentMocks.runEmbeddedPiAgent.mock.calls[0]?.[0] as { prompt?: string } | undefined) - ?.prompt ?? ""; - expect(prompt).toContain("Chat history since last reply (untrusted, for context):"); - expect(prompt).toContain('"sender": "Peter"'); - expect(prompt).toContain('"body": "hello"'); - expect(prompt).toContain("status please"); - expect(prompt).not.toContain("/think:high"); + it("handles directives and history in the prompt", () => { + const sessionCtx = finalizeInboundContext({ + Body: "/think:high status please", + BodyForAgent: "/think:high status please", + BodyForCommands: "/think:high status please", + RawBody: "/think:high status please", + InboundHistory: [{ sender: "Peter", body: "hello", timestamp: 1700000000000 }], + From: "+1222", + To: "+1222", + ChatType: "group", + GroupSubject: "Ops", + SenderName: "Jake McInteer", + SenderE164: "+6421807830", + CommandAuthorized: true, }); + const directives = parseInlineDirectives(sessionCtx.BodyForCommands ?? "", { + allowStatusDirective: true, + }); + const prefixedBody = [buildInboundUserContextPrefix(sessionCtx), directives.cleaned] + .filter(Boolean) + .join("\n\n"); + const prompt = buildReplyPromptBodies({ + ctx: sessionCtx, + sessionCtx: { ...sessionCtx, BodyStripped: directives.cleaned }, + effectiveBaseBody: prefixedBody, + prefixedBody, + }).prefixedCommandBody; + + expect(prompt).toContain("Chat history since last reply (untrusted, for context):"); + expect(prompt).toContain('"sender": "Peter"'); + expect(prompt).toContain('"body": "hello"'); + expect(prompt).toContain("status please"); + expect(prompt).not.toContain("/think:high"); }); });