@open-everywhere/provider-teams 0.0.0-stage → 0.1.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 (146) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +21 -0
  3. package/README.md +46 -2
  4. package/agent/channels/teams-bot-name.ts +162 -0
  5. package/agent/channels/teams-catch-up.ts +244 -0
  6. package/agent/channels/teams-connector.ts +183 -0
  7. package/agent/channels/teams-conversation-ref.ts +398 -0
  8. package/agent/channels/teams-directory.ts +193 -0
  9. package/agent/channels/teams-engagement.ts +391 -0
  10. package/agent/channels/teams-fire-eligibility.ts +152 -0
  11. package/agent/channels/teams-front.ts +1404 -0
  12. package/agent/channels/teams-graph-boundary.ts +178 -0
  13. package/agent/channels/teams-graph-team-id.ts +106 -0
  14. package/agent/channels/teams-id-restore.ts +131 -0
  15. package/agent/channels/teams-identity.ts +395 -0
  16. package/agent/channels/teams-input-response.ts +129 -0
  17. package/agent/channels/teams-message-ops.ts +79 -0
  18. package/agent/channels/teams-origin-codec.ts +104 -0
  19. package/agent/channels/teams-post-arm.ts +323 -0
  20. package/agent/channels/teams-reaction-feedback.ts +127 -0
  21. package/agent/channels/teams-read.ts +510 -0
  22. package/agent/channels/teams-roster.ts +136 -0
  23. package/agent/channels/teams-stop.ts +319 -0
  24. package/agent/channels/teams-supervisor.ts +638 -0
  25. package/agent/channels/teams.ts +270 -0
  26. package/agent/contributions.ts +31 -0
  27. package/agent/docs-generated.ts +15 -0
  28. package/agent/ops.ts +129 -0
  29. package/agent/provider-events.ts +69 -0
  30. package/agent/register.ts +219 -0
  31. package/agent/surface-parity.ts +112 -0
  32. package/dist/agent/channels/teams-bot-name.d.ts +36 -0
  33. package/dist/agent/channels/teams-bot-name.d.ts.map +1 -0
  34. package/dist/agent/channels/teams-bot-name.js +141 -0
  35. package/dist/agent/channels/teams-bot-name.js.map +1 -0
  36. package/dist/agent/channels/teams-catch-up.d.ts +53 -0
  37. package/dist/agent/channels/teams-catch-up.d.ts.map +1 -0
  38. package/dist/agent/channels/teams-catch-up.js +221 -0
  39. package/dist/agent/channels/teams-catch-up.js.map +1 -0
  40. package/dist/agent/channels/teams-connector.d.ts +57 -0
  41. package/dist/agent/channels/teams-connector.d.ts.map +1 -0
  42. package/dist/agent/channels/teams-connector.js +132 -0
  43. package/dist/agent/channels/teams-connector.js.map +1 -0
  44. package/dist/agent/channels/teams-conversation-ref.d.ts +123 -0
  45. package/dist/agent/channels/teams-conversation-ref.d.ts.map +1 -0
  46. package/dist/agent/channels/teams-conversation-ref.js +368 -0
  47. package/dist/agent/channels/teams-conversation-ref.js.map +1 -0
  48. package/dist/agent/channels/teams-directory.d.ts +11 -0
  49. package/dist/agent/channels/teams-directory.d.ts.map +1 -0
  50. package/dist/agent/channels/teams-directory.js +179 -0
  51. package/dist/agent/channels/teams-directory.js.map +1 -0
  52. package/dist/agent/channels/teams-engagement.d.ts +174 -0
  53. package/dist/agent/channels/teams-engagement.d.ts.map +1 -0
  54. package/dist/agent/channels/teams-engagement.js +336 -0
  55. package/dist/agent/channels/teams-engagement.js.map +1 -0
  56. package/dist/agent/channels/teams-fire-eligibility.d.ts +26 -0
  57. package/dist/agent/channels/teams-fire-eligibility.d.ts.map +1 -0
  58. package/dist/agent/channels/teams-fire-eligibility.js +126 -0
  59. package/dist/agent/channels/teams-fire-eligibility.js.map +1 -0
  60. package/dist/agent/channels/teams-front.d.ts +249 -0
  61. package/dist/agent/channels/teams-front.d.ts.map +1 -0
  62. package/dist/agent/channels/teams-front.js +1143 -0
  63. package/dist/agent/channels/teams-front.js.map +1 -0
  64. package/dist/agent/channels/teams-graph-boundary.d.ts +45 -0
  65. package/dist/agent/channels/teams-graph-boundary.d.ts.map +1 -0
  66. package/dist/agent/channels/teams-graph-boundary.js +168 -0
  67. package/dist/agent/channels/teams-graph-boundary.js.map +1 -0
  68. package/dist/agent/channels/teams-graph-team-id.d.ts +30 -0
  69. package/dist/agent/channels/teams-graph-team-id.d.ts.map +1 -0
  70. package/dist/agent/channels/teams-graph-team-id.js +89 -0
  71. package/dist/agent/channels/teams-graph-team-id.js.map +1 -0
  72. package/dist/agent/channels/teams-id-restore.d.ts +34 -0
  73. package/dist/agent/channels/teams-id-restore.d.ts.map +1 -0
  74. package/dist/agent/channels/teams-id-restore.js +123 -0
  75. package/dist/agent/channels/teams-id-restore.js.map +1 -0
  76. package/dist/agent/channels/teams-identity.d.ts +148 -0
  77. package/dist/agent/channels/teams-identity.d.ts.map +1 -0
  78. package/dist/agent/channels/teams-identity.js +336 -0
  79. package/dist/agent/channels/teams-identity.js.map +1 -0
  80. package/dist/agent/channels/teams-input-response.d.ts +23 -0
  81. package/dist/agent/channels/teams-input-response.d.ts.map +1 -0
  82. package/dist/agent/channels/teams-input-response.js +102 -0
  83. package/dist/agent/channels/teams-input-response.js.map +1 -0
  84. package/dist/agent/channels/teams-message-ops.d.ts +3 -0
  85. package/dist/agent/channels/teams-message-ops.d.ts.map +1 -0
  86. package/dist/agent/channels/teams-message-ops.js +72 -0
  87. package/dist/agent/channels/teams-message-ops.js.map +1 -0
  88. package/dist/agent/channels/teams-origin-codec.d.ts +16 -0
  89. package/dist/agent/channels/teams-origin-codec.d.ts.map +1 -0
  90. package/dist/agent/channels/teams-origin-codec.js +99 -0
  91. package/dist/agent/channels/teams-origin-codec.js.map +1 -0
  92. package/dist/agent/channels/teams-post-arm.d.ts +37 -0
  93. package/dist/agent/channels/teams-post-arm.d.ts.map +1 -0
  94. package/dist/agent/channels/teams-post-arm.js +280 -0
  95. package/dist/agent/channels/teams-post-arm.js.map +1 -0
  96. package/dist/agent/channels/teams-reaction-feedback.d.ts +22 -0
  97. package/dist/agent/channels/teams-reaction-feedback.d.ts.map +1 -0
  98. package/dist/agent/channels/teams-reaction-feedback.js +108 -0
  99. package/dist/agent/channels/teams-reaction-feedback.js.map +1 -0
  100. package/dist/agent/channels/teams-read.d.ts +127 -0
  101. package/dist/agent/channels/teams-read.d.ts.map +1 -0
  102. package/dist/agent/channels/teams-read.js +424 -0
  103. package/dist/agent/channels/teams-read.js.map +1 -0
  104. package/dist/agent/channels/teams-roster.d.ts +54 -0
  105. package/dist/agent/channels/teams-roster.d.ts.map +1 -0
  106. package/dist/agent/channels/teams-roster.js +121 -0
  107. package/dist/agent/channels/teams-roster.js.map +1 -0
  108. package/dist/agent/channels/teams-stop.d.ts +74 -0
  109. package/dist/agent/channels/teams-stop.d.ts.map +1 -0
  110. package/dist/agent/channels/teams-stop.js +252 -0
  111. package/dist/agent/channels/teams-stop.js.map +1 -0
  112. package/dist/agent/channels/teams-supervisor.d.ts +351 -0
  113. package/dist/agent/channels/teams-supervisor.d.ts.map +1 -0
  114. package/dist/agent/channels/teams-supervisor.js +366 -0
  115. package/dist/agent/channels/teams-supervisor.js.map +1 -0
  116. package/dist/agent/channels/teams.d.ts +5 -0
  117. package/dist/agent/channels/teams.d.ts.map +1 -0
  118. package/dist/agent/channels/teams.js +246 -0
  119. package/dist/agent/channels/teams.js.map +1 -0
  120. package/dist/agent/contributions.d.ts +12 -0
  121. package/dist/agent/contributions.d.ts.map +1 -0
  122. package/dist/agent/contributions.js +31 -0
  123. package/dist/agent/contributions.js.map +1 -0
  124. package/dist/agent/docs-generated.d.ts +3 -0
  125. package/dist/agent/docs-generated.d.ts.map +1 -0
  126. package/dist/agent/docs-generated.js +13 -0
  127. package/dist/agent/docs-generated.js.map +1 -0
  128. package/dist/agent/ops.d.ts +14 -0
  129. package/dist/agent/ops.d.ts.map +1 -0
  130. package/dist/agent/ops.js +122 -0
  131. package/dist/agent/ops.js.map +1 -0
  132. package/dist/agent/provider-events.d.ts +3 -0
  133. package/dist/agent/provider-events.d.ts.map +1 -0
  134. package/dist/agent/provider-events.js +67 -0
  135. package/dist/agent/provider-events.js.map +1 -0
  136. package/dist/agent/register.d.ts +16 -0
  137. package/dist/agent/register.d.ts.map +1 -0
  138. package/dist/agent/register.js +198 -0
  139. package/dist/agent/register.js.map +1 -0
  140. package/dist/agent/surface-parity.d.ts +3 -0
  141. package/dist/agent/surface-parity.d.ts.map +1 -0
  142. package/dist/agent/surface-parity.js +106 -0
  143. package/dist/agent/surface-parity.js.map +1 -0
  144. package/package.json +183 -4
  145. package/tsconfig.build.json +22 -0
  146. package/tsconfig.json +15 -0
