diff --git a/CHANGELOG.md b/CHANGELOG.md index 03607cd2dd3..4d2763cf34b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ Docs: https://docs.openclaw.ai - Plugins/CLI: redact authenticated git URLs from git install command failure details, so failed clone or checkout output cannot leak credentials during plugin installs. Thanks @vincentkoc. - Channels/status reactions: remove stale non-terminal lifecycle reactions when a run reaches done or error, so Discord does not leave a permanent thinking emoji after completion. Fixes #75458. Thanks @davelutztx. - Discord/doctor: migrate unsupported per-channel `agentId` entries under guild channel config into top-level `bindings[]` routes, so `openclaw doctor --fix` preserves the intended agent route instead of stripping it as an unknown key. Fixes #62455. Thanks @lobster-biscuit. +- Discord/DMs: set inbound direct-message `ctx.To` to the semantic `user:` target while keeping delivery routed through the DM channel, so mirror and recovery paths do not treat DMs as channel conversations. Fixes #68126. Thanks @illuminate0623. - Gateway/config: log config health-state write failures instead of silently hiding config observe-recovery write errors. Thanks @sallyom. - Diagnostics: reset stuck-session timers on reply, tool, status, block, and ACP progress events, and back off repeated `session.stuck` diagnostics while a session remains unchanged. Supersedes #72010. Thanks @rubencu. diff --git a/extensions/discord/src/monitor/message-handler.context.ts b/extensions/discord/src/monitor/message-handler.context.ts index 6adc02e21bd..9456dc0f08c 100644 --- a/extensions/discord/src/monitor/message-handler.context.ts +++ b/extensions/discord/src/monitor/message-handler.context.ts @@ -274,17 +274,17 @@ export async function buildDiscordMessageProcessContext(params: { const effectiveFrom = isDirectMessage ? `discord:${author.id}` : (autoThreadContext?.From ?? `discord:channel:${messageChannelId}`); - const effectiveTo = autoThreadContext?.To ?? replyTarget; - if (!effectiveTo) { - runtime.error?.(danger("discord: missing reply target")); - return null; - } const dmConversationTarget = isDirectMessage ? resolveDiscordConversationIdentity({ isDirectMessage, userId: author.id, }) : undefined; + const effectiveTo = autoThreadContext?.To ?? dmConversationTarget ?? replyTarget; + if (!effectiveTo) { + runtime.error?.(danger("discord: missing reply target")); + return null; + } const lastRouteTo = dmConversationTarget ?? effectiveTo; const inboundHistory = shouldIncludeChannelHistory && historyLimit > 0 diff --git a/extensions/discord/src/monitor/message-handler.process.test.ts b/extensions/discord/src/monitor/message-handler.process.test.ts index 861ee864945..1964f2922c3 100644 --- a/extensions/discord/src/monitor/message-handler.process.test.ts +++ b/extensions/discord/src/monitor/message-handler.process.test.ts @@ -353,12 +353,16 @@ function getLastRouteUpdate(): function getLastDispatchCtx(): | { BodyForAgent?: string; + ChatType?: string; CommandBody?: string; + From?: string; MediaTranscribedIndexes?: number[]; MessageThreadId?: string | number; ModelParentSessionKey?: string; + OriginatingTo?: string; ParentSessionKey?: string; SessionKey?: string; + To?: string; Transcript?: string; } | undefined { @@ -367,12 +371,16 @@ function getLastDispatchCtx(): | { ctx?: { BodyForAgent?: string; + ChatType?: string; CommandBody?: string; + From?: string; MediaTranscribedIndexes?: number[]; MessageThreadId?: string | number; ModelParentSessionKey?: string; + OriginatingTo?: string; ParentSessionKey?: string; SessionKey?: string; + To?: string; Transcript?: string; }; } @@ -808,6 +816,13 @@ describe("processDiscordMessage session routing", () => { to: "user:U1", accountId: "default", }); + expect(getLastDispatchCtx()).toMatchObject({ + ChatType: "direct", + From: "discord:U1", + To: "user:U1", + OriginatingTo: "user:U1", + SessionKey: "agent:main:discord:direct:u1", + }); }); it("pins Discord text DM main-route updates to the single configured DM owner", async () => {