fix: route decrypted E2EE events to message handler
Previously room.decrypted_event was only logged but not processed, causing encrypted messages (including files) to be ignored by agents with encryption enabled (like Mona).
This commit is contained in:
parent
2c653096eb
commit
8e36620140
11 changed files with 88 additions and 21 deletions
|
|
@ -45,6 +45,9 @@ export const matrixMessageActions: ChannelMessageActionAdapter = {
|
|||
},
|
||||
handleAction: async (ctx: ChannelMessageActionContext) => {
|
||||
const { action, params, cfg } = ctx;
|
||||
// Get accountId from context for multi-account support
|
||||
const accountId = (ctx as { accountId?: string }).accountId ?? undefined;
|
||||
|
||||
const resolveRoomId = () =>
|
||||
readStringParam(params, "roomId") ??
|
||||
readStringParam(params, "channelId") ??
|
||||
|
|
@ -67,6 +70,7 @@ export const matrixMessageActions: ChannelMessageActionAdapter = {
|
|||
mediaUrl: mediaUrl ?? undefined,
|
||||
replyToId: replyTo ?? undefined,
|
||||
threadId: threadId ?? undefined,
|
||||
accountId,
|
||||
},
|
||||
cfg,
|
||||
);
|
||||
|
|
@ -83,6 +87,7 @@ export const matrixMessageActions: ChannelMessageActionAdapter = {
|
|||
messageId,
|
||||
emoji,
|
||||
remove,
|
||||
accountId,
|
||||
},
|
||||
cfg,
|
||||
);
|
||||
|
|
@ -97,6 +102,7 @@ export const matrixMessageActions: ChannelMessageActionAdapter = {
|
|||
roomId: resolveRoomId(),
|
||||
messageId,
|
||||
limit,
|
||||
accountId,
|
||||
},
|
||||
cfg,
|
||||
);
|
||||
|
|
@ -111,6 +117,7 @@ export const matrixMessageActions: ChannelMessageActionAdapter = {
|
|||
limit,
|
||||
before: readStringParam(params, "before"),
|
||||
after: readStringParam(params, "after"),
|
||||
accountId,
|
||||
},
|
||||
cfg,
|
||||
);
|
||||
|
|
@ -125,6 +132,7 @@ export const matrixMessageActions: ChannelMessageActionAdapter = {
|
|||
roomId: resolveRoomId(),
|
||||
messageId,
|
||||
content,
|
||||
accountId,
|
||||
},
|
||||
cfg,
|
||||
);
|
||||
|
|
@ -137,6 +145,7 @@ export const matrixMessageActions: ChannelMessageActionAdapter = {
|
|||
action: "deleteMessage",
|
||||
roomId: resolveRoomId(),
|
||||
messageId,
|
||||
accountId,
|
||||
},
|
||||
cfg,
|
||||
);
|
||||
|
|
@ -153,6 +162,7 @@ export const matrixMessageActions: ChannelMessageActionAdapter = {
|
|||
action === "pin" ? "pinMessage" : action === "unpin" ? "unpinMessage" : "listPins",
|
||||
roomId: resolveRoomId(),
|
||||
messageId,
|
||||
accountId,
|
||||
},
|
||||
cfg,
|
||||
);
|
||||
|
|
@ -165,6 +175,7 @@ export const matrixMessageActions: ChannelMessageActionAdapter = {
|
|||
action: "memberInfo",
|
||||
userId,
|
||||
roomId: readStringParam(params, "roomId") ?? readStringParam(params, "channelId"),
|
||||
accountId,
|
||||
},
|
||||
cfg,
|
||||
);
|
||||
|
|
@ -175,6 +186,7 @@ export const matrixMessageActions: ChannelMessageActionAdapter = {
|
|||
{
|
||||
action: "channelInfo",
|
||||
roomId: resolveRoomId(),
|
||||
accountId,
|
||||
},
|
||||
cfg,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -20,18 +20,23 @@ export async function resolveActionClient(
|
|||
): Promise<MatrixActionClient> {
|
||||
ensureNodeRuntime();
|
||||
if (opts.client) return { client: opts.client, stopOnDone: false };
|
||||
const active = getActiveMatrixClient();
|
||||
|
||||
// Try to get the active client for the specified account
|
||||
const active = getActiveMatrixClient(opts.accountId);
|
||||
if (active) return { client: active, stopOnDone: false };
|
||||
|
||||
const shouldShareClient = Boolean(process.env.OPENCLAW_GATEWAY_PORT);
|
||||
if (shouldShareClient) {
|
||||
const client = await resolveSharedMatrixClient({
|
||||
cfg: getMatrixRuntime().config.loadConfig() as CoreConfig,
|
||||
timeoutMs: opts.timeoutMs,
|
||||
accountId: opts.accountId,
|
||||
});
|
||||
return { client, stopOnDone: false };
|
||||
}
|
||||
const auth = await resolveMatrixAuth({
|
||||
cfg: getMatrixRuntime().config.loadConfig() as CoreConfig,
|
||||
accountId: opts.accountId ?? undefined,
|
||||
});
|
||||
const client = await createMatrixClient({
|
||||
homeserver: auth.homeserver,
|
||||
|
|
@ -39,6 +44,7 @@ export async function resolveActionClient(
|
|||
accessToken: auth.accessToken,
|
||||
encryption: auth.encryption,
|
||||
localTimeoutMs: opts.timeoutMs,
|
||||
accountId: opts.accountId ?? undefined,
|
||||
});
|
||||
if (auth.encryption && client.crypto) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ export async function sendMatrixMessage(
|
|||
threadId: opts.threadId,
|
||||
client: opts.client,
|
||||
timeoutMs: opts.timeoutMs,
|
||||
accountId: opts.accountId,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ export type MatrixRawEvent = {
|
|||
export type MatrixActionClientOpts = {
|
||||
client?: MatrixClient;
|
||||
timeoutMs?: number;
|
||||
accountId?: string | null;
|
||||
};
|
||||
|
||||
export type MatrixMessageSummary = {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,34 @@
|
|||
import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
|
||||
|
||||
let activeClient: MatrixClient | null = null;
|
||||
const DEFAULT_ACCOUNT_KEY = "default";
|
||||
|
||||
export function setActiveMatrixClient(client: MatrixClient | null): void {
|
||||
activeClient = client;
|
||||
// Multi-account: Map of accountId -> client
|
||||
const activeClients = new Map<string, MatrixClient>();
|
||||
|
||||
function normalizeAccountKey(accountId?: string | null): string {
|
||||
return accountId?.trim().toLowerCase() || DEFAULT_ACCOUNT_KEY;
|
||||
}
|
||||
|
||||
export function getActiveMatrixClient(): MatrixClient | null {
|
||||
return activeClient;
|
||||
export function setActiveMatrixClient(client: MatrixClient | null, accountId?: string | null): void {
|
||||
const key = normalizeAccountKey(accountId);
|
||||
if (client) {
|
||||
activeClients.set(key, client);
|
||||
} else {
|
||||
activeClients.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
export function getActiveMatrixClient(accountId?: string | null): MatrixClient | null {
|
||||
const key = normalizeAccountKey(accountId);
|
||||
const client = activeClients.get(key);
|
||||
if (client) return client;
|
||||
// Fallback: if specific account not found, try default
|
||||
if (key !== DEFAULT_ACCOUNT_KEY) {
|
||||
return activeClients.get(DEFAULT_ACCOUNT_KEY) ?? null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function listActiveMatrixClients(): Array<{ accountId: string; client: MatrixClient }> {
|
||||
return Array.from(activeClients.entries()).map(([accountId, client]) => ({ accountId, client }));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ export function registerMatrixMonitorEvents(params: {
|
|||
const eventId = event?.event_id ?? "unknown";
|
||||
const eventType = event?.type ?? "unknown";
|
||||
logVerboseMessage(`matrix: decrypted event room=${roomId} type=${eventType} id=${eventId}`);
|
||||
// Process decrypted messages through the normal handler
|
||||
onRoomMessage(roomId, event);
|
||||
});
|
||||
|
||||
client.on(
|
||||
|
|
|
|||
|
|
@ -328,6 +328,11 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
|||
? content.file
|
||||
: undefined;
|
||||
const mediaUrl = contentUrl ?? contentFile?.url;
|
||||
|
||||
// DEBUG: Log media detection
|
||||
const msgtype = "msgtype" in content ? content.msgtype : undefined;
|
||||
logVerboseMessage(`matrix: content check msgtype=${msgtype} contentUrl=${contentUrl ?? "none"} mediaUrl=${mediaUrl ?? "none"} rawBody="${rawBody.slice(0,50)}"`);
|
||||
|
||||
if (!rawBody && !mediaUrl) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -340,6 +345,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
|||
const contentSize =
|
||||
typeof contentInfo?.size === "number" ? contentInfo.size : undefined;
|
||||
if (mediaUrl?.startsWith("mxc://")) {
|
||||
logVerboseMessage(`matrix: attempting media download url=${mediaUrl} size=${contentSize ?? "unknown"} maxBytes=${mediaMaxBytes}`);
|
||||
try {
|
||||
media = await downloadMatrixMedia({
|
||||
client,
|
||||
|
|
@ -349,9 +355,12 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
|||
maxBytes: mediaMaxBytes,
|
||||
file: contentFile,
|
||||
});
|
||||
logVerboseMessage(`matrix: media download success path=${media?.path ?? "none"}`);
|
||||
} catch (err) {
|
||||
logVerboseMessage(`matrix: media download failed: ${String(err)}`);
|
||||
}
|
||||
} else if (mediaUrl) {
|
||||
logVerboseMessage(`matrix: skipping non-mxc media url=${mediaUrl}`);
|
||||
}
|
||||
|
||||
const bodyText = rawBody || media?.placeholder || "";
|
||||
|
|
@ -544,7 +553,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
|||
}),
|
||||
);
|
||||
if (shouldAckReaction() && messageId) {
|
||||
reactMatrixMessage(roomId, messageId, ackReaction, client).catch((err) => {
|
||||
reactMatrixMessage(roomId, messageId, ackReaction, { client }).catch((err) => {
|
||||
logVerboseMessage(`matrix react failed for room ${roomId}: ${String(err)}`);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
|
|||
startClient: false,
|
||||
accountId: opts.accountId,
|
||||
});
|
||||
setActiveMatrixClient(client);
|
||||
setActiveMatrixClient(client, opts.accountId);
|
||||
|
||||
const mentionRegexes = core.channel.mentions.buildMentionRegexes(cfg);
|
||||
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
||||
|
|
@ -267,7 +267,7 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
|
|||
logVerboseMessage(`matrix: stopping client for account ${opts.accountId ?? "default"}`);
|
||||
stopSharedClient(opts.accountId);
|
||||
} finally {
|
||||
setActiveMatrixClient(null);
|
||||
setActiveMatrixClient(null, opts.accountId);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ export async function sendMessageMatrix(
|
|||
const { client, stopOnDone } = await resolveMatrixClient({
|
||||
client: opts.client,
|
||||
timeoutMs: opts.timeoutMs,
|
||||
accountId: opts.accountId,
|
||||
});
|
||||
try {
|
||||
const roomId = await resolveMatrixRoomId(client, to);
|
||||
|
|
@ -229,13 +230,14 @@ export async function reactMatrixMessage(
|
|||
roomId: string,
|
||||
messageId: string,
|
||||
emoji: string,
|
||||
client?: MatrixClient,
|
||||
opts: { client?: MatrixClient; accountId?: string | null } = {},
|
||||
): Promise<void> {
|
||||
if (!emoji.trim()) {
|
||||
throw new Error("Matrix reaction requires an emoji");
|
||||
}
|
||||
const { client: resolved, stopOnDone } = await resolveMatrixClient({
|
||||
client,
|
||||
client: opts.client,
|
||||
accountId: opts.accountId,
|
||||
});
|
||||
try {
|
||||
const resolvedRoom = await resolveMatrixRoomId(resolved, roomId);
|
||||
|
|
|
|||
|
|
@ -29,25 +29,31 @@ export function resolveMediaMaxBytes(): number | undefined {
|
|||
export async function resolveMatrixClient(opts: {
|
||||
client?: MatrixClient;
|
||||
timeoutMs?: number;
|
||||
accountId?: string | null;
|
||||
}): Promise<{ client: MatrixClient; stopOnDone: boolean }> {
|
||||
ensureNodeRuntime();
|
||||
if (opts.client) return { client: opts.client, stopOnDone: false };
|
||||
const active = getActiveMatrixClient();
|
||||
|
||||
// Try to get the active client for the specified account
|
||||
const active = getActiveMatrixClient(opts.accountId);
|
||||
if (active) return { client: active, stopOnDone: false };
|
||||
|
||||
const shouldShareClient = Boolean(process.env.OPENCLAW_GATEWAY_PORT);
|
||||
if (shouldShareClient) {
|
||||
const client = await resolveSharedMatrixClient({
|
||||
timeoutMs: opts.timeoutMs,
|
||||
accountId: opts.accountId,
|
||||
});
|
||||
return { client, stopOnDone: false };
|
||||
}
|
||||
const auth = await resolveMatrixAuth();
|
||||
const auth = await resolveMatrixAuth({ accountId: opts.accountId ?? undefined });
|
||||
const client = await createMatrixClient({
|
||||
homeserver: auth.homeserver,
|
||||
userId: auth.userId,
|
||||
accessToken: auth.accessToken,
|
||||
encryption: auth.encryption,
|
||||
localTimeoutMs: opts.timeoutMs,
|
||||
accountId: opts.accountId ?? undefined,
|
||||
});
|
||||
if (auth.encryption && client.crypto) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ export async function handleMatrixAction(
|
|||
cfg: CoreConfig,
|
||||
): Promise<AgentToolResult<unknown>> {
|
||||
const action = readStringParam(params, "action", { required: true });
|
||||
const accountId = readStringParam(params, "accountId") ?? undefined;
|
||||
const isActionEnabled = createActionGate(cfg.channels?.matrix?.actions);
|
||||
|
||||
if (reactionActions.has(action)) {
|
||||
|
|
@ -54,13 +55,14 @@ export async function handleMatrixAction(
|
|||
if (remove || isEmpty) {
|
||||
const result = await removeMatrixReactions(roomId, messageId, {
|
||||
emoji: remove ? emoji : undefined,
|
||||
accountId,
|
||||
});
|
||||
return jsonResult({ ok: true, removed: result.removed });
|
||||
}
|
||||
await reactMatrixMessage(roomId, messageId, emoji);
|
||||
await reactMatrixMessage(roomId, messageId, emoji, { accountId });
|
||||
return jsonResult({ ok: true, added: emoji });
|
||||
}
|
||||
const reactions = await listMatrixReactions(roomId, messageId);
|
||||
const reactions = await listMatrixReactions(roomId, messageId, { accountId });
|
||||
return jsonResult({ ok: true, reactions });
|
||||
}
|
||||
|
||||
|
|
@ -82,6 +84,7 @@ export async function handleMatrixAction(
|
|||
mediaUrl: mediaUrl ?? undefined,
|
||||
replyToId: replyToId ?? undefined,
|
||||
threadId: threadId ?? undefined,
|
||||
accountId,
|
||||
});
|
||||
return jsonResult({ ok: true, result });
|
||||
}
|
||||
|
|
@ -89,14 +92,14 @@ export async function handleMatrixAction(
|
|||
const roomId = readRoomId(params);
|
||||
const messageId = readStringParam(params, "messageId", { required: true });
|
||||
const content = readStringParam(params, "content", { required: true });
|
||||
const result = await editMatrixMessage(roomId, messageId, content);
|
||||
const result = await editMatrixMessage(roomId, messageId, content, { accountId });
|
||||
return jsonResult({ ok: true, result });
|
||||
}
|
||||
case "deleteMessage": {
|
||||
const roomId = readRoomId(params);
|
||||
const messageId = readStringParam(params, "messageId", { required: true });
|
||||
const reason = readStringParam(params, "reason");
|
||||
await deleteMatrixMessage(roomId, messageId, { reason: reason ?? undefined });
|
||||
await deleteMatrixMessage(roomId, messageId, { reason: reason ?? undefined, accountId });
|
||||
return jsonResult({ ok: true, deleted: true });
|
||||
}
|
||||
case "readMessages": {
|
||||
|
|
@ -108,6 +111,7 @@ export async function handleMatrixAction(
|
|||
limit: limit ?? undefined,
|
||||
before: before ?? undefined,
|
||||
after: after ?? undefined,
|
||||
accountId,
|
||||
});
|
||||
return jsonResult({ ok: true, ...result });
|
||||
}
|
||||
|
|
@ -123,15 +127,15 @@ export async function handleMatrixAction(
|
|||
const roomId = readRoomId(params);
|
||||
if (action === "pinMessage") {
|
||||
const messageId = readStringParam(params, "messageId", { required: true });
|
||||
const result = await pinMatrixMessage(roomId, messageId);
|
||||
const result = await pinMatrixMessage(roomId, messageId, { accountId });
|
||||
return jsonResult({ ok: true, pinned: result.pinned });
|
||||
}
|
||||
if (action === "unpinMessage") {
|
||||
const messageId = readStringParam(params, "messageId", { required: true });
|
||||
const result = await unpinMatrixMessage(roomId, messageId);
|
||||
const result = await unpinMatrixMessage(roomId, messageId, { accountId });
|
||||
return jsonResult({ ok: true, pinned: result.pinned });
|
||||
}
|
||||
const result = await listMatrixPins(roomId);
|
||||
const result = await listMatrixPins(roomId, { accountId });
|
||||
return jsonResult({ ok: true, pinned: result.pinned, events: result.events });
|
||||
}
|
||||
|
||||
|
|
@ -143,6 +147,7 @@ export async function handleMatrixAction(
|
|||
const roomId = readStringParam(params, "roomId") ?? readStringParam(params, "channelId");
|
||||
const result = await getMatrixMemberInfo(userId, {
|
||||
roomId: roomId ?? undefined,
|
||||
accountId,
|
||||
});
|
||||
return jsonResult({ ok: true, member: result });
|
||||
}
|
||||
|
|
@ -152,7 +157,7 @@ export async function handleMatrixAction(
|
|||
throw new Error("Matrix room info is disabled.");
|
||||
}
|
||||
const roomId = readRoomId(params);
|
||||
const result = await getMatrixRoomInfo(roomId);
|
||||
const result = await getMatrixRoomInfo(roomId, { accountId });
|
||||
return jsonResult({ ok: true, room: result });
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue