@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.
- package/LICENSE +201 -0
- package/NOTICE +21 -0
- package/README.md +46 -2
- package/agent/channels/teams-bot-name.ts +162 -0
- package/agent/channels/teams-catch-up.ts +244 -0
- package/agent/channels/teams-connector.ts +183 -0
- package/agent/channels/teams-conversation-ref.ts +398 -0
- package/agent/channels/teams-directory.ts +193 -0
- package/agent/channels/teams-engagement.ts +391 -0
- package/agent/channels/teams-fire-eligibility.ts +152 -0
- package/agent/channels/teams-front.ts +1404 -0
- package/agent/channels/teams-graph-boundary.ts +178 -0
- package/agent/channels/teams-graph-team-id.ts +106 -0
- package/agent/channels/teams-id-restore.ts +131 -0
- package/agent/channels/teams-identity.ts +395 -0
- package/agent/channels/teams-input-response.ts +129 -0
- package/agent/channels/teams-message-ops.ts +79 -0
- package/agent/channels/teams-origin-codec.ts +104 -0
- package/agent/channels/teams-post-arm.ts +323 -0
- package/agent/channels/teams-reaction-feedback.ts +127 -0
- package/agent/channels/teams-read.ts +510 -0
- package/agent/channels/teams-roster.ts +136 -0
- package/agent/channels/teams-stop.ts +319 -0
- package/agent/channels/teams-supervisor.ts +638 -0
- package/agent/channels/teams.ts +270 -0
- package/agent/contributions.ts +31 -0
- package/agent/docs-generated.ts +15 -0
- package/agent/ops.ts +129 -0
- package/agent/provider-events.ts +69 -0
- package/agent/register.ts +219 -0
- package/agent/surface-parity.ts +112 -0
- package/dist/agent/channels/teams-bot-name.d.ts +36 -0
- package/dist/agent/channels/teams-bot-name.d.ts.map +1 -0
- package/dist/agent/channels/teams-bot-name.js +141 -0
- package/dist/agent/channels/teams-bot-name.js.map +1 -0
- package/dist/agent/channels/teams-catch-up.d.ts +53 -0
- package/dist/agent/channels/teams-catch-up.d.ts.map +1 -0
- package/dist/agent/channels/teams-catch-up.js +221 -0
- package/dist/agent/channels/teams-catch-up.js.map +1 -0
- package/dist/agent/channels/teams-connector.d.ts +57 -0
- package/dist/agent/channels/teams-connector.d.ts.map +1 -0
- package/dist/agent/channels/teams-connector.js +132 -0
- package/dist/agent/channels/teams-connector.js.map +1 -0
- package/dist/agent/channels/teams-conversation-ref.d.ts +123 -0
- package/dist/agent/channels/teams-conversation-ref.d.ts.map +1 -0
- package/dist/agent/channels/teams-conversation-ref.js +368 -0
- package/dist/agent/channels/teams-conversation-ref.js.map +1 -0
- package/dist/agent/channels/teams-directory.d.ts +11 -0
- package/dist/agent/channels/teams-directory.d.ts.map +1 -0
- package/dist/agent/channels/teams-directory.js +179 -0
- package/dist/agent/channels/teams-directory.js.map +1 -0
- package/dist/agent/channels/teams-engagement.d.ts +174 -0
- package/dist/agent/channels/teams-engagement.d.ts.map +1 -0
- package/dist/agent/channels/teams-engagement.js +336 -0
- package/dist/agent/channels/teams-engagement.js.map +1 -0
- package/dist/agent/channels/teams-fire-eligibility.d.ts +26 -0
- package/dist/agent/channels/teams-fire-eligibility.d.ts.map +1 -0
- package/dist/agent/channels/teams-fire-eligibility.js +126 -0
- package/dist/agent/channels/teams-fire-eligibility.js.map +1 -0
- package/dist/agent/channels/teams-front.d.ts +249 -0
- package/dist/agent/channels/teams-front.d.ts.map +1 -0
- package/dist/agent/channels/teams-front.js +1143 -0
- package/dist/agent/channels/teams-front.js.map +1 -0
- package/dist/agent/channels/teams-graph-boundary.d.ts +45 -0
- package/dist/agent/channels/teams-graph-boundary.d.ts.map +1 -0
- package/dist/agent/channels/teams-graph-boundary.js +168 -0
- package/dist/agent/channels/teams-graph-boundary.js.map +1 -0
- package/dist/agent/channels/teams-graph-team-id.d.ts +30 -0
- package/dist/agent/channels/teams-graph-team-id.d.ts.map +1 -0
- package/dist/agent/channels/teams-graph-team-id.js +89 -0
- package/dist/agent/channels/teams-graph-team-id.js.map +1 -0
- package/dist/agent/channels/teams-id-restore.d.ts +34 -0
- package/dist/agent/channels/teams-id-restore.d.ts.map +1 -0
- package/dist/agent/channels/teams-id-restore.js +123 -0
- package/dist/agent/channels/teams-id-restore.js.map +1 -0
- package/dist/agent/channels/teams-identity.d.ts +148 -0
- package/dist/agent/channels/teams-identity.d.ts.map +1 -0
- package/dist/agent/channels/teams-identity.js +336 -0
- package/dist/agent/channels/teams-identity.js.map +1 -0
- package/dist/agent/channels/teams-input-response.d.ts +23 -0
- package/dist/agent/channels/teams-input-response.d.ts.map +1 -0
- package/dist/agent/channels/teams-input-response.js +102 -0
- package/dist/agent/channels/teams-input-response.js.map +1 -0
- package/dist/agent/channels/teams-message-ops.d.ts +3 -0
- package/dist/agent/channels/teams-message-ops.d.ts.map +1 -0
- package/dist/agent/channels/teams-message-ops.js +72 -0
- package/dist/agent/channels/teams-message-ops.js.map +1 -0
- package/dist/agent/channels/teams-origin-codec.d.ts +16 -0
- package/dist/agent/channels/teams-origin-codec.d.ts.map +1 -0
- package/dist/agent/channels/teams-origin-codec.js +99 -0
- package/dist/agent/channels/teams-origin-codec.js.map +1 -0
- package/dist/agent/channels/teams-post-arm.d.ts +37 -0
- package/dist/agent/channels/teams-post-arm.d.ts.map +1 -0
- package/dist/agent/channels/teams-post-arm.js +280 -0
- package/dist/agent/channels/teams-post-arm.js.map +1 -0
- package/dist/agent/channels/teams-reaction-feedback.d.ts +22 -0
- package/dist/agent/channels/teams-reaction-feedback.d.ts.map +1 -0
- package/dist/agent/channels/teams-reaction-feedback.js +108 -0
- package/dist/agent/channels/teams-reaction-feedback.js.map +1 -0
- package/dist/agent/channels/teams-read.d.ts +127 -0
- package/dist/agent/channels/teams-read.d.ts.map +1 -0
- package/dist/agent/channels/teams-read.js +424 -0
- package/dist/agent/channels/teams-read.js.map +1 -0
- package/dist/agent/channels/teams-roster.d.ts +54 -0
- package/dist/agent/channels/teams-roster.d.ts.map +1 -0
- package/dist/agent/channels/teams-roster.js +121 -0
- package/dist/agent/channels/teams-roster.js.map +1 -0
- package/dist/agent/channels/teams-stop.d.ts +74 -0
- package/dist/agent/channels/teams-stop.d.ts.map +1 -0
- package/dist/agent/channels/teams-stop.js +252 -0
- package/dist/agent/channels/teams-stop.js.map +1 -0
- package/dist/agent/channels/teams-supervisor.d.ts +351 -0
- package/dist/agent/channels/teams-supervisor.d.ts.map +1 -0
- package/dist/agent/channels/teams-supervisor.js +366 -0
- package/dist/agent/channels/teams-supervisor.js.map +1 -0
- package/dist/agent/channels/teams.d.ts +5 -0
- package/dist/agent/channels/teams.d.ts.map +1 -0
- package/dist/agent/channels/teams.js +246 -0
- package/dist/agent/channels/teams.js.map +1 -0
- package/dist/agent/contributions.d.ts +12 -0
- package/dist/agent/contributions.d.ts.map +1 -0
- package/dist/agent/contributions.js +31 -0
- package/dist/agent/contributions.js.map +1 -0
- package/dist/agent/docs-generated.d.ts +3 -0
- package/dist/agent/docs-generated.d.ts.map +1 -0
- package/dist/agent/docs-generated.js +13 -0
- package/dist/agent/docs-generated.js.map +1 -0
- package/dist/agent/ops.d.ts +14 -0
- package/dist/agent/ops.d.ts.map +1 -0
- package/dist/agent/ops.js +122 -0
- package/dist/agent/ops.js.map +1 -0
- package/dist/agent/provider-events.d.ts +3 -0
- package/dist/agent/provider-events.d.ts.map +1 -0
- package/dist/agent/provider-events.js +67 -0
- package/dist/agent/provider-events.js.map +1 -0
- package/dist/agent/register.d.ts +16 -0
- package/dist/agent/register.d.ts.map +1 -0
- package/dist/agent/register.js +198 -0
- package/dist/agent/register.js.map +1 -0
- package/dist/agent/surface-parity.d.ts +3 -0
- package/dist/agent/surface-parity.d.ts.map +1 -0
- package/dist/agent/surface-parity.js +106 -0
- package/dist/agent/surface-parity.js.map +1 -0
- package/package.json +183 -4
- package/tsconfig.build.json +22 -0
- 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":"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"}
|
|
@@ -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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"}
|