refactor(discord): extract inbound context helpers

This commit is contained in:
Peter Steinberger
2026-03-08 01:22:33 +00:00
parent 08597e817d
commit 547436bca7
5 changed files with 134 additions and 57 deletions

View File

@@ -63,9 +63,12 @@ import {
resolveDiscordGuildEntry,
resolveDiscordMemberAccessState,
resolveDiscordOwnerAccess,
resolveDiscordOwnerAllowFrom,
} from "./allow-list.js";
import { formatDiscordUserTag } from "./format.js";
import {
buildDiscordInboundAccessContext,
buildDiscordGroupSystemPrompt,
} from "./inbound-context.js";
import { buildDirectLabel, buildGuildLabel } from "./reply-context.js";
import { deliverDiscordReply } from "./reply-delivery.js";
import { sendTyping } from "./typing.js";
@@ -865,13 +868,14 @@ async function dispatchDiscordComponentEvent(params: {
scope: channelCtx.isThread ? "thread" : "channel",
});
const allowNameMatching = isDangerousNameMatchingEnabled(ctx.discordConfig);
const groupSystemPrompt = channelConfig?.systemPrompt?.trim() || undefined;
const ownerAllowFrom = resolveDiscordOwnerAllowFrom({
const { ownerAllowFrom } = buildDiscordInboundAccessContext({
channelConfig,
guildInfo,
sender: { id: interactionCtx.user.id, name: interactionCtx.user.username, tag: senderTag },
allowNameMatching,
isGuild: !interactionCtx.isDirectMessage,
});
const groupSystemPrompt = buildDiscordGroupSystemPrompt(channelConfig);
const pinnedMainDmOwner = interactionCtx.isDirectMessage
? resolvePinnedMainDmOwnerFromAllowlist({
dmScope: ctx.cfg.session?.dmScope,

View File

@@ -0,0 +1,55 @@
import { describe, expect, it } from "vitest";
import {
buildDiscordGroupSystemPrompt,
buildDiscordInboundAccessContext,
buildDiscordUntrustedContext,
} from "./inbound-context.js";
describe("Discord inbound context helpers", () => {
it("builds guild access context from channel config and topic", () => {
expect(
buildDiscordInboundAccessContext({
channelConfig: {
allowed: true,
users: ["discord:user-1"],
systemPrompt: "Use the runbook.",
},
guildInfo: { id: "guild-1" },
sender: {
id: "user-1",
name: "tester",
tag: "tester#0001",
},
isGuild: true,
channelTopic: "Production alerts only",
}),
).toEqual({
groupSystemPrompt: "Use the runbook.",
untrustedContext: [expect.stringContaining("Production alerts only")],
ownerAllowFrom: ["user-1"],
});
});
it("omits guild-only metadata for direct messages", () => {
expect(
buildDiscordInboundAccessContext({
sender: {
id: "user-1",
},
isGuild: false,
channelTopic: "ignored",
}),
).toEqual({
groupSystemPrompt: undefined,
untrustedContext: undefined,
ownerAllowFrom: undefined,
});
});
it("keeps direct helper behavior consistent", () => {
expect(buildDiscordGroupSystemPrompt({ allowed: true, systemPrompt: " hi " })).toBe("hi");
expect(buildDiscordUntrustedContext({ isGuild: true, channelTopic: "topic" })).toEqual([
expect.stringContaining("topic"),
]);
});
});

View File

@@ -0,0 +1,59 @@
import { buildUntrustedChannelMetadata } from "../../security/channel-metadata.js";
import {
resolveDiscordOwnerAllowFrom,
type DiscordChannelConfigResolved,
type DiscordGuildEntryResolved,
} from "./allow-list.js";
export function buildDiscordGroupSystemPrompt(
channelConfig?: DiscordChannelConfigResolved | null,
): string | undefined {
const systemPromptParts = [channelConfig?.systemPrompt?.trim() || null].filter(
(entry): entry is string => Boolean(entry),
);
return systemPromptParts.length > 0 ? systemPromptParts.join("\n\n") : undefined;
}
export function buildDiscordUntrustedContext(params: {
isGuild: boolean;
channelTopic?: string;
}): string[] | undefined {
if (!params.isGuild) {
return undefined;
}
const untrustedChannelMetadata = buildUntrustedChannelMetadata({
source: "discord",
label: "Discord channel topic",
entries: [params.channelTopic],
});
return untrustedChannelMetadata ? [untrustedChannelMetadata] : undefined;
}
export function buildDiscordInboundAccessContext(params: {
channelConfig?: DiscordChannelConfigResolved | null;
guildInfo?: DiscordGuildEntryResolved | null;
sender: {
id: string;
name?: string;
tag?: string;
};
allowNameMatching?: boolean;
isGuild: boolean;
channelTopic?: string;
}) {
return {
groupSystemPrompt: params.isGuild
? buildDiscordGroupSystemPrompt(params.channelConfig)
: undefined,
untrustedContext: buildDiscordUntrustedContext({
isGuild: params.isGuild,
channelTopic: params.channelTopic,
}),
ownerAllowFrom: resolveDiscordOwnerAllowFrom({
channelConfig: params.channelConfig,
guildInfo: params.guildInfo,
sender: params.sender,
allowNameMatching: params.allowNameMatching,
}),
};
}

View File

@@ -30,7 +30,6 @@ import { convertMarkdownTables } from "../../markdown/tables.js";
import { getAgentScopedMediaLocalRoots } from "../../media/local-roots.js";
import { buildAgentSessionKey } from "../../routing/resolve-route.js";
import { resolveThreadSessionKeys } from "../../routing/session-key.js";
import { buildUntrustedChannelMetadata } from "../../security/channel-metadata.js";
import { stripReasoningTagsFromText } from "../../shared/text/reasoning-tags.js";
import { truncateUtf16Safe } from "../../utils.js";
import { chunkDiscordTextWithMode } from "../chunk.js";
@@ -38,8 +37,9 @@ import { resolveDiscordDraftStreamingChunking } from "../draft-chunking.js";
import { createDiscordDraftStream } from "../draft-stream.js";
import { reactMessageDiscord, removeReactionDiscord } from "../send.js";
import { editMessageDiscord } from "../send.messages.js";
import { normalizeDiscordSlug, resolveDiscordOwnerAllowFrom } from "./allow-list.js";
import { normalizeDiscordSlug } from "./allow-list.js";
import { resolveTimestampMs } from "./format.js";
import { buildDiscordInboundAccessContext } from "./inbound-context.js";
import type { DiscordMessagePreflightContext } from "./message-handler.preflight.js";
import {
buildDiscordMediaPayload,
@@ -212,13 +212,6 @@ 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 untrustedChannelMetadata = isGuildMessage
? buildUntrustedChannelMetadata({
source: "discord",
label: "Discord channel topic",
entries: [channelInfo?.topic],
})
: undefined;
const senderName = sender.isPluralKit
? (sender.name ?? author.username)
: (data.member?.nickname ?? author.globalName ?? author.username);
@@ -226,16 +219,13 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
? (sender.tag ?? sender.name ?? author.username)
: author.username;
const senderTag = sender.tag;
const systemPromptParts = [channelConfig?.systemPrompt?.trim() || null].filter(
(entry): entry is string => Boolean(entry),
);
const groupSystemPrompt =
systemPromptParts.length > 0 ? systemPromptParts.join("\n\n") : undefined;
const ownerAllowFrom = resolveDiscordOwnerAllowFrom({
const { groupSystemPrompt, ownerAllowFrom, untrustedContext } = buildDiscordInboundAccessContext({
channelConfig,
guildInfo,
sender: { id: sender.id, name: sender.name, tag: sender.tag },
allowNameMatching: isDangerousNameMatchingEnabled(discordConfig),
isGuild: isGuildMessage,
channelTopic: channelInfo?.topic,
});
const storePath = resolveStorePath(cfg.session?.store, {
agentId: route.agentId,
@@ -374,7 +364,7 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
SenderTag: senderTag,
GroupSubject: groupSubject,
GroupChannel: groupChannel,
UntrustedContext: untrustedChannelMetadata ? [untrustedChannelMetadata] : undefined,
UntrustedContext: untrustedContext,
GroupSystemPrompt: isGuildMessage ? groupSystemPrompt : undefined,
GroupSpace: isGuildMessage ? (guildInfo?.id ?? guildSlug) || undefined : undefined,
OwnerAllowFrom: ownerAllowFrom,

View File

@@ -1,11 +1,7 @@
import type { CommandArgs } from "../../auto-reply/commands-registry.js";
import { finalizeInboundContext } from "../../auto-reply/reply/inbound-context.js";
import { buildUntrustedChannelMetadata } from "../../security/channel-metadata.js";
import {
resolveDiscordOwnerAllowFrom,
type DiscordChannelConfigResolved,
type DiscordGuildEntryResolved,
} from "./allow-list.js";
import { type DiscordChannelConfigResolved, type DiscordGuildEntryResolved } from "./allow-list.js";
import { buildDiscordInboundAccessContext } from "./inbound-context.js";
export type BuildDiscordNativeCommandContextParams = {
prompt: string;
@@ -39,39 +35,17 @@ export type BuildDiscordNativeCommandContextParams = {
timestampMs?: number;
};
function buildDiscordNativeCommandSystemPrompt(
channelConfig?: DiscordChannelConfigResolved | null,
): string | undefined {
const systemPromptParts = [channelConfig?.systemPrompt?.trim() || null].filter(
(entry): entry is string => Boolean(entry),
);
return systemPromptParts.length > 0 ? systemPromptParts.join("\n\n") : undefined;
}
function buildDiscordNativeCommandUntrustedContext(params: {
isGuild: boolean;
channelTopic?: string;
}): string[] | undefined {
if (!params.isGuild) {
return undefined;
}
const untrustedChannelMetadata = buildUntrustedChannelMetadata({
source: "discord",
label: "Discord channel topic",
entries: [params.channelTopic],
});
return untrustedChannelMetadata ? [untrustedChannelMetadata] : undefined;
}
export function buildDiscordNativeCommandContext(params: BuildDiscordNativeCommandContextParams) {
const conversationLabel = params.isDirectMessage
? (params.user.globalName ?? params.user.username)
: params.channelId;
const ownerAllowFrom = resolveDiscordOwnerAllowFrom({
const { groupSystemPrompt, ownerAllowFrom, untrustedContext } = buildDiscordInboundAccessContext({
channelConfig: params.channelConfig,
guildInfo: params.guildInfo,
sender: params.sender,
allowNameMatching: params.allowNameMatching,
isGuild: params.isGuild,
channelTopic: params.channelTopic,
});
return finalizeInboundContext({
@@ -92,13 +66,8 @@ export function buildDiscordNativeCommandContext(params: BuildDiscordNativeComma
ChatType: params.isDirectMessage ? "direct" : params.isGroupDm ? "group" : "channel",
ConversationLabel: conversationLabel,
GroupSubject: params.isGuild ? params.guildName : undefined,
GroupSystemPrompt: params.isGuild
? buildDiscordNativeCommandSystemPrompt(params.channelConfig)
: undefined,
UntrustedContext: buildDiscordNativeCommandUntrustedContext({
isGuild: params.isGuild,
channelTopic: params.channelTopic,
}),
GroupSystemPrompt: groupSystemPrompt,
UntrustedContext: untrustedContext,
OwnerAllowFrom: ownerAllowFrom,
SenderName: params.user.globalName ?? params.user.username,
SenderId: params.user.id,