@@ -0,0 +1,368 @@
1
+ // THE TEAMS CONVERSATION REFERENCE (OPE-887) — the durable answer to "where may this deployment
2
+ // reach a Teams person or a Teams team when no turn is running?".
3
+ //
4
+ // WHY TEAMS NEEDS THIS AND NO OTHER SURFACE DOES. Slack and Discord cold-address a direct message
5
+ // from a bare person id under the bot token: `conversations.open`, `POST /users/@me/channels`. The
6
+ // Bot Framework has no equivalent. Every write goes to `{serviceUrl}/v3/...`, `serviceUrl` is
7
+ // REGIONAL (`.../emea/`, `.../amer/`, `.../uk/`) and appears nowhere but on an inbound activity, and
8
+ // the conversation id is minted by Microsoft. So an address that is never captured is an address
9
+ // this deployment can never hold: without this module a proactive Teams delivery is not "not wired",
10
+ // it is impossible.
11
+ //
12
+ // THREE SPECIES, AND CONFUSING THEM WOULD BE THE WORST BUG IN THIS FILE:
13
+ //
14
+ // • `personal` — keyed on the Bot Framework user id (`from.id`, a `29:` string), and its
15
+ // conversation id is a 1:1 chat between that person and the bot. It is written ONLY from an
16
+ // activity whose scope IS `personal`. 🔴 A CHANNEL CONVERSATION ID MUST NEVER LAND IN A
17
+ // PERSONAL ROW: the one caller of the personal address is the DM arm, which delivers private
18
+ // things — an identity-link consent ask, a governance pane read — and a channel id there would
19
+ // post them into a room where everybody can read them.
20
+ // • `team` — keyed on the Teams team id, and its conversation id is the CHANNEL-grain conversation.
21
+ // Its reader is the account directory (`teams-directory.ts`), which needs a conversation to read
22
+ // a roster from.
23
+ // • `conversation` (OPE-899) — keyed on the EXACT conversation id an activity arrived on, at
24
+ // WHATEVER grain that id is (a channel's reply chain included, `;messageid=<root>` suffix and
25
+ // all). Written for every scope, personal/group/channel alike — unlike the two rows above, this
26
+ // one is never selective. Its reader is `teams-message-ops.ts`'s SurfaceMessageOps: the stop
27
+ // acknowledgement (turn-anchor.ts's `postSurfaceText`) is handed only a bare conversation id
28
+ // (core's `channelId`), never a `serviceUrl`, so posting the cancel note into that EXACT id needs
29
+ // a lookup this row is the only thing that can answer. The `personal`/`team` rows cannot stand in
30
+ // for it: `personal` requires a principal id the caller does not have, and `team` stores the
31
+ // CHANNEL grain, not the reply-chain grain a channel turn actually anchors on.
32
+ // A CHANNEL activity writes this species TWICE (OPE-1093): once at the reply-chain grain it
33
+ // arrived on, and once at the CHANNEL grain, which is the grain every roster entry, surface key
34
+ // and resource-grant address names. Without the second row those two sets are disjoint and no
35
+ // Teams container grant can deliver — see the CHANNEL-GRAIN TWIN block in `teamsAddressRowsFor`.
36
+ //
37
+ // IT ALSO WRITES THE ID-RESTORATION INDEX ROWS (`teams-id-restore.ts`), because this is the one
38
+ // point where a raw Teams id enters the deployment. Those rows are a SPELLING and not an address:
39
+ // they carry no `serviceUrl`, they name no reachable place, and they exist only so a resource-grant
40
+ // address — which can spell a Teams id one way and only one way — can be turned back into something
41
+ // the Bot Framework accepts. Read that module's header before changing anything here.
42
+ //
43
+ // A GROUP CHAT WRITES NO `personal`/`team` ROW — it is neither a 1:1 nor a team, its members are an
44
+ // ad-hoc set, and neither reader has a use for it — but it DOES write a `conversation` row, same as
45
+ // every other scope. "Unsupported, never faked" still holds: a group chat has no DM-arm or directory
46
+ // use, but it is exactly as stoppable-and-acknowledgeable as a channel or a personal chat, so the one
47
+ // row that stop acknowledgement depends on is written unconditionally.
48
+ //
49
+ // EVERY ROW IS RE-STAMPED, NEVER WRITTEN ONCE. Microsoft makes no promise that a tenant keeps its
50
+ // `serviceUrl` region, so the newest inbound activity always wins. That is also why there is no TTL:
51
+ // an address is corrected by traffic, and expiring one would trade a stale address for none at all.
52
+ //
53
+ // 🔴 THE HOST ALLOWLIST IS APPLIED TWICE, ON PURPOSE. A stored `serviceUrl` becomes the target of a
54
+ // `client_credentials` Connector bearer token, and it arrived in an activity BODY that eve's JWT
55
+ // verifier does not bind (see `isTrustedTeamsServiceUrl`'s own comment). So an untrusted host is
56
+ // refused at WRITE time — it never reaches the table — and every read re-checks, because a row
57
+ // written by an older, weaker build must not become a token exfiltration route after an upgrade.
58
+ import { getOperationalStore, deploymentOrg } from "@open-everywhere/core/agent/adapter-api.js";
59
+ import { isTrustedTeamsServiceUrl } from "./teams-origin-codec.js";
60
+ import { teamsIdIndexRow, TEAMS_CONVERSATION_KEY_KIND, TEAMS_PERSON_KEY_KIND } from "./teams-id-restore.js";
61
+ /** The platform key both row species are stored under. */
62
+ export const TEAMS_ADDRESS_PLATFORM = "teams";
63
+ /** The `kind` of a 1:1 chat reference — keyed on the Bot Framework `29:` user id. */
64
+ export const TEAMS_PERSONAL_ADDRESS_KIND = "personal";
65
+ /** The `kind` of a team reference — keyed on the Teams team id. */
66
+ export const TEAMS_TEAM_ADDRESS_KIND = "team";
67
+ /** The `kind` of a bare conversation reference (OPE-899) — keyed on the EXACT conversation id, at
68
+ * whatever grain it arrived on. See the header's third species. */
69
+ const TEAMS_CONVERSATION_ADDRESS_KIND = "conversation";
70
+ /** How many team references one directory search may read. A deployment serving more teams than this
71
+ * is not a shape we have; the bound exists so a growing table can never turn a human-paced lookup
72
+ * into an unbounded fan-out of live calls inside one turn. */
73
+ export const TEAMS_TEAM_ADDRESS_READ_LIMIT = 8;
74
+ /** The `;messageid=<root>` suffix Teams appends to a CHANNEL conversation id. It names one REPLY
75
+ * CHAIN inside the channel, never the channel itself. */
76
+ const TEAMS_REPLY_CHAIN_SUFFIX = ";messageid=";
77
+ /**
78
+ * The CHANNEL-grain conversation id — the same id with any reply-chain suffix removed. A personal or
79
+ * group chat id carries no suffix and comes back unchanged.
80
+ *
81
+ * WHY THIS IS SPELLED HERE RATHER THAN BORROWED. eve strips the SAME suffix at the SAME (last)
82
+ * occurrence in `normalizeTeamsContinuationAddress`, but eve does not re-export that function from
83
+ * `eve/channels/teams`: it lives behind eve's private `#public/channels/teams/api.js` specifier and
84
+ * eve's package `exports` map admits no deep import. Measured against eve 0.38.3 on 2026-08-15 — the
85
+ * barrel re-exports eleven names from `api.js` and this is not one of them. Re-check on an eve bump;
86
+ * if eve exports it, delete this and call eve's.
87
+ *
88
+ * TWO READERS, TWO DIFFERENT USES, both deliberate:
89
+ * • the roster probe (`teams-roster.ts`) uses it as a CACHE KEY ONLY. The id it sends on the wire
90
+ * stays exactly as the caller passed it, because that is the shape the 2026-08-15 live-tenant
91
+ * probe measured.
92
+ * • this module stores it as the TEAM address, because a team's roster is a property of the
93
+ * channel and not of one reply chain.
94
+ */
95
+ export function teamsChannelGrainConversationId(conversationId) {
96
+ const i = conversationId.lastIndexOf(TEAMS_REPLY_CHAIN_SUFFIX);
97
+ return i < 0 ? conversationId : conversationId.slice(0, i);
98
+ }
99
+ /**
100
+ * The REPLY-CHAIN ROOT message id inside a channel conversation id, or undefined when the id names
101
+ * no reply chain (a personal or group chat, or a channel id already at channel grain).
102
+ *
103
+ * The twin of {@link teamsChannelGrainConversationId}: one splits the id at the suffix and keeps the
104
+ * left half, this one keeps the right. They live together so the suffix is spelled ONCE — a second
105
+ * spelling elsewhere is how the two halves drift apart. `channels/teams-read.ts` reads this to give
106
+ * `read_thread` its Graph `.../messages/{root}/replies` id (OPE-929).
107
+ */
108
+ export function teamsReplyChainRoot(conversationId) {
109
+ const i = conversationId.lastIndexOf(TEAMS_REPLY_CHAIN_SUFFIX);
110
+ if (i < 0)
111
+ return undefined;
112
+ const root = conversationId.slice(i + TEAMS_REPLY_CHAIN_SUFFIX.length);
113
+ return root.length > 0 ? root : undefined;
114
+ }
115
+ function nonEmpty(v) {
116
+ return typeof v === "string" && v.trim().length > 0 ? v.trim() : undefined;
117
+ }
118
+ /**
119
+ * PURE: the rows one inbound activity should write — zero, one or two. Separated from the write so
120
+ * the whole decision (which species, which key, which conversation id, and every refusal) is
121
+ * unit-testable without a store, a network or an eve activity.
122
+ *
123
+ * REFUSES, NEVER REPAIRS on the shared preconditions: a missing tenant, a missing conversation, or an
124
+ * untrusted `serviceUrl` yields NO row of ANY species. Past that gate, the `conversation` row
125
+ * (OPE-899) is scope-agnostic and always included; the `personal`/`team` rows stay scope-selective —
126
+ * a blank author on a personal activity or a channel activity with no team id costs only ITS OWN row,
127
+ * never the conversation one. A half-written `personal`/`team` address is worse than none (the DM arm
128
+ * would treat it as "I can reach this person" and then fail at the transport); the `conversation` row
129
+ * carries no such risk — its one reader (`teams-message-ops.ts`) already holds the exact id it asks
130
+ * for, so there is nothing for a wrong key to misdirect.
131
+ */
132
+ export function teamsAddressRowsFor(input, at = new Date()) {
133
+ const serviceUrl = nonEmpty(input.serviceUrl);
134
+ const tenantId = nonEmpty(input.tenantId);
135
+ const conversationId = nonEmpty(input.conversationId);
136
+ if (!serviceUrl || !tenantId || !conversationId)
137
+ return [];
138
+ // WRITE-TIME half of the double allowlist — see this file's header.
139
+ if (!isTrustedTeamsServiceUrl(serviceUrl))
140
+ return [];
141
+ const updatedAt = at.toISOString();
142
+ const rows = [
143
+ // The `conversation` row (OPE-899) — EVERY scope, at the EXACT id the activity arrived on. This
144
+ // is what lets a stop acknowledgement resolve a `serviceUrl` from the bare conversation id
145
+ // `postSurfaceText` is handed (see the header's third species).
146
+ {
147
+ platform: TEAMS_ADDRESS_PLATFORM,
148
+ kind: TEAMS_CONVERSATION_ADDRESS_KIND,
149
+ refKey: conversationId,
150
+ tenant: tenantId,
151
+ address: { serviceUrl, conversationId },
152
+ updatedAt,
153
+ },
154
+ ];
155
+ // ─── THE CHANNEL-GRAIN TWIN (OPE-1093) ────────────────────────────────────────────────────────
156
+ //
157
+ // WHY THIS EXISTS. Every live Teams CHANNEL activity carries a `;messageid=<root>` suffix, so the
158
+ // `conversation` row above and the `conversation-key` index row below only ever hold the REPLY-CHAIN
159
+ // grain. The domain roster, the surface key and a resource-grant address all name the CHANNEL grain.
160
+ // The two sets were disjoint: the only container address the fence admits was the one the transport
161
+ // could never restore, so EVERY Teams container grant refused. Measured on the live fixture tenant,
162
+ // deploy #128, 2026-08-24. This pair closes the join — the address row makes the channel itself
163
+ // postable, and the index row makes the roster spelling restore to it.
164
+ //
165
+ // A POST AT THIS GRAIN STARTS A NEW THREAD in the channel, which is what a container grant means:
166
+ // the grant names the channel, never whichever reply chain the deployment last saw traffic in.
167
+ //
168
+ // IT REACHES NO FURTHER THAN THE CHANNEL THE ACTIVITY CAME FROM. The channel grain is DERIVED from
169
+ // an id this deployment already holds as a `conversation` address, and it is stored under the same
170
+ // `serviceUrl`, from the same activity, past the same trusted-host gate. On a channel activity that
171
+ // names a team, the `team` row below already stores this exact conversation id; this pair makes the
172
+ // same place reachable by conversation id rather than by team id.
173
+ //
174
+ // THE TEST IS THE IDS, NOT THE SCOPE. A personal or group chat id carries no reply-chain suffix, so
175
+ // the channel grain equals the exact id and this block writes nothing — the rows above already are
176
+ // the channel-grain rows. The same holds for a channel activity that ever arrives at channel grain.
177
+ const channelGrain = teamsChannelGrainConversationId(conversationId);
178
+ if (channelGrain.length > 0 && channelGrain !== conversationId) {
179
+ rows.push({
180
+ platform: TEAMS_ADDRESS_PLATFORM,
181
+ kind: TEAMS_CONVERSATION_ADDRESS_KIND,
182
+ refKey: channelGrain,
183
+ tenant: tenantId,
184
+ address: { serviceUrl, conversationId: channelGrain },
185
+ updatedAt,
186
+ });
187
+ }
188
+ if (input.scope === "personal") {
189
+ const userId = nonEmpty(input.userId);
190
+ if (userId) {
191
+ rows.push({
192
+ platform: TEAMS_ADDRESS_PLATFORM,
193
+ kind: TEAMS_PERSONAL_ADDRESS_KIND,
194
+ refKey: userId,
195
+ tenant: tenantId,
196
+ address: { serviceUrl, conversationId },
197
+ updatedAt,
198
+ });
199
+ }
200
+ }
201
+ else if (input.scope === "channel") {
202
+ const teamId = nonEmpty(input.teamId);
203
+ if (teamId) {
204
+ rows.push({
205
+ platform: TEAMS_ADDRESS_PLATFORM,
206
+ kind: TEAMS_TEAM_ADDRESS_KIND,
207
+ refKey: teamId,
208
+ tenant: tenantId,
209
+ // The CHANNEL grain, never the reply chain the activity arrived on.
210
+ address: { serviceUrl, conversationId: teamsChannelGrainConversationId(conversationId) },
211
+ updatedAt,
212
+ });
213
+ }
214
+ }
215
+ // groupChat, "unknown", anything unrecognised — no `personal`/`team` row, but the `conversation`
216
+ // row above still stands. See the header.
217
+ // THE ID-RESTORATION INDEX (OPE-1078, `teams-id-restore.ts`) — the spelling→raw rows that let a
218
+ // resource-grant address reach these transports at all. Two properties are deliberate:
219
+ // • they sit BELOW the shared gate, so an untrusted `serviceUrl` still records NOTHING of ANY
220
+ // species. One gate, not two.
221
+ // • the PERSON row is scope-agnostic, unlike the `personal` address row above. An index row
222
+ // claims no reachability — it is a spelling, not an address — so the "a half address is worse
223
+ // than none" rule that makes `personal` selective does not apply to it.
224
+ // `teamsIdIndexRow` answers undefined when there is nothing to index (see its own doc).
225
+ const conversationKey = teamsIdIndexRow(TEAMS_CONVERSATION_KEY_KIND, conversationId, tenantId, updatedAt);
226
+ if (conversationKey)
227
+ rows.push(conversationKey);
228
+ // The CHANNEL-GRAIN index row — the half of the OPE-1093 twin that a granted address actually
229
+ // reads. `restoreTeamsConversationId` turns the roster spelling into this raw id, and the
230
+ // channel-grain ADDRESS row above is what that raw id then resolves to.
231
+ if (channelGrain.length > 0 && channelGrain !== conversationId) {
232
+ const channelKey = teamsIdIndexRow(TEAMS_CONVERSATION_KEY_KIND, channelGrain, tenantId, updatedAt);
233
+ if (channelKey)
234
+ rows.push(channelKey);
235
+ }
236
+ const authorId = nonEmpty(input.userId);
237
+ if (authorId) {
238
+ const personKey = teamsIdIndexRow(TEAMS_PERSON_KEY_KIND, authorId, tenantId, updatedAt);
239
+ if (personKey)
240
+ rows.push(personKey);
241
+ }
242
+ return rows;
243
+ }
244
+ function log(msg) {
245
+ console.log(`[open-everywhere:teams-address] ${msg}`);
246
+ }
247
+ /**
248
+ * Record what one inbound activity says about where we can reach its author or its team.
249
+ *
250
+ * FAIL-SOFT, TOTALLY. A store outage, a refused write, a malformed activity — every one of them is a
251
+ * logged line and nothing else. This runs on the ingest path behind the Bot Framework's 200 ack, and
252
+ * a proactive address that was not captured is a future delivery that does not happen; it is never a
253
+ * reason to take a live wake off the air.
254
+ */
255
+ export async function recordTeamsConversationReference(input) {
256
+ try {
257
+ const rows = teamsAddressRowsFor(input);
258
+ if (rows.length === 0)
259
+ return;
260
+ const org = deploymentOrg();
261
+ const store = getOperationalStore();
262
+ for (const row of rows)
263
+ await store.recordProactiveAddress(org, row);
264
+ }
265
+ catch (err) {
266
+ log(`could not record the conversation reference: ${err?.message ?? String(err)}`);
267
+ }
268
+ }
269
+ /** One stored row as a usable reference, or undefined. The READ half of the double allowlist, plus
270
+ * the non-empty checks that keep a partial row from reaching a caller as an address. */
271
+ function refFrom(row) {
272
+ if (!row)
273
+ return undefined;
274
+ const serviceUrl = nonEmpty(row.address["serviceUrl"]);
275
+ const conversationId = nonEmpty(row.address["conversationId"]);
276
+ const tenantId = nonEmpty(row.tenant);
277
+ if (!serviceUrl || !conversationId || !tenantId)
278
+ return undefined;
279
+ if (!isTrustedTeamsServiceUrl(serviceUrl))
280
+ return undefined;
281
+ return { serviceUrl, conversationId, tenantId };
282
+ }
283
+ /**
284
+ * The 1:1 chat reference for one Bot Framework user id, or undefined when this deployment has never
285
+ * had a personal chat with them. Undefined is a REAL answer — the DM arm's honest "I have no way to
286
+ * reach this person" — never a reason to fall back to some other conversation.
287
+ */
288
+ export async function teamsPersonalConversationRef(principalId) {
289
+ const id = nonEmpty(principalId);
290
+ if (!id)
291
+ return undefined;
292
+ try {
293
+ const row = await getOperationalStore().proactiveAddress(deploymentOrg(), TEAMS_ADDRESS_PLATFORM, TEAMS_PERSONAL_ADDRESS_KIND, id);
294
+ return refFrom(row);
295
+ }
296
+ catch (err) {
297
+ log(`could not read the personal conversation reference: ${err?.message ?? String(err)}`);
298
+ return undefined;
299
+ }
300
+ }
301
+ /**
302
+ * The reference for one EXACT conversation id (OPE-899) — the reply chain a channel turn anchored
303
+ * on, a group chat, or a 1:1, at whatever grain the caller's id already is. Undefined when this
304
+ * deployment has never seen an inbound activity on that id. This is the ONE lookup
305
+ * `teams-message-ops.ts` uses to turn a bare `channelId` (core's SurfaceMessageOps contract, which
306
+ * carries no `serviceUrl`) back into something postable.
307
+ */
308
+ export async function teamsConversationRef(conversationId) {
309
+ const id = nonEmpty(conversationId);
310
+ if (!id)
311
+ return undefined;
312
+ try {
313
+ const row = await getOperationalStore().proactiveAddress(deploymentOrg(), TEAMS_ADDRESS_PLATFORM, TEAMS_CONVERSATION_ADDRESS_KIND, id);
314
+ return refFrom(row);
315
+ }
316
+ catch (err) {
317
+ log(`could not read the conversation reference: ${err?.message ?? String(err)}`);
318
+ return undefined;
319
+ }
320
+ }
321
+ /** Persist a 1:1 chat reference the DM arm has just created, so the next delivery to that person is
322
+ * one call instead of two. Fail-soft: a failed write costs a repeated create, never a failed send. */
323
+ export async function rememberTeamsPersonalConversation(principalId, ref) {
324
+ await recordTeamsConversationReference({
325
+ scope: "personal",
326
+ userId: principalId,
327
+ tenantId: ref.tenantId,
328
+ conversationId: ref.conversationId,
329
+ serviceUrl: ref.serviceUrl,
330
+ });
331
+ }
332
+ /** Every team reference this deployment holds, most recently seen first and bounded by
333
+ * {@link TEAMS_TEAM_ADDRESS_READ_LIMIT}. Rows that fail the read-time checks are dropped. */
334
+ export async function teamsTeamConversationRefs() {
335
+ try {
336
+ const rows = await getOperationalStore().proactiveAddresses(deploymentOrg(), TEAMS_ADDRESS_PLATFORM, TEAMS_TEAM_ADDRESS_KIND, TEAMS_TEAM_ADDRESS_READ_LIMIT);
337
+ return rows.map(refFrom).filter((r) => r !== undefined);
338
+ }
339
+ catch (err) {
340
+ log(`could not read the team conversation references: ${err?.message ?? String(err)}`);
341
+ return [];
342
+ }
343
+ }
344
+ /**
345
+ * The (region, tenant) this deployment can open a NEW 1:1 chat in, when it has no personal reference
346
+ * for somebody yet — read off the team references it does hold.
347
+ *
348
+ * 🔴 ONE TENANT OR NOTHING. Opening a conversation names a tenant in `channelData`, and picking one
349
+ * of several would be a guess about which organisation a bare `29:` id belongs to. A wrong guess is
350
+ * not a failed send — Microsoft would refuse it, or worse, resolve a different person. So two
351
+ * disagreeing tenants yield `undefined` and the arm refuses out loud, which is the same posture
352
+ * `soleAdmittedTenantFromConfig` takes in core.
353
+ *
354
+ * `conversationId` on the returned reference is the TEAM's conversation and must never be posted
355
+ * into — the caller uses only `serviceUrl` and `tenantId`. That is why this returns the narrow pair
356
+ * rather than a {@link TeamsConversationRef}.
357
+ */
358
+ export async function teamsSoleTenantRegion() {
359
+ const refs = await teamsTeamConversationRefs();
360
+ if (refs.length === 0)
361
+ return undefined;
362
+ const tenants = new Set(refs.map((r) => r.tenantId));
363
+ if (tenants.size !== 1)
364
+ return undefined;
365
+ const newest = refs[0]; // proactiveAddresses orders newest first
366
+ return { serviceUrl: newest.serviceUrl, tenantId: newest.tenantId };
367
+ }
368
+ //# sourceMappingURL=teams-conversation-ref.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"teams-conversation-ref.js","sourceRoot":"","sources":["../../../agent/channels/teams-conversation-ref.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,kEAAkE;AAClE,EAAE;AACF,kGAAkG;AAClG,mGAAmG;AACnG,8FAA8F;AAC9F,qGAAqG;AACrG,iGAAiG;AACjG,qGAAqG;AACrG,oBAAoB;AACpB,EAAE;AACF,yEAAyE;AACzE,EAAE;AACF,2FAA2F;AAC3F,gGAAgG;AAChG,4FAA4F;AAC5F,iGAAiG;AACjG,mGAAmG;AACnG,2DAA2D;AAC3D,sGAAsG;AACtG,qGAAqG;AACrG,qBAAqB;AACrB,+FAA+F;AAC/F,kGAAkG;AAClG,oGAAoG;AACpG,iGAAiG;AACjG,iGAAiG;AACjG,sGAAsG;AACtG,sGAAsG;AACtG,iGAAiG;AACjG,mFAAmF;AACnF,gGAAgG;AAChG,oGAAoG;AACpG,kGAAkG;AAClG,qGAAqG;AACrG,EAAE;AACF,gGAAgG;AAChG,kGAAkG;AAClG,oGAAoG;AACpG,oGAAoG;AACpG,sFAAsF;AACtF,EAAE;AACF,oGAAoG;AACpG,oGAAoG;AACpG,qGAAqG;AACrG,sGAAsG;AACtG,uEAAuE;AACvE,EAAE;AACF,kGAAkG;AAClG,qGAAqG;AACrG,oGAAoG;AACpG,EAAE;AACF,oGAAoG;AACpG,iGAAiG;AACjG,iGAAiG;AACjG,+FAA+F;AAC/F,iGAAiG;AAEjG,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAyB,MAAM,4CAA4C,CAAC;AACvH,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,2BAA2B,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAE5G,0DAA0D;AAC1D,MAAM,CAAC,MAAM,sBAAsB,GAAG,OAAO,CAAC;AAE9C,qFAAqF;AACrF,MAAM,CAAC,MAAM,2BAA2B,GAAG,UAAU,CAAC;AAEtD,mEAAmE;AACnE,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,CAAC;AAE9C;oEACoE;AACpE,MAAM,+BAA+B,GAAG,cAAc,CAAC;AAEvD;;+DAE+D;AAC/D,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,CAAC;AAE/C;0DAC0D;AAC1D,MAAM,wBAAwB,GAAG,aAAa,CAAC;AAE/C;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,+BAA+B,CAAC,cAAsB;IACpE,MAAM,CAAC,GAAG,cAAc,CAAC,WAAW,CAAC,wBAAwB,CAAC,CAAC;IAC/D,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAAC,cAAsB;IACxD,MAAM,CAAC,GAAG,cAAc,CAAC,WAAW,CAAC,wBAAwB,CAAC,CAAC;IAC/D,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IAC5B,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC,GAAG,wBAAwB,CAAC,MAAM,CAAC,CAAC;IACvE,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAC5C,CAAC;AAwBD,SAAS,QAAQ,CAAC,CAAU;IAC1B,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAC7E,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAA4B,EAAE,EAAE,GAAS,IAAI,IAAI,EAAE;IACrF,MAAM,UAAU,GAAG,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC1C,MAAM,cAAc,GAAG,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IACtD,IAAI,CAAC,UAAU,IAAI,CAAC,QAAQ,IAAI,CAAC,cAAc;QAAE,OAAO,EAAE,CAAC;IAC3D,oEAAoE;IACpE,IAAI,CAAC,wBAAwB,CAAC,UAAU,CAAC;QAAE,OAAO,EAAE,CAAC;IACrD,MAAM,SAAS,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC;IAEnC,MAAM,IAAI,GAAuB;QAC/B,gGAAgG;QAChG,2FAA2F;QAC3F,gEAAgE;QAChE;YACE,QAAQ,EAAE,sBAAsB;YAChC,IAAI,EAAE,+BAA+B;YACrC,MAAM,EAAE,cAAc;YACtB,MAAM,EAAE,QAAQ;YAChB,OAAO,EAAE,EAAE,UAAU,EAAE,cAAc,EAAE;YACvC,SAAS;SACV;KACF,CAAC;IAEF,iGAAiG;IACjG,EAAE;IACF,kGAAkG;IAClG,qGAAqG;IACrG,qGAAqG;IACrG,oGAAoG;IACpG,oGAAoG;IACpG,gGAAgG;IAChG,uEAAuE;IACvE,EAAE;IACF,kGAAkG;IAClG,+FAA+F;IAC/F,EAAE;IACF,mGAAmG;IACnG,mGAAmG;IACnG,oGAAoG;IACpG,oGAAoG;IACpG,kEAAkE;IAClE,EAAE;IACF,oGAAoG;IACpG,mGAAmG;IACnG,oGAAoG;IACpG,MAAM,YAAY,GAAG,+BAA+B,CAAC,cAAc,CAAC,CAAC;IACrE,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,IAAI,YAAY,KAAK,cAAc,EAAE,CAAC;QAC/D,IAAI,CAAC,IAAI,CAAC;YACR,QAAQ,EAAE,sBAAsB;YAChC,IAAI,EAAE,+BAA+B;YACrC,MAAM,EAAE,YAAY;YACpB,MAAM,EAAE,QAAQ;YAChB,OAAO,EAAE,EAAE,UAAU,EAAE,cAAc,EAAE,YAAY,EAAE;YACrD,SAAS;SACV,CAAC,CAAC;IACL,CAAC;IAED,IAAI,KAAK,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACtC,IAAI,MAAM,EAAE,CAAC;YACX,IAAI,CAAC,IAAI,CAAC;gBACR,QAAQ,EAAE,sBAAsB;gBAChC,IAAI,EAAE,2BAA2B;gBACjC,MAAM,EAAE,MAAM;gBACd,MAAM,EAAE,QAAQ;gBAChB,OAAO,EAAE,EAAE,UAAU,EAAE,cAAc,EAAE;gBACvC,SAAS;aACV,CAAC,CAAC;QACL,CAAC;IACH,CAAC;SAAM,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QACrC,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACtC,IAAI,MAAM,EAAE,CAAC;YACX,IAAI,CAAC,IAAI,CAAC;gBACR,QAAQ,EAAE,sBAAsB;gBAChC,IAAI,EAAE,uBAAuB;gBAC7B,MAAM,EAAE,MAAM;gBACd,MAAM,EAAE,QAAQ;gBAChB,oEAAoE;gBACpE,OAAO,EAAE,EAAE,UAAU,EAAE,cAAc,EAAE,+BAA+B,CAAC,cAAc,CAAC,EAAE;gBACxF,SAAS;aACV,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,iGAAiG;IACjG,0CAA0C;IAE1C,gGAAgG;IAChG,uFAAuF;IACvF,gGAAgG;IAChG,kCAAkC;IAClC,8FAA8F;IAC9F,kGAAkG;IAClG,4EAA4E;IAC5E,wFAAwF;IACxF,MAAM,eAAe,GAAG,eAAe,CAAC,2BAA2B,EAAE,cAAc,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;IAC1G,IAAI,eAAe;QAAE,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAChD,8FAA8F;IAC9F,0FAA0F;IAC1F,wEAAwE;IACxE,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,IAAI,YAAY,KAAK,cAAc,EAAE,CAAC;QAC/D,MAAM,UAAU,GAAG,eAAe,CAAC,2BAA2B,EAAE,YAAY,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;QACnG,IAAI,UAAU;YAAE,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACxC,CAAC;IACD,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,SAAS,GAAG,eAAe,CAAC,qBAAqB,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;QACxF,IAAI,SAAS;YAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACtC,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,GAAG,CAAC,GAAW;IACtB,OAAO,CAAC,GAAG,CAAC,mCAAmC,GAAG,EAAE,CAAC,CAAC;AACxD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,gCAAgC,CAAC,KAA4B;IACjF,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC9B,MAAM,GAAG,GAAG,aAAa,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,mBAAmB,EAAE,CAAC;QACpC,KAAK,MAAM,GAAG,IAAI,IAAI;YAAE,MAAM,KAAK,CAAC,sBAAsB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACvE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,gDAAiD,GAAa,EAAE,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChG,CAAC;AACH,CAAC;AAED;yFACyF;AACzF,SAAS,OAAO,CAAC,GAAiC;IAChD,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IAC3B,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC;IACvD,MAAM,cAAc,GAAG,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAC/D,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACtC,IAAI,CAAC,UAAU,IAAI,CAAC,cAAc,IAAI,CAAC,QAAQ;QAAE,OAAO,SAAS,CAAC;IAClE,IAAI,CAAC,wBAAwB,CAAC,UAAU,CAAC;QAAE,OAAO,SAAS,CAAC;IAC5D,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC;AAClD,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAAC,WAAmB;IACpE,MAAM,EAAE,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE;QAAE,OAAO,SAAS,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,mBAAmB,EAAE,CAAC,gBAAgB,CAAC,aAAa,EAAE,EAAE,sBAAsB,EAAE,2BAA2B,EAAE,EAAE,CAAC,CAAC;QACnI,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC;IACtB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,uDAAwD,GAAa,EAAE,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACrG,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,cAAsB;IAC/D,MAAM,EAAE,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC;IACpC,IAAI,CAAC,EAAE;QAAE,OAAO,SAAS,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,mBAAmB,EAAE,CAAC,gBAAgB,CAAC,aAAa,EAAE,EAAE,sBAAsB,EAAE,+BAA+B,EAAE,EAAE,CAAC,CAAC;QACvI,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC;IACtB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,8CAA+C,GAAa,EAAE,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5F,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;uGACuG;AACvG,MAAM,CAAC,KAAK,UAAU,iCAAiC,CAAC,WAAmB,EAAE,GAAyB;IACpG,MAAM,gCAAgC,CAAC;QACrC,KAAK,EAAE,UAAU;QACjB,MAAM,EAAE,WAAW;QACnB,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,cAAc,EAAE,GAAG,CAAC,cAAc;QAClC,UAAU,EAAE,GAAG,CAAC,UAAU;KAC3B,CAAC,CAAC;AACL,CAAC;AAED;8FAC8F;AAC9F,MAAM,CAAC,KAAK,UAAU,yBAAyB;IAC7C,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,mBAAmB,EAAE,CAAC,kBAAkB,CACzD,aAAa,EAAE,EACf,sBAAsB,EACtB,uBAAuB,EACvB,6BAA6B,CAC9B,CAAC;QACF,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAA6B,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;IACrF,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,oDAAqD,GAAa,EAAE,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAClG,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB;IACzC,MAAM,IAAI,GAAG,MAAM,yBAAyB,EAAE,CAAC;IAC/C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACxC,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;IACrD,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACzC,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC,CAAC,yCAAyC;IAClE,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;AACtE,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { type DirectoryCandidate } from "@open-everywhere/core/agent/adapter-api.js";
2
+ /**
3
+ * Every account in the teams this deployment has seen whose name, UPN, email or id matches `query`,
4
+ * as `{nativeId, name, handle}`. Fail-soft to `[]`; never throws.
5
+ *
6
+ * `tenant` is deliberately unused: Teams is not a tenant-scoped platform in core's principal-key
7
+ * sense, so core never passes one. The population comes from the stored team references instead —
8
+ * see the header.
9
+ */
10
+ export declare function fetchTeamsDirectory(query: string): Promise<readonly DirectoryCandidate[]>;
11
+ //# sourceMappingURL=teams-directory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"teams-directory.d.ts","sourceRoot":"","sources":["../../../agent/channels/teams-directory.ts"],"names":[],"mappings":"AA+CA,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,4CAA4C,CAAC;AAsCrF;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,kBAAkB,EAAE,CAAC,CAuB/F"}
@@ -0,0 +1,179 @@
1
+ // THE TEAMS ACCOUNT DIRECTORY — "who, in the teams this deployment has seen, is called that?"
2
+ // (OPE-887). The Teams arm of core's identity/account-directory.ts registry, and the third one
3
+ // written; the Slack arm (`fetchWorkspaceRoster`) and the Discord arm (`fetchGuildDirectory`) are the
4
+ // shapes this follows.
5
+ //
6
+ // WHAT IT IS FOR. The reverse-initiated identity link (core identity/link-flow.ts) lets a person NAME
7
+ // the other account instead of carrying a bearer code between two surfaces. Naming needs a lookup,
8
+ // and this is Teams'. Core owns the matching, the ranking and the cap (`rankDirectoryCandidates`,
9
+ // pure), so this arm may over-return.
10
+ //
11
+ // 🔴 IT IS THE BOT CONNECTOR ROSTER, NOT MICROSOFT GRAPH, AND THE REASON IS THE ID SHAPE. A Teams
12
+ // identity link keys on `nativeId` = the Bot Framework user id (`from.id`, a `29:` string —
13
+ // teams-identity.ts's `bindTeams` sets `principalId` from it). Graph's `/users` answers with the AAD
14
+ // object id and the UPN and has NO `29:` id at all, so a Graph-backed arm would hand back candidates
15
+ // that no link can key on and no DM can address — worse than no arm, because the code-transport
16
+ // fallback would stop firing. `GET {serviceUrl}/v3/conversations/{id}/pagedmembers` returns the `29:`
17
+ // id alongside the name and the UPN, which is the right shape by construction.
18
+ //
19
+ // 🔴 A DIRECTORY HIT IS NOT EVIDENCE OF ANYTHING. It says an account with that name is in one of these
20
+ // teams — nothing about whose it is. The proof in this flow is the CONSENT the named account gives in
21
+ // its own DM, which is why this arm is useless without `teamsPostArm.deliverDirectMessage` and why
22
+ // core requires both before it offers the transport.
23
+ //
24
+ // THREE DIVERGENCES FROM THE DISCORD ARM, all forced by the platform:
25
+ //
26
+ // 1. THERE IS NO SERVER-SIDE SEARCH. Discord has `GET /guilds/{id}/members/search`; the Bot
27
+ // Connector has only a paged roster. So this arm WALKS pages and matches locally, the way the
28
+ // Slack arm does, and both bounds below exist to keep that walk inside core's 8-second arm
29
+ // timeout.
30
+ // 2. THE POPULATION COMES FROM CAPTURED STATE, NOT CONFIG. Discord fences on
31
+ // `admittedTenantsFromConfig` because a Discord bot is in every guild it was ever invited to,
32
+ // including unrelated servers. A Teams team reference exists ONLY because a JWT-verified activity
33
+ // from that team reached this deployment's own front and was accepted, so the capture IS the
34
+ // admission and a second config fence would only make the arm dead on a deployment that declares
35
+ // no Teams domains.
36
+ // 3. IT NEEDS NO PRIVILEGED PERMISSION. Discord's member endpoints need the SERVER MEMBERS INTENT;
37
+ // the Connector roster rides the bot's own client-credentials token, exactly as the guest probe
38
+ // in teams-roster.ts does.
39
+ //
40
+ // FAIL-SOFT PER TEAM. A refused, failed, hung or unreadable team contributes nothing and the other
41
+ // teams still answer. Nothing here throws: core's resolver catches, but an arm that leaned on that
42
+ // would lose every OTHER team's rows to one team's 403.
43
+ //
44
+ // BOTS ARE DROPPED, by ID SHAPE rather than by a flag. A Teams user id begins `29:` and a bot id
45
+ // begins `28:`; a link is a statement about a HUMAN, and the consent step would DM a bot that can
46
+ // never answer.
47
+ import { callTeamsConnectorApi } from "eve/channels/teams";
48
+ import { teamsTeamConversationRefs } from "./teams-conversation-ref.js";
49
+ /** How many roster pages one team may be walked for. Four pages of {@link TEAMS_ROSTER_PAGE_SIZE}
50
+ * cover a 2000-person team; past that a person narrows their query or uses the code transport. The
51
+ * bound is about core's 8-second arm timeout, not about correctness. */
52
+ const TEAMS_DIRECTORY_MAX_PAGES = 4;
53
+ /** Members per roster page. The Connector's own documented ceiling for `pagedmembers`. */
54
+ const TEAMS_ROSTER_PAGE_SIZE = 500;
55
+ /** How many matches one TEAM may contribute. Core caps the ANSWER at `DIRECTORY_MAX_CANDIDATES` (8)
56
+ * after ranking across every team, so this is deliberately larger: a first name that matches twelve
57
+ * people in one team must not lose the right person to an arbitrary truncation before core ranks. */
58
+ const TEAMS_DIRECTORY_MAX_PER_TEAM = 25;
59
+ function log(msg) {
60
+ console.log(`[open-everywhere:teams-directory] ${msg}`);
61
+ }
62
+ /** The same normalization core's matcher applies, so a local page filter and core's final rank agree
63
+ * about what "matches" means (`@tommy` and `tommy` are one query to a person). */
64
+ function normalize(raw) {
65
+ return raw.trim().replace(/^@/u, "").replace(/\s+/gu, " ").toLowerCase();
66
+ }
67
+ /**
68
+ * Every account in the teams this deployment has seen whose name, UPN, email or id matches `query`,
69
+ * as `{nativeId, name, handle}`. Fail-soft to `[]`; never throws.
70
+ *
71
+ * `tenant` is deliberately unused: Teams is not a tenant-scoped platform in core's principal-key
72
+ * sense, so core never passes one. The population comes from the stored team references instead —
73
+ * see the header.
74
+ */
75
+ export async function fetchTeamsDirectory(query) {
76
+ const q = normalize(query);
77
+ if (q.length === 0)
78
+ return [];
79
+ const teams = await teamsTeamConversationRefs();
80
+ if (teams.length === 0)
81
+ return [];
82
+ // 🔴 THE TEAMS ARE WALKED CONCURRENTLY, and that is about the ARM TIMEOUT rather than speed. Core
83
+ // gives an arm 8 seconds and answers `[]` when it overruns, so several teams walked one after
84
+ // another could turn a working directory into an empty one on a slow day — and the person would see
85
+ // the code fallback with no way to tell why. The fan-out is bounded by the read limit the reference
86
+ // store applies (TEAMS_TEAM_ADDRESS_READ_LIMIT), so "concurrent" here means at most eight walks.
87
+ const perTeam = await Promise.all(teams.map((team) => teamMembersMatching(team, q)));
88
+ // Deduped across teams: one person in three teams is ONE account, and three identical rows would
89
+ // crowd the other candidates out of core's cap. `perTeam` keeps the stored order (newest reference
90
+ // first), so which team's row wins is deterministic rather than a race.
91
+ const byId = new Map();
92
+ for (const members of perTeam) {
93
+ for (const member of members) {
94
+ if (!byId.has(member.nativeId))
95
+ byId.set(member.nativeId, member);
96
+ }
97
+ }
98
+ return [...byId.values()];
99
+ }
100
+ /** One team's matches for `q`, walked page by page. Never throws: a team that refuses contributes
101
+ * nothing and the caller moves on. */
102
+ async function teamMembersMatching(team, q) {
103
+ const out = [];
104
+ let continuationToken;
105
+ for (let page = 0; page < TEAMS_DIRECTORY_MAX_PAGES; page++) {
106
+ const body = await rosterPage(team, continuationToken);
107
+ if (!body)
108
+ return out; // a refused/failed page ends this team, keeping what it already found
109
+ for (const raw of body.members) {
110
+ const candidate = candidateFromMember(raw, q);
111
+ if (candidate)
112
+ out.push(candidate);
113
+ if (out.length >= TEAMS_DIRECTORY_MAX_PER_TEAM)
114
+ return out;
115
+ }
116
+ continuationToken = body.continuationToken;
117
+ if (!continuationToken)
118
+ return out; // the roster ended
119
+ }
120
+ return out;
121
+ }
122
+ /** One `pagedmembers` GET under the bot's own Connector credential, or undefined. The token is a
123
+ * SECRET — eve mints and holds it; nothing here logs a URL that could carry one. */
124
+ async function rosterPage(team, continuationToken) {
125
+ // 🔴 THE PATH NEEDS ITS LEADING SLASH — eve composes `trimTrailingSlash(serviceUrl) + path`, and a
126
+ // path written without it makes a malformed URL that answers non-2xx forever (the same trap
127
+ // teams-roster.ts's header states).
128
+ const params = new URLSearchParams({ pageSize: String(TEAMS_ROSTER_PAGE_SIZE) });
129
+ if (continuationToken)
130
+ params.set("continuationToken", continuationToken);
131
+ const path = `/v3/conversations/${encodeURIComponent(team.conversationId)}/pagedmembers?${params.toString()}`;
132
+ try {
133
+ const res = await callTeamsConnectorApi({ serviceUrl: team.serviceUrl, path, method: "GET" });
134
+ if (!res.ok) {
135
+ // A refusal is the one failure an operator can act on, and a silent empty answer reads as
136
+ // "nobody matched" — a different and misleading fact.
137
+ log(`the roster read did not answer (${res.status}) for conversation ${team.conversationId} — the account directory is unavailable there; linking falls back to the code flow.`);
138
+ return undefined;
139
+ }
140
+ const b = res.body;
141
+ if (!b || typeof b !== "object" || !Array.isArray(b.members))
142
+ return undefined;
143
+ return {
144
+ members: b.members,
145
+ ...(typeof b.continuationToken === "string" && b.continuationToken.length > 0 ? { continuationToken: b.continuationToken } : {}),
146
+ };
147
+ }
148
+ catch (err) {
149
+ log(`the roster read failed for conversation ${team.conversationId}: ${err?.message ?? String(err)}`);
150
+ return undefined;
151
+ }
152
+ }
153
+ /**
154
+ * One roster row as a candidate when it matches `q`, or undefined.
155
+ *
156
+ * `handle` is the UPN (or the email when Teams gave no UPN) — the unique, typeable string a person
157
+ * knows themselves by; `name` is the display name Teams shows in the room. Both are directory strings
158
+ * this arm does not trust: core sanitizes them and the tool fences them before a model reads either.
159
+ *
160
+ * The match is local and deliberately GENEROUS (substring over name, handle and id), because core
161
+ * re-ranks EXACT before PREFIX before contains. Over-returning costs a row in a capped list;
162
+ * under-returning loses the right person with no way to tell.
163
+ */
164
+ function candidateFromMember(member, q) {
165
+ const nativeId = typeof member.id === "string" ? member.id.trim() : "";
166
+ // A person's Bot Framework id begins `29:`. A `28:` id is a bot (this one included) and every other
167
+ // shape is something this arm does not understand — both are dropped rather than guessed at.
168
+ if (!nativeId.startsWith("29:"))
169
+ return undefined;
170
+ const name = typeof member.name === "string" ? member.name.trim() : "";
171
+ const upn = typeof member.userPrincipalName === "string" ? member.userPrincipalName.trim() : "";
172
+ const email = typeof member.email === "string" ? member.email.trim() : "";
173
+ const handle = upn || email;
174
+ const matched = normalize(nativeId) === q || [name, handle].some((f) => f.length > 0 && normalize(f).includes(q));
175
+ if (!matched)
176
+ return undefined;
177
+ return { nativeId, ...(name ? { name } : {}), ...(handle ? { handle } : {}) };
178
+ }
179
+ //# sourceMappingURL=teams-directory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"teams-directory.js","sourceRoot":"","sources":["../../../agent/channels/teams-directory.ts"],"names":[],"mappings":"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"}