@skaile/workspaces 1.14.1 → 1.15.0

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 (190) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/NOTICE +13 -0
  3. package/dist/app-sdk/index.js +1 -1
  4. package/dist/{asset-feeds-ZQLJ2DCP.js → asset-feeds-MWEMXMZA.js} +5 -5
  5. package/dist/{asset-feeds-ZQLJ2DCP.js.map → asset-feeds-MWEMXMZA.js.map} +1 -1
  6. package/dist/asset-manager/index.js +5 -5
  7. package/dist/asset-manager/installer.js +1 -1
  8. package/dist/bridge/drivers/claude-sdk.js +1 -1
  9. package/dist/bridge/drivers/codex.js +1 -1
  10. package/dist/bridge/drivers/continue.js +2 -2
  11. package/dist/bridge/drivers/echo.js +1 -1
  12. package/dist/bridge/drivers/gemini.js +3 -3
  13. package/dist/bridge/drivers/goose.js +2 -2
  14. package/dist/bridge/drivers/omp.js +1 -1
  15. package/dist/bridge/drivers/opencode.js +2 -2
  16. package/dist/bridge/drivers/qwen.js +3 -3
  17. package/dist/bridge/index.js +2 -2
  18. package/dist/channels/adapters/msteams/activity.d.ts +70 -0
  19. package/dist/channels/adapters/msteams/activity.d.ts.map +1 -0
  20. package/dist/channels/adapters/msteams/client.d.ts +46 -0
  21. package/dist/channels/adapters/msteams/client.d.ts.map +1 -0
  22. package/dist/channels/adapters/msteams/index.d.ts +81 -0
  23. package/dist/channels/adapters/msteams/index.d.ts.map +1 -0
  24. package/dist/channels/adapters/msteams/jwt.d.ts +27 -0
  25. package/dist/channels/adapters/msteams/jwt.d.ts.map +1 -0
  26. package/dist/channels/adapters/msteams.d.ts +2 -0
  27. package/dist/channels/adapters/msteams.js +595 -0
  28. package/dist/channels/adapters/msteams.js.map +1 -0
  29. package/dist/channels/index.d.ts +2 -0
  30. package/dist/channels/index.js +1069 -0
  31. package/dist/channels/index.js.map +1 -0
  32. package/dist/channels/src/adapter.d.ts +54 -0
  33. package/dist/channels/src/adapter.d.ts.map +1 -0
  34. package/dist/channels/src/dedupe.d.ts +67 -0
  35. package/dist/channels/src/dedupe.d.ts.map +1 -0
  36. package/dist/channels/src/egress/output-mirror.d.ts +63 -0
  37. package/dist/channels/src/egress/output-mirror.d.ts.map +1 -0
  38. package/dist/channels/src/egress/vendor/auto-close-streaming.d.ts +26 -0
  39. package/dist/channels/src/egress/vendor/auto-close-streaming.d.ts.map +1 -0
  40. package/dist/channels/src/egress/vendor/chunked-message-stream.d.ts +79 -0
  41. package/dist/channels/src/egress/vendor/chunked-message-stream.d.ts.map +1 -0
  42. package/dist/channels/src/egress/vendor/markdown-to-mrkdwn.d.ts +21 -0
  43. package/dist/channels/src/egress/vendor/markdown-to-mrkdwn.d.ts.map +1 -0
  44. package/dist/channels/src/egress/vendor/message-stream.d.ts +57 -0
  45. package/dist/channels/src/egress/vendor/message-stream.d.ts.map +1 -0
  46. package/dist/channels/src/engine.d.ts +125 -0
  47. package/dist/channels/src/engine.d.ts.map +1 -0
  48. package/dist/channels/src/format/adaptive-card.d.ts +17 -0
  49. package/dist/channels/src/format/adaptive-card.d.ts.map +1 -0
  50. package/dist/channels/src/format/mrkdwn.d.ts +4 -0
  51. package/dist/channels/src/format/mrkdwn.d.ts.map +1 -0
  52. package/dist/channels/src/format/types.d.ts +9 -0
  53. package/dist/channels/src/format/types.d.ts.map +1 -0
  54. package/dist/channels/src/index.d.ts +17 -0
  55. package/dist/channels/src/index.d.ts.map +1 -0
  56. package/dist/channels/src/mention/lookup-tool.d.ts +23 -0
  57. package/dist/channels/src/mention/lookup-tool.d.ts.map +1 -0
  58. package/dist/channels/src/mention/normalize.d.ts +27 -0
  59. package/dist/channels/src/mention/normalize.d.ts.map +1 -0
  60. package/dist/channels/src/mention/render.d.ts +10 -0
  61. package/dist/channels/src/mention/render.d.ts.map +1 -0
  62. package/dist/channels/src/ports.d.ts +71 -0
  63. package/dist/channels/src/ports.d.ts.map +1 -0
  64. package/dist/channels/src/types.d.ts +169 -0
  65. package/dist/channels/src/types.d.ts.map +1 -0
  66. package/dist/{chunk-A7GDV5BV.js → chunk-2BQBWWFX.js} +2 -2
  67. package/dist/{chunk-A7GDV5BV.js.map → chunk-2BQBWWFX.js.map} +1 -1
  68. package/dist/{chunk-MWE7DEGC.js → chunk-344DEXZS.js} +3 -3
  69. package/dist/{chunk-MWE7DEGC.js.map → chunk-344DEXZS.js.map} +1 -1
  70. package/dist/{chunk-CC6LFFTI.js → chunk-6ZLMG5GR.js} +4 -4
  71. package/dist/{chunk-CC6LFFTI.js.map → chunk-6ZLMG5GR.js.map} +1 -1
  72. package/dist/{chunk-T2H2ZCU4.js → chunk-73HV7RV3.js} +3 -3
  73. package/dist/{chunk-T2H2ZCU4.js.map → chunk-73HV7RV3.js.map} +1 -1
  74. package/dist/{chunk-P5VLR3L7.js → chunk-BI2NKPA4.js} +8 -4
  75. package/dist/chunk-BI2NKPA4.js.map +1 -0
  76. package/dist/{chunk-IIGWJE5T.js → chunk-DPUZJI45.js} +3 -3
  77. package/dist/{chunk-IIGWJE5T.js.map → chunk-DPUZJI45.js.map} +1 -1
  78. package/dist/chunk-F2IBDVBP.js +72 -0
  79. package/dist/chunk-F2IBDVBP.js.map +1 -0
  80. package/dist/{chunk-G73FOLLO.js → chunk-F3ET4UGP.js} +3 -3
  81. package/dist/{chunk-G73FOLLO.js.map → chunk-F3ET4UGP.js.map} +1 -1
  82. package/dist/{chunk-ZWPXSHMW.js → chunk-HA53HVIS.js} +336 -100
  83. package/dist/chunk-HA53HVIS.js.map +1 -0
  84. package/dist/{chunk-HLIN4HRK.js → chunk-LTQOZIJO.js} +2 -2
  85. package/dist/{chunk-HLIN4HRK.js.map → chunk-LTQOZIJO.js.map} +1 -1
  86. package/dist/{chunk-CS6NFRNQ.js → chunk-MJEUK6VJ.js} +2 -2
  87. package/dist/{chunk-CS6NFRNQ.js.map → chunk-MJEUK6VJ.js.map} +1 -1
  88. package/dist/{chunk-AF2NTM4B.js → chunk-NRXVS7E4.js} +6 -6
  89. package/dist/{chunk-AF2NTM4B.js.map → chunk-NRXVS7E4.js.map} +1 -1
  90. package/dist/{chunk-UDUFMVJD.js → chunk-RADBQ5XJ.js} +7 -7
  91. package/dist/{chunk-UDUFMVJD.js.map → chunk-RADBQ5XJ.js.map} +1 -1
  92. package/dist/{chunk-TSB46BHR.js → chunk-RDE43ORT.js} +4 -4
  93. package/dist/{chunk-TSB46BHR.js.map → chunk-RDE43ORT.js.map} +1 -1
  94. package/dist/{chunk-4RI4TNMO.js → chunk-SELNYOEO.js} +2 -2
  95. package/dist/{chunk-4RI4TNMO.js.map → chunk-SELNYOEO.js.map} +1 -1
  96. package/dist/{chunk-KECY74AL.js → chunk-SRWIVMJI.js} +4 -4
  97. package/dist/{chunk-KECY74AL.js.map → chunk-SRWIVMJI.js.map} +1 -1
  98. package/dist/{chunk-VK24PIAB.js → chunk-TFIFNDT2.js} +3 -3
  99. package/dist/{chunk-VK24PIAB.js.map → chunk-TFIFNDT2.js.map} +1 -1
  100. package/dist/{chunk-NIZNN42B.js → chunk-XW3SWH7C.js} +3 -3
  101. package/dist/{chunk-NIZNN42B.js.map → chunk-XW3SWH7C.js.map} +1 -1
  102. package/dist/{chunk-BIAA3N2A.js → chunk-Z6NGK5YC.js} +3 -3
  103. package/dist/{chunk-BIAA3N2A.js.map → chunk-Z6NGK5YC.js.map} +1 -1
  104. package/dist/cli/index.js +41 -41
  105. package/dist/connectors/config.js +1 -1
  106. package/dist/connectors/index.js +1 -1
  107. package/dist/core/index.js +1 -1
  108. package/dist/deploy/index.js +1 -1
  109. package/dist/{ensure-sources-O7X3LYLG.js → ensure-sources-QGL44IRI.js} +8 -8
  110. package/dist/{ensure-sources-O7X3LYLG.js.map → ensure-sources-QGL44IRI.js.map} +1 -1
  111. package/dist/factory-assets/connectors/box.js +1 -1
  112. package/dist/factory-assets/connectors/deploy.js +1 -1
  113. package/dist/factory-assets/connectors/devserver.js +1 -1
  114. package/dist/factory-assets/connectors/flow/adapter.js +1 -1
  115. package/dist/factory-assets/connectors/flow/engine.js +1 -1
  116. package/dist/factory-assets/connectors/flow/run-flow.js +2 -2
  117. package/dist/factory-assets/connectors/flow.js +1 -1
  118. package/dist/factory-assets/connectors/git.js +1 -1
  119. package/dist/factory-assets/connectors/gmail.js +1 -1
  120. package/dist/factory-assets/connectors/googledrive.js +1 -1
  121. package/dist/factory-assets/connectors/local.js +1 -1
  122. package/dist/factory-assets/connectors/mattermost.js +1 -1
  123. package/dist/factory-assets/connectors/memory.js +1 -1
  124. package/dist/factory-assets/connectors/minio.js +1 -1
  125. package/dist/factory-assets/connectors/postgres.js +1 -1
  126. package/dist/factory-assets/connectors/s3.js +1 -1
  127. package/dist/factory-assets/connectors/sharepoint.js +1 -1
  128. package/dist/factory-assets/connectors/sqlite.js +1 -1
  129. package/dist/factory-assets/connectors/static-server.js +1 -1
  130. package/dist/factory-assets/connectors/tunnel.js +1 -1
  131. package/dist/factory-assets/connectors/webdav.js +1 -1
  132. package/dist/factory-assets/connectors/xstate-store.js +1 -1
  133. package/dist/factory-assets/connectors/xstate.js +1 -1
  134. package/dist/{flows-TIUWM7SZ.js → flows-FHWBCLXF.js} +3 -3
  135. package/dist/{flows-TIUWM7SZ.js.map → flows-FHWBCLXF.js.map} +1 -1
  136. package/dist/{helpers-DNTGJSA5.js → helpers-FMWODXTW.js} +8 -8
  137. package/dist/{helpers-DNTGJSA5.js.map → helpers-FMWODXTW.js.map} +1 -1
  138. package/dist/library/index.js +4 -4
  139. package/dist/library/install.js +1 -1
  140. package/dist/{library-publish-XKQN6RTR.js → library-publish-7YDHQGBN.js} +10 -10
  141. package/dist/{library-publish-XKQN6RTR.js.map → library-publish-7YDHQGBN.js.map} +1 -1
  142. package/dist/{open-library-2BWIUK4F.js → open-library-O6T3ELPK.js} +9 -9
  143. package/dist/{open-library-2BWIUK4F.js.map → open-library-O6T3ELPK.js.map} +1 -1
  144. package/dist/{paths-KOKCMBL5.js → paths-5QJJBAND.js} +7 -7
  145. package/dist/{paths-KOKCMBL5.js.map → paths-5QJJBAND.js.map} +1 -1
  146. package/dist/{plugin-store-QMJ2B3VM.js → plugin-store-ZBJZ7F6D.js} +3 -3
  147. package/dist/{plugin-store-QMJ2B3VM.js.map → plugin-store-ZBJZ7F6D.js.map} +1 -1
  148. package/dist/{project-4NPDM7R3.js → project-HAMLHLD5.js} +9 -9
  149. package/dist/{project-4NPDM7R3.js.map → project-HAMLHLD5.js.map} +1 -1
  150. package/dist/runner/index.js +8 -8
  151. package/dist/runner/src/serve.d.ts +138 -1
  152. package/dist/runner/src/serve.d.ts.map +1 -1
  153. package/dist/sdk/asset-manager.js +5 -5
  154. package/dist/sdk/bridge.js +2 -2
  155. package/dist/sdk/core.js +1 -1
  156. package/dist/sdk/flow.js +1 -1
  157. package/dist/sdk/index.js +8 -8
  158. package/dist/sdk/runner.js +8 -8
  159. package/dist/sdk/session.js +2 -2
  160. package/dist/sdk/transport/ws.js +1 -1
  161. package/dist/sdk/transport.js +1 -1
  162. package/dist/sdk/types.js +1 -1
  163. package/dist/session/index.js +2 -2
  164. package/dist/session/src/dispatcher.d.ts.map +1 -1
  165. package/dist/{setup-TNHKXCBL.js → setup-OJC74Q2Q.js} +5 -5
  166. package/dist/{setup-TNHKXCBL.js.map → setup-OJC74Q2Q.js.map} +1 -1
  167. package/dist/{source-HVMQJZU7.js → source-ITDH4367.js} +10 -10
  168. package/dist/{source-HVMQJZU7.js.map → source-ITDH4367.js.map} +1 -1
  169. package/dist/{source-manifest-publish-JN62UMZD.js → source-manifest-publish-RMHC7R6M.js} +11 -11
  170. package/dist/{source-manifest-publish-JN62UMZD.js.map → source-manifest-publish-RMHC7R6M.js.map} +1 -1
  171. package/dist/transport/index.js +1 -1
  172. package/dist/transport/ws.js +1 -1
  173. package/dist/tui/index.js +8 -8
  174. package/dist/types/index.js +1 -1
  175. package/dist/types/src/events.d.ts +74 -1
  176. package/dist/types/src/events.d.ts.map +1 -1
  177. package/dist/types/src/flow.d.ts +34 -0
  178. package/dist/types/src/flow.d.ts.map +1 -1
  179. package/dist/types/src/index.d.ts +2 -2
  180. package/dist/types/src/index.d.ts.map +1 -1
  181. package/dist/types/src/protocol.d.ts +6 -0
  182. package/dist/types/src/protocol.d.ts.map +1 -1
  183. package/dist/types/src/version.d.ts +23 -1
  184. package/dist/types/src/version.d.ts.map +1 -1
  185. package/dist/workspace-plugin/adapters/mcp.js +1 -1
  186. package/dist/workspace-plugin/adapters/omp.js +3 -3
  187. package/dist/workspace-plugin/index.js +1 -1
  188. package/package.json +15 -2
  189. package/dist/chunk-P5VLR3L7.js.map +0 -1
  190. package/dist/chunk-ZWPXSHMW.js.map +0 -1
