From a0b280541c86ef3297ec586d1e3d9340339e3669 Mon Sep 17 00:00:00 2001 From: hzhang Date: Mon, 8 Jun 2026 12:40:25 +0100 Subject: [PATCH] fix(routing): map Fabric DM to a per-channel group session MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A Fabric DM (xType='dm') was routed as peerKind='direct', which openclaw resolves through session.dmScope (default "main") — collapsing EVERY DM into the agent's single agent::main session. Fabric supports many independent DM channels for the same user pair, so each must open its own openclaw session; otherwise they pile into :main and share context (a recruiter DM'd on channel A sees channel B's history and never opens a fresh linear journal per DM). Route 'dm' as peerKind='group' (session key agent::fabric:group:, one per DM channel) while keeping chatType='direct' for 1:1 semantics. The no-wakeup-gating DM exception keys off xType==='dm', not chatType, so it's unaffected. Inbound + outbound (incl. cold-cache fallback) both resolve to 'group', so sessions never split. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/channel.ts | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/channel.ts b/src/channel.ts index c83ed5b..1576efe 100644 --- a/src/channel.ts +++ b/src/channel.ts @@ -27,27 +27,31 @@ import { getChannelType } from './channel-meta.js'; * Map a Fabric channel xType to an openclaw routing peer.kind / ChatType. * * Fabric distinguishes channels by xType ('dm' | 'triage' | 'group' | - * 'broadcast' | 'announce' | ...). Openclaw's session router only knows - * 'direct' | 'group' | 'channel'. We collapse: - * - 'dm' → 'direct' (1:1 conversation; agent always speaks) - * - rest → 'group' (multi-party; turn-engine gates speech) + * 'broadcast' | 'announce' | ...). This maps each to: + * - peerKind — openclaw's SESSION router key ('direct' | 'group') + * - chatType — ctx.ChatType the agent sees (drives isDirectMessage etc.) * - * Sessions are keyed by peer.kind, so inbound and outbound MUST agree — - * otherwise the agent's outbound message lands in a different session - * than the inbound that triggered it and conversation state splits. + * CRITICAL: peerKind for 'dm' is 'group', NOT 'direct'. Openclaw routes + * peer.kind==='direct' through `session.dmScope` (default "main"), which + * collapses EVERY direct conversation into the agent's single `:main` + * session. Fabric, however, supports many independent DM channels for the + * same pair of users — each must get its own openclaw session, or they all + * pile into `:main` and share context (a recruiter DM'd on channel A would + * see channel B's history, and never opens a fresh linear journal per DM). + * Routing 'dm' as peerKind='group' keys the session by channelId + * (`agent::fabric:group:`) — one session per DM channel — while + * chatType='direct' preserves 1:1 semantics (agent always speaks; the + * inbound no-wakeup-gating exception keys off xType==='dm', not chatType). * - * Outbound has no live xType (the agent target is just a channelId), so - * it consults the channel-meta cache populated by inbound. Cache miss - * (channel never observed) falls back to 'group' — same as the pre-fix - * behavior, no regression on cold cache. The proactive-DM-first-message - * edge case (agent DMs a channel before any inbound) still lands as - * 'group' on that one outbound; the next inbound + outbound pair will - * agree on 'direct'. + * Sessions are keyed by peerKind, so inbound and outbound MUST agree. + * Both now resolve 'dm' (and the cold-cache fallback) to 'group', so they + * never split. */ export type FabricPeerRouting = { peerKind: 'direct' | 'group'; chatType: 'direct' | 'group' }; export function fabricPeerRoutingForXType(xType: string | null | undefined): FabricPeerRouting { - if (xType === 'dm') return { peerKind: 'direct', chatType: 'direct' }; + // dm → group session routing (per-channel), but direct chat semantics. + if (xType === 'dm') return { peerKind: 'group', chatType: 'direct' }; return { peerKind: 'group', chatType: 'group' }; }