From 757bfebc316c5cbeaa9769a39379e548b950103b Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Mon, 9 Mar 2026 04:42:41 -0400 Subject: [PATCH] Matrix: align reaction routing with thread bindings --- .../matrix/src/matrix/monitor/handler.test.ts | 62 +++++++++++++++++++ .../src/matrix/monitor/reaction-events.ts | 28 ++++++--- 2 files changed, 83 insertions(+), 7 deletions(-) diff --git a/extensions/matrix/src/matrix/monitor/handler.test.ts b/extensions/matrix/src/matrix/monitor/handler.test.ts index c49ac0a0cdd..c826dc6e576 100644 --- a/extensions/matrix/src/matrix/monitor/handler.test.ts +++ b/extensions/matrix/src/matrix/monitor/handler.test.ts @@ -314,6 +314,68 @@ describe("matrix monitor handler pairing account scope", () => { ); }); + it("routes reaction notifications for bound thread messages to the bound session", async () => { + registerSessionBindingAdapter({ + channel: "matrix", + accountId: "ops", + listBySession: () => [], + resolveByConversation: (ref) => + ref.conversationId === "$root" + ? { + bindingId: "ops:!room:example.org:$root", + targetSessionKey: "agent:bound:session-1", + targetKind: "session", + conversation: { + channel: "matrix", + accountId: "ops", + conversationId: "$root", + parentConversationId: "!room:example.org", + }, + status: "active", + boundAt: Date.now(), + metadata: { + boundBy: "user-1", + }, + } + : null, + touch: vi.fn(), + }); + + const { handler, enqueueSystemEvent } = createMatrixHandlerTestHarness({ + client: { + getEvent: async () => + createMatrixTextMessageEvent({ + eventId: "$reply1", + sender: "@bot:example.org", + body: "follow up", + relatesTo: { + rel_type: "m.thread", + event_id: "$root", + "m.in_reply_to": { event_id: "$root" }, + }, + }), + }, + isDirectMessage: false, + }); + + await handler( + "!room:example.org", + createMatrixReactionEvent({ + eventId: "$reaction-thread", + targetEventId: "$reply1", + key: "🎯", + }), + ); + + expect(enqueueSystemEvent).toHaveBeenCalledWith( + "Matrix reaction added: 🎯 by sender on msg $reply1", + { + sessionKey: "agent:bound:session-1", + contextKey: "matrix:reaction:add:!room:example.org:$reply1:@user:example.org:🎯", + }, + ); + }); + it("ignores reactions that do not target bot-authored messages", async () => { const { handler, enqueueSystemEvent, resolveAgentRoute } = createReactionHarness({ targetSender: "@other:example.org", diff --git a/extensions/matrix/src/matrix/monitor/reaction-events.ts b/extensions/matrix/src/matrix/monitor/reaction-events.ts index 5631ec3a781..2eef8f06f39 100644 --- a/extensions/matrix/src/matrix/monitor/reaction-events.ts +++ b/extensions/matrix/src/matrix/monitor/reaction-events.ts @@ -3,7 +3,9 @@ import type { CoreConfig } from "../../types.js"; import { resolveMatrixAccountConfig } from "../accounts.js"; import { extractMatrixReactionAnnotation } from "../reaction-common.js"; import type { MatrixClient } from "../sdk.js"; -import type { MatrixRawEvent } from "./types.js"; +import { resolveMatrixInboundRoute } from "./route.js"; +import { resolveMatrixThreadRootId } from "./threads.js"; +import type { MatrixRawEvent, RoomMessageEventContent } from "./types.js"; export type MatrixReactionNotificationMode = "off" | "own"; @@ -60,14 +62,26 @@ export async function handleInboundMatrixReaction(params: { return; } - const route = params.core.channel.routing.resolveAgentRoute({ + const targetContent = + targetEvent && targetEvent.content && typeof targetEvent.content === "object" + ? (targetEvent.content as RoomMessageEventContent) + : undefined; + const threadRootId = targetContent + ? resolveMatrixThreadRootId({ + event: targetEvent as MatrixRawEvent, + content: targetContent, + }) + : undefined; + const { route } = resolveMatrixInboundRoute({ cfg: params.cfg, - channel: "matrix", accountId: params.accountId, - peer: { - kind: params.isDirectMessage ? "direct" : "channel", - id: params.isDirectMessage ? params.senderId : params.roomId, - }, + roomId: params.roomId, + senderId: params.senderId, + isDirectMessage: params.isDirectMessage, + messageId: reaction.eventId, + threadRootId, + eventTs: params.event.origin_server_ts, + resolveAgentRoute: params.core.channel.routing.resolveAgentRoute, }); const text = `Matrix reaction added: ${reaction.key} by ${params.senderLabel} on msg ${reaction.eventId}`; params.core.system.enqueueSystemEvent(text, {