Merge dbddfaeb54 into 09be5d45d5
This commit is contained in:
commit
ec4c4e2bc2
@ -20,6 +20,15 @@ function listConfiguredAccountIds(cfg: OpenClawConfig): string[] {
|
|||||||
|
|
||||||
export function listDiscordAccountIds(cfg: OpenClawConfig): string[] {
|
export function listDiscordAccountIds(cfg: OpenClawConfig): string[] {
|
||||||
const ids = listConfiguredAccountIds(cfg);
|
const ids = listConfiguredAccountIds(cfg);
|
||||||
|
const explicitDefault = ids.includes(DEFAULT_ACCOUNT_ID);
|
||||||
|
|
||||||
|
if (!explicitDefault) {
|
||||||
|
const defaultResolution = resolveDiscordToken(cfg, { accountId: DEFAULT_ACCOUNT_ID });
|
||||||
|
if (defaultResolution.source !== "none") {
|
||||||
|
ids.push(DEFAULT_ACCOUNT_ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (ids.length === 0) return [DEFAULT_ACCOUNT_ID];
|
if (ids.length === 0) return [DEFAULT_ACCOUNT_ID];
|
||||||
return ids.sort((a, b) => a.localeCompare(b));
|
return ids.sort((a, b) => a.localeCompare(b));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -284,6 +284,7 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
|
|||||||
Surface: "discord" as const,
|
Surface: "discord" as const,
|
||||||
WasMentioned: effectiveWasMentioned,
|
WasMentioned: effectiveWasMentioned,
|
||||||
MessageSid: message.id,
|
MessageSid: message.id,
|
||||||
|
RpcId: `discord:${route.accountId}:${message.id}`,
|
||||||
ParentSessionKey: autoThreadContext?.ParentSessionKey ?? threadKeys.parentSessionKey,
|
ParentSessionKey: autoThreadContext?.ParentSessionKey ?? threadKeys.parentSessionKey,
|
||||||
ThreadStarterBody: threadStarterBody,
|
ThreadStarterBody: threadStarterBody,
|
||||||
ThreadLabel: threadLabel,
|
ThreadLabel: threadLabel,
|
||||||
|
|||||||
91
src/discord/multi-account.test.ts
Normal file
91
src/discord/multi-account.test.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { type MoltbotConfig } from "../config/config.js";
|
||||||
|
import { listDiscordAccountIds } from "./accounts.js";
|
||||||
|
import { resolveDiscordToken } from "./token.js";
|
||||||
|
|
||||||
|
describe("discord multi-account logic", () => {
|
||||||
|
it("should list all configured accounts", () => {
|
||||||
|
const config = {
|
||||||
|
channels: {
|
||||||
|
discord: {
|
||||||
|
accounts: {
|
||||||
|
bot_a: { token: "token_a", enabled: true },
|
||||||
|
bot_b: { token: "token_b", enabled: true },
|
||||||
|
bot_c: { token: "token_c", enabled: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as unknown as MoltbotConfig;
|
||||||
|
|
||||||
|
const ids = listDiscordAccountIds(config);
|
||||||
|
expect(ids).toEqual(["bot_a", "bot_b", "bot_c"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should resolve specific account tokens", () => {
|
||||||
|
const config = {
|
||||||
|
channels: {
|
||||||
|
discord: {
|
||||||
|
accounts: {
|
||||||
|
bot_a: { token: "token_a" },
|
||||||
|
bot_b: { token: "token_b" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as unknown as MoltbotConfig;
|
||||||
|
|
||||||
|
const tokenA = resolveDiscordToken(config, { accountId: "bot_a" });
|
||||||
|
const tokenB = resolveDiscordToken(config, { accountId: "bot_b" });
|
||||||
|
const tokenUnknown = resolveDiscordToken(config, { accountId: "bot_unknown" });
|
||||||
|
|
||||||
|
expect(tokenA.token).toBe("token_a");
|
||||||
|
expect(tokenB.token).toBe("token_b");
|
||||||
|
expect(tokenUnknown.token).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fall back to default token if no account token specified", () => {
|
||||||
|
const config = {
|
||||||
|
channels: {
|
||||||
|
discord: {
|
||||||
|
token: "default_token",
|
||||||
|
accounts: {
|
||||||
|
bot_a: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as unknown as MoltbotConfig;
|
||||||
|
|
||||||
|
const tokenA = resolveDiscordToken(config, { accountId: "bot_a" });
|
||||||
|
|
||||||
|
expect(tokenA.token).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should include default account if top-level token is present", () => {
|
||||||
|
const config = {
|
||||||
|
channels: {
|
||||||
|
discord: {
|
||||||
|
token: "default_token",
|
||||||
|
accounts: {
|
||||||
|
bot_a: { token: "token_a" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as unknown as MoltbotConfig;
|
||||||
|
|
||||||
|
const ids = listDiscordAccountIds(config);
|
||||||
|
expect(ids).toContain("default");
|
||||||
|
expect(ids).toContain("bot_a");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should resolve default account correctly", () => {
|
||||||
|
const config = {
|
||||||
|
channels: {
|
||||||
|
discord: {
|
||||||
|
token: "default_token",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as unknown as MoltbotConfig;
|
||||||
|
|
||||||
|
const token = resolveDiscordToken(config, { accountId: "default" });
|
||||||
|
expect(token.token).toBe("default_token");
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -104,7 +104,7 @@ describe("resolveAgentRoute", () => {
|
|||||||
peer: { kind: "dm", id: "+1000" },
|
peer: { kind: "dm", id: "+1000" },
|
||||||
});
|
});
|
||||||
expect(route.agentId).toBe("a");
|
expect(route.agentId).toBe("a");
|
||||||
expect(route.sessionKey).toBe("agent:a:main");
|
expect(route.sessionKey).toBe("agent:a:main:biz");
|
||||||
expect(route.matchedBy).toBe("binding.peer");
|
expect(route.matchedBy).toBe("binding.peer");
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -224,7 +224,8 @@ describe("resolveAgentRoute", () => {
|
|||||||
peer: { kind: "dm", id: "+1000" },
|
peer: { kind: "dm", id: "+1000" },
|
||||||
});
|
});
|
||||||
expect(route.agentId).toBe("home");
|
expect(route.agentId).toBe("home");
|
||||||
expect(route.sessionKey).toBe("agent:home:main");
|
// Updated: Non-default accounts get distinct session keys
|
||||||
|
expect(route.sessionKey).toBe("agent:home:main:biz");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -139,11 +139,27 @@ export function buildAgentPeerSessionKey(params: {
|
|||||||
}
|
}
|
||||||
if (dmScope === "per-channel-peer" && peerId) {
|
if (dmScope === "per-channel-peer" && peerId) {
|
||||||
const channel = (params.channel ?? "").trim().toLowerCase() || "unknown";
|
const channel = (params.channel ?? "").trim().toLowerCase() || "unknown";
|
||||||
|
const accountId = normalizeAccountId(params.accountId);
|
||||||
|
if (accountId !== DEFAULT_ACCOUNT_ID) {
|
||||||
|
return `agent:${normalizeAgentId(params.agentId)}:${channel}:${accountId}:dm:${peerId}`;
|
||||||
|
}
|
||||||
return `agent:${normalizeAgentId(params.agentId)}:${channel}:dm:${peerId}`;
|
return `agent:${normalizeAgentId(params.agentId)}:${channel}:dm:${peerId}`;
|
||||||
}
|
}
|
||||||
if (dmScope === "per-peer" && peerId) {
|
if (dmScope === "per-peer" && peerId) {
|
||||||
return `agent:${normalizeAgentId(params.agentId)}:dm:${peerId}`;
|
return `agent:${normalizeAgentId(params.agentId)}:dm:${peerId}`;
|
||||||
}
|
}
|
||||||
|
const accountId = normalizeAccountId(params.accountId);
|
||||||
|
if (accountId && accountId !== DEFAULT_ACCOUNT_ID) {
|
||||||
|
if (dmScope === "main") {
|
||||||
|
return (
|
||||||
|
buildAgentMainSessionKey({
|
||||||
|
agentId: params.agentId,
|
||||||
|
mainKey: params.mainKey,
|
||||||
|
}) + `:${accountId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return `agent:${normalizeAgentId(params.agentId)}:dm:${peerId}:${accountId}`;
|
||||||
|
}
|
||||||
return buildAgentMainSessionKey({
|
return buildAgentMainSessionKey({
|
||||||
agentId: params.agentId,
|
agentId: params.agentId,
|
||||||
mainKey: params.mainKey,
|
mainKey: params.mainKey,
|
||||||
@ -151,7 +167,13 @@ export function buildAgentPeerSessionKey(params: {
|
|||||||
}
|
}
|
||||||
const channel = (params.channel ?? "").trim().toLowerCase() || "unknown";
|
const channel = (params.channel ?? "").trim().toLowerCase() || "unknown";
|
||||||
const peerId = ((params.peerId ?? "").trim() || "unknown").toLowerCase();
|
const peerId = ((params.peerId ?? "").trim() || "unknown").toLowerCase();
|
||||||
return `agent:${normalizeAgentId(params.agentId)}:${channel}:${peerKind}:${peerId}`;
|
const accountId = normalizeAccountId(params.accountId);
|
||||||
|
const baseKey = `agent:${normalizeAgentId(params.agentId)}:${channel}:${peerKind}:${peerId}`;
|
||||||
|
|
||||||
|
if (accountId && accountId !== DEFAULT_ACCOUNT_ID) {
|
||||||
|
return `${baseKey}:${accountId}`;
|
||||||
|
}
|
||||||
|
return baseKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveLinkedPeerId(params: {
|
function resolveLinkedPeerId(params: {
|
||||||
|
|||||||
58
src/routing/shared-channel.test.ts
Normal file
58
src/routing/shared-channel.test.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { resolveAgentRoute, type ResolveAgentRouteInput } from "./resolve-route.js";
|
||||||
|
import type { MoltbotConfig } from "../config/config.js";
|
||||||
|
|
||||||
|
const mockConfig: MoltbotConfig = {
|
||||||
|
agents: { list: [{ id: "main" }] },
|
||||||
|
channels: {
|
||||||
|
discord: {
|
||||||
|
enabled: true,
|
||||||
|
accounts: {
|
||||||
|
bot_a: { token: "taken_a" },
|
||||||
|
bot_b: { token: "token_b" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("resolveAgentRoute - Shared Channel", () => {
|
||||||
|
it("should generate distinct keys for SAME channel on DIFFERENT bots", () => {
|
||||||
|
const inputA: ResolveAgentRouteInput = {
|
||||||
|
cfg: mockConfig,
|
||||||
|
channel: "discord",
|
||||||
|
accountId: "bot_a",
|
||||||
|
peer: { kind: "channel", id: "channel_123" },
|
||||||
|
};
|
||||||
|
|
||||||
|
const inputB: ResolveAgentRouteInput = {
|
||||||
|
cfg: mockConfig,
|
||||||
|
channel: "discord",
|
||||||
|
accountId: "bot_b",
|
||||||
|
peer: { kind: "channel", id: "channel_123" },
|
||||||
|
};
|
||||||
|
|
||||||
|
const routeA = resolveAgentRoute(inputA);
|
||||||
|
const routeB = resolveAgentRoute(inputB);
|
||||||
|
|
||||||
|
console.log("Channel RouteA:", routeA.sessionKey);
|
||||||
|
console.log("Channel RouteB:", routeB.sessionKey);
|
||||||
|
|
||||||
|
expect(routeA.sessionKey).not.toEqual(routeB.sessionKey);
|
||||||
|
expect(routeA.sessionKey).toContain(":bot_a");
|
||||||
|
expect(routeB.sessionKey).toContain(":bot_b");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should preserve backward compatibility for default bot in channel", () => {
|
||||||
|
const input: ResolveAgentRouteInput = {
|
||||||
|
cfg: mockConfig,
|
||||||
|
channel: "discord",
|
||||||
|
accountId: "default",
|
||||||
|
peer: { kind: "channel", id: "channel_123" },
|
||||||
|
};
|
||||||
|
|
||||||
|
const route = resolveAgentRoute(input);
|
||||||
|
console.log("Channel Default:", route.sessionKey);
|
||||||
|
|
||||||
|
expect(route.sessionKey).toBe("agent:main:discord:channel:channel_123");
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -200,7 +200,7 @@ describe("createTelegramBot", () => {
|
|||||||
expect(replySpy).toHaveBeenCalledTimes(1);
|
expect(replySpy).toHaveBeenCalledTimes(1);
|
||||||
const payload = replySpy.mock.calls[0][0];
|
const payload = replySpy.mock.calls[0][0];
|
||||||
expect(payload.AccountId).toBe("opie");
|
expect(payload.AccountId).toBe("opie");
|
||||||
expect(payload.SessionKey).toBe("agent:opie:main");
|
expect(payload.SessionKey).toBe("agent:opie:main:opie");
|
||||||
});
|
});
|
||||||
it("allows per-group requireMention override", async () => {
|
it("allows per-group requireMention override", async () => {
|
||||||
onSpy.mockReset();
|
onSpy.mockReset();
|
||||||
|
|||||||
@ -1260,7 +1260,7 @@ describe("createTelegramBot", () => {
|
|||||||
expect(replySpy).toHaveBeenCalledTimes(1);
|
expect(replySpy).toHaveBeenCalledTimes(1);
|
||||||
const payload = replySpy.mock.calls[0][0];
|
const payload = replySpy.mock.calls[0][0];
|
||||||
expect(payload.AccountId).toBe("opie");
|
expect(payload.AccountId).toBe("opie");
|
||||||
expect(payload.SessionKey).toBe("agent:opie:main");
|
expect(payload.SessionKey).toBe("agent:opie:main:opie");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows per-group requireMention override", async () => {
|
it("allows per-group requireMention override", async () => {
|
||||||
|
|||||||
@ -242,7 +242,7 @@ describe("partial reply gating", () => {
|
|||||||
});
|
});
|
||||||
it("updates last-route for group chats with account id", async () => {
|
it("updates last-route for group chats with account id", async () => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const groupSessionKey = "agent:main:whatsapp:group:123@g.us";
|
const groupSessionKey = "agent:main:whatsapp:group:123@g.us:work";
|
||||||
const store = await makeSessionStore({
|
const store = await makeSessionStore({
|
||||||
[groupSessionKey]: { sessionId: "sid", updatedAt: now - 1 },
|
[groupSessionKey]: { sessionId: "sid", updatedAt: now - 1 },
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user