@openclaw/msteams 2026.2.14 → 2026.2.17
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 +18 -0
- package/package.json +1 -1
- package/src/attachments/download.ts +5 -5
- package/src/attachments/graph.ts +6 -6
- package/src/attachments/html.ts +1 -1
- package/src/attachments.test.ts +3 -2
- package/src/channel.directory.test.ts +21 -3
- package/src/channel.ts +4 -13
- package/src/conversation-store-fs.test.ts +3 -16
- package/src/messenger.test.ts +6 -0
- package/src/monitor-handler/inbound-media.ts +1 -1
- package/src/monitor-handler/message-handler.ts +3 -3
- package/src/monitor-handler.ts +3 -3
- package/src/monitor.ts +2 -2
- package/src/onboarding.ts +32 -58
- package/src/policy.ts +2 -18
- package/src/polls.test.ts +2 -15
- package/src/probe.ts +2 -4
- package/src/reply-dispatcher.ts +2 -2
- package/src/send-context.ts +1 -1
- package/src/send.ts +53 -42
- package/src/store-fs.ts +3 -25
- package/src/test-runtime.ts +16 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2026.2.17
|
|
4
|
+
|
|
5
|
+
### Changes
|
|
6
|
+
|
|
7
|
+
- Version alignment with core OpenClaw release numbers.
|
|
8
|
+
|
|
9
|
+
## 2026.2.16
|
|
10
|
+
|
|
11
|
+
### Changes
|
|
12
|
+
|
|
13
|
+
- Version alignment with core OpenClaw release numbers.
|
|
14
|
+
|
|
15
|
+
## 2026.2.15
|
|
16
|
+
|
|
17
|
+
### Changes
|
|
18
|
+
|
|
19
|
+
- Version alignment with core OpenClaw release numbers.
|
|
20
|
+
|
|
3
21
|
## 2026.2.14
|
|
4
22
|
|
|
5
23
|
### Changes
|
package/package.json
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
MSTeamsAccessTokenProvider,
|
|
3
|
-
MSTeamsAttachmentLike,
|
|
4
|
-
MSTeamsInboundMedia,
|
|
5
|
-
} from "./types.js";
|
|
6
1
|
import { getMSTeamsRuntime } from "../runtime.js";
|
|
7
2
|
import {
|
|
8
3
|
extractInlineImageCandidates,
|
|
@@ -14,6 +9,11 @@ import {
|
|
|
14
9
|
resolveAuthAllowedHosts,
|
|
15
10
|
resolveAllowedHosts,
|
|
16
11
|
} from "./shared.js";
|
|
12
|
+
import type {
|
|
13
|
+
MSTeamsAccessTokenProvider,
|
|
14
|
+
MSTeamsAttachmentLike,
|
|
15
|
+
MSTeamsInboundMedia,
|
|
16
|
+
} from "./types.js";
|
|
17
17
|
|
|
18
18
|
type DownloadCandidate = {
|
|
19
19
|
url: string;
|
package/src/attachments/graph.ts
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
MSTeamsAccessTokenProvider,
|
|
3
|
-
MSTeamsAttachmentLike,
|
|
4
|
-
MSTeamsGraphMediaResult,
|
|
5
|
-
MSTeamsInboundMedia,
|
|
6
|
-
} from "./types.js";
|
|
7
1
|
import { getMSTeamsRuntime } from "../runtime.js";
|
|
8
2
|
import { downloadMSTeamsAttachments } from "./download.js";
|
|
9
3
|
import {
|
|
@@ -13,6 +7,12 @@ import {
|
|
|
13
7
|
normalizeContentType,
|
|
14
8
|
resolveAllowedHosts,
|
|
15
9
|
} from "./shared.js";
|
|
10
|
+
import type {
|
|
11
|
+
MSTeamsAccessTokenProvider,
|
|
12
|
+
MSTeamsAttachmentLike,
|
|
13
|
+
MSTeamsGraphMediaResult,
|
|
14
|
+
MSTeamsInboundMedia,
|
|
15
|
+
} from "./types.js";
|
|
16
16
|
|
|
17
17
|
type GraphHostedContent = {
|
|
18
18
|
id?: string | null;
|
package/src/attachments/html.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { MSTeamsAttachmentLike, MSTeamsHtmlAttachmentSummary } from "./types.js";
|
|
2
1
|
import {
|
|
3
2
|
ATTACHMENT_TAG_RE,
|
|
4
3
|
extractHtmlFromAttachment,
|
|
@@ -7,6 +6,7 @@ import {
|
|
|
7
6
|
isLikelyImageAttachment,
|
|
8
7
|
safeHostForUrl,
|
|
9
8
|
} from "./shared.js";
|
|
9
|
+
import type { MSTeamsAttachmentLike, MSTeamsHtmlAttachmentSummary } from "./types.js";
|
|
10
10
|
|
|
11
11
|
export function summarizeMSTeamsHtmlAttachments(
|
|
12
12
|
attachments: MSTeamsAttachmentLike[] | undefined,
|
package/src/attachments.test.ts
CHANGED
|
@@ -10,11 +10,12 @@ const saveMediaBufferMock = vi.fn(async () => ({
|
|
|
10
10
|
|
|
11
11
|
const runtimeStub = {
|
|
12
12
|
media: {
|
|
13
|
-
detectMime:
|
|
13
|
+
detectMime: detectMimeMock as unknown as PluginRuntime["media"]["detectMime"],
|
|
14
14
|
},
|
|
15
15
|
channel: {
|
|
16
16
|
media: {
|
|
17
|
-
saveMediaBuffer:
|
|
17
|
+
saveMediaBuffer:
|
|
18
|
+
saveMediaBufferMock as unknown as PluginRuntime["channel"]["media"]["saveMediaBuffer"],
|
|
18
19
|
},
|
|
19
20
|
},
|
|
20
21
|
} as unknown as PluginRuntime;
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
1
|
+
import type { OpenClawConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
|
2
2
|
import { describe, expect, it } from "vitest";
|
|
3
3
|
import { msteamsPlugin } from "./channel.js";
|
|
4
4
|
|
|
5
5
|
describe("msteams directory", () => {
|
|
6
|
+
const runtimeEnv: RuntimeEnv = {
|
|
7
|
+
log: () => {},
|
|
8
|
+
error: () => {},
|
|
9
|
+
exit: (code: number): never => {
|
|
10
|
+
throw new Error(`exit ${code}`);
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
6
14
|
it("lists peers and groups from config", async () => {
|
|
7
15
|
const cfg = {
|
|
8
16
|
channels: {
|
|
@@ -26,7 +34,12 @@ describe("msteams directory", () => {
|
|
|
26
34
|
expect(msteamsPlugin.directory?.listGroups).toBeTruthy();
|
|
27
35
|
|
|
28
36
|
await expect(
|
|
29
|
-
msteamsPlugin.directory!.listPeers({
|
|
37
|
+
msteamsPlugin.directory!.listPeers!({
|
|
38
|
+
cfg,
|
|
39
|
+
query: undefined,
|
|
40
|
+
limit: undefined,
|
|
41
|
+
runtime: runtimeEnv,
|
|
42
|
+
}),
|
|
30
43
|
).resolves.toEqual(
|
|
31
44
|
expect.arrayContaining([
|
|
32
45
|
{ kind: "user", id: "user:alice" },
|
|
@@ -37,7 +50,12 @@ describe("msteams directory", () => {
|
|
|
37
50
|
);
|
|
38
51
|
|
|
39
52
|
await expect(
|
|
40
|
-
msteamsPlugin.directory!.listGroups({
|
|
53
|
+
msteamsPlugin.directory!.listGroups!({
|
|
54
|
+
cfg,
|
|
55
|
+
query: undefined,
|
|
56
|
+
limit: undefined,
|
|
57
|
+
runtime: runtimeEnv,
|
|
58
|
+
}),
|
|
41
59
|
).resolves.toEqual(
|
|
42
60
|
expect.arrayContaining([
|
|
43
61
|
{ kind: "group", id: "conversation:chan1" },
|
package/src/channel.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { ChannelMessageActionName, ChannelPlugin, OpenClawConfig } from "openclaw/plugin-sdk";
|
|
2
2
|
import {
|
|
3
|
+
buildBaseChannelStatusSummary,
|
|
3
4
|
buildChannelConfigSchema,
|
|
5
|
+
createDefaultChannelRuntimeState,
|
|
4
6
|
DEFAULT_ACCOUNT_ID,
|
|
5
7
|
MSTeamsConfigSchema,
|
|
6
8
|
PAIRING_APPROVED_MESSAGE,
|
|
@@ -415,20 +417,9 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount> = {
|
|
|
415
417
|
},
|
|
416
418
|
outbound: msteamsOutbound,
|
|
417
419
|
status: {
|
|
418
|
-
defaultRuntime: {
|
|
419
|
-
accountId: DEFAULT_ACCOUNT_ID,
|
|
420
|
-
running: false,
|
|
421
|
-
lastStartAt: null,
|
|
422
|
-
lastStopAt: null,
|
|
423
|
-
lastError: null,
|
|
424
|
-
port: null,
|
|
425
|
-
},
|
|
420
|
+
defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID, { port: null }),
|
|
426
421
|
buildChannelSummary: ({ snapshot }) => ({
|
|
427
|
-
|
|
428
|
-
running: snapshot.running ?? false,
|
|
429
|
-
lastStartAt: snapshot.lastStartAt ?? null,
|
|
430
|
-
lastStopAt: snapshot.lastStopAt ?? null,
|
|
431
|
-
lastError: snapshot.lastError ?? null,
|
|
422
|
+
...buildBaseChannelStatusSummary(snapshot),
|
|
432
423
|
port: snapshot.port ?? null,
|
|
433
424
|
probe: snapshot.probe,
|
|
434
425
|
lastProbeAt: snapshot.lastProbeAt ?? null,
|
|
@@ -1,28 +1,15 @@
|
|
|
1
|
-
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
|
2
1
|
import fs from "node:fs";
|
|
3
2
|
import os from "node:os";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
import { beforeEach, describe, expect, it } from "vitest";
|
|
6
|
-
import type { StoredConversationReference } from "./conversation-store.js";
|
|
7
5
|
import { createMSTeamsConversationStoreFs } from "./conversation-store-fs.js";
|
|
6
|
+
import type { StoredConversationReference } from "./conversation-store.js";
|
|
8
7
|
import { setMSTeamsRuntime } from "./runtime.js";
|
|
9
|
-
|
|
10
|
-
const runtimeStub = {
|
|
11
|
-
state: {
|
|
12
|
-
resolveStateDir: (env: NodeJS.ProcessEnv = process.env, homedir?: () => string) => {
|
|
13
|
-
const override = env.OPENCLAW_STATE_DIR?.trim() || env.OPENCLAW_STATE_DIR?.trim();
|
|
14
|
-
if (override) {
|
|
15
|
-
return override;
|
|
16
|
-
}
|
|
17
|
-
const resolvedHome = homedir ? homedir() : os.homedir();
|
|
18
|
-
return path.join(resolvedHome, ".openclaw");
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
} as unknown as PluginRuntime;
|
|
8
|
+
import { msteamsRuntimeStub } from "./test-runtime.js";
|
|
22
9
|
|
|
23
10
|
describe("msteams conversation store (fs)", () => {
|
|
24
11
|
beforeEach(() => {
|
|
25
|
-
setMSTeamsRuntime(
|
|
12
|
+
setMSTeamsRuntime(msteamsRuntimeStub);
|
|
26
13
|
});
|
|
27
14
|
|
|
28
15
|
it("filters and prunes expired entries (but keeps legacy ones)", async () => {
|
package/src/messenger.test.ts
CHANGED
|
@@ -125,6 +125,7 @@ describe("msteams messenger", () => {
|
|
|
125
125
|
|
|
126
126
|
const adapter: MSTeamsAdapter = {
|
|
127
127
|
continueConversation: async () => {},
|
|
128
|
+
process: async () => {},
|
|
128
129
|
};
|
|
129
130
|
|
|
130
131
|
const ids = await sendMSTeamsMessages({
|
|
@@ -154,6 +155,7 @@ describe("msteams messenger", () => {
|
|
|
154
155
|
},
|
|
155
156
|
});
|
|
156
157
|
},
|
|
158
|
+
process: async () => {},
|
|
157
159
|
};
|
|
158
160
|
|
|
159
161
|
const ids = await sendMSTeamsMessages({
|
|
@@ -191,6 +193,7 @@ describe("msteams messenger", () => {
|
|
|
191
193
|
|
|
192
194
|
const adapter: MSTeamsAdapter = {
|
|
193
195
|
continueConversation: async () => {},
|
|
196
|
+
process: async () => {},
|
|
194
197
|
};
|
|
195
198
|
|
|
196
199
|
const ids = await sendMSTeamsMessages({
|
|
@@ -250,6 +253,7 @@ describe("msteams messenger", () => {
|
|
|
250
253
|
|
|
251
254
|
const adapter: MSTeamsAdapter = {
|
|
252
255
|
continueConversation: async () => {},
|
|
256
|
+
process: async () => {},
|
|
253
257
|
};
|
|
254
258
|
|
|
255
259
|
const ids = await sendMSTeamsMessages({
|
|
@@ -277,6 +281,7 @@ describe("msteams messenger", () => {
|
|
|
277
281
|
|
|
278
282
|
const adapter: MSTeamsAdapter = {
|
|
279
283
|
continueConversation: async () => {},
|
|
284
|
+
process: async () => {},
|
|
280
285
|
};
|
|
281
286
|
|
|
282
287
|
await expect(
|
|
@@ -310,6 +315,7 @@ describe("msteams messenger", () => {
|
|
|
310
315
|
},
|
|
311
316
|
});
|
|
312
317
|
},
|
|
318
|
+
process: async () => {},
|
|
313
319
|
};
|
|
314
320
|
|
|
315
321
|
const ids = await sendMSTeamsMessages({
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { MSTeamsTurnContext } from "../sdk-types.js";
|
|
2
1
|
import {
|
|
3
2
|
buildMSTeamsGraphMessageUrls,
|
|
4
3
|
downloadMSTeamsAttachments,
|
|
@@ -8,6 +7,7 @@ import {
|
|
|
8
7
|
type MSTeamsHtmlAttachmentSummary,
|
|
9
8
|
type MSTeamsInboundMedia,
|
|
10
9
|
} from "../attachments.js";
|
|
10
|
+
import type { MSTeamsTurnContext } from "../sdk-types.js";
|
|
11
11
|
|
|
12
12
|
type MSTeamsLogger = {
|
|
13
13
|
debug?: (message: string, meta?: Record<string, unknown>) => void;
|
|
@@ -9,15 +9,13 @@ import {
|
|
|
9
9
|
formatAllowlistMatchMeta,
|
|
10
10
|
type HistoryEntry,
|
|
11
11
|
} from "openclaw/plugin-sdk";
|
|
12
|
-
import type { StoredConversationReference } from "../conversation-store.js";
|
|
13
|
-
import type { MSTeamsMessageHandlerDeps } from "../monitor-handler.js";
|
|
14
|
-
import type { MSTeamsTurnContext } from "../sdk-types.js";
|
|
15
12
|
import {
|
|
16
13
|
buildMSTeamsAttachmentPlaceholder,
|
|
17
14
|
buildMSTeamsMediaPayload,
|
|
18
15
|
type MSTeamsAttachmentLike,
|
|
19
16
|
summarizeMSTeamsHtmlAttachments,
|
|
20
17
|
} from "../attachments.js";
|
|
18
|
+
import type { StoredConversationReference } from "../conversation-store.js";
|
|
21
19
|
import { formatUnknownError } from "../errors.js";
|
|
22
20
|
import {
|
|
23
21
|
extractMSTeamsConversationMessageId,
|
|
@@ -26,6 +24,7 @@ import {
|
|
|
26
24
|
stripMSTeamsMentionTags,
|
|
27
25
|
wasMSTeamsBotMentioned,
|
|
28
26
|
} from "../inbound.js";
|
|
27
|
+
import type { MSTeamsMessageHandlerDeps } from "../monitor-handler.js";
|
|
29
28
|
import {
|
|
30
29
|
isMSTeamsGroupAllowed,
|
|
31
30
|
resolveMSTeamsAllowlistMatch,
|
|
@@ -35,6 +34,7 @@ import {
|
|
|
35
34
|
import { extractMSTeamsPollVote } from "../polls.js";
|
|
36
35
|
import { createMSTeamsReplyDispatcher } from "../reply-dispatcher.js";
|
|
37
36
|
import { getMSTeamsRuntime } from "../runtime.js";
|
|
37
|
+
import type { MSTeamsTurnContext } from "../sdk-types.js";
|
|
38
38
|
import { recordMSTeamsSentMessage, wasMSTeamsMessageSent } from "../sent-message-cache.js";
|
|
39
39
|
import { resolveMSTeamsInboundMedia } from "./inbound-media.js";
|
|
40
40
|
|
package/src/monitor-handler.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { OpenClawConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
|
2
2
|
import type { MSTeamsConversationStore } from "./conversation-store.js";
|
|
3
|
+
import { buildFileInfoCard, parseFileConsentInvoke, uploadToConsentUrl } from "./file-consent.js";
|
|
3
4
|
import type { MSTeamsAdapter } from "./messenger.js";
|
|
5
|
+
import { createMSTeamsMessageHandler } from "./monitor-handler/message-handler.js";
|
|
4
6
|
import type { MSTeamsMonitorLogger } from "./monitor-types.js";
|
|
7
|
+
import { getPendingUpload, removePendingUpload } from "./pending-uploads.js";
|
|
5
8
|
import type { MSTeamsPollStore } from "./polls.js";
|
|
6
9
|
import type { MSTeamsTurnContext } from "./sdk-types.js";
|
|
7
|
-
import { buildFileInfoCard, parseFileConsentInvoke, uploadToConsentUrl } from "./file-consent.js";
|
|
8
|
-
import { createMSTeamsMessageHandler } from "./monitor-handler/message-handler.js";
|
|
9
|
-
import { getPendingUpload, removePendingUpload } from "./pending-uploads.js";
|
|
10
10
|
|
|
11
11
|
export type MSTeamsAccessTokenProvider = {
|
|
12
12
|
getAccessToken: (scope: string) => Promise<string>;
|
package/src/monitor.ts
CHANGED
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
type OpenClawConfig,
|
|
7
7
|
type RuntimeEnv,
|
|
8
8
|
} from "openclaw/plugin-sdk";
|
|
9
|
-
import type { MSTeamsConversationStore } from "./conversation-store.js";
|
|
10
|
-
import type { MSTeamsAdapter } from "./messenger.js";
|
|
11
9
|
import { createMSTeamsConversationStoreFs } from "./conversation-store-fs.js";
|
|
10
|
+
import type { MSTeamsConversationStore } from "./conversation-store.js";
|
|
12
11
|
import { formatUnknownError } from "./errors.js";
|
|
12
|
+
import type { MSTeamsAdapter } from "./messenger.js";
|
|
13
13
|
import { registerMSTeamsHandlers, type MSTeamsActivityHandler } from "./monitor-handler.js";
|
|
14
14
|
import { createMSTeamsPollStoreFs, type MSTeamsPollStore } from "./polls.js";
|
|
15
15
|
import {
|
package/src/onboarding.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
addWildcardAllowFrom,
|
|
11
11
|
DEFAULT_ACCOUNT_ID,
|
|
12
12
|
formatDocsLink,
|
|
13
|
+
mergeAllowFromEntries,
|
|
13
14
|
promptChannelAccessConfig,
|
|
14
15
|
} from "openclaw/plugin-sdk";
|
|
15
16
|
import {
|
|
@@ -63,6 +64,32 @@ function looksLikeGuid(value: string): boolean {
|
|
|
63
64
|
return /^[0-9a-fA-F-]{16,}$/.test(value);
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
async function promptMSTeamsCredentials(prompter: WizardPrompter): Promise<{
|
|
68
|
+
appId: string;
|
|
69
|
+
appPassword: string;
|
|
70
|
+
tenantId: string;
|
|
71
|
+
}> {
|
|
72
|
+
const appId = String(
|
|
73
|
+
await prompter.text({
|
|
74
|
+
message: "Enter MS Teams App ID",
|
|
75
|
+
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
76
|
+
}),
|
|
77
|
+
).trim();
|
|
78
|
+
const appPassword = String(
|
|
79
|
+
await prompter.text({
|
|
80
|
+
message: "Enter MS Teams App Password",
|
|
81
|
+
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
82
|
+
}),
|
|
83
|
+
).trim();
|
|
84
|
+
const tenantId = String(
|
|
85
|
+
await prompter.text({
|
|
86
|
+
message: "Enter MS Teams Tenant ID",
|
|
87
|
+
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
88
|
+
}),
|
|
89
|
+
).trim();
|
|
90
|
+
return { appId, appPassword, tenantId };
|
|
91
|
+
}
|
|
92
|
+
|
|
66
93
|
async function promptMSTeamsAllowFrom(params: {
|
|
67
94
|
cfg: OpenClawConfig;
|
|
68
95
|
prompter: WizardPrompter;
|
|
@@ -107,9 +134,7 @@ async function promptMSTeamsAllowFrom(params: {
|
|
|
107
134
|
);
|
|
108
135
|
continue;
|
|
109
136
|
}
|
|
110
|
-
const unique =
|
|
111
|
-
...new Set([...existing.map((v) => String(v).trim()).filter(Boolean), ...ids]),
|
|
112
|
-
];
|
|
137
|
+
const unique = mergeAllowFromEntries(existing, ids);
|
|
113
138
|
return setMSTeamsAllowFrom(params.cfg, unique);
|
|
114
139
|
}
|
|
115
140
|
|
|
@@ -123,7 +148,7 @@ async function promptMSTeamsAllowFrom(params: {
|
|
|
123
148
|
}
|
|
124
149
|
|
|
125
150
|
const ids = resolved.map((item) => item.id as string);
|
|
126
|
-
const unique =
|
|
151
|
+
const unique = mergeAllowFromEntries(existing, ids);
|
|
127
152
|
return setMSTeamsAllowFrom(params.cfg, unique);
|
|
128
153
|
}
|
|
129
154
|
}
|
|
@@ -251,24 +276,7 @@ export const msteamsOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
|
251
276
|
},
|
|
252
277
|
};
|
|
253
278
|
} else {
|
|
254
|
-
appId =
|
|
255
|
-
await prompter.text({
|
|
256
|
-
message: "Enter MS Teams App ID",
|
|
257
|
-
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
258
|
-
}),
|
|
259
|
-
).trim();
|
|
260
|
-
appPassword = String(
|
|
261
|
-
await prompter.text({
|
|
262
|
-
message: "Enter MS Teams App Password",
|
|
263
|
-
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
264
|
-
}),
|
|
265
|
-
).trim();
|
|
266
|
-
tenantId = String(
|
|
267
|
-
await prompter.text({
|
|
268
|
-
message: "Enter MS Teams Tenant ID",
|
|
269
|
-
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
270
|
-
}),
|
|
271
|
-
).trim();
|
|
279
|
+
({ appId, appPassword, tenantId } = await promptMSTeamsCredentials(prompter));
|
|
272
280
|
}
|
|
273
281
|
} else if (hasConfigCreds) {
|
|
274
282
|
const keep = await prompter.confirm({
|
|
@@ -276,44 +284,10 @@ export const msteamsOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
|
276
284
|
initialValue: true,
|
|
277
285
|
});
|
|
278
286
|
if (!keep) {
|
|
279
|
-
appId =
|
|
280
|
-
await prompter.text({
|
|
281
|
-
message: "Enter MS Teams App ID",
|
|
282
|
-
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
283
|
-
}),
|
|
284
|
-
).trim();
|
|
285
|
-
appPassword = String(
|
|
286
|
-
await prompter.text({
|
|
287
|
-
message: "Enter MS Teams App Password",
|
|
288
|
-
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
289
|
-
}),
|
|
290
|
-
).trim();
|
|
291
|
-
tenantId = String(
|
|
292
|
-
await prompter.text({
|
|
293
|
-
message: "Enter MS Teams Tenant ID",
|
|
294
|
-
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
295
|
-
}),
|
|
296
|
-
).trim();
|
|
287
|
+
({ appId, appPassword, tenantId } = await promptMSTeamsCredentials(prompter));
|
|
297
288
|
}
|
|
298
289
|
} else {
|
|
299
|
-
appId =
|
|
300
|
-
await prompter.text({
|
|
301
|
-
message: "Enter MS Teams App ID",
|
|
302
|
-
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
303
|
-
}),
|
|
304
|
-
).trim();
|
|
305
|
-
appPassword = String(
|
|
306
|
-
await prompter.text({
|
|
307
|
-
message: "Enter MS Teams App Password",
|
|
308
|
-
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
309
|
-
}),
|
|
310
|
-
).trim();
|
|
311
|
-
tenantId = String(
|
|
312
|
-
await prompter.text({
|
|
313
|
-
message: "Enter MS Teams Tenant ID",
|
|
314
|
-
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
315
|
-
}),
|
|
316
|
-
).trim();
|
|
290
|
+
({ appId, appPassword, tenantId } = await promptMSTeamsCredentials(prompter));
|
|
317
291
|
}
|
|
318
292
|
|
|
319
293
|
if (appId && appPassword && tenantId) {
|
package/src/policy.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
import {
|
|
12
12
|
buildChannelKeyCandidates,
|
|
13
13
|
normalizeChannelSlug,
|
|
14
|
+
resolveAllowlistMatchSimple,
|
|
14
15
|
resolveToolsBySender,
|
|
15
16
|
resolveChannelEntryMatchWithFallback,
|
|
16
17
|
resolveNestedAllowlistDecision,
|
|
@@ -209,24 +210,7 @@ export function resolveMSTeamsAllowlistMatch(params: {
|
|
|
209
210
|
senderId: string;
|
|
210
211
|
senderName?: string | null;
|
|
211
212
|
}): MSTeamsAllowlistMatch {
|
|
212
|
-
|
|
213
|
-
.map((entry) => String(entry).trim().toLowerCase())
|
|
214
|
-
.filter(Boolean);
|
|
215
|
-
if (allowFrom.length === 0) {
|
|
216
|
-
return { allowed: false };
|
|
217
|
-
}
|
|
218
|
-
if (allowFrom.includes("*")) {
|
|
219
|
-
return { allowed: true, matchKey: "*", matchSource: "wildcard" };
|
|
220
|
-
}
|
|
221
|
-
const senderId = params.senderId.toLowerCase();
|
|
222
|
-
if (allowFrom.includes(senderId)) {
|
|
223
|
-
return { allowed: true, matchKey: senderId, matchSource: "id" };
|
|
224
|
-
}
|
|
225
|
-
const senderName = params.senderName?.toLowerCase();
|
|
226
|
-
if (senderName && allowFrom.includes(senderName)) {
|
|
227
|
-
return { allowed: true, matchKey: senderName, matchSource: "name" };
|
|
228
|
-
}
|
|
229
|
-
return { allowed: false };
|
|
213
|
+
return resolveAllowlistMatchSimple(params);
|
|
230
214
|
}
|
|
231
215
|
|
|
232
216
|
export function resolveMSTeamsReplyPolicy(params: {
|
package/src/polls.test.ts
CHANGED
|
@@ -1,27 +1,14 @@
|
|
|
1
|
-
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
|
2
1
|
import fs from "node:fs";
|
|
3
2
|
import os from "node:os";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
import { beforeEach, describe, expect, it } from "vitest";
|
|
6
5
|
import { buildMSTeamsPollCard, createMSTeamsPollStoreFs, extractMSTeamsPollVote } from "./polls.js";
|
|
7
6
|
import { setMSTeamsRuntime } from "./runtime.js";
|
|
8
|
-
|
|
9
|
-
const runtimeStub = {
|
|
10
|
-
state: {
|
|
11
|
-
resolveStateDir: (env: NodeJS.ProcessEnv = process.env, homedir?: () => string) => {
|
|
12
|
-
const override = env.OPENCLAW_STATE_DIR?.trim() || env.OPENCLAW_STATE_DIR?.trim();
|
|
13
|
-
if (override) {
|
|
14
|
-
return override;
|
|
15
|
-
}
|
|
16
|
-
const resolvedHome = homedir ? homedir() : os.homedir();
|
|
17
|
-
return path.join(resolvedHome, ".openclaw");
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
} as unknown as PluginRuntime;
|
|
7
|
+
import { msteamsRuntimeStub } from "./test-runtime.js";
|
|
21
8
|
|
|
22
9
|
describe("msteams polls", () => {
|
|
23
10
|
beforeEach(() => {
|
|
24
|
-
setMSTeamsRuntime(
|
|
11
|
+
setMSTeamsRuntime(msteamsRuntimeStub);
|
|
25
12
|
});
|
|
26
13
|
|
|
27
14
|
it("builds poll cards with fallback text", () => {
|
package/src/probe.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import type { MSTeamsConfig } from "openclaw/plugin-sdk";
|
|
1
|
+
import type { BaseProbeResult, MSTeamsConfig } from "openclaw/plugin-sdk";
|
|
2
2
|
import { formatUnknownError } from "./errors.js";
|
|
3
3
|
import { loadMSTeamsSdkWithAuth } from "./sdk.js";
|
|
4
4
|
import { resolveMSTeamsCredentials } from "./token.js";
|
|
5
5
|
|
|
6
|
-
export type ProbeMSTeamsResult = {
|
|
7
|
-
ok: boolean;
|
|
8
|
-
error?: string;
|
|
6
|
+
export type ProbeMSTeamsResult = BaseProbeResult<string> & {
|
|
9
7
|
appId?: string;
|
|
10
8
|
graph?: {
|
|
11
9
|
ok: boolean;
|
package/src/reply-dispatcher.ts
CHANGED
|
@@ -9,8 +9,6 @@ import {
|
|
|
9
9
|
} from "openclaw/plugin-sdk";
|
|
10
10
|
import type { MSTeamsAccessTokenProvider } from "./attachments/types.js";
|
|
11
11
|
import type { StoredConversationReference } from "./conversation-store.js";
|
|
12
|
-
import type { MSTeamsMonitorLogger } from "./monitor-types.js";
|
|
13
|
-
import type { MSTeamsTurnContext } from "./sdk-types.js";
|
|
14
12
|
import {
|
|
15
13
|
classifyMSTeamsSendError,
|
|
16
14
|
formatMSTeamsSendErrorHint,
|
|
@@ -21,7 +19,9 @@ import {
|
|
|
21
19
|
renderReplyPayloadsToMessages,
|
|
22
20
|
sendMSTeamsMessages,
|
|
23
21
|
} from "./messenger.js";
|
|
22
|
+
import type { MSTeamsMonitorLogger } from "./monitor-types.js";
|
|
24
23
|
import { getMSTeamsRuntime } from "./runtime.js";
|
|
24
|
+
import type { MSTeamsTurnContext } from "./sdk-types.js";
|
|
25
25
|
|
|
26
26
|
export function createMSTeamsReplyDispatcher(params: {
|
|
27
27
|
cfg: OpenClawConfig;
|
package/src/send-context.ts
CHANGED
|
@@ -4,12 +4,12 @@ import {
|
|
|
4
4
|
type PluginRuntime,
|
|
5
5
|
} from "openclaw/plugin-sdk";
|
|
6
6
|
import type { MSTeamsAccessTokenProvider } from "./attachments/types.js";
|
|
7
|
+
import { createMSTeamsConversationStoreFs } from "./conversation-store-fs.js";
|
|
7
8
|
import type {
|
|
8
9
|
MSTeamsConversationStore,
|
|
9
10
|
StoredConversationReference,
|
|
10
11
|
} from "./conversation-store.js";
|
|
11
12
|
import type { MSTeamsAdapter } from "./messenger.js";
|
|
12
|
-
import { createMSTeamsConversationStoreFs } from "./conversation-store-fs.js";
|
|
13
13
|
import { getMSTeamsRuntime } from "./runtime.js";
|
|
14
14
|
import { createMSTeamsAdapter, loadMSTeamsSdkWithAuth } from "./sdk.js";
|
|
15
15
|
import { resolveMSTeamsCredentials } from "./token.js";
|
package/src/send.ts
CHANGED
|
@@ -374,6 +374,45 @@ async function sendTextWithMedia(
|
|
|
374
374
|
};
|
|
375
375
|
}
|
|
376
376
|
|
|
377
|
+
type ProactiveActivityParams = {
|
|
378
|
+
adapter: MSTeamsProactiveContext["adapter"];
|
|
379
|
+
appId: string;
|
|
380
|
+
ref: MSTeamsProactiveContext["ref"];
|
|
381
|
+
activity: Record<string, unknown>;
|
|
382
|
+
errorPrefix: string;
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
async function sendProactiveActivity({
|
|
386
|
+
adapter,
|
|
387
|
+
appId,
|
|
388
|
+
ref,
|
|
389
|
+
activity,
|
|
390
|
+
errorPrefix,
|
|
391
|
+
}: ProactiveActivityParams): Promise<string> {
|
|
392
|
+
const baseRef = buildConversationReference(ref);
|
|
393
|
+
const proactiveRef = {
|
|
394
|
+
...baseRef,
|
|
395
|
+
activityId: undefined,
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
let messageId = "unknown";
|
|
399
|
+
try {
|
|
400
|
+
await adapter.continueConversation(appId, proactiveRef, async (ctx) => {
|
|
401
|
+
const response = await ctx.sendActivity(activity);
|
|
402
|
+
messageId = extractMessageId(response) ?? "unknown";
|
|
403
|
+
});
|
|
404
|
+
return messageId;
|
|
405
|
+
} catch (err) {
|
|
406
|
+
const classification = classifyMSTeamsSendError(err);
|
|
407
|
+
const hint = formatMSTeamsSendErrorHint(classification);
|
|
408
|
+
const status = classification.statusCode ? ` (HTTP ${classification.statusCode})` : "";
|
|
409
|
+
throw new Error(
|
|
410
|
+
`${errorPrefix} failed${status}: ${formatUnknownError(err)}${hint ? ` (${hint})` : ""}`,
|
|
411
|
+
{ cause: err },
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
377
416
|
/**
|
|
378
417
|
* Send a poll (Adaptive Card) to a Teams conversation or user.
|
|
379
418
|
*/
|
|
@@ -409,27 +448,13 @@ export async function sendPollMSTeams(
|
|
|
409
448
|
};
|
|
410
449
|
|
|
411
450
|
// Send poll via proactive conversation (Adaptive Cards require direct activity send)
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
try {
|
|
420
|
-
await adapter.continueConversation(appId, proactiveRef, async (ctx) => {
|
|
421
|
-
const response = await ctx.sendActivity(activity);
|
|
422
|
-
messageId = extractMessageId(response) ?? "unknown";
|
|
423
|
-
});
|
|
424
|
-
} catch (err) {
|
|
425
|
-
const classification = classifyMSTeamsSendError(err);
|
|
426
|
-
const hint = formatMSTeamsSendErrorHint(classification);
|
|
427
|
-
const status = classification.statusCode ? ` (HTTP ${classification.statusCode})` : "";
|
|
428
|
-
throw new Error(
|
|
429
|
-
`msteams poll send failed${status}: ${formatUnknownError(err)}${hint ? ` (${hint})` : ""}`,
|
|
430
|
-
{ cause: err },
|
|
431
|
-
);
|
|
432
|
-
}
|
|
451
|
+
const messageId = await sendProactiveActivity({
|
|
452
|
+
adapter,
|
|
453
|
+
appId,
|
|
454
|
+
ref,
|
|
455
|
+
activity,
|
|
456
|
+
errorPrefix: "msteams poll send",
|
|
457
|
+
});
|
|
433
458
|
|
|
434
459
|
log.info("sent poll", { conversationId, pollId: pollCard.pollId, messageId });
|
|
435
460
|
|
|
@@ -469,27 +494,13 @@ export async function sendAdaptiveCardMSTeams(
|
|
|
469
494
|
};
|
|
470
495
|
|
|
471
496
|
// Send card via proactive conversation
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
try {
|
|
480
|
-
await adapter.continueConversation(appId, proactiveRef, async (ctx) => {
|
|
481
|
-
const response = await ctx.sendActivity(activity);
|
|
482
|
-
messageId = extractMessageId(response) ?? "unknown";
|
|
483
|
-
});
|
|
484
|
-
} catch (err) {
|
|
485
|
-
const classification = classifyMSTeamsSendError(err);
|
|
486
|
-
const hint = formatMSTeamsSendErrorHint(classification);
|
|
487
|
-
const status = classification.statusCode ? ` (HTTP ${classification.statusCode})` : "";
|
|
488
|
-
throw new Error(
|
|
489
|
-
`msteams card send failed${status}: ${formatUnknownError(err)}${hint ? ` (${hint})` : ""}`,
|
|
490
|
-
{ cause: err },
|
|
491
|
-
);
|
|
492
|
-
}
|
|
497
|
+
const messageId = await sendProactiveActivity({
|
|
498
|
+
adapter,
|
|
499
|
+
appId,
|
|
500
|
+
ref,
|
|
501
|
+
activity,
|
|
502
|
+
errorPrefix: "msteams card send",
|
|
503
|
+
});
|
|
493
504
|
|
|
494
505
|
log.info("sent adaptive card", { conversationId, messageId });
|
|
495
506
|
|
package/src/store-fs.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import crypto from "node:crypto";
|
|
2
1
|
import fs from "node:fs";
|
|
3
|
-
import
|
|
4
|
-
import { safeParseJson } from "openclaw/plugin-sdk";
|
|
2
|
+
import { readJsonFileWithFallback, writeJsonFileAtomically } from "openclaw/plugin-sdk";
|
|
5
3
|
import { withFileLock as withPathLock } from "./file-lock.js";
|
|
6
4
|
|
|
7
5
|
const STORE_LOCK_OPTIONS = {
|
|
@@ -19,31 +17,11 @@ export async function readJsonFile<T>(
|
|
|
19
17
|
filePath: string,
|
|
20
18
|
fallback: T,
|
|
21
19
|
): Promise<{ value: T; exists: boolean }> {
|
|
22
|
-
|
|
23
|
-
const raw = await fs.promises.readFile(filePath, "utf-8");
|
|
24
|
-
const parsed = safeParseJson<T>(raw);
|
|
25
|
-
if (parsed == null) {
|
|
26
|
-
return { value: fallback, exists: true };
|
|
27
|
-
}
|
|
28
|
-
return { value: parsed, exists: true };
|
|
29
|
-
} catch (err) {
|
|
30
|
-
const code = (err as { code?: string }).code;
|
|
31
|
-
if (code === "ENOENT") {
|
|
32
|
-
return { value: fallback, exists: false };
|
|
33
|
-
}
|
|
34
|
-
return { value: fallback, exists: false };
|
|
35
|
-
}
|
|
20
|
+
return await readJsonFileWithFallback(filePath, fallback);
|
|
36
21
|
}
|
|
37
22
|
|
|
38
23
|
export async function writeJsonFile(filePath: string, value: unknown): Promise<void> {
|
|
39
|
-
|
|
40
|
-
await fs.promises.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
41
|
-
const tmp = path.join(dir, `${path.basename(filePath)}.${crypto.randomUUID()}.tmp`);
|
|
42
|
-
await fs.promises.writeFile(tmp, `${JSON.stringify(value, null, 2)}\n`, {
|
|
43
|
-
encoding: "utf-8",
|
|
44
|
-
});
|
|
45
|
-
await fs.promises.chmod(tmp, 0o600);
|
|
46
|
-
await fs.promises.rename(tmp, filePath);
|
|
24
|
+
await writeJsonFileAtomically(filePath, value);
|
|
47
25
|
}
|
|
48
26
|
|
|
49
27
|
async function ensureJsonFile(filePath: string, fallback: unknown) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
|
4
|
+
|
|
5
|
+
export const msteamsRuntimeStub = {
|
|
6
|
+
state: {
|
|
7
|
+
resolveStateDir: (env: NodeJS.ProcessEnv = process.env, homedir?: () => string) => {
|
|
8
|
+
const override = env.OPENCLAW_STATE_DIR?.trim() || env.OPENCLAW_STATE_DIR?.trim();
|
|
9
|
+
if (override) {
|
|
10
|
+
return override;
|
|
11
|
+
}
|
|
12
|
+
const resolvedHome = homedir ? homedir() : os.homedir();
|
|
13
|
+
return path.join(resolvedHome, ".openclaw");
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
} as unknown as PluginRuntime;
|