mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:00:42 +00:00
Merge remote-tracking branch 'origin/main' into release/2026.4.25
This commit is contained in:
@@ -150,6 +150,107 @@ const replyMediaPathMocks = vi.hoisted(() => ({
|
||||
const runtimePluginMocks = vi.hoisted(() => ({
|
||||
ensureRuntimePluginsLoaded: vi.fn(),
|
||||
}));
|
||||
const conversationBindingMocks = vi.hoisted(() => {
|
||||
type BindingMsgContext = {
|
||||
OriginatingChannel?: string | null;
|
||||
Surface?: string | null;
|
||||
Provider?: string | null;
|
||||
AccountId?: string | null;
|
||||
MessageThreadId?: string | number | null;
|
||||
ThreadParentId?: string | null;
|
||||
SenderId?: string | null;
|
||||
SessionKey?: string | null;
|
||||
ParentSessionKey?: string | null;
|
||||
OriginatingTo?: string | null;
|
||||
To?: string | null;
|
||||
From?: string | null;
|
||||
NativeChannelId?: string | null;
|
||||
};
|
||||
type BindingConfig = {
|
||||
channels?: Record<string, { defaultAccount?: string | null } | undefined>;
|
||||
};
|
||||
|
||||
const normalizeText = (value: string | number | null | undefined) =>
|
||||
typeof value === "number" ? `${value}` : (value ?? "").trim();
|
||||
const normalizeChannel = (value: string | null | undefined) => normalizeText(value).toLowerCase();
|
||||
const resolveChannel = (ctx: BindingMsgContext, commandChannel?: string | null) =>
|
||||
normalizeChannel(ctx.OriginatingChannel ?? commandChannel ?? ctx.Surface ?? ctx.Provider);
|
||||
const resolveAccountId = (ctx: BindingMsgContext, cfg: BindingConfig, channel: string) =>
|
||||
normalizeText(ctx.AccountId) ||
|
||||
normalizeText(cfg.channels?.[channel]?.defaultAccount) ||
|
||||
"default";
|
||||
const resolveTarget = (channel: string, value: string | null | undefined) => {
|
||||
const target = normalizeText(value);
|
||||
if (!target) {
|
||||
return undefined;
|
||||
}
|
||||
const channelPrefix = `${channel}:`;
|
||||
return target.toLowerCase().startsWith(channelPrefix)
|
||||
? target.slice(channelPrefix.length)
|
||||
: target;
|
||||
};
|
||||
const resolveThreadId = (ctx: BindingMsgContext) =>
|
||||
normalizeText(ctx.MessageThreadId) || undefined;
|
||||
|
||||
const resolveConversationBindingContextFromMessage = vi.fn(
|
||||
(params: { cfg: BindingConfig; ctx: BindingMsgContext }) => {
|
||||
const channel = resolveChannel(params.ctx);
|
||||
if (!channel) {
|
||||
return null;
|
||||
}
|
||||
const threadId = resolveThreadId(params.ctx);
|
||||
const baseConversationId =
|
||||
resolveTarget(channel, params.ctx.OriginatingTo) ?? resolveTarget(channel, params.ctx.To);
|
||||
const conversationId = threadId ?? baseConversationId;
|
||||
if (!conversationId) {
|
||||
return null;
|
||||
}
|
||||
const parentConversationId =
|
||||
threadId && baseConversationId && baseConversationId !== threadId
|
||||
? baseConversationId
|
||||
: resolveTarget(channel, params.ctx.ThreadParentId);
|
||||
return {
|
||||
channel,
|
||||
accountId: resolveAccountId(params.ctx, params.cfg, channel),
|
||||
conversationId,
|
||||
...(parentConversationId ? { parentConversationId } : {}),
|
||||
...(threadId ? { threadId } : {}),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
resolveConversationBindingAccountIdFromMessage: (params: {
|
||||
ctx: BindingMsgContext;
|
||||
cfg: BindingConfig;
|
||||
commandChannel?: string | null;
|
||||
}) =>
|
||||
resolveAccountId(params.ctx, params.cfg, resolveChannel(params.ctx, params.commandChannel)),
|
||||
resolveConversationBindingChannelFromMessage: (
|
||||
ctx: BindingMsgContext,
|
||||
commandChannel?: string | null,
|
||||
) => resolveChannel(ctx, commandChannel),
|
||||
resolveConversationBindingContextFromAcpCommand: (params: {
|
||||
cfg: BindingConfig;
|
||||
ctx: BindingMsgContext;
|
||||
command?: { to?: string | null; senderId?: string | null };
|
||||
sessionKey?: string | null;
|
||||
parentSessionKey?: string | null;
|
||||
}) =>
|
||||
resolveConversationBindingContextFromMessage({
|
||||
cfg: params.cfg,
|
||||
ctx: {
|
||||
...params.ctx,
|
||||
SenderId: params.command?.senderId ?? params.ctx.SenderId,
|
||||
SessionKey: params.sessionKey ?? params.ctx.SessionKey,
|
||||
ParentSessionKey: params.parentSessionKey ?? params.ctx.ParentSessionKey,
|
||||
To: params.command?.to ?? params.ctx.To,
|
||||
},
|
||||
}),
|
||||
resolveConversationBindingContextFromMessage,
|
||||
resolveConversationBindingThreadIdFromMessage: (ctx: BindingMsgContext) => resolveThreadId(ctx),
|
||||
};
|
||||
});
|
||||
const threadInfoMocks = vi.hoisted(() => ({
|
||||
parseSessionThreadInfo: vi.fn<
|
||||
(sessionKey: string | undefined) => {
|
||||
@@ -345,6 +446,18 @@ vi.mock("./reply-media-paths.runtime.js", () => ({
|
||||
vi.mock("../../agents/runtime-plugins.js", () => ({
|
||||
ensureRuntimePluginsLoaded: runtimePluginMocks.ensureRuntimePluginsLoaded,
|
||||
}));
|
||||
vi.mock("./conversation-binding-input.js", () => ({
|
||||
resolveConversationBindingAccountIdFromMessage:
|
||||
conversationBindingMocks.resolveConversationBindingAccountIdFromMessage,
|
||||
resolveConversationBindingChannelFromMessage:
|
||||
conversationBindingMocks.resolveConversationBindingChannelFromMessage,
|
||||
resolveConversationBindingContextFromAcpCommand:
|
||||
conversationBindingMocks.resolveConversationBindingContextFromAcpCommand,
|
||||
resolveConversationBindingContextFromMessage:
|
||||
conversationBindingMocks.resolveConversationBindingContextFromMessage,
|
||||
resolveConversationBindingThreadIdFromMessage:
|
||||
conversationBindingMocks.resolveConversationBindingThreadIdFromMessage,
|
||||
}));
|
||||
vi.mock("../../tts/status-config.js", () => ({
|
||||
resolveStatusTtsSnapshot: () => ({
|
||||
autoMode: "always",
|
||||
@@ -771,7 +884,8 @@ describe("dispatchReplyFromConfig", () => {
|
||||
OriginatingTo: undefined,
|
||||
});
|
||||
|
||||
const replyResolver = async () => ({ text: "hi" }) satisfies ReplyPayload;
|
||||
const replyResolver = async () =>
|
||||
({ text: "hi", mediaUrl: "https://example.test/reply.png" }) satisfies ReplyPayload;
|
||||
await dispatchReplyFromConfig({ ctx, cfg, dispatcher, replyResolver });
|
||||
|
||||
expect(dispatcher.sendFinalReply).not.toHaveBeenCalled();
|
||||
|
||||
@@ -432,26 +432,38 @@ export async function dispatchReplyFromConfig(
|
||||
});
|
||||
const routeReplyTo = replyRoute.to;
|
||||
const deliveryChannel = shouldRouteToOriginating ? routeReplyChannel : currentSurface;
|
||||
const { createReplyMediaPathNormalizer } = await loadReplyMediaPathsRuntime();
|
||||
const normalizeReplyMediaPaths = createReplyMediaPathNormalizer({
|
||||
cfg,
|
||||
sessionKey: acpDispatchSessionKey,
|
||||
workspaceDir,
|
||||
messageProvider: deliveryChannel,
|
||||
accountId: replyRoute.accountId,
|
||||
groupId,
|
||||
groupChannel: ctx.GroupChannel,
|
||||
groupSpace: ctx.GroupSpace,
|
||||
requesterSenderId: ctx.SenderId,
|
||||
requesterSenderName: ctx.SenderName,
|
||||
requesterSenderUsername: ctx.SenderUsername,
|
||||
requesterSenderE164: ctx.SenderE164,
|
||||
});
|
||||
let normalizeReplyMediaPaths:
|
||||
| ReturnType<
|
||||
(typeof import("./reply-media-paths.runtime.js"))["createReplyMediaPathNormalizer"]
|
||||
>
|
||||
| undefined;
|
||||
const getNormalizeReplyMediaPaths = async () => {
|
||||
if (normalizeReplyMediaPaths) {
|
||||
return normalizeReplyMediaPaths;
|
||||
}
|
||||
const { createReplyMediaPathNormalizer } = await loadReplyMediaPathsRuntime();
|
||||
normalizeReplyMediaPaths = createReplyMediaPathNormalizer({
|
||||
cfg,
|
||||
sessionKey: acpDispatchSessionKey,
|
||||
workspaceDir,
|
||||
messageProvider: deliveryChannel,
|
||||
accountId: replyRoute.accountId,
|
||||
groupId,
|
||||
groupChannel: ctx.GroupChannel,
|
||||
groupSpace: ctx.GroupSpace,
|
||||
requesterSenderId: ctx.SenderId,
|
||||
requesterSenderName: ctx.SenderName,
|
||||
requesterSenderUsername: ctx.SenderUsername,
|
||||
requesterSenderE164: ctx.SenderE164,
|
||||
});
|
||||
return normalizeReplyMediaPaths;
|
||||
};
|
||||
const normalizeReplyMediaPayload = async (payload: ReplyPayload): Promise<ReplyPayload> => {
|
||||
if (!resolveSendableOutboundReplyParts(payload).hasMedia) {
|
||||
return payload;
|
||||
}
|
||||
return await normalizeReplyMediaPaths(payload);
|
||||
const normalizeReplyMediaPayloadPaths = await getNormalizeReplyMediaPaths();
|
||||
return await normalizeReplyMediaPayloadPaths(payload);
|
||||
};
|
||||
|
||||
const routeReplyToOriginating = async (
|
||||
|
||||
Reference in New Issue
Block a user