feat(routing): add thread parent binding inheritance for Discord (#3892)

* feat(routing): add thread parent binding inheritance for Discord

When a Discord thread message doesn't match a direct peer binding,
now checks if the parent channel has a binding and uses that agent.

This enables multi-agent setups where threads inherit their parent
channel's agent binding automatically.

Changes:
- Add parentPeer parameter to ResolveAgentRouteInput
- Add binding.peer.parent match type
- Resolve thread parent early in Discord preflight
- Pass parentPeer to resolveAgentRoute for threads

Fixes thread routing in Discord multi-agent configurations where
threads were incorrectly routed to the default agent instead of
inheriting from their parent channel's binding.

* ci: trigger fresh macOS runners

* Discord: inherit thread bindings in reactions

* fix: add changelog for thread parent binding (#3892) (thanks @aerolalit)

---------

Co-authored-by: Lalit Singh <lalit@clawd.bot>
Co-authored-by: OSS Agent <oss-agent@clawdbot.ai>
Co-authored-by: Shadow <shadow@clawd.bot>
This commit is contained in:
Lalit Singh
2026-02-01 03:30:45 +01:00
committed by GitHub
parent a393ae79d2
commit 01d76e4799
7 changed files with 241 additions and 59 deletions

View File

@@ -192,6 +192,32 @@ export async function preflightDiscordMessage(
accountId: params.accountId,
direction: "inbound",
});
// Resolve thread parent early for binding inheritance
const channelName =
channelInfo?.name ??
((isGuildMessage || isGroupDm) && message.channel && "name" in message.channel
? message.channel.name
: undefined);
const earlyThreadChannel = resolveDiscordThreadChannel({
isGuildMessage,
message,
channelInfo,
});
let earlyThreadParentId: string | undefined;
let earlyThreadParentName: string | undefined;
let earlyThreadParentType: ChannelType | undefined;
if (earlyThreadChannel) {
const parentInfo = await resolveDiscordThreadParentInfo({
client: params.client,
threadChannel: earlyThreadChannel,
channelInfo,
});
earlyThreadParentId = parentInfo.id;
earlyThreadParentName = parentInfo.name;
earlyThreadParentType = parentInfo.type;
}
const route = resolveAgentRoute({
cfg: params.cfg,
channel: "discord",
@@ -201,6 +227,8 @@ export async function preflightDiscordMessage(
kind: isDirectMessage ? "dm" : isGroupDm ? "group" : "channel",
id: isDirectMessage ? author.id : message.channelId,
},
// Pass parent peer for thread binding inheritance
parentPeer: earlyThreadParentId ? { kind: "channel", id: earlyThreadParentId } : undefined,
});
const mentionRegexes = buildMentionRegexes(params.cfg, route.agentId);
const explicitlyMentioned = Boolean(
@@ -262,29 +290,11 @@ export async function preflightDiscordMessage(
return null;
}
const channelName =
channelInfo?.name ??
((isGuildMessage || isGroupDm) && message.channel && "name" in message.channel
? message.channel.name
: undefined);
const threadChannel = resolveDiscordThreadChannel({
isGuildMessage,
message,
channelInfo,
});
let threadParentId: string | undefined;
let threadParentName: string | undefined;
let threadParentType: ChannelType | undefined;
if (threadChannel) {
const parentInfo = await resolveDiscordThreadParentInfo({
client: params.client,
threadChannel,
channelInfo,
});
threadParentId = parentInfo.id;
threadParentName = parentInfo.name;
threadParentType = parentInfo.type;
}
// Reuse early thread resolution from above (for binding inheritance)
const threadChannel = earlyThreadChannel;
const threadParentId = earlyThreadParentId;
const threadParentName = earlyThreadParentName;
const threadParentType = earlyThreadParentType;
const threadName = threadChannel?.name;
const configChannelName = threadParentName ?? channelName;
const configChannelSlug = configChannelName ? normalizeDiscordSlug(configChannelName) : "";