msteams: implement Teams AI agent UX best practices (#51808)

Migrates the Teams extension from @microsoft/agents-hosting to the official Teams SDK (@microsoft/teams.apps + @microsoft/teams.api) and implements Microsoft's AI UX best practices for Teams agents.

- AI-generated label on all bot messages (Teams native badge + thumbs up/down)
- Streaming responses in 1:1 chats via Teams streaminfo protocol
- Welcome card with configurable prompt starters on bot install
- Feedback with reflective learning (negative feedback triggers background reflection)
- Typing indicators for personal + group chats (disabled for channels)
- Informative status updates (progress bar while LLM processes)
- JWT validation via Teams SDK createServiceTokenValidator
- User-Agent: teams.ts[apps]/<sdk-version> OpenClaw/<version> on outbound requests
- Fix copy-pasted image downloads (smba.trafficmanager.net auth allowlist)
- Pre-parse auth gate (reject unauthenticated requests before body parsing)
- Reflection dispatcher lifecycle fix (prevent leaked dispatchers)
- Colon-safe session filenames (Windows compatibility)
- Cooldown cache eviction (prevent unbounded memory growth)

Closes #51806
This commit is contained in:
Sid Uppal
2026-03-23 22:03:39 -07:00
committed by GitHub
parent ea62655e19
commit cd90130877
39 changed files with 3349 additions and 690 deletions

View File

@@ -324,6 +324,11 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
return;
}
// Extract clientInfo entity (Teams sends this on every activity with timezone, locale, etc.)
const clientInfo = activity.entities?.find((e) => e.type === "clientInfo") as
| { timezone?: string; locale?: string; country?: string; platform?: string }
| undefined;
// Build conversation reference for proactive replies.
const agent = activity.recipient;
const conversationRef: StoredConversationReference = {
@@ -340,6 +345,8 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
channelId: activity.channelId,
serviceUrl: activity.serviceUrl,
locale: activity.locale,
// Only set timezone if present (preserve previously stored value on next upsert)
...(clientInfo?.timezone ? { timezone: clientInfo.timezone } : {}),
};
conversationStore.upsert(conversationId, conversationRef).catch((err) => {
log.debug?.("failed to save conversation reference", {
@@ -575,10 +582,26 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
sharePointSiteId,
});
// Use Teams clientInfo timezone if no explicit userTimezone is configured.
// This ensures the agent knows the sender's timezone for time-aware responses
// and proactive sends within the same session.
// Apply Teams clientInfo timezone if no explicit userTimezone is configured.
const senderTimezone = clientInfo?.timezone || conversationRef.timezone;
const effectiveCfg =
senderTimezone && !cfg.agents?.defaults?.userTimezone
? {
...cfg,
agents: {
...cfg.agents,
defaults: { ...cfg.agents?.defaults, userTimezone: senderTimezone },
},
}
: cfg;
log.info("dispatching to agent", { sessionKey: route.sessionKey });
try {
const { queuedFinal, counts } = await dispatchReplyFromConfigWithSettledDispatcher({
cfg,
cfg: effectiveCfg,
ctxPayload,
dispatcher,
onSettled: () => markDispatchIdle(),