@openclaw/msteams 2026.3.13 → 2026.5.1-beta.1
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/api.ts +3 -0
- package/channel-config-api.ts +1 -0
- package/channel-plugin-api.ts +2 -0
- package/config-api.ts +4 -0
- package/contract-api.ts +4 -0
- package/index.ts +15 -12
- package/openclaw.plugin.json +553 -1
- package/package.json +46 -12
- package/runtime-api.ts +73 -0
- package/secret-contract-api.ts +5 -0
- package/setup-entry.ts +13 -0
- package/setup-plugin-api.ts +3 -0
- package/src/ai-entity.ts +7 -0
- package/src/approval-auth.ts +44 -0
- package/src/attachments/bot-framework.test.ts +461 -0
- package/src/attachments/bot-framework.ts +362 -0
- package/src/attachments/download.ts +63 -19
- package/src/attachments/graph.test.ts +416 -0
- package/src/attachments/graph.ts +163 -72
- package/src/attachments/html.ts +33 -1
- package/src/attachments/payload.ts +1 -1
- package/src/attachments/remote-media.test.ts +137 -0
- package/src/attachments/remote-media.ts +75 -8
- package/src/attachments/shared.test.ts +138 -1
- package/src/attachments/shared.ts +193 -26
- package/src/attachments/types.ts +10 -0
- package/src/attachments.graph.test.ts +342 -0
- package/src/attachments.helpers.test.ts +246 -0
- package/src/attachments.test-helpers.ts +17 -0
- package/src/attachments.test.ts +163 -418
- package/src/attachments.ts +5 -5
- package/src/block-streaming-config.test.ts +61 -0
- package/src/channel-api.ts +1 -0
- package/src/channel.actions.test.ts +742 -0
- package/src/channel.directory.test.ts +145 -4
- package/src/channel.runtime.ts +56 -0
- package/src/channel.setup.ts +77 -0
- package/src/channel.test.ts +128 -0
- package/src/channel.ts +1077 -395
- package/src/config-schema.ts +6 -0
- package/src/config-ui-hints.ts +12 -0
- package/src/conversation-store-fs.test.ts +4 -5
- package/src/conversation-store-fs.ts +35 -51
- package/src/conversation-store-helpers.test.ts +202 -0
- package/src/conversation-store-helpers.ts +105 -0
- package/src/conversation-store-memory.ts +27 -23
- package/src/conversation-store.shared.test.ts +225 -0
- package/src/conversation-store.ts +30 -0
- package/src/directory-live.test.ts +156 -0
- package/src/directory-live.ts +7 -4
- package/src/doctor.ts +27 -0
- package/src/errors.test.ts +64 -1
- package/src/errors.ts +50 -9
- package/src/feedback-reflection-prompt.ts +117 -0
- package/src/feedback-reflection-store.ts +114 -0
- package/src/feedback-reflection.test.ts +237 -0
- package/src/feedback-reflection.ts +283 -0
- package/src/file-consent-helpers.test.ts +83 -0
- package/src/file-consent-helpers.ts +64 -11
- package/src/file-consent-invoke.ts +150 -0
- package/src/file-consent.test.ts +363 -0
- package/src/file-consent.ts +165 -4
- package/src/graph-chat.ts +5 -3
- package/src/graph-group-management.test.ts +318 -0
- package/src/graph-group-management.ts +168 -0
- package/src/graph-members.test.ts +89 -0
- package/src/graph-members.ts +48 -0
- package/src/graph-messages.actions.test.ts +243 -0
- package/src/graph-messages.read.test.ts +391 -0
- package/src/graph-messages.search.test.ts +213 -0
- package/src/graph-messages.test-helpers.ts +50 -0
- package/src/graph-messages.ts +534 -0
- package/src/graph-teams.test.ts +215 -0
- package/src/graph-teams.ts +114 -0
- package/src/graph-thread.test.ts +246 -0
- package/src/graph-thread.ts +146 -0
- package/src/graph-upload.test.ts +161 -4
- package/src/graph-upload.ts +147 -56
- package/src/graph.test.ts +516 -0
- package/src/graph.ts +233 -21
- package/src/inbound.test.ts +156 -1
- package/src/inbound.ts +101 -1
- package/src/media-helpers.ts +1 -1
- package/src/mentions.test.ts +27 -18
- package/src/mentions.ts +2 -2
- package/src/messenger.test.ts +504 -23
- package/src/messenger.ts +133 -52
- package/src/monitor-handler/access.ts +125 -0
- package/src/monitor-handler/inbound-media.test.ts +289 -0
- package/src/monitor-handler/inbound-media.ts +57 -5
- package/src/monitor-handler/message-handler-mock-support.test-support.ts +28 -0
- package/src/monitor-handler/message-handler.authz.test.ts +588 -74
- package/src/monitor-handler/message-handler.dm-media.test.ts +54 -0
- package/src/monitor-handler/message-handler.test-support.ts +100 -0
- package/src/monitor-handler/message-handler.thread-parent.test.ts +223 -0
- package/src/monitor-handler/message-handler.thread-session.test.ts +77 -0
- package/src/monitor-handler/message-handler.ts +470 -164
- package/src/monitor-handler/reaction-handler.test.ts +267 -0
- package/src/monitor-handler/reaction-handler.ts +210 -0
- package/src/monitor-handler/thread-session.ts +17 -0
- package/src/monitor-handler.adaptive-card.test.ts +162 -0
- package/src/monitor-handler.feedback-authz.test.ts +314 -0
- package/src/monitor-handler.file-consent.test.ts +281 -79
- package/src/monitor-handler.sso.test.ts +563 -0
- package/src/monitor-handler.test-helpers.ts +180 -0
- package/src/monitor-handler.ts +459 -115
- package/src/monitor-handler.types.ts +27 -0
- package/src/monitor-types.ts +1 -0
- package/src/monitor.lifecycle.test.ts +74 -10
- package/src/monitor.test.ts +35 -1
- package/src/monitor.ts +143 -46
- package/src/oauth.flow.ts +77 -0
- package/src/oauth.shared.ts +37 -0
- package/src/oauth.test.ts +305 -0
- package/src/oauth.token.ts +158 -0
- package/src/oauth.ts +130 -0
- package/src/outbound.test.ts +10 -11
- package/src/outbound.ts +62 -44
- package/src/pending-uploads-fs.test.ts +246 -0
- package/src/pending-uploads-fs.ts +235 -0
- package/src/pending-uploads.test.ts +173 -0
- package/src/pending-uploads.ts +34 -2
- package/src/policy.test.ts +11 -5
- package/src/policy.ts +5 -5
- package/src/polls.test.ts +106 -5
- package/src/polls.ts +15 -7
- package/src/presentation.ts +68 -0
- package/src/probe.test.ts +27 -8
- package/src/probe.ts +43 -9
- package/src/reply-dispatcher.test.ts +437 -0
- package/src/reply-dispatcher.ts +259 -73
- package/src/reply-stream-controller.test.ts +235 -0
- package/src/reply-stream-controller.ts +147 -0
- package/src/resolve-allowlist.test.ts +105 -1
- package/src/resolve-allowlist.ts +112 -7
- package/src/runtime.ts +6 -3
- package/src/sdk-types.ts +43 -3
- package/src/sdk.test.ts +666 -0
- package/src/sdk.ts +867 -16
- package/src/secret-contract.ts +49 -0
- package/src/secret-input.ts +1 -1
- package/src/send-context.ts +76 -9
- package/src/send.test.ts +389 -5
- package/src/send.ts +140 -32
- package/src/sent-message-cache.ts +30 -18
- package/src/session-route.ts +40 -0
- package/src/setup-core.ts +160 -0
- package/src/setup-surface.test.ts +202 -0
- package/src/setup-surface.ts +320 -0
- package/src/sso-token-store.test.ts +72 -0
- package/src/sso-token-store.ts +166 -0
- package/src/sso.ts +300 -0
- package/src/storage.ts +1 -1
- package/src/store-fs.ts +2 -2
- package/src/streaming-message.test.ts +262 -0
- package/src/streaming-message.ts +297 -0
- package/src/test-runtime.ts +1 -1
- package/src/thread-parent-context.test.ts +224 -0
- package/src/thread-parent-context.ts +159 -0
- package/src/token.test.ts +237 -50
- package/src/token.ts +162 -7
- package/src/user-agent.test.ts +86 -0
- package/src/user-agent.ts +53 -0
- package/src/webhook-timeouts.ts +27 -0
- package/src/welcome-card.test.ts +81 -0
- package/src/welcome-card.ts +57 -0
- package/test-api.ts +1 -0
- package/tsconfig.json +16 -0
- package/CHANGELOG.md +0 -107
- package/src/file-lock.ts +0 -1
- package/src/graph-users.test.ts +0 -66
- package/src/onboarding.ts +0 -381
- package/src/polls-store.test.ts +0 -38
- package/src/revoked-context.test.ts +0 -39
- package/src/token-response.test.ts +0 -23
package/src/graph-users.test.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { searchGraphUsers } from "./graph-users.js";
|
|
3
|
-
import { fetchGraphJson } from "./graph.js";
|
|
4
|
-
|
|
5
|
-
vi.mock("./graph.js", () => ({
|
|
6
|
-
escapeOData: vi.fn((value: string) => value.replace(/'/g, "''")),
|
|
7
|
-
fetchGraphJson: vi.fn(),
|
|
8
|
-
}));
|
|
9
|
-
|
|
10
|
-
describe("searchGraphUsers", () => {
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
vi.mocked(fetchGraphJson).mockReset();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it("returns empty array for blank queries", async () => {
|
|
16
|
-
await expect(searchGraphUsers({ token: "token-1", query: " " })).resolves.toEqual([]);
|
|
17
|
-
expect(fetchGraphJson).not.toHaveBeenCalled();
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it("uses exact mail/upn filter lookup for email-like queries", async () => {
|
|
21
|
-
vi.mocked(fetchGraphJson).mockResolvedValueOnce({
|
|
22
|
-
value: [{ id: "user-1", displayName: "User One" }],
|
|
23
|
-
} as never);
|
|
24
|
-
|
|
25
|
-
const result = await searchGraphUsers({
|
|
26
|
-
token: "token-2",
|
|
27
|
-
query: "alice.o'hara@example.com",
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
expect(fetchGraphJson).toHaveBeenCalledWith({
|
|
31
|
-
token: "token-2",
|
|
32
|
-
path: "/users?$filter=(mail%20eq%20'alice.o''hara%40example.com'%20or%20userPrincipalName%20eq%20'alice.o''hara%40example.com')&$select=id,displayName,mail,userPrincipalName",
|
|
33
|
-
});
|
|
34
|
-
expect(result).toEqual([{ id: "user-1", displayName: "User One" }]);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("uses displayName search with eventual consistency and custom top", async () => {
|
|
38
|
-
vi.mocked(fetchGraphJson).mockResolvedValueOnce({
|
|
39
|
-
value: [{ id: "user-2", displayName: "Bob" }],
|
|
40
|
-
} as never);
|
|
41
|
-
|
|
42
|
-
const result = await searchGraphUsers({
|
|
43
|
-
token: "token-3",
|
|
44
|
-
query: "bob",
|
|
45
|
-
top: 25,
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
expect(fetchGraphJson).toHaveBeenCalledWith({
|
|
49
|
-
token: "token-3",
|
|
50
|
-
path: "/users?$search=%22displayName%3Abob%22&$select=id,displayName,mail,userPrincipalName&$top=25",
|
|
51
|
-
headers: { ConsistencyLevel: "eventual" },
|
|
52
|
-
});
|
|
53
|
-
expect(result).toEqual([{ id: "user-2", displayName: "Bob" }]);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it("falls back to default top and empty value handling", async () => {
|
|
57
|
-
vi.mocked(fetchGraphJson).mockResolvedValueOnce({} as never);
|
|
58
|
-
|
|
59
|
-
await expect(searchGraphUsers({ token: "token-4", query: "carol" })).resolves.toEqual([]);
|
|
60
|
-
expect(fetchGraphJson).toHaveBeenCalledWith({
|
|
61
|
-
token: "token-4",
|
|
62
|
-
path: "/users?$search=%22displayName%3Acarol%22&$select=id,displayName,mail,userPrincipalName&$top=10",
|
|
63
|
-
headers: { ConsistencyLevel: "eventual" },
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
});
|
package/src/onboarding.ts
DELETED
|
@@ -1,381 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ChannelOnboardingAdapter,
|
|
3
|
-
ChannelOnboardingDmPolicy,
|
|
4
|
-
OpenClawConfig,
|
|
5
|
-
DmPolicy,
|
|
6
|
-
WizardPrompter,
|
|
7
|
-
MSTeamsTeamConfig,
|
|
8
|
-
} from "openclaw/plugin-sdk/msteams";
|
|
9
|
-
import {
|
|
10
|
-
DEFAULT_ACCOUNT_ID,
|
|
11
|
-
formatDocsLink,
|
|
12
|
-
mergeAllowFromEntries,
|
|
13
|
-
promptChannelAccessConfig,
|
|
14
|
-
setTopLevelChannelAllowFrom,
|
|
15
|
-
setTopLevelChannelDmPolicyWithAllowFrom,
|
|
16
|
-
setTopLevelChannelGroupPolicy,
|
|
17
|
-
splitOnboardingEntries,
|
|
18
|
-
} from "openclaw/plugin-sdk/msteams";
|
|
19
|
-
import {
|
|
20
|
-
parseMSTeamsTeamEntry,
|
|
21
|
-
resolveMSTeamsChannelAllowlist,
|
|
22
|
-
resolveMSTeamsUserAllowlist,
|
|
23
|
-
} from "./resolve-allowlist.js";
|
|
24
|
-
import { normalizeSecretInputString } from "./secret-input.js";
|
|
25
|
-
import { hasConfiguredMSTeamsCredentials, resolveMSTeamsCredentials } from "./token.js";
|
|
26
|
-
|
|
27
|
-
const channel = "msteams" as const;
|
|
28
|
-
|
|
29
|
-
function setMSTeamsDmPolicy(cfg: OpenClawConfig, dmPolicy: DmPolicy) {
|
|
30
|
-
return setTopLevelChannelDmPolicyWithAllowFrom({
|
|
31
|
-
cfg,
|
|
32
|
-
channel: "msteams",
|
|
33
|
-
dmPolicy,
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function setMSTeamsAllowFrom(cfg: OpenClawConfig, allowFrom: string[]): OpenClawConfig {
|
|
38
|
-
return setTopLevelChannelAllowFrom({
|
|
39
|
-
cfg,
|
|
40
|
-
channel: "msteams",
|
|
41
|
-
allowFrom,
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function looksLikeGuid(value: string): boolean {
|
|
46
|
-
return /^[0-9a-fA-F-]{16,}$/.test(value);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async function promptMSTeamsCredentials(prompter: WizardPrompter): Promise<{
|
|
50
|
-
appId: string;
|
|
51
|
-
appPassword: string;
|
|
52
|
-
tenantId: string;
|
|
53
|
-
}> {
|
|
54
|
-
const appId = String(
|
|
55
|
-
await prompter.text({
|
|
56
|
-
message: "Enter MS Teams App ID",
|
|
57
|
-
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
58
|
-
}),
|
|
59
|
-
).trim();
|
|
60
|
-
const appPassword = String(
|
|
61
|
-
await prompter.text({
|
|
62
|
-
message: "Enter MS Teams App Password",
|
|
63
|
-
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
64
|
-
}),
|
|
65
|
-
).trim();
|
|
66
|
-
const tenantId = String(
|
|
67
|
-
await prompter.text({
|
|
68
|
-
message: "Enter MS Teams Tenant ID",
|
|
69
|
-
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
70
|
-
}),
|
|
71
|
-
).trim();
|
|
72
|
-
return { appId, appPassword, tenantId };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async function promptMSTeamsAllowFrom(params: {
|
|
76
|
-
cfg: OpenClawConfig;
|
|
77
|
-
prompter: WizardPrompter;
|
|
78
|
-
}): Promise<OpenClawConfig> {
|
|
79
|
-
const existing = params.cfg.channels?.msteams?.allowFrom ?? [];
|
|
80
|
-
await params.prompter.note(
|
|
81
|
-
[
|
|
82
|
-
"Allowlist MS Teams DMs by display name, UPN/email, or user id.",
|
|
83
|
-
"We resolve names to user IDs via Microsoft Graph when credentials allow.",
|
|
84
|
-
"Examples:",
|
|
85
|
-
"- alex@example.com",
|
|
86
|
-
"- Alex Johnson",
|
|
87
|
-
"- 00000000-0000-0000-0000-000000000000",
|
|
88
|
-
].join("\n"),
|
|
89
|
-
"MS Teams allowlist",
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
while (true) {
|
|
93
|
-
const entry = await params.prompter.text({
|
|
94
|
-
message: "MS Teams allowFrom (usernames or ids)",
|
|
95
|
-
placeholder: "alex@example.com, Alex Johnson",
|
|
96
|
-
initialValue: existing[0] ? String(existing[0]) : undefined,
|
|
97
|
-
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
|
98
|
-
});
|
|
99
|
-
const parts = splitOnboardingEntries(String(entry));
|
|
100
|
-
if (parts.length === 0) {
|
|
101
|
-
await params.prompter.note("Enter at least one user.", "MS Teams allowlist");
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const resolved = await resolveMSTeamsUserAllowlist({
|
|
106
|
-
cfg: params.cfg,
|
|
107
|
-
entries: parts,
|
|
108
|
-
}).catch(() => null);
|
|
109
|
-
|
|
110
|
-
if (!resolved) {
|
|
111
|
-
const ids = parts.filter((part) => looksLikeGuid(part));
|
|
112
|
-
if (ids.length !== parts.length) {
|
|
113
|
-
await params.prompter.note(
|
|
114
|
-
"Graph lookup unavailable. Use user IDs only.",
|
|
115
|
-
"MS Teams allowlist",
|
|
116
|
-
);
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
const unique = mergeAllowFromEntries(existing, ids);
|
|
120
|
-
return setMSTeamsAllowFrom(params.cfg, unique);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const unresolved = resolved.filter((item) => !item.resolved || !item.id);
|
|
124
|
-
if (unresolved.length > 0) {
|
|
125
|
-
await params.prompter.note(
|
|
126
|
-
`Could not resolve: ${unresolved.map((item) => item.input).join(", ")}`,
|
|
127
|
-
"MS Teams allowlist",
|
|
128
|
-
);
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const ids = resolved.map((item) => item.id as string);
|
|
133
|
-
const unique = mergeAllowFromEntries(existing, ids);
|
|
134
|
-
return setMSTeamsAllowFrom(params.cfg, unique);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
async function noteMSTeamsCredentialHelp(prompter: WizardPrompter): Promise<void> {
|
|
139
|
-
await prompter.note(
|
|
140
|
-
[
|
|
141
|
-
"1) Azure Bot registration → get App ID + Tenant ID",
|
|
142
|
-
"2) Add a client secret (App Password)",
|
|
143
|
-
"3) Set webhook URL + messaging endpoint",
|
|
144
|
-
"Tip: you can also set MSTEAMS_APP_ID / MSTEAMS_APP_PASSWORD / MSTEAMS_TENANT_ID.",
|
|
145
|
-
`Docs: ${formatDocsLink("/channels/msteams", "msteams")}`,
|
|
146
|
-
].join("\n"),
|
|
147
|
-
"MS Teams credentials",
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function setMSTeamsGroupPolicy(
|
|
152
|
-
cfg: OpenClawConfig,
|
|
153
|
-
groupPolicy: "open" | "allowlist" | "disabled",
|
|
154
|
-
): OpenClawConfig {
|
|
155
|
-
return setTopLevelChannelGroupPolicy({
|
|
156
|
-
cfg,
|
|
157
|
-
channel: "msteams",
|
|
158
|
-
groupPolicy,
|
|
159
|
-
enabled: true,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function setMSTeamsTeamsAllowlist(
|
|
164
|
-
cfg: OpenClawConfig,
|
|
165
|
-
entries: Array<{ teamKey: string; channelKey?: string }>,
|
|
166
|
-
): OpenClawConfig {
|
|
167
|
-
const baseTeams = cfg.channels?.msteams?.teams ?? {};
|
|
168
|
-
const teams: Record<string, { channels?: Record<string, unknown> }> = { ...baseTeams };
|
|
169
|
-
for (const entry of entries) {
|
|
170
|
-
const teamKey = entry.teamKey;
|
|
171
|
-
if (!teamKey) {
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
174
|
-
const existing = teams[teamKey] ?? {};
|
|
175
|
-
if (entry.channelKey) {
|
|
176
|
-
const channels = { ...existing.channels };
|
|
177
|
-
channels[entry.channelKey] = channels[entry.channelKey] ?? {};
|
|
178
|
-
teams[teamKey] = { ...existing, channels };
|
|
179
|
-
} else {
|
|
180
|
-
teams[teamKey] = existing;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
return {
|
|
184
|
-
...cfg,
|
|
185
|
-
channels: {
|
|
186
|
-
...cfg.channels,
|
|
187
|
-
msteams: {
|
|
188
|
-
...cfg.channels?.msteams,
|
|
189
|
-
enabled: true,
|
|
190
|
-
teams: teams as Record<string, MSTeamsTeamConfig>,
|
|
191
|
-
},
|
|
192
|
-
},
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const dmPolicy: ChannelOnboardingDmPolicy = {
|
|
197
|
-
label: "MS Teams",
|
|
198
|
-
channel,
|
|
199
|
-
policyKey: "channels.msteams.dmPolicy",
|
|
200
|
-
allowFromKey: "channels.msteams.allowFrom",
|
|
201
|
-
getCurrent: (cfg) => cfg.channels?.msteams?.dmPolicy ?? "pairing",
|
|
202
|
-
setPolicy: (cfg, policy) => setMSTeamsDmPolicy(cfg, policy),
|
|
203
|
-
promptAllowFrom: promptMSTeamsAllowFrom,
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
export const msteamsOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
207
|
-
channel,
|
|
208
|
-
getStatus: async ({ cfg }) => {
|
|
209
|
-
const configured =
|
|
210
|
-
Boolean(resolveMSTeamsCredentials(cfg.channels?.msteams)) ||
|
|
211
|
-
hasConfiguredMSTeamsCredentials(cfg.channels?.msteams);
|
|
212
|
-
return {
|
|
213
|
-
channel,
|
|
214
|
-
configured,
|
|
215
|
-
statusLines: [`MS Teams: ${configured ? "configured" : "needs app credentials"}`],
|
|
216
|
-
selectionHint: configured ? "configured" : "needs app creds",
|
|
217
|
-
quickstartScore: configured ? 2 : 0,
|
|
218
|
-
};
|
|
219
|
-
},
|
|
220
|
-
configure: async ({ cfg, prompter }) => {
|
|
221
|
-
const resolved = resolveMSTeamsCredentials(cfg.channels?.msteams);
|
|
222
|
-
const hasConfigCreds = hasConfiguredMSTeamsCredentials(cfg.channels?.msteams);
|
|
223
|
-
const canUseEnv = Boolean(
|
|
224
|
-
!hasConfigCreds &&
|
|
225
|
-
normalizeSecretInputString(process.env.MSTEAMS_APP_ID) &&
|
|
226
|
-
normalizeSecretInputString(process.env.MSTEAMS_APP_PASSWORD) &&
|
|
227
|
-
normalizeSecretInputString(process.env.MSTEAMS_TENANT_ID),
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
let next = cfg;
|
|
231
|
-
let appId: string | null = null;
|
|
232
|
-
let appPassword: string | null = null;
|
|
233
|
-
let tenantId: string | null = null;
|
|
234
|
-
|
|
235
|
-
if (!resolved && !hasConfigCreds) {
|
|
236
|
-
await noteMSTeamsCredentialHelp(prompter);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (canUseEnv) {
|
|
240
|
-
const keepEnv = await prompter.confirm({
|
|
241
|
-
message:
|
|
242
|
-
"MSTEAMS_APP_ID + MSTEAMS_APP_PASSWORD + MSTEAMS_TENANT_ID detected. Use env vars?",
|
|
243
|
-
initialValue: true,
|
|
244
|
-
});
|
|
245
|
-
if (keepEnv) {
|
|
246
|
-
next = {
|
|
247
|
-
...next,
|
|
248
|
-
channels: {
|
|
249
|
-
...next.channels,
|
|
250
|
-
msteams: { ...next.channels?.msteams, enabled: true },
|
|
251
|
-
},
|
|
252
|
-
};
|
|
253
|
-
} else {
|
|
254
|
-
({ appId, appPassword, tenantId } = await promptMSTeamsCredentials(prompter));
|
|
255
|
-
}
|
|
256
|
-
} else if (hasConfigCreds) {
|
|
257
|
-
const keep = await prompter.confirm({
|
|
258
|
-
message: "MS Teams credentials already configured. Keep them?",
|
|
259
|
-
initialValue: true,
|
|
260
|
-
});
|
|
261
|
-
if (!keep) {
|
|
262
|
-
({ appId, appPassword, tenantId } = await promptMSTeamsCredentials(prompter));
|
|
263
|
-
}
|
|
264
|
-
} else {
|
|
265
|
-
({ appId, appPassword, tenantId } = await promptMSTeamsCredentials(prompter));
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (appId && appPassword && tenantId) {
|
|
269
|
-
next = {
|
|
270
|
-
...next,
|
|
271
|
-
channels: {
|
|
272
|
-
...next.channels,
|
|
273
|
-
msteams: {
|
|
274
|
-
...next.channels?.msteams,
|
|
275
|
-
enabled: true,
|
|
276
|
-
appId,
|
|
277
|
-
appPassword,
|
|
278
|
-
tenantId,
|
|
279
|
-
},
|
|
280
|
-
},
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const currentEntries = Object.entries(next.channels?.msteams?.teams ?? {}).flatMap(
|
|
285
|
-
([teamKey, value]) => {
|
|
286
|
-
const channels = value?.channels ?? {};
|
|
287
|
-
const channelKeys = Object.keys(channels);
|
|
288
|
-
if (channelKeys.length === 0) {
|
|
289
|
-
return [teamKey];
|
|
290
|
-
}
|
|
291
|
-
return channelKeys.map((channelKey) => `${teamKey}/${channelKey}`);
|
|
292
|
-
},
|
|
293
|
-
);
|
|
294
|
-
const accessConfig = await promptChannelAccessConfig({
|
|
295
|
-
prompter,
|
|
296
|
-
label: "MS Teams channels",
|
|
297
|
-
currentPolicy: next.channels?.msteams?.groupPolicy ?? "allowlist",
|
|
298
|
-
currentEntries,
|
|
299
|
-
placeholder: "Team Name/Channel Name, teamId/conversationId",
|
|
300
|
-
updatePrompt: Boolean(next.channels?.msteams?.teams),
|
|
301
|
-
});
|
|
302
|
-
if (accessConfig) {
|
|
303
|
-
if (accessConfig.policy !== "allowlist") {
|
|
304
|
-
next = setMSTeamsGroupPolicy(next, accessConfig.policy);
|
|
305
|
-
} else {
|
|
306
|
-
let entries = accessConfig.entries
|
|
307
|
-
.map((entry) => parseMSTeamsTeamEntry(entry))
|
|
308
|
-
.filter(Boolean) as Array<{ teamKey: string; channelKey?: string }>;
|
|
309
|
-
if (accessConfig.entries.length > 0 && resolveMSTeamsCredentials(next.channels?.msteams)) {
|
|
310
|
-
try {
|
|
311
|
-
const resolved = await resolveMSTeamsChannelAllowlist({
|
|
312
|
-
cfg: next,
|
|
313
|
-
entries: accessConfig.entries,
|
|
314
|
-
});
|
|
315
|
-
const resolvedChannels = resolved.filter(
|
|
316
|
-
(entry) => entry.resolved && entry.teamId && entry.channelId,
|
|
317
|
-
);
|
|
318
|
-
const resolvedTeams = resolved.filter(
|
|
319
|
-
(entry) => entry.resolved && entry.teamId && !entry.channelId,
|
|
320
|
-
);
|
|
321
|
-
const unresolved = resolved
|
|
322
|
-
.filter((entry) => !entry.resolved)
|
|
323
|
-
.map((entry) => entry.input);
|
|
324
|
-
|
|
325
|
-
entries = [
|
|
326
|
-
...resolvedChannels.map((entry) => ({
|
|
327
|
-
teamKey: entry.teamId as string,
|
|
328
|
-
channelKey: entry.channelId as string,
|
|
329
|
-
})),
|
|
330
|
-
...resolvedTeams.map((entry) => ({
|
|
331
|
-
teamKey: entry.teamId as string,
|
|
332
|
-
})),
|
|
333
|
-
...unresolved.map((entry) => parseMSTeamsTeamEntry(entry)).filter(Boolean),
|
|
334
|
-
] as Array<{ teamKey: string; channelKey?: string }>;
|
|
335
|
-
|
|
336
|
-
if (resolvedChannels.length > 0 || resolvedTeams.length > 0 || unresolved.length > 0) {
|
|
337
|
-
const summary: string[] = [];
|
|
338
|
-
if (resolvedChannels.length > 0) {
|
|
339
|
-
summary.push(
|
|
340
|
-
`Resolved channels: ${resolvedChannels
|
|
341
|
-
.map((entry) => entry.channelId)
|
|
342
|
-
.filter(Boolean)
|
|
343
|
-
.join(", ")}`,
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
if (resolvedTeams.length > 0) {
|
|
347
|
-
summary.push(
|
|
348
|
-
`Resolved teams: ${resolvedTeams
|
|
349
|
-
.map((entry) => entry.teamId)
|
|
350
|
-
.filter(Boolean)
|
|
351
|
-
.join(", ")}`,
|
|
352
|
-
);
|
|
353
|
-
}
|
|
354
|
-
if (unresolved.length > 0) {
|
|
355
|
-
summary.push(`Unresolved (kept as typed): ${unresolved.join(", ")}`);
|
|
356
|
-
}
|
|
357
|
-
await prompter.note(summary.join("\n"), "MS Teams channels");
|
|
358
|
-
}
|
|
359
|
-
} catch (err) {
|
|
360
|
-
await prompter.note(
|
|
361
|
-
`Channel lookup failed; keeping entries as typed. ${String(err)}`,
|
|
362
|
-
"MS Teams channels",
|
|
363
|
-
);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
next = setMSTeamsGroupPolicy(next, "allowlist");
|
|
367
|
-
next = setMSTeamsTeamsAllowlist(next, entries);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
return { cfg: next, accountId: DEFAULT_ACCOUNT_ID };
|
|
372
|
-
},
|
|
373
|
-
dmPolicy,
|
|
374
|
-
disable: (cfg) => ({
|
|
375
|
-
...cfg,
|
|
376
|
-
channels: {
|
|
377
|
-
...cfg.channels,
|
|
378
|
-
msteams: { ...cfg.channels?.msteams, enabled: false },
|
|
379
|
-
},
|
|
380
|
-
}),
|
|
381
|
-
};
|
package/src/polls-store.test.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { describe, expect, it } from "vitest";
|
|
5
|
-
import { createMSTeamsPollStoreMemory } from "./polls-store-memory.js";
|
|
6
|
-
import { createMSTeamsPollStoreFs } from "./polls.js";
|
|
7
|
-
|
|
8
|
-
const createFsStore = async () => {
|
|
9
|
-
const stateDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "openclaw-msteams-polls-"));
|
|
10
|
-
return createMSTeamsPollStoreFs({ stateDir });
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const createMemoryStore = () => createMSTeamsPollStoreMemory();
|
|
14
|
-
|
|
15
|
-
describe.each([
|
|
16
|
-
{ name: "memory", createStore: createMemoryStore },
|
|
17
|
-
{ name: "fs", createStore: createFsStore },
|
|
18
|
-
])("$name poll store", ({ createStore }) => {
|
|
19
|
-
it("stores polls and records normalized votes", async () => {
|
|
20
|
-
const store = await createStore();
|
|
21
|
-
await store.createPoll({
|
|
22
|
-
id: "poll-1",
|
|
23
|
-
question: "Lunch?",
|
|
24
|
-
options: ["Pizza", "Sushi"],
|
|
25
|
-
maxSelections: 1,
|
|
26
|
-
createdAt: new Date().toISOString(),
|
|
27
|
-
votes: {},
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
const poll = await store.recordVote({
|
|
31
|
-
pollId: "poll-1",
|
|
32
|
-
voterId: "user-1",
|
|
33
|
-
selections: ["0", "1"],
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
expect(poll?.votes["user-1"]).toEqual(["0"]);
|
|
37
|
-
});
|
|
38
|
-
});
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { withRevokedProxyFallback } from "./revoked-context.js";
|
|
3
|
-
|
|
4
|
-
describe("msteams revoked context helper", () => {
|
|
5
|
-
it("returns primary result when no error occurs", async () => {
|
|
6
|
-
await expect(
|
|
7
|
-
withRevokedProxyFallback({
|
|
8
|
-
run: async () => "ok",
|
|
9
|
-
onRevoked: async () => "fallback",
|
|
10
|
-
}),
|
|
11
|
-
).resolves.toBe("ok");
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it("uses fallback when proxy-revoked TypeError is thrown", async () => {
|
|
15
|
-
const onRevokedLog = vi.fn();
|
|
16
|
-
await expect(
|
|
17
|
-
withRevokedProxyFallback({
|
|
18
|
-
run: async () => {
|
|
19
|
-
throw new TypeError("Cannot perform 'get' on a proxy that has been revoked");
|
|
20
|
-
},
|
|
21
|
-
onRevoked: async () => "fallback",
|
|
22
|
-
onRevokedLog,
|
|
23
|
-
}),
|
|
24
|
-
).resolves.toBe("fallback");
|
|
25
|
-
expect(onRevokedLog).toHaveBeenCalledOnce();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("rethrows non-revoked errors", async () => {
|
|
29
|
-
const err = Object.assign(new Error("boom"), { statusCode: 500 });
|
|
30
|
-
await expect(
|
|
31
|
-
withRevokedProxyFallback({
|
|
32
|
-
run: async () => {
|
|
33
|
-
throw err;
|
|
34
|
-
},
|
|
35
|
-
onRevoked: async () => "fallback",
|
|
36
|
-
}),
|
|
37
|
-
).rejects.toBe(err);
|
|
38
|
-
});
|
|
39
|
-
});
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { readAccessToken } from "./token-response.js";
|
|
3
|
-
|
|
4
|
-
describe("readAccessToken", () => {
|
|
5
|
-
it("returns raw string token values", () => {
|
|
6
|
-
expect(readAccessToken("abc")).toBe("abc");
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
it("returns accessToken from object value", () => {
|
|
10
|
-
expect(readAccessToken({ accessToken: "access-token" })).toBe("access-token");
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it("returns token fallback from object value", () => {
|
|
14
|
-
expect(readAccessToken({ token: "fallback-token" })).toBe("fallback-token");
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("returns null for unsupported values", () => {
|
|
18
|
-
expect(readAccessToken({ accessToken: 123 })).toBeNull();
|
|
19
|
-
expect(readAccessToken({ token: false })).toBeNull();
|
|
20
|
-
expect(readAccessToken(null)).toBeNull();
|
|
21
|
-
expect(readAccessToken(undefined)).toBeNull();
|
|
22
|
-
});
|
|
23
|
-
});
|