@open-everywhere/provider-teams 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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,219 @@
1
+ // registerTeamsProvider — the ONE boot entry of @open-everywhere/provider-teams. The app's
2
+ // adapter-wiring module (agent/lib/register-adapters.ts) imports this STATICALLY and calls it once
3
+ // at boot; every seam the teams bundle implements registers here and nowhere else. Static imports
4
+ // only: eve bundles the agent definition and its whole import graph into one rolldown chunk — a
5
+ // literal dynamic import() code-splits and trips eve's "expected one authored module" invariant.
6
+ //
7
+ // What teams registers (and what it deliberately does NOT):
8
+ // • identity mapper — bindTeams (chat-surface plane; channels/teams-identity.ts)
9
+ // • capability manifest — midTurnPost via the host-side Connector write seam
10
+ // (channels/teams-connector.ts, OPE-1237); no reaction primitive
11
+ // • post arm — teamsPostArm (mid-turn reply + unified reply + redrive helpers + the
12
+ // 1:1 DM delivery arm, OPE-887)
13
+ // • wake-origin codec — the stored-data teams arm (channels/teams-origin-codec.ts, which also
14
+ // owns the Bot-Connector serviceUrl allowlist isTrustedTeamsServiceUrl)
15
+ // • read adapter — teamsReadAdapter (Microsoft Graph channel/thread history reads)
16
+ // • routine dispatch — resolveTeamsTarget (proactive routine fires need the captured Bot
17
+ // Framework conversation reference + the teams channel object, both of
18
+ // which now live in this bundle, so its resolver moved here too)
19
+ // • teams BRIDGE — the bundle→core seam for the routine fire-eligibility leg's Graph
20
+ // checks (OPE-522, mirroring the slack provider's own bridge):
21
+ // accountEnabled + channel-membership, keyed on the creator's Azure AD
22
+ // object id (channels/teams-fire-eligibility.ts, core channels/
23
+ // teams-bridge.ts)
24
+ // • bot display name — a HANDLE line on a Teams-pinned session, not identity since OPE-610,
25
+ // read from the app's OWN service principal via Graph (OPE-587;
26
+ // channels/teams-bot-name.ts, core identity/bot-display-name.ts)
27
+ // • account directory — the Bot Connector roster of the teams this deployment has seen, for
28
+ // the identity-linking directory transport (OPE-887;
29
+ // channels/teams-directory.ts). NOT Microsoft Graph — Graph has no `29:`
30
+ // id and a link keys on one; the header of that file carries the whole
31
+ // argument
32
+ // • surface message ops — teamsMessageOps: postText/editMessage (OPE-899), the transport behind
33
+ // the stop acknowledgement and the approval decision notice. Resolves
34
+ // its own serviceUrl from the `conversation`-grain address store
35
+ // (channels/teams-conversation-ref.ts), since the registry's callers
36
+ // hand it only a bare conversation id.
37
+ // • reaction surface — INBOUND ONLY (OPE-1502). Teams delivers `messageReaction` activities
38
+ // and refuses an application identity the outbound WRITE, so the
39
+ // feedback ingest is wired (channels/teams-reaction-feedback.ts) while
40
+ // the capability manifest still reads `reactions: false`
41
+ // • NO attachment resolver — the Teams surface carries no attachment plane today
42
+
43
+ import {
44
+ registerIdentityMapper,
45
+ registerCapabilityManifest,
46
+ registerSurfaceParity,
47
+ registerProviderEvents,
48
+ registerReactionSurface,
49
+ registerPostArm,
50
+ registerWakeOriginCodec,
51
+ registerReadAdapter,
52
+ registerSeatCatchUp,
53
+ registerRoutineDispatch,
54
+ registerTeamsBridge,
55
+ registerBotDisplayName,
56
+ registerAccountDirectory,
57
+ registerSurfaceMessageOps,
58
+ type DispatchTarget,
59
+ type Routine,
60
+ type UnifiedWakeOrigin,
61
+ registerProviderDocs,
62
+ } from "@open-everywhere/core/agent/adapter-api.js";
63
+ import { PROVIDER_DOCS } from "./docs-generated.js";
64
+ import { TEAMS_SURFACE_PARITY } from "./surface-parity.js";
65
+ import { TEAMS_PROVIDER_EVENTS } from "./provider-events.js";
66
+ import { registerTeamsOps } from "./ops.js";
67
+ import { teamsMessageOps } from "./channels/teams-message-ops.js";
68
+ import teamsChannel from "./channels/teams.js";
69
+ import { bindTeams, teamsMode } from "./channels/teams-identity.js";
70
+ import { teamsPostArm } from "./channels/teams-post-arm.js";
71
+ import { teamsWakeOriginCodec } from "./channels/teams-origin-codec.js";
72
+ import { teamsReadAdapter } from "./channels/teams-read.js";
73
+ import { teamsSeatCatchUpFetch, TEAMS_TIMESTAMP_PRECISION_MS } from "./channels/teams-catch-up.js";
74
+ import { liveTeamsRoomMembership, liveTeamsAccountActive } from "./channels/teams-fire-eligibility.js";
75
+ import { teamsBotDisplayName } from "./channels/teams-bot-name.js";
76
+ import { fetchTeamsDirectory } from "./channels/teams-directory.js";
77
+
78
+ /** One stored-target field as a non-empty string, or undefined (never a coerced non-string — the
79
+ * store row is data, validated on the way out, not trusted). Local copy of the core dispatcher's
80
+ * private helper (a two-line structural read; not a barrel port). */
81
+ function storedString(t: Routine["target"], key: string): string | undefined {
82
+ const v = t?.[key];
83
+ return typeof v === "string" && v.length > 0 ? v : undefined;
84
+ }
85
+
86
+ /**
87
+ * Resolve a Teams routine to its proactive-fire DispatchTarget — MOVED VERBATIM out of core's
88
+ * schedules/dispatch.ts (OPE-114 stage 4). Proactive messaging on Teams REQUIRES a Bot Framework
89
+ * conversation reference (serviceUrl + conversation id) captured on a prior INBOUND turn — you
90
+ * cannot cold-send to a Teams user/channel the bot has never heard from. bindTeams captures exactly
91
+ * that off every inbound activity, and create_routine stores it on the routine, so by construction
92
+ * every Teams routine created in-thread carries its reference. A routine WITHOUT one (both fields
93
+ * are required by eve's teamsChannel().receive, which throws otherwise) fails closed to dry-run
94
+ * (null) here.
95
+ */
96
+ export function resolveTeamsTarget(r: Routine): DispatchTarget | null {
97
+ const serviceUrl = storedString(r.target, "serviceUrl");
98
+ const conversationId = storedString(r.target, "conversationId");
99
+ if (!serviceUrl || !conversationId) return null;
100
+ const conversationType = storedString(r.target, "conversationType");
101
+ const channelId = storedString(r.target, "channelId");
102
+ const teamId = storedString(r.target, "teamId");
103
+ // The creator's Azure AD object id (OPE-522) — stamped by bindTeams onto the routine's STORED
104
+ // target at creation time (channels/teams-identity.ts). Read back here into ATTRIBUTES, never
105
+ // into the fresh `target` below: `target` is what actually rides into the Bot Connector send
106
+ // (routine-dispatch.ts's DispatchTarget doc — "stored-target extras... feed attributes instead,
107
+ // never the adapter call"), and this value has no business in a Connector payload. The
108
+ // dispatcher's fire-eligibility leg reads it off `attributes.aad_object_id`.
109
+ const aadObjectId = storedString(r.target, "aadObjectId");
110
+ // tenant: the stored raw fact, else the surfaceKey's workspace segment (a tenant GUID is seg()-safe,
111
+ // so it round-trips verbatim — unlike the conversation id, whose ":"/"@" get sanitized in the key
112
+ // and MUST come from the stored target).
113
+ const tenantId = storedString(r.target, "tenantId") ?? r.surfaceKey.split("/")[1] ?? "";
114
+ // teamsMode is the one tested scope→mode place (personal→dm, groupChat→mpim); an absent stored
115
+ // scope resolves to "channel" (capped — the conservative arm).
116
+ const mode = teamsMode(conversationType);
117
+ // OPE-486 — the fire's WAKE ORIGIN, parity with resolveSlackTarget (routine-dispatch.ts). Absent
118
+ // before this fix, so a Teams fire could never divert into the unified window (fire-window.ts's
119
+ // planFireWindow fails closed on a missing origin). serviceUrl/conversationId are already
120
+ // non-empty-checked above (isTrustedTeamsServiceUrl re-validates serviceUrl in the codec's
121
+ // validate arm); no replyToActivityId — a proactive fire has no reply chain to anchor to, matching
122
+ // teams-front.ts's own ambient-origin mint (`{ platform: "teams", serviceUrl, conversationId }`).
123
+ const origin: UnifiedWakeOrigin = { platform: "teams", serviceUrl, conversationId, ...(tenantId ? { tenantId } : {}) };
124
+ return {
125
+ channel: teamsChannel,
126
+ // Full TeamsReceiveTarget: conversationType lets eve anchor non-personal threads correctly;
127
+ // tenant/team/channel ids ride into the Connector's channelData.
128
+ target: {
129
+ serviceUrl,
130
+ conversationId,
131
+ ...(conversationType ? { conversationType } : {}),
132
+ ...(channelId ? { channelId } : {}),
133
+ ...(teamId ? { teamId } : {}),
134
+ ...(tenantId ? { tenantId } : {}),
135
+ },
136
+ mode,
137
+ // The facts bindTeams reads (workspace = tenant, surface = conversation). NO membership_type is
138
+ // stamped — it was never re-verified at fire time, so teamsTrust yields no verdict and bindFrom
139
+ // fails the fire closed to `external` (shared memory / domain skills stay off), matching what an
140
+ // inbound Teams activity without membershipType gets.
141
+ attributes: {
142
+ conversation_id: conversationId,
143
+ service_url: serviceUrl,
144
+ ...(tenantId ? { tenant_id: tenantId } : {}),
145
+ ...(channelId ? { channel_id: channelId } : {}),
146
+ ...(teamId ? { team_id: teamId } : {}),
147
+ ...(conversationType ? { scope: conversationType } : {}),
148
+ ...(aadObjectId ? { aad_object_id: aadObjectId } : {}),
149
+ },
150
+ origin,
151
+ };
152
+ }
153
+
154
+ /** Perform ALL of the teams bundle's registrations. Idempotent (each register call replaces);
155
+ * cheap and side-effect-free beyond the registry writes. */
156
+ export function registerTeamsProvider(): void {
157
+ registerIdentityMapper("teams", bindTeams);
158
+ // Bot-Connector proactive post wired via the host-side Connector write seam (channels/
159
+ // teams-connector.ts, OPE-1237 — the post arm sends a message activity into the captured
160
+ // conversation reference). No integration reaction primitive.
161
+ // closingProseDropped (OPE-2006): the Teams completion handler DROPS a turn's closing text and
162
+ // audits it. `reply` is the only route out.
163
+ registerCapabilityManifest("teams", { midTurnPost: true, reactions: false, closingProseDropped: true });
164
+ // The OPE-1499 parity ledger: every capability the chat-surface contract names, answered
165
+ // `supported` or `{ unsupported: "<reason>" }`. Gates nothing at runtime — `lint:surface-parity`
166
+ // reds on an unanswered row, and the contract's §7.1/§7.1.1/§7.2 tables are generated from it.
167
+ registerSurfaceParity("teams", TEAMS_SURFACE_PARITY);
168
+ // The OPE-1612 event inventory: every vendor event that could wake a routine, and how far the
169
+ // normalizer plane carries each one. Gates nothing at runtime — `lint:provider-events` reds a
170
+ // bundle that declares none or that promises a match key the normalizer does not declare, and
171
+ // the manual's inventory table is generated from it.
172
+ registerProviderEvents("teams", TEAMS_PROVIDER_EVENTS);
173
+ registerPostArm(teamsPostArm);
174
+ registerWakeOriginCodec("teams", teamsWakeOriginCodec);
175
+ // Teams channel/thread history reads ride Microsoft Graph (Bot Framework has no history API).
176
+ registerReadAdapter(teamsReadAdapter);
177
+ // THE SEAT CATCH-UP FETCH (OPE-1726): the channel (or reply chain) history since the seat's
178
+ // last wake, over Microsoft Graph — the same transport `read_channel` uses, and NOT the Bot
179
+ // Connector the wake arrives on. Core's shared path applies the bounds, the policy and the cap.
180
+ // The surface DECLARES the unit its own message stamps carry, and core widens both catch-up
181
+ // bounds by it (OPE-1847). A wrong or missing unit bounds the seat out of its own window.
182
+ registerSeatCatchUp("teams", teamsSeatCatchUpFetch, { timestampPrecisionMs: TEAMS_TIMESTAMP_PRECISION_MS });
183
+ // Proactive routine fires on the teams surface (needs the captured Bot Framework reference).
184
+ registerRoutineDispatch("teams", { resolveTarget: resolveTeamsTarget });
185
+ // The bundle→core bridge for the routine fire-eligibility leg's Graph checks (OPE-522) — core's
186
+ // routines/fire-eligibility.ts consumes these through channels/teams-bridge.ts, never Graph
187
+ // directly, mirroring the slack provider's own registerSlackBridge call.
188
+ registerTeamsBridge({ roomMembership: liveTeamsRoomMembership, accountActive: liveTeamsAccountActive });
189
+ // The bot's own display name (OPE-587) — core's per-surface registry arm, rendered as a HANDLE
190
+ // line, not identity, since OPE-610 settled the naming model. Reads the app's OWN service
191
+ // principal through the same Graph token the read adapter mints; needs no extra Azure consent
192
+ // (the app's implicit self-read, measured live). See channels/teams-bot-name.ts.
193
+ registerBotDisplayName("teams", teamsBotDisplayName);
194
+ // THE TEAMS ACCOUNT DIRECTORY (OPE-887) — the lookup the reverse-initiated link uses to NAME the
195
+ // other account instead of posting a bearer code where a room can read it. It walks the Bot
196
+ // Connector roster of the teams this deployment has captured a conversation reference for, so the
197
+ // ids it returns are the `29:` ids a Teams link actually keys on. The other half of the transport
198
+ // is the post arm's `deliverDirectMessage` — core requires BOTH before it offers the transport,
199
+ // and both rest on the same captured reference (channels/teams-conversation-ref.ts).
200
+ registerAccountDirectory("teams", (query) => fetchTeamsDirectory(query));
201
+ // THE STOP ACKNOWLEDGEMENT'S TRANSPORT (OPE-899) — postText/editMessage, so turn-anchor.ts's
202
+ // postSurfaceText resolves a real send instead of undefined on a Teams turn. No postFiles (no
203
+ // attachment plane) and no setReaction (no reaction primitive beyond the `angry` stop trigger).
204
+ registerSurfaceMessageOps(teamsMessageOps);
205
+ // REACTION-FEEDBACK INGEST (OPE-1502) — INBOUND only, and that is not a contradiction with the
206
+ // `reactions: false` manifest above. Teams refuses an application identity the outbound WRITE
207
+ // (§7.3's probe), while it delivers `messageReaction` activities inbound freely. The arm that
208
+ // records them is channels/teams-reaction-feedback.ts, reached from the stop arm's fan-out.
209
+ registerReactionSurface("teams");
210
+ // Shipped-manual plane (OPE-138): this bundle's own setup runbook (docs/deploy/surfaces/teams.md,
211
+ // compiled into ./docs-generated.ts because docs/ is not on disk at runtime). De-select the bundle
212
+ // and the module leaves the import graph; registerProviderDocs re-checks the selection gate anyway.
213
+ registerProviderDocs("teams", PROVIDER_DOCS);
214
+ // Ops plane (OPE-428): the live preflight probe that reports which Microsoft boundary governs
215
+ // channel reads. Split into ./ops.js so the STANDALONE ops MCP entry (repo-root
216
+ // scripts/ops-mcp.ts, which serves preflight with no agent boot) registers it without pulling
217
+ // the whole channel graph — the same split slack uses.
218
+ registerTeamsOps();
219
+ }
@@ -0,0 +1,112 @@
1
+ // TEAMS' surface-parity declaration (OPE-1499). See the core module's header
2
+ // (packages/core/agent/identity/surface-parity.ts) for what the three cell shapes mean.
3
+ //
4
+ // Some of the gaps below are PERMANENT vendor limits and some are NOT BUILT YET. The distinction is
5
+ // the whole point of writing a reason down: `react` and the ✋ glyph are things Microsoft does not
6
+ // let an application identity do, so no ticket will ever close them. Reaction-feedback ingest was
7
+ // the other kind — unwritten OE work — and OPE-1502 closed it, which is what the row now says.
8
+
9
+ import type { SurfaceParityDeclaration } from "@open-everywhere/core/agent/adapter-api.js";
10
+
11
+ export const TEAMS_SURFACE_PARITY: SurfaceParityDeclaration = {
12
+ // --- registration seams (§7.1) ---------------------------------------------------------------
13
+ "identity-mapper": "supported",
14
+ "capability-manifest": "supported",
15
+ "post-arm": "supported",
16
+ "wake-origin-codec": "supported",
17
+ "unified-action-plane": {
18
+ unsupported:
19
+ "the arm posts a plain message activity, and a button here needs an Adaptive Card PLUS an invoke route to receive the tap. `teams-front.ts` forwards every `invoke` to the native channel, whose HITL route resolves a tap against the TEAMS channel's own session — a unified seat's parked request lives on another session entirely, so an OE-minted card would render a button that settles nothing. Prose, which stands on its own, until that route exists",
20
+ },
21
+ "origin-placement-carry": {
22
+ unsupported:
23
+ "the placement is INSIDE the conversation identity: `threadRef` keys on `replyToActivityId`, the very activity a reply hangs under, so two origins that pass the conversation test already agree on placement and there is no gap to carry",
24
+ },
25
+ "read-adapter": "supported",
26
+ // INBOUND reaction ingest is wired (OPE-1502); the OUTBOUND write below is not, and the two are
27
+ // independent. The note is the honest half: Microsoft delivers `messageReaction` only for messages
28
+ // the agent itself sent, and delivers no author for the reacted message — so the "is this the
29
+ // agent's own reply" test on this surface is that vendor fence plus a present `replyToId`, not the
30
+ // author comparison Slack, Discord and Linear make.
31
+ "reaction-feedback-ingest": { supported: "the vendor fence is the guarantee — Teams delivers the event only for the agent's own messages, and carries no author to compare" },
32
+ "ancillary-ops": "supported",
33
+ // WIRED, AND TENANT-CONDITIONAL — the qualified form, because a bare ✅ over-claimed it against
34
+ // the support matrix's own 🔧 row (OPE-1527). The seam is built and armed on every channel and
35
+ // group chat; whether a PLAIN follow-up ever reaches it is the tenant's RSC consent, which no API
36
+ // can probe (channels/teams-engagement.ts). Without the grant only @mention activities arrive, so
37
+ // the seam wakes nothing and the surface answers once per mention exactly as before.
38
+ "ambient-engagement-seam": {
39
+ supported: "the seam is wired; plain follow-ups reach it only where the tenant consented to `ChannelMessage.Read.Group` / `ChatMessage.Read.Chat`, which no API can probe",
40
+ },
41
+ // The single-window divert writes `plan.window.addressing ?? "channel-watch"` (teams-front.ts), so
42
+ // an engaged follow-up and unowned room chatter leave the same row. No journey reds on it today —
43
+ // `J-ENGAGED-teams` does not gate a dispatch addressing — so the gap is declared here rather than
44
+ // carried as a silent one.
45
+ "engaged-row-promotion": {
46
+ unsupported: "not built yet — the single-window divert writes the plan's own `addressing`, which calls an engaged follow-up and room chatter alike",
47
+ },
48
+ // The reply chain IS the thread on Teams, so the router reads `replyToActivityId` the way it
49
+ // reads Slack's `threadTs`. A plain conversation send starts its own chain and stays top-level.
50
+ // The personal/group-chat DM leg keeps the raw divert — it mints no reply-chain origin.
51
+ "owned-thread-ingress": { supported: "the channel leg only — a reply chain is the thread; the DM leg mints no `replyToActivityId`, so it can own none" },
52
+
53
+ // --- wake legs and conversation shapes -------------------------------------------------------
54
+ "addressed-leg": "supported",
55
+ // Not the engagement seam: the wake is the single-window front's Leg A, a different mechanism.
56
+ // The registry does suppress it, though — the front drops a stood-down conversation above both
57
+ // legs, for a conversation an @mention enrolled (§7.6).
58
+ "ambient-leg": { supported: "the single-window front's Leg A, not the §2.5 engagement registry" },
59
+ // 🔴 OVER GRAPH, NOT THE BOT CONNECTOR. The transport a Teams activity arrives on carries no
60
+ // history API — which is why `teams-supervisor.ts` once said this surface could not catch up at all
61
+ // — but `teams-catch-up.ts` reads `/teams/{team}/channels/{channel}/messages`, the same Graph call
62
+ // `read_channel` uses. A personal or group chat carries no team id, so it takes no catch-up.
63
+ "seat-catch-up": { supported: "over Microsoft Graph (channel activities only — a personal/group chat carries no team id)" },
64
+ // The slot is EMPTY on this surface: `teams-front.ts` builds no `contextLines`, so there is no pour
65
+ // to police. The seat's room context here is the catch-up read above and the carried tail, both of
66
+ // which already pass the domain's policy in core.
67
+ "seat-context-admission": { unsupported: "the hydration fills no context-line slot — the seat's room context is the catch-up read, which core already admits" },
68
+ // The same missing invoke route the component-plane row above names: a card needs an Adaptive Card
69
+ // PLUS a tap that resolves against the UNIFIED seat's parked request, and `teams-front.ts` forwards
70
+ // every `invoke` to the native channel's own session, so nothing here can card in place. See the
71
+ // roster row for what that costs and where it does NOT cost anything.
72
+ "approval-card": { unsupported: "no invoke route resolves a tap against a unified seat's parked request. A governed act that takes the venue walk is redirected to a card-capable room in the SAME domain, or refused when the domain holds none. The paths OUTSIDE that walk do not: the spend checkpoint (spend/checkpoint.ts `requireSessionContinuation`) fails OPEN here and records the crossing in an audit row instead" },
73
+ // THE TRACE ARRIVES EVEN THOUGH THE BUTTON CANNOT (OPE-1854). A judge-lifted act posts its
74
+ // notice here as a plain message activity through the shared road (`postJudgeLiftNotice`), on this
75
+ // surface's own `SurfaceMessageOps` transport. It carries the overrule as an INSTRUCTION in
76
+ // words, with the action id a reviewer finds the act by, because no tap can land here.
77
+ // 🔴 BARE, NOT `{ supported: "…" }`. A noted cell is exempt from the overclaim arm, and the
78
+ // arm is the point: drop this bundle's text transport and the notice silently stops arriving.
79
+ "judge-lift-notice": "supported",
80
+ "name-drop-mention": { unsupported: "not built yet — Leg A wakes on an @mention or room chatter, never on a name-drop match" },
81
+ // 🔴 THE ACTIVITY TYPE IS THE FILTER. Teams sends an edit as a `messageUpdate` activity, and the
82
+ // inbound parser admits `message`, `invoke` and `conversationUpdate` only, so the edit resolves to
83
+ // null before `teams-front.ts` sees it. `teams-front.ts` forwards every non-`message` activity to
84
+ // the native channel for the same reason — there is nothing on this surface to gate.
85
+ "message-edit-wake": { unsupported: "an edit arrives as a `messageUpdate` activity, and the inbound parser admits `message`, `invoke` and `conversationUpdate` only" },
86
+ // The `;messageid=` reply chain (§4.6.1).
87
+ threads: "supported",
88
+ rooms: "supported",
89
+ dm: "supported",
90
+ mpim: "supported",
91
+
92
+ // --- core verbs (§7.2) -----------------------------------------------------------------------
93
+ reply: "supported",
94
+ edit_reply: "supported",
95
+ // PERMANENT. Microsoft Graph carries an outbound reaction route, but it refuses the
96
+ // application-only context OE runs in — there is no primitive an app identity can drive (§7.3).
97
+ react: { unsupported: "vendor limit — Graph's outbound reaction route refuses an application-only identity" },
98
+ no_reply: "supported",
99
+ read_channel: "supported",
100
+ read_thread: "supported",
101
+ search: { unsupported: "no work-search adapter — Teams is a chat surface, not a work corpus (§4.7)" },
102
+ post_message: "supported",
103
+ dm_person: "supported",
104
+ set_dm_delivery: "supported",
105
+ // Teams' six fixed reaction values carry no raised hand, so the stop trigger is `angry` — the one
106
+ // of the six people already use to mean "this is wrong" (§4.8).
107
+ stop: { supported: "the `angry` reaction — Teams' six fixed values carry no ✋" },
108
+ ack_stop_glyph: { unsupported: "vendor limit — Teams' fixed six-value reaction vocabulary has no raised hand" },
109
+ ack_cancel_note: "supported",
110
+ stand_down: { supported: "marks the conversation row; refuses on a 1:1 and when no row exists" },
111
+ post_file: { unsupported: "not built yet — no Teams attachment transport is wired (§7.3)" },
112
+ };
@@ -0,0 +1,36 @@
1
+ import type { BotDisplayNameArm } from "@open-everywhere/core/agent/adapter-api.js";
2
+ import { type TeamsGraphRequester } from "./teams-read.js";
3
+ /**
4
+ * The app's own display name from its service principal, or `undefined` when Graph cannot answer.
5
+ * Pure but for the injected `req` — exported (not just the live wrapper below) so the URL, the
6
+ * shape read and the fail-soft rules are unit-testable with a canned requester, no token mint and
7
+ * no network call. Same DI shape as teams-fire-eligibility.ts's probes.
8
+ *
9
+ * FAIL-SOFT on everything: an empty appId, a thrown request (`liveTeamsGraphRequest` throws on a
10
+ * missing token or a non-ok response), a response that is not an object, and a `displayName` that is
11
+ * absent, non-string or blank all read `undefined`.
12
+ */
13
+ export declare function teamsBotDisplayNameViaGraph(appId: string, req: TeamsGraphRequester): Promise<string | undefined>;
14
+ /**
15
+ * The Teams app-CATALOG display name (OPE-609) — what the Teams client renders beside a message —
16
+ * or `undefined` when Graph cannot answer. FAIL-SOFT identically to {@link teamsBotDisplayNameViaGraph}:
17
+ * a missing `AppCatalog.Read.All` consent throws a 403 inside `req`, caught here, so an unconsented
18
+ * or de-consented tenant reads `undefined` rather than blocking a turn. An empty `value` array (no
19
+ * catalog entry matches the app id) also reads `undefined`.
20
+ */
21
+ export declare function teamsCatalogDisplayNameViaGraph(appId: string, req: TeamsGraphRequester): Promise<string | undefined>;
22
+ /**
23
+ * The full resolution order (OPE-609): the catalog name first, the service-principal name second,
24
+ * `undefined` last. Pure but for the injected `req` — one requester serves both legs, so a test can
25
+ * assert exactly how many calls each leg made. Exported so the live arm and its tests share one
26
+ * definition of "what order do the two legs run in."
27
+ */
28
+ export declare function resolveTeamsBotDisplayNameViaGraph(appId: string, req: TeamsGraphRequester): Promise<string | undefined>;
29
+ /** Live arm: {@link resolveTeamsBotDisplayNameViaGraph} through the real app id + Graph transport,
30
+ * resolved once per instance. `resolveTeamsAppId()` throws when MICROSOFT_APP_ID is unset, so it
31
+ * is inside the try — an unconfigured deployment reads `undefined`, never an exception into
32
+ * instruction assembly. */
33
+ export declare const teamsBotDisplayName: BotDisplayNameArm;
34
+ /** Test seam: drop the memoised name so one test cannot inherit another's. */
35
+ export declare function _resetTeamsBotDisplayNameForTest(): void;
36
+ //# sourceMappingURL=teams-bot-name.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"teams-bot-name.d.ts","sourceRoot":"","sources":["../../../agent/channels/teams-bot-name.ts"],"names":[],"mappings":"AA8CA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAC;AACpF,OAAO,EAAyB,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AA8BlF;;;;;;;;;GASG;AACH,wBAAsB,2BAA2B,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAQtH;AAED;;;;;;GAMG;AACH,wBAAsB,+BAA+B,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAW1H;AAED;;;;;GAKG;AACH,wBAAsB,kCAAkC,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAI7H;AAKD;;;4BAG4B;AAC5B,eAAO,MAAM,mBAAmB,EAAE,iBAoBjC,CAAC;AAEF,8EAA8E;AAC9E,wBAAgB,gCAAgC,IAAI,IAAI,CAEvD"}
@@ -0,0 +1,141 @@
1
+ // teams-bot-name — the bot's OWN display name on Teams, read from Microsoft Graph (OPE-587,
2
+ // catalog leg OPE-609). The teams arm of core's per-surface bot-display-name registry
3
+ // (identity/bot-display-name.ts), so a Teams-pinned session introduces the agent by a real name
4
+ // instead of the generic identity line.
5
+ //
6
+ // ── TWO NAMES, TRIED IN ORDER ────────────────────────────────────────────────────────────────
7
+ // 1. THE TEAMS APP-CATALOG NAME — `teamsApps.displayName` — is what the Teams client actually
8
+ // renders beside a message (the manifest's `name.short`). Reading it needs a tenant-admin-
9
+ // consented `AppCatalog.Read.All`; OPE-587 shipped without this leg because that call 403'd
10
+ // (`Missing role permissions`) on the only tenant available at the time. OPE-609 grants that
11
+ // consent, so this arm now tries the catalog first:
12
+ // GET /v1.0/appCatalogs/teamsApps?$filter=externalId eq '{MICROSOFT_APP_ID}'&$expand=appDefinitions
13
+ // NOT RE-MEASURED against a live 200 response as of this change — the consent was granted but a
14
+ // fresh probe of the catalog call itself was not run. The fail-soft path below means a wrong
15
+ // guess about the response shape degrades to the fallback below, never to a broken turn.
16
+ // 2. THE SERVICE PRINCIPAL NAME — the ENTRA APP REGISTRATION display name — is the pre-OPE-609
17
+ // fallback and MEASURED LIVE against the e2e Teams tenant on 2026-08-15:
18
+ // GET /v1.0/servicePrincipals(appId='{MICROSOFT_APP_ID}')?$select=displayName
19
+ // The token carried exactly ONE application role (the RSC grant `Group.Selected`) and this call
20
+ // still returned HTTP 200 — an IMPLICIT self-read of the app's own directory object, needing no
21
+ // directory-read permission at all. `GET /users/{id}?$select=displayName` on the SAME token
22
+ // returned 403, confirming it is not a general directory-read grant. This is the name a
23
+ // deployment gets when `AppCatalog.Read.All` is unconsented, revoked, or the catalog call fails
24
+ // for any other reason — it can differ from the catalog name in case and spacing (measured in
25
+ // the e2e tenant: `open-everywhere` here versus `Open Everywhere` in the catalog / manifest).
26
+ //
27
+ // WHY THE SERVICE PRINCIPAL, NOT `/applications`, FOR LEG 2. `GET /v1.0/applications(appId='…')`
28
+ // answered 200 with the same string in the same probe, so both work for a SINGLE-TENANT bot. They
29
+ // stop being equivalent for a MULTI-TENANT one: the `application` object lives only in the app's
30
+ // HOME tenant, while a `servicePrincipal` exists in EVERY tenant that consented to the app — and
31
+ // the Graph token this arm rides is minted per tenant (`resolveTeamsTenantId`). The service
32
+ // principal is therefore the object that exists wherever the token points. NOT VERIFIED: only one
33
+ // tenant was available, so the foreign-tenant self-read is reasoned, not measured. Either leg fails
34
+ // soft: an unresolvable name renders the generic identity line, which is what a Teams session got
35
+ // before this arm existed.
36
+ //
37
+ // ── CACHING ──────────────────────────────────────────────────────────────────────────────────
38
+ // One memoised promise per instance, matching the slack arm's `botDisplayName()` exactly: the
39
+ // instruction assembler renders the identity line every turn, so an uncached arm would add a Graph
40
+ // round trip per turn and could flip the standing block's bytes mid-session. A rename needs a cold
41
+ // start to take effect. A FAILED resolution is cached too — the same posture — so a deployment
42
+ // missing the token does not re-probe Graph on every turn. The 5s timeout bounds BOTH legs
43
+ // together (one race around the whole two-call chain), not each leg separately — a hung catalog
44
+ // call cannot silently double the turn's worst-case wait by handing the fallback leg its own budget.
45
+ import { resolveTeamsAppId } from "eve/channels/teams";
46
+ import { liveTeamsGraphRequest } from "./teams-read.js";
47
+ const GRAPH_BASE = "https://graph.microsoft.com/v1.0";
48
+ /** A Graph `displayName` field reduced to a usable string, or `undefined` for any other shape
49
+ * (absent, non-string, blank) — shared by both name legs so "what counts as a name" is defined
50
+ * exactly once. */
51
+ function trimmedName(value) {
52
+ if (typeof value !== "string")
53
+ return undefined;
54
+ const trimmed = value.trim();
55
+ return trimmed.length > 0 ? trimmed : undefined;
56
+ }
57
+ /**
58
+ * The app's own display name from its service principal, or `undefined` when Graph cannot answer.
59
+ * Pure but for the injected `req` — exported (not just the live wrapper below) so the URL, the
60
+ * shape read and the fail-soft rules are unit-testable with a canned requester, no token mint and
61
+ * no network call. Same DI shape as teams-fire-eligibility.ts's probes.
62
+ *
63
+ * FAIL-SOFT on everything: an empty appId, a thrown request (`liveTeamsGraphRequest` throws on a
64
+ * missing token or a non-ok response), a response that is not an object, and a `displayName` that is
65
+ * absent, non-string or blank all read `undefined`.
66
+ */
67
+ export async function teamsBotDisplayNameViaGraph(appId, req) {
68
+ if (appId.length === 0)
69
+ return undefined;
70
+ try {
71
+ const json = (await req(`${GRAPH_BASE}/servicePrincipals(appId='${encodeURIComponent(appId)}')?$select=displayName`));
72
+ return trimmedName(json?.displayName);
73
+ }
74
+ catch {
75
+ return undefined; // no token / 401 / 403 / 404 / transport failure ⇒ core renders the generic line
76
+ }
77
+ }
78
+ /**
79
+ * The Teams app-CATALOG display name (OPE-609) — what the Teams client renders beside a message —
80
+ * or `undefined` when Graph cannot answer. FAIL-SOFT identically to {@link teamsBotDisplayNameViaGraph}:
81
+ * a missing `AppCatalog.Read.All` consent throws a 403 inside `req`, caught here, so an unconsented
82
+ * or de-consented tenant reads `undefined` rather than blocking a turn. An empty `value` array (no
83
+ * catalog entry matches the app id) also reads `undefined`.
84
+ */
85
+ export async function teamsCatalogDisplayNameViaGraph(appId, req) {
86
+ if (appId.length === 0)
87
+ return undefined;
88
+ try {
89
+ const json = (await req(`${GRAPH_BASE}/appCatalogs/teamsApps?$filter=externalId eq '${encodeURIComponent(appId)}'&$expand=appDefinitions`));
90
+ const entry = Array.isArray(json?.value) ? json.value[0] : undefined;
91
+ return trimmedName(entry?.displayName) ?? trimmedName(entry?.appDefinitions?.[0]?.displayName);
92
+ }
93
+ catch {
94
+ return undefined; // no consent / 401 / 403 / 404 / transport failure ⇒ fall back to the service principal
95
+ }
96
+ }
97
+ /**
98
+ * The full resolution order (OPE-609): the catalog name first, the service-principal name second,
99
+ * `undefined` last. Pure but for the injected `req` — one requester serves both legs, so a test can
100
+ * assert exactly how many calls each leg made. Exported so the live arm and its tests share one
101
+ * definition of "what order do the two legs run in."
102
+ */
103
+ export async function resolveTeamsBotDisplayNameViaGraph(appId, req) {
104
+ const catalog = await teamsCatalogDisplayNameViaGraph(appId, req);
105
+ if (catalog !== undefined)
106
+ return catalog;
107
+ return teamsBotDisplayNameViaGraph(appId, req);
108
+ }
109
+ /** The memoised per-instance promise — see the CACHING note in the header. */
110
+ let displayNamePromise;
111
+ /** Live arm: {@link resolveTeamsBotDisplayNameViaGraph} through the real app id + Graph transport,
112
+ * resolved once per instance. `resolveTeamsAppId()` throws when MICROSOFT_APP_ID is unset, so it
113
+ * is inside the try — an unconfigured deployment reads `undefined`, never an exception into
114
+ * instruction assembly. */
115
+ export const teamsBotDisplayName = () => {
116
+ if (!displayNamePromise) {
117
+ displayNamePromise = (async () => {
118
+ try {
119
+ // The memoised value is a PROMISE and instruction assembly awaits it on every Teams turn,
120
+ // so a hung Graph call would block every turn on the instance, not one. The race bounds
121
+ // it: a stall settles the memo to `undefined` (the generic line), same as any other miss.
122
+ // The bound wraps BOTH legs (see the CACHING header note) — a slow catalog call eats into
123
+ // the same 5s budget the fallback leg would otherwise need.
124
+ const timeout = new Promise((resolve) => {
125
+ const t = setTimeout(() => resolve(undefined), 5000);
126
+ t.unref?.();
127
+ });
128
+ return await Promise.race([resolveTeamsBotDisplayNameViaGraph(await resolveTeamsAppId(), liveTeamsGraphRequest), timeout]);
129
+ }
130
+ catch {
131
+ return undefined;
132
+ }
133
+ })();
134
+ }
135
+ return displayNamePromise;
136
+ };
137
+ /** Test seam: drop the memoised name so one test cannot inherit another's. */
138
+ export function _resetTeamsBotDisplayNameForTest() {
139
+ displayNamePromise = undefined;
140
+ }
141
+ //# sourceMappingURL=teams-bot-name.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"teams-bot-name.js","sourceRoot":"","sources":["../../../agent/channels/teams-bot-name.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ import { type TeamsGraphRequester } from "./teams-read.js";
2
+ import { type AuditInput, type NormalizedMessage, type SeatCatchUpFetch } from "@open-everywhere/core/agent/adapter-api.js";
3
+ /** Split the `<teamId>/<channelId>` container this surface's catch-up is addressed by. Returns
4
+ * undefined for anything else rather than guessing a half — a partial address would read another
5
+ * team's channel, the silent mis-address the wake-container design refuses. */
6
+ export declare function parseTeamsCatchUpContainer(container: string): {
7
+ teamId: string;
8
+ channelId: string;
9
+ } | undefined;
10
+ /** Injected seam so the fetch drives offline in tests; defaults to the live Graph transport. */
11
+ export interface TeamsCatchUpDeps {
12
+ readonly request: TeamsGraphRequester;
13
+ /** The deployment's own Entra object id, so its prior posts read as `self`. Absent ⇒ they arrive
14
+ * tagged `bot` (an application author), which is visible and honest, never a crash. */
15
+ readonly selfActorId?: string;
16
+ /** The audit writer. Injected so the contract test reads the row this fetch emits instead of a
17
+ * store; live callers get {@link appendAudit}, which never throws by its own contract. */
18
+ readonly audit?: (entry: AuditInput) => Promise<void>;
19
+ }
20
+ /** The audit action this read records under. One family, one spelling — a query for the next red
21
+ * ship is `action = "teams.catch_up"` and nothing else. */
22
+ export declare const TEAMS_CATCH_UP_ACTION = "teams.catch_up";
23
+ /** The `<platform>/<workspace>/<surface>` domain-grain key the shared path hands down, split for the
24
+ * audit row's own WHERE columns. Returns undefined for an absent key or a non-Teams one rather than
25
+ * guessing a workspace — the row then says `unknown`, which is a fact, not a filled-in blank.
26
+ * Pure; exported so the contract test pins the parse rather than re-deriving it. */
27
+ export declare function teamsCatchUpScopeFromChannelKey(channelKey: string | undefined): {
28
+ workspace: string;
29
+ surface: string;
30
+ } | undefined;
31
+ /** The fetch, with its seams exposed so the contract test drives it with no network. */
32
+ export declare function fetchTeamsCatchUpWindow(req: {
33
+ container: string;
34
+ threadRoot?: string;
35
+ sinceMs: number;
36
+ channelKey?: string;
37
+ }, deps?: TeamsCatchUpDeps): Promise<readonly NormalizedMessage[]>;
38
+ /**
39
+ * A Graph message's `createdDateTime` carries MILLISECOND grain (OPE-1847,
40
+ * `core/agent/channels/seat-catch-up.ts` {@link SeatCatchUpDeclaration.timestampPrecisionMs}).
41
+ * 🔴 ONE MILLISECOND IS NOT ZERO WIDENING. The road widens the LOWER bound by nothing here and the
42
+ * UPPER bound by exactly one millisecond, so a message stamped at the trigger's own instant is
43
+ * INSIDE the window and `excludeId` is the only thing that removes the trigger. A rendered Teams
44
+ * line carries the Graph message id in its `ts` slot, which is the value the wake path passes as
45
+ * `excludeId`.
46
+ * 🔴 IT IS STILL DECLARED, and the parity test requires it. The declaration is what makes "this
47
+ * surface needs the finest unit" a CHECKED claim rather than an omission, and an omission is
48
+ * indistinguishable from the GitHub and Notion defect this whole mechanism exists to close.
49
+ */
50
+ export declare const TEAMS_TIMESTAMP_PRECISION_MS = 1;
51
+ /** Teams' registered {@link SeatCatchUpFetch} — the one thing this surface contributes to the shared path. */
52
+ export declare const teamsSeatCatchUpFetch: SeatCatchUpFetch;
53
+ //# sourceMappingURL=teams-catch-up.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"teams-catch-up.d.ts","sourceRoot":"","sources":["../../../agent/channels/teams-catch-up.ts"],"names":[],"mappings":"AAyCA,OAAO,EAAwG,KAAK,mBAAmB,EAA0B,MAAM,iBAAiB,CAAC;AACzL,OAAO,EAA6C,KAAK,UAAU,EAAE,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,MAAM,4CAA4C,CAAC;AAMvK;;gFAEgF;AAChF,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAO/G;AA0CD,gGAAgG;AAChG,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IACtC;4FACwF;IACxF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;+FAC2F;IAC3F,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvD;AAID;4DAC4D;AAC5D,eAAO,MAAM,qBAAqB,mBAAmB,CAAC;AAEtD;;;qFAGqF;AACrF,wBAAgB,+BAA+B,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAKlI;AAmBD,wFAAwF;AACxF,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,EACrF,IAAI,GAAE,gBAA2B,GAChC,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CAqEvC;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAE9C,8GAA8G;AAC9G,eAAO,MAAM,qBAAqB,EAAE,gBAQhC,CAAC"}