Discord: add trusted channel topics on new sessions
This commit is contained in:
@@ -16,6 +16,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Discord/Streaming: add stream preview mode for live draft replies with partial/block options and configurable chunking. Thanks @thewilloftheshadow. Inspiration @neoagentic-ship-it.
|
||||
- Discord/Telegram: add configurable lifecycle status reactions for queued/thinking/tool/done/error phases with a shared controller and emoji/timing overrides. Thanks @wolly-tundracube and @thewilloftheshadow.
|
||||
- Discord/Voice: add voice channel join/leave/status via `/vc`, plus auto-join configuration for realtime voice conversations. Thanks @thewilloftheshadow.
|
||||
- Discord: include channel topics in trusted inbound metadata on new sessions. Thanks @thewilloftheshadow.
|
||||
- Docs/Discord: document forum channel thread creation flows and component limits. Thanks @thewilloftheshadow.
|
||||
|
||||
### Fixes
|
||||
|
||||
@@ -614,7 +614,7 @@ See [Slash commands](/tools/slash-commands) for command catalog and behavior.
|
||||
- parent thread metadata can be used for parent-session linkage
|
||||
- thread config inherits parent channel config unless a thread-specific entry exists
|
||||
|
||||
Channel topics are injected as **untrusted** context (not as system prompt).
|
||||
Channel topics are injected as untrusted context and also included in trusted inbound metadata on new sessions.
|
||||
|
||||
</Accordion>
|
||||
|
||||
|
||||
@@ -71,6 +71,36 @@ describe("buildInboundMetaSystemPrompt", () => {
|
||||
const payload = parseInboundMetaPayload(prompt);
|
||||
expect(payload["sender_id"]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("includes discord channel topics only for new sessions", () => {
|
||||
const prompt = buildInboundMetaSystemPrompt({
|
||||
OriginatingTo: "discord:channel:123",
|
||||
OriginatingChannel: "discord",
|
||||
Provider: "discord",
|
||||
Surface: "discord",
|
||||
ChatType: "group",
|
||||
ChannelTopic: " Shipping updates ",
|
||||
IsNewSession: "true",
|
||||
} as TemplateContext);
|
||||
|
||||
const payload = parseInboundMetaPayload(prompt);
|
||||
expect(payload["channel_topic"]).toBe("Shipping updates");
|
||||
});
|
||||
|
||||
it("omits discord channel topics for existing sessions", () => {
|
||||
const prompt = buildInboundMetaSystemPrompt({
|
||||
OriginatingTo: "discord:channel:123",
|
||||
OriginatingChannel: "discord",
|
||||
Provider: "discord",
|
||||
Surface: "discord",
|
||||
ChatType: "group",
|
||||
ChannelTopic: "Shipping updates",
|
||||
IsNewSession: "false",
|
||||
} as TemplateContext);
|
||||
|
||||
const payload = parseInboundMetaPayload(prompt);
|
||||
expect(payload["channel_topic"]).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildInboundUserContextPrefix", () => {
|
||||
|
||||
@@ -13,8 +13,15 @@ function safeTrim(value: unknown): string | undefined {
|
||||
export function buildInboundMetaSystemPrompt(ctx: TemplateContext): string {
|
||||
const chatType = normalizeChatType(ctx.ChatType);
|
||||
const isDirect = !chatType || chatType === "direct";
|
||||
const isNewSession = ctx.IsNewSession === "true";
|
||||
const originatingChannel = safeTrim(ctx.OriginatingChannel);
|
||||
const surface = safeTrim(ctx.Surface);
|
||||
const provider = safeTrim(ctx.Provider);
|
||||
const isDiscord =
|
||||
provider === "discord" || surface === "discord" || originatingChannel === "discord";
|
||||
|
||||
// Keep system metadata strictly free of attacker-controlled strings (sender names, group subjects, etc.).
|
||||
// Keep system metadata strictly free of attacker-controlled strings (sender names, group subjects, etc.)
|
||||
// unless explicitly opted into for new-session context (e.g. Discord channel topics).
|
||||
// Those belong in the user-role "untrusted context" blocks.
|
||||
// Per-message identifiers (message_id, reply_to_id, sender_id) are also excluded here: they change
|
||||
// on every turn and would bust prefix-based prompt caches on local model providers. They are
|
||||
@@ -23,25 +30,27 @@ export function buildInboundMetaSystemPrompt(ctx: TemplateContext): string {
|
||||
// Resolve channel identity: prefer explicit channel, then surface, then provider.
|
||||
// For webchat/Hub Chat sessions (when Surface is 'webchat' or undefined with no real channel),
|
||||
// omit the channel field entirely rather than falling back to an unrelated provider.
|
||||
let channelValue = safeTrim(ctx.OriginatingChannel) ?? safeTrim(ctx.Surface);
|
||||
let channelValue = originatingChannel ?? surface;
|
||||
if (!channelValue) {
|
||||
// Only fall back to Provider if it represents a real messaging channel.
|
||||
// For webchat/internal sessions, ctx.Provider may be unrelated (e.g., the user's configured
|
||||
// default channel), so skip it to avoid incorrect runtime labels like "channel=whatsapp".
|
||||
const provider = safeTrim(ctx.Provider);
|
||||
// Check if provider is "webchat" or if we're in an internal/webchat context
|
||||
if (provider !== "webchat" && ctx.Surface !== "webchat") {
|
||||
if (provider !== "webchat" && surface !== "webchat") {
|
||||
channelValue = provider;
|
||||
}
|
||||
// Otherwise leave channelValue undefined (no channel label)
|
||||
}
|
||||
|
||||
const channelTopic = isNewSession && isDiscord ? safeTrim(ctx.ChannelTopic) : undefined;
|
||||
|
||||
const payload = {
|
||||
schema: "openclaw.inbound_meta.v1",
|
||||
chat_id: safeTrim(ctx.OriginatingTo),
|
||||
channel: channelValue,
|
||||
provider: safeTrim(ctx.Provider),
|
||||
surface: safeTrim(ctx.Surface),
|
||||
channel_topic: channelTopic,
|
||||
provider,
|
||||
surface,
|
||||
chat_type: chatType ?? (isDirect ? "direct" : undefined),
|
||||
flags: {
|
||||
is_group_chat: !isDirect ? true : undefined,
|
||||
|
||||
@@ -98,6 +98,8 @@ export type MsgContext = {
|
||||
GroupSubject?: string;
|
||||
/** Human label for channel-like group conversations (e.g. #general, #support). */
|
||||
GroupChannel?: string;
|
||||
/** Channel topic/description (trusted metadata for new session context). */
|
||||
ChannelTopic?: string;
|
||||
GroupSpace?: string;
|
||||
GroupMembers?: string;
|
||||
GroupSystemPrompt?: string;
|
||||
|
||||
@@ -173,6 +173,7 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
|
||||
const forumContextLine = isForumStarter ? `[Forum parent: #${forumParentSlug}]` : null;
|
||||
const groupChannel = isGuildMessage && displayChannelSlug ? `#${displayChannelSlug}` : undefined;
|
||||
const groupSubject = isDirectMessage ? undefined : groupChannel;
|
||||
const channelTopic = isGuildMessage ? channelInfo?.topic : undefined;
|
||||
const untrustedChannelMetadata = isGuildMessage
|
||||
? buildUntrustedChannelMetadata({
|
||||
source: "discord",
|
||||
@@ -334,6 +335,7 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
|
||||
SenderTag: senderTag,
|
||||
GroupSubject: groupSubject,
|
||||
GroupChannel: groupChannel,
|
||||
ChannelTopic: channelTopic,
|
||||
UntrustedContext: untrustedChannelMetadata ? [untrustedChannelMetadata] : undefined,
|
||||
GroupSystemPrompt: isGuildMessage ? groupSystemPrompt : undefined,
|
||||
GroupSpace: isGuildMessage ? (guildInfo?.id ?? guildSlug) || undefined : undefined,
|
||||
|
||||
Reference in New Issue
Block a user