feat(msteams): add outbound sends and fix reply delivery

- Add sendMessageMSTeams for proactive messaging via CLI/gateway
- Wire msteams into outbound delivery, heartbeat targets, and gateway send
- Fix reply delivery to use SDK's getConversationReference() for proper
  bot info, avoiding "Activity Recipient undefined" errors
- Use proactive messaging for replies to post as top-level messages
  (not threaded) by omitting activityId from conversation reference
- Add lazy logger in send.ts to avoid test initialization issues
This commit is contained in:
Onur
2026-01-08 03:22:16 +03:00
committed by Peter Steinberger
parent 2c7d5c82f3
commit 269a3c4000
7 changed files with 340 additions and 33 deletions

View File

@@ -7,6 +7,7 @@ import type { ReplyPayload } from "../../auto-reply/types.js";
import type { ClawdbotConfig } from "../../config/config.js";
import { sendMessageDiscord } from "../../discord/send.js";
import { sendMessageIMessage } from "../../imessage/send.js";
import { sendMessageMSTeams } from "../../msteams/send.js";
import { normalizeAccountId } from "../../routing/session-key.js";
import { sendMessageSignal } from "../../signal/send.js";
import { sendMessageSlack } from "../../slack/send.js";
@@ -28,6 +29,11 @@ export type OutboundSendDeps = {
sendSlack?: typeof sendMessageSlack;
sendSignal?: typeof sendMessageSignal;
sendIMessage?: typeof sendMessageIMessage;
sendMSTeams?: (
to: string,
text: string,
opts?: { mediaUrl?: string },
) => Promise<{ messageId: string; conversationId: string }>;
};
export type OutboundDeliveryResult =
@@ -36,7 +42,8 @@ export type OutboundDeliveryResult =
| { provider: "discord"; messageId: string; channelId: string }
| { provider: "slack"; messageId: string; channelId: string }
| { provider: "signal"; messageId: string; timestamp?: number }
| { provider: "imessage"; messageId: string };
| { provider: "imessage"; messageId: string }
| { provider: "msteams"; messageId: string; conversationId: string };
type Chunker = (text: string, limit: number) => string[];
@@ -50,6 +57,7 @@ const providerCaps: Record<
slack: { chunker: null },
signal: { chunker: chunkText },
imessage: { chunker: chunkText },
msteams: { chunker: chunkMarkdownText },
};
type ProviderHandler = {
@@ -204,6 +212,17 @@ function createProviderHandler(params: {
})),
}),
},
msteams: {
chunker: providerCaps.msteams.chunker,
sendText: async (text) => ({
provider: "msteams",
...(await deps.sendMSTeams(to, text)),
}),
sendMedia: async (caption, mediaUrl) => ({
provider: "msteams",
...(await deps.sendMSTeams(to, caption, { mediaUrl })),
}),
},
};
return handlers[params.provider];
@@ -222,6 +241,11 @@ export async function deliverOutboundPayloads(params: {
}): Promise<OutboundDeliveryResult[]> {
const { cfg, provider, to, payloads } = params;
const accountId = params.accountId;
const defaultSendMSTeams = async (
to: string,
text: string,
opts?: { mediaUrl?: string },
) => sendMessageMSTeams({ cfg, to, text, mediaUrl: opts?.mediaUrl });
const deps = {
sendWhatsApp: params.deps?.sendWhatsApp ?? sendMessageWhatsApp,
sendTelegram: params.deps?.sendTelegram ?? sendMessageTelegram,
@@ -229,6 +253,7 @@ export async function deliverOutboundPayloads(params: {
sendSlack: params.deps?.sendSlack ?? sendMessageSlack,
sendSignal: params.deps?.sendSignal ?? sendMessageSignal,
sendIMessage: params.deps?.sendIMessage ?? sendMessageIMessage,
sendMSTeams: params.deps?.sendMSTeams ?? defaultSendMSTeams,
};
const results: OutboundDeliveryResult[] = [];
const handler = createProviderHandler({

View File

@@ -9,6 +9,7 @@ export type OutboundProvider =
| "slack"
| "signal"
| "imessage"
| "msteams"
| "none";
export type HeartbeatTarget = OutboundProvider | "last";
@@ -31,6 +32,7 @@ export function resolveOutboundTarget(params: {
| "slack"
| "signal"
| "imessage"
| "msteams"
| "webchat";
to?: string;
allowFrom?: string[];
@@ -104,6 +106,17 @@ export function resolveOutboundTarget(params: {
}
return { ok: true, to: trimmed };
}
if (params.provider === "msteams") {
if (!trimmed) {
return {
ok: false,
error: new Error(
"Delivering to MS Teams requires --to <conversationId|user:ID|conversation:ID>",
),
};
}
return { ok: true, to: trimmed };
}
return {
ok: false,
error: new Error(
@@ -125,6 +138,7 @@ export function resolveHeartbeatDeliveryTarget(params: {
rawTarget === "slack" ||
rawTarget === "signal" ||
rawTarget === "imessage" ||
rawTarget === "msteams" ||
rawTarget === "none" ||
rawTarget === "last"
? rawTarget
@@ -152,6 +166,7 @@ export function resolveHeartbeatDeliveryTarget(params: {
| "slack"
| "signal"
| "imessage"
| "msteams"
| undefined =
target === "last"
? lastProvider
@@ -160,7 +175,8 @@ export function resolveHeartbeatDeliveryTarget(params: {
target === "discord" ||
target === "slack" ||
target === "signal" ||
target === "imessage"
target === "imessage" ||
target === "msteams"
? target
: undefined;