refactor(core): extract shared dedup helpers

This commit is contained in:
Peter Steinberger
2026-03-07 10:40:49 +00:00
parent 14c61bb33f
commit 3c71e2bd48
114 changed files with 3400 additions and 2040 deletions

View File

@@ -21,6 +21,12 @@ import {
createThreadBindingManager,
} from "./thread-bindings.js";
type DiscordConfig = NonNullable<
import("../../config/config.js").OpenClawConfig["channels"]
>["discord"];
type DiscordMessageEvent = import("./listeners.js").DiscordMessageEvent;
type DiscordClient = import("@buape/carbon").Client;
function createThreadBinding(
overrides?: Partial<
import("../../infra/outbound/session-binding-service.js").SessionBindingRecord
@@ -48,6 +54,34 @@ function createThreadBinding(
} satisfies import("../../infra/outbound/session-binding-service.js").SessionBindingRecord;
}
function createPreflightArgs(params: {
cfg: import("../../config/config.js").OpenClawConfig;
discordConfig: DiscordConfig;
data: DiscordMessageEvent;
client: DiscordClient;
}): Parameters<typeof preflightDiscordMessage>[0] {
return {
cfg: params.cfg,
discordConfig: params.discordConfig,
accountId: "default",
token: "token",
runtime: {} as import("../../runtime.js").RuntimeEnv,
botUserId: "openclaw-bot",
guildHistories: new Map(),
historyLimit: 0,
mediaMaxBytes: 1_000_000,
textLimit: 2_000,
replyToMode: "all",
dmEnabled: true,
groupDmEnabled: true,
ackReactionScope: "direct",
groupPolicy: "open",
threadBindings: createNoopThreadBindingManager("default"),
data: params.data,
client: params.client,
};
}
describe("resolvePreflightMentionRequirement", () => {
it("requires mention when config requires mention and thread is not bound", () => {
expect(
@@ -312,42 +346,30 @@ describe("preflightDiscordMessage", () => {
resolveByConversation: (ref) => (ref.conversationId === threadId ? threadBinding : null),
});
const result = await preflightDiscordMessage({
cfg: {
session: {
mainKey: "main",
scope: "per-sender",
},
} as import("../../config/config.js").OpenClawConfig,
discordConfig: {
allowBots: true,
} as NonNullable<import("../../config/config.js").OpenClawConfig["channels"]>["discord"],
accountId: "default",
token: "token",
runtime: {} as import("../../runtime.js").RuntimeEnv,
botUserId: "openclaw-bot",
guildHistories: new Map(),
historyLimit: 0,
mediaMaxBytes: 1_000_000,
textLimit: 2_000,
replyToMode: "all",
dmEnabled: true,
groupDmEnabled: true,
ackReactionScope: "direct",
groupPolicy: "open",
threadBindings: createNoopThreadBindingManager("default"),
data: {
channel_id: threadId,
guild_id: "guild-1",
guild: {
id: "guild-1",
name: "Guild One",
},
author: message.author,
message,
} as unknown as import("./listeners.js").DiscordMessageEvent,
client,
});
const result = await preflightDiscordMessage(
createPreflightArgs({
cfg: {
session: {
mainKey: "main",
scope: "per-sender",
},
} as import("../../config/config.js").OpenClawConfig,
discordConfig: {
allowBots: true,
} as DiscordConfig,
data: {
channel_id: threadId,
guild_id: "guild-1",
guild: {
id: "guild-1",
name: "Guild One",
},
author: message.author,
message,
} as unknown as DiscordMessageEvent,
client,
}),
);
expect(result).not.toBeNull();
expect(result?.boundSessionKey).toBe(threadBinding.targetSessionKey);
@@ -768,47 +790,33 @@ describe("preflightDiscordMessage", () => {
},
} as unknown as import("@buape/carbon").Message;
const result = await preflightDiscordMessage({
cfg: {
session: {
mainKey: "main",
scope: "per-sender",
},
messages: {
groupChat: {
mentionPatterns: ["openclaw"],
const result = await preflightDiscordMessage(
createPreflightArgs({
cfg: {
session: {
mainKey: "main",
scope: "per-sender",
},
},
} as import("../../config/config.js").OpenClawConfig,
discordConfig: {} as NonNullable<
import("../../config/config.js").OpenClawConfig["channels"]
>["discord"],
accountId: "default",
token: "token",
runtime: {} as import("../../runtime.js").RuntimeEnv,
botUserId: "openclaw-bot",
guildHistories: new Map(),
historyLimit: 0,
mediaMaxBytes: 1_000_000,
textLimit: 2_000,
replyToMode: "all",
dmEnabled: true,
groupDmEnabled: true,
ackReactionScope: "direct",
groupPolicy: "open",
threadBindings: createNoopThreadBindingManager("default"),
data: {
channel_id: channelId,
guild_id: "guild-1",
guild: {
id: "guild-1",
name: "Guild One",
},
author: message.author,
message,
} as unknown as import("./listeners.js").DiscordMessageEvent,
client,
});
messages: {
groupChat: {
mentionPatterns: ["openclaw"],
},
},
} as import("../../config/config.js").OpenClawConfig,
discordConfig: {} as DiscordConfig,
data: {
channel_id: channelId,
guild_id: "guild-1",
guild: {
id: "guild-1",
name: "Guild One",
},
author: message.author,
message,
} as unknown as DiscordMessageEvent,
client,
}),
);
expect(transcribeFirstAudioMock).toHaveBeenCalledTimes(1);
expect(transcribeFirstAudioMock).toHaveBeenCalledWith(

View File

@@ -199,6 +199,30 @@ describe("DiscordVoiceManager", () => {
);
};
type ProcessSegmentInvoker = {
processSegment: (params: {
entry: unknown;
wavPath: string;
userId: string;
durationSeconds: number;
}) => Promise<void>;
};
const processVoiceSegment = async (
manager: InstanceType<typeof managerModule.DiscordVoiceManager>,
userId: string,
) =>
await (manager as unknown as ProcessSegmentInvoker).processSegment({
entry: {
guildId: "g1",
channelId: "c1",
route: { sessionKey: "discord:g1:c1", agentId: "agent-1" },
},
wavPath: "/tmp/test.wav",
userId,
durationSeconds: 1.2,
});
it("keeps the new session when an old disconnected handler fires", async () => {
const oldConnection = createConnectionMock();
const newConnection = createConnectionMock();
@@ -298,25 +322,7 @@ describe("DiscordVoiceManager", () => {
},
});
const manager = createManager({ allowFrom: ["discord:u-owner"] }, client);
await (
manager as unknown as {
processSegment: (params: {
entry: unknown;
wavPath: string;
userId: string;
durationSeconds: number;
}) => Promise<void>;
}
).processSegment({
entry: {
guildId: "g1",
channelId: "c1",
route: { sessionKey: "discord:g1:c1", agentId: "agent-1" },
},
wavPath: "/tmp/test.wav",
userId: "u-owner",
durationSeconds: 1.2,
});
await processVoiceSegment(manager, "u-owner");
const commandArgs = agentCommandMock.mock.calls.at(-1)?.[0] as
| { senderIsOwner?: boolean }
@@ -336,25 +342,7 @@ describe("DiscordVoiceManager", () => {
},
});
const manager = createManager({ allowFrom: ["discord:u-owner"] }, client);
await (
manager as unknown as {
processSegment: (params: {
entry: unknown;
wavPath: string;
userId: string;
durationSeconds: number;
}) => Promise<void>;
}
).processSegment({
entry: {
guildId: "g1",
channelId: "c1",
route: { sessionKey: "discord:g1:c1", agentId: "agent-1" },
},
wavPath: "/tmp/test.wav",
userId: "u-guest",
durationSeconds: 1.2,
});
await processVoiceSegment(manager, "u-guest");
const commandArgs = agentCommandMock.mock.calls.at(-1)?.[0] as
| { senderIsOwner?: boolean }
@@ -374,26 +362,7 @@ describe("DiscordVoiceManager", () => {
},
});
const manager = createManager({ allowFrom: ["discord:u-cache"] }, client);
const runSegment = async () =>
await (
manager as unknown as {
processSegment: (params: {
entry: unknown;
wavPath: string;
userId: string;
durationSeconds: number;
}) => Promise<void>;
}
).processSegment({
entry: {
guildId: "g1",
channelId: "c1",
route: { sessionKey: "discord:g1:c1", agentId: "agent-1" },
},
wavPath: "/tmp/test.wav",
userId: "u-cache",
durationSeconds: 1.2,
});
const runSegment = async () => await processVoiceSegment(manager, "u-cache");
await runSegment();
await runSegment();