From 1643d15057ec365bfdf8069e2ea3119655422fb6 Mon Sep 17 00:00:00 2001 From: Dinakar Sarbada Date: Sat, 21 Mar 2026 07:43:59 -0700 Subject: [PATCH] fix(matrix): pass agentId to buildMentionRegexes for agent-level mention patterns (#51272) * fix(matrix): pass agentId to buildMentionRegexes for agent-level mention patterns * fix(matrix): resolve conflicts from main branch * Retrigger CI --------- Co-authored-by: Dinakar Sarbada --- .../monitor/handler.media-failure.test.ts | 3 + .../matrix/monitor/handler.test-helpers.ts | 3 + .../matrix/src/matrix/monitor/handler.test.ts | 25 ++--- .../monitor/handler.thread-root-media.test.ts | 3 + .../matrix/src/matrix/monitor/handler.ts | 93 ++++++++++--------- 5 files changed, 64 insertions(+), 63 deletions(-) diff --git a/extensions/matrix/src/matrix/monitor/handler.media-failure.test.ts b/extensions/matrix/src/matrix/monitor/handler.media-failure.test.ts index 8623d8541f2..d545e479564 100644 --- a/extensions/matrix/src/matrix/monitor/handler.media-failure.test.ts +++ b/extensions/matrix/src/matrix/monitor/handler.media-failure.test.ts @@ -40,6 +40,9 @@ function createHandlerHarness() { mainSessionKey: "agent:main:main", }), }, + mentions: { + buildMentionRegexes: vi.fn().mockReturnValue([]), + }, session: { resolveStorePath: vi.fn().mockReturnValue("/tmp/openclaw-test-session.json"), readSessionUpdatedAt: vi.fn().mockReturnValue(123), diff --git a/extensions/matrix/src/matrix/monitor/handler.test-helpers.ts b/extensions/matrix/src/matrix/monitor/handler.test-helpers.ts index 585ce851b0a..a52518a8aa7 100644 --- a/extensions/matrix/src/matrix/monitor/handler.test-helpers.ts +++ b/extensions/matrix/src/matrix/monitor/handler.test-helpers.ts @@ -134,6 +134,9 @@ export function createMatrixHandlerTestHarness( routing: { resolveAgentRoute, }, + mentions: { + buildMentionRegexes: () => options.mentionRegexes ?? [], + }, session: { resolveStorePath: options.resolveStorePath ?? (() => "/tmp/session-store"), readSessionUpdatedAt: options.readSessionUpdatedAt ?? (() => undefined), diff --git a/extensions/matrix/src/matrix/monitor/handler.test.ts b/extensions/matrix/src/matrix/monitor/handler.test.ts index 8e842e38baa..a2acddafb28 100644 --- a/extensions/matrix/src/matrix/monitor/handler.test.ts +++ b/extensions/matrix/src/matrix/monitor/handler.test.ts @@ -261,7 +261,7 @@ describe("matrix monitor handler pairing account scope", () => { }); it("drops room messages from configured Matrix bot accounts when allowBots is off", async () => { - const { handler, resolveAgentRoute, recordInboundSession } = createMatrixHandlerTestHarness({ + const { handler, recordInboundSession } = createMatrixHandlerTestHarness({ isDirectMessage: false, configuredBotUserIds: new Set(["@ops:example.org"]), roomsConfig: { @@ -279,12 +279,11 @@ describe("matrix monitor handler pairing account scope", () => { }), ); - expect(resolveAgentRoute).not.toHaveBeenCalled(); expect(recordInboundSession).not.toHaveBeenCalled(); }); it("accepts room messages from configured Matrix bot accounts when allowBots is true", async () => { - const { handler, resolveAgentRoute, recordInboundSession } = createMatrixHandlerTestHarness({ + const { handler, recordInboundSession } = createMatrixHandlerTestHarness({ isDirectMessage: false, accountAllowBots: true, configuredBotUserIds: new Set(["@ops:example.org"]), @@ -303,7 +302,6 @@ describe("matrix monitor handler pairing account scope", () => { }), ); - expect(resolveAgentRoute).toHaveBeenCalled(); expect(recordInboundSession).toHaveBeenCalled(); }); @@ -331,7 +329,7 @@ describe("matrix monitor handler pairing account scope", () => { }); it('drops configured Matrix bot room messages without a mention when allowBots="mentions"', async () => { - const { handler, resolveAgentRoute, recordInboundSession } = createMatrixHandlerTestHarness({ + const { handler, recordInboundSession } = createMatrixHandlerTestHarness({ isDirectMessage: false, accountAllowBots: "mentions", configuredBotUserIds: new Set(["@ops:example.org"]), @@ -351,12 +349,11 @@ describe("matrix monitor handler pairing account scope", () => { }), ); - expect(resolveAgentRoute).not.toHaveBeenCalled(); expect(recordInboundSession).not.toHaveBeenCalled(); }); it('accepts configured Matrix bot room messages with a mention when allowBots="mentions"', async () => { - const { handler, resolveAgentRoute, recordInboundSession } = createMatrixHandlerTestHarness({ + const { handler, recordInboundSession } = createMatrixHandlerTestHarness({ isDirectMessage: false, accountAllowBots: "mentions", configuredBotUserIds: new Set(["@ops:example.org"]), @@ -377,12 +374,11 @@ describe("matrix monitor handler pairing account scope", () => { }), ); - expect(resolveAgentRoute).toHaveBeenCalled(); expect(recordInboundSession).toHaveBeenCalled(); }); it('accepts configured Matrix bot DMs without a mention when allowBots="mentions"', async () => { - const { handler, resolveAgentRoute, recordInboundSession } = createMatrixHandlerTestHarness({ + const { handler, recordInboundSession } = createMatrixHandlerTestHarness({ isDirectMessage: true, accountAllowBots: "mentions", configuredBotUserIds: new Set(["@ops:example.org"]), @@ -398,12 +394,11 @@ describe("matrix monitor handler pairing account scope", () => { }), ); - expect(resolveAgentRoute).toHaveBeenCalled(); expect(recordInboundSession).toHaveBeenCalled(); }); it("lets room-level allowBots override a permissive account default", async () => { - const { handler, resolveAgentRoute, recordInboundSession } = createMatrixHandlerTestHarness({ + const { handler, recordInboundSession } = createMatrixHandlerTestHarness({ isDirectMessage: false, accountAllowBots: true, configuredBotUserIds: new Set(["@ops:example.org"]), @@ -422,7 +417,6 @@ describe("matrix monitor handler pairing account scope", () => { }), ); - expect(resolveAgentRoute).not.toHaveBeenCalled(); expect(recordInboundSession).not.toHaveBeenCalled(); }); @@ -442,7 +436,6 @@ describe("matrix monitor handler pairing account scope", () => { }), ); - expect(resolveAgentRoute).not.toHaveBeenCalled(); expect(recordInboundSession).not.toHaveBeenCalled(); }); @@ -450,7 +443,7 @@ describe("matrix monitor handler pairing account scope", () => { const downloadContent = vi.fn(async () => Buffer.from("image")); const getMemberDisplayName = vi.fn(async () => "sender"); const getRoomInfo = vi.fn(async () => ({ altAliases: [] })); - const { handler, resolveAgentRoute } = createMatrixHandlerTestHarness({ + const { handler } = createMatrixHandlerTestHarness({ client: { downloadContent, }, @@ -479,7 +472,6 @@ describe("matrix monitor handler pairing account scope", () => { expect(downloadContent).not.toHaveBeenCalled(); expect(getMemberDisplayName).not.toHaveBeenCalled(); expect(getRoomInfo).not.toHaveBeenCalled(); - expect(resolveAgentRoute).not.toHaveBeenCalled(); }); it("skips poll snapshot fetches for unmentioned group poll responses", async () => { @@ -504,7 +496,7 @@ describe("matrix monitor handler pairing account scope", () => { })); const getMemberDisplayName = vi.fn(async () => "sender"); const getRoomInfo = vi.fn(async () => ({ altAliases: [] })); - const { handler, resolveAgentRoute } = createMatrixHandlerTestHarness({ + const { handler } = createMatrixHandlerTestHarness({ client: { getEvent, getRelations, @@ -535,7 +527,6 @@ describe("matrix monitor handler pairing account scope", () => { expect(getRelations).not.toHaveBeenCalled(); expect(getMemberDisplayName).not.toHaveBeenCalled(); expect(getRoomInfo).not.toHaveBeenCalled(); - expect(resolveAgentRoute).not.toHaveBeenCalled(); }); it("records thread starter context for inbound thread replies", async () => { diff --git a/extensions/matrix/src/matrix/monitor/handler.thread-root-media.test.ts b/extensions/matrix/src/matrix/monitor/handler.thread-root-media.test.ts index aea230f3afc..c8dfe5ddc8b 100644 --- a/extensions/matrix/src/matrix/monitor/handler.thread-root-media.test.ts +++ b/extensions/matrix/src/matrix/monitor/handler.thread-root-media.test.ts @@ -45,6 +45,9 @@ describe("createMatrixRoomMessageHandler thread root media", () => { mainSessionKey: "agent:main:main", }), }, + mentions: { + buildMentionRegexes: vi.fn().mockReturnValue([]), + }, session: { resolveStorePath: vi.fn().mockReturnValue("/tmp/openclaw-test-session.json"), readSessionUpdatedAt: vi.fn().mockReturnValue(undefined), diff --git a/extensions/matrix/src/matrix/monitor/handler.ts b/extensions/matrix/src/matrix/monitor/handler.ts index 40c386e3820..3e330059b40 100644 --- a/extensions/matrix/src/matrix/monitor/handler.ts +++ b/extensions/matrix/src/matrix/monitor/handler.ts @@ -527,11 +527,25 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam return; } + const _messageId = event.event_id ?? ""; + const _threadRootId = resolveMatrixThreadRootId({ event, content }); + const { route: _route, configuredBinding: _configuredBinding } = resolveMatrixInboundRoute({ + cfg, + accountId, + roomId, + senderId, + isDirectMessage, + messageId: _messageId, + threadRootId: _threadRootId, + eventTs: eventTs ?? undefined, + resolveAgentRoute: core.channel.routing.resolveAgentRoute, + }); + const agentMentionRegexes = core.channel.mentions.buildMentionRegexes(cfg, _route.agentId); const { wasMentioned, hasExplicitMention } = resolveMentions({ content, userId: selfUserId, text: mentionPrecheckText, - mentionRegexes, + mentionRegexes: agentMentionRegexes, }); if ( isConfiguredBotSender && @@ -588,7 +602,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam !hasExplicitMention && commandAuthorized && hasControlCommandInMessage; - const canDetectMention = mentionRegexes.length > 0 || hasExplicitMention; + const canDetectMention = agentMentionRegexes.length > 0 || hasExplicitMention; if (isRoom && shouldRequireMention && !wasMentioned && !shouldBypassMention) { logger.info("skipping room message", { roomId, reason: "no-mention" }); await commitInboundEventIfClaimed(); @@ -674,54 +688,41 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam const roomInfo = isRoom ? await getRoomInfo(roomId) : undefined; const roomName = roomInfo?.name; - const messageId = event.event_id ?? ""; const replyToEventId = content["m.relates_to"]?.["m.in_reply_to"]?.event_id; - const threadRootId = resolveMatrixThreadRootId({ event, content }); const threadTarget = resolveMatrixThreadTarget({ threadReplies, - messageId, - threadRootId, + messageId: _messageId, + threadRootId: _threadRootId, isThreadRoot: false, // Raw event payload does not carry explicit thread-root metadata. }); - const threadContext = threadRootId - ? await resolveThreadContext({ roomId, threadRootId }) + const threadContext = _threadRootId + ? await resolveThreadContext({ roomId, threadRootId: _threadRootId }) : undefined; - const { route, configuredBinding } = resolveMatrixInboundRoute({ - cfg, - accountId, - roomId, - senderId, - isDirectMessage, - messageId, - threadRootId, - eventTs: eventTs ?? undefined, - resolveAgentRoute: core.channel.routing.resolveAgentRoute, - }); - if (configuredBinding) { + if (_configuredBinding) { const ensured = await ensureConfiguredAcpBindingReady({ cfg, - configuredBinding, + configuredBinding: _configuredBinding, }); if (!ensured.ok) { logInboundDrop({ log: logVerboseMessage, channel: "matrix", reason: "configured ACP binding unavailable", - target: configuredBinding.spec.conversationId, + target: _configuredBinding.spec.conversationId, }); return; } } const envelopeFrom = isDirectMessage ? senderName : (roomName ?? roomId); - const textWithId = `${bodyText}\n[matrix event id: ${messageId} room: ${roomId}]`; + const textWithId = `${bodyText}\n[matrix event id: ${_messageId} room: ${roomId}]`; const storePath = core.channel.session.resolveStorePath(cfg.session?.store, { - agentId: route.agentId, + agentId: _route.agentId, }); const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg); const previousTimestamp = core.channel.session.readSessionUpdatedAt({ storePath, - sessionKey: route.sessionKey, + sessionKey: _route.sessionKey, }); const body = core.channel.reply.formatAgentEnvelope({ channel: "Matrix", @@ -739,8 +740,8 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam CommandBody: bodyText, From: isDirectMessage ? `matrix:${senderId}` : `matrix:channel:${roomId}`, To: `room:${roomId}`, - SessionKey: route.sessionKey, - AccountId: route.accountId, + SessionKey: _route.sessionKey, + AccountId: _route.accountId, ChatType: isDirectMessage ? "direct" : "channel", ConversationLabel: envelopeFrom, SenderName: senderName, @@ -752,7 +753,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam Provider: "matrix" as const, Surface: "matrix" as const, WasMentioned: isRoom ? wasMentioned : undefined, - MessageSid: messageId, + MessageSid: _messageId, ReplyToId: threadTarget ? undefined : (replyToEventId ?? undefined), MessageThreadId: threadTarget, ThreadStarterBody: threadContext?.threadStarterBody, @@ -769,21 +770,21 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam await core.channel.session.recordInboundSession({ storePath, - sessionKey: ctxPayload.SessionKey ?? route.sessionKey, + sessionKey: ctxPayload.SessionKey ?? _route.sessionKey, ctx: ctxPayload, updateLastRoute: isDirectMessage ? { - sessionKey: route.mainSessionKey, + sessionKey: _route.mainSessionKey, channel: "matrix", to: `room:${roomId}`, - accountId: route.accountId, + accountId: _route.accountId, } : undefined, onRecordError: (err) => { logger.warn("failed updating session meta", { error: String(err), storePath, - sessionKey: ctxPayload.SessionKey ?? route.sessionKey, + sessionKey: ctxPayload.SessionKey ?? _route.sessionKey, }); }, }); @@ -793,7 +794,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam const { ackReaction, ackReactionScope: ackScope } = resolveMatrixAckReactionConfig({ cfg, - agentId: route.agentId, + agentId: _route.agentId, accountId, }); const shouldAckReaction = () => @@ -810,8 +811,8 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam shouldBypassMention, }), ); - if (shouldAckReaction() && messageId) { - reactMatrixMessage(roomId, messageId, ackReaction, client).catch((err) => { + if (shouldAckReaction() && _messageId) { + reactMatrixMessage(roomId, _messageId, ackReaction, client).catch((err) => { logVerboseMessage(`matrix react failed for room ${roomId}: ${String(err)}`); }); } @@ -822,10 +823,10 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam return; } - if (messageId) { - sendReadReceiptMatrix(roomId, messageId, client).catch((err) => { + if (_messageId) { + sendReadReceiptMatrix(roomId, _messageId, client).catch((err) => { logVerboseMessage( - `matrix: read receipt failed room=${roomId} id=${messageId}: ${String(err)}`, + `matrix: read receipt failed room=${roomId} id=${_messageId}: ${String(err)}`, ); }); } @@ -833,16 +834,16 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam const tableMode = core.channel.text.resolveMarkdownTableMode({ cfg, channel: "matrix", - accountId: route.accountId, + accountId: _route.accountId, }); - const mediaLocalRoots = getAgentScopedMediaLocalRoots(cfg, route.agentId); + const mediaLocalRoots = getAgentScopedMediaLocalRoots(cfg, _route.agentId); let finalReplyDeliveryFailed = false; let nonFinalReplyDeliveryFailed = false; const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({ cfg, - agentId: route.agentId, + agentId: _route.agentId, channel: "matrix", - accountId: route.accountId, + accountId: _route.accountId, }); const typingCallbacks = createTypingCallbacks({ start: () => sendTypingMatrix(roomId, true, undefined, client), @@ -869,7 +870,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam const { dispatcher, replyOptions, markDispatchIdle, markRunComplete } = core.channel.reply.createReplyDispatcherWithTyping({ ...prefixOptions, - humanDelay: core.channel.reply.resolveHumanDelayConfig(cfg, route.agentId), + humanDelay: core.channel.reply.resolveHumanDelayConfig(cfg, _route.agentId), deliver: async (payload: ReplyPayload) => { await deliverMatrixReplies({ cfg, @@ -880,7 +881,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam textLimit, replyToMode, threadId: threadTarget, - accountId: route.accountId, + accountId: _route.accountId, mediaLocalRoots, tableMode, }); @@ -921,13 +922,13 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam }); if (finalReplyDeliveryFailed) { logVerboseMessage( - `matrix: final reply delivery failed room=${roomId} id=${messageId}; leaving event uncommitted`, + `matrix: final reply delivery failed room=${roomId} id=${_messageId}; leaving event uncommitted`, ); return; } if (!queuedFinal && nonFinalReplyDeliveryFailed) { logVerboseMessage( - `matrix: non-final reply delivery failed room=${roomId} id=${messageId}; leaving event uncommitted`, + `matrix: non-final reply delivery failed room=${roomId} id=${_messageId}; leaving event uncommitted`, ); return; }