mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
Matrix: honor scoped media roots
This commit is contained in:
@@ -60,6 +60,7 @@ describe("matrixMessageActions account propagation", () => {
|
|||||||
accountId: "ops",
|
accountId: "ops",
|
||||||
}),
|
}),
|
||||||
expect.any(Object),
|
expect.any(Object),
|
||||||
|
{ mediaLocalRoots: undefined },
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -80,6 +81,7 @@ describe("matrixMessageActions account propagation", () => {
|
|||||||
accountId: "ops",
|
accountId: "ops",
|
||||||
}),
|
}),
|
||||||
expect.any(Object),
|
expect.any(Object),
|
||||||
|
{ mediaLocalRoots: undefined },
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -103,6 +105,7 @@ describe("matrixMessageActions account propagation", () => {
|
|||||||
avatarUrl: "mxc://example/avatar",
|
avatarUrl: "mxc://example/avatar",
|
||||||
}),
|
}),
|
||||||
expect.any(Object),
|
expect.any(Object),
|
||||||
|
{ mediaLocalRoots: undefined },
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -124,6 +127,32 @@ describe("matrixMessageActions account propagation", () => {
|
|||||||
avatarPath: "/tmp/avatar.jpg",
|
avatarPath: "/tmp/avatar.jpg",
|
||||||
}),
|
}),
|
||||||
expect.any(Object),
|
expect.any(Object),
|
||||||
|
{ mediaLocalRoots: undefined },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("forwards mediaLocalRoots for media sends", async () => {
|
||||||
|
await matrixMessageActions.handleAction?.(
|
||||||
|
createContext({
|
||||||
|
action: "send",
|
||||||
|
accountId: "ops",
|
||||||
|
mediaLocalRoots: ["/tmp/openclaw-matrix-test"],
|
||||||
|
params: {
|
||||||
|
to: "room:!room:example",
|
||||||
|
message: "hello",
|
||||||
|
media: "file:///tmp/photo.png",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mocks.handleMatrixAction).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
action: "sendMessage",
|
||||||
|
accountId: "ops",
|
||||||
|
mediaUrl: "file:///tmp/photo.png",
|
||||||
|
}),
|
||||||
|
expect.any(Object),
|
||||||
|
{ mediaLocalRoots: ["/tmp/openclaw-matrix-test"] },
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export const matrixMessageActions: ChannelMessageActionAdapter = {
|
|||||||
return { to };
|
return { to };
|
||||||
},
|
},
|
||||||
handleAction: async (ctx: ChannelMessageActionContext) => {
|
handleAction: async (ctx: ChannelMessageActionContext) => {
|
||||||
const { action, params, cfg, accountId } = ctx;
|
const { action, params, cfg, accountId, mediaLocalRoots } = ctx;
|
||||||
const dispatch = async (actionParams: Record<string, unknown>) =>
|
const dispatch = async (actionParams: Record<string, unknown>) =>
|
||||||
await handleMatrixAction(
|
await handleMatrixAction(
|
||||||
{
|
{
|
||||||
@@ -101,6 +101,7 @@ export const matrixMessageActions: ChannelMessageActionAdapter = {
|
|||||||
...(accountId ? { accountId } : {}),
|
...(accountId ? { accountId } : {}),
|
||||||
},
|
},
|
||||||
cfg as CoreConfig,
|
cfg as CoreConfig,
|
||||||
|
{ mediaLocalRoots },
|
||||||
);
|
);
|
||||||
const resolveRoomId = () =>
|
const resolveRoomId = () =>
|
||||||
readStringParam(params, "roomId") ??
|
readStringParam(params, "roomId") ??
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export async function sendMatrixMessage(
|
|||||||
return await sendMessageMatrix(to, content, {
|
return await sendMessageMatrix(to, content, {
|
||||||
cfg: opts.cfg,
|
cfg: opts.cfg,
|
||||||
mediaUrl: opts.mediaUrl,
|
mediaUrl: opts.mediaUrl,
|
||||||
|
mediaLocalRoots: opts.mediaLocalRoots,
|
||||||
replyToId: opts.replyToId,
|
replyToId: opts.replyToId,
|
||||||
threadId: opts.threadId,
|
threadId: opts.threadId,
|
||||||
accountId: opts.accountId ?? undefined,
|
accountId: opts.accountId ?? undefined,
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ export async function updateMatrixOwnProfile(
|
|||||||
avatarPath: avatarPath || undefined,
|
avatarPath: avatarPath || undefined,
|
||||||
loadAvatarFromUrl: async (url, maxBytes) => await runtime.media.loadWebMedia(url, maxBytes),
|
loadAvatarFromUrl: async (url, maxBytes) => await runtime.media.loadWebMedia(url, maxBytes),
|
||||||
loadAvatarFromPath: async (path, maxBytes) =>
|
loadAvatarFromPath: async (path, maxBytes) =>
|
||||||
await runtime.media.loadWebMedia(path, maxBytes),
|
await runtime.media.loadWebMedia(path, {
|
||||||
|
maxBytes,
|
||||||
|
localRoots: opts.mediaLocalRoots,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
"persist",
|
"persist",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export type RoomTopicEventContent = {
|
|||||||
export type MatrixActionClientOpts = {
|
export type MatrixActionClientOpts = {
|
||||||
client?: MatrixClient;
|
client?: MatrixClient;
|
||||||
cfg?: CoreConfig;
|
cfg?: CoreConfig;
|
||||||
|
mediaLocalRoots?: readonly string[];
|
||||||
timeoutMs?: number;
|
timeoutMs?: number;
|
||||||
accountId?: string | null;
|
accountId?: string | null;
|
||||||
readiness?: "none" | "prepared" | "started";
|
readiness?: "none" | "prepared" | "started";
|
||||||
|
|||||||
@@ -232,9 +232,27 @@ describe("sendMessageMatrix media", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(loadConfigMock).not.toHaveBeenCalled();
|
expect(loadConfigMock).not.toHaveBeenCalled();
|
||||||
expect(loadWebMediaMock).toHaveBeenCalledWith("file:///tmp/photo.png", 1024 * 1024);
|
expect(loadWebMediaMock).toHaveBeenCalledWith("file:///tmp/photo.png", {
|
||||||
|
maxBytes: 1024 * 1024,
|
||||||
|
localRoots: undefined,
|
||||||
|
});
|
||||||
expect(resolveTextChunkLimitMock).toHaveBeenCalledWith(explicitCfg, "matrix", "ops");
|
expect(resolveTextChunkLimitMock).toHaveBeenCalledWith(explicitCfg, "matrix", "ops");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("passes caller mediaLocalRoots to media loading", async () => {
|
||||||
|
const { client } = makeClient();
|
||||||
|
|
||||||
|
await sendMessageMatrix("room:!room:example", "caption", {
|
||||||
|
client,
|
||||||
|
mediaUrl: "file:///tmp/photo.png",
|
||||||
|
mediaLocalRoots: ["/tmp/openclaw-matrix-test"],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(loadWebMediaMock).toHaveBeenCalledWith("file:///tmp/photo.png", {
|
||||||
|
maxBytes: undefined,
|
||||||
|
localRoots: ["/tmp/openclaw-matrix-test"],
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("sendMessageMatrix threads", () => {
|
describe("sendMessageMatrix threads", () => {
|
||||||
|
|||||||
@@ -109,7 +109,10 @@ export async function sendMessageMatrix(
|
|||||||
let lastMessageId = "";
|
let lastMessageId = "";
|
||||||
if (opts.mediaUrl) {
|
if (opts.mediaUrl) {
|
||||||
const maxBytes = resolveMediaMaxBytes(opts.accountId, cfg);
|
const maxBytes = resolveMediaMaxBytes(opts.accountId, cfg);
|
||||||
const media = await getCore().media.loadWebMedia(opts.mediaUrl, maxBytes);
|
const media = await getCore().media.loadWebMedia(opts.mediaUrl, {
|
||||||
|
maxBytes,
|
||||||
|
localRoots: opts.mediaLocalRoots,
|
||||||
|
});
|
||||||
const uploaded = await uploadMediaMaybeEncrypted(client, roomId, media.buffer, {
|
const uploaded = await uploadMediaMaybeEncrypted(client, roomId, media.buffer, {
|
||||||
contentType: media.contentType,
|
contentType: media.contentType,
|
||||||
filename: media.fileName,
|
filename: media.fileName,
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ export type MatrixSendOpts = {
|
|||||||
client?: import("../sdk.js").MatrixClient;
|
client?: import("../sdk.js").MatrixClient;
|
||||||
cfg?: CoreConfig;
|
cfg?: CoreConfig;
|
||||||
mediaUrl?: string;
|
mediaUrl?: string;
|
||||||
|
mediaLocalRoots?: readonly string[];
|
||||||
accountId?: string;
|
accountId?: string;
|
||||||
replyToId?: string;
|
replyToId?: string;
|
||||||
threadId?: string | number | null;
|
threadId?: string | number | null;
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ describe("matrixOutbound cfg threading", () => {
|
|||||||
to: "room:!room:example",
|
to: "room:!room:example",
|
||||||
text: "caption",
|
text: "caption",
|
||||||
mediaUrl: "file:///tmp/cat.png",
|
mediaUrl: "file:///tmp/cat.png",
|
||||||
|
mediaLocalRoots: ["/tmp/openclaw"],
|
||||||
accountId: "default",
|
accountId: "default",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -84,6 +85,7 @@ describe("matrixOutbound cfg threading", () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
cfg,
|
cfg,
|
||||||
mediaUrl: "file:///tmp/cat.png",
|
mediaUrl: "file:///tmp/cat.png",
|
||||||
|
mediaLocalRoots: ["/tmp/openclaw"],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,13 +23,24 @@ export const matrixOutbound: ChannelOutboundAdapter = {
|
|||||||
roomId: result.roomId,
|
roomId: result.roomId,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
sendMedia: async ({ cfg, to, text, mediaUrl, deps, replyToId, threadId, accountId }) => {
|
sendMedia: async ({
|
||||||
|
cfg,
|
||||||
|
to,
|
||||||
|
text,
|
||||||
|
mediaUrl,
|
||||||
|
mediaLocalRoots,
|
||||||
|
deps,
|
||||||
|
replyToId,
|
||||||
|
threadId,
|
||||||
|
accountId,
|
||||||
|
}) => {
|
||||||
const send = deps?.sendMatrix ?? sendMessageMatrix;
|
const send = deps?.sendMatrix ?? sendMessageMatrix;
|
||||||
const resolvedThreadId =
|
const resolvedThreadId =
|
||||||
threadId !== undefined && threadId !== null ? String(threadId) : undefined;
|
threadId !== undefined && threadId !== null ? String(threadId) : undefined;
|
||||||
const result = await send(to, text, {
|
const result = await send(to, text, {
|
||||||
cfg,
|
cfg,
|
||||||
mediaUrl,
|
mediaUrl,
|
||||||
|
mediaLocalRoots,
|
||||||
replyToId: replyToId ?? undefined,
|
replyToId: replyToId ?? undefined,
|
||||||
threadId: resolvedThreadId,
|
threadId: resolvedThreadId,
|
||||||
accountId: accountId ?? undefined,
|
accountId: accountId ?? undefined,
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export async function applyMatrixProfileUpdate(params: {
|
|||||||
displayName?: string;
|
displayName?: string;
|
||||||
avatarUrl?: string;
|
avatarUrl?: string;
|
||||||
avatarPath?: string;
|
avatarPath?: string;
|
||||||
|
mediaLocalRoots?: readonly string[];
|
||||||
}): Promise<MatrixProfileUpdateResult> {
|
}): Promise<MatrixProfileUpdateResult> {
|
||||||
const runtime = getMatrixRuntime();
|
const runtime = getMatrixRuntime();
|
||||||
const persistedCfg = runtime.config.loadConfig() as CoreConfig;
|
const persistedCfg = runtime.config.loadConfig() as CoreConfig;
|
||||||
@@ -41,6 +42,7 @@ export async function applyMatrixProfileUpdate(params: {
|
|||||||
displayName: displayName ?? undefined,
|
displayName: displayName ?? undefined,
|
||||||
avatarUrl: avatarUrl ?? undefined,
|
avatarUrl: avatarUrl ?? undefined,
|
||||||
avatarPath: avatarPath ?? undefined,
|
avatarPath: avatarPath ?? undefined,
|
||||||
|
mediaLocalRoots: params.mediaLocalRoots,
|
||||||
});
|
});
|
||||||
const persistedAvatarUrl =
|
const persistedAvatarUrl =
|
||||||
synced.uploadedAvatarSource && synced.resolvedAvatarUrl ? synced.resolvedAvatarUrl : avatarUrl;
|
synced.uploadedAvatarSource && synced.resolvedAvatarUrl ? synced.resolvedAvatarUrl : avatarUrl;
|
||||||
|
|||||||
@@ -194,17 +194,41 @@ describe("handleMatrixAction pollVote", () => {
|
|||||||
threadId: "$thread",
|
threadId: "$thread",
|
||||||
},
|
},
|
||||||
cfg,
|
cfg,
|
||||||
|
{ mediaLocalRoots: ["/tmp/openclaw-matrix-test"] },
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(mocks.sendMatrixMessage).toHaveBeenCalledWith("room:!room:example", "hello", {
|
expect(mocks.sendMatrixMessage).toHaveBeenCalledWith("room:!room:example", "hello", {
|
||||||
cfg,
|
cfg,
|
||||||
accountId: "ops",
|
accountId: "ops",
|
||||||
mediaUrl: undefined,
|
mediaUrl: undefined,
|
||||||
|
mediaLocalRoots: ["/tmp/openclaw-matrix-test"],
|
||||||
replyToId: undefined,
|
replyToId: undefined,
|
||||||
threadId: "$thread",
|
threadId: "$thread",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("passes mediaLocalRoots to profile updates", async () => {
|
||||||
|
const cfg = { channels: { matrix: { actions: { profile: true } } } } as CoreConfig;
|
||||||
|
await handleMatrixAction(
|
||||||
|
{
|
||||||
|
action: "setProfile",
|
||||||
|
accountId: "ops",
|
||||||
|
avatarPath: "/tmp/avatar.jpg",
|
||||||
|
},
|
||||||
|
cfg,
|
||||||
|
{ mediaLocalRoots: ["/tmp/openclaw-matrix-test"] },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mocks.applyMatrixProfileUpdate).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
cfg,
|
||||||
|
account: "ops",
|
||||||
|
avatarPath: "/tmp/avatar.jpg",
|
||||||
|
mediaLocalRoots: ["/tmp/openclaw-matrix-test"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("passes account-scoped opts to pin listing", async () => {
|
it("passes account-scoped opts to pin listing", async () => {
|
||||||
const cfg = { channels: { matrix: { actions: { pins: true } } } } as CoreConfig;
|
const cfg = { channels: { matrix: { actions: { pins: true } } } } as CoreConfig;
|
||||||
await handleMatrixAction(
|
await handleMatrixAction(
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ function readNumericArrayParam(
|
|||||||
export async function handleMatrixAction(
|
export async function handleMatrixAction(
|
||||||
params: Record<string, unknown>,
|
params: Record<string, unknown>,
|
||||||
cfg: CoreConfig,
|
cfg: CoreConfig,
|
||||||
|
opts: { mediaLocalRoots?: readonly string[] } = {},
|
||||||
): Promise<AgentToolResult<unknown>> {
|
): Promise<AgentToolResult<unknown>> {
|
||||||
const action = readStringParam(params, "action", { required: true });
|
const action = readStringParam(params, "action", { required: true });
|
||||||
const accountId = readStringParam(params, "accountId") ?? undefined;
|
const accountId = readStringParam(params, "accountId") ?? undefined;
|
||||||
@@ -204,6 +205,7 @@ export async function handleMatrixAction(
|
|||||||
const threadId = readStringParam(params, "threadId");
|
const threadId = readStringParam(params, "threadId");
|
||||||
const result = await sendMatrixMessage(to, content, {
|
const result = await sendMatrixMessage(to, content, {
|
||||||
mediaUrl: mediaUrl ?? undefined,
|
mediaUrl: mediaUrl ?? undefined,
|
||||||
|
mediaLocalRoots: opts.mediaLocalRoots,
|
||||||
replyToId: replyToId ?? undefined,
|
replyToId: replyToId ?? undefined,
|
||||||
threadId: threadId ?? undefined,
|
threadId: threadId ?? undefined,
|
||||||
...clientOpts,
|
...clientOpts,
|
||||||
@@ -278,6 +280,7 @@ export async function handleMatrixAction(
|
|||||||
displayName: readStringParam(params, "displayName") ?? readStringParam(params, "name"),
|
displayName: readStringParam(params, "displayName") ?? readStringParam(params, "name"),
|
||||||
avatarUrl: readStringParam(params, "avatarUrl"),
|
avatarUrl: readStringParam(params, "avatarUrl"),
|
||||||
avatarPath,
|
avatarPath,
|
||||||
|
mediaLocalRoots: opts.mediaLocalRoots,
|
||||||
});
|
});
|
||||||
return jsonResult({ ok: true, ...result });
|
return jsonResult({ ok: true, ...result });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ type SendMatrixMessage = (
|
|||||||
cfg?: OpenClawConfig;
|
cfg?: OpenClawConfig;
|
||||||
accountId?: string;
|
accountId?: string;
|
||||||
mediaUrl?: string;
|
mediaUrl?: string;
|
||||||
|
mediaLocalRoots?: readonly string[];
|
||||||
replyToId?: string;
|
replyToId?: string;
|
||||||
threadId?: string;
|
threadId?: string;
|
||||||
timeoutMs?: number;
|
timeoutMs?: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user