Skip to content

Commit f74436d

Browse files
committed
refactor: assemble channel contexts in core
1 parent 8cc1aee commit f74436d

2 files changed

Lines changed: 185 additions & 111 deletions

File tree

extensions/discord/src/monitor/message-handler.context.ts

Lines changed: 92 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import {
2+
buildChannelTurnContext,
23
formatInboundEnvelope,
34
resolveEnvelopeFormatOptions,
45
} from "openclaw/plugin-sdk/channel-inbound";
56
import { resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/context-visibility-runtime";
67
import { resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/conversation-runtime";
78
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
8-
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-dispatch-runtime";
99
import { createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
1010
import { buildAgentSessionKey, resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
1111
import { danger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
@@ -22,7 +22,6 @@ import {
2222
} from "./inbound-context.js";
2323
import type { DiscordMessagePreflightContext } from "./message-handler.preflight.js";
2424
import {
25-
buildDiscordMediaPayload,
2625
resolveReferencedReplyMediaList,
2726
resolveDiscordMessageText,
2827
type DiscordMediaInfo,
@@ -263,7 +262,6 @@ export async function buildDiscordMessageProcessContext(params: {
263262
parentSessionKey = undefined;
264263
}
265264
}
266-
const mediaPayload = buildDiscordMediaPayload(mediaListForContext);
267265
const preflightAudioIndex =
268266
preflightAudioTranscript === undefined
269267
? -1
@@ -329,59 +327,102 @@ export async function buildDiscordMessageProcessContext(params: {
329327
sessionKey: effectiveSessionKey,
330328
});
331329

332-
const ctxPayload = finalizeInboundContext({
333-
Body: combinedBody,
334-
BodyForAgent: preflightAudioTranscript ?? baseText ?? text,
335-
InboundHistory: inboundHistory,
336-
RawBody: preflightAudioTranscript ?? baseText,
337-
CommandBody: preflightAudioTranscript ?? baseText,
338-
...(preflightAudioTranscript !== undefined ? { Transcript: preflightAudioTranscript } : {}),
339-
From: effectiveFrom,
340-
To: effectiveTo,
341-
SessionKey: effectiveSessionKey,
342-
AccountId: route.accountId,
343-
ChatType: isDirectMessage ? "direct" : "channel",
344-
ConversationLabel: fromLabel,
345-
SenderName: senderName,
346-
SenderId: sender.id,
347-
SenderUsername: senderUsername,
348-
SenderTag: sender.tag,
349-
GroupSubject: groupSubject,
350-
GroupChannel: groupChannel,
351-
MemberRoleIds: memberRoleIds,
352-
UntrustedContext: untrustedContext,
353-
GroupSystemPrompt: isGuildMessage ? groupSystemPrompt : undefined,
354-
GroupSpace: isGuildMessage ? (guildInfo?.id ?? guildSlug) || undefined : undefined,
355-
OwnerAllowFrom: ownerAllowFrom,
356-
Provider: "discord" as const,
357-
Surface: "discord" as const,
358-
WasMentioned: ctx.effectiveWasMentioned,
359-
MessageSid: canonicalMessageId ?? message.id,
360-
...(canonicalMessageId && canonicalMessageId !== message.id
361-
? { MessageSidFull: message.id }
362-
: {}),
363-
ReplyToId: filteredReplyContext?.id,
364-
ReplyToBody: filteredReplyContext?.body,
365-
ReplyToSender: filteredReplyContext?.sender,
366-
ParentSessionKey: autoThreadContext?.ParentSessionKey ?? threadKeys.parentSessionKey,
367-
ModelParentSessionKey:
368-
autoThreadContext?.ModelParentSessionKey ?? modelParentSessionKey ?? undefined,
369-
MessageThreadId: threadChannel?.id ?? autoThreadContext?.createdThreadId ?? undefined,
370-
ThreadStarterBody: !effectivePreviousTimestamp ? threadStarterBody : undefined,
371-
ThreadLabel: threadLabel,
372-
Timestamp: resolveTimestampMs(message.timestamp),
373-
...mediaPayload,
374-
...(preflightAudioIndex >= 0 ? { MediaTranscribedIndexes: [preflightAudioIndex] } : {}),
375-
CommandAuthorized: commandAuthorized,
376-
CommandTurn: {
330+
const ctxPayload = buildChannelTurnContext({
331+
channel: "discord",
332+
provider: "discord",
333+
surface: "discord",
334+
accountId: route.accountId,
335+
messageId: canonicalMessageId ?? message.id,
336+
messageIdFull: canonicalMessageId && canonicalMessageId !== message.id ? message.id : undefined,
337+
timestamp: resolveTimestampMs(message.timestamp),
338+
from: effectiveFrom,
339+
sender: {
340+
id: sender.id,
341+
name: senderName,
342+
username: senderUsername,
343+
tag: sender.tag,
344+
roles: memberRoleIds,
345+
displayLabel: senderLabel,
346+
},
347+
conversation: {
348+
kind: isDirectMessage ? "direct" : "channel",
349+
id: messageChannelId,
350+
label: fromLabel,
351+
spaceId: isGuildMessage ? (guildInfo?.id ?? guildSlug) || undefined : undefined,
352+
threadId: threadChannel?.id ?? autoThreadContext?.createdThreadId ?? undefined,
353+
routePeer: {
354+
kind: isDirectMessage ? "direct" : "channel",
355+
id: isDirectMessage ? author.id : messageChannelId,
356+
},
357+
},
358+
route: {
359+
agentId: route.agentId,
360+
accountId: route.accountId,
361+
routeSessionKey: route.sessionKey,
362+
dispatchSessionKey: effectiveSessionKey,
363+
parentSessionKey: autoThreadContext?.ParentSessionKey ?? threadKeys.parentSessionKey,
364+
modelParentSessionKey:
365+
autoThreadContext?.ModelParentSessionKey ?? modelParentSessionKey ?? undefined,
366+
},
367+
reply: {
368+
to: effectiveTo,
369+
originatingTo,
370+
},
371+
message: {
372+
body: combinedBody,
373+
rawBody: preflightAudioTranscript ?? baseText,
374+
bodyForAgent: preflightAudioTranscript ?? baseText ?? text,
375+
commandBody: preflightAudioTranscript ?? baseText,
376+
envelopeFrom: fromLabel,
377+
inboundHistory,
378+
},
379+
access: {
380+
mentions: {
381+
canDetectMention: ctx.canDetectMention,
382+
wasMentioned: ctx.effectiveWasMentioned,
383+
hasAnyMention: ctx.hasAnyMention,
384+
requireMention: ctx.shouldRequireMention,
385+
effectiveWasMentioned: ctx.effectiveWasMentioned,
386+
},
387+
commands: {
388+
authorized: commandAuthorized,
389+
allowTextCommands: ctx.allowTextCommands,
390+
useAccessGroups: false,
391+
authorizers: [],
392+
},
393+
},
394+
commandTurn: {
377395
kind: "text-slash" as const,
378396
source: "text" as const,
379397
authorized: commandAuthorized,
380398
body: preflightAudioTranscript ?? baseText,
381399
},
382-
CommandSource: "text" as const,
383-
OriginatingChannel: "discord" as const,
384-
OriginatingTo: originatingTo,
400+
media: mediaListForContext.map((media, index) => ({
401+
path: media.path,
402+
contentType: media.contentType,
403+
transcribed: index === preflightAudioIndex,
404+
})),
405+
supplemental: {
406+
quote: filteredReplyContext
407+
? {
408+
id: filteredReplyContext.id,
409+
body: filteredReplyContext.body,
410+
sender: filteredReplyContext.sender,
411+
}
412+
: undefined,
413+
thread: {
414+
starterBody: !effectivePreviousTimestamp ? threadStarterBody : undefined,
415+
label: threadLabel,
416+
},
417+
groupSystemPrompt: isGuildMessage ? groupSystemPrompt : undefined,
418+
},
419+
extra: {
420+
...(preflightAudioTranscript !== undefined ? { Transcript: preflightAudioTranscript } : {}),
421+
GroupSubject: groupSubject,
422+
GroupChannel: groupChannel,
423+
UntrustedContext: untrustedContext,
424+
OwnerAllowFrom: ownerAllowFrom,
425+
},
385426
});
386427
const persistedSessionKey = ctxPayload.SessionKey ?? route.sessionKey;
387428

extensions/slack/src/monitor/message-handler/prepare.ts

Lines changed: 93 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
type AckReactionScope,
55
} from "openclaw/plugin-sdk/channel-feedback";
66
import {
7+
buildChannelTurnContext,
78
buildMentionRegexes,
89
formatInboundEnvelope,
910
implicitMentionKindWhen,
@@ -18,7 +19,6 @@ import { ensureConfiguredBindingRouteReady } from "openclaw/plugin-sdk/conversat
1819
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
1920
import { recordDroppedChannelTurnHistory } from "openclaw/plugin-sdk/inbound-reply-dispatch";
2021
import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
21-
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-dispatch-runtime";
2222
import { createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
2323
import type { FinalizedMsgContext } from "openclaw/plugin-sdk/reply-runtime";
2424
import { resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
@@ -1056,8 +1056,6 @@ export async function prepareSlackMessage(params: {
10561056

10571057
// Use direct media (including forwarded attachment media) if available, else thread starter media
10581058
const effectiveMedia = effectiveDirectMedia ?? threadStarterMedia;
1059-
const firstMedia = effectiveMedia?.[0];
1060-
10611059
const inboundHistory =
10621060
isRoomish && ctx.historyLimit > 0
10631061
? channelHistory.buildInboundHistory({
@@ -1067,63 +1065,98 @@ export async function prepareSlackMessage(params: {
10671065
: dmHistoryContext.inboundHistory;
10681066
const commandBody = textForCommandDetection.trim();
10691067

1070-
const ctxPayload = finalizeInboundContext({
1071-
Body: combinedBody,
1072-
BodyForAgent: rawBody,
1073-
InboundHistory: inboundHistory,
1074-
RawBody: rawBody,
1075-
CommandBody: commandBody,
1076-
BodyForCommands: commandBody,
1077-
From: slackFrom,
1078-
To: slackTo,
1079-
SessionKey: sessionKey,
1080-
AccountId: route.accountId,
1081-
ChatType: chatType,
1082-
ConversationLabel: envelopeFrom,
1083-
GroupSubject: isRoomish ? roomLabel : undefined,
1084-
GroupSpace: ctx.teamId || undefined,
1085-
GroupSystemPrompt: groupSystemPrompt,
1086-
UntrustedContext: untrustedChannelMetadata ? [untrustedChannelMetadata] : undefined,
1087-
SenderName: senderName,
1088-
SenderId: senderId,
1089-
Provider: "slack" as const,
1090-
Surface: "slack" as const,
1091-
MessageSid: message.ts,
1092-
ReplyToId: threadContext.replyToId,
1093-
// Preserve thread context for routed tool notifications.
1094-
MessageThreadId: threadContext.messageThreadId,
1095-
ParentSessionKey: threadKeys.parentSessionKey,
1096-
// Only include thread starter body for NEW sessions (existing sessions already have it in their transcript)
1097-
ThreadStarterBody: !threadSessionPreviousTimestamp ? threadStarterBody : undefined,
1098-
ThreadHistoryBody: threadHistoryBody,
1099-
IsFirstThreadTurn:
1100-
isThreadReply && threadTs && !threadSessionPreviousTimestamp ? true : undefined,
1101-
ThreadLabel: threadLabel,
1102-
Timestamp: message.ts ? Math.round(Number(message.ts) * 1000) : undefined,
1103-
...buildSlackMentionContextPayload({
1104-
isRoomish,
1105-
effectiveWasMentioned,
1106-
explicitlyMentioned,
1107-
mentionedUserIds,
1108-
mentionedSubteamIds,
1109-
matchedImplicitMentionKinds,
1110-
mentionSource,
1111-
}),
1112-
MediaPath: firstMedia?.path,
1113-
MediaType: firstMedia?.contentType,
1114-
MediaUrl: firstMedia?.path,
1115-
MediaPaths:
1116-
effectiveMedia && effectiveMedia.length > 0 ? effectiveMedia.map((m) => m.path) : undefined,
1117-
MediaUrls:
1118-
effectiveMedia && effectiveMedia.length > 0 ? effectiveMedia.map((m) => m.path) : undefined,
1119-
MediaTypes:
1120-
effectiveMedia && effectiveMedia.length > 0
1121-
? effectiveMedia.map((m) => m.contentType ?? "")
1122-
: undefined,
1123-
CommandAuthorized: commandAuthorized,
1124-
OriginatingChannel: "slack" as const,
1125-
OriginatingTo: slackTo,
1126-
NativeChannelId: message.channel,
1068+
const ctxPayload = buildChannelTurnContext({
1069+
channel: "slack",
1070+
provider: "slack",
1071+
surface: "slack",
1072+
accountId: route.accountId,
1073+
messageId: message.ts,
1074+
timestamp: message.ts ? Math.round(Number(message.ts) * 1000) : undefined,
1075+
from: slackFrom,
1076+
sender: {
1077+
id: senderId,
1078+
name: senderName,
1079+
displayLabel: senderName,
1080+
},
1081+
conversation: {
1082+
kind: chatType,
1083+
id: message.channel,
1084+
label: envelopeFrom,
1085+
spaceId: ctx.teamId || undefined,
1086+
threadId: threadContext.messageThreadId,
1087+
nativeChannelId: message.channel,
1088+
routePeer: {
1089+
kind: chatType,
1090+
id: message.channel,
1091+
},
1092+
},
1093+
route: {
1094+
agentId: route.agentId,
1095+
accountId: route.accountId,
1096+
routeSessionKey: sessionKey,
1097+
parentSessionKey: threadKeys.parentSessionKey,
1098+
},
1099+
reply: {
1100+
to: slackTo,
1101+
originatingTo: slackTo,
1102+
replyToId: threadContext.replyToId,
1103+
messageThreadId: threadContext.messageThreadId,
1104+
nativeChannelId: message.channel,
1105+
},
1106+
message: {
1107+
body: combinedBody,
1108+
bodyForAgent: rawBody,
1109+
rawBody,
1110+
commandBody,
1111+
envelopeFrom,
1112+
inboundHistory,
1113+
},
1114+
access: {
1115+
mentions: {
1116+
canDetectMention: isRoomish,
1117+
wasMentioned: effectiveWasMentioned,
1118+
hasAnyMention: explicitlyMentioned || mentionedSubteamIds.length > 0,
1119+
implicitMentionKinds: matchedImplicitMentionKinds as Array<
1120+
"reply_to_bot" | "quoted_bot" | "bot_thread_participant" | "native"
1121+
>,
1122+
requireMention: shouldRequireMention,
1123+
effectiveWasMentioned,
1124+
},
1125+
commands: {
1126+
authorized: commandAuthorized,
1127+
allowTextCommands,
1128+
useAccessGroups: false,
1129+
authorizers: [],
1130+
},
1131+
},
1132+
media: effectiveMedia?.map((media) => ({
1133+
path: media.path,
1134+
contentType: media.contentType,
1135+
})),
1136+
supplemental: {
1137+
thread: {
1138+
// Only include thread starter body for NEW sessions (existing sessions already have it in their transcript)
1139+
starterBody: !threadSessionPreviousTimestamp ? threadStarterBody : undefined,
1140+
historyBody: threadHistoryBody,
1141+
label: threadLabel,
1142+
},
1143+
groupSystemPrompt,
1144+
},
1145+
extra: {
1146+
GroupSubject: isRoomish ? roomLabel : undefined,
1147+
UntrustedContext: untrustedChannelMetadata ? [untrustedChannelMetadata] : undefined,
1148+
IsFirstThreadTurn:
1149+
isThreadReply && threadTs && !threadSessionPreviousTimestamp ? true : undefined,
1150+
...buildSlackMentionContextPayload({
1151+
isRoomish,
1152+
effectiveWasMentioned,
1153+
explicitlyMentioned,
1154+
mentionedUserIds,
1155+
mentionedSubteamIds,
1156+
matchedImplicitMentionKinds,
1157+
mentionSource,
1158+
}),
1159+
},
11271160
}) satisfies FinalizedMsgContext;
11281161

11291162
if (isRoomish && !shouldRequireMention) {

0 commit comments

Comments
 (0)