Discord: allow disabling thread starter context
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -209,22 +209,25 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
|
||||
let threadLabel: string | undefined;
|
||||
let parentSessionKey: string | undefined;
|
||||
if (threadChannel) {
|
||||
const starter = await resolveDiscordThreadStarter({
|
||||
channel: threadChannel,
|
||||
client,
|
||||
parentId: threadParentId,
|
||||
parentType: threadParentType,
|
||||
resolveTimestampMs,
|
||||
});
|
||||
if (starter?.text) {
|
||||
const starterEnvelope = formatThreadStarterEnvelope({
|
||||
channel: "Discord",
|
||||
author: starter.author,
|
||||
timestamp: starter.timestamp,
|
||||
body: starter.text,
|
||||
envelope: envelopeOptions,
|
||||
const includeThreadStarter = channelConfig?.includeThreadStarter !== false;
|
||||
if (includeThreadStarter) {
|
||||
const starter = await resolveDiscordThreadStarter({
|
||||
channel: threadChannel,
|
||||
client,
|
||||
parentId: threadParentId,
|
||||
parentType: threadParentType,
|
||||
resolveTimestampMs,
|
||||
});
|
||||
threadStarterBody = starterEnvelope;
|
||||
if (starter?.text) {
|
||||
const starterEnvelope = formatThreadStarterEnvelope({
|
||||
channel: "Discord",
|
||||
author: starter.author,
|
||||
timestamp: starter.timestamp,
|
||||
body: starter.text,
|
||||
envelope: envelopeOptions,
|
||||
});
|
||||
threadStarterBody = starterEnvelope;
|
||||
}
|
||||
}
|
||||
const parentName = threadParentName ?? "parent";
|
||||
threadLabel = threadName
|
||||
|
||||
Reference in New Issue
Block a user