feat: mirror delivered outbound messages (#1031)

Co-authored-by: T Savo <TSavo@users.noreply.github.com>
This commit is contained in:
Peter Steinberger
2026-01-17 01:48:02 +00:00
parent 3fb699a84b
commit fdaeada3ec
26 changed files with 697 additions and 29 deletions

View File

@@ -2,7 +2,22 @@ import { describe, expect, it, vi } from "vitest";
import type { ClawdbotConfig } from "../../config/config.js";
import { markdownToSignalTextChunks } from "../../signal/format.js";
import { deliverOutboundPayloads, normalizeOutboundPayloads } from "./deliver.js";
const mocks = vi.hoisted(() => ({
appendAssistantMessageToSessionTranscript: vi.fn(async () => ({ ok: true, sessionFile: "x" })),
}));
vi.mock("../../config/sessions.js", async () => {
const actual = await vi.importActual<typeof import("../../config/sessions.js")>(
"../../config/sessions.js",
);
return {
...actual,
appendAssistantMessageToSessionTranscript: mocks.appendAssistantMessageToSessionTranscript,
};
});
const { deliverOutboundPayloads, normalizeOutboundPayloads } = await import("./deliver.js");
describe("deliverOutboundPayloads", () => {
it("chunks telegram markdown and passes through accountId", async () => {
@@ -193,4 +208,29 @@ describe("deliverOutboundPayloads", () => {
expect(onError).toHaveBeenCalledTimes(1);
expect(results).toEqual([{ channel: "whatsapp", messageId: "w2", toJid: "jid" }]);
});
it("mirrors delivered output when mirror options are provided", async () => {
const sendTelegram = vi.fn().mockResolvedValue({ messageId: "m1", chatId: "c1" });
const cfg: ClawdbotConfig = {
channels: { telegram: { botToken: "tok-1", textChunkLimit: 2 } },
};
mocks.appendAssistantMessageToSessionTranscript.mockClear();
await deliverOutboundPayloads({
cfg,
channel: "telegram",
to: "123",
payloads: [{ text: "caption", mediaUrl: "https://example.com/files/report.pdf?sig=1" }],
deps: { sendTelegram },
mirror: {
sessionKey: "agent:main:main",
text: "caption",
mediaUrls: ["https://example.com/files/report.pdf?sig=1"],
},
});
expect(mocks.appendAssistantMessageToSessionTranscript).toHaveBeenCalledWith(
expect.objectContaining({ text: "report.pdf" }),
);
});
});

View File

@@ -11,6 +11,10 @@ import { sendMessageSignal } from "../../signal/send.js";
import type { sendMessageSlack } from "../../slack/send.js";
import type { sendMessageTelegram } from "../../telegram/send.js";
import type { sendMessageWhatsApp } from "../../web/outbound.js";
import {
appendAssistantMessageToSessionTranscript,
resolveMirroredTranscriptText,
} from "../../config/sessions.js";
import type { NormalizedOutboundPayload } from "./payloads.js";
import { normalizeOutboundPayloads } from "./payloads.js";
import type { OutboundChannel } from "./targets.js";
@@ -159,6 +163,12 @@ export async function deliverOutboundPayloads(params: {
bestEffort?: boolean;
onError?: (err: unknown, payload: NormalizedOutboundPayload) => void;
onPayload?: (payload: NormalizedOutboundPayload) => void;
mirror?: {
sessionKey: string;
agentId?: string;
text?: string;
mediaUrls?: string[];
};
}): Promise<OutboundDeliveryResult[]> {
const { cfg, channel, to, payloads } = params;
const accountId = params.accountId;
@@ -279,5 +289,18 @@ export async function deliverOutboundPayloads(params: {
params.onError?.(err, payload);
}
}
if (params.mirror && results.length > 0) {
const mirrorText = resolveMirroredTranscriptText({
text: params.mirror.text,
mediaUrls: params.mirror.mediaUrls,
});
if (mirrorText) {
await appendAssistantMessageToSessionTranscript({
agentId: params.mirror.agentId,
sessionKey: params.mirror.sessionKey,
text: mirrorText,
});
}
}
return results;
}

View File

@@ -36,6 +36,8 @@ export type RunMessageActionParams = {
toolContext?: ChannelThreadingToolContext;
gateway?: MessageActionRunnerGateway;
deps?: OutboundSendDeps;
sessionKey?: string;
agentId?: string;
dryRun?: boolean;
};
@@ -265,6 +267,13 @@ export async function runMessageAction(
bestEffort: bestEffort ?? undefined,
deps: input.deps,
gateway,
mirror:
input.sessionKey && !dryRun
? {
sessionKey: input.sessionKey,
agentId: input.agentId,
}
: undefined,
});
return {

View File

@@ -42,6 +42,10 @@ type MessageSendParams = {
cfg?: ClawdbotConfig;
gateway?: MessageGatewayOptions;
idempotencyKey?: string;
mirror?: {
sessionKey: string;
agentId?: string;
};
};
export type MessageSendResult = {
@@ -142,6 +146,13 @@ export async function sendMessage(params: MessageSendParams): Promise<MessageSen
gifPlayback: params.gifPlayback,
deps: params.deps,
bestEffort: params.bestEffort,
mirror: params.mirror
? {
...params.mirror,
text: params.content,
mediaUrls: params.mediaUrl ? [params.mediaUrl] : undefined,
}
: undefined,
});
return {
@@ -165,6 +176,7 @@ export async function sendMessage(params: MessageSendParams): Promise<MessageSen
gifPlayback: params.gifPlayback,
accountId: params.accountId,
channel,
sessionKey: params.mirror?.sessionKey,
idempotencyKey: params.idempotencyKey ?? randomIdempotencyKey(),
},
timeoutMs: gateway.timeoutMs,