mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 15:04:33 +00:00
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:
@@ -252,3 +252,160 @@ test("dmScope=per-account-channel-peer uses default accountId when not provided"
|
||||
});
|
||||
expect(route.sessionKey).toBe("agent:main:telegram:default:dm:7550356539");
|
||||
});
|
||||
|
||||
describe("parentPeer binding inheritance (thread support)", () => {
|
||||
test("thread inherits binding from parent channel when no direct match", () => {
|
||||
const cfg: MoltbotConfig = {
|
||||
bindings: [
|
||||
{
|
||||
agentId: "adecco",
|
||||
match: {
|
||||
channel: "discord",
|
||||
peer: { kind: "channel", id: "parent-channel-123" },
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const route = resolveAgentRoute({
|
||||
cfg,
|
||||
channel: "discord",
|
||||
peer: { kind: "channel", id: "thread-456" },
|
||||
parentPeer: { kind: "channel", id: "parent-channel-123" },
|
||||
});
|
||||
expect(route.agentId).toBe("adecco");
|
||||
expect(route.matchedBy).toBe("binding.peer.parent");
|
||||
});
|
||||
|
||||
test("direct peer binding wins over parent peer binding", () => {
|
||||
const cfg: MoltbotConfig = {
|
||||
bindings: [
|
||||
{
|
||||
agentId: "thread-agent",
|
||||
match: {
|
||||
channel: "discord",
|
||||
peer: { kind: "channel", id: "thread-456" },
|
||||
},
|
||||
},
|
||||
{
|
||||
agentId: "parent-agent",
|
||||
match: {
|
||||
channel: "discord",
|
||||
peer: { kind: "channel", id: "parent-channel-123" },
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const route = resolveAgentRoute({
|
||||
cfg,
|
||||
channel: "discord",
|
||||
peer: { kind: "channel", id: "thread-456" },
|
||||
parentPeer: { kind: "channel", id: "parent-channel-123" },
|
||||
});
|
||||
expect(route.agentId).toBe("thread-agent");
|
||||
expect(route.matchedBy).toBe("binding.peer");
|
||||
});
|
||||
|
||||
test("parent peer binding wins over guild binding", () => {
|
||||
const cfg: MoltbotConfig = {
|
||||
bindings: [
|
||||
{
|
||||
agentId: "parent-agent",
|
||||
match: {
|
||||
channel: "discord",
|
||||
peer: { kind: "channel", id: "parent-channel-123" },
|
||||
},
|
||||
},
|
||||
{
|
||||
agentId: "guild-agent",
|
||||
match: {
|
||||
channel: "discord",
|
||||
guildId: "guild-789",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const route = resolveAgentRoute({
|
||||
cfg,
|
||||
channel: "discord",
|
||||
peer: { kind: "channel", id: "thread-456" },
|
||||
parentPeer: { kind: "channel", id: "parent-channel-123" },
|
||||
guildId: "guild-789",
|
||||
});
|
||||
expect(route.agentId).toBe("parent-agent");
|
||||
expect(route.matchedBy).toBe("binding.peer.parent");
|
||||
});
|
||||
|
||||
test("falls back to guild binding when no parent peer match", () => {
|
||||
const cfg: MoltbotConfig = {
|
||||
bindings: [
|
||||
{
|
||||
agentId: "other-parent-agent",
|
||||
match: {
|
||||
channel: "discord",
|
||||
peer: { kind: "channel", id: "other-parent-999" },
|
||||
},
|
||||
},
|
||||
{
|
||||
agentId: "guild-agent",
|
||||
match: {
|
||||
channel: "discord",
|
||||
guildId: "guild-789",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const route = resolveAgentRoute({
|
||||
cfg,
|
||||
channel: "discord",
|
||||
peer: { kind: "channel", id: "thread-456" },
|
||||
parentPeer: { kind: "channel", id: "parent-channel-123" },
|
||||
guildId: "guild-789",
|
||||
});
|
||||
expect(route.agentId).toBe("guild-agent");
|
||||
expect(route.matchedBy).toBe("binding.guild");
|
||||
});
|
||||
|
||||
test("parentPeer with empty id is ignored", () => {
|
||||
const cfg: MoltbotConfig = {
|
||||
bindings: [
|
||||
{
|
||||
agentId: "parent-agent",
|
||||
match: {
|
||||
channel: "discord",
|
||||
peer: { kind: "channel", id: "parent-channel-123" },
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const route = resolveAgentRoute({
|
||||
cfg,
|
||||
channel: "discord",
|
||||
peer: { kind: "channel", id: "thread-456" },
|
||||
parentPeer: { kind: "channel", id: "" },
|
||||
});
|
||||
expect(route.agentId).toBe("main");
|
||||
expect(route.matchedBy).toBe("default");
|
||||
});
|
||||
|
||||
test("null parentPeer is handled gracefully", () => {
|
||||
const cfg: MoltbotConfig = {
|
||||
bindings: [
|
||||
{
|
||||
agentId: "parent-agent",
|
||||
match: {
|
||||
channel: "discord",
|
||||
peer: { kind: "channel", id: "parent-channel-123" },
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const route = resolveAgentRoute({
|
||||
cfg,
|
||||
channel: "discord",
|
||||
peer: { kind: "channel", id: "thread-456" },
|
||||
parentPeer: null,
|
||||
});
|
||||
expect(route.agentId).toBe("main");
|
||||
expect(route.matchedBy).toBe("default");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user