refactor(discord): centralize autoThread reply plan (#856)
This commit is contained in:
@@ -25,9 +25,7 @@ import {
|
|||||||
import { buildDirectLabel, buildGuildLabel, resolveReplyContext } from "./reply-context.js";
|
import { buildDirectLabel, buildGuildLabel, resolveReplyContext } from "./reply-context.js";
|
||||||
import { deliverDiscordReply } from "./reply-delivery.js";
|
import { deliverDiscordReply } from "./reply-delivery.js";
|
||||||
import {
|
import {
|
||||||
maybeCreateDiscordAutoThread,
|
resolveDiscordAutoThreadReplyPlan,
|
||||||
resolveDiscordAutoThreadContext,
|
|
||||||
resolveDiscordReplyDeliveryPlan,
|
|
||||||
resolveDiscordThreadStarter,
|
resolveDiscordThreadStarter,
|
||||||
} from "./threading.js";
|
} from "./threading.js";
|
||||||
import { sendTyping } from "./typing.js";
|
import { sendTyping } from "./typing.js";
|
||||||
@@ -201,35 +199,22 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
|
|||||||
parentSessionKey,
|
parentSessionKey,
|
||||||
useSuffix: false,
|
useSuffix: false,
|
||||||
});
|
});
|
||||||
const inboundTarget = `channel:${message.channelId}`;
|
const replyPlan = await resolveDiscordAutoThreadReplyPlan({
|
||||||
const createdThreadId = await maybeCreateDiscordAutoThread({
|
|
||||||
client,
|
client,
|
||||||
message,
|
message,
|
||||||
isGuildMessage,
|
isGuildMessage,
|
||||||
channelConfig,
|
channelConfig,
|
||||||
threadChannel,
|
threadChannel,
|
||||||
baseText: baseText ?? "",
|
baseText: baseText ?? "",
|
||||||
combinedBody,
|
combinedBody,
|
||||||
});
|
|
||||||
const replyPlan = resolveDiscordReplyDeliveryPlan({
|
|
||||||
replyTarget: inboundTarget,
|
|
||||||
replyToMode,
|
replyToMode,
|
||||||
messageId: message.id,
|
agentId: route.agentId,
|
||||||
threadChannel,
|
channel: route.channel,
|
||||||
createdThreadId,
|
|
||||||
});
|
});
|
||||||
const deliverTarget = replyPlan.deliverTarget;
|
const deliverTarget = replyPlan.deliverTarget;
|
||||||
const replyTarget = replyPlan.replyTarget;
|
const replyTarget = replyPlan.replyTarget;
|
||||||
const replyReference = replyPlan.replyReference;
|
const replyReference = replyPlan.replyReference;
|
||||||
|
const autoThreadContext = replyPlan.autoThreadContext;
|
||||||
const autoThreadContext = isGuildMessage
|
|
||||||
? resolveDiscordAutoThreadContext({
|
|
||||||
agentId: route.agentId,
|
|
||||||
channel: route.channel,
|
|
||||||
messageChannelId: message.channelId,
|
|
||||||
createdThreadId,
|
|
||||||
})
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const effectiveFrom = isDirectMessage
|
const effectiveFrom = isDirectMessage
|
||||||
? `discord:${author.id}`
|
? `discord:${author.id}`
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { buildAgentSessionKey } from "../../routing/resolve-route.js";
|
import { buildAgentSessionKey } from "../../routing/resolve-route.js";
|
||||||
import { resolveDiscordAutoThreadContext } from "./threading.js";
|
import type { Client } from "@buape/carbon";
|
||||||
|
import {
|
||||||
|
resolveDiscordAutoThreadContext,
|
||||||
|
resolveDiscordAutoThreadReplyPlan,
|
||||||
|
resolveDiscordReplyDeliveryPlan,
|
||||||
|
} from "./threading.js";
|
||||||
|
|
||||||
describe("resolveDiscordAutoThreadContext", () => {
|
describe("resolveDiscordAutoThreadContext", () => {
|
||||||
it("returns null when no createdThreadId", () => {
|
it("returns null when no createdThreadId", () => {
|
||||||
@@ -42,3 +47,88 @@ describe("resolveDiscordAutoThreadContext", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -202,6 +202,51 @@ export function resolveDiscordAutoThreadContext(params: {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DiscordAutoThreadReplyPlan = DiscordReplyDeliveryPlan & {
|
||||||
|
createdThreadId?: string;
|
||||||
|
autoThreadContext: DiscordAutoThreadContext | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function resolveDiscordAutoThreadReplyPlan(params: {
|
||||||
|
client: Client;
|
||||||
|
message: DiscordMessageEvent["message"];
|
||||||
|
isGuildMessage: boolean;
|
||||||
|
channelConfig?: DiscordChannelConfigResolved | null;
|
||||||
|
threadChannel?: DiscordThreadChannel | null;
|
||||||
|
baseText: string;
|
||||||
|
combinedBody: string;
|
||||||
|
replyToMode: ReplyToMode;
|
||||||
|
agentId: string;
|
||||||
|
channel: string;
|
||||||
|
}): Promise<DiscordAutoThreadReplyPlan> {
|
||||||
|
const originalReplyTarget = `channel:${params.message.channelId}`;
|
||||||
|
const createdThreadId = await maybeCreateDiscordAutoThread({
|
||||||
|
client: params.client,
|
||||||
|
message: params.message,
|
||||||
|
isGuildMessage: params.isGuildMessage,
|
||||||
|
channelConfig: params.channelConfig,
|
||||||
|
threadChannel: params.threadChannel,
|
||||||
|
baseText: params.baseText,
|
||||||
|
combinedBody: params.combinedBody,
|
||||||
|
});
|
||||||
|
const deliveryPlan = resolveDiscordReplyDeliveryPlan({
|
||||||
|
replyTarget: originalReplyTarget,
|
||||||
|
replyToMode: params.replyToMode,
|
||||||
|
messageId: params.message.id,
|
||||||
|
threadChannel: params.threadChannel,
|
||||||
|
createdThreadId,
|
||||||
|
});
|
||||||
|
const autoThreadContext = params.isGuildMessage
|
||||||
|
? resolveDiscordAutoThreadContext({
|
||||||
|
agentId: params.agentId,
|
||||||
|
channel: params.channel,
|
||||||
|
messageChannelId: params.message.channelId,
|
||||||
|
createdThreadId,
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
return { ...deliveryPlan, createdThreadId, autoThreadContext };
|
||||||
|
}
|
||||||
|
|
||||||
export async function maybeCreateDiscordAutoThread(params: {
|
export async function maybeCreateDiscordAutoThread(params: {
|
||||||
client: Client;
|
client: Client;
|
||||||
message: DiscordMessageEvent["message"];
|
message: DiscordMessageEvent["message"];
|
||||||
|
|||||||
Reference in New Issue
Block a user