@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.
Files changed (175) hide show
  1. package/api.ts +3 -0
  2. package/channel-config-api.ts +1 -0
  3. package/channel-plugin-api.ts +2 -0
  4. package/config-api.ts +4 -0
  5. package/contract-api.ts +4 -0
  6. package/index.ts +15 -12
  7. package/openclaw.plugin.json +553 -1
  8. package/package.json +46 -12
  9. package/runtime-api.ts +73 -0
  10. package/secret-contract-api.ts +5 -0
  11. package/setup-entry.ts +13 -0
  12. package/setup-plugin-api.ts +3 -0
  13. package/src/ai-entity.ts +7 -0
  14. package/src/approval-auth.ts +44 -0
  15. package/src/attachments/bot-framework.test.ts +461 -0
  16. package/src/attachments/bot-framework.ts +362 -0
  17. package/src/attachments/download.ts +63 -19
  18. package/src/attachments/graph.test.ts +416 -0
  19. package/src/attachments/graph.ts +163 -72
  20. package/src/attachments/html.ts +33 -1
  21. package/src/attachments/payload.ts +1 -1
  22. package/src/attachments/remote-media.test.ts +137 -0
  23. package/src/attachments/remote-media.ts +75 -8
  24. package/src/attachments/shared.test.ts +138 -1
  25. package/src/attachments/shared.ts +193 -26
  26. package/src/attachments/types.ts +10 -0
  27. package/src/attachments.graph.test.ts +342 -0
  28. package/src/attachments.helpers.test.ts +246 -0
  29. package/src/attachments.test-helpers.ts +17 -0
  30. package/src/attachments.test.ts +163 -418
  31. package/src/attachments.ts +5 -5
  32. package/src/block-streaming-config.test.ts +61 -0
  33. package/src/channel-api.ts +1 -0
  34. package/src/channel.actions.test.ts +742 -0
  35. package/src/channel.directory.test.ts +145 -4
  36. package/src/channel.runtime.ts +56 -0
  37. package/src/channel.setup.ts +77 -0
  38. package/src/channel.test.ts +128 -0
  39. package/src/channel.ts +1077 -395
  40. package/src/config-schema.ts +6 -0
  41. package/src/config-ui-hints.ts +12 -0
  42. package/src/conversation-store-fs.test.ts +4 -5
  43. package/src/conversation-store-fs.ts +35 -51
  44. package/src/conversation-store-helpers.test.ts +202 -0
  45. package/src/conversation-store-helpers.ts +105 -0
  46. package/src/conversation-store-memory.ts +27 -23
  47. package/src/conversation-store.shared.test.ts +225 -0
  48. package/src/conversation-store.ts +30 -0
  49. package/src/directory-live.test.ts +156 -0
  50. package/src/directory-live.ts +7 -4
  51. package/src/doctor.ts +27 -0
  52. package/src/errors.test.ts +64 -1
  53. package/src/errors.ts +50 -9
  54. package/src/feedback-reflection-prompt.ts +117 -0
  55. package/src/feedback-reflection-store.ts +114 -0
  56. package/src/feedback-reflection.test.ts +237 -0
  57. package/src/feedback-reflection.ts +283 -0
  58. package/src/file-consent-helpers.test.ts +83 -0
  59. package/src/file-consent-helpers.ts +64 -11
  60. package/src/file-consent-invoke.ts +150 -0
  61. package/src/file-consent.test.ts +363 -0
  62. package/src/file-consent.ts +165 -4
  63. package/src/graph-chat.ts +5 -3
  64. package/src/graph-group-management.test.ts +318 -0
  65. package/src/graph-group-management.ts +168 -0
  66. package/src/graph-members.test.ts +89 -0
  67. package/src/graph-members.ts +48 -0
  68. package/src/graph-messages.actions.test.ts +243 -0
  69. package/src/graph-messages.read.test.ts +391 -0
  70. package/src/graph-messages.search.test.ts +213 -0
  71. package/src/graph-messages.test-helpers.ts +50 -0
  72. package/src/graph-messages.ts +534 -0
  73. package/src/graph-teams.test.ts +215 -0
  74. package/src/graph-teams.ts +114 -0
  75. package/src/graph-thread.test.ts +246 -0
  76. package/src/graph-thread.ts +146 -0
  77. package/src/graph-upload.test.ts +161 -4
  78. package/src/graph-upload.ts +147 -56
  79. package/src/graph.test.ts +516 -0
  80. package/src/graph.ts +233 -21
  81. package/src/inbound.test.ts +156 -1
  82. package/src/inbound.ts +101 -1
  83. package/src/media-helpers.ts +1 -1
  84. package/src/mentions.test.ts +27 -18
  85. package/src/mentions.ts +2 -2
  86. package/src/messenger.test.ts +504 -23
  87. package/src/messenger.ts +133 -52
  88. package/src/monitor-handler/access.ts +125 -0
  89. package/src/monitor-handler/inbound-media.test.ts +289 -0
  90. package/src/monitor-handler/inbound-media.ts +57 -5
  91. package/src/monitor-handler/message-handler-mock-support.test-support.ts +28 -0
  92. package/src/monitor-handler/message-handler.authz.test.ts +588 -74
  93. package/src/monitor-handler/message-handler.dm-media.test.ts +54 -0
  94. package/src/monitor-handler/message-handler.test-support.ts +100 -0
  95. package/src/monitor-handler/message-handler.thread-parent.test.ts +223 -0
  96. package/src/monitor-handler/message-handler.thread-session.test.ts +77 -0
  97. package/src/monitor-handler/message-handler.ts +470 -164
  98. package/src/monitor-handler/reaction-handler.test.ts +267 -0
  99. package/src/monitor-handler/reaction-handler.ts +210 -0
  100. package/src/monitor-handler/thread-session.ts +17 -0
  101. package/src/monitor-handler.adaptive-card.test.ts +162 -0
  102. package/src/monitor-handler.feedback-authz.test.ts +314 -0
  103. package/src/monitor-handler.file-consent.test.ts +281 -79
  104. package/src/monitor-handler.sso.test.ts +563 -0
  105. package/src/monitor-handler.test-helpers.ts +180 -0
  106. package/src/monitor-handler.ts +459 -115
  107. package/src/monitor-handler.types.ts +27 -0
  108. package/src/monitor-types.ts +1 -0
  109. package/src/monitor.lifecycle.test.ts +74 -10
  110. package/src/monitor.test.ts +35 -1
  111. package/src/monitor.ts +143 -46
  112. package/src/oauth.flow.ts +77 -0
  113. package/src/oauth.shared.ts +37 -0
  114. package/src/oauth.test.ts +305 -0
  115. package/src/oauth.token.ts +158 -0
  116. package/src/oauth.ts +130 -0
  117. package/src/outbound.test.ts +10 -11
  118. package/src/outbound.ts +62 -44
  119. package/src/pending-uploads-fs.test.ts +246 -0
  120. package/src/pending-uploads-fs.ts +235 -0
  121. package/src/pending-uploads.test.ts +173 -0
  122. package/src/pending-uploads.ts +34 -2
  123. package/src/policy.test.ts +11 -5
  124. package/src/policy.ts +5 -5
  125. package/src/polls.test.ts +106 -5
  126. package/src/polls.ts +15 -7
  127. package/src/presentation.ts +68 -0
  128. package/src/probe.test.ts +27 -8
  129. package/src/probe.ts +43 -9
  130. package/src/reply-dispatcher.test.ts +437 -0
  131. package/src/reply-dispatcher.ts +259 -73
  132. package/src/reply-stream-controller.test.ts +235 -0
  133. package/src/reply-stream-controller.ts +147 -0
  134. package/src/resolve-allowlist.test.ts +105 -1
  135. package/src/resolve-allowlist.ts +112 -7
  136. package/src/runtime.ts +6 -3
  137. package/src/sdk-types.ts +43 -3
  138. package/src/sdk.test.ts +666 -0
  139. package/src/sdk.ts +867 -16
  140. package/src/secret-contract.ts +49 -0
  141. package/src/secret-input.ts +1 -1
  142. package/src/send-context.ts +76 -9
  143. package/src/send.test.ts +389 -5
  144. package/src/send.ts +140 -32
  145. package/src/sent-message-cache.ts +30 -18
  146. package/src/session-route.ts +40 -0
  147. package/src/setup-core.ts +160 -0
  148. package/src/setup-surface.test.ts +202 -0
  149. package/src/setup-surface.ts +320 -0
  150. package/src/sso-token-store.test.ts +72 -0
  151. package/src/sso-token-store.ts +166 -0
  152. package/src/sso.ts +300 -0
  153. package/src/storage.ts +1 -1
  154. package/src/store-fs.ts +2 -2
  155. package/src/streaming-message.test.ts +262 -0
  156. package/src/streaming-message.ts +297 -0
  157. package/src/test-runtime.ts +1 -1
  158. package/src/thread-parent-context.test.ts +224 -0
  159. package/src/thread-parent-context.ts +159 -0
  160. package/src/token.test.ts +237 -50
  161. package/src/token.ts +162 -7
  162. package/src/user-agent.test.ts +86 -0
  163. package/src/user-agent.ts +53 -0
  164. package/src/webhook-timeouts.ts +27 -0
  165. package/src/welcome-card.test.ts +81 -0
  166. package/src/welcome-card.ts +57 -0
  167. package/test-api.ts +1 -0
  168. package/tsconfig.json +16 -0
  169. package/CHANGELOG.md +0 -107
  170. package/src/file-lock.ts +0 -1
  171. package/src/graph-users.test.ts +0 -66
  172. package/src/onboarding.ts +0 -381
  173. package/src/polls-store.test.ts +0 -38
  174. package/src/revoked-context.test.ts +0 -39
  175. package/src/token-response.test.ts +0 -23
