Compare commits
2 Commits
feat/expos
...
fix/fabric
| Author | SHA1 | Date | |
|---|---|---|---|
| b8e0e424fa | |||
| 81a10f2a1f |
18
dist/fabric/src/channel.js
vendored
18
dist/fabric/src/channel.js
vendored
@@ -11,6 +11,15 @@
|
||||
import { createChatChannelPlugin, createChannelPluginBase, buildChannelOutboundSessionRoute, } from 'openclaw/plugin-sdk/core';
|
||||
import { FabricClient } from './fabric-client.js';
|
||||
import { listFabricAccountIds, resolveFabricAccount, resolveDefaultFabricAccountId, } from './accounts.js';
|
||||
import { getChannelType } from './channel-meta.js';
|
||||
export function fabricPeerRoutingForXType(xType) {
|
||||
if (xType === 'dm')
|
||||
return { peerKind: 'direct', chatType: 'direct' };
|
||||
return { peerKind: 'group', chatType: 'group' };
|
||||
}
|
||||
export function fabricPeerRoutingForChannel(channelId) {
|
||||
return fabricPeerRoutingForXType(getChannelType(channelId));
|
||||
}
|
||||
// ---- target grammar: fabric:<channelId> ----
|
||||
export function stripFabricTargetPrefix(raw) {
|
||||
let s = (raw ?? '').trim();
|
||||
@@ -38,13 +47,18 @@ export function resolveFabricOutboundSessionRoute(params) {
|
||||
const id = stripFabricTargetPrefix(params.target);
|
||||
if (!id)
|
||||
return null;
|
||||
// Consult the channel-meta cache populated by inbound — DM channels
|
||||
// need peer.kind='direct' so the outbound session key matches the
|
||||
// inbound one. Cache miss falls back to 'group' (the pre-fix default,
|
||||
// no regression on cold cache).
|
||||
const { peerKind, chatType } = fabricPeerRoutingForChannel(id);
|
||||
return buildChannelOutboundSessionRoute({
|
||||
cfg: params.cfg,
|
||||
agentId: params.agentId,
|
||||
channel: 'fabric',
|
||||
accountId: params.accountId,
|
||||
peer: { kind: 'group', id },
|
||||
chatType: 'group',
|
||||
peer: { kind: peerKind, id },
|
||||
chatType,
|
||||
from: `fabric:channel:${id}`,
|
||||
to: `fabric:${id}`,
|
||||
});
|
||||
|
||||
13
dist/fabric/src/inbound.js
vendored
13
dist/fabric/src/inbound.js
vendored
@@ -4,6 +4,7 @@ import { join } from 'node:path';
|
||||
import { io } from 'socket.io-client';
|
||||
import { dispatchInboundReplyWithBase } from 'openclaw/plugin-sdk/inbound-reply-dispatch';
|
||||
import { resolveCoalesce } from './accounts.js';
|
||||
import { fabricPeerRoutingForXType } from './channel.js';
|
||||
import { recordChannelType } from './channel-meta.js';
|
||||
import { enqueueDelivery, flushFabricForChannel } from './coalesce.js';
|
||||
export class FabricInbound {
|
||||
@@ -433,11 +434,19 @@ export class FabricInbound {
|
||||
const core = this.core;
|
||||
const cfg = this.cfg;
|
||||
try {
|
||||
// Route by xType. DM channels need peer.kind='direct' so openclaw
|
||||
// treats them as 1:1 (sessionKey 'agent:<id>:fabric:direct:<chan>'
|
||||
// and ctx.ChatType='direct') rather than as a multi-party group.
|
||||
// Without this, the agent's user-prompt metadata says
|
||||
// 'is_group_chat: true' on a DM and downstream prompt logic
|
||||
// (commands-handlers `isDirectMessage` checks ChatType==='direct')
|
||||
// misclassifies the turn.
|
||||
const { peerKind, chatType } = fabricPeerRoutingForXType(m.xType);
|
||||
const route = core.channel.routing.resolveAgentRoute({
|
||||
cfg: this.cfg,
|
||||
channel: 'fabric',
|
||||
accountId: agentId,
|
||||
peer: { kind: 'group', id: channelId },
|
||||
peer: { kind: peerKind, id: channelId },
|
||||
});
|
||||
const storePath = core.channel.session.resolveStorePath(cfg.session?.store, {
|
||||
agentId: route.agentId,
|
||||
@@ -451,7 +460,7 @@ export class FabricInbound {
|
||||
To: `fabric:${channelId}`,
|
||||
SessionKey: route.sessionKey,
|
||||
AccountId: route.accountId ?? agentId,
|
||||
ChatType: 'group',
|
||||
ChatType: chatType,
|
||||
ConversationLabel: `fabric:${guild.nodeId}`,
|
||||
SenderId: m.authorUserId ?? 'fabric',
|
||||
Provider: 'fabric',
|
||||
|
||||
@@ -21,6 +21,39 @@ import {
|
||||
resolveDefaultFabricAccountId,
|
||||
type ResolvedFabricAccount,
|
||||
} from './accounts.js';
|
||||
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)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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'.
|
||||
*/
|
||||
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' };
|
||||
return { peerKind: 'group', chatType: 'group' };
|
||||
}
|
||||
|
||||
export function fabricPeerRoutingForChannel(channelId: string): FabricPeerRouting {
|
||||
return fabricPeerRoutingForXType(getChannelType(channelId));
|
||||
}
|
||||
|
||||
type AnyCfg = { channels?: { fabric?: unknown }; [k: string]: unknown };
|
||||
|
||||
@@ -45,13 +78,18 @@ export function looksLikeFabricTargetId(raw: string): boolean {
|
||||
export function resolveFabricOutboundSessionRoute(params: ChannelOutboundSessionRouteParams) {
|
||||
const id = stripFabricTargetPrefix(params.target);
|
||||
if (!id) return null;
|
||||
// Consult the channel-meta cache populated by inbound — DM channels
|
||||
// need peer.kind='direct' so the outbound session key matches the
|
||||
// inbound one. Cache miss falls back to 'group' (the pre-fix default,
|
||||
// no regression on cold cache).
|
||||
const { peerKind, chatType } = fabricPeerRoutingForChannel(id);
|
||||
return buildChannelOutboundSessionRoute({
|
||||
cfg: params.cfg,
|
||||
agentId: params.agentId,
|
||||
channel: 'fabric',
|
||||
accountId: params.accountId,
|
||||
peer: { kind: 'group', id },
|
||||
chatType: 'group',
|
||||
peer: { kind: peerKind, id },
|
||||
chatType,
|
||||
from: `fabric:channel:${id}`,
|
||||
to: `fabric:${id}`,
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import { dispatchInboundReplyWithBase } from 'openclaw/plugin-sdk/inbound-reply-
|
||||
import type { FabricClient, FabricSession } from './fabric-client.js';
|
||||
import type { IdentityRegistry } from './identity.js';
|
||||
import { resolveCoalesce } from './accounts.js';
|
||||
import { fabricPeerRoutingForXType } from './channel.js';
|
||||
import { recordChannelType } from './channel-meta.js';
|
||||
import { enqueueDelivery, flushFabricForChannel } from './coalesce.js';
|
||||
|
||||
@@ -504,11 +505,19 @@ export class FabricInbound {
|
||||
const core = this.core as Core & Record<string, unknown>;
|
||||
const cfg = this.cfg as { session?: { store?: unknown } };
|
||||
try {
|
||||
// Route by xType. DM channels need peer.kind='direct' so openclaw
|
||||
// treats them as 1:1 (sessionKey 'agent:<id>:fabric:direct:<chan>'
|
||||
// and ctx.ChatType='direct') rather than as a multi-party group.
|
||||
// Without this, the agent's user-prompt metadata says
|
||||
// 'is_group_chat: true' on a DM and downstream prompt logic
|
||||
// (commands-handlers `isDirectMessage` checks ChatType==='direct')
|
||||
// misclassifies the turn.
|
||||
const { peerKind, chatType } = fabricPeerRoutingForXType(m.xType);
|
||||
const route = core.channel.routing.resolveAgentRoute({
|
||||
cfg: this.cfg,
|
||||
channel: 'fabric',
|
||||
accountId: agentId,
|
||||
peer: { kind: 'group', id: channelId },
|
||||
peer: { kind: peerKind, id: channelId },
|
||||
});
|
||||
const storePath = core.channel.session.resolveStorePath(cfg.session?.store, {
|
||||
agentId: route.agentId,
|
||||
@@ -523,7 +532,7 @@ export class FabricInbound {
|
||||
To: `fabric:${channelId}`,
|
||||
SessionKey: route.sessionKey,
|
||||
AccountId: route.accountId ?? agentId,
|
||||
ChatType: 'group',
|
||||
ChatType: chatType,
|
||||
ConversationLabel: `fabric:${guild.nodeId}`,
|
||||
SenderId: m.authorUserId ?? 'fabric',
|
||||
Provider: 'fabric',
|
||||
|
||||
Reference in New Issue
Block a user