mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:50:43 +00:00
fix(gateway): resolve inbound assistant media refs
This commit is contained in:
@@ -4,6 +4,7 @@ import type { IncomingMessage } from "node:http";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { resolveStateDir } from "../config/paths.js";
|
||||
import { approveDevicePairing, requestDevicePairing } from "../infra/device-pairing.js";
|
||||
import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js";
|
||||
import type { ResolvedGatewayAuth } from "./auth.js";
|
||||
@@ -331,6 +332,47 @@ describe("handleControlUiHttpRequest", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("serves assistant media from canonical inbound media refs", async () => {
|
||||
const stateDir = resolveStateDir();
|
||||
const id = `ui-media-ref-${Date.now()}-${Math.random().toString(36).slice(2)}.png`;
|
||||
const filePath = path.join(stateDir, "media", "inbound", id);
|
||||
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
||||
await fs.writeFile(filePath, Buffer.from("not-a-real-png"));
|
||||
|
||||
try {
|
||||
const { res, handled } = await runAssistantMediaRequest({
|
||||
url: `/__openclaw__/assistant-media?source=${encodeURIComponent(`media://inbound/${id}`)}&token=test-token`,
|
||||
method: "GET",
|
||||
auth: { mode: "token", token: "test-token", allowTailscale: false },
|
||||
});
|
||||
expect(handled).toBe(true);
|
||||
expect(res.statusCode).toBe(200);
|
||||
} finally {
|
||||
await fs.rm(filePath, { force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("reports assistant media metadata for canonical inbound media refs", async () => {
|
||||
const stateDir = resolveStateDir();
|
||||
const id = `ui-media-ref-meta-${Date.now()}-${Math.random().toString(36).slice(2)}.png`;
|
||||
const filePath = path.join(stateDir, "media", "inbound", id);
|
||||
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
||||
await fs.writeFile(filePath, Buffer.from("not-a-real-png"));
|
||||
|
||||
try {
|
||||
const { res, handled, end } = await runAssistantMediaRequest({
|
||||
url: `/__openclaw__/assistant-media?meta=1&source=${encodeURIComponent(`media://inbound/${id}`)}&token=test-token`,
|
||||
method: "GET",
|
||||
auth: { mode: "token", token: "test-token", allowTailscale: false },
|
||||
});
|
||||
expect(handled).toBe(true);
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(JSON.parse(String(end.mock.calls[0]?.[0] ?? ""))).toEqual({ available: true });
|
||||
} finally {
|
||||
await fs.rm(filePath, { force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects assistant local media outside allowed preview roots", async () => {
|
||||
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-ui-media-blocked-"));
|
||||
try {
|
||||
|
||||
@@ -15,6 +15,7 @@ import { isWithinDir } from "../infra/path-safety.js";
|
||||
import { openVerifiedFileSync } from "../infra/safe-open-sync.js";
|
||||
import { assertLocalMediaAllowed, getDefaultLocalRoots } from "../media/local-media-access.js";
|
||||
import { getAgentScopedMediaLocalRoots } from "../media/local-roots.js";
|
||||
import { resolveMediaReferenceLocalPath } from "../media/media-reference.js";
|
||||
import { detectMime } from "../media/mime.js";
|
||||
import { AVATAR_MAX_BYTES } from "../shared/avatar-policy.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
@@ -401,8 +402,9 @@ async function resolveAssistantMediaAvailability(
|
||||
localRoots: readonly string[],
|
||||
): Promise<AssistantMediaAvailability> {
|
||||
try {
|
||||
await assertLocalMediaAllowed(source, localRoots);
|
||||
const opened = await openLocalFileSafely({ filePath: source });
|
||||
const localPath = await resolveMediaReferenceLocalPath(source);
|
||||
await assertLocalMediaAllowed(localPath, localRoots);
|
||||
const opened = await openLocalFileSafely({ filePath: localPath });
|
||||
await opened.handle.close();
|
||||
return { available: true };
|
||||
} catch (err) {
|
||||
@@ -460,6 +462,7 @@ export async function handleControlUiAssistantMediaRequest(
|
||||
}
|
||||
|
||||
let opened: Awaited<ReturnType<typeof openLocalFileSafely>> | null = null;
|
||||
let localPath = source;
|
||||
let handleClosed = false;
|
||||
const closeOpenedHandle = async () => {
|
||||
if (!opened || handleClosed) {
|
||||
@@ -469,8 +472,9 @@ export async function handleControlUiAssistantMediaRequest(
|
||||
await opened.handle.close().catch(() => {});
|
||||
};
|
||||
try {
|
||||
await assertLocalMediaAllowed(source, localRoots);
|
||||
opened = await openLocalFileSafely({ filePath: source });
|
||||
localPath = await resolveMediaReferenceLocalPath(source);
|
||||
await assertLocalMediaAllowed(localPath, localRoots);
|
||||
opened = await openLocalFileSafely({ filePath: localPath });
|
||||
const sniffLength = Math.min(opened.stat.size, 8192);
|
||||
const sniffBuffer = sniffLength > 0 ? Buffer.allocUnsafe(sniffLength) : undefined;
|
||||
const bytesRead =
|
||||
@@ -479,7 +483,7 @@ export async function handleControlUiAssistantMediaRequest(
|
||||
: 0;
|
||||
const mime = await detectMime({
|
||||
buffer: sniffBuffer?.subarray(0, bytesRead),
|
||||
filePath: source,
|
||||
filePath: localPath,
|
||||
});
|
||||
if (mime) {
|
||||
res.setHeader("Content-Type", mime);
|
||||
|
||||
Reference in New Issue
Block a user