@openclaw/msteams 2026.3.2 → 2026.3.7
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/index.ts +2 -2
- package/package.json +1 -1
- package/src/attachments/graph.ts +1 -1
- package/src/attachments/payload.ts +1 -1
- package/src/attachments/remote-media.ts +1 -1
- package/src/attachments/shared.ts +2 -2
- package/src/attachments.test.ts +1 -1
- package/src/channel.directory.test.ts +1 -1
- package/src/channel.ts +95 -101
- package/src/directory-live.ts +1 -1
- package/src/file-lock.ts +1 -1
- package/src/graph.ts +1 -1
- package/src/media-helpers.ts +1 -1
- package/src/messenger.test.ts +16 -19
- package/src/messenger.ts +1 -1
- package/src/monitor-handler/message-handler.authz.test.ts +1 -1
- package/src/monitor-handler/message-handler.ts +59 -53
- package/src/monitor-handler.file-consent.test.ts +1 -1
- package/src/monitor-handler.ts +1 -1
- package/src/monitor.lifecycle.test.ts +9 -3
- package/src/monitor.ts +1 -1
- package/src/onboarding.ts +23 -47
- package/src/outbound.test.ts +131 -0
- package/src/outbound.ts +1 -1
- package/src/policy.test.ts +1 -1
- package/src/policy.ts +9 -10
- package/src/probe.test.ts +1 -1
- package/src/probe.ts +6 -2
- package/src/reply-dispatcher.ts +1 -1
- package/src/resolve-allowlist.test.ts +78 -0
- package/src/resolve-allowlist.ts +70 -79
- package/src/runtime.ts +1 -1
- package/src/secret-input.ts +1 -1
- package/src/send-context.ts +1 -1
- package/src/send.test.ts +2 -2
- package/src/send.ts +42 -43
- package/src/store-fs.ts +1 -1
- package/src/test-runtime.ts +1 -1
- package/src/token.test.ts +1 -1
- package/src/token.ts +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
|
-
import type { OpenClawConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
|
2
|
+
import type { OpenClawConfig, RuntimeEnv } from "openclaw/plugin-sdk/msteams";
|
|
3
3
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
4
4
|
import type { MSTeamsConversationStore } from "./conversation-store.js";
|
|
5
5
|
import type { MSTeamsPollStore } from "./polls.js";
|
|
@@ -15,8 +15,14 @@ const expressControl = vi.hoisted(() => ({
|
|
|
15
15
|
mode: { value: "listening" as "listening" | "error" },
|
|
16
16
|
}));
|
|
17
17
|
|
|
18
|
-
vi.mock("openclaw/plugin-sdk", () => ({
|
|
18
|
+
vi.mock("openclaw/plugin-sdk/msteams", () => ({
|
|
19
19
|
DEFAULT_WEBHOOK_MAX_BODY_BYTES: 1024 * 1024,
|
|
20
|
+
normalizeSecretInputString: (value: unknown) =>
|
|
21
|
+
typeof value === "string" && value.trim() ? value.trim() : undefined,
|
|
22
|
+
hasConfiguredSecretInput: (value: unknown) =>
|
|
23
|
+
typeof value === "string" && value.trim().length > 0,
|
|
24
|
+
normalizeResolvedSecretInputString: (params: { value?: unknown }) =>
|
|
25
|
+
typeof params?.value === "string" && params.value.trim() ? params.value.trim() : undefined,
|
|
20
26
|
keepHttpServerTaskAlive: vi.fn(
|
|
21
27
|
async (params: { abortSignal?: AbortSignal; onAbort?: () => Promise<void> | void }) => {
|
|
22
28
|
await new Promise<void>((resolve) => {
|
|
@@ -134,7 +140,7 @@ function createConfig(port: number): OpenClawConfig {
|
|
|
134
140
|
msteams: {
|
|
135
141
|
enabled: true,
|
|
136
142
|
appId: "app-id",
|
|
137
|
-
appPassword: "app-password",
|
|
143
|
+
appPassword: "app-password", // pragma: allowlist secret
|
|
138
144
|
tenantId: "tenant-id",
|
|
139
145
|
webhook: {
|
|
140
146
|
port,
|
package/src/monitor.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
summarizeMapping,
|
|
8
8
|
type OpenClawConfig,
|
|
9
9
|
type RuntimeEnv,
|
|
10
|
-
} from "openclaw/plugin-sdk";
|
|
10
|
+
} from "openclaw/plugin-sdk/msteams";
|
|
11
11
|
import { createMSTeamsConversationStoreFs } from "./conversation-store-fs.js";
|
|
12
12
|
import type { MSTeamsConversationStore } from "./conversation-store.js";
|
|
13
13
|
import { formatUnknownError } from "./errors.js";
|
package/src/onboarding.ts
CHANGED
|
@@ -5,14 +5,17 @@ import type {
|
|
|
5
5
|
DmPolicy,
|
|
6
6
|
WizardPrompter,
|
|
7
7
|
MSTeamsTeamConfig,
|
|
8
|
-
} from "openclaw/plugin-sdk";
|
|
8
|
+
} from "openclaw/plugin-sdk/msteams";
|
|
9
9
|
import {
|
|
10
|
-
addWildcardAllowFrom,
|
|
11
10
|
DEFAULT_ACCOUNT_ID,
|
|
12
11
|
formatDocsLink,
|
|
13
12
|
mergeAllowFromEntries,
|
|
14
13
|
promptChannelAccessConfig,
|
|
15
|
-
|
|
14
|
+
setTopLevelChannelAllowFrom,
|
|
15
|
+
setTopLevelChannelDmPolicyWithAllowFrom,
|
|
16
|
+
setTopLevelChannelGroupPolicy,
|
|
17
|
+
splitOnboardingEntries,
|
|
18
|
+
} from "openclaw/plugin-sdk/msteams";
|
|
16
19
|
import {
|
|
17
20
|
parseMSTeamsTeamEntry,
|
|
18
21
|
resolveMSTeamsChannelAllowlist,
|
|
@@ -24,41 +27,19 @@ import { hasConfiguredMSTeamsCredentials, resolveMSTeamsCredentials } from "./to
|
|
|
24
27
|
const channel = "msteams" as const;
|
|
25
28
|
|
|
26
29
|
function setMSTeamsDmPolicy(cfg: OpenClawConfig, dmPolicy: DmPolicy) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
...cfg,
|
|
33
|
-
channels: {
|
|
34
|
-
...cfg.channels,
|
|
35
|
-
msteams: {
|
|
36
|
-
...cfg.channels?.msteams,
|
|
37
|
-
dmPolicy,
|
|
38
|
-
...(allowFrom ? { allowFrom } : {}),
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
};
|
|
30
|
+
return setTopLevelChannelDmPolicyWithAllowFrom({
|
|
31
|
+
cfg,
|
|
32
|
+
channel: "msteams",
|
|
33
|
+
dmPolicy,
|
|
34
|
+
});
|
|
42
35
|
}
|
|
43
36
|
|
|
44
37
|
function setMSTeamsAllowFrom(cfg: OpenClawConfig, allowFrom: string[]): OpenClawConfig {
|
|
45
|
-
return {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
...cfg.channels?.msteams,
|
|
51
|
-
allowFrom,
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function parseAllowFromInput(raw: string): string[] {
|
|
58
|
-
return raw
|
|
59
|
-
.split(/[\n,;]+/g)
|
|
60
|
-
.map((entry) => entry.trim())
|
|
61
|
-
.filter(Boolean);
|
|
38
|
+
return setTopLevelChannelAllowFrom({
|
|
39
|
+
cfg,
|
|
40
|
+
channel: "msteams",
|
|
41
|
+
allowFrom,
|
|
42
|
+
});
|
|
62
43
|
}
|
|
63
44
|
|
|
64
45
|
function looksLikeGuid(value: string): boolean {
|
|
@@ -115,7 +96,7 @@ async function promptMSTeamsAllowFrom(params: {
|
|
|
115
96
|
initialValue: existing[0] ? String(existing[0]) : undefined,
|
|
116
97
|
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
|
117
98
|
});
|
|
118
|
-
const parts =
|
|
99
|
+
const parts = splitOnboardingEntries(String(entry));
|
|
119
100
|
if (parts.length === 0) {
|
|
120
101
|
await params.prompter.note("Enter at least one user.", "MS Teams allowlist");
|
|
121
102
|
continue;
|
|
@@ -171,17 +152,12 @@ function setMSTeamsGroupPolicy(
|
|
|
171
152
|
cfg: OpenClawConfig,
|
|
172
153
|
groupPolicy: "open" | "allowlist" | "disabled",
|
|
173
154
|
): OpenClawConfig {
|
|
174
|
-
return {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
enabled: true,
|
|
181
|
-
groupPolicy,
|
|
182
|
-
},
|
|
183
|
-
},
|
|
184
|
-
};
|
|
155
|
+
return setTopLevelChannelGroupPolicy({
|
|
156
|
+
cfg,
|
|
157
|
+
channel: "msteams",
|
|
158
|
+
groupPolicy,
|
|
159
|
+
enabled: true,
|
|
160
|
+
});
|
|
185
161
|
}
|
|
186
162
|
|
|
187
163
|
function setMSTeamsTeamsAllowlist(
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/msteams";
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
const mocks = vi.hoisted(() => ({
|
|
5
|
+
sendMessageMSTeams: vi.fn(),
|
|
6
|
+
sendPollMSTeams: vi.fn(),
|
|
7
|
+
createPoll: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
vi.mock("./send.js", () => ({
|
|
11
|
+
sendMessageMSTeams: mocks.sendMessageMSTeams,
|
|
12
|
+
sendPollMSTeams: mocks.sendPollMSTeams,
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
vi.mock("./polls.js", () => ({
|
|
16
|
+
createMSTeamsPollStoreFs: () => ({
|
|
17
|
+
createPoll: mocks.createPoll,
|
|
18
|
+
}),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
vi.mock("./runtime.js", () => ({
|
|
22
|
+
getMSTeamsRuntime: () => ({
|
|
23
|
+
channel: {
|
|
24
|
+
text: {
|
|
25
|
+
chunkMarkdownText: (text: string) => [text],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
}),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
import { msteamsOutbound } from "./outbound.js";
|
|
32
|
+
|
|
33
|
+
describe("msteamsOutbound cfg threading", () => {
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
mocks.sendMessageMSTeams.mockReset();
|
|
36
|
+
mocks.sendPollMSTeams.mockReset();
|
|
37
|
+
mocks.createPoll.mockReset();
|
|
38
|
+
mocks.sendMessageMSTeams.mockResolvedValue({
|
|
39
|
+
messageId: "msg-1",
|
|
40
|
+
conversationId: "conv-1",
|
|
41
|
+
});
|
|
42
|
+
mocks.sendPollMSTeams.mockResolvedValue({
|
|
43
|
+
pollId: "poll-1",
|
|
44
|
+
messageId: "msg-poll-1",
|
|
45
|
+
conversationId: "conv-1",
|
|
46
|
+
});
|
|
47
|
+
mocks.createPoll.mockResolvedValue(undefined);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("passes resolved cfg to sendMessageMSTeams for text sends", async () => {
|
|
51
|
+
const cfg = {
|
|
52
|
+
channels: {
|
|
53
|
+
msteams: {
|
|
54
|
+
appId: "resolved-app-id",
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
} as OpenClawConfig;
|
|
58
|
+
|
|
59
|
+
await msteamsOutbound.sendText!({
|
|
60
|
+
cfg,
|
|
61
|
+
to: "conversation:abc",
|
|
62
|
+
text: "hello",
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(mocks.sendMessageMSTeams).toHaveBeenCalledWith({
|
|
66
|
+
cfg,
|
|
67
|
+
to: "conversation:abc",
|
|
68
|
+
text: "hello",
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("passes resolved cfg and media roots for media sends", async () => {
|
|
73
|
+
const cfg = {
|
|
74
|
+
channels: {
|
|
75
|
+
msteams: {
|
|
76
|
+
appId: "resolved-app-id",
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
} as OpenClawConfig;
|
|
80
|
+
|
|
81
|
+
await msteamsOutbound.sendMedia!({
|
|
82
|
+
cfg,
|
|
83
|
+
to: "conversation:abc",
|
|
84
|
+
text: "photo",
|
|
85
|
+
mediaUrl: "file:///tmp/photo.png",
|
|
86
|
+
mediaLocalRoots: ["/tmp"],
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(mocks.sendMessageMSTeams).toHaveBeenCalledWith({
|
|
90
|
+
cfg,
|
|
91
|
+
to: "conversation:abc",
|
|
92
|
+
text: "photo",
|
|
93
|
+
mediaUrl: "file:///tmp/photo.png",
|
|
94
|
+
mediaLocalRoots: ["/tmp"],
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("passes resolved cfg to sendPollMSTeams and stores poll metadata", async () => {
|
|
99
|
+
const cfg = {
|
|
100
|
+
channels: {
|
|
101
|
+
msteams: {
|
|
102
|
+
appId: "resolved-app-id",
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
} as OpenClawConfig;
|
|
106
|
+
|
|
107
|
+
await msteamsOutbound.sendPoll!({
|
|
108
|
+
cfg,
|
|
109
|
+
to: "conversation:abc",
|
|
110
|
+
poll: {
|
|
111
|
+
question: "Snack?",
|
|
112
|
+
options: ["Pizza", "Sushi"],
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
expect(mocks.sendPollMSTeams).toHaveBeenCalledWith({
|
|
117
|
+
cfg,
|
|
118
|
+
to: "conversation:abc",
|
|
119
|
+
question: "Snack?",
|
|
120
|
+
options: ["Pizza", "Sushi"],
|
|
121
|
+
maxSelections: 1,
|
|
122
|
+
});
|
|
123
|
+
expect(mocks.createPoll).toHaveBeenCalledWith(
|
|
124
|
+
expect.objectContaining({
|
|
125
|
+
id: "poll-1",
|
|
126
|
+
question: "Snack?",
|
|
127
|
+
options: ["Pizza", "Sushi"],
|
|
128
|
+
}),
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
});
|
package/src/outbound.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChannelOutboundAdapter } from "openclaw/plugin-sdk";
|
|
1
|
+
import type { ChannelOutboundAdapter } from "openclaw/plugin-sdk/msteams";
|
|
2
2
|
import { createMSTeamsPollStoreFs } from "./polls.js";
|
|
3
3
|
import { getMSTeamsRuntime } from "./runtime.js";
|
|
4
4
|
import { sendMessageMSTeams, sendPollMSTeams } from "./send.js";
|
package/src/policy.test.ts
CHANGED
package/src/policy.ts
CHANGED
|
@@ -7,15 +7,16 @@ import type {
|
|
|
7
7
|
MSTeamsConfig,
|
|
8
8
|
MSTeamsReplyStyle,
|
|
9
9
|
MSTeamsTeamConfig,
|
|
10
|
-
} from "openclaw/plugin-sdk";
|
|
10
|
+
} from "openclaw/plugin-sdk/msteams";
|
|
11
11
|
import {
|
|
12
12
|
buildChannelKeyCandidates,
|
|
13
|
+
evaluateSenderGroupAccessForPolicy,
|
|
13
14
|
normalizeChannelSlug,
|
|
14
15
|
resolveAllowlistMatchSimple,
|
|
15
16
|
resolveToolsBySender,
|
|
16
17
|
resolveChannelEntryMatchWithFallback,
|
|
17
18
|
resolveNestedAllowlistDecision,
|
|
18
|
-
} from "openclaw/plugin-sdk";
|
|
19
|
+
} from "openclaw/plugin-sdk/msteams";
|
|
19
20
|
|
|
20
21
|
export type MSTeamsResolvedRouteConfig = {
|
|
21
22
|
teamConfig?: MSTeamsTeamConfig;
|
|
@@ -248,12 +249,10 @@ export function isMSTeamsGroupAllowed(params: {
|
|
|
248
249
|
senderName?: string | null;
|
|
249
250
|
allowNameMatching?: boolean;
|
|
250
251
|
}): boolean {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
return resolveMSTeamsAllowlistMatch(params).allowed;
|
|
252
|
+
return evaluateSenderGroupAccessForPolicy({
|
|
253
|
+
groupPolicy: params.groupPolicy,
|
|
254
|
+
groupAllowFrom: params.allowFrom.map((entry) => String(entry)),
|
|
255
|
+
senderId: params.senderId,
|
|
256
|
+
isSenderAllowed: () => resolveMSTeamsAllowlistMatch(params).allowed,
|
|
257
|
+
}).allowed;
|
|
259
258
|
}
|
package/src/probe.test.ts
CHANGED
package/src/probe.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
normalizeStringEntries,
|
|
3
|
+
type BaseProbeResult,
|
|
4
|
+
type MSTeamsConfig,
|
|
5
|
+
} from "openclaw/plugin-sdk/msteams";
|
|
2
6
|
import { formatUnknownError } from "./errors.js";
|
|
3
7
|
import { loadMSTeamsSdkWithAuth } from "./sdk.js";
|
|
4
8
|
import { readAccessToken } from "./token-response.js";
|
|
@@ -35,7 +39,7 @@ function readStringArray(value: unknown): string[] | undefined {
|
|
|
35
39
|
if (!Array.isArray(value)) {
|
|
36
40
|
return undefined;
|
|
37
41
|
}
|
|
38
|
-
const out = value
|
|
42
|
+
const out = normalizeStringEntries(value);
|
|
39
43
|
return out.length > 0 ? out : undefined;
|
|
40
44
|
}
|
|
41
45
|
|
package/src/reply-dispatcher.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
type OpenClawConfig,
|
|
7
7
|
type MSTeamsReplyStyle,
|
|
8
8
|
type RuntimeEnv,
|
|
9
|
-
} from "openclaw/plugin-sdk";
|
|
9
|
+
} from "openclaw/plugin-sdk/msteams";
|
|
10
10
|
import type { MSTeamsAccessTokenProvider } from "./attachments/types.js";
|
|
11
11
|
import type { StoredConversationReference } from "./conversation-store.js";
|
|
12
12
|
import {
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
listTeamsByName,
|
|
5
|
+
listChannelsForTeam,
|
|
6
|
+
normalizeQuery,
|
|
7
|
+
resolveGraphToken,
|
|
8
|
+
searchGraphUsers,
|
|
9
|
+
} = vi.hoisted(() => ({
|
|
10
|
+
listTeamsByName: vi.fn(),
|
|
11
|
+
listChannelsForTeam: vi.fn(),
|
|
12
|
+
normalizeQuery: vi.fn((value: string) => value.trim().toLowerCase()),
|
|
13
|
+
resolveGraphToken: vi.fn(async () => "graph-token"),
|
|
14
|
+
searchGraphUsers: vi.fn(),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
vi.mock("./graph.js", () => ({
|
|
18
|
+
listTeamsByName,
|
|
19
|
+
listChannelsForTeam,
|
|
20
|
+
normalizeQuery,
|
|
21
|
+
resolveGraphToken,
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
vi.mock("./graph-users.js", () => ({
|
|
25
|
+
searchGraphUsers,
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
import {
|
|
29
|
+
resolveMSTeamsChannelAllowlist,
|
|
30
|
+
resolveMSTeamsUserAllowlist,
|
|
31
|
+
} from "./resolve-allowlist.js";
|
|
32
|
+
|
|
33
|
+
describe("resolveMSTeamsUserAllowlist", () => {
|
|
34
|
+
it("marks empty input unresolved", async () => {
|
|
35
|
+
const [result] = await resolveMSTeamsUserAllowlist({ cfg: {}, entries: [" "] });
|
|
36
|
+
expect(result).toEqual({ input: " ", resolved: false });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("resolves first Graph user match", async () => {
|
|
40
|
+
searchGraphUsers.mockResolvedValueOnce([
|
|
41
|
+
{ id: "user-1", displayName: "Alice One" },
|
|
42
|
+
{ id: "user-2", displayName: "Alice Two" },
|
|
43
|
+
]);
|
|
44
|
+
const [result] = await resolveMSTeamsUserAllowlist({ cfg: {}, entries: ["alice"] });
|
|
45
|
+
expect(result).toEqual({
|
|
46
|
+
input: "alice",
|
|
47
|
+
resolved: true,
|
|
48
|
+
id: "user-1",
|
|
49
|
+
name: "Alice One",
|
|
50
|
+
note: "multiple matches; chose first",
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("resolveMSTeamsChannelAllowlist", () => {
|
|
56
|
+
it("resolves team/channel by team name + channel display name", async () => {
|
|
57
|
+
listTeamsByName.mockResolvedValueOnce([{ id: "team-1", displayName: "Product Team" }]);
|
|
58
|
+
listChannelsForTeam.mockResolvedValueOnce([
|
|
59
|
+
{ id: "channel-1", displayName: "General" },
|
|
60
|
+
{ id: "channel-2", displayName: "Roadmap" },
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
const [result] = await resolveMSTeamsChannelAllowlist({
|
|
64
|
+
cfg: {},
|
|
65
|
+
entries: ["Product Team/Roadmap"],
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(result).toEqual({
|
|
69
|
+
input: "Product Team/Roadmap",
|
|
70
|
+
resolved: true,
|
|
71
|
+
teamId: "team-1",
|
|
72
|
+
teamName: "Product Team",
|
|
73
|
+
channelId: "channel-2",
|
|
74
|
+
channelName: "Roadmap",
|
|
75
|
+
note: "multiple channels; chose first",
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
});
|
package/src/resolve-allowlist.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mapAllowlistResolutionInputs } from "openclaw/plugin-sdk/compat";
|
|
1
2
|
import { searchGraphUsers } from "./graph-users.js";
|
|
2
3
|
import {
|
|
3
4
|
listChannelsForTeam,
|
|
@@ -105,61 +106,55 @@ export async function resolveMSTeamsChannelAllowlist(params: {
|
|
|
105
106
|
entries: string[];
|
|
106
107
|
}): Promise<MSTeamsChannelResolution[]> {
|
|
107
108
|
const token = await resolveGraphToken(params.cfg);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
109
|
+
return await mapAllowlistResolutionInputs({
|
|
110
|
+
inputs: params.entries,
|
|
111
|
+
mapInput: async (input): Promise<MSTeamsChannelResolution> => {
|
|
112
|
+
const { team, channel } = parseMSTeamsTeamChannelInput(input);
|
|
113
|
+
if (!team) {
|
|
114
|
+
return { input, resolved: false };
|
|
115
|
+
}
|
|
116
|
+
const teams = /^[0-9a-fA-F-]{16,}$/.test(team)
|
|
117
|
+
? [{ id: team, displayName: team }]
|
|
118
|
+
: await listTeamsByName(token, team);
|
|
119
|
+
if (teams.length === 0) {
|
|
120
|
+
return { input, resolved: false, note: "team not found" };
|
|
121
|
+
}
|
|
122
|
+
const teamMatch = teams[0];
|
|
123
|
+
const teamId = teamMatch.id?.trim();
|
|
124
|
+
const teamName = teamMatch.displayName?.trim() || team;
|
|
125
|
+
if (!teamId) {
|
|
126
|
+
return { input, resolved: false, note: "team id missing" };
|
|
127
|
+
}
|
|
128
|
+
if (!channel) {
|
|
129
|
+
return {
|
|
130
|
+
input,
|
|
131
|
+
resolved: true,
|
|
132
|
+
teamId,
|
|
133
|
+
teamName,
|
|
134
|
+
note: teams.length > 1 ? "multiple teams; chose first" : undefined,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const channels = await listChannelsForTeam(token, teamId);
|
|
138
|
+
const channelMatch =
|
|
139
|
+
channels.find((item) => item.id === channel) ??
|
|
140
|
+
channels.find((item) => item.displayName?.toLowerCase() === channel.toLowerCase()) ??
|
|
141
|
+
channels.find((item) =>
|
|
142
|
+
item.displayName?.toLowerCase().includes(channel.toLowerCase() ?? ""),
|
|
143
|
+
);
|
|
144
|
+
if (!channelMatch?.id) {
|
|
145
|
+
return { input, resolved: false, note: "channel not found" };
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
132
148
|
input,
|
|
133
149
|
resolved: true,
|
|
134
150
|
teamId,
|
|
135
151
|
teamName,
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
channels.find((item) => item.id === channel) ??
|
|
143
|
-
channels.find((item) => item.displayName?.toLowerCase() === channel.toLowerCase()) ??
|
|
144
|
-
channels.find((item) =>
|
|
145
|
-
item.displayName?.toLowerCase().includes(channel.toLowerCase() ?? ""),
|
|
146
|
-
);
|
|
147
|
-
if (!channelMatch?.id) {
|
|
148
|
-
results.push({ input, resolved: false, note: "channel not found" });
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
results.push({
|
|
152
|
-
input,
|
|
153
|
-
resolved: true,
|
|
154
|
-
teamId,
|
|
155
|
-
teamName,
|
|
156
|
-
channelId: channelMatch.id,
|
|
157
|
-
channelName: channelMatch.displayName ?? channel,
|
|
158
|
-
note: channels.length > 1 ? "multiple channels; chose first" : undefined,
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return results;
|
|
152
|
+
channelId: channelMatch.id,
|
|
153
|
+
channelName: channelMatch.displayName ?? channel,
|
|
154
|
+
note: channels.length > 1 ? "multiple channels; chose first" : undefined,
|
|
155
|
+
};
|
|
156
|
+
},
|
|
157
|
+
});
|
|
163
158
|
}
|
|
164
159
|
|
|
165
160
|
export async function resolveMSTeamsUserAllowlist(params: {
|
|
@@ -167,32 +162,28 @@ export async function resolveMSTeamsUserAllowlist(params: {
|
|
|
167
162
|
entries: string[];
|
|
168
163
|
}): Promise<MSTeamsUserResolution[]> {
|
|
169
164
|
const token = await resolveGraphToken(params.cfg);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return results;
|
|
165
|
+
return await mapAllowlistResolutionInputs({
|
|
166
|
+
inputs: params.entries,
|
|
167
|
+
mapInput: async (input): Promise<MSTeamsUserResolution> => {
|
|
168
|
+
const query = normalizeQuery(normalizeMSTeamsUserInput(input));
|
|
169
|
+
if (!query) {
|
|
170
|
+
return { input, resolved: false };
|
|
171
|
+
}
|
|
172
|
+
if (/^[0-9a-fA-F-]{16,}$/.test(query)) {
|
|
173
|
+
return { input, resolved: true, id: query };
|
|
174
|
+
}
|
|
175
|
+
const users = await searchGraphUsers({ token, query, top: 10 });
|
|
176
|
+
const match = users[0];
|
|
177
|
+
if (!match?.id) {
|
|
178
|
+
return { input, resolved: false };
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
input,
|
|
182
|
+
resolved: true,
|
|
183
|
+
id: match.id,
|
|
184
|
+
name: match.displayName ?? undefined,
|
|
185
|
+
note: users.length > 1 ? "multiple matches; chose first" : undefined,
|
|
186
|
+
};
|
|
187
|
+
},
|
|
188
|
+
});
|
|
198
189
|
}
|
package/src/runtime.ts
CHANGED
package/src/secret-input.ts
CHANGED
|
@@ -2,6 +2,6 @@ import {
|
|
|
2
2
|
hasConfiguredSecretInput,
|
|
3
3
|
normalizeResolvedSecretInputString,
|
|
4
4
|
normalizeSecretInputString,
|
|
5
|
-
} from "openclaw/plugin-sdk";
|
|
5
|
+
} from "openclaw/plugin-sdk/msteams";
|
|
6
6
|
|
|
7
7
|
export { hasConfiguredSecretInput, normalizeResolvedSecretInputString, normalizeSecretInputString };
|
package/src/send-context.ts
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
resolveChannelMediaMaxBytes,
|
|
3
3
|
type OpenClawConfig,
|
|
4
4
|
type PluginRuntime,
|
|
5
|
-
} from "openclaw/plugin-sdk";
|
|
5
|
+
} from "openclaw/plugin-sdk/msteams";
|
|
6
6
|
import type { MSTeamsAccessTokenProvider } from "./attachments/types.js";
|
|
7
7
|
import { createMSTeamsConversationStoreFs } from "./conversation-store-fs.js";
|
|
8
8
|
import type {
|
package/src/send.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
1
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/msteams";
|
|
2
2
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
3
|
import { sendMessageMSTeams } from "./send.js";
|
|
4
4
|
|
|
@@ -11,7 +11,7 @@ const mockState = vi.hoisted(() => ({
|
|
|
11
11
|
sendMSTeamsMessages: vi.fn(),
|
|
12
12
|
}));
|
|
13
13
|
|
|
14
|
-
vi.mock("openclaw/plugin-sdk", () => ({
|
|
14
|
+
vi.mock("openclaw/plugin-sdk/msteams", () => ({
|
|
15
15
|
loadOutboundMediaFromUrl: mockState.loadOutboundMediaFromUrl,
|
|
16
16
|
}));
|
|
17
17
|
|