Matrix: dedupe monitor handler tests

This commit is contained in:
Gustavo Madeira Santana 2026-03-09 04:26:21 -04:00
parent 272509ec4b
commit 22addcd6ec
No known key found for this signature in database
3 changed files with 410 additions and 746 deletions

View File

@ -1,9 +1,11 @@
import type { RuntimeEnv, RuntimeLogger } from "openclaw/plugin-sdk/matrix";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { setMatrixRuntime } from "../../runtime.js";
import type { MatrixClient } from "../sdk.js";
import { createMatrixRoomMessageHandler } from "./handler.js";
import { EventType, type MatrixRawEvent } from "./types.js";
import {
createMatrixHandlerTestHarness,
createMatrixTextMessageEvent,
} from "./handler.test-helpers.js";
import type { MatrixRawEvent } from "./types.js";
describe("createMatrixRoomMessageHandler inbound body formatting", () => {
beforeEach(() => {
@ -26,117 +28,34 @@ describe("createMatrixRoomMessageHandler inbound body formatting", () => {
});
it("records thread metadata for group thread messages", async () => {
const recordInboundSession = vi.fn(async () => {});
const finalizeInboundContext = vi.fn((ctx) => ctx);
const handler = createMatrixRoomMessageHandler({
client: {
getUserId: async () => "@bot:example.org",
getEvent: async () => ({
event_id: "$thread-root",
sender: "@alice:example.org",
type: EventType.RoomMessage,
origin_server_ts: Date.now(),
content: {
msgtype: "m.text",
body: "Root topic",
},
}),
} as never,
core: {
channel: {
pairing: {
readAllowFromStore: async () => [] as string[],
upsertPairingRequest: async () => ({ code: "ABCDEFGH", created: false }),
},
commands: {
shouldHandleTextCommands: () => false,
},
text: {
hasControlCommand: () => false,
resolveMarkdownTableMode: () => "preserve",
},
routing: {
resolveAgentRoute: () => ({
agentId: "ops",
channel: "matrix",
accountId: "ops",
sessionKey: "agent:ops:main",
mainSessionKey: "agent:ops:main",
matchedBy: "binding.account",
const { handler, finalizeInboundContext, recordInboundSession } =
createMatrixHandlerTestHarness({
client: {
getEvent: async () =>
createMatrixTextMessageEvent({
eventId: "$thread-root",
sender: "@alice:example.org",
body: "Root topic",
}),
},
session: {
resolveStorePath: () => "/tmp/session-store",
readSessionUpdatedAt: () => undefined,
recordInboundSession,
},
reply: {
resolveEnvelopeFormatOptions: () => ({}),
formatAgentEnvelope: ({ body }: { body: string }) => body,
finalizeInboundContext,
createReplyDispatcherWithTyping: () => ({
dispatcher: {},
replyOptions: {},
markDispatchIdle: () => {},
}),
resolveHumanDelayConfig: () => undefined,
dispatchReplyFromConfig: async () => ({
queuedFinal: false,
counts: { final: 0, block: 0, tool: 0 },
}),
},
reactions: {
shouldAckReaction: () => false,
},
},
} as never,
cfg: {} as never,
accountId: "ops",
runtime: {
error: () => {},
} as RuntimeEnv,
logger: {
info: () => {},
warn: () => {},
error: () => {},
} as RuntimeLogger,
logVerboseMessage: () => {},
allowFrom: [],
mentionRegexes: [],
groupPolicy: "open",
replyToMode: "off",
threadReplies: "inbound",
dmEnabled: true,
dmPolicy: "open",
textLimit: 8_000,
mediaMaxBytes: 10_000_000,
startupMs: 0,
startupGraceMs: 0,
directTracker: {
isDirectMessage: async () => false,
},
getRoomInfo: async () => ({ altAliases: [] }),
getMemberDisplayName: async (_roomId, userId) =>
userId === "@alice:example.org" ? "Alice" : "sender",
});
isDirectMessage: false,
getMemberDisplayName: async (_roomId, userId) =>
userId === "@alice:example.org" ? "Alice" : "sender",
});
await handler("!room:example.org", {
type: EventType.RoomMessage,
sender: "@user:example.org",
event_id: "$reply1",
origin_server_ts: Date.now(),
content: {
msgtype: "m.text",
await handler(
"!room:example.org",
createMatrixTextMessageEvent({
eventId: "$reply1",
body: "follow up",
"m.relates_to": {
relatesTo: {
rel_type: "m.thread",
event_id: "$thread-root",
"m.in_reply_to": { event_id: "$thread-root" },
},
"m.mentions": { room: true },
},
} as MatrixRawEvent);
mentions: { room: true },
}),
);
expect(finalizeInboundContext).toHaveBeenCalledWith(
expect.objectContaining({
@ -152,123 +71,47 @@ describe("createMatrixRoomMessageHandler inbound body formatting", () => {
});
it("records formatted poll results for inbound poll response events", async () => {
const recordInboundSession = vi.fn(async () => {});
const finalizeInboundContext = vi.fn((ctx) => ctx);
const handler = createMatrixRoomMessageHandler({
client: {
getUserId: async () => "@bot:example.org",
getEvent: async () => ({
event_id: "$poll",
sender: "@bot:example.org",
type: "m.poll.start",
origin_server_ts: 1,
content: {
"m.poll.start": {
question: { "m.text": "Lunch?" },
kind: "m.poll.disclosed",
max_selections: 1,
answers: [
{ id: "a1", "m.text": "Pizza" },
{ id: "a2", "m.text": "Sushi" },
],
},
},
}),
getRelations: async () => ({
events: [
{
type: "m.poll.response",
event_id: "$vote1",
sender: "@user:example.org",
origin_server_ts: 2,
content: {
"m.poll.response": { answers: ["a1"] },
"m.relates_to": { rel_type: "m.reference", event_id: "$poll" },
const { handler, finalizeInboundContext, recordInboundSession } =
createMatrixHandlerTestHarness({
client: {
getEvent: async () => ({
event_id: "$poll",
sender: "@bot:example.org",
type: "m.poll.start",
origin_server_ts: 1,
content: {
"m.poll.start": {
question: { "m.text": "Lunch?" },
kind: "m.poll.disclosed",
max_selections: 1,
answers: [
{ id: "a1", "m.text": "Pizza" },
{ id: "a2", "m.text": "Sushi" },
],
},
},
],
nextBatch: null,
prevBatch: null,
}),
} as unknown as MatrixClient,
core: {
channel: {
pairing: {
readAllowFromStore: async () => [] as string[],
upsertPairingRequest: async () => ({ code: "ABCDEFGH", created: false }),
},
commands: {
shouldHandleTextCommands: () => false,
},
text: {
hasControlCommand: () => false,
resolveMarkdownTableMode: () => "preserve",
},
routing: {
resolveAgentRoute: () => ({
agentId: "ops",
channel: "matrix",
accountId: "ops",
sessionKey: "agent:ops:main",
mainSessionKey: "agent:ops:main",
matchedBy: "binding.account",
}),
},
session: {
resolveStorePath: () => "/tmp/session-store",
readSessionUpdatedAt: () => undefined,
recordInboundSession,
},
reply: {
resolveEnvelopeFormatOptions: () => ({}),
formatAgentEnvelope: ({ body }: { body: string }) => body,
finalizeInboundContext,
createReplyDispatcherWithTyping: () => ({
dispatcher: {},
replyOptions: {},
markDispatchIdle: () => {},
}),
resolveHumanDelayConfig: () => undefined,
dispatchReplyFromConfig: async () => ({
queuedFinal: false,
counts: { final: 0, block: 0, tool: 0 },
}),
},
reactions: {
shouldAckReaction: () => false,
},
},
} as never,
cfg: {} as never,
accountId: "ops",
runtime: {
error: () => {},
} as RuntimeEnv,
logger: {
info: () => {},
warn: () => {},
error: () => {},
} as RuntimeLogger,
logVerboseMessage: () => {},
allowFrom: [],
mentionRegexes: [],
groupPolicy: "open",
replyToMode: "off",
threadReplies: "inbound",
dmEnabled: true,
dmPolicy: "open",
textLimit: 8_000,
mediaMaxBytes: 10_000_000,
startupMs: 0,
startupGraceMs: 0,
directTracker: {
isDirectMessage: async () => true,
},
getRoomInfo: async () => ({ altAliases: [] }),
getMemberDisplayName: async (_roomId, userId) =>
userId === "@bot:example.org" ? "Bot" : "sender",
});
}),
getRelations: async () => ({
events: [
{
type: "m.poll.response",
event_id: "$vote1",
sender: "@user:example.org",
origin_server_ts: 2,
content: {
"m.poll.response": { answers: ["a1"] },
"m.relates_to": { rel_type: "m.reference", event_id: "$poll" },
},
},
],
nextBatch: null,
prevBatch: null,
}),
} as unknown as Partial<MatrixClient>,
isDirectMessage: true,
getMemberDisplayName: async (_roomId, userId) =>
userId === "@bot:example.org" ? "Bot" : "sender",
});
await handler("!room:example.org", {
type: "m.poll.response",

View File

@ -0,0 +1,219 @@
import type { RuntimeEnv, RuntimeLogger } from "openclaw/plugin-sdk/matrix";
import { vi } from "vitest";
import type { MatrixRoomConfig, ReplyToMode } from "../../types.js";
import type { MatrixClient } from "../sdk.js";
import { createMatrixRoomMessageHandler, type MatrixMonitorHandlerParams } from "./handler.js";
import { EventType, type MatrixRawEvent, type RoomMessageEventContent } from "./types.js";
const DEFAULT_ROUTE = {
agentId: "ops",
channel: "matrix",
accountId: "ops",
sessionKey: "agent:ops:main",
mainSessionKey: "agent:ops:main",
matchedBy: "binding.account" as const,
};
type MatrixHandlerTestHarnessOptions = {
accountId?: string;
cfg?: unknown;
client?: Partial<MatrixClient>;
runtime?: RuntimeEnv;
logger?: RuntimeLogger;
logVerboseMessage?: (message: string) => void;
allowFrom?: string[];
groupAllowFrom?: string[];
roomsConfig?: Record<string, MatrixRoomConfig>;
mentionRegexes?: MatrixMonitorHandlerParams["mentionRegexes"];
groupPolicy?: "open" | "allowlist" | "disabled";
replyToMode?: ReplyToMode;
threadReplies?: "off" | "inbound" | "always";
dmEnabled?: boolean;
dmPolicy?: "pairing" | "allowlist" | "open" | "disabled";
textLimit?: number;
mediaMaxBytes?: number;
startupMs?: number;
startupGraceMs?: number;
isDirectMessage?: boolean;
readAllowFromStore?: MatrixMonitorHandlerParams["core"]["channel"]["pairing"]["readAllowFromStore"];
upsertPairingRequest?: MatrixMonitorHandlerParams["core"]["channel"]["pairing"]["upsertPairingRequest"];
buildPairingReply?: () => string;
shouldHandleTextCommands?: () => boolean;
hasControlCommand?: () => boolean;
resolveMarkdownTableMode?: () => string;
resolveAgentRoute?: () => typeof DEFAULT_ROUTE;
resolveStorePath?: () => string;
readSessionUpdatedAt?: () => number | undefined;
recordInboundSession?: (...args: unknown[]) => Promise<void>;
resolveEnvelopeFormatOptions?: () => Record<string, never>;
formatAgentEnvelope?: ({ body }: { body: string }) => string;
finalizeInboundContext?: (ctx: unknown) => unknown;
createReplyDispatcherWithTyping?: () => {
dispatcher: Record<string, unknown>;
replyOptions: Record<string, unknown>;
markDispatchIdle: () => void;
};
resolveHumanDelayConfig?: () => undefined;
dispatchReplyFromConfig?: () => Promise<{
queuedFinal: boolean;
counts: { final: number; block: number; tool: number };
}>;
shouldAckReaction?: () => boolean;
enqueueSystemEvent?: (...args: unknown[]) => void;
getRoomInfo?: MatrixMonitorHandlerParams["getRoomInfo"];
getMemberDisplayName?: MatrixMonitorHandlerParams["getMemberDisplayName"];
};
export function createMatrixHandlerTestHarness(options: MatrixHandlerTestHarnessOptions = {}) {
const readAllowFromStore = options.readAllowFromStore ?? vi.fn(async () => [] as string[]);
const upsertPairingRequest =
options.upsertPairingRequest ?? vi.fn(async () => ({ code: "ABCDEFGH", created: false }));
const resolveAgentRoute = options.resolveAgentRoute ?? vi.fn(() => DEFAULT_ROUTE);
const recordInboundSession = options.recordInboundSession ?? vi.fn(async () => {});
const finalizeInboundContext = options.finalizeInboundContext ?? vi.fn((ctx) => ctx);
const dispatchReplyFromConfig =
options.dispatchReplyFromConfig ??
(async () => ({
queuedFinal: false,
counts: { final: 0, block: 0, tool: 0 },
}));
const enqueueSystemEvent = options.enqueueSystemEvent ?? vi.fn();
const handler = createMatrixRoomMessageHandler({
client: {
getUserId: async () => "@bot:example.org",
getEvent: async () => ({ sender: "@bot:example.org" }),
...options.client,
} as never,
core: {
channel: {
pairing: {
readAllowFromStore,
upsertPairingRequest,
buildPairingReply: options.buildPairingReply ?? (() => "pairing"),
},
commands: {
shouldHandleTextCommands: options.shouldHandleTextCommands ?? (() => false),
},
text: {
hasControlCommand: options.hasControlCommand ?? (() => false),
resolveMarkdownTableMode: options.resolveMarkdownTableMode ?? (() => "preserve"),
},
routing: {
resolveAgentRoute,
},
session: {
resolveStorePath: options.resolveStorePath ?? (() => "/tmp/session-store"),
readSessionUpdatedAt: options.readSessionUpdatedAt ?? (() => undefined),
recordInboundSession,
},
reply: {
resolveEnvelopeFormatOptions: options.resolveEnvelopeFormatOptions ?? (() => ({})),
formatAgentEnvelope: options.formatAgentEnvelope ?? (({ body }) => body),
finalizeInboundContext,
createReplyDispatcherWithTyping:
options.createReplyDispatcherWithTyping ??
(() => ({
dispatcher: {},
replyOptions: {},
markDispatchIdle: () => {},
})),
resolveHumanDelayConfig: options.resolveHumanDelayConfig ?? (() => undefined),
dispatchReplyFromConfig,
},
reactions: {
shouldAckReaction: options.shouldAckReaction ?? (() => false),
},
},
system: {
enqueueSystemEvent,
},
} as never,
cfg: (options.cfg ?? {}) as never,
accountId: options.accountId ?? "ops",
runtime: (options.runtime ??
({
error: () => {},
} as RuntimeEnv)) as RuntimeEnv,
logger: (options.logger ??
({
info: () => {},
warn: () => {},
error: () => {},
} as RuntimeLogger)) as RuntimeLogger,
logVerboseMessage: options.logVerboseMessage ?? (() => {}),
allowFrom: options.allowFrom ?? [],
groupAllowFrom: options.groupAllowFrom ?? [],
roomsConfig: options.roomsConfig,
mentionRegexes: options.mentionRegexes ?? [],
groupPolicy: options.groupPolicy ?? "open",
replyToMode: options.replyToMode ?? "off",
threadReplies: options.threadReplies ?? "inbound",
dmEnabled: options.dmEnabled ?? true,
dmPolicy: options.dmPolicy ?? "open",
textLimit: options.textLimit ?? 8_000,
mediaMaxBytes: options.mediaMaxBytes ?? 10_000_000,
startupMs: options.startupMs ?? 0,
startupGraceMs: options.startupGraceMs ?? 0,
directTracker: {
isDirectMessage: async () => options.isDirectMessage ?? true,
},
getRoomInfo: options.getRoomInfo ?? (async () => ({ altAliases: [] })),
getMemberDisplayName: options.getMemberDisplayName ?? (async () => "sender"),
});
return {
dispatchReplyFromConfig,
enqueueSystemEvent,
finalizeInboundContext,
handler,
readAllowFromStore,
recordInboundSession,
resolveAgentRoute,
upsertPairingRequest,
};
}
export function createMatrixTextMessageEvent(params: {
eventId: string;
sender?: string;
body: string;
originServerTs?: number;
relatesTo?: RoomMessageEventContent["m.relates_to"];
mentions?: RoomMessageEventContent["m.mentions"];
}): MatrixRawEvent {
return {
type: EventType.RoomMessage,
sender: params.sender ?? "@user:example.org",
event_id: params.eventId,
origin_server_ts: params.originServerTs ?? Date.now(),
content: {
msgtype: "m.text",
body: params.body,
...(params.relatesTo ? { "m.relates_to": params.relatesTo } : {}),
...(params.mentions ? { "m.mentions": params.mentions } : {}),
},
} as MatrixRawEvent;
}
export function createMatrixReactionEvent(params: {
eventId: string;
targetEventId: string;
key: string;
sender?: string;
originServerTs?: number;
}): MatrixRawEvent {
return {
type: EventType.Reaction,
sender: params.sender ?? "@user:example.org",
event_id: params.eventId,
origin_server_ts: params.originServerTs ?? Date.now(),
content: {
"m.relates_to": {
rel_type: "m.annotation",
event_id: params.targetEventId,
key: params.key,
},
},
} as MatrixRawEvent;
}

View File

@ -3,8 +3,12 @@ import {
__testing as sessionBindingTesting,
registerSessionBindingAdapter,
} from "../../../../../src/infra/outbound/session-binding-service.js";
import { createMatrixRoomMessageHandler } from "./handler.js";
import { EventType, type MatrixRawEvent } from "./types.js";
import {
createMatrixHandlerTestHarness,
createMatrixReactionEvent,
createMatrixTextMessageEvent,
} from "./handler.test-helpers.js";
import type { MatrixRawEvent } from "./types.js";
const sendMessageMatrixMock = vi.hoisted(() =>
vi.fn(async (..._args: unknown[]) => ({ messageId: "evt", roomId: "!room" })),
@ -30,149 +34,47 @@ function createReactionHarness(params?: {
isDirectMessage?: boolean;
senderName?: string;
}) {
const readAllowFromStore = vi.fn(async () => params?.storeAllowFrom ?? []);
const upsertPairingRequest = vi.fn(async () => ({ code: "ABCDEFGH", created: false }));
const resolveAgentRoute = vi.fn(() => ({
agentId: "ops",
channel: "matrix",
accountId: "ops",
sessionKey: "agent:ops:main",
mainSessionKey: "agent:ops:main",
matchedBy: "binding.account",
}));
const enqueueSystemEvent = vi.fn();
const handler = createMatrixRoomMessageHandler({
return createMatrixHandlerTestHarness({
cfg: params?.cfg,
dmPolicy: params?.dmPolicy,
allowFrom: params?.allowFrom,
readAllowFromStore: vi.fn(async () => params?.storeAllowFrom ?? []),
client: {
getUserId: async () => "@bot:example.org",
getEvent: async () => ({ sender: params?.targetSender ?? "@bot:example.org" }),
} as never,
core: {
channel: {
pairing: {
readAllowFromStore,
upsertPairingRequest,
buildPairingReply: () => "pairing",
},
commands: {
shouldHandleTextCommands: () => false,
},
text: {
hasControlCommand: () => false,
},
routing: {
resolveAgentRoute,
},
},
system: {
enqueueSystemEvent,
},
} as never,
cfg: (params?.cfg ?? {}) as never,
accountId: "ops",
runtime: {
error: () => {},
} as never,
logger: {
info: () => {},
warn: () => {},
} as never,
logVerboseMessage: () => {},
allowFrom: params?.allowFrom ?? [],
mentionRegexes: [],
groupPolicy: "open",
replyToMode: "off",
threadReplies: "inbound",
dmEnabled: true,
dmPolicy: params?.dmPolicy ?? "open",
textLimit: 8_000,
mediaMaxBytes: 10_000_000,
startupMs: 0,
startupGraceMs: 0,
directTracker: {
isDirectMessage: async () => params?.isDirectMessage ?? true,
},
getRoomInfo: async () => ({ altAliases: [] }),
isDirectMessage: params?.isDirectMessage,
getMemberDisplayName: async () => params?.senderName ?? "sender",
});
return {
handler,
enqueueSystemEvent,
readAllowFromStore,
resolveAgentRoute,
upsertPairingRequest,
};
}
describe("matrix monitor handler pairing account scope", () => {
it("caches account-scoped allowFrom store reads on hot path", async () => {
const readAllowFromStore = vi.fn(async () => [] as string[]);
const upsertPairingRequest = vi.fn(async () => ({ code: "ABCDEFGH", created: false }));
sendMessageMatrixMock.mockClear();
const handler = createMatrixRoomMessageHandler({
client: {
getUserId: async () => "@bot:example.org",
} as never,
core: {
channel: {
pairing: {
readAllowFromStore,
upsertPairingRequest,
buildPairingReply: () => "pairing",
},
},
} as never,
cfg: {} as never,
accountId: "ops",
runtime: {} as never,
logger: {
info: () => {},
warn: () => {},
} as never,
logVerboseMessage: () => {},
allowFrom: [],
mentionRegexes: [],
groupPolicy: "open",
replyToMode: "off",
threadReplies: "inbound",
dmEnabled: true,
const { handler } = createMatrixHandlerTestHarness({
readAllowFromStore,
dmPolicy: "pairing",
textLimit: 8_000,
mediaMaxBytes: 10_000_000,
startupMs: 0,
startupGraceMs: 0,
directTracker: {
isDirectMessage: async () => true,
},
getRoomInfo: async () => ({ altAliases: [] }),
getMemberDisplayName: async () => "sender",
buildPairingReply: () => "pairing",
});
await handler("!room:example.org", {
type: EventType.RoomMessage,
sender: "@user:example.org",
event_id: "$event1",
origin_server_ts: Date.now(),
content: {
msgtype: "m.text",
await handler(
"!room:example.org",
createMatrixTextMessageEvent({
eventId: "$event1",
body: "hello",
"m.mentions": { room: true },
},
} as MatrixRawEvent);
mentions: { room: true },
}),
);
await handler("!room:example.org", {
type: EventType.RoomMessage,
sender: "@user:example.org",
event_id: "$event2",
origin_server_ts: Date.now(),
content: {
msgtype: "m.text",
await handler(
"!room:example.org",
createMatrixTextMessageEvent({
eventId: "$event2",
body: "hello again",
"m.mentions": { room: true },
},
} as MatrixRawEvent);
mentions: { room: true },
}),
);
expect(readAllowFromStore).toHaveBeenCalledTimes(1);
});
@ -182,60 +84,22 @@ describe("matrix monitor handler pairing account scope", () => {
vi.setSystemTime(new Date("2026-03-01T10:00:00.000Z"));
try {
const readAllowFromStore = vi.fn(async () => [] as string[]);
const upsertPairingRequest = vi.fn(async () => ({ code: "ABCDEFGH", created: false }));
sendMessageMatrixMock.mockClear();
const handler = createMatrixRoomMessageHandler({
client: {
getUserId: async () => "@bot:example.org",
} as never,
core: {
channel: {
pairing: {
readAllowFromStore,
upsertPairingRequest,
buildPairingReply: () => "Pairing code: ABCDEFGH",
},
},
} as never,
cfg: {} as never,
accountId: "ops",
runtime: {} as never,
logger: {
info: () => {},
warn: () => {},
} as never,
logVerboseMessage: () => {},
allowFrom: [],
mentionRegexes: [],
groupPolicy: "open",
replyToMode: "off",
threadReplies: "inbound",
dmEnabled: true,
const { handler } = createMatrixHandlerTestHarness({
readAllowFromStore,
dmPolicy: "pairing",
textLimit: 8_000,
mediaMaxBytes: 10_000_000,
startupMs: 0,
startupGraceMs: 0,
directTracker: {
isDirectMessage: async () => true,
},
getRoomInfo: async () => ({ altAliases: [] }),
buildPairingReply: () => "Pairing code: ABCDEFGH",
isDirectMessage: true,
getMemberDisplayName: async () => "sender",
});
const makeEvent = (id: string): MatrixRawEvent =>
({
type: EventType.RoomMessage,
sender: "@user:example.org",
event_id: id,
origin_server_ts: Date.now(),
content: {
msgtype: "m.text",
body: "hello",
"m.mentions": { room: true },
},
}) as MatrixRawEvent;
createMatrixTextMessageEvent({
eventId: id,
body: "hello",
mentions: { room: true },
});
await handler("!room:example.org", makeEvent("$event1"));
await handler("!room:example.org", makeEvent("$event2"));
@ -256,55 +120,22 @@ describe("matrix monitor handler pairing account scope", () => {
const readAllowFromStore = vi.fn(async () => [] as string[]);
const upsertPairingRequest = vi.fn(async () => ({ code: "ABCDEFGH", created: false }));
const handler = createMatrixRoomMessageHandler({
client: {
getUserId: async () => "@bot:example.org",
} as never,
core: {
channel: {
pairing: {
readAllowFromStore,
upsertPairingRequest,
},
},
} as never,
cfg: {} as never,
accountId: "ops",
runtime: {} as never,
logger: {
info: () => {},
warn: () => {},
} as never,
logVerboseMessage: () => {},
allowFrom: [],
mentionRegexes: [],
groupPolicy: "open",
replyToMode: "off",
threadReplies: "inbound",
dmEnabled: true,
const { handler } = createMatrixHandlerTestHarness({
readAllowFromStore,
upsertPairingRequest,
dmPolicy: "pairing",
textLimit: 8_000,
mediaMaxBytes: 10_000_000,
startupMs: 0,
startupGraceMs: 0,
directTracker: {
isDirectMessage: async () => true,
},
getRoomInfo: async () => ({ altAliases: [] }),
isDirectMessage: true,
getMemberDisplayName: async () => "sender",
});
await handler("!room:example.org", {
type: EventType.RoomMessage,
sender: "@user:example.org",
event_id: "$event1",
origin_server_ts: Date.now(),
content: {
msgtype: "m.text",
await handler(
"!room:example.org",
createMatrixTextMessageEvent({
eventId: "$event1",
body: "hello",
"m.mentions": { room: true },
},
} as MatrixRawEvent);
mentions: { room: true },
}),
);
expect(readAllowFromStore).toHaveBeenCalledWith({
channel: "matrix",
@ -329,66 +160,20 @@ describe("matrix monitor handler pairing account scope", () => {
matchedBy: "binding.account",
}));
const handler = createMatrixRoomMessageHandler({
client: {
getUserId: async () => "@bot:example.org",
} as never,
core: {
channel: {
pairing: {
readAllowFromStore: async () => [] as string[],
upsertPairingRequest: async () => ({ code: "ABCDEFGH", created: false }),
},
commands: {
shouldHandleTextCommands: () => false,
},
text: {
hasControlCommand: () => false,
},
routing: {
resolveAgentRoute,
},
},
} as never,
cfg: {} as never,
accountId: "ops",
runtime: {
error: () => {},
} as never,
logger: {
info: () => {},
warn: () => {},
} as never,
logVerboseMessage: () => {},
allowFrom: [],
mentionRegexes: [],
groupPolicy: "open",
replyToMode: "off",
threadReplies: "inbound",
dmEnabled: true,
dmPolicy: "open",
textLimit: 8_000,
mediaMaxBytes: 10_000_000,
startupMs: 0,
startupGraceMs: 0,
directTracker: {
isDirectMessage: async () => true,
},
getRoomInfo: async () => ({ altAliases: [] }),
const { handler } = createMatrixHandlerTestHarness({
resolveAgentRoute,
isDirectMessage: true,
getMemberDisplayName: async () => "sender",
});
await handler("!room:example.org", {
type: EventType.RoomMessage,
sender: "@user:example.org",
event_id: "$event2",
origin_server_ts: Date.now(),
content: {
msgtype: "m.text",
await handler(
"!room:example.org",
createMatrixTextMessageEvent({
eventId: "$event2",
body: "hello",
"m.mentions": { room: true },
},
} as MatrixRawEvent);
mentions: { room: true },
}),
);
expect(resolveAgentRoute).toHaveBeenCalledWith(
expect.objectContaining({
@ -399,116 +184,34 @@ describe("matrix monitor handler pairing account scope", () => {
});
it("records thread starter context for inbound thread replies", async () => {
const recordInboundSession = vi.fn(async () => {});
const finalizeInboundContext = vi.fn((ctx) => ctx);
const handler = createMatrixRoomMessageHandler({
client: {
getUserId: async () => "@bot:example.org",
getEvent: async () => ({
event_id: "$root",
sender: "@alice:example.org",
type: EventType.RoomMessage,
origin_server_ts: Date.now(),
content: {
msgtype: "m.text",
body: "Root topic",
},
}),
} as never,
core: {
channel: {
pairing: {
readAllowFromStore: async () => [] as string[],
upsertPairingRequest: async () => ({ code: "ABCDEFGH", created: false }),
},
commands: {
shouldHandleTextCommands: () => false,
},
text: {
hasControlCommand: () => false,
resolveMarkdownTableMode: () => "preserve",
},
routing: {
resolveAgentRoute: () => ({
agentId: "ops",
channel: "matrix",
accountId: "ops",
sessionKey: "agent:ops:main",
mainSessionKey: "agent:ops:main",
matchedBy: "binding.account",
const { handler, finalizeInboundContext, recordInboundSession } =
createMatrixHandlerTestHarness({
client: {
getEvent: async () =>
createMatrixTextMessageEvent({
eventId: "$root",
sender: "@alice:example.org",
body: "Root topic",
}),
},
session: {
resolveStorePath: () => "/tmp/session-store",
readSessionUpdatedAt: () => undefined,
recordInboundSession,
},
reply: {
resolveEnvelopeFormatOptions: () => ({}),
formatAgentEnvelope: ({ body }: { body: string }) => body,
finalizeInboundContext,
createReplyDispatcherWithTyping: () => ({
dispatcher: {},
replyOptions: {},
markDispatchIdle: () => {},
}),
resolveHumanDelayConfig: () => undefined,
dispatchReplyFromConfig: async () => ({
queuedFinal: false,
counts: { final: 0, block: 0, tool: 0 },
}),
},
reactions: {
shouldAckReaction: () => false,
},
},
} as never,
cfg: {} as never,
accountId: "ops",
runtime: {
error: () => {},
} as never,
logger: {
info: () => {},
warn: () => {},
} as never,
logVerboseMessage: () => {},
allowFrom: [],
mentionRegexes: [],
groupPolicy: "open",
replyToMode: "off",
threadReplies: "inbound",
dmEnabled: true,
dmPolicy: "open",
textLimit: 8_000,
mediaMaxBytes: 10_000_000,
startupMs: 0,
startupGraceMs: 0,
directTracker: {
isDirectMessage: async () => false,
},
getRoomInfo: async () => ({ altAliases: [] }),
getMemberDisplayName: async (_roomId, userId) =>
userId === "@alice:example.org" ? "Alice" : "sender",
});
isDirectMessage: false,
getMemberDisplayName: async (_roomId, userId) =>
userId === "@alice:example.org" ? "Alice" : "sender",
});
await handler("!room:example.org", {
type: EventType.RoomMessage,
sender: "@user:example.org",
event_id: "$reply1",
origin_server_ts: Date.now(),
content: {
msgtype: "m.text",
await handler(
"!room:example.org",
createMatrixTextMessageEvent({
eventId: "$reply1",
body: "follow up",
"m.relates_to": {
relatesTo: {
rel_type: "m.thread",
event_id: "$root",
"m.in_reply_to": { event_id: "$root" },
},
"m.mentions": { room: true },
},
} as MatrixRawEvent);
mentions: { room: true },
}),
);
expect(finalizeInboundContext).toHaveBeenCalledWith(
expect.objectContaining({
@ -549,114 +252,33 @@ describe("matrix monitor handler pairing account scope", () => {
: null,
touch: vi.fn(),
});
const recordInboundSession = vi.fn(async () => {});
const handler = createMatrixRoomMessageHandler({
const { handler, recordInboundSession } = createMatrixHandlerTestHarness({
client: {
getUserId: async () => "@bot:example.org",
getEvent: async () => ({
event_id: "$root",
sender: "@alice:example.org",
type: EventType.RoomMessage,
origin_server_ts: Date.now(),
content: {
msgtype: "m.text",
getEvent: async () =>
createMatrixTextMessageEvent({
eventId: "$root",
sender: "@alice:example.org",
body: "Root topic",
},
}),
} as never,
core: {
channel: {
pairing: {
readAllowFromStore: async () => [] as string[],
upsertPairingRequest: async () => ({ code: "ABCDEFGH", created: false }),
},
commands: {
shouldHandleTextCommands: () => false,
},
text: {
hasControlCommand: () => false,
resolveMarkdownTableMode: () => "preserve",
},
routing: {
resolveAgentRoute: () => ({
agentId: "ops",
channel: "matrix",
accountId: "ops",
sessionKey: "agent:ops:main",
mainSessionKey: "agent:ops:main",
matchedBy: "binding.account",
}),
},
session: {
resolveStorePath: () => "/tmp/session-store",
readSessionUpdatedAt: () => undefined,
recordInboundSession,
},
reply: {
resolveEnvelopeFormatOptions: () => ({}),
formatAgentEnvelope: ({ body }: { body: string }) => body,
finalizeInboundContext: (ctx: unknown) => ctx,
createReplyDispatcherWithTyping: () => ({
dispatcher: {},
replyOptions: {},
markDispatchIdle: () => {},
}),
resolveHumanDelayConfig: () => undefined,
dispatchReplyFromConfig: async () => ({
queuedFinal: false,
counts: { final: 0, block: 0, tool: 0 },
}),
},
reactions: {
shouldAckReaction: () => false,
},
},
} as never,
cfg: {} as never,
accountId: "ops",
runtime: {
error: () => {},
} as never,
logger: {
info: () => {},
warn: () => {},
} as never,
logVerboseMessage: () => {},
allowFrom: [],
mentionRegexes: [],
groupPolicy: "open",
replyToMode: "off",
threadReplies: "inbound",
dmEnabled: true,
dmPolicy: "open",
textLimit: 8_000,
mediaMaxBytes: 10_000_000,
startupMs: 0,
startupGraceMs: 0,
directTracker: {
isDirectMessage: async () => false,
}),
},
getRoomInfo: async () => ({ altAliases: [] }),
isDirectMessage: false,
finalizeInboundContext: (ctx: unknown) => ctx,
getMemberDisplayName: async () => "sender",
});
await handler("!room:example", {
type: EventType.RoomMessage,
sender: "@user:example.org",
event_id: "$reply1",
origin_server_ts: Date.now(),
content: {
msgtype: "m.text",
await handler(
"!room:example",
createMatrixTextMessageEvent({
eventId: "$reply1",
body: "follow up",
"m.relates_to": {
relatesTo: {
rel_type: "m.thread",
event_id: "$root",
"m.in_reply_to": { event_id: "$root" },
},
"m.mentions": { room: true },
},
} as MatrixRawEvent);
mentions: { room: true },
}),
);
expect(recordInboundSession).toHaveBeenCalledWith(
expect.objectContaining({
@ -770,19 +392,14 @@ describe("matrix monitor handler pairing account scope", () => {
it("enqueues system events for reactions on bot-authored messages", async () => {
const { handler, enqueueSystemEvent, resolveAgentRoute } = createReactionHarness();
await handler("!room:example.org", {
type: EventType.Reaction,
sender: "@user:example.org",
event_id: "$reaction1",
origin_server_ts: Date.now(),
content: {
"m.relates_to": {
rel_type: "m.annotation",
event_id: "$msg1",
key: "👍",
},
},
} as MatrixRawEvent);
await handler(
"!room:example.org",
createMatrixReactionEvent({
eventId: "$reaction1",
targetEventId: "$msg1",
key: "👍",
}),
);
expect(resolveAgentRoute).toHaveBeenCalledWith(
expect.objectContaining({
@ -804,19 +421,14 @@ describe("matrix monitor handler pairing account scope", () => {
targetSender: "@other:example.org",
});
await handler("!room:example.org", {
type: EventType.Reaction,
sender: "@user:example.org",
event_id: "$reaction2",
origin_server_ts: Date.now(),
content: {
"m.relates_to": {
rel_type: "m.annotation",
event_id: "$msg2",
key: "👀",
},
},
} as MatrixRawEvent);
await handler(
"!room:example.org",
createMatrixReactionEvent({
eventId: "$reaction2",
targetEventId: "$msg2",
key: "👀",
}),
);
expect(enqueueSystemEvent).not.toHaveBeenCalled();
expect(resolveAgentRoute).not.toHaveBeenCalled();
@ -827,19 +439,14 @@ describe("matrix monitor handler pairing account scope", () => {
dmPolicy: "pairing",
});
await handler("!room:example.org", {
type: EventType.Reaction,
sender: "@user:example.org",
event_id: "$reaction3",
origin_server_ts: Date.now(),
content: {
"m.relates_to": {
rel_type: "m.annotation",
event_id: "$msg3",
key: "🔥",
},
},
} as MatrixRawEvent);
await handler(
"!room:example.org",
createMatrixReactionEvent({
eventId: "$reaction3",
targetEventId: "$msg3",
key: "🔥",
}),
);
expect(upsertPairingRequest).not.toHaveBeenCalled();
expect(enqueueSystemEvent).not.toHaveBeenCalled();
@ -861,19 +468,14 @@ describe("matrix monitor handler pairing account scope", () => {
},
});
await handler("!room:example.org", {
type: EventType.Reaction,
sender: "@user:example.org",
event_id: "$reaction4",
origin_server_ts: Date.now(),
content: {
"m.relates_to": {
rel_type: "m.annotation",
event_id: "$msg4",
key: "✅",
},
},
} as MatrixRawEvent);
await handler(
"!room:example.org",
createMatrixReactionEvent({
eventId: "$reaction4",
targetEventId: "$msg4",
key: "✅",
}),
);
expect(enqueueSystemEvent).not.toHaveBeenCalled();
});