feat(msteams): add MS Teams provider skeleton

- Add Microsoft 365 Agents SDK packages (@microsoft/agents-hosting,
  @microsoft/agents-hosting-express, @microsoft/agents-hosting-extensions-teams)
- Add MSTeamsConfig type and zod schema
- Create src/msteams/ provider with monitor, token, send, probe
- Wire provider into gateway (server-providers.ts, server.ts)
- Add msteams to all provider type unions (hooks, queue, cron, etc.)
- Update implementation guide with new SDK and progress
This commit is contained in:
Onur
2026-01-07 21:29:39 +03:00
committed by Peter Steinberger
parent 7274d6e757
commit d9cbecac7f
16 changed files with 708 additions and 41 deletions

View File

@@ -25,7 +25,8 @@ export type HookMappingResolved = {
| "discord"
| "slack"
| "signal"
| "imessage";
| "imessage"
| "msteams";
to?: string;
model?: string;
thinking?: string;
@@ -65,7 +66,8 @@ export type HookAction =
| "discord"
| "slack"
| "signal"
| "imessage";
| "imessage"
| "msteams";
to?: string;
model?: string;
thinking?: string;

View File

@@ -39,7 +39,8 @@ type HookDispatchers = {
| "discord"
| "slack"
| "signal"
| "imessage";
| "imessage"
| "msteams";
to?: string;
model?: string;
thinking?: string;

View File

@@ -88,6 +88,14 @@ export type IMessageRuntimeStatus = {
dbPath?: string | null;
};
export type MSTeamsRuntimeStatus = {
running: boolean;
lastStartAt?: number | null;
lastStopAt?: number | null;
lastError?: string | null;
port?: number | null;
};
export type ProviderRuntimeSnapshot = {
whatsapp: WebProviderStatus;
whatsappAccounts?: Record<string, WebProviderStatus>;
@@ -101,6 +109,7 @@ export type ProviderRuntimeSnapshot = {
signalAccounts?: Record<string, SignalRuntimeStatus>;
imessage: IMessageRuntimeStatus;
imessageAccounts?: Record<string, IMessageRuntimeStatus>;
msteams: MSTeamsRuntimeStatus;
};
type SubsystemLogger = ReturnType<typeof createSubsystemLogger>;
@@ -113,12 +122,14 @@ type ProviderManagerOptions = {
logSlack: SubsystemLogger;
logSignal: SubsystemLogger;
logIMessage: SubsystemLogger;
logMSTeams: SubsystemLogger;
whatsappRuntimeEnv: RuntimeEnv;
telegramRuntimeEnv: RuntimeEnv;
discordRuntimeEnv: RuntimeEnv;
slackRuntimeEnv: RuntimeEnv;
signalRuntimeEnv: RuntimeEnv;
imessageRuntimeEnv: RuntimeEnv;
msteamsRuntimeEnv: RuntimeEnv;
};
export type ProviderManager = {
@@ -136,6 +147,8 @@ export type ProviderManager = {
stopSignalProvider: (accountId?: string) => Promise<void>;
startIMessageProvider: (accountId?: string) => Promise<void>;
stopIMessageProvider: (accountId?: string) => Promise<void>;
startMSTeamsProvider: () => Promise<void>;
stopMSTeamsProvider: () => Promise<void>;
markWhatsAppLoggedOut: (cleared: boolean, accountId?: string) => void;
};
@@ -150,12 +163,14 @@ export function createProviderManager(
logSlack,
logSignal,
logIMessage,
logMSTeams,
whatsappRuntimeEnv,
telegramRuntimeEnv,
discordRuntimeEnv,
slackRuntimeEnv,
signalRuntimeEnv,
imessageRuntimeEnv,
msteamsRuntimeEnv,
} = opts;
const whatsappAborts = new Map<string, AbortController>();
@@ -164,7 +179,9 @@ export function createProviderManager(
const slackAborts = new Map<string, AbortController>();
const signalAborts = new Map<string, AbortController>();
const imessageAborts = new Map<string, AbortController>();
let msteamsAbort: AbortController | null = null;
const whatsappTasks = new Map<string, Promise<unknown>>();
let msteamsTask: Promise<unknown> | null = null;
const telegramTasks = new Map<string, Promise<unknown>>();
const discordTasks = new Map<string, Promise<unknown>>();
const slackTasks = new Map<string, Promise<unknown>>();
@@ -224,6 +241,13 @@ export function createProviderManager(
cliPath: null,
dbPath: null,
});
let msteamsRuntime: MSTeamsRuntimeStatus = {
running: false,
lastStartAt: null,
lastStopAt: null,
lastError: null,
port: null,
};
const updateWhatsAppStatus = (accountId: string, next: WebProviderStatus) => {
whatsappRuntimes.set(accountId, next);
@@ -1026,6 +1050,83 @@ export function createProviderManager(
);
};
const startMSTeamsProvider = async () => {
if (msteamsTask) return;
const cfg = loadConfig();
if (!cfg.msteams) {
msteamsRuntime = {
...msteamsRuntime,
running: false,
lastError: "not configured",
};
if (shouldLogVerbose()) {
logMSTeams.debug("msteams provider not configured (no msteams config)");
}
return;
}
if (cfg.msteams?.enabled === false) {
msteamsRuntime = {
...msteamsRuntime,
running: false,
lastError: "disabled",
};
if (shouldLogVerbose()) {
logMSTeams.debug("msteams provider disabled (msteams.enabled=false)");
}
return;
}
const { monitorMSTeamsProvider } = await import("../msteams/index.js");
const port = cfg.msteams?.webhook?.port ?? 3978;
logMSTeams.info(`starting provider (port ${port})`);
msteamsAbort = new AbortController();
msteamsRuntime = {
...msteamsRuntime,
running: true,
lastStartAt: Date.now(),
lastError: null,
port,
};
const task = monitorMSTeamsProvider({
cfg,
runtime: msteamsRuntimeEnv,
abortSignal: msteamsAbort.signal,
})
.catch((err) => {
msteamsRuntime = {
...msteamsRuntime,
lastError: formatError(err),
};
logMSTeams.error(`provider exited: ${formatError(err)}`);
})
.finally(() => {
msteamsAbort = null;
msteamsTask = null;
msteamsRuntime = {
...msteamsRuntime,
running: false,
lastStopAt: Date.now(),
};
});
msteamsTask = task;
};
const stopMSTeamsProvider = async () => {
if (!msteamsAbort && !msteamsTask) return;
msteamsAbort?.abort();
try {
await msteamsTask;
} catch {
// ignore
}
msteamsAbort = null;
msteamsTask = null;
msteamsRuntime = {
...msteamsRuntime,
running: false,
lastStopAt: Date.now(),
};
};
const startProviders = async () => {
await startWhatsAppProvider();
await startDiscordProvider();
@@ -1033,6 +1134,7 @@ export function createProviderManager(
await startTelegramProvider();
await startSignalProvider();
await startIMessageProvider();
await startMSTeamsProvider();
};
const markWhatsAppLoggedOut = (cleared: boolean, accountId?: string) => {
@@ -1180,6 +1282,7 @@ export function createProviderManager(
signalAccounts,
imessage,
imessageAccounts,
msteams: { ...msteamsRuntime },
};
};
@@ -1198,6 +1301,8 @@ export function createProviderManager(
stopSignalProvider,
startIMessageProvider,
stopIMessageProvider,
startMSTeamsProvider,
stopMSTeamsProvider,
markWhatsAppLoggedOut,
};
}

View File

@@ -183,6 +183,7 @@ const logDiscord = logProviders.child("discord");
const logSlack = logProviders.child("slack");
const logSignal = logProviders.child("signal");
const logIMessage = logProviders.child("imessage");
const logMSTeams = logProviders.child("msteams");
const canvasRuntime = runtimeForLogger(logCanvas);
const whatsappRuntimeEnv = runtimeForLogger(logWhatsApp);
const telegramRuntimeEnv = runtimeForLogger(logTelegram);
@@ -190,6 +191,7 @@ const discordRuntimeEnv = runtimeForLogger(logDiscord);
const slackRuntimeEnv = runtimeForLogger(logSlack);
const signalRuntimeEnv = runtimeForLogger(logSignal);
const imessageRuntimeEnv = runtimeForLogger(logIMessage);
const msteamsRuntimeEnv = runtimeForLogger(logMSTeams);
type GatewayModelChoice = ModelCatalogEntry;
@@ -501,7 +503,8 @@ export async function startGatewayServer(
| "discord"
| "slack"
| "signal"
| "imessage";
| "imessage"
| "msteams";
to?: string;
model?: string;
thinking?: string;
@@ -756,12 +759,14 @@ export async function startGatewayServer(
logSlack,
logSignal,
logIMessage,
logMSTeams,
whatsappRuntimeEnv,
telegramRuntimeEnv,
discordRuntimeEnv,
slackRuntimeEnv,
signalRuntimeEnv,
imessageRuntimeEnv,
msteamsRuntimeEnv,
});
const {
getRuntimeSnapshot,
@@ -772,12 +777,14 @@ export async function startGatewayServer(
startSlackProvider,
startSignalProvider,
startIMessageProvider,
startMSTeamsProvider,
stopWhatsAppProvider,
stopTelegramProvider,
stopDiscordProvider,
stopSlackProvider,
stopSignalProvider,
stopIMessageProvider,
stopMSTeamsProvider,
markWhatsAppLoggedOut,
} = providerManager;