fix(msteams): resolve Graph chat ID for personal DM media downloads (#62219)#63063
Conversation
…penclaw#62219) Bot Framework personal DM conversation IDs use an opaque `a:...` format that the Graph `/chats/{chatId}/messages` endpoint rejects as "Invalid ThreadId". When the direct Bot Framework attachment download fails and the code falls back to the Graph API path, inbound media (images, files) is silently dropped. Resolve the real Graph chat ID via `resolveGraphChatId()` before constructing Graph message URLs, with conversation-store caching so subsequent messages skip the API lookup.
Greptile SummaryThis PR fixes a bug where Bot Framework personal DM conversation IDs in
Confidence Score: 4/5Safe to merge after fixing the cache-invalidation bug in One P1 finding: the advertised per-conversation caching of extensions/msteams/src/monitor-handler/message-handler.ts (caching logic) and extensions/msteams/src/conversation-store-helpers.ts (mergeStoredConversationReference needs a graphChatId preserve guard)
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a3eafaa652
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
mergeStoredConversationReference only preserved timezone from the existing entry — graphChatId was silently overwritten on every activity-triggered upsert, defeating the cache and causing repeated Graph API lookups on every DM turn. Mirror the existing timezone guard so graphChatId survives upserts that don't carry it.
💡 Codex Reviewopenclaw/extensions/msteams/src/monitor-handler/message-handler.ts Lines 516 to 520 in 061c21d Avoid resolving ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
|
@BradGroux could you help to have a look at this PR? |
…62219) (#63063) * fix(msteams): resolve Graph chat ID for personal DM media downloads (#62219) Bot Framework personal DM conversation IDs use an opaque `a:...` format that the Graph `/chats/{chatId}/messages` endpoint rejects as "Invalid ThreadId". When the direct Bot Framework attachment download fails and the code falls back to the Graph API path, inbound media (images, files) is silently dropped. Resolve the real Graph chat ID via `resolveGraphChatId()` before constructing Graph message URLs, with conversation-store caching so subsequent messages skip the API lookup. * fix(msteams): preserve graphChatId across conversation store upserts mergeStoredConversationReference only preserved timezone from the existing entry — graphChatId was silently overwritten on every activity-triggered upsert, defeating the cache and causing repeated Graph API lookups on every DM turn. Mirror the existing timezone guard so graphChatId survives upserts that don't carry it.
…penclaw#62219) (openclaw#63063) * fix(msteams): resolve Graph chat ID for personal DM media downloads (openclaw#62219) Bot Framework personal DM conversation IDs use an opaque `a:...` format that the Graph `/chats/{chatId}/messages` endpoint rejects as "Invalid ThreadId". When the direct Bot Framework attachment download fails and the code falls back to the Graph API path, inbound media (images, files) is silently dropped. Resolve the real Graph chat ID via `resolveGraphChatId()` before constructing Graph message URLs, with conversation-store caching so subsequent messages skip the API lookup. * fix(msteams): preserve graphChatId across conversation store upserts mergeStoredConversationReference only preserved timezone from the existing entry — graphChatId was silently overwritten on every activity-triggered upsert, defeating the cache and causing repeated Graph API lookups on every DM turn. Mirror the existing timezone guard so graphChatId survives upserts that don't carry it.
…penclaw#62219) (openclaw#63063) * fix(msteams): resolve Graph chat ID for personal DM media downloads (openclaw#62219) Bot Framework personal DM conversation IDs use an opaque `a:...` format that the Graph `/chats/{chatId}/messages` endpoint rejects as "Invalid ThreadId". When the direct Bot Framework attachment download fails and the code falls back to the Graph API path, inbound media (images, files) is silently dropped. Resolve the real Graph chat ID via `resolveGraphChatId()` before constructing Graph message URLs, with conversation-store caching so subsequent messages skip the API lookup. * fix(msteams): preserve graphChatId across conversation store upserts mergeStoredConversationReference only preserved timezone from the existing entry — graphChatId was silently overwritten on every activity-triggered upsert, defeating the cache and causing repeated Graph API lookups on every DM turn. Mirror the existing timezone guard so graphChatId survives upserts that don't carry it.
…penclaw#62219) (openclaw#63063) * fix(msteams): resolve Graph chat ID for personal DM media downloads (openclaw#62219) Bot Framework personal DM conversation IDs use an opaque `a:...` format that the Graph `/chats/{chatId}/messages` endpoint rejects as "Invalid ThreadId". When the direct Bot Framework attachment download fails and the code falls back to the Graph API path, inbound media (images, files) is silently dropped. Resolve the real Graph chat ID via `resolveGraphChatId()` before constructing Graph message URLs, with conversation-store caching so subsequent messages skip the API lookup. * fix(msteams): preserve graphChatId across conversation store upserts mergeStoredConversationReference only preserved timezone from the existing entry — graphChatId was silently overwritten on every activity-triggered upsert, defeating the cache and causing repeated Graph API lookups on every DM turn. Mirror the existing timezone guard so graphChatId survives upserts that don't carry it.
Summary
a:...format) are not valid Graph API chat IDs. When the direct Bot Framework attachment download fails and the code falls back to the Graph API path,buildMSTeamsGraphMessageUrlsconstructs URLs with the invalida:ID, Graph returns 404 "Invalid ThreadId", and inbound media (images, documents) is silently dropped.<media:document>/<media:image>placeholders but the agent receives no file content.resolveMSTeamsInboundMedia, resolve the real Graph chat ID viaresolveGraphChatId()for personal DMs witha:conversation IDs. The resolved ID is cached in the conversation store so subsequent messages skip the API lookup.smba.trafficmanager.net), the channel message path, and thebuildMSTeamsGraphMessageUrlsfunction itself are unchanged.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
Root Cause (if applicable)
translateMSTeamsDmConversationIdForGraphsynthesizes19:{userId}_{appId}@unq.gbl.spaceswhich is not always accepted by the Graph/chats/{chatId}/messagesendpoint. TheresolveGraphChatIdfunction (used in the send path for SharePoint uploads) was not used in the inbound media download path.buildMSTeamsGraphMessageUrlsis a valid Graph chat ID for the/chats/endpoint.smba.trafficmanager.net) is the primary path and usually succeeds. The Graph fallback is reached when the direct download fails (e.g., auth issues, unsupported attachment types). Thea:ID bug in the fallback path has been masked by the primary path working.Regression Test Plan (if applicable)
extensions/msteams/src/attachments.helpers.test.tsbuildMSTeamsGraphMessageUrlscorrectly uses a resolved Graph chat ID (not thea:format) for personal DMs.User-visible / Behavior Changes
Diagram (if applicable)
Security Impact (required)
tokenProvider.getAccessTokenandresolveGraphChatId)/me/chats?$filter=...) per personal DM conversation, cached after first resolution/me/chatscall uses the same token scope already used by the send path. The resolved chat ID is cached in the conversation store (same store used by send-context.ts).Repro + Verification
Environment
dmPolicy: "open",groupPolicy: "open"Steps
<media:image>or<media:document>placeholder but no file contentExpected
Actual
"graph media fetch empty"withhostedStatus: 403/attachmentStatus: 404Evidence
Before fix (live AKS pod):
After patching
trafficmanager.netinto auth allowlist (direct download fix, already on upstream/main):Graph fallback path validated via instrumented logs:
Confirmed the
a:conversation ID reachesbuildMSTeamsGraphMessageUrlsunchanged whenresolveGraphChatIdis not called.Human Verification (required)
resolveMSTeamsInboundMediaandbuildMSTeamsGraphMessageUrls19:format passes through unchanged),a:prefix detection, conversation store caching path/me/chatsresolution with delegated auth tokens (tested environment uses app-only tokens where/mereturns 400; theresolveGraphChatIdfunction is already used and tested in the send path)Review Conversations
Compatibility / Migration
Risks and Mitigations
resolveGraphChatIdcalls Graph/me/chatswhich requires delegated auth — may return null with app-only tokenstranslateMSTeamsDmConversationIdForGraphID (same behavior as before this fix). The primary download path (smba.trafficmanager.net) handles most cases; this fix improves the Graph fallback path.