fix(matrix): preserve stable DM peer metadata

This commit is contained in:
Gustavo Madeira Santana
2026-04-05 13:30:42 -04:00
parent 7185605d44
commit 114715b25f
7 changed files with 92 additions and 17 deletions

View File

@@ -778,10 +778,11 @@ describe("matrix monitor handler pairing account scope", () => {
ChatType: "direct",
Provider: "matrix",
Surface: "matrix",
From: "matrix:@user:example.org",
To: "room:@user:example.org",
From: "matrix:@other:example.org",
To: "room:@other:example.org",
NativeDirectUserId: "@user:example.org",
OriginatingChannel: "matrix",
OriginatingTo: "room:@user:example.org",
OriginatingTo: "room:@other:example.org",
},
});

View File

@@ -1160,6 +1160,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
CommandAuthorized: commandAuthorized,
CommandSource: "text" as const,
NativeChannelId: roomId,
NativeDirectUserId: isDirectMessage ? senderId : undefined,
OriginatingChannel: "matrix" as const,
OriginatingTo: `room:${roomId}`,
});

View File

@@ -48,6 +48,7 @@ type MatrixStoredSessionEntryLike = {
from?: unknown;
to?: unknown;
nativeChannelId?: unknown;
nativeDirectUserId?: unknown;
accountId?: unknown;
chatType?: unknown;
};
@@ -80,16 +81,18 @@ export function resolveMatrixStoredSessionMeta(entry?: MatrixStoredSessionEntryL
originNativeChannelId: entry.origin?.nativeChannelId,
originTo: entry.origin?.to,
});
const directUserId = resolveMatrixDirectUserId({
from: trimMaybeString(entry.origin?.from),
to:
(roomId ? `room:${roomId}` : undefined) ??
trimMaybeString(entry.deliveryContext?.to) ??
trimMaybeString(entry.lastTo) ??
trimMaybeString(entry.origin?.to),
chatType:
trimMaybeString(entry.origin?.chatType) ?? trimMaybeString(entry.chatType) ?? undefined,
});
const directUserId =
trimMaybeString(entry.origin?.nativeDirectUserId) ??
resolveMatrixDirectUserId({
from: trimMaybeString(entry.origin?.from),
to:
(roomId ? `room:${roomId}` : undefined) ??
trimMaybeString(entry.deliveryContext?.to) ??
trimMaybeString(entry.lastTo) ??
trimMaybeString(entry.origin?.to),
chatType:
trimMaybeString(entry.origin?.chatType) ?? trimMaybeString(entry.chatType) ?? undefined,
});
if (!channel && !accountId && !roomId && !directUserId) {
return null;
}

View File

@@ -196,17 +196,18 @@ describe("resolveMatrixOutboundSessionRoute", () => {
chatType: "direct",
origin: {
chatType: "direct",
from: "matrix:@alice:example.org",
to: "room:@alice:example.org",
from: "matrix:@bob:example.org",
to: "room:@bob:example.org",
nativeChannelId: "!dm:example.org",
nativeDirectUserId: "@alice:example.org",
accountId: "ops",
},
deliveryContext: {
channel: "matrix",
to: "room:@alice:example.org",
to: "room:@bob:example.org",
accountId: "ops",
},
lastTo: "room:@alice:example.org",
lastTo: "room:@bob:example.org",
lastAccountId: "ops",
},
});
@@ -246,6 +247,65 @@ describe("resolveMatrixOutboundSessionRoute", () => {
});
});
it("does not reuse the canonical DM room for a different Matrix user after latest metadata drift", () => {
const storePath = createTempStore({
"agent:main:matrix:channel:!dm:example.org": {
sessionId: "sess-1",
updatedAt: Date.now(),
chatType: "direct",
origin: {
chatType: "direct",
from: "matrix:@bob:example.org",
to: "room:@bob:example.org",
nativeChannelId: "!dm:example.org",
nativeDirectUserId: "@alice:example.org",
accountId: "ops",
},
deliveryContext: {
channel: "matrix",
to: "room:@bob:example.org",
accountId: "ops",
},
lastTo: "room:@bob:example.org",
lastAccountId: "ops",
},
});
const cfg = {
session: {
store: storePath,
},
channels: {
matrix: {
dm: {
sessionScope: "per-room",
},
},
},
} satisfies OpenClawConfig;
const route = resolveMatrixOutboundSessionRoute({
cfg,
agentId: "main",
accountId: "ops",
currentSessionKey: "agent:main:matrix:channel:!dm:example.org",
target: "@bob:example.org",
resolvedTarget: {
to: "@bob:example.org",
kind: "user",
source: "normalized",
},
});
expect(route).toMatchObject({
sessionKey: "agent:main:main",
baseSessionKey: "agent:main:main",
peer: { kind: "direct", id: "@bob:example.org" },
chatType: "direct",
from: "matrix:@bob:example.org",
to: "room:@bob:example.org",
});
});
it("uses the effective default Matrix account when accountId is omitted", () => {
const storePath = createTempStore({
"agent:main:matrix:channel:!dm:example.org": {

View File

@@ -161,6 +161,8 @@ export type MsgContext = {
MessageThreadId?: string | number;
/** Platform-native channel/conversation id (e.g. Slack DM channel "D…" id). */
NativeChannelId?: string;
/** Stable provider-native direct-peer id when a DM room/user mapping must survive later writes. */
NativeDirectUserId?: string;
/** Telegram forum supergroup marker. */
IsForum?: boolean;
/** Warning: DM has topics enabled but this message is not in a topic. */

View File

@@ -35,6 +35,9 @@ const mergeOrigin = (
if (next?.nativeChannelId) {
merged.nativeChannelId = next.nativeChannelId;
}
if (next?.nativeDirectUserId) {
merged.nativeDirectUserId = next.nativeDirectUserId;
}
if (next?.accountId) {
merged.accountId = next.accountId;
}
@@ -57,6 +60,7 @@ export function deriveSessionOrigin(ctx: MsgContext): SessionOrigin | undefined
const to =
(typeof ctx.OriginatingTo === "string" ? ctx.OriginatingTo : ctx.To)?.trim() ?? undefined;
const nativeChannelId = ctx.NativeChannelId?.trim();
const nativeDirectUserId = ctx.NativeDirectUserId?.trim();
const accountId = ctx.AccountId?.trim();
const threadId = ctx.MessageThreadId ?? undefined;
@@ -82,6 +86,9 @@ export function deriveSessionOrigin(ctx: MsgContext): SessionOrigin | undefined
if (nativeChannelId) {
origin.nativeChannelId = nativeChannelId;
}
if (nativeDirectUserId) {
origin.nativeDirectUserId = nativeDirectUserId;
}
if (accountId) {
origin.accountId = accountId;
}

View File

@@ -19,6 +19,7 @@ export type SessionOrigin = {
from?: string;
to?: string;
nativeChannelId?: string;
nativeDirectUserId?: string;
accountId?: string;
threadId?: string | number;
};