feat(discord): Discord transport

This commit is contained in:
Shadow
2025-12-15 10:11:18 -06:00
committed by Peter Steinberger
parent 557f8e5a04
commit ac659ff5a7
44 changed files with 1352 additions and 56 deletions

View File

@@ -87,6 +87,7 @@ describe("runCronIsolatedAgentTurn", () => {
const deps: CliDeps = {
sendMessageWhatsApp: vi.fn(),
sendMessageTelegram: vi.fn(),
sendMessageDiscord: vi.fn(),
};
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
payloads: [{ text: "first" }, { text: " " }, { text: " last " }],
@@ -116,6 +117,7 @@ describe("runCronIsolatedAgentTurn", () => {
const deps: CliDeps = {
sendMessageWhatsApp: vi.fn(),
sendMessageTelegram: vi.fn(),
sendMessageDiscord: vi.fn(),
};
const long = "a".repeat(2001);
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
@@ -146,6 +148,7 @@ describe("runCronIsolatedAgentTurn", () => {
const deps: CliDeps = {
sendMessageWhatsApp: vi.fn(),
sendMessageTelegram: vi.fn(),
sendMessageDiscord: vi.fn(),
};
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
payloads: [{ text: "hello" }],
@@ -183,6 +186,7 @@ describe("runCronIsolatedAgentTurn", () => {
const deps: CliDeps = {
sendMessageWhatsApp: vi.fn(),
sendMessageTelegram: vi.fn(),
sendMessageDiscord: vi.fn(),
};
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
payloads: [{ text: "hello" }],
@@ -212,4 +216,47 @@ describe("runCronIsolatedAgentTurn", () => {
expect(deps.sendMessageWhatsApp).not.toHaveBeenCalled();
});
});
it("delivers via discord when configured", async () => {
await withTempHome(async (home) => {
const storePath = await writeSessionStore(home);
const deps: CliDeps = {
sendMessageWhatsApp: vi.fn(),
sendMessageTelegram: vi.fn(),
sendMessageDiscord: vi.fn().mockResolvedValue({
messageId: "d1",
channelId: "chan",
}),
};
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
payloads: [{ text: "hello from cron" }],
meta: {
durationMs: 5,
agentMeta: { sessionId: "s", provider: "p", model: "m" },
},
});
const res = await runCronIsolatedAgentTurn({
cfg: makeCfg(home, storePath),
deps,
job: makeJob({
kind: "agentTurn",
message: "do it",
deliver: true,
channel: "discord",
to: "channel:1122",
}),
message: "do it",
sessionKey: "cron:job-1",
lane: "cron",
});
expect(res.status).toBe("ok");
expect(deps.sendMessageDiscord).toHaveBeenCalledWith(
"channel:1122",
"hello from cron",
expect.objectContaining({ token: process.env.DISCORD_BOT_TOKEN }),
);
});
});
});

View File

@@ -53,7 +53,7 @@ function pickSummaryFromPayloads(
function resolveDeliveryTarget(
cfg: ClawdisConfig,
jobPayload: {
channel?: "last" | "whatsapp" | "telegram";
channel?: "last" | "whatsapp" | "telegram" | "discord";
to?: string;
},
) {
@@ -76,7 +76,11 @@ function resolveDeliveryTarget(
const lastTo = typeof main?.lastTo === "string" ? main.lastTo.trim() : "";
const channel = (() => {
if (requestedChannel === "whatsapp" || requestedChannel === "telegram") {
if (
requestedChannel === "whatsapp" ||
requestedChannel === "telegram" ||
requestedChannel === "discord"
) {
return requestedChannel;
}
return lastChannel ?? "whatsapp";
@@ -366,6 +370,50 @@ export async function runCronIsolatedAgentTurn(params: {
return { status: "error", summary, error: String(err) };
return { status: "ok", summary };
}
} else if (resolvedDelivery.channel === "discord") {
if (!resolvedDelivery.to) {
if (!bestEffortDeliver)
return {
status: "error",
summary,
error:
"Cron delivery to Discord requires --channel discord and --to <channelId|user:ID>",
};
return {
status: "skipped",
summary: "Delivery skipped (no Discord destination).",
};
}
const discordTarget = resolvedDelivery.to;
try {
for (const payload of payloads) {
const mediaList =
payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []);
if (mediaList.length === 0) {
await params.deps.sendMessageDiscord(
discordTarget,
payload.text ?? "",
{
token: process.env.DISCORD_BOT_TOKEN,
},
);
} else {
let first = true;
for (const url of mediaList) {
const caption = first ? (payload.text ?? "") : "";
first = false;
await params.deps.sendMessageDiscord(discordTarget, caption, {
token: process.env.DISCORD_BOT_TOKEN,
mediaUrl: url,
});
}
}
}
} catch (err) {
if (!bestEffortDeliver)
return { status: "error", summary, error: String(err) };
return { status: "ok", summary };
}
}
}

View File

@@ -14,7 +14,7 @@ export type CronPayload =
thinking?: string;
timeoutSeconds?: number;
deliver?: boolean;
channel?: "last" | "whatsapp" | "telegram";
channel?: "last" | "whatsapp" | "telegram" | "discord";
to?: string;
bestEffortDeliver?: boolean;
};