@kodelyth/msteams 2026.5.42 → 2026.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/klaw.plugin.json +726 -2
  2. package/package.json +18 -6
  3. package/api.ts +0 -3
  4. package/channel-config-api.ts +0 -1
  5. package/channel-plugin-api.ts +0 -2
  6. package/config-api.ts +0 -4
  7. package/contract-api.ts +0 -4
  8. package/index.ts +0 -20
  9. package/runtime-api.ts +0 -66
  10. package/secret-contract-api.ts +0 -5
  11. package/setup-entry.ts +0 -13
  12. package/setup-plugin-api.ts +0 -3
  13. package/src/ai-entity.ts +0 -7
  14. package/src/approval-auth.ts +0 -44
  15. package/src/attachments/bot-framework.test.ts +0 -506
  16. package/src/attachments/bot-framework.ts +0 -348
  17. package/src/attachments/download.ts +0 -328
  18. package/src/attachments/graph.test.ts +0 -441
  19. package/src/attachments/graph.ts +0 -489
  20. package/src/attachments/html.ts +0 -122
  21. package/src/attachments/payload.ts +0 -14
  22. package/src/attachments/remote-media.test.ts +0 -187
  23. package/src/attachments/remote-media.ts +0 -86
  24. package/src/attachments/shared.test.ts +0 -547
  25. package/src/attachments/shared.ts +0 -655
  26. package/src/attachments/types.ts +0 -47
  27. package/src/attachments.graph.test.ts +0 -414
  28. package/src/attachments.helpers.test.ts +0 -245
  29. package/src/attachments.test-helpers.ts +0 -17
  30. package/src/attachments.test.ts +0 -754
  31. package/src/attachments.ts +0 -18
  32. package/src/block-streaming-config.test.ts +0 -61
  33. package/src/channel-api.ts +0 -1
  34. package/src/channel.actions.test.ts +0 -797
  35. package/src/channel.directory.test.ts +0 -176
  36. package/src/channel.message-adapter.test.ts +0 -227
  37. package/src/channel.runtime.ts +0 -56
  38. package/src/channel.setup.ts +0 -77
  39. package/src/channel.test.ts +0 -136
  40. package/src/channel.ts +0 -1176
  41. package/src/config-schema.ts +0 -6
  42. package/src/config-ui-hints.ts +0 -40
  43. package/src/conversation-store-fs.test.ts +0 -81
  44. package/src/conversation-store-fs.ts +0 -149
  45. package/src/conversation-store-helpers.test.ts +0 -202
  46. package/src/conversation-store-helpers.ts +0 -105
  47. package/src/conversation-store-memory.ts +0 -51
  48. package/src/conversation-store.shared.test.ts +0 -260
  49. package/src/conversation-store.ts +0 -71
  50. package/src/directory-live.test.ts +0 -156
  51. package/src/directory-live.ts +0 -111
  52. package/src/doctor.ts +0 -27
  53. package/src/errors.test.ts +0 -154
  54. package/src/errors.ts +0 -270
  55. package/src/feedback-reflection-prompt.ts +0 -117
  56. package/src/feedback-reflection-store.ts +0 -113
  57. package/src/feedback-reflection.test.ts +0 -237
  58. package/src/feedback-reflection.ts +0 -268
  59. package/src/file-consent-helpers.test.ts +0 -328
  60. package/src/file-consent-helpers.ts +0 -115
  61. package/src/file-consent-invoke.ts +0 -150
  62. package/src/file-consent.test.ts +0 -378
  63. package/src/file-consent.ts +0 -223
  64. package/src/graph-chat.ts +0 -36
  65. package/src/graph-group-management.test.ts +0 -332
  66. package/src/graph-group-management.ts +0 -168
  67. package/src/graph-members.test.ts +0 -89
  68. package/src/graph-members.ts +0 -48
  69. package/src/graph-messages.actions.test.ts +0 -253
  70. package/src/graph-messages.read.test.ts +0 -391
  71. package/src/graph-messages.search.test.ts +0 -227
  72. package/src/graph-messages.test-helpers.ts +0 -50
  73. package/src/graph-messages.ts +0 -534
  74. package/src/graph-teams.test.ts +0 -222
  75. package/src/graph-teams.ts +0 -114
  76. package/src/graph-thread.test.ts +0 -252
  77. package/src/graph-thread.ts +0 -146
  78. package/src/graph-upload.test.ts +0 -253
  79. package/src/graph-upload.ts +0 -531
  80. package/src/graph-users.ts +0 -29
  81. package/src/graph.test.ts +0 -540
  82. package/src/graph.ts +0 -308
  83. package/src/inbound.test.ts +0 -221
  84. package/src/inbound.ts +0 -148
  85. package/src/index.ts +0 -4
  86. package/src/media-helpers.test.ts +0 -220
  87. package/src/media-helpers.ts +0 -105
  88. package/src/mentions.test.ts +0 -254
  89. package/src/mentions.ts +0 -114
  90. package/src/messenger.test.ts +0 -961
  91. package/src/messenger.ts +0 -608
  92. package/src/monitor-handler/access.ts +0 -136
  93. package/src/monitor-handler/inbound-media.test.ts +0 -314
  94. package/src/monitor-handler/inbound-media.ts +0 -180
  95. package/src/monitor-handler/message-handler-mock-support.test-support.ts +0 -28
  96. package/src/monitor-handler/message-handler.authz.test.ts +0 -739
  97. package/src/monitor-handler/message-handler.dm-media.test.ts +0 -54
  98. package/src/monitor-handler/message-handler.test-support.ts +0 -99
  99. package/src/monitor-handler/message-handler.thread-parent.test.ts +0 -225
  100. package/src/monitor-handler/message-handler.thread-session.test.ts +0 -132
  101. package/src/monitor-handler/message-handler.ts +0 -1003
  102. package/src/monitor-handler/reaction-handler.test.ts +0 -325
  103. package/src/monitor-handler/reaction-handler.ts +0 -122
  104. package/src/monitor-handler/thread-session.ts +0 -30
  105. package/src/monitor-handler.adaptive-card.test.ts +0 -158
  106. package/src/monitor-handler.feedback-authz.test.ts +0 -357
  107. package/src/monitor-handler.file-consent.test.ts +0 -443
  108. package/src/monitor-handler.sso.test.ts +0 -576
  109. package/src/monitor-handler.test-helpers.ts +0 -181
  110. package/src/monitor-handler.ts +0 -538
  111. package/src/monitor-handler.types.ts +0 -27
  112. package/src/monitor-types.ts +0 -6
  113. package/src/monitor.lifecycle.test.ts +0 -457
  114. package/src/monitor.test.ts +0 -119
  115. package/src/monitor.ts +0 -476
  116. package/src/oauth.flow.ts +0 -77
  117. package/src/oauth.shared.ts +0 -37
  118. package/src/oauth.test.ts +0 -350
  119. package/src/oauth.token.ts +0 -162
  120. package/src/oauth.ts +0 -130
  121. package/src/outbound.test.ts +0 -400
  122. package/src/outbound.ts +0 -198
  123. package/src/pending-uploads-fs.test.ts +0 -261
  124. package/src/pending-uploads-fs.ts +0 -235
  125. package/src/pending-uploads.test.ts +0 -186
  126. package/src/pending-uploads.ts +0 -121
  127. package/src/policy.test.ts +0 -156
  128. package/src/policy.ts +0 -245
  129. package/src/polls-store-memory.ts +0 -32
  130. package/src/polls.test.ts +0 -169
  131. package/src/polls.ts +0 -312
  132. package/src/presentation.ts +0 -93
  133. package/src/probe.test.ts +0 -79
  134. package/src/probe.ts +0 -132
  135. package/src/reply-dispatcher.test.ts +0 -543
  136. package/src/reply-dispatcher.ts +0 -523
  137. package/src/reply-stream-controller.test.ts +0 -424
  138. package/src/reply-stream-controller.ts +0 -334
  139. package/src/resolve-allowlist.test.ts +0 -253
  140. package/src/resolve-allowlist.ts +0 -309
  141. package/src/revoked-context.ts +0 -17
  142. package/src/runtime.ts +0 -12
  143. package/src/sdk-types.ts +0 -59
  144. package/src/sdk.test.ts +0 -727
  145. package/src/sdk.ts +0 -916
  146. package/src/secret-contract.ts +0 -49
  147. package/src/secret-input.ts +0 -7
  148. package/src/send-context.test.ts +0 -93
  149. package/src/send-context.ts +0 -269
  150. package/src/send.test.ts +0 -588
  151. package/src/send.ts +0 -697
  152. package/src/sent-message-cache.test.ts +0 -106
  153. package/src/sent-message-cache.ts +0 -174
  154. package/src/session-route.ts +0 -40
  155. package/src/setup-core.ts +0 -162
  156. package/src/setup-surface.test.ts +0 -175
  157. package/src/setup-surface.ts +0 -319
  158. package/src/sso-token-store.test.ts +0 -74
  159. package/src/sso-token-store.ts +0 -166
  160. package/src/sso.ts +0 -300
  161. package/src/storage.ts +0 -25
  162. package/src/store-fs.ts +0 -42
  163. package/src/streaming-message.test.ts +0 -323
  164. package/src/streaming-message.ts +0 -327
  165. package/src/test-runtime.ts +0 -16
  166. package/src/thread-parent-context.test.ts +0 -224
  167. package/src/thread-parent-context.ts +0 -159
  168. package/src/token-response.ts +0 -11
  169. package/src/token.test.ts +0 -268
  170. package/src/token.ts +0 -194
  171. package/src/user-agent.test.ts +0 -121
  172. package/src/user-agent.ts +0 -53
  173. package/src/webhook-timeouts.ts +0 -27
  174. package/src/welcome-card.test.ts +0 -104
  175. package/src/welcome-card.ts +0 -57
  176. package/test-api.ts +0 -1
  177. package/tsconfig.json +0 -16
