@openclaw/msteams 2026.5.2 → 2026.5.3-beta.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.
- package/dist/api.js +3 -0
- package/dist/channel-D7hdreTh.js +984 -0
- package/dist/channel-config-api.js +2 -0
- package/dist/channel-plugin-api.js +2 -0
- package/dist/channel.runtime-BC1ruIfN.js +573 -0
- package/dist/config-schema-B8QezH6t.js +15 -0
- package/dist/contract-api.js +2 -0
- package/dist/graph-users-9uQJepqr.js +1354 -0
- package/dist/index.js +22 -0
- package/dist/oauth-BWJyilR1.js +114 -0
- package/dist/oauth.token-xxpoLWy5.js +115 -0
- package/dist/policy-DTnU2GR7.js +142 -0
- package/dist/probe-D_H8yFps.js +2194 -0
- package/dist/resolve-allowlist-D41JSziq.js +219 -0
- package/dist/runtime-api-DV1iVMn1.js +28 -0
- package/dist/runtime-api.js +2 -0
- package/dist/secret-contract-BuoEXmPS.js +35 -0
- package/dist/secret-contract-api.js +2 -0
- package/dist/setup-entry.js +15 -0
- package/dist/setup-plugin-api.js +64 -0
- package/dist/setup-surface-BLkFQYIQ.js +313 -0
- package/dist/src-CFp1QpFd.js +4064 -0
- package/dist/test-api.js +2 -0
- package/package.json +14 -6
- package/api.ts +0 -3
- package/channel-config-api.ts +0 -1
- package/channel-plugin-api.ts +0 -2
- package/config-api.ts +0 -4
- package/contract-api.ts +0 -4
- package/index.ts +0 -20
- package/runtime-api.ts +0 -73
- package/secret-contract-api.ts +0 -5
- package/setup-entry.ts +0 -13
- package/setup-plugin-api.ts +0 -3
- package/src/ai-entity.ts +0 -7
- package/src/approval-auth.ts +0 -44
- package/src/attachments/bot-framework.test.ts +0 -461
- package/src/attachments/bot-framework.ts +0 -362
- package/src/attachments/download.ts +0 -311
- package/src/attachments/graph.test.ts +0 -416
- package/src/attachments/graph.ts +0 -484
- package/src/attachments/html.ts +0 -122
- package/src/attachments/payload.ts +0 -14
- package/src/attachments/remote-media.test.ts +0 -137
- package/src/attachments/remote-media.ts +0 -112
- package/src/attachments/shared.test.ts +0 -530
- package/src/attachments/shared.ts +0 -626
- package/src/attachments/types.ts +0 -47
- package/src/attachments.graph.test.ts +0 -342
- package/src/attachments.helpers.test.ts +0 -246
- package/src/attachments.test-helpers.ts +0 -17
- package/src/attachments.test.ts +0 -687
- package/src/attachments.ts +0 -18
- package/src/block-streaming-config.test.ts +0 -61
- package/src/channel-api.ts +0 -1
- package/src/channel.actions.test.ts +0 -742
- package/src/channel.directory.test.ts +0 -200
- package/src/channel.runtime.ts +0 -56
- package/src/channel.setup.ts +0 -77
- package/src/channel.test.ts +0 -128
- package/src/channel.ts +0 -1136
- package/src/config-schema.ts +0 -6
- package/src/config-ui-hints.ts +0 -12
- package/src/conversation-store-fs.test.ts +0 -74
- package/src/conversation-store-fs.ts +0 -149
- package/src/conversation-store-helpers.test.ts +0 -202
- package/src/conversation-store-helpers.ts +0 -105
- package/src/conversation-store-memory.ts +0 -51
- package/src/conversation-store.shared.test.ts +0 -225
- package/src/conversation-store.ts +0 -71
- package/src/directory-live.test.ts +0 -156
- package/src/directory-live.ts +0 -111
- package/src/doctor.ts +0 -27
- package/src/errors.test.ts +0 -133
- package/src/errors.ts +0 -246
- package/src/feedback-reflection-prompt.ts +0 -117
- package/src/feedback-reflection-store.ts +0 -114
- package/src/feedback-reflection.test.ts +0 -237
- package/src/feedback-reflection.ts +0 -283
- package/src/file-consent-helpers.test.ts +0 -326
- package/src/file-consent-helpers.ts +0 -126
- package/src/file-consent-invoke.ts +0 -150
- package/src/file-consent.test.ts +0 -363
- package/src/file-consent.ts +0 -287
- package/src/graph-chat.ts +0 -55
- package/src/graph-group-management.test.ts +0 -318
- package/src/graph-group-management.ts +0 -168
- package/src/graph-members.test.ts +0 -89
- package/src/graph-members.ts +0 -48
- package/src/graph-messages.actions.test.ts +0 -243
- package/src/graph-messages.read.test.ts +0 -391
- package/src/graph-messages.search.test.ts +0 -213
- package/src/graph-messages.test-helpers.ts +0 -50
- package/src/graph-messages.ts +0 -534
- package/src/graph-teams.test.ts +0 -215
- package/src/graph-teams.ts +0 -114
- package/src/graph-thread.test.ts +0 -246
- package/src/graph-thread.ts +0 -146
- package/src/graph-upload.test.ts +0 -258
- package/src/graph-upload.ts +0 -531
- package/src/graph-users.ts +0 -29
- package/src/graph.test.ts +0 -516
- package/src/graph.ts +0 -293
- package/src/inbound.test.ts +0 -221
- package/src/inbound.ts +0 -148
- package/src/index.ts +0 -4
- package/src/media-helpers.test.ts +0 -202
- package/src/media-helpers.ts +0 -105
- package/src/mentions.test.ts +0 -244
- package/src/mentions.ts +0 -114
- package/src/messenger.test.ts +0 -865
- package/src/messenger.ts +0 -605
- package/src/monitor-handler/access.ts +0 -125
- package/src/monitor-handler/inbound-media.test.ts +0 -289
- package/src/monitor-handler/inbound-media.ts +0 -180
- package/src/monitor-handler/message-handler-mock-support.test-support.ts +0 -28
- package/src/monitor-handler/message-handler.authz.test.ts +0 -669
- package/src/monitor-handler/message-handler.dm-media.test.ts +0 -54
- package/src/monitor-handler/message-handler.test-support.ts +0 -100
- package/src/monitor-handler/message-handler.thread-parent.test.ts +0 -223
- package/src/monitor-handler/message-handler.thread-session.test.ts +0 -77
- package/src/monitor-handler/message-handler.ts +0 -1000
- package/src/monitor-handler/reaction-handler.test.ts +0 -267
- package/src/monitor-handler/reaction-handler.ts +0 -210
- package/src/monitor-handler/thread-session.ts +0 -17
- package/src/monitor-handler.adaptive-card.test.ts +0 -162
- package/src/monitor-handler.feedback-authz.test.ts +0 -314
- package/src/monitor-handler.file-consent.test.ts +0 -423
- package/src/monitor-handler.sso.test.ts +0 -563
- package/src/monitor-handler.test-helpers.ts +0 -180
- package/src/monitor-handler.ts +0 -534
- package/src/monitor-handler.types.ts +0 -27
- package/src/monitor-types.ts +0 -6
- package/src/monitor.lifecycle.test.ts +0 -278
- package/src/monitor.test.ts +0 -119
- package/src/monitor.ts +0 -442
- package/src/oauth.flow.ts +0 -77
- package/src/oauth.shared.ts +0 -37
- package/src/oauth.test.ts +0 -305
- package/src/oauth.token.ts +0 -158
- package/src/oauth.ts +0 -130
- package/src/outbound.test.ts +0 -130
- package/src/outbound.ts +0 -71
- package/src/pending-uploads-fs.test.ts +0 -246
- package/src/pending-uploads-fs.ts +0 -235
- package/src/pending-uploads.test.ts +0 -173
- package/src/pending-uploads.ts +0 -121
- package/src/policy.test.ts +0 -240
- package/src/policy.ts +0 -262
- package/src/polls-store-memory.ts +0 -32
- package/src/polls.test.ts +0 -160
- package/src/polls.ts +0 -323
- package/src/presentation.ts +0 -68
- package/src/probe.test.ts +0 -77
- package/src/probe.ts +0 -132
- package/src/reply-dispatcher.test.ts +0 -437
- package/src/reply-dispatcher.ts +0 -346
- package/src/reply-stream-controller.test.ts +0 -235
- package/src/reply-stream-controller.ts +0 -147
- package/src/resolve-allowlist.test.ts +0 -250
- package/src/resolve-allowlist.ts +0 -309
- package/src/revoked-context.ts +0 -17
- package/src/runtime.ts +0 -9
- package/src/sdk-types.ts +0 -59
- package/src/sdk.test.ts +0 -666
- package/src/sdk.ts +0 -884
- package/src/secret-contract.ts +0 -49
- package/src/secret-input.ts +0 -7
- package/src/send-context.ts +0 -231
- package/src/send.test.ts +0 -493
- package/src/send.ts +0 -637
- package/src/sent-message-cache.test.ts +0 -15
- package/src/sent-message-cache.ts +0 -56
- package/src/session-route.ts +0 -40
- package/src/setup-core.ts +0 -160
- package/src/setup-surface.test.ts +0 -202
- package/src/setup-surface.ts +0 -320
- package/src/sso-token-store.test.ts +0 -72
- package/src/sso-token-store.ts +0 -166
- package/src/sso.ts +0 -300
- package/src/storage.ts +0 -25
- package/src/store-fs.ts +0 -44
- package/src/streaming-message.test.ts +0 -262
- package/src/streaming-message.ts +0 -297
- package/src/test-runtime.ts +0 -16
- package/src/thread-parent-context.test.ts +0 -224
- package/src/thread-parent-context.ts +0 -159
- package/src/token-response.ts +0 -11
- package/src/token.test.ts +0 -259
- package/src/token.ts +0 -195
- package/src/user-agent.test.ts +0 -86
- package/src/user-agent.ts +0 -53
- package/src/webhook-timeouts.ts +0 -27
- package/src/welcome-card.test.ts +0 -81
- package/src/welcome-card.ts +0 -57
- package/test-api.ts +0 -1
- package/tsconfig.json +0 -16
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
import { s as chunkTextForOutbound } from "./runtime-api-DV1iVMn1.js";
|
|
2
|
+
import { a as fetchGraphJson, c as normalizeQuery, d as postGraphJson, f as resolveGraphToken, i as fetchGraphAbsoluteUrl, l as patchGraphJson, n as deleteGraphRequest, o as listChannelsForTeam, r as escapeOData, s as listTeamsByName, t as searchGraphUsers, u as postGraphBetaJson } from "./graph-users-9uQJepqr.js";
|
|
3
|
+
import { S as createMSTeamsConversationStoreFs, a as sendMessageMSTeams, b as createMSTeamsPollStoreFs, i as sendAdaptiveCardMSTeams, n as deleteMessageMSTeams, o as sendPollMSTeams, r as editMessageMSTeams, t as probeMSTeams } from "./probe-D_H8yFps.js";
|
|
4
|
+
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
|
5
|
+
import { createAttachedChannelResultAdapter } from "openclaw/plugin-sdk/channel-send-result";
|
|
6
|
+
import { resolveOutboundSendDep } from "openclaw/plugin-sdk/outbound-send-deps";
|
|
7
|
+
//#region extensions/msteams/src/directory-live.ts
|
|
8
|
+
async function listMSTeamsDirectoryPeersLive(params) {
|
|
9
|
+
const query = normalizeQuery(params.query);
|
|
10
|
+
if (!query) return [];
|
|
11
|
+
return (await searchGraphUsers({
|
|
12
|
+
token: await resolveGraphToken(params.cfg),
|
|
13
|
+
query,
|
|
14
|
+
top: typeof params.limit === "number" && params.limit > 0 ? params.limit : 20
|
|
15
|
+
})).map((user) => {
|
|
16
|
+
const id = user.id?.trim();
|
|
17
|
+
if (!id) return null;
|
|
18
|
+
const name = user.displayName?.trim();
|
|
19
|
+
const handle = user.userPrincipalName?.trim() || user.mail?.trim();
|
|
20
|
+
return {
|
|
21
|
+
kind: "user",
|
|
22
|
+
id: `user:${id}`,
|
|
23
|
+
name: name || void 0,
|
|
24
|
+
handle: handle ? `@${handle}` : void 0,
|
|
25
|
+
raw: user
|
|
26
|
+
};
|
|
27
|
+
}).filter(Boolean);
|
|
28
|
+
}
|
|
29
|
+
async function listMSTeamsDirectoryGroupsLive(params) {
|
|
30
|
+
const rawQuery = normalizeQuery(params.query);
|
|
31
|
+
if (!rawQuery) return [];
|
|
32
|
+
const token = await resolveGraphToken(params.cfg);
|
|
33
|
+
const limit = typeof params.limit === "number" && params.limit > 0 ? params.limit : 20;
|
|
34
|
+
const [teamQuery, channelQuery] = rawQuery.includes("/") ? rawQuery.split("/", 2).map((part) => part.trim()).filter(Boolean) : [rawQuery, null];
|
|
35
|
+
const teams = await listTeamsByName(token, teamQuery);
|
|
36
|
+
const results = [];
|
|
37
|
+
for (const team of teams) {
|
|
38
|
+
const teamId = team.id?.trim();
|
|
39
|
+
if (!teamId) continue;
|
|
40
|
+
const teamName = team.displayName?.trim() || teamQuery;
|
|
41
|
+
if (!channelQuery) {
|
|
42
|
+
results.push({
|
|
43
|
+
kind: "group",
|
|
44
|
+
id: `team:${teamId}`,
|
|
45
|
+
name: teamName,
|
|
46
|
+
handle: teamName ? `#${teamName}` : void 0,
|
|
47
|
+
raw: team
|
|
48
|
+
});
|
|
49
|
+
if (results.length >= limit) return results;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const channels = await listChannelsForTeam(token, teamId);
|
|
53
|
+
for (const channel of channels) {
|
|
54
|
+
const name = channel.displayName?.trim();
|
|
55
|
+
if (!name) continue;
|
|
56
|
+
if (!normalizeLowercaseStringOrEmpty(name).includes(normalizeLowercaseStringOrEmpty(channelQuery))) continue;
|
|
57
|
+
results.push({
|
|
58
|
+
kind: "group",
|
|
59
|
+
id: `conversation:${channel.id}`,
|
|
60
|
+
name: `${teamName}/${name}`,
|
|
61
|
+
handle: `#${name}`,
|
|
62
|
+
raw: channel
|
|
63
|
+
});
|
|
64
|
+
if (results.length >= limit) return results;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return results;
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region extensions/msteams/src/graph-messages.ts
|
|
71
|
+
/**
|
|
72
|
+
* Resolve the Graph API path prefix for a conversation.
|
|
73
|
+
* If `to` contains "/" it's a `teamId/channelId` (channel path),
|
|
74
|
+
* otherwise it's a chat ID.
|
|
75
|
+
*/
|
|
76
|
+
/**
|
|
77
|
+
* Strip common target prefixes (`conversation:`, `user:`) so raw
|
|
78
|
+
* conversation IDs can be used directly in Graph paths.
|
|
79
|
+
*/
|
|
80
|
+
function stripTargetPrefix(raw) {
|
|
81
|
+
const trimmed = raw.trim();
|
|
82
|
+
if (/^conversation:/i.test(trimmed)) return trimmed.slice(13).trim();
|
|
83
|
+
if (/^user:/i.test(trimmed)) return trimmed.slice(5).trim();
|
|
84
|
+
return trimmed;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Resolve a target to a Graph-compatible conversation ID.
|
|
88
|
+
* `user:<aadId>` targets are looked up in the conversation store to find the
|
|
89
|
+
* actual `19:xxx@thread.*` chat ID that Graph API requires.
|
|
90
|
+
* Conversation IDs and `teamId/channelId` pairs pass through unchanged.
|
|
91
|
+
*/
|
|
92
|
+
async function resolveGraphConversationId(to) {
|
|
93
|
+
const trimmed = to.trim();
|
|
94
|
+
const isUserTarget = /^user:/i.test(trimmed);
|
|
95
|
+
const cleaned = stripTargetPrefix(trimmed);
|
|
96
|
+
if (!isUserTarget) return cleaned;
|
|
97
|
+
const found = await createMSTeamsConversationStoreFs().findPreferredDmByUserId(cleaned);
|
|
98
|
+
if (!found) throw new Error(`No conversation found for user:${cleaned}. The bot must receive a message from this user before Graph API operations work.`);
|
|
99
|
+
if (found.reference.graphChatId) return found.reference.graphChatId;
|
|
100
|
+
if (found.conversationId.startsWith("19:")) return found.conversationId;
|
|
101
|
+
throw new Error(`Conversation for user:${cleaned} uses a Bot Framework ID (${found.conversationId}) that Graph API does not accept. Send a message to this user first so the Graph chat ID is cached.`);
|
|
102
|
+
}
|
|
103
|
+
function resolveConversationPath(to) {
|
|
104
|
+
const cleaned = stripTargetPrefix(to);
|
|
105
|
+
if (cleaned.includes("/")) {
|
|
106
|
+
const [teamId, channelId] = cleaned.split("/", 2);
|
|
107
|
+
return {
|
|
108
|
+
kind: "channel",
|
|
109
|
+
basePath: `/teams/${encodeURIComponent(teamId)}/channels/${encodeURIComponent(channelId)}`,
|
|
110
|
+
teamId,
|
|
111
|
+
channelId
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
kind: "chat",
|
|
116
|
+
basePath: `/chats/${encodeURIComponent(cleaned)}`,
|
|
117
|
+
chatId: cleaned
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Retrieve a single message by ID from a chat or channel via Graph API.
|
|
122
|
+
*/
|
|
123
|
+
async function getMessageMSTeams(params) {
|
|
124
|
+
const token = await resolveGraphToken(params.cfg);
|
|
125
|
+
const { basePath } = resolveConversationPath(await resolveGraphConversationId(params.to));
|
|
126
|
+
const msg = await fetchGraphJson({
|
|
127
|
+
token,
|
|
128
|
+
path: `${basePath}/messages/${encodeURIComponent(params.messageId)}`
|
|
129
|
+
});
|
|
130
|
+
return {
|
|
131
|
+
id: msg.id ?? params.messageId,
|
|
132
|
+
text: msg.body?.content,
|
|
133
|
+
from: msg.from,
|
|
134
|
+
createdAt: msg.createdDateTime
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Pin a message in a chat conversation via Graph API.
|
|
139
|
+
*
|
|
140
|
+
* Chat pinning uses the v1.0 endpoint: `POST /chats/{chatId}/pinnedMessages`.
|
|
141
|
+
*
|
|
142
|
+
* Channel pinning uses `POST /teams/{teamId}/channels/{channelId}/pinnedMessages`.
|
|
143
|
+
* **Note:** The channel pin endpoint may require the Graph beta API or specific
|
|
144
|
+
* tenant-level permissions. As of March 2026, general availability is not
|
|
145
|
+
* confirmed for all tenants. If the call returns 404 or 403, the endpoint may
|
|
146
|
+
* not be enabled for the target tenant.
|
|
147
|
+
*/
|
|
148
|
+
async function pinMessageMSTeams(params) {
|
|
149
|
+
const token = await resolveGraphToken(params.cfg);
|
|
150
|
+
const conversationId = await resolveGraphConversationId(params.to);
|
|
151
|
+
const conv = resolveConversationPath(conversationId);
|
|
152
|
+
if (conv.kind === "channel") throw new Error("Pin/unpin is not supported for channel messages on Graph v1.0. Only chat conversations support pinned messages.");
|
|
153
|
+
const body = { "message@odata.bind": `https://graph.microsoft.com/v1.0/chats/${encodeURIComponent(conversationId)}/messages/${encodeURIComponent(params.messageId)}` };
|
|
154
|
+
return {
|
|
155
|
+
ok: true,
|
|
156
|
+
pinnedMessageId: (await postGraphJson({
|
|
157
|
+
token,
|
|
158
|
+
path: `${conv.basePath}/pinnedMessages`,
|
|
159
|
+
body
|
|
160
|
+
})).id
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Unpin a message in a chat conversation via Graph API.
|
|
165
|
+
* `pinnedMessageId` is the pinned-message resource ID (from pin or list-pins),
|
|
166
|
+
* not the underlying chat message ID.
|
|
167
|
+
*
|
|
168
|
+
* Channel unpin uses `DELETE /teams/{teamId}/channels/{channelId}/pinnedMessages/{id}`.
|
|
169
|
+
* See the note on {@link pinMessageMSTeams} regarding beta/GA status.
|
|
170
|
+
*/
|
|
171
|
+
async function unpinMessageMSTeams(params) {
|
|
172
|
+
const token = await resolveGraphToken(params.cfg);
|
|
173
|
+
const conv = resolveConversationPath(await resolveGraphConversationId(params.to));
|
|
174
|
+
if (conv.kind === "channel") throw new Error("Pin/unpin is not supported for channel messages on Graph v1.0. Only chat conversations support pinned messages.");
|
|
175
|
+
await deleteGraphRequest({
|
|
176
|
+
token,
|
|
177
|
+
path: `${conv.basePath}/pinnedMessages/${encodeURIComponent(params.pinnedMessageId)}`
|
|
178
|
+
});
|
|
179
|
+
return { ok: true };
|
|
180
|
+
}
|
|
181
|
+
/** Maximum number of pagination pages to follow to avoid unbounded loops. */
|
|
182
|
+
const LIST_PINS_MAX_PAGES = 10;
|
|
183
|
+
/**
|
|
184
|
+
* List all pinned messages in a chat conversation via Graph API.
|
|
185
|
+
* Follows `@odata.nextLink` pagination to collect the full pin set.
|
|
186
|
+
*
|
|
187
|
+
* Channel list-pins uses the same endpoint pattern as channel pin/unpin.
|
|
188
|
+
* See the note on {@link pinMessageMSTeams} regarding beta/GA status.
|
|
189
|
+
*/
|
|
190
|
+
async function listPinsMSTeams(params) {
|
|
191
|
+
const token = await resolveGraphToken(params.cfg);
|
|
192
|
+
const conv = resolveConversationPath(await resolveGraphConversationId(params.to));
|
|
193
|
+
if (conv.kind === "channel") throw new Error("Listing pinned messages is not supported for channels on Graph v1.0. Only chat conversations support pinned messages.");
|
|
194
|
+
const path = `${conv.basePath}/pinnedMessages?$expand=message`;
|
|
195
|
+
const allPins = [];
|
|
196
|
+
let res = await fetchGraphJson({
|
|
197
|
+
token,
|
|
198
|
+
path
|
|
199
|
+
});
|
|
200
|
+
let pages = 1;
|
|
201
|
+
while (true) {
|
|
202
|
+
for (const pin of res.value ?? []) allPins.push({
|
|
203
|
+
id: pin.id ?? "",
|
|
204
|
+
pinnedMessageId: pin.id ?? "",
|
|
205
|
+
messageId: pin.message?.id,
|
|
206
|
+
text: pin.message?.body?.content
|
|
207
|
+
});
|
|
208
|
+
const nextLink = res["@odata.nextLink"];
|
|
209
|
+
if (!nextLink || pages >= LIST_PINS_MAX_PAGES) break;
|
|
210
|
+
res = await fetchGraphAbsoluteUrl({
|
|
211
|
+
token,
|
|
212
|
+
url: nextLink
|
|
213
|
+
});
|
|
214
|
+
pages++;
|
|
215
|
+
}
|
|
216
|
+
return { pins: allPins };
|
|
217
|
+
}
|
|
218
|
+
const TEAMS_REACTION_TYPES = [
|
|
219
|
+
"like",
|
|
220
|
+
"heart",
|
|
221
|
+
"laugh",
|
|
222
|
+
"surprised",
|
|
223
|
+
"sad",
|
|
224
|
+
"angry"
|
|
225
|
+
];
|
|
226
|
+
/** Map well-known reaction type names to representative emoji for CLI display. */
|
|
227
|
+
const REACTION_TYPE_EMOJI = {
|
|
228
|
+
like: "👍",
|
|
229
|
+
heart: "❤️",
|
|
230
|
+
laugh: "😆",
|
|
231
|
+
surprised: "😮",
|
|
232
|
+
sad: "😢",
|
|
233
|
+
angry: "😡"
|
|
234
|
+
};
|
|
235
|
+
/**
|
|
236
|
+
* Normalize a reaction type string. Graph setReaction/unsetReaction accepts
|
|
237
|
+
* the well-known legacy names (like, heart, laugh, surprised, sad, angry)
|
|
238
|
+
* as well as Unicode emoji values — so we pass unknown types through rather
|
|
239
|
+
* than rejecting them.
|
|
240
|
+
*/
|
|
241
|
+
function normalizeReactionType(raw) {
|
|
242
|
+
const normalized = raw.trim();
|
|
243
|
+
if (!normalized) throw new Error(`Reaction type is required. Common types: ${TEAMS_REACTION_TYPES.join(", ")}`);
|
|
244
|
+
const lowered = normalized.toLowerCase();
|
|
245
|
+
if (TEAMS_REACTION_TYPES.includes(lowered)) return lowered;
|
|
246
|
+
return normalized;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Add an emoji reaction to a message via Graph API (beta).
|
|
250
|
+
*
|
|
251
|
+
* Writes (setReaction) require a Delegated token, so we pass
|
|
252
|
+
* `preferDelegated: true`. The resolver falls back to the app-only token when
|
|
253
|
+
* delegated auth is not configured, preserving today's behavior while letting
|
|
254
|
+
* delegated-auth-enabled deployments hit the user-scoped endpoint.
|
|
255
|
+
*/
|
|
256
|
+
async function reactMessageMSTeams(params) {
|
|
257
|
+
const reactionType = normalizeReactionType(params.reactionType);
|
|
258
|
+
const token = await resolveGraphToken(params.cfg, { preferDelegated: true });
|
|
259
|
+
const { basePath } = resolveConversationPath(await resolveGraphConversationId(params.to));
|
|
260
|
+
await postGraphBetaJson({
|
|
261
|
+
token,
|
|
262
|
+
path: `${basePath}/messages/${encodeURIComponent(params.messageId)}/setReaction`,
|
|
263
|
+
body: { reactionType }
|
|
264
|
+
});
|
|
265
|
+
return { ok: true };
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Remove an emoji reaction from a message via Graph API (beta).
|
|
269
|
+
*
|
|
270
|
+
* Writes (unsetReaction) require a Delegated token, so we pass
|
|
271
|
+
* `preferDelegated: true`. See `reactMessageMSTeams` for fallback rules.
|
|
272
|
+
*/
|
|
273
|
+
async function unreactMessageMSTeams(params) {
|
|
274
|
+
const reactionType = normalizeReactionType(params.reactionType);
|
|
275
|
+
const token = await resolveGraphToken(params.cfg, { preferDelegated: true });
|
|
276
|
+
const { basePath } = resolveConversationPath(await resolveGraphConversationId(params.to));
|
|
277
|
+
await postGraphBetaJson({
|
|
278
|
+
token,
|
|
279
|
+
path: `${basePath}/messages/${encodeURIComponent(params.messageId)}/unsetReaction`,
|
|
280
|
+
body: { reactionType }
|
|
281
|
+
});
|
|
282
|
+
return { ok: true };
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* List reactions on a message, grouped by type.
|
|
286
|
+
* Uses Graph v1.0 (reactions are included in the message resource).
|
|
287
|
+
*/
|
|
288
|
+
async function listReactionsMSTeams(params) {
|
|
289
|
+
const token = await resolveGraphToken(params.cfg);
|
|
290
|
+
const { basePath } = resolveConversationPath(await resolveGraphConversationId(params.to));
|
|
291
|
+
const msg = await fetchGraphJson({
|
|
292
|
+
token,
|
|
293
|
+
path: `${basePath}/messages/${encodeURIComponent(params.messageId)}`
|
|
294
|
+
});
|
|
295
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
296
|
+
for (const reaction of msg.reactions ?? []) {
|
|
297
|
+
const type = reaction.reactionType ?? "unknown";
|
|
298
|
+
if (!grouped.has(type)) grouped.set(type, {
|
|
299
|
+
count: 0,
|
|
300
|
+
users: []
|
|
301
|
+
});
|
|
302
|
+
const group = grouped.get(type);
|
|
303
|
+
group.count++;
|
|
304
|
+
if (reaction.user?.id) group.users.push({
|
|
305
|
+
id: reaction.user.id,
|
|
306
|
+
displayName: reaction.user.displayName
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
return { reactions: Array.from(grouped.entries()).map(([type, group]) => ({
|
|
310
|
+
reactionType: type,
|
|
311
|
+
name: type,
|
|
312
|
+
emoji: REACTION_TYPE_EMOJI[type],
|
|
313
|
+
count: group.count,
|
|
314
|
+
users: group.users
|
|
315
|
+
})) };
|
|
316
|
+
}
|
|
317
|
+
const SEARCH_DEFAULT_LIMIT = 25;
|
|
318
|
+
const SEARCH_MAX_LIMIT = 50;
|
|
319
|
+
/**
|
|
320
|
+
* Search messages in a chat or channel by content via Graph API.
|
|
321
|
+
* Uses `$search` for full-text body search and optional `$filter` for sender.
|
|
322
|
+
*/
|
|
323
|
+
async function searchMessagesMSTeams(params) {
|
|
324
|
+
const token = await resolveGraphToken(params.cfg);
|
|
325
|
+
const { basePath } = resolveConversationPath(await resolveGraphConversationId(params.to));
|
|
326
|
+
const rawLimit = params.limit ?? SEARCH_DEFAULT_LIMIT;
|
|
327
|
+
const top = Number.isFinite(rawLimit) ? Math.min(Math.max(Math.floor(rawLimit), 1), SEARCH_MAX_LIMIT) : SEARCH_DEFAULT_LIMIT;
|
|
328
|
+
const sanitizedQuery = params.query.replace(/"/g, "");
|
|
329
|
+
const parts = [`$search=${encodeURIComponent(`"${sanitizedQuery}"`)}`];
|
|
330
|
+
parts.push(`$top=${top}`);
|
|
331
|
+
if (params.from) parts.push(`$filter=${encodeURIComponent(`from/user/displayName eq '${escapeOData(params.from)}'`)}`);
|
|
332
|
+
return { messages: ((await fetchGraphJson({
|
|
333
|
+
token,
|
|
334
|
+
path: `${basePath}/messages?${parts.join("&")}`,
|
|
335
|
+
headers: { ConsistencyLevel: "eventual" }
|
|
336
|
+
})).value ?? []).map((msg) => ({
|
|
337
|
+
id: msg.id ?? "",
|
|
338
|
+
text: msg.body?.content,
|
|
339
|
+
from: msg.from,
|
|
340
|
+
createdAt: msg.createdDateTime
|
|
341
|
+
})) };
|
|
342
|
+
}
|
|
343
|
+
//#endregion
|
|
344
|
+
//#region extensions/msteams/src/graph-group-management.ts
|
|
345
|
+
function normalizeConversationMemberRole(role) {
|
|
346
|
+
const normalized = role?.trim().toLowerCase() ?? "";
|
|
347
|
+
if (!normalized) return "member";
|
|
348
|
+
if (normalized === "member" || normalized === "owner") return normalized;
|
|
349
|
+
throw new Error("MS Teams participant role must be \"member\" or \"owner\".");
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Add a user to a chat or channel via Graph API.
|
|
353
|
+
*/
|
|
354
|
+
async function addParticipantMSTeams(params) {
|
|
355
|
+
const token = await resolveGraphToken(params.cfg);
|
|
356
|
+
const conversationId = await resolveGraphConversationId(params.to);
|
|
357
|
+
const conv = resolveConversationPath(conversationId);
|
|
358
|
+
const body = {
|
|
359
|
+
"@odata.type": "#microsoft.graph.aadUserConversationMember",
|
|
360
|
+
roles: [normalizeConversationMemberRole(params.role)],
|
|
361
|
+
"user@odata.bind": `https://graph.microsoft.com/v1.0/users('${escapeOData(params.userId)}')`
|
|
362
|
+
};
|
|
363
|
+
await postGraphJson({
|
|
364
|
+
token,
|
|
365
|
+
path: `${conv.basePath}/members`,
|
|
366
|
+
body
|
|
367
|
+
});
|
|
368
|
+
return { added: {
|
|
369
|
+
userId: params.userId,
|
|
370
|
+
chatId: conversationId
|
|
371
|
+
} };
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Remove a user from a chat or channel via Graph API.
|
|
375
|
+
* Lists members first to resolve the membership ID, then deletes.
|
|
376
|
+
*/
|
|
377
|
+
async function removeParticipantMSTeams(params) {
|
|
378
|
+
const token = await resolveGraphToken(params.cfg);
|
|
379
|
+
const conversationId = await resolveGraphConversationId(params.to);
|
|
380
|
+
const conv = resolveConversationPath(conversationId);
|
|
381
|
+
const MAX_PAGES = 10;
|
|
382
|
+
let nextPath = `${conv.basePath}/members`;
|
|
383
|
+
let page = 0;
|
|
384
|
+
let member;
|
|
385
|
+
while (nextPath && page < MAX_PAGES && !member) {
|
|
386
|
+
const membersRes = await fetchGraphJson({
|
|
387
|
+
token,
|
|
388
|
+
path: nextPath
|
|
389
|
+
});
|
|
390
|
+
member = (membersRes.value ?? []).find((candidate) => candidate.userId === params.userId);
|
|
391
|
+
if (member) break;
|
|
392
|
+
const nextLink = membersRes["@odata.nextLink"];
|
|
393
|
+
nextPath = nextLink ? nextLink.replace("https://graph.microsoft.com/v1.0", "") : void 0;
|
|
394
|
+
page++;
|
|
395
|
+
}
|
|
396
|
+
if (!member?.id) throw new Error(`User ${params.userId} is not a member of this conversation`);
|
|
397
|
+
await deleteGraphRequest({
|
|
398
|
+
token,
|
|
399
|
+
path: `${conv.basePath}/members/${encodeURIComponent(member.id)}`
|
|
400
|
+
});
|
|
401
|
+
return { removed: {
|
|
402
|
+
userId: params.userId,
|
|
403
|
+
chatId: conversationId
|
|
404
|
+
} };
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Rename a chat (topic) or channel (displayName) via Graph API.
|
|
408
|
+
*/
|
|
409
|
+
async function renameGroupMSTeams(params) {
|
|
410
|
+
const token = await resolveGraphToken(params.cfg);
|
|
411
|
+
const conversationId = await resolveGraphConversationId(params.to);
|
|
412
|
+
const conv = resolveConversationPath(conversationId);
|
|
413
|
+
const body = conv.kind === "chat" ? { topic: params.name } : { displayName: params.name };
|
|
414
|
+
await patchGraphJson({
|
|
415
|
+
token,
|
|
416
|
+
path: conv.basePath,
|
|
417
|
+
body
|
|
418
|
+
});
|
|
419
|
+
return { renamed: {
|
|
420
|
+
chatId: conversationId,
|
|
421
|
+
newName: params.name
|
|
422
|
+
} };
|
|
423
|
+
}
|
|
424
|
+
//#endregion
|
|
425
|
+
//#region extensions/msteams/src/graph-members.ts
|
|
426
|
+
/**
|
|
427
|
+
* Fetch a user profile from Microsoft Graph by user ID.
|
|
428
|
+
*/
|
|
429
|
+
async function getMemberInfoMSTeams(params) {
|
|
430
|
+
const user = await fetchGraphJson({
|
|
431
|
+
token: await resolveGraphToken(params.cfg),
|
|
432
|
+
path: `/users/${encodeURIComponent(params.userId)}?$select=id,displayName,mail,jobTitle,userPrincipalName,officeLocation`
|
|
433
|
+
});
|
|
434
|
+
return { user: {
|
|
435
|
+
id: user.id,
|
|
436
|
+
displayName: user.displayName,
|
|
437
|
+
mail: user.mail,
|
|
438
|
+
jobTitle: user.jobTitle,
|
|
439
|
+
userPrincipalName: user.userPrincipalName,
|
|
440
|
+
officeLocation: user.officeLocation
|
|
441
|
+
} };
|
|
442
|
+
}
|
|
443
|
+
//#endregion
|
|
444
|
+
//#region extensions/msteams/src/graph-teams.ts
|
|
445
|
+
/**
|
|
446
|
+
* List channels in a team via Graph API.
|
|
447
|
+
* Returns id, displayName, description, and membershipType for each channel.
|
|
448
|
+
* Follows @odata.nextLink for paginated results (up to 10 pages).
|
|
449
|
+
*/
|
|
450
|
+
async function listChannelsMSTeams(params) {
|
|
451
|
+
const token = await resolveGraphToken(params.cfg);
|
|
452
|
+
const firstPath = `/teams/${encodeURIComponent(params.teamId)}/channels?$select=id,displayName,description,membershipType`;
|
|
453
|
+
const collected = [];
|
|
454
|
+
let nextPath = firstPath;
|
|
455
|
+
const MAX_PAGES = 10;
|
|
456
|
+
let page = 0;
|
|
457
|
+
while (nextPath && page < MAX_PAGES) {
|
|
458
|
+
const res = await fetchGraphJson({
|
|
459
|
+
token,
|
|
460
|
+
path: nextPath
|
|
461
|
+
});
|
|
462
|
+
collected.push(...res.value ?? []);
|
|
463
|
+
const nextLink = res["@odata.nextLink"];
|
|
464
|
+
nextPath = nextLink ? nextLink.replace("https://graph.microsoft.com/v1.0", "") : void 0;
|
|
465
|
+
page++;
|
|
466
|
+
}
|
|
467
|
+
return {
|
|
468
|
+
channels: collected.map((ch) => ({
|
|
469
|
+
id: ch.id,
|
|
470
|
+
displayName: ch.displayName,
|
|
471
|
+
description: ch.description,
|
|
472
|
+
membershipType: ch.membershipType
|
|
473
|
+
})),
|
|
474
|
+
truncated: !!nextPath
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Get detailed information about a single channel in a team via Graph API.
|
|
479
|
+
* Returns id, displayName, description, membershipType, webUrl, and createdDateTime.
|
|
480
|
+
*/
|
|
481
|
+
async function getChannelInfoMSTeams(params) {
|
|
482
|
+
const ch = await fetchGraphJson({
|
|
483
|
+
token: await resolveGraphToken(params.cfg),
|
|
484
|
+
path: `/teams/${encodeURIComponent(params.teamId)}/channels/${encodeURIComponent(params.channelId)}?$select=id,displayName,description,membershipType,webUrl,createdDateTime`
|
|
485
|
+
});
|
|
486
|
+
return { channel: {
|
|
487
|
+
id: ch.id,
|
|
488
|
+
displayName: ch.displayName,
|
|
489
|
+
description: ch.description,
|
|
490
|
+
membershipType: ch.membershipType,
|
|
491
|
+
webUrl: ch.webUrl,
|
|
492
|
+
createdDateTime: ch.createdDateTime
|
|
493
|
+
} };
|
|
494
|
+
}
|
|
495
|
+
//#endregion
|
|
496
|
+
//#region extensions/msteams/src/channel.runtime.ts
|
|
497
|
+
const msTeamsChannelRuntime = {
|
|
498
|
+
addParticipantMSTeams,
|
|
499
|
+
deleteMessageMSTeams,
|
|
500
|
+
editMessageMSTeams,
|
|
501
|
+
getChannelInfoMSTeams,
|
|
502
|
+
getMemberInfoMSTeams,
|
|
503
|
+
getMessageMSTeams,
|
|
504
|
+
listChannelsMSTeams,
|
|
505
|
+
listPinsMSTeams,
|
|
506
|
+
listReactionsMSTeams,
|
|
507
|
+
pinMessageMSTeams,
|
|
508
|
+
reactMessageMSTeams,
|
|
509
|
+
removeParticipantMSTeams,
|
|
510
|
+
renameGroupMSTeams,
|
|
511
|
+
searchMessagesMSTeams,
|
|
512
|
+
unpinMessageMSTeams,
|
|
513
|
+
unreactMessageMSTeams,
|
|
514
|
+
listMSTeamsDirectoryGroupsLive,
|
|
515
|
+
listMSTeamsDirectoryPeersLive,
|
|
516
|
+
msteamsOutbound: {
|
|
517
|
+
deliveryMode: "direct",
|
|
518
|
+
chunker: chunkTextForOutbound,
|
|
519
|
+
chunkerMode: "markdown",
|
|
520
|
+
textChunkLimit: 4e3,
|
|
521
|
+
pollMaxOptions: 12,
|
|
522
|
+
...createAttachedChannelResultAdapter({
|
|
523
|
+
channel: "msteams",
|
|
524
|
+
sendText: async ({ cfg, to, text, deps }) => {
|
|
525
|
+
return await (resolveOutboundSendDep(deps, "msteams") ?? ((to, text) => sendMessageMSTeams({
|
|
526
|
+
cfg,
|
|
527
|
+
to,
|
|
528
|
+
text
|
|
529
|
+
})))(to, text);
|
|
530
|
+
},
|
|
531
|
+
sendMedia: async ({ cfg, to, text, mediaUrl, mediaLocalRoots, mediaReadFile, deps }) => {
|
|
532
|
+
return await (resolveOutboundSendDep(deps, "msteams") ?? ((to, text, opts) => sendMessageMSTeams({
|
|
533
|
+
cfg,
|
|
534
|
+
to,
|
|
535
|
+
text,
|
|
536
|
+
mediaUrl: opts?.mediaUrl,
|
|
537
|
+
mediaLocalRoots: opts?.mediaLocalRoots,
|
|
538
|
+
mediaReadFile: opts?.mediaReadFile
|
|
539
|
+
})))(to, text, {
|
|
540
|
+
mediaUrl,
|
|
541
|
+
mediaLocalRoots,
|
|
542
|
+
mediaReadFile
|
|
543
|
+
});
|
|
544
|
+
},
|
|
545
|
+
sendPoll: async ({ cfg, to, poll }) => {
|
|
546
|
+
const maxSelections = poll.maxSelections ?? 1;
|
|
547
|
+
const result = await sendPollMSTeams({
|
|
548
|
+
cfg,
|
|
549
|
+
to,
|
|
550
|
+
question: poll.question,
|
|
551
|
+
options: poll.options,
|
|
552
|
+
maxSelections
|
|
553
|
+
});
|
|
554
|
+
await createMSTeamsPollStoreFs().createPoll({
|
|
555
|
+
id: result.pollId,
|
|
556
|
+
question: poll.question,
|
|
557
|
+
options: poll.options,
|
|
558
|
+
maxSelections,
|
|
559
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
560
|
+
conversationId: result.conversationId,
|
|
561
|
+
messageId: result.messageId,
|
|
562
|
+
votes: {}
|
|
563
|
+
});
|
|
564
|
+
return result;
|
|
565
|
+
}
|
|
566
|
+
})
|
|
567
|
+
},
|
|
568
|
+
probeMSTeams,
|
|
569
|
+
sendAdaptiveCardMSTeams,
|
|
570
|
+
sendMessageMSTeams
|
|
571
|
+
};
|
|
572
|
+
//#endregion
|
|
573
|
+
export { msTeamsChannelRuntime };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { MSTeamsConfigSchema, buildChannelConfigSchema } from "openclaw/plugin-sdk/bundled-channel-config-schema";
|
|
2
|
+
//#endregion
|
|
3
|
+
//#region extensions/msteams/src/config-schema.ts
|
|
4
|
+
const MSTeamsChannelConfigSchema = buildChannelConfigSchema(MSTeamsConfigSchema, { uiHints: {
|
|
5
|
+
"": {
|
|
6
|
+
label: "MS Teams",
|
|
7
|
+
help: "Microsoft Teams channel provider configuration and provider-specific policy toggles. Use this section to isolate Teams behavior from other enterprise chat providers."
|
|
8
|
+
},
|
|
9
|
+
configWrites: {
|
|
10
|
+
label: "MS Teams Config Writes",
|
|
11
|
+
help: "Allow Microsoft Teams to write config in response to channel events/commands (default: true)."
|
|
12
|
+
}
|
|
13
|
+
} });
|
|
14
|
+
//#endregion
|
|
15
|
+
export { MSTeamsChannelConfigSchema as t };
|