import type { Client } from "@buape/carbon"; import { describe, expect, it } from "vitest"; import { buildAgentSessionKey } from "../../routing/resolve-route.js"; import { resolveDiscordAutoThreadContext, resolveDiscordAutoThreadReplyPlan, resolveDiscordReplyDeliveryPlan, } from "./threading.js"; describe("resolveDiscordAutoThreadContext", () => { it("returns null when no createdThreadId", () => { expect( resolveDiscordAutoThreadContext({ agentId: "agent", channel: "discord", messageChannelId: "parent", createdThreadId: undefined, }), ).toBeNull(); }); it("re-keys session context to the created thread", () => { const context = resolveDiscordAutoThreadContext({ agentId: "agent", channel: "discord", messageChannelId: "parent", createdThreadId: "thread", }); expect(context).not.toBeNull(); expect(context?.To).toBe("channel:thread"); expect(context?.From).toBe("discord:channel:thread"); expect(context?.OriginatingTo).toBe("channel:thread"); expect(context?.SessionKey).toBe( buildAgentSessionKey({ agentId: "agent", channel: "discord", peer: { kind: "channel", id: "thread" }, }), ); expect(context?.ParentSessionKey).toBe( buildAgentSessionKey({ agentId: "agent", channel: "discord", peer: { kind: "channel", id: "parent" }, }), ); }); }); describe("resolveDiscordReplyDeliveryPlan", () => { it("uses reply references when posting to the original target", () => { const plan = resolveDiscordReplyDeliveryPlan({ replyTarget: "channel:parent", replyToMode: "all", messageId: "m1", threadChannel: null, createdThreadId: null, }); expect(plan.deliverTarget).toBe("channel:parent"); expect(plan.replyTarget).toBe("channel:parent"); expect(plan.replyReference.use()).toBe("m1"); }); it("disables reply references when autoThread creates a new thread", () => { const plan = resolveDiscordReplyDeliveryPlan({ replyTarget: "channel:parent", replyToMode: "all", messageId: "m1", threadChannel: null, createdThreadId: "thread", }); expect(plan.deliverTarget).toBe("channel:thread"); expect(plan.replyTarget).toBe("channel:thread"); expect(plan.replyReference.use()).toBeUndefined(); }); it("always uses existingId when inside a thread", () => { const plan = resolveDiscordReplyDeliveryPlan({ replyTarget: "channel:thread", replyToMode: "off", messageId: "m1", threadChannel: { id: "thread" }, createdThreadId: null, }); expect(plan.replyReference.use()).toBe("m1"); }); }); describe("resolveDiscordAutoThreadReplyPlan", () => { it("switches delivery + session context to the created thread", async () => { const client = { rest: { post: async () => ({ id: "thread" }) }, } as unknown as Client; const plan = await resolveDiscordAutoThreadReplyPlan({ client, message: { id: "m1", channelId: "parent", } as unknown as import("./listeners.js").DiscordMessageEvent["message"], isGuildMessage: true, channelConfig: { autoThread: true, } as unknown as import("./allow-list.js").DiscordChannelConfigResolved, threadChannel: null, baseText: "hello", combinedBody: "hello", replyToMode: "all", agentId: "agent", channel: "discord", }); expect(plan.deliverTarget).toBe("channel:thread"); expect(plan.replyReference.use()).toBeUndefined(); expect(plan.autoThreadContext?.SessionKey).toBe( buildAgentSessionKey({ agentId: "agent", channel: "discord", peer: { kind: "channel", id: "thread" }, }), ); }); it("does nothing when autoThread is disabled", async () => { const client = { rest: { post: async () => ({ id: "thread" }) } } as unknown as Client; const plan = await resolveDiscordAutoThreadReplyPlan({ client, message: { id: "m1", channelId: "parent", } as unknown as import("./listeners.js").DiscordMessageEvent["message"], isGuildMessage: true, channelConfig: { autoThread: false, } as unknown as import("./allow-list.js").DiscordChannelConfigResolved, threadChannel: null, baseText: "hello", combinedBody: "hello", replyToMode: "all", agentId: "agent", channel: "discord", }); expect(plan.deliverTarget).toBe("channel:parent"); expect(plan.autoThreadContext).toBeNull(); }); });