package/src/token.ts CHANGED
@@ -1,17 +1,75 @@
1
- import type { MSTeamsConfig } from "openclaw/plugin-sdk/msteams";
1
+ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import type { MSTeamsConfig } from "../runtime-api.js";
4
+ import type { MSTeamsDelegatedTokens } from "./oauth.shared.js";
5
+ import { refreshMSTeamsDelegatedTokens } from "./oauth.token.js";
2
6
  import {
3
7
  hasConfiguredSecretInput,
4
8
  normalizeResolvedSecretInputString,
5
9
  normalizeSecretInputString,
6
10
  } from "./secret-input.js";
11
+ import { resolveMSTeamsStorePath } from "./storage.js";
7
12
 
8
- export type MSTeamsCredentials = {
13
+ // ── Credential types ───────────────────────────────────────────────────────
14
+
15
+ export type MSTeamsSecretCredentials = {
16
+ type: "secret";
9
17
  appId: string;
10
18
  appPassword: string;
11
19
  tenantId: string;
12
20
  };
13
21
 
22
+ export type MSTeamsFederatedCredentials = {
23
+ type: "federated";
24
+ appId: string;
25
+ tenantId: string;
26
+ certificatePath?: string;
27
+ certificateThumbprint?: string;
28
+ useManagedIdentity?: boolean;
29
+ managedIdentityClientId?: string;
30
+ };
31
+
32
+ export type MSTeamsCredentials = MSTeamsSecretCredentials | MSTeamsFederatedCredentials;
33
+
34
+ // ── Helpers ────────────────────────────────────────────────────────────────
35
+
36
+ function resolveAuthType(cfg?: MSTeamsConfig): "secret" | "federated" {
37
+ const fromCfg = cfg?.authType;
38
+ if (fromCfg === "secret" || fromCfg === "federated") {
39
+ return fromCfg;
40
+ }
41
+
42
+ const fromEnv = process.env.MSTEAMS_AUTH_TYPE;
43
+ if (fromEnv === "federated") {
44
+ return "federated";
45
+ }
46
+
47
+ return "secret";
48
+ }
49
+
50
+ // ── hasConfiguredMSTeamsCredentials ────────────────────────────────────────
51
+
14
52
  export function hasConfiguredMSTeamsCredentials(cfg?: MSTeamsConfig): boolean {
53
+ const authType = resolveAuthType(cfg);
54
+
55
+ const hasAppId = Boolean(
56
+ normalizeSecretInputString(cfg?.appId) ||
57
+ normalizeSecretInputString(process.env.MSTEAMS_APP_ID),
58
+ );
59
+ const hasTenantId = Boolean(
60
+ normalizeSecretInputString(cfg?.tenantId) ||
61
+ normalizeSecretInputString(process.env.MSTEAMS_TENANT_ID),
62
+ );
63
+
64
+ if (authType === "federated") {
65
+ const hasCert = Boolean(cfg?.certificatePath || process.env.MSTEAMS_CERTIFICATE_PATH);
66
+ const hasManagedIdentity =
67
+ cfg?.useManagedIdentity ?? process.env.MSTEAMS_USE_MANAGED_IDENTITY === "true";
68
+
69
+ return hasAppId && hasTenantId && (hasCert || hasManagedIdentity);
70
+ }
71
+
72
+ // "secret" (default) — original logic
15
73
  return Boolean(
16
74
  normalizeSecretInputString(cfg?.appId) &&
17
75
  hasConfiguredSecretInput(cfg?.appPassword) &&
@@ -19,22 +77,119 @@ export function hasConfiguredMSTeamsCredentials(cfg?: MSTeamsConfig): boolean {
19
77
  );
20
78
  }
21
79
 
80
+ // ── resolveMSTeamsCredentials ─────────────────────────────────────────────
81
+
22
82
  export function resolveMSTeamsCredentials(cfg?: MSTeamsConfig): MSTeamsCredentials | undefined {
83
+ const authType = resolveAuthType(cfg);
84
+
23
85
  const appId =
24
86
  normalizeSecretInputString(cfg?.appId) ||
25
87
  normalizeSecretInputString(process.env.MSTEAMS_APP_ID);
88
+
89
+ const tenantId =
90
+ normalizeSecretInputString(cfg?.tenantId) ||
91
+ normalizeSecretInputString(process.env.MSTEAMS_TENANT_ID);
92
+
93
+ if (!appId || !tenantId) {
94
+ return undefined;
95
+ }
96
+
97
+ if (authType === "federated") {
98
+ const certificatePath =
99
+ cfg?.certificatePath || process.env.MSTEAMS_CERTIFICATE_PATH || undefined;
100
+
101
+ const certificateThumbprint =
102
+ cfg?.certificateThumbprint || process.env.MSTEAMS_CERTIFICATE_THUMBPRINT || undefined;
103
+
104
+ const useManagedIdentity =
105
+ cfg?.useManagedIdentity ?? process.env.MSTEAMS_USE_MANAGED_IDENTITY === "true";
106
+
107
+ const managedIdentityClientId =
108
+ cfg?.managedIdentityClientId || process.env.MSTEAMS_MANAGED_IDENTITY_CLIENT_ID || undefined;
109
+
110
+ // At least one federated mechanism must be configured.
111
+ if (!certificatePath && !useManagedIdentity) {
112
+ return undefined;
113
+ }
114
+
115
+ return {
116
+ type: "federated",
117
+ appId,
118
+ tenantId,
119
+ certificatePath,
120
+ certificateThumbprint,
121
+ useManagedIdentity: useManagedIdentity || undefined,
122
+ managedIdentityClientId,
123
+ };
124
+ }
125
+
126
+ // "secret" (default) — original logic
26
127
  const appPassword =
27
128
  normalizeResolvedSecretInputString({
28
129
  value: cfg?.appPassword,
29
130
  path: "channels.msteams.appPassword",
30
131
  }) || normalizeSecretInputString(process.env.MSTEAMS_APP_PASSWORD);
31
- const tenantId =
32
- normalizeSecretInputString(cfg?.tenantId) ||
33
- normalizeSecretInputString(process.env.MSTEAMS_TENANT_ID);
34
132
 
35
- if (!appId || !appPassword || !tenantId) {
133
+ if (!appPassword) {
36
134
  return undefined;
37
135
  }
38
136
 
39
- return { appId, appPassword, tenantId };
137
+ return { type: "secret", appId, appPassword, tenantId };
138
+ }
139
+
140
+ // ---------------------------------------------------------------------------
141
+ // Delegated token storage / resolution
142
+ // ---------------------------------------------------------------------------
143
+
144
+ const DELEGATED_TOKEN_FILENAME = "msteams-delegated.json";
145
+
146
+ function resolveDelegatedTokenPath(): string {
147
+ return resolveMSTeamsStorePath({ filename: DELEGATED_TOKEN_FILENAME });
148
+ }
149
+
150
+ export function loadDelegatedTokens(): MSTeamsDelegatedTokens | undefined {
151
+ try {
152
+ const content = readFileSync(resolveDelegatedTokenPath(), "utf8");
153
+ return JSON.parse(content) as MSTeamsDelegatedTokens;
154
+ } catch {
155
+ return undefined;
156
+ }
157
+ }
158
+
159
+ export function saveDelegatedTokens(tokens: MSTeamsDelegatedTokens): void {
160
+ const tokenPath = resolveDelegatedTokenPath();
161
+ const dir = dirname(tokenPath);
162
+ mkdirSync(dir, { recursive: true });
163
+ writeFileSync(tokenPath, JSON.stringify(tokens, null, 2), "utf8");
164
+ }
165
+
166
+ export async function resolveDelegatedAccessToken(params: {
167
+ tenantId: string;
168
+ clientId: string;
169
+ clientSecret: string;
170
+ }): Promise<string | undefined> {
171
+ const tokens = loadDelegatedTokens();
172
+ if (!tokens) {
173
+ return undefined;
174
+ }
175
+
176
+ // Token still valid (5-min buffer already baked into expiresAt)
177
+ if (tokens.expiresAt > Date.now()) {
178
+ return tokens.accessToken;
179
+ }
180
+
181
+ // Attempt refresh
182
+ try {
183
+ const refreshed = await refreshMSTeamsDelegatedTokens({
184
+ tenantId: params.tenantId,
185
+ clientId: params.clientId,
186
+ clientSecret: params.clientSecret,
187
+ refreshToken: tokens.refreshToken,
188
+ scopes: tokens.scopes,
189
+ });
190
+ saveDelegatedTokens(refreshed);
191
+ return refreshed.accessToken;
192
+ } catch {
193
+ return undefined;
194
+ }
40
195
  }
@@ -0,0 +1,86 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+
3
+ // Mock the runtime before importing buildUserAgent
4
+ const mockRuntime = {
5
+ version: "2026.3.19",
6
+ };
7
+
8
+ vi.mock("./runtime.js", () => ({
9
+ getMSTeamsRuntime: vi.fn(() => mockRuntime),
10
+ }));
11
+
12
+ import { fetchGraphJson } from "./graph.js";
13
+ import { getMSTeamsRuntime } from "./runtime.js";
14
+ import { buildUserAgent, ensureUserAgentHeader, resetUserAgentCache } from "./user-agent.js";
15
+
16
+ describe("buildUserAgent", () => {
17
+ beforeEach(() => {
18
+ resetUserAgentCache();
19
+ vi.mocked(getMSTeamsRuntime).mockReturnValue(mockRuntime as never);
20
+ });
21
+
22
+ afterEach(() => {
23
+ vi.restoreAllMocks();
24
+ });
25
+
26
+ it("returns teams.ts[apps]/<sdk> OpenClaw/<version> format", () => {
27
+ const ua = buildUserAgent();
28
+ expect(ua).toMatch(/^teams\.ts\[apps\]\/.+ OpenClaw\/2026\.3\.19$/);
29
+ });
30
+
31
+ it("reflects the runtime version", () => {
32
+ vi.mocked(getMSTeamsRuntime).mockReturnValue({ version: "1.2.3" } as never);
33
+ const ua = buildUserAgent();
34
+ expect(ua).toMatch(/OpenClaw\/1\.2\.3$/);
35
+ });
36
+
37
+ it("returns OpenClaw/unknown when runtime is not initialized", () => {
38
+ vi.mocked(getMSTeamsRuntime).mockImplementation(() => {
39
+ throw new Error("MSTeams runtime not initialized");
40
+ });
41
+ const ua = buildUserAgent();
42
+ expect(ua).toMatch(/OpenClaw\/unknown$/);
43
+ // SDK version should still be present
44
+ expect(ua).toMatch(/^teams\.ts\[apps\]\//);
45
+ });
46
+
47
+ it("sends the generated User-Agent in Graph requests by default", async () => {
48
+ const mockFetch = vi.fn().mockResolvedValueOnce({
49
+ ok: true,
50
+ json: async () => ({ value: [] }),
51
+ });
52
+ vi.stubGlobal("fetch", mockFetch);
53
+
54
+ await fetchGraphJson({ token: "test-token", path: "/groups" });
55
+
56
+ expect(mockFetch).toHaveBeenCalledOnce();
57
+ const [, init] = mockFetch.mock.calls[0];
58
+ expect(init.headers["User-Agent"]).toMatch(/^teams\.ts\[apps\]\/.+ OpenClaw\/2026\.3\.19$/);
59
+ expect(init.headers).toHaveProperty("Authorization", "Bearer test-token");
60
+ });
61
+
62
+ it("lets caller headers override the default Graph User-Agent", async () => {
63
+ const mockFetch = vi.fn().mockResolvedValueOnce({
64
+ ok: true,
65
+ json: async () => ({ value: [] }),
66
+ });
67
+ vi.stubGlobal("fetch", mockFetch);
68
+
69
+ await fetchGraphJson({
70
+ token: "test-token",
71
+ path: "/groups",
72
+ headers: { "User-Agent": "custom-agent/1.0" },
73
+ });
74
+
75
+ const [, init] = mockFetch.mock.calls[0];
76
+ expect(init.headers["User-Agent"]).toBe("custom-agent/1.0");
77
+ });
78
+
79
+ it("adds the generated User-Agent to Headers instances without overwriting callers", () => {
80
+ const generated = ensureUserAgentHeader();
81
+ expect(generated.get("User-Agent")).toMatch(/^teams\.ts\[apps\]\/.+ OpenClaw\/2026\.3\.19$/);
82
+
83
+ const custom = ensureUserAgentHeader({ "User-Agent": "custom-agent/2.0" });
84
+ expect(custom.get("User-Agent")).toBe("custom-agent/2.0");
85
+ });
86
+ });
@@ -0,0 +1,53 @@
1
+ import { createRequire } from "node:module";
2
+ import { getMSTeamsRuntime } from "./runtime.js";
3
+
4
+ let cachedUserAgent: string | undefined;
5
+
6
+ function resolveTeamsSdkVersion(): string {
7
+ try {
8
+ const require = createRequire(import.meta.url);
9
+ const pkg = require("@microsoft/teams.apps/package.json") as { version?: string };
10
+ return pkg.version ?? "unknown";
11
+ } catch {
12
+ return "unknown";
13
+ }
14
+ }
15
+
16
+ function resolveOpenClawVersion(): string {
17
+ try {
18
+ return getMSTeamsRuntime().version;
19
+ } catch {
20
+ return "unknown";
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Build a combined User-Agent string that preserves the Teams SDK identity
26
+ * and appends the OpenClaw version.
27
+ *
28
+ * Format: "teams.ts[apps]/<sdk-version> OpenClaw/<openclaw-version>"
29
+ * Example: "teams.ts[apps]/2.0.5 OpenClaw/2026.3.22"
30
+ *
31
+ * This lets the Teams backend track SDK usage while also identifying the
32
+ * host application.
33
+ */
34
+ /** Reset the cached User-Agent (for testing). */
35
+ export function resetUserAgentCache(): void {
36
+ cachedUserAgent = undefined;
37
+ }
38
+
39
+ export function buildUserAgent(): string {
40
+ if (cachedUserAgent) {
41
+ return cachedUserAgent;
42
+ }
43
+ cachedUserAgent = `teams.ts[apps]/${resolveTeamsSdkVersion()} OpenClaw/${resolveOpenClawVersion()}`;
44
+ return cachedUserAgent;
45
+ }
46
+
47
+ export function ensureUserAgentHeader(headers?: HeadersInit): Headers {
48
+ const nextHeaders = new Headers(headers);
49
+ if (!nextHeaders.has("User-Agent")) {
50
+ nextHeaders.set("User-Agent", buildUserAgent());
51
+ }
52
+ return nextHeaders;
53
+ }
@@ -0,0 +1,27 @@
1
+ import type { Server } from "node:http";
2
+
3
+ const MSTEAMS_WEBHOOK_INACTIVITY_TIMEOUT_MS = 30_000;
4
+ const MSTEAMS_WEBHOOK_REQUEST_TIMEOUT_MS = 30_000;
5
+ const MSTEAMS_WEBHOOK_HEADERS_TIMEOUT_MS = 15_000;
6
+
7
+ type ApplyMSTeamsWebhookTimeoutsOpts = {
8
+ inactivityTimeoutMs?: number;
9
+ requestTimeoutMs?: number;
10
+ headersTimeoutMs?: number;
11
+ };
12
+
13
+ export function applyMSTeamsWebhookTimeouts(
14
+ httpServer: Server,
15
+ opts?: ApplyMSTeamsWebhookTimeoutsOpts,
16
+ ): void {
17
+ const inactivityTimeoutMs = opts?.inactivityTimeoutMs ?? MSTEAMS_WEBHOOK_INACTIVITY_TIMEOUT_MS;
18
+ const requestTimeoutMs = opts?.requestTimeoutMs ?? MSTEAMS_WEBHOOK_REQUEST_TIMEOUT_MS;
19
+ const headersTimeoutMs = Math.min(
20
+ opts?.headersTimeoutMs ?? MSTEAMS_WEBHOOK_HEADERS_TIMEOUT_MS,
21
+ requestTimeoutMs,
22
+ );
23
+
24
+ httpServer.setTimeout(inactivityTimeoutMs);
25
+ httpServer.requestTimeout = requestTimeoutMs;
26
+ httpServer.headersTimeout = headersTimeoutMs;
27
+ }
@@ -0,0 +1,81 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { buildMSTeamsPresentationCard } from "./presentation.js";
3
+ import { buildGroupWelcomeText, buildWelcomeCard } from "./welcome-card.js";
4
+
5
+ describe("buildMSTeamsPresentationCard", () => {
6
+ it("preserves message text when rendering presentation controls", () => {
7
+ expect(
8
+ buildMSTeamsPresentationCard({
9
+ text: "Deploy finished",
10
+ presentation: {
11
+ blocks: [
12
+ {
13
+ type: "buttons",
14
+ buttons: [{ label: "Open", value: "open" }],
15
+ },
16
+ ],
17
+ },
18
+ }),
19
+ ).toEqual({
20
+ type: "AdaptiveCard",
21
+ version: "1.4",
22
+ body: [{ type: "TextBlock", text: "Deploy finished", wrap: true }],
23
+ actions: [{ type: "Action.Submit", title: "Open", data: { value: "open", label: "Open" } }],
24
+ });
25
+ });
26
+ });
27
+
28
+ describe("buildWelcomeCard", () => {
29
+ it("builds card with default prompt starters", () => {
30
+ const card = buildWelcomeCard();
31
+ expect(card.type).toBe("AdaptiveCard");
32
+ expect(card.version).toBe("1.5");
33
+
34
+ const body = card.body as Array<{ text: string }>;
35
+ expect(body[0]?.text).toContain("OpenClaw");
36
+
37
+ const actions = card.actions as Array<{ title: string; data: unknown }>;
38
+ expect(actions.length).toBe(3);
39
+ expect(actions[0]?.title).toBe("What can you do?");
40
+ });
41
+
42
+ it("uses custom bot name", () => {
43
+ const card = buildWelcomeCard({ botName: "TestBot" });
44
+ const body = card.body as Array<{ text: string }>;
45
+ expect(body[0]?.text).toContain("TestBot");
46
+ });
47
+
48
+ it("uses custom prompt starters", () => {
49
+ const card = buildWelcomeCard({
50
+ promptStarters: ["Do X", "Do Y"],
51
+ });
52
+ const actions = card.actions as Array<{ title: string; data: unknown }>;
53
+ expect(actions.length).toBe(2);
54
+ expect(actions[0]?.title).toBe("Do X");
55
+ expect(actions[1]?.title).toBe("Do Y");
56
+
57
+ // Verify imBack data
58
+ const data = actions[0]?.data as { msteams: { type: string; value: string } };
59
+ expect(data.msteams.type).toBe("imBack");
60
+ expect(data.msteams.value).toBe("Do X");
61
+ });
62
+
63
+ it("falls back to defaults when promptStarters is empty", () => {
64
+ const card = buildWelcomeCard({ promptStarters: [] });
65
+ const actions = card.actions as Array<{ title: string }>;
66
+ expect(actions.length).toBe(3);
67
+ });
68
+ });
69
+
70
+ describe("buildGroupWelcomeText", () => {
71
+ it("includes bot name", () => {
72
+ const text = buildGroupWelcomeText("MyBot");
73
+ expect(text).toContain("MyBot");
74
+ expect(text).toContain("@MyBot");
75
+ });
76
+
77
+ it("defaults to OpenClaw", () => {
78
+ const text = buildGroupWelcomeText();
79
+ expect(text).toContain("OpenClaw");
80
+ });
81
+ });
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Builds an Adaptive Card for welcoming users when the bot is added to a conversation.
3
+ */
4
+
5
+ const DEFAULT_PROMPT_STARTERS = [
6
+ "What can you do?",
7
+ "Summarize my last meeting",
8
+ "Help me draft an email",
9
+ ];
10
+
11
+ type WelcomeCardOptions = {
12
+ /** Bot display name. Falls back to "OpenClaw". */
13
+ botName?: string;
14
+ /** Custom prompt starters. Falls back to defaults. */
15
+ promptStarters?: string[];
16
+ };
17
+
18
+ /**
19
+ * Build a welcome Adaptive Card for 1:1 personal chats.
20
+ */
21
+ export function buildWelcomeCard(options?: WelcomeCardOptions): Record<string, unknown> {
22
+ const botName = options?.botName || "OpenClaw";
23
+ const starters = options?.promptStarters?.length
24
+ ? options.promptStarters
25
+ : DEFAULT_PROMPT_STARTERS;
26
+
27
+ return {
28
+ type: "AdaptiveCard",
29
+ version: "1.5",
30
+ body: [
31
+ {
32
+ type: "TextBlock",
33
+ text: `Hi! I'm ${botName}.`,
34
+ weight: "bolder",
35
+ size: "medium",
36
+ },
37
+ {
38
+ type: "TextBlock",
39
+ text: "I can help you with questions, tasks, and more. Here are some things to try:",
40
+ wrap: true,
41
+ },
42
+ ],
43
+ actions: starters.map((label) => ({
44
+ type: "Action.Submit",
45
+ title: label,
46
+ data: { msteams: { type: "imBack", value: label } },
47
+ })),
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Build a brief welcome message for group chats (when the bot is @mentioned).
53
+ */
54
+ export function buildGroupWelcomeText(botName?: string): string {
55
+ const name = botName || "OpenClaw";
56
+ return `Hi! I'm ${name}. Mention me with @${name} to get started.`;
57
+ }
package/test-api.ts ADDED
@@ -0,0 +1 @@
1
+ export { msteamsPlugin } from "./src/channel.js";
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "extends": "../tsconfig.package-boundary.base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "."
5
+ },
6
+ "include": ["./*.ts", "./src/**/*.ts"],
7
+ "exclude": [
8
+ "./**/*.test.ts",
9
+ "./dist/**",
10
+ "./node_modules/**",
11
+ "./src/test-support/**",
12
+ "./src/**/*test-helpers.ts",
13
+ "./src/**/*test-harness.ts",
14
+ "./src/**/*test-support.ts"
15
+ ]
16
+ }
package/CHANGELOG.md DELETED
@@ -1,107 +0,0 @@
1
- # Changelog
2
-
3
- ## 2026.3.13
4
-
5
- ### Changes
6
-
7
- - Version alignment with core OpenClaw release numbers.
8
-
9
- ## 2026.3.12
10
-
11
- ### Changes
12
-
13
- - Version alignment with core OpenClaw release numbers.
14
-
15
- ## 2026.3.11
16
-
17
- ### Changes
18
-
19
- - Version alignment with core OpenClaw release numbers.
20
-
21
- ## 2026.3.10
22
-
23
- ### Changes
24
-
25
- - Version alignment with core OpenClaw release numbers.
26
-
27
- ## 2026.3.9
28
-
29
- ### Changes
30
-
31
- - Version alignment with core OpenClaw release numbers.
32
-
33
- ## 2026.3.8-beta.1
34
-
35
- ### Changes
36
-
37
- - Version alignment with core OpenClaw release numbers.
38
-
39
- ## 2026.3.8
40
-
41
- ### Changes
42
-
43
- - Version alignment with core OpenClaw release numbers.
44
-
45
- ## 2026.3.7
46
-
47
- ### Changes
48
-
49
- - Version alignment with core OpenClaw release numbers.
50
-
51
- ## 2026.3.3
52
-
53
- ### Changes
54
-
55
- - Version alignment with core OpenClaw release numbers.
56
-
57
- ## 2026.3.2
58
-
59
- ### Changes
60
-
61
- - Version alignment with core OpenClaw release numbers.
62
-
63
- ## 2026.3.1
64
-
65
- ### Changes
66
-
67
- - Version alignment with core OpenClaw release numbers.
68
-
69
- ## 2026.2.26
70
-
71
- ### Changes
72
-
73
- - Version alignment with core OpenClaw release numbers.
74
-
75
- ## 2026.2.25
76
-
77
- ### Changes
78
-
79
- - Version alignment with core OpenClaw release numbers.
80
-
81
- ## 2026.2.24
82
-
83
- ### Changes
84
-
85
- - Version alignment with core OpenClaw release numbers.
86
-
87
- ## 2026.2.22
88
-
89
- ### Changes
90
-
91
- - Version alignment with core OpenClaw release numbers.
92
-
93
- ## 2026.1.15
94
-
95
- ### Features
96
-
97
- - Bot Framework gateway monitor (Express + JWT auth) with configurable webhook path/port and `/api/messages` fallback.
98
- - Onboarding flow for Azure Bot credentials (config + env var detection) and DM policy setup.
99
- - Channel capabilities: DMs, group chats, channels, threads, media, polls, and `teams` alias.
100
- - DM pairing/allowlist enforcement plus group policies with per-team/channel overrides and mention gating.
101
- - Inbound debounce + history context for room/group chats; mention tag stripping and timestamp parsing.
102
- - Proactive messaging via stored conversation references (file store with TTL/size pruning).
103
- - Outbound text/media send with markdown chunking, 4k limit, split/inline media handling.
104
- - Adaptive Card polls: build cards, parse votes, and persist poll state with vote tracking.
105
- - Attachment processing: placeholders + HTML summaries, inline image extraction (including data: URLs).
106
- - Media downloads with host allowlist, auth scope fallback, and Graph hostedContents/attachments fallback.
107
- - Retry/backoff on transient/throttled sends with classified errors + helpful hints.
package/src/file-lock.ts DELETED
@@ -1 +0,0 @@
1
- export { withFileLock } from "openclaw/plugin-sdk/msteams";