@@ -1,49 +0,0 @@
1
- import {
2
- collectSecretInputAssignment,
3
- getChannelRecord,
4
- type ResolverContext,
5
- type SecretDefaults,
6
- } from "klaw/plugin-sdk/channel-secret-basic-runtime";
7
-
8
- export const secretTargetRegistryEntries: import("klaw/plugin-sdk/channel-secret-basic-runtime").SecretTargetRegistryEntry[] =
9
- [
10
- {
11
- id: "channels.msteams.appPassword",
12
- targetType: "channels.msteams.appPassword",
13
- configFile: "klaw.json",
14
- pathPattern: "channels.msteams.appPassword",
15
- secretShape: "secret_input",
16
- expectedResolvedValue: "string",
17
- includeInPlan: true,
18
- includeInConfigure: true,
19
- includeInAudit: true,
20
- },
21
- ];
22
-
23
- export function collectRuntimeConfigAssignments(params: {
24
- config: { channels?: Record<string, unknown> };
25
- defaults?: SecretDefaults;
26
- context: ResolverContext;
27
- }): void {
28
- const msteams = getChannelRecord(params.config, "msteams");
29
- if (!msteams) {
30
- return;
31
- }
32
- collectSecretInputAssignment({
33
- value: msteams.appPassword,
34
- path: "channels.msteams.appPassword",
35
- expected: "string",
36
- defaults: params.defaults,
37
- context: params.context,
38
- active: msteams.enabled !== false,
39
- inactiveReason: "Microsoft Teams channel is disabled.",
40
- apply: (value) => {
41
- msteams.appPassword = value;
42
- },
43
- });
44
- }
45
-
46
- export const channelSecrets = {
47
- secretTargetRegistryEntries,
48
- collectRuntimeConfigAssignments,
49
- };
@@ -1,7 +0,0 @@
1
- import {
2
- hasConfiguredSecretInput,
3
- normalizeResolvedSecretInputString,
4
- normalizeSecretInputString,
5
- } from "klaw/plugin-sdk/secret-input";
6
-
7
- export { hasConfiguredSecretInput, normalizeResolvedSecretInputString, normalizeSecretInputString };
@@ -1,93 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import type { MSTeamsConfig } from "../runtime-api.js";
3
- import type { StoredConversationReference } from "./conversation-store.js";
4
- import { resolveMSTeamsProactiveReplyStyle } from "./send-context.js";
5
-
6
- function channelRef(params?: Partial<StoredConversationReference>): StoredConversationReference {
7
- return {
8
- user: { id: "user-1" },
9
- agent: { id: "agent-1" },
10
- conversation: { id: "19:channel@thread.tacv2", conversationType: "channel" },
11
- channelId: "msteams",
12
- teamId: "team-1",
13
- ...params,
14
- };
15
- }
16
-
17
- describe("resolveMSTeamsProactiveReplyStyle", () => {
18
- it("uses thread for channel conversations with a stored thread root", () => {
19
- expect(
20
- resolveMSTeamsProactiveReplyStyle({
21
- cfg: {},
22
- conversationId: "19:channel@thread.tacv2",
23
- ref: channelRef({ threadId: "thread-root-1" }),
24
- conversationType: "channel",
25
- }),
26
- ).toBe("thread");
27
- });
28
-
29
- it("falls back to activityId for legacy channel references", () => {
30
- expect(
31
- resolveMSTeamsProactiveReplyStyle({
32
- cfg: {},
33
- conversationId: "19:channel@thread.tacv2",
34
- ref: channelRef({ activityId: "legacy-root-1" }),
35
- conversationType: "channel",
36
- }),
37
- ).toBe("thread");
38
- });
39
-
40
- it("keeps configured top-level channel routing", () => {
41
- const cfg: MSTeamsConfig = {
42
- replyStyle: "thread",
43
- teams: {
44
- "team-1": {
45
- channels: {
46
- "19:channel@thread.tacv2": { replyStyle: "top-level" },
47
- },
48
- },
49
- },
50
- };
51
-
52
- expect(
53
- resolveMSTeamsProactiveReplyStyle({
54
- cfg,
55
- conversationId: "19:channel@thread.tacv2",
56
- ref: channelRef({ threadId: "thread-root-1" }),
57
- conversationType: "channel",
58
- }),
59
- ).toBe("top-level");
60
- });
61
-
62
- it("uses top-level when a channel has no stored thread root", () => {
63
- expect(
64
- resolveMSTeamsProactiveReplyStyle({
65
- cfg: { replyStyle: "thread" },
66
- conversationId: "19:channel@thread.tacv2",
67
- ref: channelRef(),
68
- conversationType: "channel",
69
- }),
70
- ).toBe("top-level");
71
- });
72
-
73
- it("uses top-level for non-channel conversations", () => {
74
- const ref = channelRef({ activityId: "activity-1" });
75
-
76
- expect(
77
- resolveMSTeamsProactiveReplyStyle({
78
- cfg: { replyStyle: "thread" },
79
- conversationId: "19:group@thread.v2",
80
- ref,
81
- conversationType: "groupChat",
82
- }),
83
- ).toBe("top-level");
84
- expect(
85
- resolveMSTeamsProactiveReplyStyle({
86
- cfg: { replyStyle: "thread" },
87
- conversationId: "a:personal",
88
- ref,
89
- conversationType: "personal",
90
- }),
91
- ).toBe("top-level");
92
- });
93
- });
@@ -1,269 +0,0 @@
1
- import { normalizeLowercaseStringOrEmpty } from "klaw/plugin-sdk/string-coerce-runtime";
2
- import {
3
- resolveChannelMediaMaxBytes,
4
- type MSTeamsConfig,
5
- type MSTeamsReplyStyle,
6
- type KlawConfig,
7
- type PluginRuntime,
8
- } from "../runtime-api.js";
9
- import type { MSTeamsAccessTokenProvider } from "./attachments/types.js";
10
- import { createMSTeamsConversationStoreFs } from "./conversation-store-fs.js";
11
- import type {
12
- MSTeamsConversationStore,
13
- StoredConversationReference,
14
- } from "./conversation-store.js";
15
- import { formatUnknownError } from "./errors.js";
16
- import { resolveGraphChatId } from "./graph-upload.js";
17
- import type { MSTeamsAdapter } from "./messenger.js";
18
- import { resolveMSTeamsReplyPolicy, resolveMSTeamsRouteConfig } from "./policy.js";
19
- import { getMSTeamsRuntime } from "./runtime.js";
20
- import { createMSTeamsAdapter, createMSTeamsTokenProvider, loadMSTeamsSdkWithAuth } from "./sdk.js";
21
- import { resolveMSTeamsCredentials } from "./token.js";
22
-
23
- type MSTeamsConversationType = "personal" | "groupChat" | "channel";
24
-
25
- export type MSTeamsProactiveContext = {
26
- appId: string;
27
- conversationId: string;
28
- ref: StoredConversationReference;
29
- adapter: MSTeamsAdapter;
30
- log: ReturnType<PluginRuntime["logging"]["getChildLogger"]>;
31
- /** The type of conversation: personal (1:1), groupChat, or channel */
32
- conversationType: MSTeamsConversationType;
33
- /** Reply style resolved for proactive text/media sends. */
34
- replyStyle: MSTeamsReplyStyle;
35
- /** Token provider for Graph API / OneDrive operations */
36
- tokenProvider: MSTeamsAccessTokenProvider;
37
- /** SharePoint site ID for file uploads in group chats/channels */
38
- sharePointSiteId?: string;
39
- /** Resolved media max bytes from config (default: 100MB) */
40
- mediaMaxBytes?: number;
41
- /**
42
- * Graph API-native chat ID for this conversation.
43
- * Bot Framework personal DM IDs (`a:1xxx` / `8:orgid:xxx`) cannot be used directly
44
- * with Graph chat endpoints. This field holds the resolved `19:xxx` format ID.
45
- * Null if resolution failed or not applicable.
46
- */
47
- graphChatId?: string | null;
48
- };
49
-
50
- export function resolveMSTeamsProactiveReplyStyle(params: {
51
- cfg?: MSTeamsConfig;
52
- conversationId: string;
53
- ref: StoredConversationReference;
54
- conversationType: MSTeamsConversationType;
55
- }): MSTeamsReplyStyle {
56
- const threadRootId = params.ref.threadId ?? params.ref.activityId;
57
- if (params.conversationType !== "channel" || !threadRootId) {
58
- return "top-level";
59
- }
60
-
61
- const routeConfig = resolveMSTeamsRouteConfig({
62
- cfg: params.cfg,
63
- teamId: params.ref.teamId,
64
- conversationId: params.conversationId,
65
- allowNameMatching: false,
66
- });
67
- const { replyStyle } = resolveMSTeamsReplyPolicy({
68
- isDirectMessage: false,
69
- globalConfig: params.cfg,
70
- teamConfig: routeConfig.teamConfig,
71
- channelConfig: routeConfig.channelConfig,
72
- });
73
- return replyStyle;
74
- }
75
-
76
- /**
77
- * Parse the target value into a conversation reference lookup key.
78
- * Supported formats:
79
- * - conversation:19:abc@thread.tacv2 → lookup by conversation ID
80
- * - user:aad-object-id → lookup by user AAD object ID
81
- * - 19:abc@thread.tacv2 → direct conversation ID
82
- */
83
- function parseRecipient(to: string): {
84
- type: "conversation" | "user";
85
- id: string;
86
- } {
87
- const trimmed = to.trim();
88
- const finalize = (type: "conversation" | "user", id: string) => {
89
- const normalized = id.trim();
90
- if (!normalized) {
91
- throw new Error(`Invalid target value: missing ${type} id`);
92
- }
93
- return { type, id: normalized };
94
- };
95
- if (trimmed.startsWith("conversation:")) {
96
- return finalize("conversation", trimmed.slice("conversation:".length));
97
- }
98
- if (trimmed.startsWith("user:")) {
99
- return finalize("user", trimmed.slice("user:".length));
100
- }
101
- // Assume it's a conversation ID if it looks like one
102
- if (trimmed.startsWith("19:") || trimmed.includes("@thread")) {
103
- return finalize("conversation", trimmed);
104
- }
105
- // Otherwise treat as user ID
106
- return finalize("user", trimmed);
107
- }
108
-
109
- /**
110
- * Find a stored conversation reference for the given recipient.
111
- */
112
- async function findConversationReference(recipient: {
113
- type: "conversation" | "user";
114
- id: string;
115
- store: MSTeamsConversationStore;
116
- }): Promise<{
117
- conversationId: string;
118
- ref: StoredConversationReference;
119
- } | null> {
120
- if (recipient.type === "conversation") {
121
- const ref = await recipient.store.get(recipient.id);
122
- if (ref) {
123
- return { conversationId: recipient.id, ref };
124
- }
125
- return null;
126
- }
127
-
128
- const found = await recipient.store.findPreferredDmByUserId(recipient.id);
129
- if (!found) {
130
- return null;
131
- }
132
- return { conversationId: found.conversationId, ref: found.reference };
133
- }
134
-
135
- export async function resolveMSTeamsSendContext(params: {
136
- cfg: KlawConfig;
137
- to: string;
138
- }): Promise<MSTeamsProactiveContext> {
139
- const msteamsCfg = params.cfg.channels?.msteams;
140
-
141
- if (!msteamsCfg?.enabled) {
142
- throw new Error("msteams provider is not enabled");
143
- }
144
-
145
- const creds = resolveMSTeamsCredentials(msteamsCfg);
146
- if (!creds) {
147
- throw new Error("msteams credentials not configured");
148
- }
149
-
150
- const store = createMSTeamsConversationStoreFs();
151
-
152
- // Parse recipient and find conversation reference
153
- const recipient = parseRecipient(params.to);
154
- const found = await findConversationReference({ ...recipient, store });
155
-
156
- if (!found) {
157
- throw new Error(
158
- `No conversation reference found for ${recipient.type}:${recipient.id}. ` +
159
- `The bot must receive a message from this conversation before it can send proactively.`,
160
- );
161
- }
162
-
163
- const { conversationId, ref } = found;
164
-
165
- // Safety check: when the caller targeted a specific user (DM), verify the
166
- // resolved conversation is actually a personal DM. Without this guard a
167
- // stale or mismatched conversation store could route a private DM reply
168
- // into a shared channel or group chat -- see #54520.
169
- if (recipient.type === "user") {
170
- const resolvedType = normalizeLowercaseStringOrEmpty(ref.conversation?.conversationType ?? "");
171
- if (resolvedType && resolvedType !== "personal") {
172
- throw new Error(
173
- `Conversation reference for user:${recipient.id} resolved to a ${resolvedType} ` +
174
- `conversation (${conversationId}) instead of a personal DM. ` +
175
- `The bot must receive a DM from this user before it can send proactively.`,
176
- );
177
- }
178
- }
179
- const core = getMSTeamsRuntime();
180
- const log = core.logging.getChildLogger({ name: "msteams:send" });
181
-
182
- const { sdk, app } = await loadMSTeamsSdkWithAuth(creds);
183
- const adapter = createMSTeamsAdapter(app, sdk);
184
-
185
- // Create token provider adapter for Graph API / OneDrive operations
186
- const tokenProvider: MSTeamsAccessTokenProvider = createMSTeamsTokenProvider(app);
187
-
188
- // Determine conversation type from stored reference
189
- const storedConversationType = normalizeLowercaseStringOrEmpty(
190
- ref.conversation?.conversationType ?? "",
191
- );
192
- let conversationType: MSTeamsConversationType;
193
- if (storedConversationType === "personal") {
194
- conversationType = "personal";
195
- } else if (storedConversationType === "channel") {
196
- conversationType = "channel";
197
- } else {
198
- // groupChat, or unknown defaults to groupChat behavior
199
- conversationType = "groupChat";
200
- }
201
- const replyStyle = resolveMSTeamsProactiveReplyStyle({
202
- cfg: msteamsCfg,
203
- conversationId,
204
- ref,
205
- conversationType,
206
- });
207
-
208
- // Get SharePoint site ID from config (required for file uploads in group chats/channels)
209
- const sharePointSiteId = msteamsCfg.sharePointSiteId;
210
-
211
- // Resolve media max bytes from config
212
- const mediaMaxBytes = resolveChannelMediaMaxBytes({
213
- cfg: params.cfg,
214
- resolveChannelLimitMb: ({ cfg }) => cfg.channels?.msteams?.mediaMaxMb,
215
- });
216
-
217
- // Resolve Graph API-native chat ID if needed for SharePoint per-user sharing.
218
- // Bot Framework personal DM conversation IDs (e.g. `a:1xxx` or `8:orgid:xxx`) cannot
219
- // be used directly with Graph /chats/{chatId} endpoints — the Graph API requires the
220
- // `19:xxx@thread.tacv2` or `19:xxx@unq.gbl.spaces` format.
221
- // We check the cached value first, then resolve via Graph API and cache for future sends.
222
- let graphChatId: string | null | undefined = ref.graphChatId ?? undefined;
223
- if (graphChatId === undefined && sharePointSiteId) {
224
- // Only resolve when SharePoint is configured (the only place chatId matters currently)
225
- try {
226
- const resolved = await resolveGraphChatId({
227
- botFrameworkConversationId: conversationId,
228
- userAadObjectId: ref.user?.aadObjectId,
229
- tokenProvider,
230
- });
231
- graphChatId = resolved;
232
-
233
- // Cache in the conversation store so subsequent sends skip the Graph lookup.
234
- // NOTE: We intentionally do NOT cache null results. Transient Graph API failures
235
- // (network, 401, rate limit) should be retried on subsequent sends rather than
236
- // permanently blocking file uploads for this conversation.
237
- if (resolved) {
238
- await store.upsert(conversationId, { ...ref, graphChatId: resolved });
239
- } else {
240
- log.warn?.("could not resolve Graph chat ID; file uploads may fail for this conversation", {
241
- conversationId,
242
- });
243
- }
244
- } catch (err) {
245
- log.warn?.(
246
- "failed to resolve Graph chat ID; file uploads may fall back to Bot Framework ID",
247
- {
248
- conversationId,
249
- error: formatUnknownError(err),
250
- },
251
- );
252
- graphChatId = null;
253
- }
254
- }
255
-
256
- return {
257
- appId: creds.appId,
258
- conversationId,
259
- ref,
260
- adapter: adapter as unknown as MSTeamsAdapter,
261
- log,
262
- conversationType,
263
- replyStyle,
264
- tokenProvider,
265
- sharePointSiteId,
266
- mediaMaxBytes,
267
- graphChatId,
268
- };
269
- }