mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-09 05:19:32 +08:00
feat(routing): add per-account-channel-peer session scope
Adds a new dmScope option that includes accountId in session keys, enabling isolated sessions per channel account for multi-bot setups. - Add 'per-account-channel-peer' to DmScope type - Update session key generation to include accountId - Pass accountId through routing chain - Add tests for new routing behavior (13/13 passing) Closes #3094 Co-authored-by: Sebastian Almeida <89653954+SebastianAlmeida@users.noreply.github.com>
This commit is contained in:
committed by
Ayaan Zaidi
parent
93c2d65398
commit
d499b14842
@@ -3,7 +3,7 @@ import type { NormalizedChatType } from "../channels/chat-type.js";
|
||||
export type ReplyMode = "text" | "command";
|
||||
export type TypingMode = "never" | "instant" | "thinking" | "message";
|
||||
export type SessionScope = "per-sender" | "global";
|
||||
export type DmScope = "main" | "per-peer" | "per-channel-peer";
|
||||
export type DmScope = "main" | "per-peer" | "per-channel-peer" | "per-account-channel-peer";
|
||||
export type ReplyToMode = "off" | "first" | "all";
|
||||
export type GroupPolicy = "open" | "disabled" | "allowlist";
|
||||
export type DmPolicy = "pairing" | "allowlist" | "open" | "disabled";
|
||||
|
||||
@@ -20,7 +20,12 @@ export const SessionSchema = z
|
||||
.object({
|
||||
scope: z.union([z.literal("per-sender"), z.literal("global")]).optional(),
|
||||
dmScope: z
|
||||
.union([z.literal("main"), z.literal("per-peer"), z.literal("per-channel-peer")])
|
||||
.union([
|
||||
z.literal("main"),
|
||||
z.literal("per-peer"),
|
||||
z.literal("per-channel-peer"),
|
||||
z.literal("per-account-channel-peer"),
|
||||
])
|
||||
.optional(),
|
||||
identityLinks: z.record(z.string(), z.array(z.string())).optional(),
|
||||
resetTriggers: z.array(z.string()).optional(),
|
||||
|
||||
@@ -103,11 +103,13 @@ function buildBaseSessionKey(params: {
|
||||
cfg: MoltbotConfig;
|
||||
agentId: string;
|
||||
channel: ChannelId;
|
||||
accountId?: string | null;
|
||||
peer: RoutePeer;
|
||||
}): string {
|
||||
return buildAgentSessionKey({
|
||||
agentId: params.agentId,
|
||||
channel: params.channel,
|
||||
accountId: params.accountId,
|
||||
peer: params.peer,
|
||||
dmScope: params.cfg.session?.dmScope ?? "main",
|
||||
identityLinks: params.cfg.session?.identityLinks,
|
||||
@@ -200,6 +202,7 @@ async function resolveSlackSession(
|
||||
cfg: params.cfg,
|
||||
agentId: params.agentId,
|
||||
channel: "slack",
|
||||
accountId: params.accountId,
|
||||
peer,
|
||||
});
|
||||
const threadId = normalizeThreadId(params.threadId ?? params.replyToId);
|
||||
@@ -237,6 +240,7 @@ function resolveDiscordSession(
|
||||
cfg: params.cfg,
|
||||
agentId: params.agentId,
|
||||
channel: "discord",
|
||||
accountId: params.accountId,
|
||||
peer,
|
||||
});
|
||||
const explicitThreadId = normalizeThreadId(params.threadId);
|
||||
@@ -285,6 +289,7 @@ function resolveTelegramSession(
|
||||
cfg: params.cfg,
|
||||
agentId: params.agentId,
|
||||
channel: "telegram",
|
||||
accountId: params.accountId,
|
||||
peer,
|
||||
});
|
||||
return {
|
||||
@@ -312,6 +317,7 @@ function resolveWhatsAppSession(
|
||||
cfg: params.cfg,
|
||||
agentId: params.agentId,
|
||||
channel: "whatsapp",
|
||||
accountId: params.accountId,
|
||||
peer,
|
||||
});
|
||||
return {
|
||||
@@ -337,6 +343,7 @@ function resolveSignalSession(
|
||||
cfg: params.cfg,
|
||||
agentId: params.agentId,
|
||||
channel: "signal",
|
||||
accountId: params.accountId,
|
||||
peer,
|
||||
});
|
||||
return {
|
||||
@@ -371,6 +378,7 @@ function resolveSignalSession(
|
||||
cfg: params.cfg,
|
||||
agentId: params.agentId,
|
||||
channel: "signal",
|
||||
accountId: params.accountId,
|
||||
peer,
|
||||
});
|
||||
return {
|
||||
@@ -395,6 +403,7 @@ function resolveIMessageSession(
|
||||
cfg: params.cfg,
|
||||
agentId: params.agentId,
|
||||
channel: "imessage",
|
||||
accountId: params.accountId,
|
||||
peer,
|
||||
});
|
||||
return {
|
||||
@@ -419,6 +428,7 @@ function resolveIMessageSession(
|
||||
cfg: params.cfg,
|
||||
agentId: params.agentId,
|
||||
channel: "imessage",
|
||||
accountId: params.accountId,
|
||||
peer,
|
||||
});
|
||||
const toPrefix =
|
||||
@@ -450,6 +460,7 @@ function resolveMatrixSession(
|
||||
cfg: params.cfg,
|
||||
agentId: params.agentId,
|
||||
channel: "matrix",
|
||||
accountId: params.accountId,
|
||||
peer,
|
||||
});
|
||||
return {
|
||||
@@ -483,6 +494,7 @@ function resolveMSTeamsSession(
|
||||
cfg: params.cfg,
|
||||
agentId: params.agentId,
|
||||
channel: "msteams",
|
||||
accountId: params.accountId,
|
||||
peer,
|
||||
});
|
||||
return {
|
||||
@@ -517,6 +529,7 @@ function resolveMattermostSession(
|
||||
cfg: params.cfg,
|
||||
agentId: params.agentId,
|
||||
channel: "mattermost",
|
||||
accountId: params.accountId,
|
||||
peer,
|
||||
});
|
||||
const threadId = normalizeThreadId(params.replyToId ?? params.threadId);
|
||||
@@ -561,6 +574,7 @@ function resolveBlueBubblesSession(
|
||||
cfg: params.cfg,
|
||||
agentId: params.agentId,
|
||||
channel: "bluebubbles",
|
||||
accountId: params.accountId,
|
||||
peer,
|
||||
});
|
||||
return {
|
||||
@@ -586,6 +600,7 @@ function resolveNextcloudTalkSession(
|
||||
cfg: params.cfg,
|
||||
agentId: params.agentId,
|
||||
channel: "nextcloud-talk",
|
||||
accountId: params.accountId,
|
||||
peer,
|
||||
});
|
||||
return {
|
||||
@@ -612,6 +627,7 @@ function resolveZaloSession(
|
||||
cfg: params.cfg,
|
||||
agentId: params.agentId,
|
||||
channel: "zalo",
|
||||
accountId: params.accountId,
|
||||
peer,
|
||||
});
|
||||
return {
|
||||
@@ -639,6 +655,7 @@ function resolveZalouserSession(
|
||||
cfg: params.cfg,
|
||||
agentId: params.agentId,
|
||||
channel: "zalouser",
|
||||
accountId: params.accountId,
|
||||
peer,
|
||||
});
|
||||
return {
|
||||
@@ -661,6 +678,7 @@ function resolveNostrSession(
|
||||
cfg: params.cfg,
|
||||
agentId: params.agentId,
|
||||
channel: "nostr",
|
||||
accountId: params.accountId,
|
||||
peer,
|
||||
});
|
||||
return {
|
||||
@@ -719,6 +737,7 @@ function resolveTlonSession(
|
||||
cfg: params.cfg,
|
||||
agentId: params.agentId,
|
||||
channel: "tlon",
|
||||
accountId: params.accountId,
|
||||
peer,
|
||||
});
|
||||
return {
|
||||
|
||||
@@ -227,3 +227,29 @@ describe("resolveAgentRoute", () => {
|
||||
expect(route.sessionKey).toBe("agent:home:main");
|
||||
});
|
||||
});
|
||||
|
||||
test("dmScope=per-account-channel-peer isolates DM sessions per account, channel and sender", () => {
|
||||
const cfg: MoltbotConfig = {
|
||||
session: { dmScope: "per-account-channel-peer" },
|
||||
};
|
||||
const route = resolveAgentRoute({
|
||||
cfg,
|
||||
channel: "telegram",
|
||||
accountId: "tasks",
|
||||
peer: { kind: "dm", id: "7550356539" },
|
||||
});
|
||||
expect(route.sessionKey).toBe("agent:main:telegram:tasks:dm:7550356539");
|
||||
});
|
||||
|
||||
test("dmScope=per-account-channel-peer uses default accountId when not provided", () => {
|
||||
const cfg: MoltbotConfig = {
|
||||
session: { dmScope: "per-account-channel-peer" },
|
||||
};
|
||||
const route = resolveAgentRoute({
|
||||
cfg,
|
||||
channel: "telegram",
|
||||
accountId: null,
|
||||
peer: { kind: "dm", id: "7550356539" },
|
||||
});
|
||||
expect(route.sessionKey).toBe("agent:main:telegram:default:dm:7550356539");
|
||||
});
|
||||
|
||||
@@ -69,9 +69,10 @@ function matchesAccountId(match: string | undefined, actual: string): boolean {
|
||||
export function buildAgentSessionKey(params: {
|
||||
agentId: string;
|
||||
channel: string;
|
||||
accountId?: string | null;
|
||||
peer?: RoutePeer | null;
|
||||
/** DM session scope. */
|
||||
dmScope?: "main" | "per-peer" | "per-channel-peer";
|
||||
dmScope?: "main" | "per-peer" | "per-channel-peer" | "per-account-channel-peer";
|
||||
identityLinks?: Record<string, string[]>;
|
||||
}): string {
|
||||
const channel = normalizeToken(params.channel) || "unknown";
|
||||
@@ -80,6 +81,7 @@ export function buildAgentSessionKey(params: {
|
||||
agentId: params.agentId,
|
||||
mainKey: DEFAULT_MAIN_KEY,
|
||||
channel,
|
||||
accountId: params.accountId,
|
||||
peerKind: peer?.kind ?? "dm",
|
||||
peerId: peer ? normalizeId(peer.id) || "unknown" : null,
|
||||
dmScope: params.dmScope,
|
||||
@@ -160,6 +162,7 @@ export function resolveAgentRoute(input: ResolveAgentRouteInput): ResolvedAgentR
|
||||
const sessionKey = buildAgentSessionKey({
|
||||
agentId: resolvedAgentId,
|
||||
channel,
|
||||
accountId,
|
||||
peer,
|
||||
dmScope,
|
||||
identityLinks,
|
||||
|
||||
@@ -111,11 +111,12 @@ export function buildAgentPeerSessionKey(params: {
|
||||
agentId: string;
|
||||
mainKey?: string | undefined;
|
||||
channel: string;
|
||||
accountId?: string | null;
|
||||
peerKind?: "dm" | "group" | "channel" | null;
|
||||
peerId?: string | null;
|
||||
identityLinks?: Record<string, string[]>;
|
||||
/** DM session scope. */
|
||||
dmScope?: "main" | "per-peer" | "per-channel-peer";
|
||||
dmScope?: "main" | "per-peer" | "per-channel-peer" | "per-account-channel-peer";
|
||||
}): string {
|
||||
const peerKind = params.peerKind ?? "dm";
|
||||
if (peerKind === "dm") {
|
||||
@@ -131,6 +132,11 @@ export function buildAgentPeerSessionKey(params: {
|
||||
});
|
||||
if (linkedPeerId) peerId = linkedPeerId;
|
||||
peerId = peerId.toLowerCase();
|
||||
if (dmScope === "per-account-channel-peer" && peerId) {
|
||||
const channel = (params.channel ?? "").trim().toLowerCase() || "unknown";
|
||||
const accountId = normalizeAccountId(params.accountId);
|
||||
return `agent:${normalizeAgentId(params.agentId)}:${channel}:${accountId}:dm:${peerId}`;
|
||||
}
|
||||
if (dmScope === "per-channel-peer" && peerId) {
|
||||
const channel = (params.channel ?? "").trim().toLowerCase() || "unknown";
|
||||
return `agent:${normalizeAgentId(params.agentId)}:${channel}:dm:${peerId}`;
|
||||
|
||||
Reference in New Issue
Block a user