@open-everywhere/provider-teams 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/approvals/teams-card.ts +188 -0
- package/agent/approvals/teams-flow.ts +467 -0
- package/agent/channels/teams-declared-target.ts +108 -0
- package/agent/channels/teams-front.ts +25 -2
- package/agent/docs-generated.ts +1 -1
- package/agent/register.ts +28 -1
- package/agent/surface-parity.ts +19 -11
- package/dist/agent/approvals/teams-card.d.ts +65 -0
- package/dist/agent/approvals/teams-card.d.ts.map +1 -0
- package/dist/agent/approvals/teams-card.js +134 -0
- package/dist/agent/approvals/teams-card.js.map +1 -0
- package/dist/agent/approvals/teams-flow.d.ts +144 -0
- package/dist/agent/approvals/teams-flow.d.ts.map +1 -0
- package/dist/agent/approvals/teams-flow.js +366 -0
- package/dist/agent/approvals/teams-flow.js.map +1 -0
- package/dist/agent/channels/teams-declared-target.d.ts +12 -0
- package/dist/agent/channels/teams-declared-target.d.ts.map +1 -0
- package/dist/agent/channels/teams-declared-target.js +96 -0
- package/dist/agent/channels/teams-declared-target.js.map +1 -0
- package/dist/agent/channels/teams-front.d.ts +5 -0
- package/dist/agent/channels/teams-front.d.ts.map +1 -1
- package/dist/agent/channels/teams-front.js +19 -2
- package/dist/agent/channels/teams-front.js.map +1 -1
- package/dist/agent/docs-generated.js +1 -1
- package/dist/agent/docs-generated.js.map +1 -1
- package/dist/agent/register.d.ts.map +1 -1
- package/dist/agent/register.js +26 -2
- package/dist/agent/register.js.map +1 -1
- package/dist/agent/surface-parity.d.ts.map +1 -1
- package/dist/agent/surface-parity.js +18 -11
- package/dist/agent/surface-parity.js.map +1 -1
- package/package.json +15 -2
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
// The Teams I/O half of the approval flow (OPE-2424) — the sibling of core's Slack wiring and the
|
|
2
|
+
// Discord bundle's discord-flow.ts, and it funnels through the SAME platform-neutral decide core
|
|
3
|
+
// (`resolveApprovalClick`). Nothing here decides: the CAS, the approver policy, the clicker gate, the
|
|
4
|
+
// halt arrow, the execute and every audit row stay in core. This file renders and transports.
|
|
5
|
+
//
|
|
6
|
+
// postTeamsApprovalCard() — the CardPoster this bundle registers. It renders the pure card
|
|
7
|
+
// (teams-card.ts) and sends it through the Bot Connector into the
|
|
8
|
+
// card's conversation. NEVER throws (CardPoster contract): a failure
|
|
9
|
+
// resolves undefined, and requestApproval expires the pending.
|
|
10
|
+
// postTeamsJudgeLiftNotice() — the JudgeLiftNoticePoster: the trace of an act the armed judge
|
|
11
|
+
// lifted, with the one "This should not have run" button. Same
|
|
12
|
+
// transport, and it is best-effort, because the act has already run.
|
|
13
|
+
// handleTeamsApprovalInvoke() — called by the front route (channels/teams-front.ts) for one
|
|
14
|
+
// VERIFIED `adaptiveCard/action` invoke that carries an OE approval
|
|
15
|
+
// payload. It owns the tap end to end and returns the invoke
|
|
16
|
+
// response body. NEVER throws: Teams shows an error over a tap whose
|
|
17
|
+
// invoke does not answer.
|
|
18
|
+
// teamsDecisionFeedback — the two decision-feedback legs core's sweep and session-grant
|
|
19
|
+
// release call for a Teams card (core approvals/flow.ts
|
|
20
|
+
// `registerDecisionFeedbackSurface`).
|
|
21
|
+
//
|
|
22
|
+
// 🔴 THE TAP IS AUTHENTICATED BEFORE IT REACHES THIS FILE. The front route verifies the Bot Framework
|
|
23
|
+
// JWT (eve's `verifyTeamsRequest`) on EVERY request and answers 401 before it parses the body, so an
|
|
24
|
+
// unsigned or forged invoke never reaches `handleTeamsApprovalInvoke`. It is the same verifier, on
|
|
25
|
+
// the same handler, that admits a message activity.
|
|
26
|
+
//
|
|
27
|
+
// 🔴 WHAT THE TOKEN PROVES, AND WHAT IT DOES NOT. The token proves that the Bot Framework service
|
|
28
|
+
// relayed the request to this app id. It does not bind the body, and it is the same token for EVERY
|
|
29
|
+
// channel on the bot registration (`teams-origin-codec.ts` records the same limit). So this file
|
|
30
|
+
// accepts a tap ONLY when the activity's own `channelId` is `msteams`: a Web Chat or Direct Line
|
|
31
|
+
// client sets `from` and `channelData` itself, and a tap from there must never reach the decide core
|
|
32
|
+
// as a Teams person. The clicker, the tenant and the room are then read off a Teams activity that
|
|
33
|
+
// Microsoft relayed; the card's data payload names only the org and the pending id. A tap with no
|
|
34
|
+
// tenant is refused rather than decided with the clicker gate switched off.
|
|
35
|
+
//
|
|
36
|
+
// WHY THE RESPONSE IS THE UI (Discord's shape, not Slack's): an `Action.Execute` tap is answered by
|
|
37
|
+
// the invoke response itself. A card body REPLACES the card (the decided render, no buttons), and a
|
|
38
|
+
// message body shows a note to the clicker alone (a refusal, with the card left live for someone who
|
|
39
|
+
// can decide it). The decision FEEDBACK — the fresh notice beside the card and the decision wake —
|
|
40
|
+
// runs under the route's `waitUntil` when there is one, because nothing in the response depends on it.
|
|
41
|
+
import { resolveApprovalClick, deliverDecisionFeedback, clickerFloorRefusalNotice, judgeContradictionNotice, getOperationalStore, surfaceKeySegment, } from "@open-everywhere/core/agent/adapter-api.js";
|
|
42
|
+
import { TEAMS_ADAPTIVE_CARD_ACTION_INVOKE, TEAMS_ADAPTIVE_CARD_CONTENT_TYPE, decidedTeamsCard, judgeLiftNoticeTeamsCard, parseTeamsApprovalData, pendingTeamsCard, teamsCardAttachment, } from "./teams-card.js";
|
|
43
|
+
import { sendTeamsActivityBackedOff, updateTeamsActivityBackedOff } from "../channels/teams-connector.js";
|
|
44
|
+
import { teamsChannelGrainConversationId, teamsConversationRef } from "../channels/teams-conversation-ref.js";
|
|
45
|
+
import { restoreTeamsConversationId } from "../channels/teams-id-restore.js";
|
|
46
|
+
import { isTrustedTeamsServiceUrl } from "../channels/teams-origin-codec.js";
|
|
47
|
+
import { resolveTeamsUserType, teamsServiceUrlRosterProbe } from "../channels/teams-roster.js";
|
|
48
|
+
function log(msg) {
|
|
49
|
+
console.log(`[open-everywhere:teams-approvals] ${msg}`);
|
|
50
|
+
}
|
|
51
|
+
async function liveResolveRef(channelId) {
|
|
52
|
+
return teamsConversationRef(await restoreTeamsConversationId(channelId));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The conversation reference a card's `channelId` resolves to, or undefined. The read half of the
|
|
56
|
+
* address store already refuses an untrusted `serviceUrl`; the check here is the same allowlist again,
|
|
57
|
+
* so an injected resolver cannot hand this file a host the store would refuse.
|
|
58
|
+
*/
|
|
59
|
+
async function cardConversation(channelId, deps) {
|
|
60
|
+
const ref = await (deps.resolveRef ?? liveResolveRef)(channelId);
|
|
61
|
+
return ref && isTrustedTeamsServiceUrl(ref.serviceUrl) ? ref : undefined;
|
|
62
|
+
}
|
|
63
|
+
// --- the posters ----------------------------------------------------------------------------------
|
|
64
|
+
/**
|
|
65
|
+
* Render the approval card and send it into `row.channelId` through the Bot Connector. Resolves the
|
|
66
|
+
* posted activity id, or undefined on ANY failure: no channel id, no stored conversation reference
|
|
67
|
+
* (this deployment never saw an inbound activity there), a missing Bot credential, a Connector error.
|
|
68
|
+
* NEVER throws.
|
|
69
|
+
*
|
|
70
|
+
* WHERE IT LANDS. `row.channelId` is the Teams CHANNEL id (bindTeams' `target.channelId`), so the card
|
|
71
|
+
* posts as a NEW post in the channel, not inside the reply chain that asked. bindTeams sets no
|
|
72
|
+
* `threadTs` on purpose (about twenty consumers read that field as a Slack thread_ts), so the row
|
|
73
|
+
* carries no chain to thread under. The tool result names the card, and the decision notice threads
|
|
74
|
+
* under the card itself.
|
|
75
|
+
*/
|
|
76
|
+
export async function postTeamsApprovalCard(row, summaryLines, deps = {}) {
|
|
77
|
+
return postTeamsCardMessage(row, deps, "approval card", () => pendingTeamsCard(row, summaryLines));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* THE JUDGE-LIFT NOTICE POSTER: the room's trace of an act the armed judge lifted, with the overrule
|
|
81
|
+
* button. Best-effort by contract — the frozen args have already run, so a failed post is logged and
|
|
82
|
+
* resolves void. The audit rows (`approval.autoapproved`, `approval.judge.verdict`) land either way.
|
|
83
|
+
*/
|
|
84
|
+
export async function postTeamsJudgeLiftNotice(row, summaryLines, outcomeLine, deps = {}) {
|
|
85
|
+
await postTeamsCardMessage(row, deps, "judge-lift notice", () => judgeLiftNoticeTeamsCard(row, summaryLines, outcomeLine));
|
|
86
|
+
}
|
|
87
|
+
async function postTeamsCardMessage(row, deps, what, render) {
|
|
88
|
+
if (!row.channelId) {
|
|
89
|
+
log(`no channel id on the pending row — nothing to post the ${what} to`);
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const ref = await cardConversation(row.channelId, deps);
|
|
94
|
+
if (!ref) {
|
|
95
|
+
log(`no conversation reference for the ${what}'s channel — this deployment has never seen an inbound activity there`);
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
const posted = await (deps.send ?? sendTeamsActivityBackedOff)({
|
|
99
|
+
serviceUrl: ref.serviceUrl,
|
|
100
|
+
conversationId: ref.conversationId,
|
|
101
|
+
body: { type: "message", attachments: [teamsCardAttachment(render())] },
|
|
102
|
+
});
|
|
103
|
+
return posted.id.length > 0 ? posted.id : undefined;
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
// Token-free: the Connector seam's errors carry a status and ids, never a bearer token.
|
|
107
|
+
log(`Teams ${what} post failed: ${err?.message ?? String(err)}`);
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// --- the row-addressed feedback legs (the sweep and the session-grant release) -------------------
|
|
112
|
+
/**
|
|
113
|
+
* The decision notice for a row with NO click in hand — a TTL expiry or a session-grant release. It
|
|
114
|
+
* posts into the card's own conversation at the grain the card was sent to. It does not try to thread
|
|
115
|
+
* under the card: a reply-chain address needs a vendor id shape this file would have to guess, and the
|
|
116
|
+
* click path below threads exactly, off the invoke's own conversation id. Throws on failure; core's
|
|
117
|
+
* `deliverDecisionFeedback` swallows a notice failure.
|
|
118
|
+
*/
|
|
119
|
+
export async function postTeamsRowDecisionNotice(row, text, deps = {}) {
|
|
120
|
+
const ref = await cardConversation(row.channelId, deps);
|
|
121
|
+
if (!ref)
|
|
122
|
+
throw new Error("no Teams conversation reference for the card's channel");
|
|
123
|
+
return (deps.send ?? sendTeamsActivityBackedOff)({ serviceUrl: ref.serviceUrl, conversationId: ref.conversationId, body: { type: "message", text } });
|
|
124
|
+
}
|
|
125
|
+
/** The decision-wake target for a row with no click in hand. Null when the card's conversation has no
|
|
126
|
+
* stored reference or the row has no `postedTo`. The authority frozen at proposal still wins over it. */
|
|
127
|
+
export async function teamsRowWakeTarget(row, deps = {}) {
|
|
128
|
+
if (!row.postedTo)
|
|
129
|
+
return null;
|
|
130
|
+
try {
|
|
131
|
+
const ref = await cardConversation(row.channelId, deps);
|
|
132
|
+
if (!ref)
|
|
133
|
+
return null;
|
|
134
|
+
return { surfaceKey: row.postedTo, origin: { platform: "teams", serviceUrl: ref.serviceUrl, conversationId: ref.conversationId, tenantId: ref.tenantId } };
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/** The legs this bundle registers with core (`registerDecisionFeedbackSurface`). */
|
|
141
|
+
export const teamsDecisionFeedback = {
|
|
142
|
+
postNotice: (row, text) => postTeamsRowDecisionNotice(row, text),
|
|
143
|
+
wakeTarget: (row) => teamsRowWakeTarget(row),
|
|
144
|
+
};
|
|
145
|
+
function isObject(v) {
|
|
146
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
147
|
+
}
|
|
148
|
+
function nonEmpty(v) {
|
|
149
|
+
return typeof v === "string" && v.length > 0 ? v : undefined;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Probe one parsed activity for an OE approval tap: an `invoke` named `adaptiveCard/action` whose
|
|
153
|
+
* `value.action.data` carries the OE payload. Returns null for anything else — an eve HITL answer, a
|
|
154
|
+
* different invoke, a message — which the front forwards to the native channel untouched. NEVER throws.
|
|
155
|
+
*/
|
|
156
|
+
export function parseTeamsApprovalInvoke(activity) {
|
|
157
|
+
if (!isObject(activity) || activity["type"] !== "invoke")
|
|
158
|
+
return null;
|
|
159
|
+
const invoke = activity;
|
|
160
|
+
if (invoke.name !== TEAMS_ADAPTIVE_CARD_ACTION_INVOKE)
|
|
161
|
+
return null;
|
|
162
|
+
// The Teams channel only — see the header. Read off the raw activity: eve's parsed shape drops it.
|
|
163
|
+
const raw = isObject(invoke.raw) ? invoke.raw : activity;
|
|
164
|
+
if (raw["channelId"] !== "msteams")
|
|
165
|
+
return null;
|
|
166
|
+
const value = isObject(invoke.value) ? invoke.value : undefined;
|
|
167
|
+
const actionObj = value && isObject(value["action"]) ? value["action"] : undefined;
|
|
168
|
+
const action = parseTeamsApprovalData(actionObj?.["data"]);
|
|
169
|
+
if (!action)
|
|
170
|
+
return null;
|
|
171
|
+
const conversationId = nonEmpty(invoke.conversation?.id);
|
|
172
|
+
const serviceUrl = nonEmpty(invoke.serviceUrl);
|
|
173
|
+
if (!conversationId || !serviceUrl)
|
|
174
|
+
return null;
|
|
175
|
+
const clickerId = nonEmpty(invoke.from?.id);
|
|
176
|
+
const clickerName = nonEmpty(invoke.from?.name);
|
|
177
|
+
const tenantId = nonEmpty(invoke.tenantId) ?? nonEmpty(invoke.conversation?.tenantId);
|
|
178
|
+
const teamsChannelId = nonEmpty(invoke.teamsChannelId);
|
|
179
|
+
const replyToId = nonEmpty(invoke.replyToId);
|
|
180
|
+
return {
|
|
181
|
+
action,
|
|
182
|
+
conversationId,
|
|
183
|
+
serviceUrl,
|
|
184
|
+
...(clickerId ? { clickerId } : {}),
|
|
185
|
+
...(clickerName ? { clickerName } : {}),
|
|
186
|
+
...(tenantId ? { tenantId } : {}),
|
|
187
|
+
...(teamsChannelId ? { teamsChannelId } : {}),
|
|
188
|
+
...(replyToId ? { replyToId } : {}),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* The room the tap PHYSICALLY happened in, as `teams/<tenant>/<channel>` — core's clicker gate reads
|
|
193
|
+
* it (leg C compares its channel segment with the row's `channelId`). It comes from the relayed
|
|
194
|
+
* Teams activity: the channel id, else the channel grain of the conversation id. Undefined without a
|
|
195
|
+
* tenant or a room. 🔴 The handler REFUSES such a tap rather than passing no click context: core
|
|
196
|
+
* reserves "no click context" for the headless control plane, and it switches every clicker-gate leg
|
|
197
|
+
* off.
|
|
198
|
+
*
|
|
199
|
+
* TWO SPELLINGS OF ONE ROOM. A card the venue walk posted in its own room carries the RAW channel id;
|
|
200
|
+
* a card redirected to a domain's roster entry carries the roster's SURFACE-KEY spelling. Both
|
|
201
|
+
* spellings below are derived from the TAP's own channel id, so the row only chooses which spelling
|
|
202
|
+
* of the same click room to compare. `teams-id-restore.ts` states the bound: two raw ids that share a
|
|
203
|
+
* spelling are already one id to this deployment.
|
|
204
|
+
*/
|
|
205
|
+
export function teamsClickSurfaceKey(invoke, row) {
|
|
206
|
+
const room = invoke.teamsChannelId ?? teamsChannelGrainConversationId(invoke.conversationId);
|
|
207
|
+
if (!invoke.tenantId || !room)
|
|
208
|
+
return undefined;
|
|
209
|
+
const spelling = surfaceKeySegment(room);
|
|
210
|
+
const channel = row && row.channelId !== room && row.channelId === spelling ? spelling : room;
|
|
211
|
+
return `teams/${invoke.tenantId}/${channel}`;
|
|
212
|
+
}
|
|
213
|
+
const TEAMS_MESSAGE_RESPONSE_TYPE = "application/vnd.microsoft.activity.message";
|
|
214
|
+
function messageResponse(text) {
|
|
215
|
+
return { statusCode: 200, type: TEAMS_MESSAGE_RESPONSE_TYPE, value: text };
|
|
216
|
+
}
|
|
217
|
+
function cardResponse(card) {
|
|
218
|
+
return { statusCode: 200, type: TEAMS_ADAPTIVE_CARD_CONTENT_TYPE, value: card };
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Map one decide resolution to the invoke response — the Teams rendering of what core's Slack handler
|
|
222
|
+
* renders as calls. PURE, so it is tested as a matrix.
|
|
223
|
+
* decided / expired → the decided card REPLACES the pending one (no buttons left to tap).
|
|
224
|
+
* denied, clicker-denied, halted → a note to the clicker alone; the card stays live and the row
|
|
225
|
+
* stays pending, so a person who may decide it still can.
|
|
226
|
+
* contradicted → a note to the clicker; the notice card stays, because it is the room's trace.
|
|
227
|
+
* unknown → a note, with the Slack card-text wording.
|
|
228
|
+
* the silent branches (a stale re-tap, a lost race, an unregistered kind) → a short note that
|
|
229
|
+
* nothing changed. An invoke must answer with something, and re-rendering a card
|
|
230
|
+
* from a stale row could overwrite the winner's decided card.
|
|
231
|
+
*/
|
|
232
|
+
export function teamsResponseFor(res, deciderLabel) {
|
|
233
|
+
switch (res.outcome) {
|
|
234
|
+
case "unknown":
|
|
235
|
+
return messageResponse("This approval request is no longer known (it may have already been decided or has aged out).");
|
|
236
|
+
case "not-pending":
|
|
237
|
+
case "expired-race":
|
|
238
|
+
case "lost-race":
|
|
239
|
+
return messageResponse("Nothing changed — this request was already decided.");
|
|
240
|
+
case "unregistered-kind":
|
|
241
|
+
return messageResponse("Nothing changed — this deployment cannot decide this kind of request.");
|
|
242
|
+
case "expired":
|
|
243
|
+
return cardResponse(decidedTeamsCard(res.row, res.summaryLines, "expired"));
|
|
244
|
+
case "denied":
|
|
245
|
+
return messageResponse("Only a domain/org admin can decide this request. It is still pending.");
|
|
246
|
+
case "clicker-denied":
|
|
247
|
+
return messageResponse(clickerFloorRefusalNotice(res.reason));
|
|
248
|
+
case "halted":
|
|
249
|
+
return messageResponse(res.notice);
|
|
250
|
+
case "contradicted":
|
|
251
|
+
return messageResponse(judgeContradictionNotice(res.contradiction));
|
|
252
|
+
case "decided":
|
|
253
|
+
return cardResponse(decidedTeamsCard(res.row, res.summaryLines, res.outcomeLine, deciderLabel));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
async function liveUserType(invoke) {
|
|
257
|
+
if (!invoke.clickerId)
|
|
258
|
+
return undefined;
|
|
259
|
+
return resolveTeamsUserType(teamsServiceUrlRosterProbe(invoke.serviceUrl), invoke.conversationId, invoke.clickerId);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Handle one VERIFIED OE approval tap and return the invoke response body. The approver policy is
|
|
263
|
+
* core's: the config-as-code roster, matched as `teams/<from.id>` (Teams is not a tenant-scoped
|
|
264
|
+
* person-id space, identity/principal-key.ts), with no platform-native admin leg — a Teams team owner
|
|
265
|
+
* is not an OE authority. This call states its platform rather than letting core infer it. NEVER throws.
|
|
266
|
+
*/
|
|
267
|
+
export async function handleTeamsApprovalInvoke(invoke, deps = {}) {
|
|
268
|
+
const { action } = invoke;
|
|
269
|
+
try {
|
|
270
|
+
if (!invoke.clickerId) {
|
|
271
|
+
console.error(`[open-everywhere:teams-approvals] approval tap on ${action.id} carries no user id — ignoring the tap`);
|
|
272
|
+
return messageResponse("Couldn't read who clicked — this decision was not applied.");
|
|
273
|
+
}
|
|
274
|
+
const clickerId = invoke.clickerId;
|
|
275
|
+
if (!invoke.tenantId) {
|
|
276
|
+
console.error(`[open-everywhere:teams-approvals] approval tap on ${action.id} carries no tenant — refusing the tap`);
|
|
277
|
+
return messageResponse("Couldn't read where this was clicked — this decision was not applied.");
|
|
278
|
+
}
|
|
279
|
+
// Leg D's verdict, resolved BEFORE the decide call so a positive guest verdict cannot race a lost
|
|
280
|
+
// CAS. A thrown probe degrades to "no verdict", never to an aborted tap.
|
|
281
|
+
const userType = await (deps.userType ?? liveUserType)(invoke).catch(() => undefined);
|
|
282
|
+
const known = await (deps.readRow ?? ((org, id) => getOperationalStore().getPendingAction(org, id)))(action.org, action.id).catch(() => null);
|
|
283
|
+
const surfaceKey = teamsClickSurfaceKey(invoke, known);
|
|
284
|
+
if (!surfaceKey)
|
|
285
|
+
return messageResponse("Couldn't read where this was clicked — this decision was not applied.");
|
|
286
|
+
const res = await (deps.resolve ?? resolveApprovalClick)({
|
|
287
|
+
org: action.org,
|
|
288
|
+
id: action.id,
|
|
289
|
+
decision: action.decision,
|
|
290
|
+
clickerId,
|
|
291
|
+
clickerPlatform: "teams",
|
|
292
|
+
clickerTenant: invoke.tenantId,
|
|
293
|
+
clickContext: { surfaceKey },
|
|
294
|
+
...(userType === undefined ? {} : { clickerIsGuest: userType === "guest" }),
|
|
295
|
+
...(deps.waitUntil ? { waitUntil: deps.waitUntil } : {}),
|
|
296
|
+
});
|
|
297
|
+
const pending = decisionFeedback(invoke, res, deps);
|
|
298
|
+
if (pending !== undefined) {
|
|
299
|
+
if (deps.waitUntil === undefined) {
|
|
300
|
+
await pending;
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
deps.waitUntil(pending.catch((err) => {
|
|
304
|
+
log(`deferred decision feedback failed for ${action.id}: ${err?.message ?? String(err)}`);
|
|
305
|
+
}));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return teamsResponseFor(res, invoke.clickerName);
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
console.error(`[open-everywhere:teams-approvals] tap handling failed softly for ${action.id}: ${String(err)}`);
|
|
312
|
+
return messageResponse("Something went wrong handling this click — the decision was not applied.");
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* The decision FEEDBACK for a decided or expired tap: a fresh notice in the card's own conversation
|
|
317
|
+
* (the card replacement is an edit, which backscroll does not show), an update of the card message so
|
|
318
|
+
* a viewer who did not tap also sees the decided render, and the decision wake. Undefined for every
|
|
319
|
+
* other resolution. Every leg is best-effort.
|
|
320
|
+
*/
|
|
321
|
+
function decisionFeedback(invoke, res, deps) {
|
|
322
|
+
if (res.outcome !== "decided" && res.outcome !== "expired")
|
|
323
|
+
return undefined;
|
|
324
|
+
const row = res.row;
|
|
325
|
+
const outcomeLine = res.outcome === "expired" ? "expired" : res.outcomeLine;
|
|
326
|
+
const send = deps.send ?? sendTeamsActivityBackedOff;
|
|
327
|
+
// The invoke's own conversation IS the card's conversation (a channel card's own reply chain). Its
|
|
328
|
+
// serviceUrl is the relayed activity's, which the token does NOT bind, so the allowlist below is the
|
|
329
|
+
// guard for every Connector call here, each of which carries a bearer token.
|
|
330
|
+
const trusted = isTrustedTeamsServiceUrl(invoke.serviceUrl);
|
|
331
|
+
const postNotice = async (text) => {
|
|
332
|
+
if (!trusted)
|
|
333
|
+
throw new Error("the tap's serviceUrl is not a Bot Framework host");
|
|
334
|
+
return send({ serviceUrl: invoke.serviceUrl, conversationId: invoke.conversationId, body: { type: "message", text } });
|
|
335
|
+
};
|
|
336
|
+
const target = row.postedTo && trusted
|
|
337
|
+
? {
|
|
338
|
+
surfaceKey: row.postedTo,
|
|
339
|
+
origin: { platform: "teams", serviceUrl: invoke.serviceUrl, conversationId: invoke.conversationId, ...(invoke.tenantId ? { tenantId: invoke.tenantId } : {}) },
|
|
340
|
+
}
|
|
341
|
+
: null;
|
|
342
|
+
const updateCard = async () => {
|
|
343
|
+
const activityId = row.messageTs ?? invoke.replyToId;
|
|
344
|
+
if (!trusted || !activityId)
|
|
345
|
+
return;
|
|
346
|
+
const card = decidedTeamsCard(row, res.summaryLines, outcomeLine, res.outcome === "decided" ? invoke.clickerName : undefined);
|
|
347
|
+
await (deps.update ?? updateTeamsActivityBackedOff)({
|
|
348
|
+
serviceUrl: invoke.serviceUrl,
|
|
349
|
+
conversationId: invoke.conversationId,
|
|
350
|
+
activityId,
|
|
351
|
+
body: { type: "message", id: activityId, attachments: [teamsCardAttachment(card)] },
|
|
352
|
+
}).catch((err) => log(`decided-card update failed for ${row.id}: ${err?.message ?? String(err)}`));
|
|
353
|
+
};
|
|
354
|
+
const feedback = deliverDecisionFeedback({
|
|
355
|
+
row,
|
|
356
|
+
summaryLines: res.summaryLines,
|
|
357
|
+
outcome: res.outcome === "expired" ? "expired" : row.status,
|
|
358
|
+
outcomeLine,
|
|
359
|
+
executed: res.outcome === "expired" ? "none" : res.executed,
|
|
360
|
+
...(res.outcome === "decided" ? { clickerId: invoke.clickerId } : {}),
|
|
361
|
+
target,
|
|
362
|
+
postNotice,
|
|
363
|
+
}, deps.decisionWake);
|
|
364
|
+
return Promise.all([updateCard(), feedback]).then(() => undefined);
|
|
365
|
+
}
|
|
366
|
+
//# sourceMappingURL=teams-flow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"teams-flow.js","sourceRoot":"","sources":["../../../agent/approvals/teams-flow.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type DeclaredTargetResolution, type DeclaredTargetSpec } from "@open-everywhere/core/agent/adapter-api.js";
|
|
2
|
+
import { type TeamsConversationRef } from "./teams-conversation-ref.js";
|
|
3
|
+
/** The edges this resolver reads — the live store lookups by default, injectable for a test. */
|
|
4
|
+
export interface TeamsDeclaredTargetDeps {
|
|
5
|
+
readonly restoreConversationId?: (id: string) => Promise<string>;
|
|
6
|
+
readonly conversationRef?: (conversationId: string) => Promise<TeamsConversationRef | undefined>;
|
|
7
|
+
readonly restorePersonId?: (id: string) => Promise<string>;
|
|
8
|
+
readonly personalRef?: (principalId: string) => Promise<TeamsConversationRef | undefined>;
|
|
9
|
+
}
|
|
10
|
+
/** Resolve one declared Teams target. See the header for the two forms and the tenant check. */
|
|
11
|
+
export declare function resolveTeamsDeclaredTarget(spec: DeclaredTargetSpec, deps?: TeamsDeclaredTargetDeps): Promise<DeclaredTargetResolution>;
|
|
12
|
+
//# sourceMappingURL=teams-declared-target.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"teams-declared-target.d.ts","sourceRoot":"","sources":["../../../agent/channels/teams-declared-target.ts"],"names":[],"mappings":"AAgCA,OAAO,EAAqB,KAAK,wBAAwB,EAAE,KAAK,kBAAkB,EAAE,MAAM,4CAA4C,CAAC;AACvI,OAAO,EAAuF,KAAK,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAG7J,gGAAgG;AAChG,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACjE,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAAC;IACjG,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3D,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAAC;CAC3F;AAeD,gGAAgG;AAChG,wBAAsB,0BAA0B,CAAC,IAAI,EAAE,kBAAkB,EAAE,IAAI,GAAE,uBAA4B,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAiDhJ"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// THE TEAMS DECLARED-TARGET RESOLVER (OPE-2419) — how a target that config NAMES (a webhook's
|
|
2
|
+
// `target`) becomes a postable Teams conversation at fire time.
|
|
3
|
+
//
|
|
4
|
+
// WHY TEAMS NEEDS A STORE READ AND THE OTHER SURFACES DO NOT. A Bot Framework write goes to a REGIONAL
|
|
5
|
+
// `serviceUrl` that appears nowhere but on an inbound activity (teams-conversation-ref.ts's header).
|
|
6
|
+
// So a config string can name a Teams conversation, but it cannot carry the address. This resolver
|
|
7
|
+
// reads the reference the deployment CAPTURED off the last inbound activity in that conversation, and
|
|
8
|
+
// a conversation it never heard from is a refusal that names the fix, never a guess.
|
|
9
|
+
//
|
|
10
|
+
// • A ROOM, `teams/<tenant>/<conversation>`: the conversation id in its surface-key spelling (the
|
|
11
|
+
// form docs/deploy/surfaces/teams.md tells operators to write) or raw. The spelling restores
|
|
12
|
+
// through the id index (teams-id-restore.ts), then the `conversation` address row supplies the
|
|
13
|
+
// `serviceUrl`. A post at channel grain starts a new thread in the channel.
|
|
14
|
+
// • A PERSON, `dm:teams/<29: id>`: the person's `personal` address row — the 1:1 chat the person
|
|
15
|
+
// opened with the bot. The fire binds as a DM (`conversationType: "personal"`).
|
|
16
|
+
//
|
|
17
|
+
// 🔴 THE TENANT IS CHECKED. A room target names its tenant, and the captured reference carries the
|
|
18
|
+
// tenant the activity came from. A mismatch refuses: the fire's partition and domain come from the
|
|
19
|
+
// target's tenant segment, so posting into a conversation of another tenant would bind the wrong
|
|
20
|
+
// tenant's memory in that room.
|
|
21
|
+
//
|
|
22
|
+
// 🔴 THE ROOM'S KIND IS CLASSIFIED, NOT ASSUMED. The `conversation` row is written for EVERY scope —
|
|
23
|
+
// a 1:1 chat and a group chat included — and stores no conversation type. Passed through untyped, a
|
|
24
|
+
// 1:1 chat would bind as `channel`, with no DM standing grain, so a webhook in someone's private chat
|
|
25
|
+
// would carry more hands than a routine in the same chat. So the room branch reads the raw id's kind:
|
|
26
|
+
// • a team channel (`…@thread.tacv2`, `…@thread.skype`) binds `channel`;
|
|
27
|
+
// • a group chat (`…@thread.v2`) binds `groupChat` → mode `mpim`, which stamps the standing grain;
|
|
28
|
+
// • anything else — a 1:1 chat (`a:…`), or a shape this tree does not know — REFUSES, and names
|
|
29
|
+
// the `dm:teams/<29: id>` form, which binds as a DM.
|
|
30
|
+
// This is a classification toward the SAFER answer, never a rebuild of an id: an unknown shape
|
|
31
|
+
// refuses rather than guessing a mode.
|
|
32
|
+
import { surfaceKeySegment } from "@open-everywhere/core/agent/adapter-api.js";
|
|
33
|
+
import { teamsChannelGrainConversationId, teamsConversationRef, teamsPersonalConversationRef } from "./teams-conversation-ref.js";
|
|
34
|
+
import { restoreTeamsConversationId, restoreTeamsPersonId } from "./teams-id-restore.js";
|
|
35
|
+
/** The surface key a captured reference fires on — the same spelling bindTeams derives. */
|
|
36
|
+
function surfaceKeyOf(ref) {
|
|
37
|
+
return `teams/${surfaceKeySegment(ref.tenantId)}/${surfaceKeySegment(ref.conversationId)}`;
|
|
38
|
+
}
|
|
39
|
+
/** The conversation type a ROOM id names, or undefined when it names no room this resolver admits. */
|
|
40
|
+
function teamsRoomKind(conversationId) {
|
|
41
|
+
const grain = teamsChannelGrainConversationId(conversationId);
|
|
42
|
+
if (grain.endsWith("@thread.tacv2") || grain.endsWith("@thread.skype"))
|
|
43
|
+
return "channel";
|
|
44
|
+
if (grain.endsWith("@thread.v2"))
|
|
45
|
+
return "groupChat";
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
/** Resolve one declared Teams target. See the header for the two forms and the tenant check. */
|
|
49
|
+
export async function resolveTeamsDeclaredTarget(spec, deps = {}) {
|
|
50
|
+
if (spec.kind === "room") {
|
|
51
|
+
const raw = await (deps.restoreConversationId ?? restoreTeamsConversationId)(spec.surface);
|
|
52
|
+
const ref = await (deps.conversationRef ?? teamsConversationRef)(raw);
|
|
53
|
+
if (!ref) {
|
|
54
|
+
return {
|
|
55
|
+
ok: false,
|
|
56
|
+
reason: `no captured Teams conversation reference for ${spec.surface} yet. Teams accepts a proactive post only into a conversation ` +
|
|
57
|
+
`the bot has heard from: add the app to that conversation and @mention it once, and the next delivery fires.`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (surfaceKeySegment(ref.tenantId) !== spec.workspace) {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
reason: `conversation ${spec.surface} belongs to Teams tenant ${ref.tenantId}, not ${spec.workspace}. Correct the target's tenant segment.`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const conversationType = teamsRoomKind(ref.conversationId);
|
|
67
|
+
if (!conversationType) {
|
|
68
|
+
return {
|
|
69
|
+
ok: false,
|
|
70
|
+
reason: `conversation ${spec.surface} is not a team channel or a group chat. A room target names a channel or a group chat; ` +
|
|
71
|
+
`name a person's 1:1 chat as "dm:teams/<29: id>", which runs the turn as a DM.`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
ok: true,
|
|
76
|
+
facts: { surfaceKey: surfaceKeyOf(ref), target: { serviceUrl: ref.serviceUrl, conversationId: ref.conversationId, tenantId: ref.tenantId, conversationType } },
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const principalId = await (deps.restorePersonId ?? restoreTeamsPersonId)(spec.principalId);
|
|
80
|
+
const ref = await (deps.personalRef ?? teamsPersonalConversationRef)(principalId);
|
|
81
|
+
if (!ref) {
|
|
82
|
+
return {
|
|
83
|
+
ok: false,
|
|
84
|
+
reason: `no captured Teams 1:1 chat with ${spec.principalId} yet. Teams cannot open a chat from a bare person id: ` +
|
|
85
|
+
`the person must send the bot one direct message, and the next delivery fires.`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
ok: true,
|
|
90
|
+
facts: {
|
|
91
|
+
surfaceKey: surfaceKeyOf(ref),
|
|
92
|
+
target: { serviceUrl: ref.serviceUrl, conversationId: ref.conversationId, tenantId: ref.tenantId, conversationType: "personal" },
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=teams-declared-target.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"teams-declared-target.js","sourceRoot":"","sources":["../../../agent/channels/teams-declared-target.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,7 @@ import { type OpenEverywhereTeamsAuth } from "./teams-identity.js";
|
|
|
4
4
|
import { type TeamsRosterProbe } from "./teams-roster.js";
|
|
5
5
|
import { type TeamsReactionActivity, type TeamsStopOutcome } from "./teams-stop.js";
|
|
6
6
|
import { type TeamsEngagementVerdict } from "./teams-engagement.js";
|
|
7
|
+
import { type TeamsApprovalInvoke, type TeamsInvokeResponseBody } from "../approvals/teams-flow.js";
|
|
7
8
|
import { type TeamsSupervisorDeps } from "./teams-supervisor.js";
|
|
8
9
|
import { EventDedupe, ThreadFifo, type ExternalPolicy, type IngestedChatMessage, type AuthorTrustQuery, type SupervisorGrain, type UnifiedWindowWake, type Partition, type AttachSessionForCancel } from "@open-everywhere/core/agent/adapter-api.js";
|
|
9
10
|
/** An HTTP route handler — `RouteHandler` isn't a public eve export, so we mirror its shape
|
|
@@ -192,6 +193,10 @@ export interface TeamsFrontOptions {
|
|
|
192
193
|
/** The @mention's ROOM-grain re-invitation — defaults to {@link clearTeamsRoomStop}. Separate
|
|
193
194
|
* from the read so a test can prove a mention is the way back out of a room stop. */
|
|
194
195
|
readonly clearRoomStop?: (partition: Partition, atIso: string, engagedBy: string) => Promise<void>;
|
|
196
|
+
/** The approval-card tap (OPE-2424) — defaults to {@link handleTeamsApprovalInvoke}. Injected so
|
|
197
|
+
* the front's ORDERING (below the JWT verify, above the native forward) is unit-testable without a
|
|
198
|
+
* store. */
|
|
199
|
+
readonly approvalInvoke?: (invoke: TeamsApprovalInvoke, waitUntil: (p: Promise<unknown>) => void) => Promise<TeamsInvokeResponseBody>;
|
|
195
200
|
}
|
|
196
201
|
/** The live dispatch for one owned activity: per-message trust off the SAME auth capture the
|
|
197
202
|
* session binds with, then the teams-supervisor core over the live registry deps.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"teams-front.d.ts","sourceRoot":"","sources":["../../../agent/channels/teams-front.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"teams-front.d.ts","sourceRoot":"","sources":["../../../agent/channels/teams-front.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
// don't re-implement the check: eve exports it. The forward path re-verifies inside
|
|
16
16
|
// teamsChannel (a fresh Request), so the native path's auth is unchanged — we only ADD a
|
|
17
17
|
// gate, never weaken one.
|
|
18
|
-
// 2. Parses the activity (eve's public `parseTeamsActivity`).
|
|
19
|
-
//
|
|
18
|
+
// 2. Parses the activity (eve's public `parseTeamsActivity`). An OE approval-card tap (an
|
|
19
|
+
// `adaptiveCard/action` invoke carrying the OE payload, OPE-2424) is answered HERE, through the
|
|
20
|
+
// approvals/teams-flow.ts handler. Anything else that isn't a message
|
|
21
|
+
// activity — other invokes (eve's HITL card clicks), conversationUpdates, unparseable bodies — FORWARDS
|
|
20
22
|
// to teamsChannel untouched (the fail-safe default).
|
|
21
23
|
// 3. For a message activity, resolves the SINGLE-WINDOW plan (teams-supervisor.ts
|
|
22
24
|
// planTeamsSingleWindow: grain singleWindow + the fail-closed Teams privacy floor). NO plan
|
|
@@ -64,6 +66,7 @@ import { recordTeamsConversationReference, teamsChannelGrainConversationId, team
|
|
|
64
66
|
import { parseTeamsReaction, routeTeamsStopReaction } from "./teams-stop.js";
|
|
65
67
|
import { clearTeamsRoomStop, markTeamsEngaged, markTeamsRoomWatched, resolveTeamsEngagement, teamsMachineAuthored, teamsRoomWatchStopped, } from "./teams-engagement.js";
|
|
66
68
|
import { teamsGraphTeamId } from "./teams-graph-team-id.js";
|
|
69
|
+
import { handleTeamsApprovalInvoke, parseTeamsApprovalInvoke } from "../approvals/teams-flow.js";
|
|
67
70
|
import { planTeamsSingleWindow, handleTeamsSingleWindowDispatch, teamsSupervisorToken, buildTeamsDmWake, } from "./teams-supervisor.js";
|
|
68
71
|
import { shouldRotate, EventDedupe, ThreadFifo, divertToUnifiedWindow, divertInboundToUnifiedWindow, supervisorGrainFor, mintRotationToken, claimRuntimeRotation, stampMintedRuntime, grainUsesUnifiedWindow, resolvePartition, domainKeyOf, engagementTierFor, getConfig, getOperationalStore, isVouchedSurface, trustGroupKeyFor, resolveWakeTrustFromContext, stampNonMemberChatAuth, deploymentOrg, appendAudit, getOrCreateDmSeat, touchDmSeat, renderWake, mintWakeId, resolveExternalPolicy, externalPolicyOverrideFor, externalPolicyIsInert, withholdsBody, roomIsSilenced, silentRoomNotice, silentNoticeKey, EXTERNAL_SENDER_NOTICE, EXTERNAL_MENTION_ACK, owesSuppressedMentionAck, suppressionRendering, carryTailForward, recordWakeTail, tailAuthorGrain, dmWindowRegistryKey, RateLimitExhaustedError, receiptEventTime, spreadVendorTs, seatCatchUp, getSettings, } from "@open-everywhere/core/agent/adapter-api.js";
|
|
69
72
|
/** The public URL the Bot Framework POSTs every activity to (the bot registration's endpoint). */
|
|
@@ -817,6 +820,7 @@ export function buildTeamsFrontHandler(innerHandler, opts = {}) {
|
|
|
817
820
|
const roomStopped = opts.roomStopped ?? teamsRoomWatchStopped;
|
|
818
821
|
const markRoomWatched = opts.markRoomWatched ?? markTeamsRoomWatched;
|
|
819
822
|
const clearRoomStop = opts.clearRoomStop ?? clearTeamsRoomStop;
|
|
823
|
+
const approvalInvoke = opts.approvalInvoke ?? ((invoke, waitUntil) => handleTeamsApprovalInvoke(invoke, { waitUntil }));
|
|
820
824
|
return async (req, args) => {
|
|
821
825
|
let rawBody;
|
|
822
826
|
try {
|
|
@@ -852,6 +856,19 @@ export function buildTeamsFrontHandler(innerHandler, opts = {}) {
|
|
|
852
856
|
return OK();
|
|
853
857
|
}
|
|
854
858
|
const activity = parseTeamsActivity(parsed);
|
|
859
|
+
// ── THE APPROVAL-CARD TAP (OPE-2424) ─────────────────────────────────────────────────────────
|
|
860
|
+
//
|
|
861
|
+
// POSITION IS LOAD-BEARING. It sits BELOW the JWT verify at the top of this handler, so a forged
|
|
862
|
+
// or unsigned invoke is answered 401 before this line runs — the same verifier that admits a
|
|
863
|
+
// message activity. It sits ABOVE the forward, because the native channel has no route for an OE
|
|
864
|
+
// card: forwarded, the tap would reach eve's HITL decoder, which does not know the payload, and the
|
|
865
|
+
// person would see nothing happen. Every other invoke (eve's own HITL answers among them) still
|
|
866
|
+
// forwards untouched. The response body IS the UI: a decided card or a note to the clicker.
|
|
867
|
+
const tap = parseTeamsApprovalInvoke(activity);
|
|
868
|
+
if (tap) {
|
|
869
|
+
const body = await approvalInvoke(tap, (p) => args.waitUntil(p));
|
|
870
|
+
return new Response(JSON.stringify(body), { status: 200, headers: { "content-type": "application/json; charset=utf-8" } });
|
|
871
|
+
}
|
|
855
872
|
if (!activity || activity.type !== "message") {
|
|
856
873
|
return forward(); // invokes / conversationUpdates / unknown — the native channel owns them
|
|
857
874
|
}
|