fix(whatsapp): restore direct inbound metadata for relay agents (#31969)

* fix(whatsapp): restore direct inbound metadata for relay agents

* fix(auto-reply): use shared inbound channel resolver for direct metadata

* chore(ci): retrigger checks after base update

* fix: add changelog attribution for inbound metadata relay fix (#31969) (thanks @Lucenx9)

---------

Co-authored-by: Simone <simone@example.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
Lucenx9
2026-03-02 19:40:04 +01:00
committed by GitHub
parent 4030de6c73
commit 5c1eb071ca
3 changed files with 54 additions and 20 deletions

View File

@@ -111,9 +111,10 @@ describe("buildInboundUserContextPrefix", () => {
expect(text).toBe("");
});
it("hides message identifiers for direct chats", () => {
it("hides message identifiers for direct webchat chats", () => {
const text = buildInboundUserContextPrefix({
ChatType: "direct",
OriginatingChannel: "webchat",
MessageSid: "short-id",
MessageSidFull: "provider-full-id",
} as TemplateContext);
@@ -121,6 +122,33 @@ describe("buildInboundUserContextPrefix", () => {
expect(text).toBe("");
});
it("includes message identifiers for direct external-channel chats", () => {
const text = buildInboundUserContextPrefix({
ChatType: "direct",
OriginatingChannel: "whatsapp",
MessageSid: "short-id",
MessageSidFull: "provider-full-id",
SenderE164: " +15551234567 ",
} as TemplateContext);
const conversationInfo = parseConversationInfoPayload(text);
expect(conversationInfo["message_id"]).toBe("short-id");
expect(conversationInfo["message_id_full"]).toBeUndefined();
expect(conversationInfo["sender"]).toBe("+15551234567");
expect(conversationInfo["conversation_label"]).toBeUndefined();
});
it("includes message identifiers for direct chats when channel is inferred from Provider", () => {
const text = buildInboundUserContextPrefix({
ChatType: "direct",
Provider: "whatsapp",
MessageSid: "provider-only-id",
} as TemplateContext);
const conversationInfo = parseConversationInfoPayload(text);
expect(conversationInfo["message_id"]).toBe("provider-only-id");
});
it("does not treat group chats as direct based on sender id", () => {
const text = buildInboundUserContextPrefix({
ChatType: "group",

View File

@@ -31,6 +31,17 @@ function formatConversationTimestamp(value: unknown): string | undefined {
}
}
function resolveInboundChannel(ctx: TemplateContext): string | undefined {
let channelValue = safeTrim(ctx.OriginatingChannel) ?? safeTrim(ctx.Surface);
if (!channelValue) {
const provider = safeTrim(ctx.Provider);
if (provider !== "webchat" && ctx.Surface !== "webchat") {
channelValue = provider;
}
}
return channelValue;
}
export function buildInboundMetaSystemPrompt(ctx: TemplateContext): string {
const chatType = normalizeChatType(ctx.ChatType);
const isDirect = !chatType || chatType === "direct";
@@ -44,18 +55,7 @@ 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);
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") {
channelValue = provider;
}
// Otherwise leave channelValue undefined (no channel label)
}
const channelValue = resolveInboundChannel(ctx);
const payload = {
schema: "openclaw.inbound_meta.v1",
@@ -85,6 +85,11 @@ export function buildInboundUserContextPrefix(ctx: TemplateContext): string {
const blocks: string[] = [];
const chatType = normalizeChatType(ctx.ChatType);
const isDirect = !chatType || chatType === "direct";
const directChannelValue = resolveInboundChannel(ctx);
const includeDirectConversationInfo = Boolean(
directChannelValue && directChannelValue !== "webchat",
);
const shouldIncludeConversationInfo = !isDirect || includeDirectConversationInfo;
const messageId = safeTrim(ctx.MessageSid);
const messageIdFull = safeTrim(ctx.MessageSidFull);
@@ -92,16 +97,16 @@ export function buildInboundUserContextPrefix(ctx: TemplateContext): string {
const timestampStr = formatConversationTimestamp(ctx.Timestamp);
const conversationInfo = {
message_id: isDirect ? undefined : resolvedMessageId,
reply_to_id: isDirect ? undefined : safeTrim(ctx.ReplyToId),
sender_id: isDirect ? undefined : safeTrim(ctx.SenderId),
message_id: shouldIncludeConversationInfo ? resolvedMessageId : undefined,
reply_to_id: shouldIncludeConversationInfo ? safeTrim(ctx.ReplyToId) : undefined,
sender_id: shouldIncludeConversationInfo ? safeTrim(ctx.SenderId) : undefined,
conversation_label: isDirect ? undefined : safeTrim(ctx.ConversationLabel),
sender: isDirect
? undefined
: (safeTrim(ctx.SenderName) ??
sender: shouldIncludeConversationInfo
? (safeTrim(ctx.SenderName) ??
safeTrim(ctx.SenderE164) ??
safeTrim(ctx.SenderId) ??
safeTrim(ctx.SenderUsername)),
safeTrim(ctx.SenderUsername))
: undefined,
timestamp: timestampStr,
group_subject: safeTrim(ctx.GroupSubject),
group_channel: safeTrim(ctx.GroupChannel),