@@ -0,0 +1,595 @@
1
+ import { makeConversationKey, normalizeMentions, parseConversationKey, makeQuestionCard, markdownToTeamsText } from '../../chunk-F2IBDVBP.js';
2
+ import '../../chunk-NSBPE2FW.js';
3
+ import { Buffer } from 'buffer';
4
+
5
+ // channels/adapters/msteams/activity.ts
6
+ var MESSAGE_ID_SEPARATOR = ";messageid=";
7
+ function conversationKeyFromActivity(a) {
8
+ const sep = a.conversation.id.indexOf(MESSAGE_ID_SEPARATOR);
9
+ if (sep === -1) return makeConversationKey("msteams", a.conversation.id);
10
+ return makeConversationKey(
11
+ "msteams",
12
+ a.conversation.id.slice(0, sep),
13
+ a.conversation.id.slice(sep + MESSAGE_ID_SEPARATOR.length)
14
+ );
15
+ }
16
+ function tenantIdOfActivity(a) {
17
+ return a.conversation.tenantId ?? a.channelData?.tenant?.id;
18
+ }
19
+ function actorOfActivity(a) {
20
+ return {
21
+ platform: "msteams",
22
+ externalId: a.from.aadObjectId ?? a.from.id,
23
+ displayName: a.from.name ?? "",
24
+ tenantId: tenantIdOfActivity(a)
25
+ };
26
+ }
27
+ function stripMentionTokens(a) {
28
+ let text = a.text ?? "";
29
+ const entities = [];
30
+ for (const e of a.entities ?? []) {
31
+ if (e.type !== "mention" || !e.mentioned) continue;
32
+ const displayName = e.mentioned.name ?? "";
33
+ let offset = 0;
34
+ if (e.text) {
35
+ const at = text.indexOf(e.text);
36
+ if (at !== -1) {
37
+ text = text.slice(0, at) + displayName + text.slice(at + e.text.length);
38
+ offset = at;
39
+ }
40
+ }
41
+ entities.push({
42
+ externalId: e.mentioned.id,
43
+ displayName,
44
+ isBot: e.mentioned.id === a.recipient.id,
45
+ offset,
46
+ length: displayName.length
47
+ });
48
+ }
49
+ return { text, entities };
50
+ }
51
+ function optionOf(value) {
52
+ if (typeof value !== "object" || value === null) return void 0;
53
+ const option = value.option;
54
+ return typeof option === "string" ? option : void 0;
55
+ }
56
+ async function activityToChannelMessage(a, identity) {
57
+ if (a.type !== "message") return null;
58
+ if (a.from.id === a.recipient.id) return null;
59
+ const conversationKey = conversationKeyFromActivity(a);
60
+ const { text, entities } = stripMentionTokens(a);
61
+ const mentions = await normalizeMentions(
62
+ entities,
63
+ (e) => ({
64
+ platform: "msteams",
65
+ externalId: e.externalId,
66
+ displayName: e.displayName,
67
+ tenantId: tenantIdOfActivity(a)
68
+ }),
69
+ identity
70
+ );
71
+ const actor = actorOfActivity(a);
72
+ const messageRef = {
73
+ platform: "msteams",
74
+ conversationKey,
75
+ messageId: a.id,
76
+ raw: { serviceUrl: a.serviceUrl, conversationId: a.conversation.id }
77
+ };
78
+ return {
79
+ id: crypto.randomUUID(),
80
+ // seq/sessionId are assigned on the session side; ingress carries placeholders.
81
+ seq: 0,
82
+ timestamp: a.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
83
+ sessionId: "",
84
+ payload: { type: "prompt", prompt: text || (optionOf(a.value) ?? "") },
85
+ meta: {
86
+ mentions,
87
+ channel: {
88
+ platform: "msteams",
89
+ conversationKey,
90
+ eventId: a.id,
91
+ tenantId: tenantIdOfActivity(a),
92
+ actor: { externalId: actor.externalId, displayName: actor.displayName },
93
+ messageRef
94
+ }
95
+ }
96
+ };
97
+ }
98
+
99
+ // channels/adapters/msteams/client.ts
100
+ var DEFAULT_LOGIN_BASE_URL = "https://login.microsoftonline.com/botframework.com";
101
+ var BOTFRAMEWORK_SCOPE = "https://api.botframework.com/.default";
102
+ var TOKEN_EXPIRY_MARGIN_MS = 6e4;
103
+ var MAX_TRIES = 3;
104
+ var MsTeamsApiError = class extends Error {
105
+ constructor(status, message) {
106
+ super(message);
107
+ this.status = status;
108
+ this.name = "MsTeamsApiError";
109
+ }
110
+ status;
111
+ };
112
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
113
+ var MsTeamsClient = class {
114
+ opts;
115
+ fetchImpl;
116
+ now;
117
+ token;
118
+ constructor(opts) {
119
+ this.opts = opts;
120
+ this.fetchImpl = opts.fetchImpl ?? fetch;
121
+ this.now = opts.now ?? Date.now;
122
+ }
123
+ /** client_credentials token for scope https://api.botframework.com/.default; cached until exp-60s. */
124
+ async getToken() {
125
+ if (this.token && this.token.expiresAt > this.now()) return this.token.value;
126
+ const loginBaseUrl = this.opts.loginBaseUrl ?? DEFAULT_LOGIN_BASE_URL;
127
+ const res = await this.fetchImpl(`${loginBaseUrl}/oauth2/v2.0/token`, {
128
+ method: "POST",
129
+ headers: { "content-type": "application/x-www-form-urlencoded" },
130
+ body: new URLSearchParams({
131
+ grant_type: "client_credentials",
132
+ client_id: this.opts.appId,
133
+ client_secret: this.opts.clientSecret,
134
+ scope: BOTFRAMEWORK_SCOPE
135
+ }).toString()
136
+ });
137
+ if (!res.ok) throw new MsTeamsApiError(res.status, `token request failed: ${res.status}`);
138
+ const body = await res.json();
139
+ this.token = {
140
+ value: body.access_token,
141
+ expiresAt: this.now() + body.expires_in * 1e3 - TOKEN_EXPIRY_MARGIN_MS
142
+ };
143
+ return body.access_token;
144
+ }
145
+ /** POST {serviceUrl}/v3/conversations/{conversationId}/activities[/{replyToId}] → { id }. */
146
+ async postActivity(serviceUrl, conversationId, activity, replyToId) {
147
+ const suffix = replyToId ? `/${encodeURIComponent(replyToId)}` : "";
148
+ const res = await this.request(
149
+ "POST",
150
+ `${this.activitiesUrl(serviceUrl, conversationId)}${suffix}`,
151
+ activity
152
+ );
153
+ const body = await res.json().catch(() => ({}));
154
+ return { id: body.id ?? "" };
155
+ }
156
+ /** PUT {serviceUrl}/v3/conversations/{conversationId}/activities/{activityId}. Same retry policy. */
157
+ async updateActivity(serviceUrl, conversationId, activityId, activity) {
158
+ const url = `${this.activitiesUrl(serviceUrl, conversationId)}/${encodeURIComponent(activityId)}`;
159
+ await this.request("PUT", url, activity);
160
+ }
161
+ activitiesUrl(serviceUrl, conversationId) {
162
+ const base = serviceUrl.replace(/\/+$/, "");
163
+ return `${base}/v3/conversations/${encodeURIComponent(conversationId)}/activities`;
164
+ }
165
+ /**
166
+ * Authorized JSON request honoring 429 Retry-After with jittered backoff,
167
+ * max 3 tries. One 401 forces a fresh token and a single immediate retry.
168
+ */
169
+ async request(method, url, body) {
170
+ let retried401 = false;
171
+ for (let attempt = 1; ; attempt++) {
172
+ const token = await this.getToken();
173
+ const res = await this.fetchImpl(url, {
174
+ method,
175
+ headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
176
+ body: JSON.stringify(body)
177
+ });
178
+ if (res.ok) return res;
179
+ if (res.status === 401 && !retried401 && attempt < MAX_TRIES) {
180
+ retried401 = true;
181
+ this.token = void 0;
182
+ continue;
183
+ }
184
+ if (res.status !== 429 || attempt >= MAX_TRIES) {
185
+ throw new MsTeamsApiError(res.status, `${method} ${url} failed: ${res.status}`);
186
+ }
187
+ const retryAfterSec = Number(res.headers.get("retry-after"));
188
+ const baseDelayMs = Number.isFinite(retryAfterSec) && retryAfterSec > 0 ? retryAfterSec * 1e3 : 1e3 * attempt;
189
+ await sleep(baseDelayMs + Math.random() * 250);
190
+ }
191
+ }
192
+ };
193
+ var DEFAULT_OPENID_METADATA_URL = "https://login.botframework.com/v1/.well-known/openidconfiguration";
194
+ var BOTFRAMEWORK_ISSUER = "https://api.botframework.com";
195
+ var DEFAULT_CLOCK_SKEW_MS = 3e5;
196
+ var JWKS_CACHE_TTL_MS = 864e5;
197
+ var JWKS_FORCED_REFETCH_MIN_INTERVAL_MS = 6e4;
198
+ var jwksCache = /* @__PURE__ */ new Map();
199
+ var lastForcedRefetch = /* @__PURE__ */ new Map();
200
+ var fail = (reason) => ({ ok: false, reason });
201
+ function decodeSegment(segment) {
202
+ const parsed = JSON.parse(Buffer.from(segment, "base64url").toString("utf8"));
203
+ if (typeof parsed !== "object" || parsed === null) throw new Error("non-object JWT segment");
204
+ return parsed;
205
+ }
206
+ async function getSigningKey(metadataUrl, kid, fetchImpl, now) {
207
+ const cached = jwksCache.get(metadataUrl);
208
+ if (cached && cached.expiresAt > now()) {
209
+ const key = cached.keysByKid.get(kid);
210
+ if (key) return key;
211
+ const last = lastForcedRefetch.get(metadataUrl);
212
+ if (last !== void 0 && now() - last < JWKS_FORCED_REFETCH_MIN_INTERVAL_MS) return void 0;
213
+ lastForcedRefetch.set(metadataUrl, now());
214
+ }
215
+ const metadataRes = await fetchImpl(metadataUrl);
216
+ if (!metadataRes.ok) throw new Error(`OpenID metadata fetch failed: ${metadataRes.status}`);
217
+ const metadata = await metadataRes.json();
218
+ if (typeof metadata.jwks_uri !== "string") throw new Error("OpenID metadata has no jwks_uri");
219
+ const jwksRes = await fetchImpl(metadata.jwks_uri);
220
+ if (!jwksRes.ok) throw new Error(`JWKS fetch failed: ${jwksRes.status}`);
221
+ const jwks = await jwksRes.json();
222
+ const keysByKid = /* @__PURE__ */ new Map();
223
+ for (const jwk of jwks.keys ?? []) {
224
+ if (typeof jwk.kid !== "string" || jwk.kty !== "RSA") continue;
225
+ try {
226
+ const key = await crypto.subtle.importKey(
227
+ "jwk",
228
+ jwk,
229
+ { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
230
+ false,
231
+ ["verify"]
232
+ );
233
+ keysByKid.set(jwk.kid, key);
234
+ } catch {
235
+ }
236
+ }
237
+ jwksCache.set(metadataUrl, { expiresAt: now() + JWKS_CACHE_TTL_MS, keysByKid });
238
+ return keysByKid.get(kid);
239
+ }
240
+ async function validateBotFrameworkJwt(authorizationHeader, activityServiceUrl, opts) {
241
+ const clockSkewMs = opts.clockSkewMs ?? DEFAULT_CLOCK_SKEW_MS;
242
+ const fetchImpl = opts.fetchImpl ?? fetch;
243
+ const now = opts.now ?? Date.now;
244
+ const metadataUrl = opts.openIdMetadataUrl ?? DEFAULT_OPENID_METADATA_URL;
245
+ if (!authorizationHeader?.startsWith("Bearer ")) return fail("missing Bearer token");
246
+ const token = authorizationHeader.slice("Bearer ".length).trim();
247
+ const parts = token.split(".");
248
+ if (parts.length !== 3) return fail("malformed JWT (expected three segments)");
249
+ let header;
250
+ let payload;
251
+ try {
252
+ header = decodeSegment(parts[0]);
253
+ payload = decodeSegment(parts[1]);
254
+ } catch {
255
+ return fail("undecodable JWT header or payload");
256
+ }
257
+ if (header.alg !== "RS256") return fail(`unexpected alg "${String(header.alg)}"`);
258
+ if (payload.iss !== BOTFRAMEWORK_ISSUER) return fail(`wrong iss "${String(payload.iss)}"`);
259
+ const aud = payload.aud;
260
+ const audOk = Array.isArray(aud) ? aud.includes(opts.appId) : aud === opts.appId;
261
+ if (!audOk) return fail(`wrong aud "${String(aud)}"`);
262
+ if (typeof payload.exp !== "number") return fail("missing exp claim");
263
+ if (now() > payload.exp * 1e3 + clockSkewMs) return fail("token expired");
264
+ if (typeof payload.nbf === "number" && now() < payload.nbf * 1e3 - clockSkewMs) {
265
+ return fail("token not yet valid");
266
+ }
267
+ if (payload.serviceurl !== activityServiceUrl) {
268
+ return fail("serviceurl claim does not match the activity serviceUrl");
269
+ }
270
+ if (typeof header.kid !== "string") return fail("missing kid header");
271
+ let key;
272
+ try {
273
+ key = await getSigningKey(metadataUrl, header.kid, fetchImpl, now);
274
+ } catch (err) {
275
+ return fail(`signing key resolution failed: ${String(err)}`);
276
+ }
277
+ if (!key) return fail(`unknown signing key "${header.kid}"`);
278
+ const valid = await crypto.subtle.verify(
279
+ "RSASSA-PKCS1-v1_5",
280
+ key,
281
+ Buffer.from(parts[2], "base64url"),
282
+ new TextEncoder().encode(`${parts[0]}.${parts[1]}`)
283
+ );
284
+ return valid ? { ok: true } : fail("invalid signature");
285
+ }
286
+
287
+ // channels/adapters/msteams/index.ts
288
+ var DEFAULT_INGRESS_PATH = "/channels/msteams/messages";
289
+ var DEDUPE_TTL_MS = 6e5;
290
+ var NULL_IDENTITY = { resolveActor: async () => null };
291
+ var emptyOk = () => new Response(null, { status: 200 });
292
+ var jsonOk = () => new Response("{}", { status: 200, headers: { "content-type": "application/json" } });
293
+ var MsTeamsAdapter = class {
294
+ platform = "msteams";
295
+ capabilities = {
296
+ canEditMessages: true,
297
+ canDeleteMessages: true,
298
+ supportsThreads: true,
299
+ supportsCards: true,
300
+ supportsTyping: true,
301
+ supportsEphemeral: false,
302
+ supportsReactions: true,
303
+ maxMessageLength: 25e3,
304
+ ackDeadlineMs: 15e3,
305
+ editThrottleMs: 1e3,
306
+ textFormat: "adaptive-card"
307
+ };
308
+ opts;
309
+ ctx;
310
+ /** tenantId → lazily-created provider client (promise-cached to collapse races). */
311
+ clients = /* @__PURE__ */ new Map();
312
+ /** Ingress-observed conversation facts, keyed by conversationKey (thread AND channel level). */
313
+ conversations = /* @__PURE__ */ new Map();
314
+ /** Ingress-observed actors by externalId — the lookupActor roster. */
315
+ roster = /* @__PURE__ */ new Map();
316
+ /** post() idempotency: dedupeKey → prior ref, TTL {@link DEDUPE_TTL_MS}. */
317
+ posted = /* @__PURE__ */ new Map();
318
+ constructor(opts) {
319
+ this.opts = opts;
320
+ }
321
+ /** Registers the ingress route on the host; no connection is opened. */
322
+ async start(ctx) {
323
+ this.ctx = ctx;
324
+ ctx.ingress.registerHttpRoute?.(
325
+ this.opts.ingressPath ?? DEFAULT_INGRESS_PATH,
326
+ this.handleRequest
327
+ );
328
+ }
329
+ /** Drops the context and per-tenant clients; the host owns route teardown. */
330
+ async stop() {
331
+ this.ctx = void 0;
332
+ this.clients.clear();
333
+ }
334
+ /**
335
+ * The Bot Framework messaging endpoint. Guarded by a `Promise.race` at
336
+ * `ackDeadlineMs - 1000`: if processing overruns, Teams still gets its 200
337
+ * and at-least-once redelivery covers the rest.
338
+ */
339
+ handleRequest = async (req) => {
340
+ let timer;
341
+ const guard = new Promise((resolve) => {
342
+ timer = setTimeout(() => resolve(jsonOk()), this.capabilities.ackDeadlineMs - 1e3);
343
+ });
344
+ try {
345
+ return await Promise.race([this.processRequest(req), guard]);
346
+ } finally {
347
+ clearTimeout(timer);
348
+ }
349
+ };
350
+ async processRequest(req) {
351
+ const ctx = this.ctx;
352
+ if (!ctx) return new Response("adapter not started", { status: 503 });
353
+ let activity;
354
+ try {
355
+ activity = await req.json();
356
+ } catch {
357
+ return new Response("invalid JSON body", { status: 400 });
358
+ }
359
+ if (typeof activity?.type !== "string" || typeof activity?.serviceUrl !== "string") {
360
+ return new Response("not a Bot Framework activity", { status: 400 });
361
+ }
362
+ const verdict = await validateBotFrameworkJwt(
363
+ req.headers.get("authorization") ?? void 0,
364
+ activity.serviceUrl,
365
+ {
366
+ appId: this.opts.appId,
367
+ openIdMetadataUrl: this.opts.openIdMetadataUrl,
368
+ fetchImpl: this.opts.fetchImpl
369
+ }
370
+ );
371
+ if (!verdict.ok) return new Response(verdict.reason, { status: 401 });
372
+ this.rememberConversation(activity);
373
+ this.rememberActors(activity);
374
+ if (activity.from?.id === activity.recipient?.id) return emptyOk();
375
+ if (activity.type === "messageReaction") {
376
+ await this.forwardReactions(activity, ctx);
377
+ return emptyOk();
378
+ }
379
+ if (activity.type !== "message") return emptyOk();
380
+ const msg = await activityToChannelMessage(activity, NULL_IDENTITY);
381
+ if (!msg) return emptyOk();
382
+ await ctx.sink.onMessage(msg);
383
+ return jsonOk();
384
+ }
385
+ /** Records serviceUrl + tenant under the thread key AND its channel-level key. */
386
+ rememberConversation(a) {
387
+ const key = conversationKeyFromActivity(a);
388
+ const info = { serviceUrl: a.serviceUrl, tenantId: tenantIdOfActivity(a) };
389
+ this.conversations.set(key, info);
390
+ const { conversationId } = parseConversationKey(key);
391
+ this.conversations.set(makeConversationKey("msteams", conversationId), info);
392
+ }
393
+ /** Grows the lookupActor roster from the sender and mention entities (bot excluded). */
394
+ rememberActors(a) {
395
+ if (a.from?.id !== a.recipient?.id) {
396
+ const actor = actorOfActivity(a);
397
+ if (actor.externalId) this.roster.set(actor.externalId, actor);
398
+ }
399
+ for (const e of a.entities ?? []) {
400
+ if (e.type !== "mention" || !e.mentioned || e.mentioned.id === a.recipient?.id) continue;
401
+ this.roster.set(e.mentioned.id, {
402
+ platform: "msteams",
403
+ externalId: e.mentioned.id,
404
+ displayName: e.mentioned.name ?? "",
405
+ tenantId: tenantIdOfActivity(a)
406
+ });
407
+ }
408
+ }
409
+ /** Maps a messageReaction activity onto InboundReaction events (one per emoji). */
410
+ async forwardReactions(a, ctx) {
411
+ if (!ctx.sink.onReaction) return;
412
+ const conversationKey = conversationKeyFromActivity(a);
413
+ const actor = actorOfActivity(a);
414
+ const messageRef = {
415
+ platform: "msteams",
416
+ conversationKey,
417
+ // Teams points a reaction at its target via replyToId.
418
+ messageId: a.replyToId ?? a.id,
419
+ raw: { serviceUrl: a.serviceUrl, conversationId: a.conversation.id }
420
+ };
421
+ const groups = [
422
+ ["added", a.reactionsAdded ?? []],
423
+ ["removed", a.reactionsRemoved ?? []]
424
+ ];
425
+ for (const [action, reactions] of groups) {
426
+ for (const reaction of reactions) {
427
+ await ctx.sink.onReaction({
428
+ conversationKey,
429
+ messageRef,
430
+ emoji: reaction.type,
431
+ actor,
432
+ action,
433
+ eventId: `${a.id}:${action}:${reaction.type}`
434
+ });
435
+ }
436
+ }
437
+ }
438
+ /** Posts a message; a repeated `dedupeKey` returns the prior ref with no second side effect. */
439
+ async post(target, msg, dedupeKey) {
440
+ const prior = this.dedupeGet(dedupeKey);
441
+ if (prior) return prior;
442
+ if (msg.markdown.length > this.capabilities.maxMessageLength) {
443
+ throw new Error(
444
+ `message length ${msg.markdown.length} exceeds maxMessageLength ${this.capabilities.maxMessageLength} \u2014 chunk upstream`
445
+ );
446
+ }
447
+ const { serviceUrl, conversationId } = this.resolveTarget(target);
448
+ const client = await this.clientForConversation(target.conversationKey);
449
+ const { id } = await client.postActivity(serviceUrl, conversationId, this.buildActivity(msg));
450
+ const ref = {
451
+ platform: "msteams",
452
+ conversationKey: target.conversationKey,
453
+ messageId: id,
454
+ raw: { serviceUrl, conversationId }
455
+ };
456
+ this.posted.set(dedupeKey, { ref, expiresAt: Date.now() + DEDUPE_TTL_MS });
457
+ return ref;
458
+ }
459
+ /** Replaces a previously posted activity's content in place. */
460
+ async update(ref, msg) {
461
+ const raw = ref.raw ?? {};
462
+ const fallback = typeof raw.serviceUrl === "string" && typeof raw.conversationId === "string" ? { serviceUrl: raw.serviceUrl, conversationId: raw.conversationId } : this.resolveTarget({ conversationKey: ref.conversationKey });
463
+ const client = await this.clientForConversation(ref.conversationKey);
464
+ await client.updateActivity(
465
+ fallback.serviceUrl,
466
+ fallback.conversationId,
467
+ ref.messageId,
468
+ this.buildActivity(msg)
469
+ );
470
+ }
471
+ /** Native Teams typing indicator — fire-and-forget, never deduped. */
472
+ async indicateTyping(target) {
473
+ const { serviceUrl, conversationId } = this.resolveTarget(target);
474
+ const client = await this.clientForConversation(target.conversationKey);
475
+ await client.postActivity(serviceUrl, conversationId, { type: "typing" });
476
+ }
477
+ /** Preflight: a typing probe proves the bot can post; 403/404 → actionable problem. */
478
+ async verifyConversation(key) {
479
+ try {
480
+ const { serviceUrl, conversationId } = this.resolveTarget({ conversationKey: key });
481
+ const client = await this.clientForConversation(key);
482
+ await client.postActivity(serviceUrl, conversationId, { type: "typing" });
483
+ return { ok: true, problems: [] };
484
+ } catch (err) {
485
+ if (err instanceof MsTeamsApiError && (err.status === 403 || err.status === 404)) {
486
+ return {
487
+ ok: false,
488
+ problems: [
489
+ `bot cannot post to ${key} \u2014 check the app is installed in the team and the bot has access`
490
+ ]
491
+ };
492
+ }
493
+ return { ok: false, problems: [err instanceof Error ? err.message : String(err)] };
494
+ }
495
+ }
496
+ /** Resolves from the ingress-observed roster only (Graph lookup is platform glue, later). */
497
+ async lookupActor(query) {
498
+ if (query.externalId) return this.roster.get(query.externalId);
499
+ for (const actor of this.roster.values()) {
500
+ if (query.email && actor.email === query.email) return actor;
501
+ if (query.name && actor.displayName.toLowerCase().includes(query.name.toLowerCase())) {
502
+ return actor;
503
+ }
504
+ }
505
+ return void 0;
506
+ }
507
+ /** Decision 11: Adaptive Card for questions with options; markdown text otherwise. */
508
+ buildActivity(msg) {
509
+ if (msg.kind === "question" && msg.options?.length) {
510
+ return {
511
+ type: "message",
512
+ attachments: [
513
+ {
514
+ contentType: "application/vnd.microsoft.card.adaptive",
515
+ content: makeQuestionCard(markdownToTeamsText(msg.markdown), msg.options)
516
+ }
517
+ ]
518
+ };
519
+ }
520
+ const text = markdownToTeamsText(msg.markdown);
521
+ return {
522
+ type: "message",
523
+ textFormat: "markdown",
524
+ text: msg.kind === "error" ? `\u26A0\uFE0F ${text}` : text
525
+ };
526
+ }
527
+ /** serviceUrl + native conversation id for a target — threadRef.raw first, ingress memory else. */
528
+ resolveTarget(target) {
529
+ const raw = target.threadRef?.raw ?? {};
530
+ if (typeof raw.serviceUrl === "string" && typeof raw.conversationId === "string") {
531
+ return { serviceUrl: raw.serviceUrl, conversationId: raw.conversationId };
532
+ }
533
+ const { conversationId, threadRootId } = parseConversationKey(target.conversationKey);
534
+ const nativeId = threadRootId ? `${conversationId};messageid=${threadRootId}` : conversationId;
535
+ const info = this.infoFor(target.conversationKey);
536
+ if (!info) {
537
+ throw new Error(
538
+ `no known serviceUrl for ${target.conversationKey} \u2014 the conversation has not been seen on ingress`
539
+ );
540
+ }
541
+ return { serviceUrl: info.serviceUrl, conversationId: nativeId };
542
+ }
543
+ /** Conversation facts under the exact key, falling back to its channel-level key. */
544
+ infoFor(key) {
545
+ const exact = this.conversations.get(key);
546
+ if (exact) return exact;
547
+ const { conversationId } = parseConversationKey(key);
548
+ return this.conversations.get(makeConversationKey("msteams", conversationId));
549
+ }
550
+ /** One MsTeamsClient per tenant, built from CredentialPort (kind must be "msteams"). */
551
+ clientForConversation(key) {
552
+ const ctx = this.ctx;
553
+ if (!ctx) return Promise.reject(new Error("adapter not started"));
554
+ const tenantId = this.infoFor(key)?.tenantId;
555
+ if (!tenantId) {
556
+ return Promise.reject(
557
+ new Error(`no known tenant for ${key} \u2014 the conversation has not been seen on ingress`)
558
+ );
559
+ }
560
+ return this.clientForTenant(tenantId, ctx.credentials);
561
+ }
562
+ clientForTenant(tenantId, credentials) {
563
+ const cached = this.clients.get(tenantId);
564
+ if (cached) return cached;
565
+ const created = (async () => {
566
+ const cred = await credentials.forTenant("msteams", tenantId);
567
+ if (cred.kind !== "msteams") {
568
+ throw new Error(
569
+ `expected "msteams" credentials for tenant ${tenantId}, got "${cred.kind}"`
570
+ );
571
+ }
572
+ return new MsTeamsClient({
573
+ appId: cred.appId,
574
+ clientSecret: cred.clientSecret,
575
+ loginBaseUrl: this.opts.loginBaseUrl,
576
+ fetchImpl: this.opts.fetchImpl
577
+ });
578
+ })();
579
+ this.clients.set(tenantId, created);
580
+ created.catch(() => this.clients.delete(tenantId));
581
+ return created;
582
+ }
583
+ /** Live dedupe hit for `key`, sweeping expired entries opportunistically. */
584
+ dedupeGet(key) {
585
+ const now = Date.now();
586
+ for (const [k, entry] of this.posted) {
587
+ if (entry.expiresAt <= now) this.posted.delete(k);
588
+ }
589
+ return this.posted.get(key)?.ref;
590
+ }
591
+ };
592
+
593
+ export { MsTeamsAdapter, MsTeamsApiError, MsTeamsClient, activityToChannelMessage, actorOfActivity, conversationKeyFromActivity, tenantIdOfActivity, validateBotFrameworkJwt };
594
+ //# sourceMappingURL=msteams.js.map
595
+ //# sourceMappingURL=msteams.js.map