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 <dinakars777@users.noreply.github.com>
This commit is contained in:
Dinakar Sarbada
2026-03-21 07:43:59 -07:00
committed by GitHub
parent a3a5cad7d7
commit 1643d15057
5 changed files with 64 additions and 63 deletions

View File

@@ -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),

View File

@@ -134,6 +134,9 @@ export function createMatrixHandlerTestHarness(
routing: {
resolveAgentRoute,
},
mentions: {
buildMentionRegexes: () => options.mentionRegexes ?? [],
},
session: {
resolveStorePath: options.resolveStorePath ?? (() => "/tmp/session-store"),
readSessionUpdatedAt: options.readSessionUpdatedAt ?? (() => undefined),

View File

@@ -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 () => {

View File

@@ -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),

View File

@@ -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;
}