Files
openclaw/extensions/matrix/src/session-route.ts
Peter Steinberger a2a68d154b fix(agent): preserve explicit recipient sessions (#101507)
* fix(agent): honor recipient session routing

Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Co-authored-by: pingfanfan <pingfan.work@gmail.com>

* fix(agent): preserve canonical recipient routes

* fix(agent): require exact recipient routes

Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>

Co-authored-by: pingfanfan <pingfan.work@gmail.com>

* fix(agent): harden recipient route resolution

* fix(imessage): require canonical recipient handles

* fix(agent): refine provider recipient exactness

* fix(agent): bound direct alias session routing

* fix(signal): preserve direct alias route type

* fix(agent): honor binding-scoped recipient sessions

* fix(routing): honor configured main session aliases

* fix(clickclack): align account-owned session routes

* fix(twitch): preserve canonical recipient sessions

* fix(routing): isolate stable outbound identities

* chore: defer recipient session changelog

* fix(sms): use dedicated channel SDK facade

---------

Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Co-authored-by: pingfanfan <pingfan.work@gmail.com>
2026-07-07 10:07:13 +01:00

129 lines
4.4 KiB
TypeScript

// Matrix plugin module implements session route behavior.
import { normalizeAccountId } from "openclaw/plugin-sdk/account-id";
import {
buildChannelOutboundSessionRoute,
buildThreadAwareOutboundSessionRoute,
type ChannelOutboundSessionRouteParams,
} from "openclaw/plugin-sdk/channel-core";
import { parseThreadSessionSuffix } from "openclaw/plugin-sdk/routing";
import { getSessionEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
import { resolveMatrixAccountConfig } from "./matrix/account-config.js";
import { resolveDefaultMatrixAccountId } from "./matrix/accounts.js";
import { resolveMatrixStoredSessionMeta } from "./matrix/session-store-metadata.js";
import { isMatrixQualifiedUserId, resolveMatrixTargetIdentity } from "./matrix/target-ids.js";
function resolveEffectiveMatrixAccountId(
params: Pick<ChannelOutboundSessionRouteParams, "cfg" | "accountId">,
): string {
return normalizeAccountId(params.accountId ?? resolveDefaultMatrixAccountId(params.cfg));
}
function resolveMatrixDmSessionScope(params: {
cfg: ChannelOutboundSessionRouteParams["cfg"];
accountId: string;
}): "per-user" | "per-room" {
return (
resolveMatrixAccountConfig({
cfg: params.cfg,
accountId: params.accountId,
}).dm?.sessionScope ?? "per-user"
);
}
function resolveMatrixCurrentDmRoomId(params: {
cfg: ChannelOutboundSessionRouteParams["cfg"];
agentId: string;
accountId: string;
currentSessionKey?: string;
targetUserId: string;
}): string | undefined {
const sessionKey =
parseThreadSessionSuffix(params.currentSessionKey).baseSessionKey ??
params.currentSessionKey?.trim();
if (!sessionKey) {
return undefined;
}
try {
const storePath = resolveStorePath(params.cfg.session?.store, {
agentId: params.agentId,
});
const existing = getSessionEntry({
storePath,
sessionKey,
});
const currentSession = resolveMatrixStoredSessionMeta(existing);
if (!currentSession) {
return undefined;
}
if (currentSession.accountId && currentSession.accountId !== params.accountId) {
return undefined;
}
if (!currentSession.directUserId || currentSession.directUserId !== params.targetUserId) {
return undefined;
}
return currentSession.roomId;
} catch {
return undefined;
}
}
export function resolveMatrixOutboundSessionRoute(params: ChannelOutboundSessionRouteParams) {
const target =
resolveMatrixTargetIdentity(params.resolvedTarget?.to ?? params.target) ??
resolveMatrixTargetIdentity(params.target);
if (!target) {
return null;
}
const effectiveAccountId = resolveEffectiveMatrixAccountId(params);
const dmSessionScope = resolveMatrixDmSessionScope({
cfg: params.cfg,
accountId: effectiveAccountId,
});
const roomScopedDmId =
target.kind === "user" && dmSessionScope === "per-room"
? resolveMatrixCurrentDmRoomId({
cfg: params.cfg,
agentId: params.agentId,
accountId: effectiveAccountId,
currentSessionKey: params.currentSessionKey,
targetUserId: target.id,
})
: undefined;
const peer =
roomScopedDmId !== undefined
? { kind: "channel" as const, id: roomScopedDmId }
: {
kind: target.kind === "user" ? ("direct" as const) : ("channel" as const),
id: target.id,
};
const chatType = target.kind === "user" ? "direct" : "channel";
const from = target.kind === "user" ? `matrix:${target.id}` : `matrix:channel:${target.id}`;
const to = `room:${roomScopedDmId ?? target.id}`;
const baseRoute = buildChannelOutboundSessionRoute({
cfg: params.cfg,
agentId: params.agentId,
channel: "matrix",
accountId: effectiveAccountId,
recipientSessionExact:
target.kind === "room"
? dmSessionScope === "per-room" && target.id.startsWith("!") && target.id.includes(":")
: dmSessionScope === "per-user"
? isMatrixQualifiedUserId(target.id)
: roomScopedDmId !== undefined,
peer,
chatType,
from,
to,
});
return buildThreadAwareOutboundSessionRoute({
route: baseRoute,
replyToId: params.replyToId,
threadId: params.threadId,
currentSessionKey: params.currentSessionKey,
normalizeThreadId: (threadId) => threadId,
canRecoverCurrentThread: ({ route }) =>
route.peer.kind !== "direct" || (params.cfg.session?.dmScope ?? "main") !== "main",
});
}