mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 11:55:47 -06:00
a95c059f11
Media-facts program PR 5 — the planned additive bridge. Structured facts travel alongside existing prompt text through prompt prelude, GetReplyOptions, FollowupRun, queue collect/defer/retry, active steering, embedded/CLI runs, Gateway offloads (offloadedRefs no longer discarded; AgentContentPhaseResult carries media), Tlon, and late-media projection — with prompt bytes golden-equal everywhere. Facts follow the same adoption/idempotency/late-append lifecycle as prompt text. Intentionally +175 prod LOC per the audit; the facts-first hydration/prune deletion PR consumes this carrier next.
89 lines
3.2 KiB
TypeScript
89 lines
3.2 KiB
TypeScript
/** Tests media-note behavior as it appears through reply prompt assembly. */
|
|
import { describe, expect, it } from "vitest";
|
|
import { finalizeInboundContext } from "./reply/inbound-context.js";
|
|
import { buildReplyPromptEnvelope } from "./reply/prompt-prelude.js";
|
|
|
|
describe("getReplyFromConfig media note plumbing", () => {
|
|
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 envelope = buildReplyPromptEnvelope({
|
|
ctx: sessionCtx,
|
|
sessionCtx,
|
|
baseBody: sessionCtx.BodyForAgent,
|
|
hasUserBody: true,
|
|
inboundUserContext: "",
|
|
isBareSessionReset: false,
|
|
startupAction: "new",
|
|
prefixedBody: sessionCtx.BodyForAgent,
|
|
});
|
|
const prompt = envelope.prefixedCommandBody;
|
|
|
|
const mediaNote = [
|
|
"[media attached: 2 files]",
|
|
"[media attached 1/2: /tmp/a.png (application/octet-stream)]",
|
|
"[media attached 2/2: /tmp/b.png (application/octet-stream)]",
|
|
].join("\n");
|
|
const replyHint =
|
|
"To send an image back, use the message tool with structured media fields such as media, mediaUrl, path, or filePath. Keep caption in the text body.";
|
|
expect(prompt).toBe(`${mediaNote}\n${replyHint}\nhello`);
|
|
expect(envelope.queuedBody).toBe(`${mediaNote}\n${replyHint}\nhello`);
|
|
expect(envelope.transcriptCommandBody).toBe(`${mediaNote}\nhello`);
|
|
expect(envelope.media?.map(({ path }) => path)).toEqual(["/tmp/a.png", "/tmp/b.png"]);
|
|
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");
|
|
});
|
|
|
|
it("keeps the real image attachment note after image understanding rewrites the body", () => {
|
|
const describedBody = [
|
|
"[Image]",
|
|
"User text:",
|
|
"make this widescreen",
|
|
"Description:",
|
|
"a red barn at sunset",
|
|
].join("\n");
|
|
const sessionCtx = finalizeInboundContext({
|
|
Body: describedBody,
|
|
BodyForAgent: describedBody,
|
|
From: "+1001",
|
|
To: "+2000",
|
|
MediaPaths: ["/tmp/media-store/real-image.png"],
|
|
MediaUrls: ["https://example.com/real-image.png"],
|
|
MediaTypes: ["image/png"],
|
|
MediaUnderstanding: [
|
|
{
|
|
kind: "image.description",
|
|
attachmentIndex: 0,
|
|
text: "a red barn at sunset",
|
|
provider: "openai",
|
|
},
|
|
],
|
|
});
|
|
const prompt = buildReplyPromptEnvelope({
|
|
ctx: sessionCtx,
|
|
sessionCtx,
|
|
baseBody: sessionCtx.BodyForAgent,
|
|
hasUserBody: true,
|
|
inboundUserContext: "",
|
|
isBareSessionReset: false,
|
|
startupAction: "new",
|
|
prefixedBody: sessionCtx.BodyForAgent,
|
|
}).prefixedCommandBody;
|
|
|
|
expect(prompt).toContain(
|
|
"[media attached: /tmp/media-store/real-image.png (image/png) | https://example.com/real-image.png]",
|
|
);
|
|
expect(prompt).toContain(describedBody);
|
|
});
|
|
});
|