mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 11:08:37 +00:00
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:
@@ -55,6 +55,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Doctor/local memory provider checks: stop false-positive local-provider warnings when `provider=local` and no explicit `modelPath` is set by honoring default local model fallback while still warning when gateway probe reports local embeddings not ready. (#32014) Fixes #31998. Thanks @adhishthite.
|
- Doctor/local memory provider checks: stop false-positive local-provider warnings when `provider=local` and no explicit `modelPath` is set by honoring default local model fallback while still warning when gateway probe reports local embeddings not ready. (#32014) Fixes #31998. Thanks @adhishthite.
|
||||||
- Feishu/Run channel fallback: prefer `Provider` over `Surface` when inferring queued run `messageProvider` fallback (when `OriginatingChannel` is missing), preventing Feishu turns from being mislabeled as `webchat` in mixed relay metadata contexts. (#31880) Fixes #31859. Thanks @liuxiaopai-ai.
|
- Feishu/Run channel fallback: prefer `Provider` over `Surface` when inferring queued run `messageProvider` fallback (when `OriginatingChannel` is missing), preventing Feishu turns from being mislabeled as `webchat` in mixed relay metadata contexts. (#31880) Fixes #31859. Thanks @liuxiaopai-ai.
|
||||||
- Cron/session reaper reliability: move cron session reaper sweeps into `onTimer` `finally` and keep pruning active even when timer ticks fail early (for example cron store parse failures), preventing stale isolated run sessions from accumulating indefinitely. (#31996) Fixes #31946. Thanks @scoootscooob.
|
- Cron/session reaper reliability: move cron session reaper sweeps into `onTimer` `finally` and keep pruning active even when timer ticks fail early (for example cron store parse failures), preventing stale isolated run sessions from accumulating indefinitely. (#31996) Fixes #31946. Thanks @scoootscooob.
|
||||||
|
- Inbound metadata/direct relay context: restore direct-channel conversation metadata blocks for external channels (for example WhatsApp) while preserving webchat-direct suppression, so relay agents recover sender/message identifiers without reintroducing internal webchat metadata noise. (#31969) Fixes #29972. Thanks @Lucenx9.
|
||||||
- Sandbox/Docker setup command parsing: accept `agents.*.sandbox.docker.setupCommand` as either a string or a string array, and normalize arrays to newline-delimited shell scripts so multi-step setup commands no longer concatenate without separators. (#31953) Thanks @liuxiaopai-ai.
|
- Sandbox/Docker setup command parsing: accept `agents.*.sandbox.docker.setupCommand` as either a string or a string array, and normalize arrays to newline-delimited shell scripts so multi-step setup commands no longer concatenate without separators. (#31953) Thanks @liuxiaopai-ai.
|
||||||
- Gateway/Plugin HTTP route precedence: run explicit plugin HTTP routes before the Control UI SPA catch-all so registered plugin webhook/custom paths remain reachable, while unmatched paths still fall through to Control UI handling. (#31885) Thanks @Sid-Qin.
|
- Gateway/Plugin HTTP route precedence: run explicit plugin HTTP routes before the Control UI SPA catch-all so registered plugin webhook/custom paths remain reachable, while unmatched paths still fall through to Control UI handling. (#31885) Thanks @Sid-Qin.
|
||||||
- macOS/LaunchAgent security defaults: write `Umask=63` (octal `077`) into generated gateway launchd plists so post-update service reinstalls keep owner-only file permissions by default instead of falling back to system `022`. (#32022) Fixes #31905. Thanks @liuxiaopai-ai.
|
- macOS/LaunchAgent security defaults: write `Umask=63` (octal `077`) into generated gateway launchd plists so post-update service reinstalls keep owner-only file permissions by default instead of falling back to system `022`. (#32022) Fixes #31905. Thanks @liuxiaopai-ai.
|
||||||
|
|||||||
@@ -111,9 +111,10 @@ describe("buildInboundUserContextPrefix", () => {
|
|||||||
expect(text).toBe("");
|
expect(text).toBe("");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("hides message identifiers for direct chats", () => {
|
it("hides message identifiers for direct webchat chats", () => {
|
||||||
const text = buildInboundUserContextPrefix({
|
const text = buildInboundUserContextPrefix({
|
||||||
ChatType: "direct",
|
ChatType: "direct",
|
||||||
|
OriginatingChannel: "webchat",
|
||||||
MessageSid: "short-id",
|
MessageSid: "short-id",
|
||||||
MessageSidFull: "provider-full-id",
|
MessageSidFull: "provider-full-id",
|
||||||
} as TemplateContext);
|
} as TemplateContext);
|
||||||
@@ -121,6 +122,33 @@ describe("buildInboundUserContextPrefix", () => {
|
|||||||
expect(text).toBe("");
|
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", () => {
|
it("does not treat group chats as direct based on sender id", () => {
|
||||||
const text = buildInboundUserContextPrefix({
|
const text = buildInboundUserContextPrefix({
|
||||||
ChatType: "group",
|
ChatType: "group",
|
||||||
|
|||||||
@@ -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 {
|
export function buildInboundMetaSystemPrompt(ctx: TemplateContext): string {
|
||||||
const chatType = normalizeChatType(ctx.ChatType);
|
const chatType = normalizeChatType(ctx.ChatType);
|
||||||
const isDirect = !chatType || chatType === "direct";
|
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.
|
// 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),
|
// 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.
|
// omit the channel field entirely rather than falling back to an unrelated provider.
|
||||||
let channelValue = safeTrim(ctx.OriginatingChannel) ?? safeTrim(ctx.Surface);
|
const channelValue = resolveInboundChannel(ctx);
|
||||||
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 payload = {
|
const payload = {
|
||||||
schema: "openclaw.inbound_meta.v1",
|
schema: "openclaw.inbound_meta.v1",
|
||||||
@@ -85,6 +85,11 @@ export function buildInboundUserContextPrefix(ctx: TemplateContext): string {
|
|||||||
const blocks: string[] = [];
|
const blocks: string[] = [];
|
||||||
const chatType = normalizeChatType(ctx.ChatType);
|
const chatType = normalizeChatType(ctx.ChatType);
|
||||||
const isDirect = !chatType || chatType === "direct";
|
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 messageId = safeTrim(ctx.MessageSid);
|
||||||
const messageIdFull = safeTrim(ctx.MessageSidFull);
|
const messageIdFull = safeTrim(ctx.MessageSidFull);
|
||||||
@@ -92,16 +97,16 @@ export function buildInboundUserContextPrefix(ctx: TemplateContext): string {
|
|||||||
const timestampStr = formatConversationTimestamp(ctx.Timestamp);
|
const timestampStr = formatConversationTimestamp(ctx.Timestamp);
|
||||||
|
|
||||||
const conversationInfo = {
|
const conversationInfo = {
|
||||||
message_id: isDirect ? undefined : resolvedMessageId,
|
message_id: shouldIncludeConversationInfo ? resolvedMessageId : undefined,
|
||||||
reply_to_id: isDirect ? undefined : safeTrim(ctx.ReplyToId),
|
reply_to_id: shouldIncludeConversationInfo ? safeTrim(ctx.ReplyToId) : undefined,
|
||||||
sender_id: isDirect ? undefined : safeTrim(ctx.SenderId),
|
sender_id: shouldIncludeConversationInfo ? safeTrim(ctx.SenderId) : undefined,
|
||||||
conversation_label: isDirect ? undefined : safeTrim(ctx.ConversationLabel),
|
conversation_label: isDirect ? undefined : safeTrim(ctx.ConversationLabel),
|
||||||
sender: isDirect
|
sender: shouldIncludeConversationInfo
|
||||||
? undefined
|
? (safeTrim(ctx.SenderName) ??
|
||||||
: (safeTrim(ctx.SenderName) ??
|
|
||||||
safeTrim(ctx.SenderE164) ??
|
safeTrim(ctx.SenderE164) ??
|
||||||
safeTrim(ctx.SenderId) ??
|
safeTrim(ctx.SenderId) ??
|
||||||
safeTrim(ctx.SenderUsername)),
|
safeTrim(ctx.SenderUsername))
|
||||||
|
: undefined,
|
||||||
timestamp: timestampStr,
|
timestamp: timestampStr,
|
||||||
group_subject: safeTrim(ctx.GroupSubject),
|
group_subject: safeTrim(ctx.GroupSubject),
|
||||||
group_channel: safeTrim(ctx.GroupChannel),
|
group_channel: safeTrim(ctx.GroupChannel),
|
||||||
|
|||||||
Reference in New Issue
Block a user