From 692db289204b625504f3148b3f52784b046c4c76 Mon Sep 17 00:00:00 2001 From: Saurabh Chopda Date: Fri, 30 Jan 2026 19:59:06 +0530 Subject: [PATCH] fix(sessions): prevent displayName overwrite on outbound sends (#4683) --- CHANGELOG.md | 1 + src/config/sessions/metadata.test.ts | 29 +++++++++++++++++++++++ src/config/sessions/metadata.ts | 32 ++++++++++++++++++-------- src/config/sessions/store.ts | 3 +++ src/infra/outbound/outbound-session.ts | 3 +++ 5 files changed, 58 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 191c2172d..56a557cd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Docs: https://docs.openclaw.ai Status: stable. ### Changes +- Sessions: fix displayName being overwritten by outbound message recipient instead of session owner. (#4683) - Rebrand: rename the npm package/CLI to `openclaw`, add a `openclaw` compatibility shim, and move extensions to the `@openclaw/*` scope. - Onboarding: strengthen security warning copy for beta + access control expectations. - Onboarding: add Venice API key to non-interactive flow. (#1893) Thanks @jonisjongithub. diff --git a/src/config/sessions/metadata.test.ts b/src/config/sessions/metadata.test.ts index c532b862b..826e4f7bb 100644 --- a/src/config/sessions/metadata.test.ts +++ b/src/config/sessions/metadata.test.ts @@ -20,4 +20,33 @@ describe("deriveSessionMetaPatch", () => { expect(patch?.channel).toBe("whatsapp"); expect(patch?.groupId).toBe("123@g.us"); }); + + it("skips displayName when skipDisplayName is true", () => { + const patchWithDisplayName = deriveSessionMetaPatch({ + ctx: { + Provider: "whatsapp", + ChatType: "group", + GroupSubject: "Family", + From: "123@g.us", + }, + sessionKey: "agent:main:whatsapp:group:123@g.us", + }); + expect(patchWithDisplayName?.displayName).toBeDefined(); + + const patchWithoutDisplayName = deriveSessionMetaPatch({ + ctx: { + Provider: "whatsapp", + ChatType: "group", + GroupSubject: "Family", + From: "123@g.us", + }, + sessionKey: "agent:main:whatsapp:group:123@g.us", + skipDisplayName: true, + }); + expect(patchWithoutDisplayName?.displayName).toBeUndefined(); + // Other fields should still be present + expect(patchWithoutDisplayName?.subject).toBe("Family"); + expect(patchWithoutDisplayName?.channel).toBe("whatsapp"); + expect(patchWithoutDisplayName?.groupId).toBe("123@g.us"); + }); }); diff --git a/src/config/sessions/metadata.ts b/src/config/sessions/metadata.ts index 1b70177d4..9979fc692 100644 --- a/src/config/sessions/metadata.ts +++ b/src/config/sessions/metadata.ts @@ -62,6 +62,8 @@ export function deriveGroupSessionPatch(params: { sessionKey: string; existing?: SessionEntry; groupResolution?: GroupKeyResolution | null; + /** Skip displayName derivation (e.g., for outbound session entries). */ + skipDisplayName?: boolean; }): Partial | null { const resolution = params.groupResolution ?? resolveGroupSessionKey(params.ctx); if (!resolution?.channel) return null; @@ -91,15 +93,17 @@ export function deriveGroupSessionPatch(params: { if (nextGroupChannel) patch.groupChannel = nextGroupChannel; if (space) patch.space = space; - const displayName = buildGroupDisplayName({ - provider: channel, - subject: nextSubject ?? params.existing?.subject, - groupChannel: nextGroupChannel ?? params.existing?.groupChannel, - space: space ?? params.existing?.space, - id: resolution.id, - key: params.sessionKey, - }); - if (displayName) patch.displayName = displayName; + if (!params.skipDisplayName) { + const displayName = buildGroupDisplayName({ + provider: channel, + subject: nextSubject ?? params.existing?.subject, + groupChannel: nextGroupChannel ?? params.existing?.groupChannel, + space: space ?? params.existing?.space, + id: resolution.id, + key: params.sessionKey, + }); + if (displayName) patch.displayName = displayName; + } return patch; } @@ -109,8 +113,16 @@ export function deriveSessionMetaPatch(params: { sessionKey: string; existing?: SessionEntry; groupResolution?: GroupKeyResolution | null; + /** Skip displayName derivation (e.g., for outbound session entries). */ + skipDisplayName?: boolean; }): Partial | null { - const groupPatch = deriveGroupSessionPatch(params); + const groupPatch = deriveGroupSessionPatch({ + ctx: params.ctx, + sessionKey: params.sessionKey, + existing: params.existing, + groupResolution: params.groupResolution, + skipDisplayName: params.skipDisplayName, + }); const origin = deriveSessionOrigin(params.ctx); if (!groupPatch && !origin) return null; diff --git a/src/config/sessions/store.ts b/src/config/sessions/store.ts index 0f36e0ebb..9676ad670 100644 --- a/src/config/sessions/store.ts +++ b/src/config/sessions/store.ts @@ -361,6 +361,8 @@ export async function recordSessionMetaFromInbound(params: { ctx: MsgContext; groupResolution?: import("./types.js").GroupKeyResolution | null; createIfMissing?: boolean; + /** Skip displayName derivation (e.g., for outbound session entries). */ + skipDisplayName?: boolean; }): Promise { const { storePath, sessionKey, ctx } = params; const createIfMissing = params.createIfMissing ?? true; @@ -371,6 +373,7 @@ export async function recordSessionMetaFromInbound(params: { sessionKey, existing, groupResolution: params.groupResolution, + skipDisplayName: params.skipDisplayName, }); if (!patch) return existing ?? null; if (!existing && !createIfMissing) return null; diff --git a/src/infra/outbound/outbound-session.ts b/src/infra/outbound/outbound-session.ts index 79a129575..ba00e1b3f 100644 --- a/src/infra/outbound/outbound-session.ts +++ b/src/infra/outbound/outbound-session.ts @@ -850,6 +850,9 @@ export async function ensureOutboundSessionEntry(params: { storePath, sessionKey: params.route.sessionKey, ctx, + // Outbound sends should not update displayName since it represents the + // session owner (inbound initiator), not the last outbound recipient. + skipDisplayName: true, }); } catch { // Do not block outbound sends on session meta writes.