fix(discord): preserve channel session keys via channel_id fallbacks (#17622)

* fix(discord): preserve channel session keys via channel_id fallbacks

* docs(changelog): add discord session continuity note

* Tests: cover discord channel_id fallback

---------

Co-authored-by: Shadow <hi@shadowing.dev>
This commit is contained in:
Shakker
2026-02-16 02:30:17 +00:00
committed by GitHub
parent 39d5590230
commit 09566b1693
16 changed files with 235 additions and 49 deletions

View File

@@ -59,6 +59,7 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
client,
channelInfo,
channelName,
messageChannelId,
isGuildMessage,
isDirectMessage,
isGroupDm,
@@ -108,12 +109,12 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
}),
);
const ackReactionPromise = shouldAckReaction()
? reactMessageDiscord(message.channelId, message.id, ackReaction, {
? reactMessageDiscord(messageChannelId, message.id, ackReaction, {
rest: client.rest,
}).then(
() => true,
(err) => {
logVerbose(`discord react failed for channel ${message.channelId}: ${String(err)}`);
logVerbose(`discord react failed for channel ${messageChannelId}: ${String(err)}`);
return false;
},
)
@@ -123,8 +124,8 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
? buildDirectLabel(author)
: buildGuildLabel({
guild: data.guild ?? undefined,
channelName: channelName ?? message.channelId,
channelId: message.channelId,
channelName: channelName ?? messageChannelId,
channelId: messageChannelId,
});
const senderLabel = sender.label;
const isForumParent =
@@ -184,7 +185,7 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
if (shouldIncludeChannelHistory) {
combinedBody = buildPendingHistoryContextFromMap({
historyMap: guildHistories,
historyKey: message.channelId,
historyKey: messageChannelId,
limit: historyLimit,
currentMessage: combinedBody,
formatEntry: (entry) =>
@@ -192,7 +193,7 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
channel: "Discord",
from: fromLabel,
timestamp: entry.timestamp,
body: `${entry.body} [id:${entry.messageId ?? "unknown"} channel:${message.channelId}]`,
body: `${entry.body} [id:${entry.messageId ?? "unknown"} channel:${messageChannelId}]`,
chatType: "channel",
senderLabel: entry.sender,
envelope: envelopeOptions,
@@ -237,13 +238,14 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
const mediaPayload = buildDiscordMediaPayload(mediaList);
const threadKeys = resolveThreadSessionKeys({
baseSessionKey,
threadId: threadChannel ? message.channelId : undefined,
threadId: threadChannel ? messageChannelId : undefined,
parentSessionKey,
useSuffix: false,
});
const replyPlan = await resolveDiscordAutoThreadReplyPlan({
client,
message,
messageChannelId,
isGuildMessage,
channelConfig,
threadChannel,
@@ -260,7 +262,7 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
const effectiveFrom = isDirectMessage
? `discord:${author.id}`
: (autoThreadContext?.From ?? `discord:channel:${message.channelId}`);
: (autoThreadContext?.From ?? `discord:channel:${messageChannelId}`);
const effectiveTo = autoThreadContext?.To ?? replyTarget;
if (!effectiveTo) {
runtime.error?.(danger("discord: missing reply target"));
@@ -269,7 +271,7 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
const inboundHistory =
shouldIncludeChannelHistory && historyLimit > 0
? (guildHistories.get(message.channelId) ?? []).map((entry) => ({
? (guildHistories.get(messageChannelId) ?? []).map((entry) => ({
sender: entry.sender,
body: entry.body,
timestamp: entry.timestamp,
@@ -337,13 +339,13 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
if (shouldLogVerbose()) {
const preview = truncateUtf16Safe(combinedBody, 200).replace(/\n/g, "\\n");
logVerbose(
`discord inbound: channel=${message.channelId} deliver=${deliverTarget} from=${ctxPayload.From} preview="${preview}"`,
`discord inbound: channel=${messageChannelId} deliver=${deliverTarget} from=${ctxPayload.From} preview="${preview}"`,
);
}
const typingChannelId = deliverTarget.startsWith("channel:")
? deliverTarget.slice("channel:".length)
: message.channelId;
: messageChannelId;
const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
cfg,
@@ -412,7 +414,7 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
if (isGuildMessage) {
clearHistoryEntriesIfEnabled({
historyMap: guildHistories,
historyKey: message.channelId,
historyKey: messageChannelId,
limit: historyLimit,
});
}
@@ -429,7 +431,7 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
ackReactionPromise,
ackReactionValue: ackReaction,
remove: async () => {
await removeReactionDiscord(message.channelId, message.id, ackReaction, {
await removeReactionDiscord(messageChannelId, message.id, ackReaction, {
rest: client.rest,
});
},
@@ -437,7 +439,7 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
logAckFailure({
log: logVerbose,
channel: "discord",
target: `${message.channelId}/${message.id}`,
target: `${messageChannelId}/${message.id}`,
error: err,
});
},
@@ -445,7 +447,7 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
if (isGuildMessage) {
clearHistoryEntriesIfEnabled({
historyMap: guildHistories,
historyKey: message.channelId,
historyKey: messageChannelId,
limit: historyLimit,
});
}