mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 17:31:06 +00:00
refactor(channels): centralize runtime binding routes
This commit is contained in:
@@ -262,7 +262,7 @@ const {
|
||||
mockEnsureConfiguredBindingRouteReady: vi.fn(
|
||||
async (_params?: unknown): Promise<BindingReadiness> => ({ ok: true }),
|
||||
),
|
||||
mockResolveBoundConversation: vi.fn(() => null as BoundConversation),
|
||||
mockResolveBoundConversation: vi.fn((_ref?: unknown) => null as BoundConversation),
|
||||
mockTouchBinding: vi.fn(),
|
||||
mockResolveFeishuReasoningPreviewEnabled: vi.fn(() => false),
|
||||
}));
|
||||
@@ -297,6 +297,30 @@ vi.mock("openclaw/plugin-sdk/conversation-runtime", async () => {
|
||||
...actual,
|
||||
resolveConfiguredBindingRoute: (params: unknown) =>
|
||||
mockResolveConfiguredBindingRoute(params as { route: ResolvedAgentRoute }),
|
||||
resolveRuntimeConversationBindingRoute: (params: {
|
||||
route: ResolvedAgentRoute;
|
||||
conversation: Parameters<
|
||||
ReturnType<typeof actual.getSessionBindingService>["resolveByConversation"]
|
||||
>[0];
|
||||
}) => {
|
||||
const bindingRecord = mockResolveBoundConversation(params.conversation);
|
||||
const boundSessionKey = bindingRecord?.targetSessionKey?.trim();
|
||||
if (!bindingRecord || !boundSessionKey) {
|
||||
return { bindingRecord: null, route: params.route };
|
||||
}
|
||||
mockTouchBinding(bindingRecord.bindingId);
|
||||
return {
|
||||
bindingRecord,
|
||||
boundSessionKey,
|
||||
boundAgentId: params.route.agentId,
|
||||
route: {
|
||||
...params.route,
|
||||
sessionKey: boundSessionKey,
|
||||
lastRoutePolicy: boundSessionKey === params.route.mainSessionKey ? "main" : "session",
|
||||
matchedBy: "binding.channel",
|
||||
},
|
||||
};
|
||||
},
|
||||
ensureConfiguredBindingRouteReady: (params: unknown) =>
|
||||
mockEnsureConfiguredBindingRouteReady(params),
|
||||
getSessionBindingService: () => ({
|
||||
|
||||
@@ -2,8 +2,8 @@ import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pair
|
||||
import {
|
||||
ensureConfiguredBindingRouteReady,
|
||||
resolveConfiguredBindingRoute,
|
||||
resolveRuntimeConversationBindingRoute,
|
||||
} from "openclaw/plugin-sdk/conversation-runtime";
|
||||
import { getSessionBindingService } from "openclaw/plugin-sdk/conversation-runtime";
|
||||
import { resolveAgentOutboundIdentity } from "openclaw/plugin-sdk/outbound-runtime";
|
||||
import {
|
||||
buildPendingHistoryContextFromMap,
|
||||
@@ -12,8 +12,6 @@ import {
|
||||
recordPendingHistoryEntryIfEnabled,
|
||||
type HistoryEntry,
|
||||
} from "openclaw/plugin-sdk/reply-history";
|
||||
import { deriveLastRoutePolicy } from "openclaw/plugin-sdk/routing";
|
||||
import { resolveAgentIdFromSessionKey } from "openclaw/plugin-sdk/routing";
|
||||
import {
|
||||
resolveDefaultGroupPolicy,
|
||||
resolveOpenProviderRuntimeGroupPolicy,
|
||||
@@ -651,28 +649,22 @@ export async function handleFeishuMessage(params: {
|
||||
// Bound Feishu conversations intentionally require an exact live conversation-id match.
|
||||
// Sender-scoped topic sessions therefore bind on `chat:topic:root:sender:user`, while
|
||||
// configured ACP bindings may still inherit the shared `chat:topic:root` topic session.
|
||||
const threadBinding = getSessionBindingService().resolveByConversation({
|
||||
channel: "feishu",
|
||||
accountId: account.accountId,
|
||||
conversationId: currentConversationId,
|
||||
...(parentConversationId ? { parentConversationId } : {}),
|
||||
const runtimeRoute = resolveRuntimeConversationBindingRoute({
|
||||
route,
|
||||
conversation: {
|
||||
channel: "feishu",
|
||||
accountId: account.accountId,
|
||||
conversationId: currentConversationId,
|
||||
...(parentConversationId ? { parentConversationId } : {}),
|
||||
},
|
||||
});
|
||||
const boundSessionKey = threadBinding?.targetSessionKey?.trim();
|
||||
if (threadBinding && boundSessionKey) {
|
||||
route = {
|
||||
...route,
|
||||
sessionKey: boundSessionKey,
|
||||
agentId: resolveAgentIdFromSessionKey(boundSessionKey) || route.agentId,
|
||||
lastRoutePolicy: deriveLastRoutePolicy({
|
||||
sessionKey: boundSessionKey,
|
||||
mainSessionKey: route.mainSessionKey,
|
||||
}),
|
||||
matchedBy: "binding.channel",
|
||||
};
|
||||
route = runtimeRoute.route;
|
||||
if (runtimeRoute.bindingRecord) {
|
||||
configuredBinding = null;
|
||||
getSessionBindingService().touch(threadBinding.bindingId);
|
||||
log(
|
||||
`feishu[${account.accountId}]: routed via bound conversation ${currentConversationId} -> ${boundSessionKey}`,
|
||||
runtimeRoute.boundSessionKey
|
||||
? `feishu[${account.accountId}]: routed via bound conversation ${currentConversationId} -> ${runtimeRoute.boundSessionKey}`
|
||||
: `feishu[${account.accountId}]: plugin-bound conversation ${currentConversationId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ type FeishuLifecycleTestMocks = {
|
||||
monitorWebhookMock: AsyncUnknownMock;
|
||||
createFeishuThreadBindingManagerMock: UnknownMock;
|
||||
createFeishuReplyDispatcherMock: CreateFeishuReplyDispatcherMock;
|
||||
resolveBoundConversationMock: Mock<() => BoundConversation | null>;
|
||||
resolveBoundConversationMock: Mock<(ref?: unknown) => BoundConversation | null>;
|
||||
touchBindingMock: UnknownMock;
|
||||
resolveAgentRouteMock: UnknownMock;
|
||||
resolveConfiguredBindingRouteMock: UnknownMock;
|
||||
@@ -66,7 +66,7 @@ const feishuLifecycleTestMocks = vi.hoisted(
|
||||
monitorWebhookMock: vi.fn(async () => {}),
|
||||
createFeishuThreadBindingManagerMock: vi.fn(() => ({ stop: vi.fn() })),
|
||||
createFeishuReplyDispatcherMock: vi.fn(),
|
||||
resolveBoundConversationMock: vi.fn<() => BoundConversation | null>(() => null),
|
||||
resolveBoundConversationMock: vi.fn<(ref?: unknown) => BoundConversation | null>(() => null),
|
||||
touchBindingMock: vi.fn(),
|
||||
resolveAgentRouteMock: vi.fn(),
|
||||
resolveConfiguredBindingRouteMock: vi.fn(),
|
||||
@@ -155,6 +155,36 @@ vi.mock("openclaw/plugin-sdk/conversation-runtime", async () => {
|
||||
resolveConfiguredBindingRouteMock.getMockImplementation()
|
||||
? resolveConfiguredBindingRouteMock(params)
|
||||
: actual.resolveConfiguredBindingRoute(params),
|
||||
resolveRuntimeConversationBindingRoute: (
|
||||
params: Parameters<typeof actual.resolveRuntimeConversationBindingRoute>[0],
|
||||
) => {
|
||||
const conversation =
|
||||
"conversation" in params
|
||||
? params.conversation
|
||||
: {
|
||||
channel: params.channel,
|
||||
accountId: params.accountId,
|
||||
conversationId: params.conversationId,
|
||||
parentConversationId: params.parentConversationId,
|
||||
};
|
||||
const bindingRecord = resolveBoundConversationMock(conversation);
|
||||
const boundSessionKey = bindingRecord?.targetSessionKey?.trim();
|
||||
if (!bindingRecord || !boundSessionKey) {
|
||||
return { bindingRecord: null, route: params.route };
|
||||
}
|
||||
touchBindingMock(bindingRecord.bindingId);
|
||||
return {
|
||||
bindingRecord,
|
||||
boundSessionKey,
|
||||
boundAgentId: params.route.agentId,
|
||||
route: {
|
||||
...params.route,
|
||||
sessionKey: boundSessionKey,
|
||||
lastRoutePolicy: boundSessionKey === params.route.mainSessionKey ? "main" : "session",
|
||||
matchedBy: "binding.channel",
|
||||
},
|
||||
};
|
||||
},
|
||||
ensureConfiguredBindingRouteReady: (
|
||||
params: Parameters<typeof actual.ensureConfiguredBindingRouteReady>[0],
|
||||
) =>
|
||||
|
||||
Reference in New Issue
Block a user