mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 03:52:42 +00:00
Discord: allow disabling thread starter context
This commit is contained in:
@@ -38,6 +38,8 @@ export type DiscordGuildChannelConfig = {
|
|||||||
users?: Array<string | number>;
|
users?: Array<string | number>;
|
||||||
/** Optional system prompt snippet for this channel. */
|
/** Optional system prompt snippet for this channel. */
|
||||||
systemPrompt?: string;
|
systemPrompt?: string;
|
||||||
|
/** If false, omit thread starter context for this channel (default: true). */
|
||||||
|
includeThreadStarter?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DiscordReactionNotificationMode = "off" | "own" | "all" | "allowlist";
|
export type DiscordReactionNotificationMode = "off" | "own" | "all" | "allowlist";
|
||||||
|
|||||||
@@ -234,6 +234,7 @@ export const DiscordGuildChannelSchema = z
|
|||||||
enabled: z.boolean().optional(),
|
enabled: z.boolean().optional(),
|
||||||
users: z.array(z.union([z.string(), z.number()])).optional(),
|
users: z.array(z.union([z.string(), z.number()])).optional(),
|
||||||
systemPrompt: z.string().optional(),
|
systemPrompt: z.string().optional(),
|
||||||
|
includeThreadStarter: z.boolean().optional(),
|
||||||
autoThread: z.boolean().optional(),
|
autoThread: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|||||||
@@ -438,6 +438,115 @@ describe("discord tool result dispatch", () => {
|
|||||||
expect(capturedCtx?.ThreadLabel).toContain("Discord thread #general");
|
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 () => {
|
it("treats forum threads as distinct sessions without channel payloads", async () => {
|
||||||
const { createDiscordMessageHandler } = await import("./monitor.js");
|
const { createDiscordMessageHandler } = await import("./monitor.js");
|
||||||
let capturedCtx:
|
let capturedCtx:
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export type DiscordGuildEntryResolved = {
|
|||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
users?: Array<string | number>;
|
users?: Array<string | number>;
|
||||||
systemPrompt?: string;
|
systemPrompt?: string;
|
||||||
|
includeThreadStarter?: boolean;
|
||||||
autoThread?: boolean;
|
autoThread?: boolean;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
@@ -43,6 +44,7 @@ export type DiscordChannelConfigResolved = {
|
|||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
users?: Array<string | number>;
|
users?: Array<string | number>;
|
||||||
systemPrompt?: string;
|
systemPrompt?: string;
|
||||||
|
includeThreadStarter?: boolean;
|
||||||
autoThread?: boolean;
|
autoThread?: boolean;
|
||||||
matchKey?: string;
|
matchKey?: string;
|
||||||
matchSource?: ChannelMatchSource;
|
matchSource?: ChannelMatchSource;
|
||||||
@@ -241,6 +243,7 @@ function resolveDiscordChannelConfigEntry(
|
|||||||
enabled: entry.enabled,
|
enabled: entry.enabled,
|
||||||
users: entry.users,
|
users: entry.users,
|
||||||
systemPrompt: entry.systemPrompt,
|
systemPrompt: entry.systemPrompt,
|
||||||
|
includeThreadStarter: entry.includeThreadStarter,
|
||||||
autoThread: entry.autoThread,
|
autoThread: entry.autoThread,
|
||||||
};
|
};
|
||||||
return resolved;
|
return resolved;
|
||||||
|
|||||||
@@ -209,22 +209,25 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
|
|||||||
let threadLabel: string | undefined;
|
let threadLabel: string | undefined;
|
||||||
let parentSessionKey: string | undefined;
|
let parentSessionKey: string | undefined;
|
||||||
if (threadChannel) {
|
if (threadChannel) {
|
||||||
const starter = await resolveDiscordThreadStarter({
|
const includeThreadStarter = channelConfig?.includeThreadStarter !== false;
|
||||||
channel: threadChannel,
|
if (includeThreadStarter) {
|
||||||
client,
|
const starter = await resolveDiscordThreadStarter({
|
||||||
parentId: threadParentId,
|
channel: threadChannel,
|
||||||
parentType: threadParentType,
|
client,
|
||||||
resolveTimestampMs,
|
parentId: threadParentId,
|
||||||
});
|
parentType: threadParentType,
|
||||||
if (starter?.text) {
|
resolveTimestampMs,
|
||||||
const starterEnvelope = formatThreadStarterEnvelope({
|
|
||||||
channel: "Discord",
|
|
||||||
author: starter.author,
|
|
||||||
timestamp: starter.timestamp,
|
|
||||||
body: starter.text,
|
|
||||||
envelope: envelopeOptions,
|
|
||||||
});
|
});
|
||||||
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";
|
const parentName = threadParentName ?? "parent";
|
||||||
threadLabel = threadName
|
threadLabel = threadName
|
||||||
|
|||||||
Reference in New Issue
Block a user