@openclaw/msteams 2026.2.23 → 2026.2.25
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.
- package/CHANGELOG.md +12 -0
- package/package.json +1 -4
- package/src/monitor-handler/message-handler.authz.test.ts +96 -0
- package/src/monitor-handler/message-handler.ts +6 -6
- package/src/monitor-handler.file-consent.test.ts +220 -0
- package/src/monitor-handler.ts +20 -4
- package/src/reply-dispatcher.ts +1 -1
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/msteams",
|
|
3
|
-
"version": "2026.2.
|
|
3
|
+
"version": "2026.2.25",
|
|
4
4
|
"description": "OpenClaw Microsoft Teams channel plugin",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@microsoft/agents-hosting": "^1.3.1",
|
|
8
8
|
"express": "^5.2.1"
|
|
9
9
|
},
|
|
10
|
-
"devDependencies": {
|
|
11
|
-
"openclaw": "workspace:*"
|
|
12
|
-
},
|
|
13
10
|
"openclaw": {
|
|
14
11
|
"extensions": [
|
|
15
12
|
"./index.ts"
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { OpenClawConfig, PluginRuntime, RuntimeEnv } from "openclaw/plugin-sdk";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import type { MSTeamsMessageHandlerDeps } from "../monitor-handler.js";
|
|
4
|
+
import { setMSTeamsRuntime } from "../runtime.js";
|
|
5
|
+
import { createMSTeamsMessageHandler } from "./message-handler.js";
|
|
6
|
+
|
|
7
|
+
describe("msteams monitor handler authz", () => {
|
|
8
|
+
it("does not treat DM pairing-store entries as group allowlist entries", async () => {
|
|
9
|
+
const readAllowFromStore = vi.fn(async () => ["attacker-aad"]);
|
|
10
|
+
setMSTeamsRuntime({
|
|
11
|
+
logging: { shouldLogVerbose: () => false },
|
|
12
|
+
channel: {
|
|
13
|
+
debounce: {
|
|
14
|
+
resolveInboundDebounceMs: () => 0,
|
|
15
|
+
createInboundDebouncer: <T>(params: {
|
|
16
|
+
onFlush: (entries: T[]) => Promise<void>;
|
|
17
|
+
}): { enqueue: (entry: T) => Promise<void> } => ({
|
|
18
|
+
enqueue: async (entry: T) => {
|
|
19
|
+
await params.onFlush([entry]);
|
|
20
|
+
},
|
|
21
|
+
}),
|
|
22
|
+
},
|
|
23
|
+
pairing: {
|
|
24
|
+
readAllowFromStore,
|
|
25
|
+
upsertPairingRequest: vi.fn(async () => null),
|
|
26
|
+
},
|
|
27
|
+
text: {
|
|
28
|
+
hasControlCommand: () => false,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
} as unknown as PluginRuntime);
|
|
32
|
+
|
|
33
|
+
const conversationStore = {
|
|
34
|
+
upsert: vi.fn(async () => undefined),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const deps: MSTeamsMessageHandlerDeps = {
|
|
38
|
+
cfg: {
|
|
39
|
+
channels: {
|
|
40
|
+
msteams: {
|
|
41
|
+
dmPolicy: "pairing",
|
|
42
|
+
allowFrom: [],
|
|
43
|
+
groupPolicy: "allowlist",
|
|
44
|
+
groupAllowFrom: [],
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
} as OpenClawConfig,
|
|
48
|
+
runtime: { error: vi.fn() } as unknown as RuntimeEnv,
|
|
49
|
+
appId: "test-app",
|
|
50
|
+
adapter: {} as MSTeamsMessageHandlerDeps["adapter"],
|
|
51
|
+
tokenProvider: {
|
|
52
|
+
getAccessToken: vi.fn(async () => "token"),
|
|
53
|
+
},
|
|
54
|
+
textLimit: 4000,
|
|
55
|
+
mediaMaxBytes: 1024 * 1024,
|
|
56
|
+
conversationStore:
|
|
57
|
+
conversationStore as unknown as MSTeamsMessageHandlerDeps["conversationStore"],
|
|
58
|
+
pollStore: {
|
|
59
|
+
recordVote: vi.fn(async () => null),
|
|
60
|
+
} as unknown as MSTeamsMessageHandlerDeps["pollStore"],
|
|
61
|
+
log: {
|
|
62
|
+
info: vi.fn(),
|
|
63
|
+
debug: vi.fn(),
|
|
64
|
+
error: vi.fn(),
|
|
65
|
+
} as unknown as MSTeamsMessageHandlerDeps["log"],
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const handler = createMSTeamsMessageHandler(deps);
|
|
69
|
+
await handler({
|
|
70
|
+
activity: {
|
|
71
|
+
id: "msg-1",
|
|
72
|
+
type: "message",
|
|
73
|
+
text: "",
|
|
74
|
+
from: {
|
|
75
|
+
id: "attacker-id",
|
|
76
|
+
aadObjectId: "attacker-aad",
|
|
77
|
+
name: "Attacker",
|
|
78
|
+
},
|
|
79
|
+
recipient: {
|
|
80
|
+
id: "bot-id",
|
|
81
|
+
name: "Bot",
|
|
82
|
+
},
|
|
83
|
+
conversation: {
|
|
84
|
+
id: "19:group@thread.tacv2",
|
|
85
|
+
conversationType: "groupChat",
|
|
86
|
+
},
|
|
87
|
+
channelData: {},
|
|
88
|
+
attachments: [],
|
|
89
|
+
},
|
|
90
|
+
sendActivity: vi.fn(async () => undefined),
|
|
91
|
+
} as unknown as Parameters<typeof handler>[0]);
|
|
92
|
+
|
|
93
|
+
expect(readAllowFromStore).toHaveBeenCalledWith("msteams");
|
|
94
|
+
expect(conversationStore.upsert).not.toHaveBeenCalled();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -135,7 +135,8 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
|
|
135
135
|
|
|
136
136
|
// Check DM policy for direct messages.
|
|
137
137
|
const dmAllowFrom = msteamsCfg?.allowFrom ?? [];
|
|
138
|
-
const
|
|
138
|
+
const configuredDmAllowFrom = dmAllowFrom.map((v) => String(v));
|
|
139
|
+
const effectiveDmAllowFrom = [...configuredDmAllowFrom, ...storedAllowFrom];
|
|
139
140
|
if (isDirectMessage && msteamsCfg) {
|
|
140
141
|
const allowFrom = dmAllowFrom;
|
|
141
142
|
|
|
@@ -189,9 +190,7 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
|
|
189
190
|
(msteamsCfg.allowFrom && msteamsCfg.allowFrom.length > 0 ? msteamsCfg.allowFrom : []))
|
|
190
191
|
: [];
|
|
191
192
|
const effectiveGroupAllowFrom =
|
|
192
|
-
!isDirectMessage && msteamsCfg
|
|
193
|
-
? [...groupAllowFrom.map((v) => String(v)), ...storedAllowFrom]
|
|
194
|
-
: [];
|
|
193
|
+
!isDirectMessage && msteamsCfg ? groupAllowFrom.map((v) => String(v)) : [];
|
|
195
194
|
const teamId = activity.channelData?.team?.id;
|
|
196
195
|
const teamName = activity.channelData?.team?.name;
|
|
197
196
|
const channelName = activity.channelData?.channel?.name;
|
|
@@ -248,9 +247,10 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
|
|
248
247
|
}
|
|
249
248
|
}
|
|
250
249
|
|
|
250
|
+
const commandDmAllowFrom = isDirectMessage ? effectiveDmAllowFrom : configuredDmAllowFrom;
|
|
251
251
|
const ownerAllowedForCommands = isMSTeamsGroupAllowed({
|
|
252
252
|
groupPolicy: "allowlist",
|
|
253
|
-
allowFrom:
|
|
253
|
+
allowFrom: commandDmAllowFrom,
|
|
254
254
|
senderId,
|
|
255
255
|
senderName,
|
|
256
256
|
allowNameMatching: isDangerousNameMatchingEnabled(msteamsCfg),
|
|
@@ -266,7 +266,7 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
|
|
266
266
|
const commandGate = resolveControlCommandGate({
|
|
267
267
|
useAccessGroups,
|
|
268
268
|
authorizers: [
|
|
269
|
-
{ configured:
|
|
269
|
+
{ configured: commandDmAllowFrom.length > 0, allowed: ownerAllowedForCommands },
|
|
270
270
|
{ configured: effectiveGroupAllowFrom.length > 0, allowed: groupAllowedForCommands },
|
|
271
271
|
],
|
|
272
272
|
allowTextCommands: true,
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import type { OpenClawConfig, PluginRuntime, RuntimeEnv } from "openclaw/plugin-sdk";
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import type { MSTeamsConversationStore } from "./conversation-store.js";
|
|
4
|
+
import type { MSTeamsAdapter } from "./messenger.js";
|
|
5
|
+
import {
|
|
6
|
+
type MSTeamsActivityHandler,
|
|
7
|
+
type MSTeamsMessageHandlerDeps,
|
|
8
|
+
registerMSTeamsHandlers,
|
|
9
|
+
} from "./monitor-handler.js";
|
|
10
|
+
import { clearPendingUploads, getPendingUpload, storePendingUpload } from "./pending-uploads.js";
|
|
11
|
+
import type { MSTeamsPollStore } from "./polls.js";
|
|
12
|
+
import { setMSTeamsRuntime } from "./runtime.js";
|
|
13
|
+
import type { MSTeamsTurnContext } from "./sdk-types.js";
|
|
14
|
+
|
|
15
|
+
const fileConsentMockState = vi.hoisted(() => ({
|
|
16
|
+
uploadToConsentUrl: vi.fn(),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
vi.mock("./file-consent.js", async () => {
|
|
20
|
+
const actual = await vi.importActual<typeof import("./file-consent.js")>("./file-consent.js");
|
|
21
|
+
return {
|
|
22
|
+
...actual,
|
|
23
|
+
uploadToConsentUrl: fileConsentMockState.uploadToConsentUrl,
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const runtimeStub: PluginRuntime = {
|
|
28
|
+
logging: {
|
|
29
|
+
shouldLogVerbose: () => false,
|
|
30
|
+
},
|
|
31
|
+
channel: {
|
|
32
|
+
debounce: {
|
|
33
|
+
resolveInboundDebounceMs: () => 0,
|
|
34
|
+
createInboundDebouncer: () => ({
|
|
35
|
+
enqueue: async () => {},
|
|
36
|
+
}),
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
} as unknown as PluginRuntime;
|
|
40
|
+
|
|
41
|
+
function createDeps(): MSTeamsMessageHandlerDeps {
|
|
42
|
+
const adapter: MSTeamsAdapter = {
|
|
43
|
+
continueConversation: async () => {},
|
|
44
|
+
process: async () => {},
|
|
45
|
+
};
|
|
46
|
+
const conversationStore: MSTeamsConversationStore = {
|
|
47
|
+
upsert: async () => {},
|
|
48
|
+
get: async () => null,
|
|
49
|
+
list: async () => [],
|
|
50
|
+
remove: async () => false,
|
|
51
|
+
findByUserId: async () => null,
|
|
52
|
+
};
|
|
53
|
+
const pollStore: MSTeamsPollStore = {
|
|
54
|
+
createPoll: async () => {},
|
|
55
|
+
getPoll: async () => null,
|
|
56
|
+
recordVote: async () => null,
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
cfg: {} as OpenClawConfig,
|
|
60
|
+
runtime: {
|
|
61
|
+
error: vi.fn(),
|
|
62
|
+
} as unknown as RuntimeEnv,
|
|
63
|
+
appId: "test-app-id",
|
|
64
|
+
adapter,
|
|
65
|
+
tokenProvider: {
|
|
66
|
+
getAccessToken: async () => "token",
|
|
67
|
+
},
|
|
68
|
+
textLimit: 4000,
|
|
69
|
+
mediaMaxBytes: 8 * 1024 * 1024,
|
|
70
|
+
conversationStore,
|
|
71
|
+
pollStore,
|
|
72
|
+
log: {
|
|
73
|
+
info: vi.fn(),
|
|
74
|
+
error: vi.fn(),
|
|
75
|
+
debug: vi.fn(),
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function createActivityHandler(): MSTeamsActivityHandler {
|
|
81
|
+
let handler: MSTeamsActivityHandler;
|
|
82
|
+
handler = {
|
|
83
|
+
onMessage: () => handler,
|
|
84
|
+
onMembersAdded: () => handler,
|
|
85
|
+
run: async () => {},
|
|
86
|
+
};
|
|
87
|
+
return handler;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function createInvokeContext(params: {
|
|
91
|
+
conversationId: string;
|
|
92
|
+
uploadId: string;
|
|
93
|
+
action: "accept" | "decline";
|
|
94
|
+
}): { context: MSTeamsTurnContext; sendActivity: ReturnType<typeof vi.fn> } {
|
|
95
|
+
const sendActivity = vi.fn(async () => ({ id: "activity-id" }));
|
|
96
|
+
const uploadInfo =
|
|
97
|
+
params.action === "accept"
|
|
98
|
+
? {
|
|
99
|
+
name: "secret.txt",
|
|
100
|
+
uploadUrl: "https://upload.example.com/put",
|
|
101
|
+
contentUrl: "https://content.example.com/file",
|
|
102
|
+
uniqueId: "unique-id",
|
|
103
|
+
fileType: "txt",
|
|
104
|
+
}
|
|
105
|
+
: undefined;
|
|
106
|
+
return {
|
|
107
|
+
context: {
|
|
108
|
+
activity: {
|
|
109
|
+
type: "invoke",
|
|
110
|
+
name: "fileConsent/invoke",
|
|
111
|
+
conversation: { id: params.conversationId },
|
|
112
|
+
value: {
|
|
113
|
+
type: "fileUpload",
|
|
114
|
+
action: params.action,
|
|
115
|
+
uploadInfo,
|
|
116
|
+
context: { uploadId: params.uploadId },
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
sendActivity,
|
|
120
|
+
sendActivities: async () => [],
|
|
121
|
+
} as unknown as MSTeamsTurnContext,
|
|
122
|
+
sendActivity,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
describe("msteams file consent invoke authz", () => {
|
|
127
|
+
beforeEach(() => {
|
|
128
|
+
setMSTeamsRuntime(runtimeStub);
|
|
129
|
+
clearPendingUploads();
|
|
130
|
+
fileConsentMockState.uploadToConsentUrl.mockReset();
|
|
131
|
+
fileConsentMockState.uploadToConsentUrl.mockResolvedValue(undefined);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("uploads when invoke conversation matches pending upload conversation", async () => {
|
|
135
|
+
const uploadId = storePendingUpload({
|
|
136
|
+
buffer: Buffer.from("TOP_SECRET_VICTIM_FILE\n"),
|
|
137
|
+
filename: "secret.txt",
|
|
138
|
+
contentType: "text/plain",
|
|
139
|
+
conversationId: "19:victim@thread.v2",
|
|
140
|
+
});
|
|
141
|
+
const deps = createDeps();
|
|
142
|
+
const handler = registerMSTeamsHandlers(createActivityHandler(), deps);
|
|
143
|
+
const { context, sendActivity } = createInvokeContext({
|
|
144
|
+
conversationId: "19:victim@thread.v2;messageid=abc123",
|
|
145
|
+
uploadId,
|
|
146
|
+
action: "accept",
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
await handler.run?.(context);
|
|
150
|
+
|
|
151
|
+
expect(fileConsentMockState.uploadToConsentUrl).toHaveBeenCalledTimes(1);
|
|
152
|
+
expect(fileConsentMockState.uploadToConsentUrl).toHaveBeenCalledWith(
|
|
153
|
+
expect.objectContaining({
|
|
154
|
+
url: "https://upload.example.com/put",
|
|
155
|
+
}),
|
|
156
|
+
);
|
|
157
|
+
expect(getPendingUpload(uploadId)).toBeUndefined();
|
|
158
|
+
expect(sendActivity).toHaveBeenCalledWith(
|
|
159
|
+
expect.objectContaining({
|
|
160
|
+
type: "invokeResponse",
|
|
161
|
+
}),
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("rejects cross-conversation accept invoke and keeps pending upload", async () => {
|
|
166
|
+
const uploadId = storePendingUpload({
|
|
167
|
+
buffer: Buffer.from("TOP_SECRET_VICTIM_FILE\n"),
|
|
168
|
+
filename: "secret.txt",
|
|
169
|
+
contentType: "text/plain",
|
|
170
|
+
conversationId: "19:victim@thread.v2",
|
|
171
|
+
});
|
|
172
|
+
const deps = createDeps();
|
|
173
|
+
const handler = registerMSTeamsHandlers(createActivityHandler(), deps);
|
|
174
|
+
const { context, sendActivity } = createInvokeContext({
|
|
175
|
+
conversationId: "19:attacker@thread.v2",
|
|
176
|
+
uploadId,
|
|
177
|
+
action: "accept",
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
await handler.run?.(context);
|
|
181
|
+
|
|
182
|
+
expect(fileConsentMockState.uploadToConsentUrl).not.toHaveBeenCalled();
|
|
183
|
+
expect(getPendingUpload(uploadId)).toBeDefined();
|
|
184
|
+
expect(sendActivity).toHaveBeenCalledWith(
|
|
185
|
+
"The file upload request has expired. Please try sending the file again.",
|
|
186
|
+
);
|
|
187
|
+
expect(sendActivity).toHaveBeenCalledWith(
|
|
188
|
+
expect.objectContaining({
|
|
189
|
+
type: "invokeResponse",
|
|
190
|
+
}),
|
|
191
|
+
);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("ignores cross-conversation decline invoke and keeps pending upload", async () => {
|
|
195
|
+
const uploadId = storePendingUpload({
|
|
196
|
+
buffer: Buffer.from("TOP_SECRET_VICTIM_FILE\n"),
|
|
197
|
+
filename: "secret.txt",
|
|
198
|
+
contentType: "text/plain",
|
|
199
|
+
conversationId: "19:victim@thread.v2",
|
|
200
|
+
});
|
|
201
|
+
const deps = createDeps();
|
|
202
|
+
const handler = registerMSTeamsHandlers(createActivityHandler(), deps);
|
|
203
|
+
const { context, sendActivity } = createInvokeContext({
|
|
204
|
+
conversationId: "19:attacker@thread.v2",
|
|
205
|
+
uploadId,
|
|
206
|
+
action: "decline",
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
await handler.run?.(context);
|
|
210
|
+
|
|
211
|
+
expect(fileConsentMockState.uploadToConsentUrl).not.toHaveBeenCalled();
|
|
212
|
+
expect(getPendingUpload(uploadId)).toBeDefined();
|
|
213
|
+
expect(sendActivity).toHaveBeenCalledTimes(1);
|
|
214
|
+
expect(sendActivity).toHaveBeenCalledWith(
|
|
215
|
+
expect.objectContaining({
|
|
216
|
+
type: "invokeResponse",
|
|
217
|
+
}),
|
|
218
|
+
);
|
|
219
|
+
});
|
|
220
|
+
});
|
package/src/monitor-handler.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { OpenClawConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
|
2
2
|
import type { MSTeamsConversationStore } from "./conversation-store.js";
|
|
3
3
|
import { buildFileInfoCard, parseFileConsentInvoke, uploadToConsentUrl } from "./file-consent.js";
|
|
4
|
+
import { normalizeMSTeamsConversationId } from "./inbound.js";
|
|
4
5
|
import type { MSTeamsAdapter } from "./messenger.js";
|
|
5
6
|
import { createMSTeamsMessageHandler } from "./monitor-handler/message-handler.js";
|
|
6
7
|
import type { MSTeamsMonitorLogger } from "./monitor-types.js";
|
|
@@ -42,6 +43,8 @@ async function handleFileConsentInvoke(
|
|
|
42
43
|
context: MSTeamsTurnContext,
|
|
43
44
|
log: MSTeamsMonitorLogger,
|
|
44
45
|
): Promise<boolean> {
|
|
46
|
+
const expiredUploadMessage =
|
|
47
|
+
"The file upload request has expired. Please try sending the file again.";
|
|
45
48
|
const activity = context.activity;
|
|
46
49
|
if (activity.type !== "invoke" || activity.name !== "fileConsent/invoke") {
|
|
47
50
|
return false;
|
|
@@ -57,9 +60,24 @@ async function handleFileConsentInvoke(
|
|
|
57
60
|
typeof consentResponse.context?.uploadId === "string"
|
|
58
61
|
? consentResponse.context.uploadId
|
|
59
62
|
: undefined;
|
|
63
|
+
const pendingFile = getPendingUpload(uploadId);
|
|
64
|
+
if (pendingFile) {
|
|
65
|
+
const pendingConversationId = normalizeMSTeamsConversationId(pendingFile.conversationId);
|
|
66
|
+
const invokeConversationId = normalizeMSTeamsConversationId(activity.conversation?.id ?? "");
|
|
67
|
+
if (!invokeConversationId || pendingConversationId !== invokeConversationId) {
|
|
68
|
+
log.info("file consent conversation mismatch", {
|
|
69
|
+
uploadId,
|
|
70
|
+
expectedConversationId: pendingConversationId,
|
|
71
|
+
receivedConversationId: invokeConversationId || undefined,
|
|
72
|
+
});
|
|
73
|
+
if (consentResponse.action === "accept") {
|
|
74
|
+
await context.sendActivity(expiredUploadMessage);
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
60
79
|
|
|
61
80
|
if (consentResponse.action === "accept" && consentResponse.uploadInfo) {
|
|
62
|
-
const pendingFile = getPendingUpload(uploadId);
|
|
63
81
|
if (pendingFile) {
|
|
64
82
|
log.debug?.("user accepted file consent, uploading", {
|
|
65
83
|
uploadId,
|
|
@@ -101,9 +119,7 @@ async function handleFileConsentInvoke(
|
|
|
101
119
|
}
|
|
102
120
|
} else {
|
|
103
121
|
log.debug?.("pending file not found for consent", { uploadId });
|
|
104
|
-
await context.sendActivity(
|
|
105
|
-
"The file upload request has expired. Please try sending the file again.",
|
|
106
|
-
);
|
|
122
|
+
await context.sendActivity(expiredUploadMessage);
|
|
107
123
|
}
|
|
108
124
|
} else {
|
|
109
125
|
// User declined
|
package/src/reply-dispatcher.ts
CHANGED
|
@@ -68,6 +68,7 @@ export function createMSTeamsReplyDispatcher(params: {
|
|
|
68
68
|
core.channel.reply.createReplyDispatcherWithTyping({
|
|
69
69
|
...prefixOptions,
|
|
70
70
|
humanDelay: core.channel.reply.resolveHumanDelayConfig(params.cfg, params.agentId),
|
|
71
|
+
typingCallbacks,
|
|
71
72
|
deliver: async (payload) => {
|
|
72
73
|
const tableMode = core.channel.text.resolveMarkdownTableMode({
|
|
73
74
|
cfg: params.cfg,
|
|
@@ -121,7 +122,6 @@ export function createMSTeamsReplyDispatcher(params: {
|
|
|
121
122
|
hint,
|
|
122
123
|
});
|
|
123
124
|
},
|
|
124
|
-
onReplyStart: typingCallbacks.onReplyStart,
|
|
125
125
|
});
|
|
126
126
|
|
|
127
127
|
return {
|