Discord: allow disabling thread starter context

This commit is contained in:
Josh Palmer
2026-02-04 13:25:28 -08:00
parent 5d82c82313
commit bebf323775
5 changed files with 133 additions and 15 deletions

View File

@@ -38,6 +38,8 @@ export type DiscordGuildChannelConfig = {
users?: Array<string | number>;
/** Optional system prompt snippet for this channel. */
systemPrompt?: string;
/** If false, omit thread starter context for this channel (default: true). */
includeThreadStarter?: boolean;
};
export type DiscordReactionNotificationMode = "off" | "own" | "all" | "allowlist";

View File

@@ -234,6 +234,7 @@ export const DiscordGuildChannelSchema = z
enabled: z.boolean().optional(),
users: z.array(z.union([z.string(), z.number()])).optional(),
systemPrompt: z.string().optional(),
includeThreadStarter: z.boolean().optional(),
autoThread: z.boolean().optional(),
})
.strict();

View File

@@ -438,6 +438,115 @@ describe("discord tool result dispatch", () => {
expect(capturedCtx?.ThreadLabel).toContain("Discord thread #general");
});
it("skips thread starter context when disabled", async () => {
const { createDiscordMessageHandler } = await import("./monitor.js");
let capturedCtx:
| {
ThreadStarterBody?: string;
}
| undefined;
dispatchMock.mockImplementationOnce(async ({ ctx, dispatcher }) => {
capturedCtx = ctx;
dispatcher.sendFinalReply({ text: "hi" });
return { queuedFinal: true, counts: { final: 1 } };
});
const cfg = {
agents: {
defaults: {
model: "anthropic/claude-opus-4-5",
workspace: "/tmp/openclaw",
},
},
session: { store: "/tmp/openclaw-sessions.json" },
channels: {
discord: {
dm: { enabled: true, policy: "open" },
groupPolicy: "open",
guilds: {
"*": {
requireMention: false,
channels: {
"*": { includeThreadStarter: false },
},
},
},
},
},
} as ReturnType<typeof import("../config/config.js").loadConfig>;
const handler = createDiscordMessageHandler({
cfg,
discordConfig: cfg.channels.discord,
accountId: "default",
token: "token",
runtime: {
log: vi.fn(),
error: vi.fn(),
exit: (code: number): never => {
throw new Error(`exit ${code}`);
},
},
botUserId: "bot-id",
guildHistories: new Map(),
historyLimit: 0,
mediaMaxBytes: 10_000,
textLimit: 2000,
replyToMode: "off",
dmEnabled: true,
groupDmEnabled: false,
guildEntries: cfg.channels.discord.guilds,
});
const threadChannel = {
type: ChannelType.GuildText,
name: "thread-name",
parentId: "p1",
parent: { id: "p1", name: "general" },
isThread: () => true,
};
const client = {
fetchChannel: vi.fn().mockResolvedValue({
type: ChannelType.GuildText,
name: "thread-name",
}),
rest: {
get: vi.fn().mockResolvedValue({
content: "starter message",
author: { id: "u1", username: "Alice", discriminator: "0001" },
timestamp: new Date().toISOString(),
}),
},
} as unknown as Client;
await handler(
{
message: {
id: "m7",
content: "thread reply",
channelId: "t1",
channel: threadChannel,
timestamp: new Date().toISOString(),
type: MessageType.Default,
attachments: [],
embeds: [],
mentionedEveryone: false,
mentionedUsers: [],
mentionedRoles: [],
author: { id: "u2", bot: false, username: "Bob", tag: "Bob#2" },
},
author: { id: "u2", bot: false, username: "Bob", tag: "Bob#2" },
member: { displayName: "Bob" },
guild: { id: "g1", name: "Guild" },
guild_id: "g1",
},
client,
);
expect(capturedCtx?.ThreadStarterBody).toBeUndefined();
});
it("treats forum threads as distinct sessions without channel payloads", async () => {
const { createDiscordMessageHandler } = await import("./monitor.js");
let capturedCtx:

View File

@@ -31,6 +31,7 @@ export type DiscordGuildEntryResolved = {
enabled?: boolean;
users?: Array<string | number>;
systemPrompt?: string;
includeThreadStarter?: boolean;
autoThread?: boolean;
}
>;
@@ -43,6 +44,7 @@ export type DiscordChannelConfigResolved = {
enabled?: boolean;
users?: Array<string | number>;
systemPrompt?: string;
includeThreadStarter?: boolean;
autoThread?: boolean;
matchKey?: string;
matchSource?: ChannelMatchSource;
@@ -241,6 +243,7 @@ function resolveDiscordChannelConfigEntry(
enabled: entry.enabled,
users: entry.users,
systemPrompt: entry.systemPrompt,
includeThreadStarter: entry.includeThreadStarter,
autoThread: entry.autoThread,
};
return resolved;

View File

@@ -209,6 +209,8 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
let threadLabel: string | undefined;
let parentSessionKey: string | undefined;
if (threadChannel) {
const includeThreadStarter = channelConfig?.includeThreadStarter !== false;
if (includeThreadStarter) {
const starter = await resolveDiscordThreadStarter({
channel: threadChannel,
client,
@@ -226,6 +228,7 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
});
threadStarterBody = starterEnvelope;
}
}
const parentName = threadParentName ?? "parent";
threadLabel = threadName
? `Discord thread #${normalizeDiscordSlug(parentName)} ${threadName}`