From 6016e22cc0ca8bf05fddfc9b3088c0375e2a02ce Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 8 Mar 2026 01:23:56 +0000 Subject: [PATCH] refactor(discord): compose native command routes --- src/discord/monitor/native-command.ts | 39 ++++++++------------ src/discord/monitor/route-resolution.test.ts | 36 ++++++++++++++++++ src/discord/monitor/route-resolution.ts | 35 ++++++++++++++++++ 3 files changed, 87 insertions(+), 23 deletions(-) diff --git a/src/discord/monitor/native-command.ts b/src/discord/monitor/native-command.ts index 9193f24af75..7e8a6a07e4f 100644 --- a/src/discord/monitor/native-command.ts +++ b/src/discord/monitor/native-command.ts @@ -85,8 +85,7 @@ import { import { buildDiscordNativeCommandContext } from "./native-command-context.js"; import { resolveDiscordNativeCommandSessionTargets } from "./native-command-session-targets.js"; import { - buildDiscordRoutePeer, - resolveDiscordConversationRoute, + resolveDiscordBoundConversationRoute, resolveDiscordEffectiveRoute, } from "./route-resolution.js"; import { resolveDiscordSenderIdentity } from "./sender-identity.js"; @@ -451,25 +450,19 @@ async function resolveDiscordModelPickerRoute(params: { threadParentId = parentInfo.id; } - const route = resolveDiscordConversationRoute({ + const threadBinding = isThreadChannel + ? params.threadBindings.getByThreadId(rawChannelId) + : undefined; + return resolveDiscordBoundConversationRoute({ cfg, accountId, guildId: interaction.guild?.id ?? undefined, memberRoleIds, - peer: buildDiscordRoutePeer({ - isDirectMessage, - isGroupDm, - directUserId: interaction.user?.id ?? rawChannelId, - conversationId: rawChannelId, - }), + isDirectMessage, + isGroupDm, + directUserId: interaction.user?.id ?? rawChannelId, + conversationId: rawChannelId, parentConversationId: threadParentId, - }); - - const threadBinding = isThreadChannel - ? params.threadBindings.getByThreadId(rawChannelId) - : undefined; - return resolveDiscordEffectiveRoute({ - route, boundSessionKey: threadBinding?.targetSessionKey, }); } @@ -1605,18 +1598,18 @@ async function dispatchDiscordCommandInteraction(params: { const isGuild = Boolean(interaction.guild); const channelId = rawChannelId || "unknown"; const interactionId = interaction.rawData.id; - const route = resolveDiscordConversationRoute({ + const route = resolveDiscordBoundConversationRoute({ cfg, accountId, guildId: interaction.guild?.id ?? undefined, memberRoleIds, - peer: buildDiscordRoutePeer({ - isDirectMessage, - isGroupDm, - directUserId: user.id, - conversationId: channelId, - }), + isDirectMessage, + isGroupDm, + directUserId: user.id, + conversationId: channelId, parentConversationId: threadParentId, + // Configured ACP routes apply after raw route resolution, so do not pass + // bound/configured overrides here. }); const threadBinding = isThreadChannel ? threadBindings.getByThreadId(rawChannelId) : undefined; const configuredRoute = diff --git a/src/discord/monitor/route-resolution.test.ts b/src/discord/monitor/route-resolution.test.ts index afddde3fd2d..f64023cd012 100644 --- a/src/discord/monitor/route-resolution.test.ts +++ b/src/discord/monitor/route-resolution.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest"; import type { OpenClawConfig } from "../../config/config.js"; import type { ResolvedAgentRoute } from "../../routing/resolve-route.js"; import { + resolveDiscordBoundConversationRoute, buildDiscordRoutePeer, resolveDiscordConversationRoute, resolveDiscordEffectiveRoute, @@ -104,4 +105,39 @@ describe("discord route resolution helpers", () => { matchedBy: "binding.peer", }); }); + + it("composes route building with effective-route overrides", () => { + const cfg: OpenClawConfig = { + agents: { + list: [{ id: "worker" }], + }, + bindings: [ + { + agentId: "worker", + match: { + channel: "discord", + accountId: "default", + peer: { kind: "direct", id: "user-1" }, + }, + }, + ], + }; + + expect( + resolveDiscordBoundConversationRoute({ + cfg, + accountId: "default", + isDirectMessage: true, + isGroupDm: false, + directUserId: "user-1", + conversationId: "dm-1", + boundSessionKey: "agent:worker:discord:direct:user-1", + matchedBy: "binding.channel", + }), + ).toMatchObject({ + agentId: "worker", + sessionKey: "agent:worker:discord:direct:user-1", + matchedBy: "binding.channel", + }); + }); }); diff --git a/src/discord/monitor/route-resolution.ts b/src/discord/monitor/route-resolution.ts index 99ac767f7ff..b0284ff7792 100644 --- a/src/discord/monitor/route-resolution.ts +++ b/src/discord/monitor/route-resolution.ts @@ -41,6 +41,41 @@ export function resolveDiscordConversationRoute(params: { }); } +export function resolveDiscordBoundConversationRoute(params: { + cfg: OpenClawConfig; + accountId?: string | null; + guildId?: string | null; + memberRoleIds?: string[]; + isDirectMessage: boolean; + isGroupDm: boolean; + directUserId?: string | null; + conversationId: string; + parentConversationId?: string | null; + boundSessionKey?: string | null; + configuredRoute?: { route: ResolvedAgentRoute } | null; + matchedBy?: ResolvedAgentRoute["matchedBy"]; +}): ResolvedAgentRoute { + const route = resolveDiscordConversationRoute({ + cfg: params.cfg, + accountId: params.accountId, + guildId: params.guildId, + memberRoleIds: params.memberRoleIds, + peer: buildDiscordRoutePeer({ + isDirectMessage: params.isDirectMessage, + isGroupDm: params.isGroupDm, + directUserId: params.directUserId, + conversationId: params.conversationId, + }), + parentConversationId: params.parentConversationId, + }); + return resolveDiscordEffectiveRoute({ + route, + boundSessionKey: params.boundSessionKey, + configuredRoute: params.configuredRoute, + matchedBy: params.matchedBy, + }); +} + export function resolveDiscordEffectiveRoute(params: { route: ResolvedAgentRoute; boundSessionKey?: string | null;