mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-25 08:02:04 +00:00
212 lines
6.1 KiB
TypeScript
212 lines
6.1 KiB
TypeScript
import "./isolated-agent.mocks.js";
|
|
import { beforeEach, describe, expect, it } from "vitest";
|
|
import { runSubagentAnnounceFlow } from "../agents/subagent-announce.js";
|
|
import type { ChannelOutboundAdapter, ChannelOutboundContext } from "../channels/plugins/types.js";
|
|
import type { CliDeps } from "../cli/deps.js";
|
|
import { resolveOutboundSendDep } from "../infra/outbound/send-deps.js";
|
|
import { createWhatsAppTestPlugin } from "../infra/outbound/targets.test-helpers.js";
|
|
import { setActivePluginRegistry } from "../plugins/runtime.js";
|
|
import { createOutboundTestPlugin, createTestRegistry } from "../test-utils/channel-plugins.js";
|
|
import { createCliDeps, mockAgentPayloads } from "./isolated-agent.delivery.test-helpers.js";
|
|
import { runCronIsolatedAgentTurn } from "./isolated-agent.js";
|
|
import {
|
|
makeCfg,
|
|
makeJob,
|
|
withTempCronHome,
|
|
writeSessionStore,
|
|
} from "./isolated-agent.test-harness.js";
|
|
import { setupIsolatedAgentTurnMocks } from "./isolated-agent.test-setup.js";
|
|
|
|
type ChannelCase = {
|
|
name: string;
|
|
channel: "slack" | "discord" | "whatsapp" | "imessage";
|
|
to: string;
|
|
sendKey: keyof Pick<
|
|
CliDeps,
|
|
"sendMessageSlack" | "sendMessageDiscord" | "sendMessageWhatsApp" | "sendMessageIMessage"
|
|
>;
|
|
expectedTo: string;
|
|
};
|
|
|
|
const CASES: ChannelCase[] = [
|
|
{
|
|
name: "Slack",
|
|
channel: "slack",
|
|
to: "channel:C12345",
|
|
sendKey: "sendMessageSlack",
|
|
expectedTo: "channel:C12345",
|
|
},
|
|
{
|
|
name: "Discord",
|
|
channel: "discord",
|
|
to: "channel:789",
|
|
sendKey: "sendMessageDiscord",
|
|
expectedTo: "channel:789",
|
|
},
|
|
{
|
|
name: "WhatsApp",
|
|
channel: "whatsapp",
|
|
to: "+15551234567",
|
|
sendKey: "sendMessageWhatsApp",
|
|
expectedTo: "+15551234567",
|
|
},
|
|
{
|
|
name: "iMessage",
|
|
channel: "imessage",
|
|
to: "friend@example.com",
|
|
sendKey: "sendMessageIMessage",
|
|
expectedTo: "friend@example.com",
|
|
},
|
|
];
|
|
|
|
async function runExplicitAnnounceTurn(params: {
|
|
home: string;
|
|
storePath: string;
|
|
deps: CliDeps;
|
|
channel: ChannelCase["channel"];
|
|
to: string;
|
|
}) {
|
|
return await runCronIsolatedAgentTurn({
|
|
cfg: makeCfg(params.home, params.storePath),
|
|
deps: params.deps,
|
|
job: {
|
|
...makeJob({ kind: "agentTurn", message: "do it" }),
|
|
delivery: {
|
|
mode: "announce",
|
|
channel: params.channel,
|
|
to: params.to,
|
|
},
|
|
},
|
|
message: "do it",
|
|
sessionKey: "cron:job-1",
|
|
lane: "cron",
|
|
});
|
|
}
|
|
|
|
type CoreChannel = ChannelCase["channel"];
|
|
type TestSendFn = (
|
|
to: string,
|
|
text: string,
|
|
options?: Record<string, unknown>,
|
|
) => Promise<{ messageId?: string } & Record<string, unknown>>;
|
|
|
|
function withRequiredMessageId(channel: CoreChannel, result: Awaited<ReturnType<TestSendFn>>) {
|
|
return {
|
|
channel,
|
|
...result,
|
|
messageId:
|
|
typeof result.messageId === "string" && result.messageId.trim()
|
|
? result.messageId
|
|
: `${channel}-test-message`,
|
|
};
|
|
}
|
|
|
|
function resolveCoreChannelSender(
|
|
channel: CoreChannel,
|
|
deps: ChannelOutboundContext["deps"],
|
|
): TestSendFn {
|
|
const sender = resolveOutboundSendDep<TestSendFn>(deps, channel);
|
|
if (!sender) {
|
|
throw new Error(`missing ${channel} sender`);
|
|
}
|
|
return sender;
|
|
}
|
|
|
|
function createCliDelegatingOutbound(params: {
|
|
channel: CoreChannel;
|
|
deliveryMode?: ChannelOutboundAdapter["deliveryMode"];
|
|
resolveTarget?: ChannelOutboundAdapter["resolveTarget"];
|
|
}): ChannelOutboundAdapter {
|
|
return {
|
|
deliveryMode: params.deliveryMode ?? "direct",
|
|
...(params.resolveTarget ? { resolveTarget: params.resolveTarget } : {}),
|
|
sendText: async ({ cfg, to, text, accountId, deps }) =>
|
|
withRequiredMessageId(
|
|
params.channel,
|
|
await resolveCoreChannelSender(params.channel, deps)(to, text, {
|
|
cfg,
|
|
accountId: accountId ?? undefined,
|
|
}),
|
|
),
|
|
};
|
|
}
|
|
|
|
const whatsappResolveTarget = createWhatsAppTestPlugin().outbound?.resolveTarget;
|
|
|
|
describe("runCronIsolatedAgentTurn core-channel direct delivery", () => {
|
|
beforeEach(() => {
|
|
setupIsolatedAgentTurnMocks({ fast: true });
|
|
setActivePluginRegistry(
|
|
createTestRegistry([
|
|
{
|
|
pluginId: "slack",
|
|
plugin: createOutboundTestPlugin({
|
|
id: "slack",
|
|
outbound: createCliDelegatingOutbound({ channel: "slack" }),
|
|
}),
|
|
source: "test",
|
|
},
|
|
{
|
|
pluginId: "discord",
|
|
plugin: createOutboundTestPlugin({
|
|
id: "discord",
|
|
outbound: createCliDelegatingOutbound({ channel: "discord" }),
|
|
}),
|
|
source: "test",
|
|
},
|
|
{
|
|
pluginId: "whatsapp",
|
|
plugin: createOutboundTestPlugin({
|
|
id: "whatsapp",
|
|
outbound: createCliDelegatingOutbound({
|
|
channel: "whatsapp",
|
|
deliveryMode: "gateway",
|
|
resolveTarget: whatsappResolveTarget,
|
|
}),
|
|
}),
|
|
source: "test",
|
|
},
|
|
{
|
|
pluginId: "imessage",
|
|
plugin: createOutboundTestPlugin({
|
|
id: "imessage",
|
|
outbound: createCliDelegatingOutbound({ channel: "imessage" }),
|
|
}),
|
|
source: "test",
|
|
},
|
|
]),
|
|
);
|
|
});
|
|
|
|
for (const testCase of CASES) {
|
|
it(`routes ${testCase.name} text-only announce delivery through the outbound adapter`, async () => {
|
|
await withTempCronHome(async (home) => {
|
|
const storePath = await writeSessionStore(home, { lastProvider: "webchat", lastTo: "" });
|
|
const deps = createCliDeps();
|
|
mockAgentPayloads([{ text: "hello from cron" }]);
|
|
|
|
const res = await runExplicitAnnounceTurn({
|
|
home,
|
|
storePath,
|
|
deps,
|
|
channel: testCase.channel,
|
|
to: testCase.to,
|
|
});
|
|
|
|
expect(res.status).toBe("ok");
|
|
expect(res.delivered).toBe(true);
|
|
expect(res.deliveryAttempted).toBe(true);
|
|
expect(runSubagentAnnounceFlow).not.toHaveBeenCalled();
|
|
|
|
const sendFn = deps[testCase.sendKey];
|
|
expect(sendFn).toHaveBeenCalledTimes(1);
|
|
expect(sendFn).toHaveBeenCalledWith(
|
|
testCase.expectedTo,
|
|
"hello from cron",
|
|
expect.any(Object),
|
|
);
|
|
});
|
|
});
|
|
}
|
|
});
|