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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +21 -0
  3. package/README.md +46 -2
  4. package/agent/channels/teams-bot-name.ts +162 -0
  5. package/agent/channels/teams-catch-up.ts +244 -0
  6. package/agent/channels/teams-connector.ts +183 -0
  7. package/agent/channels/teams-conversation-ref.ts +398 -0
  8. package/agent/channels/teams-directory.ts +193 -0
  9. package/agent/channels/teams-engagement.ts +391 -0
  10. package/agent/channels/teams-fire-eligibility.ts +152 -0
  11. package/agent/channels/teams-front.ts +1404 -0
  12. package/agent/channels/teams-graph-boundary.ts +178 -0
  13. package/agent/channels/teams-graph-team-id.ts +106 -0
  14. package/agent/channels/teams-id-restore.ts +131 -0
  15. package/agent/channels/teams-identity.ts +395 -0
  16. package/agent/channels/teams-input-response.ts +129 -0
  17. package/agent/channels/teams-message-ops.ts +79 -0
  18. package/agent/channels/teams-origin-codec.ts +104 -0
  19. package/agent/channels/teams-post-arm.ts +323 -0
  20. package/agent/channels/teams-reaction-feedback.ts +127 -0
  21. package/agent/channels/teams-read.ts +510 -0
  22. package/agent/channels/teams-roster.ts +136 -0
  23. package/agent/channels/teams-stop.ts +319 -0
  24. package/agent/channels/teams-supervisor.ts +638 -0
  25. package/agent/channels/teams.ts +270 -0
  26. package/agent/contributions.ts +31 -0
  27. package/agent/docs-generated.ts +15 -0
  28. package/agent/ops.ts +129 -0
  29. package/agent/provider-events.ts +69 -0
  30. package/agent/register.ts +219 -0
  31. package/agent/surface-parity.ts +112 -0
  32. package/dist/agent/channels/teams-bot-name.d.ts +36 -0
  33. package/dist/agent/channels/teams-bot-name.d.ts.map +1 -0
  34. package/dist/agent/channels/teams-bot-name.js +141 -0
  35. package/dist/agent/channels/teams-bot-name.js.map +1 -0
  36. package/dist/agent/channels/teams-catch-up.d.ts +53 -0
  37. package/dist/agent/channels/teams-catch-up.d.ts.map +1 -0
  38. package/dist/agent/channels/teams-catch-up.js +221 -0
  39. package/dist/agent/channels/teams-catch-up.js.map +1 -0
  40. package/dist/agent/channels/teams-connector.d.ts +57 -0
  41. package/dist/agent/channels/teams-connector.d.ts.map +1 -0
  42. package/dist/agent/channels/teams-connector.js +132 -0
  43. package/dist/agent/channels/teams-connector.js.map +1 -0
  44. package/dist/agent/channels/teams-conversation-ref.d.ts +123 -0
  45. package/dist/agent/channels/teams-conversation-ref.d.ts.map +1 -0
  46. package/dist/agent/channels/teams-conversation-ref.js +368 -0
  47. package/dist/agent/channels/teams-conversation-ref.js.map +1 -0
  48. package/dist/agent/channels/teams-directory.d.ts +11 -0
  49. package/dist/agent/channels/teams-directory.d.ts.map +1 -0
  50. package/dist/agent/channels/teams-directory.js +179 -0
  51. package/dist/agent/channels/teams-directory.js.map +1 -0
  52. package/dist/agent/channels/teams-engagement.d.ts +174 -0
  53. package/dist/agent/channels/teams-engagement.d.ts.map +1 -0
  54. package/dist/agent/channels/teams-engagement.js +336 -0
  55. package/dist/agent/channels/teams-engagement.js.map +1 -0
  56. package/dist/agent/channels/teams-fire-eligibility.d.ts +26 -0
  57. package/dist/agent/channels/teams-fire-eligibility.d.ts.map +1 -0
  58. package/dist/agent/channels/teams-fire-eligibility.js +126 -0
  59. package/dist/agent/channels/teams-fire-eligibility.js.map +1 -0
  60. package/dist/agent/channels/teams-front.d.ts +249 -0
  61. package/dist/agent/channels/teams-front.d.ts.map +1 -0
  62. package/dist/agent/channels/teams-front.js +1143 -0
  63. package/dist/agent/channels/teams-front.js.map +1 -0
  64. package/dist/agent/channels/teams-graph-boundary.d.ts +45 -0
  65. package/dist/agent/channels/teams-graph-boundary.d.ts.map +1 -0
  66. package/dist/agent/channels/teams-graph-boundary.js +168 -0
  67. package/dist/agent/channels/teams-graph-boundary.js.map +1 -0
  68. package/dist/agent/channels/teams-graph-team-id.d.ts +30 -0
  69. package/dist/agent/channels/teams-graph-team-id.d.ts.map +1 -0
  70. package/dist/agent/channels/teams-graph-team-id.js +89 -0
  71. package/dist/agent/channels/teams-graph-team-id.js.map +1 -0
  72. package/dist/agent/channels/teams-id-restore.d.ts +34 -0
  73. package/dist/agent/channels/teams-id-restore.d.ts.map +1 -0
  74. package/dist/agent/channels/teams-id-restore.js +123 -0
  75. package/dist/agent/channels/teams-id-restore.js.map +1 -0
  76. package/dist/agent/channels/teams-identity.d.ts +148 -0
  77. package/dist/agent/channels/teams-identity.d.ts.map +1 -0
  78. package/dist/agent/channels/teams-identity.js +336 -0
  79. package/dist/agent/channels/teams-identity.js.map +1 -0
  80. package/dist/agent/channels/teams-input-response.d.ts +23 -0
  81. package/dist/agent/channels/teams-input-response.d.ts.map +1 -0
  82. package/dist/agent/channels/teams-input-response.js +102 -0
  83. package/dist/agent/channels/teams-input-response.js.map +1 -0
  84. package/dist/agent/channels/teams-message-ops.d.ts +3 -0
  85. package/dist/agent/channels/teams-message-ops.d.ts.map +1 -0
  86. package/dist/agent/channels/teams-message-ops.js +72 -0
  87. package/dist/agent/channels/teams-message-ops.js.map +1 -0
  88. package/dist/agent/channels/teams-origin-codec.d.ts +16 -0
  89. package/dist/agent/channels/teams-origin-codec.d.ts.map +1 -0
  90. package/dist/agent/channels/teams-origin-codec.js +99 -0
  91. package/dist/agent/channels/teams-origin-codec.js.map +1 -0
  92. package/dist/agent/channels/teams-post-arm.d.ts +37 -0
  93. package/dist/agent/channels/teams-post-arm.d.ts.map +1 -0
  94. package/dist/agent/channels/teams-post-arm.js +280 -0
  95. package/dist/agent/channels/teams-post-arm.js.map +1 -0
  96. package/dist/agent/channels/teams-reaction-feedback.d.ts +22 -0
  97. package/dist/agent/channels/teams-reaction-feedback.d.ts.map +1 -0
  98. package/dist/agent/channels/teams-reaction-feedback.js +108 -0
  99. package/dist/agent/channels/teams-reaction-feedback.js.map +1 -0
  100. package/dist/agent/channels/teams-read.d.ts +127 -0
  101. package/dist/agent/channels/teams-read.d.ts.map +1 -0
  102. package/dist/agent/channels/teams-read.js +424 -0
  103. package/dist/agent/channels/teams-read.js.map +1 -0
  104. package/dist/agent/channels/teams-roster.d.ts +54 -0
  105. package/dist/agent/channels/teams-roster.d.ts.map +1 -0
  106. package/dist/agent/channels/teams-roster.js +121 -0
  107. package/dist/agent/channels/teams-roster.js.map +1 -0
  108. package/dist/agent/channels/teams-stop.d.ts +74 -0
  109. package/dist/agent/channels/teams-stop.d.ts.map +1 -0
  110. package/dist/agent/channels/teams-stop.js +252 -0
  111. package/dist/agent/channels/teams-stop.js.map +1 -0
  112. package/dist/agent/channels/teams-supervisor.d.ts +351 -0
  113. package/dist/agent/channels/teams-supervisor.d.ts.map +1 -0
  114. package/dist/agent/channels/teams-supervisor.js +366 -0
  115. package/dist/agent/channels/teams-supervisor.js.map +1 -0
  116. package/dist/agent/channels/teams.d.ts +5 -0
  117. package/dist/agent/channels/teams.d.ts.map +1 -0
  118. package/dist/agent/channels/teams.js +246 -0
  119. package/dist/agent/channels/teams.js.map +1 -0
  120. package/dist/agent/contributions.d.ts +12 -0
  121. package/dist/agent/contributions.d.ts.map +1 -0
  122. package/dist/agent/contributions.js +31 -0
  123. package/dist/agent/contributions.js.map +1 -0
  124. package/dist/agent/docs-generated.d.ts +3 -0
  125. package/dist/agent/docs-generated.d.ts.map +1 -0
  126. package/dist/agent/docs-generated.js +13 -0
  127. package/dist/agent/docs-generated.js.map +1 -0
  128. package/dist/agent/ops.d.ts +14 -0
  129. package/dist/agent/ops.d.ts.map +1 -0
  130. package/dist/agent/ops.js +122 -0
  131. package/dist/agent/ops.js.map +1 -0
  132. package/dist/agent/provider-events.d.ts +3 -0
  133. package/dist/agent/provider-events.d.ts.map +1 -0
  134. package/dist/agent/provider-events.js +67 -0
  135. package/dist/agent/provider-events.js.map +1 -0
  136. package/dist/agent/register.d.ts +16 -0
  137. package/dist/agent/register.d.ts.map +1 -0
  138. package/dist/agent/register.js +198 -0
  139. package/dist/agent/register.js.map +1 -0
  140. package/dist/agent/surface-parity.d.ts +3 -0
  141. package/dist/agent/surface-parity.d.ts.map +1 -0
  142. package/dist/agent/surface-parity.js +106 -0
  143. package/dist/agent/surface-parity.js.map +1 -0
  144. package/package.json +183 -4
  145. package/tsconfig.build.json +22 -0
  146. package/tsconfig.json +15 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"teams-input-response.js","sourceRoot":"","sources":["../../../agent/channels/teams-input-response.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { SurfaceMessageOps } from "@open-everywhere/core/agent/adapter-api.js";
2
+ export declare const teamsMessageOps: SurfaceMessageOps;
3
+ //# sourceMappingURL=teams-message-ops.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"teams-message-ops.d.ts","sourceRoot":"","sources":["../../../agent/channels/teams-message-ops.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAC;AAyBpF,eAAO,MAAM,eAAe,EAAE,iBAwB7B,CAAC"}
@@ -0,0 +1,72 @@
1
+ // teams-message-ops — Teams's SurfaceMessageOps (OPE-899): the ancillary message ops core features
2
+ // dispatch through channels/surface-message-ops.ts. Before this file, Teams registered none, so the
3
+ // stop acknowledgement (turn-anchor.ts's `postSurfaceText`) resolved nothing on a Teams turn — the
4
+ // stop control worked (teams-stop.ts, the `angry` reaction), it was just invisible: a person who
5
+ // reacted saw the agent go quiet and read nothing.
6
+ //
7
+ // - postText → the SAME Bot-Framework send/reply the mid-turn post arm uses
8
+ // (teams-post-arm.ts's `dispatchTeamsReply`, exported for this file — one
9
+ // mint/send implementation, never a second copy).
10
+ // - editMessage → the SAME update-activity call `edit_reply` performs (`editTeamsMessage`).
11
+ // - NO `postFiles` (register.ts already records: "NO attachment resolver — the Teams surface
12
+ // carries no attachment plane today"). The optional tier exists exactly for this shape.
13
+ // - NO `setReaction` (register.ts: "NO reaction surface — Teams has no integration reaction
14
+ // primitive"). The `angry` reaction (teams-stop.ts) IS Teams's stop affordance; there is no
15
+ // second glyph to place beside it.
16
+ //
17
+ // THE PROBLEM THIS FILE SOLVES, IN ONE SENTENCE: `SurfaceMessageOps.postText`/`editMessage` are
18
+ // handed only a bare `channelId` (core's `channelId` is Teams's conversation id — see
19
+ // `turn-anchor.ts`'s `turnAnchorAddress`), never a `serviceUrl`, and a Bot-Framework write needs
20
+ // BOTH. `teams-conversation-ref.ts`'s `conversation`-grain address (OPE-899's addition to that
21
+ // module) is the lookup that answers it: every inbound activity, on every scope, stamps a row keyed
22
+ // on its EXACT conversation id, so the reply chain a channel turn anchored on is resolvable here by
23
+ // that same id.
24
+ //
25
+ // `opts.replyToMessageId` on `postText` threads the post via `replyToActivityId` — the SAME field
26
+ // the mid-turn reply arm anchors on. `turn-anchor.ts`'s own cancel-note call passes no such option
27
+ // (a top-level post, like every other surface's cancel note today), so this is exercised by other
28
+ // SurfaceMessageOps consumers (the approval decision notice), not by the stop path itself.
29
+ import { dispatchTeamsReply, editTeamsMessage } from "./teams-post-arm.js";
30
+ import { teamsConversationRef } from "./teams-conversation-ref.js";
31
+ import { restoreTeamsConversationId } from "./teams-id-restore.js";
32
+ /**
33
+ * The stored `conversation`-grain reference for `channelId`, or a loud, honest refusal — never a
34
+ * guessed target. Mirrors `github-message-ops.ts`'s `requireContainer`.
35
+ *
36
+ * 🔴 IT RESTORES THE RAW ID FIRST (OPE-1078). `channelId` reaches here in one of two spellings: raw,
37
+ * from a live turn's own anchor, or the SURFACE-KEY spelling, from a granted `teams:channel/<id>`
38
+ * address — the only spelling the resource-grant grammar admits. `restoreTeamsConversationId` is the
39
+ * identity on the first and the index lookup on the second, so both land on the same reference. The
40
+ * refusal below still names the id the CALLER gave, which is the one an operator can act on.
41
+ */
42
+ async function requireConversationRef(channelId) {
43
+ const ref = await teamsConversationRef(await restoreTeamsConversationId(channelId));
44
+ if (!ref) {
45
+ throw new Error(`Teams surface message ops: no conversation reference for "${channelId}" — this deployment has never seen an inbound activity on that id.`);
46
+ }
47
+ return ref;
48
+ }
49
+ export const teamsMessageOps = {
50
+ platform: "teams",
51
+ /** Post through the mid-turn reply transport, resolving `serviceUrl` from the stored conversation
52
+ * reference. `opts?.replyToMessageId` threads the post — see the header. */
53
+ async postText(channelId, text, opts) {
54
+ const { serviceUrl, conversationId } = await requireConversationRef(channelId);
55
+ const target = {
56
+ platform: "teams",
57
+ serviceUrl,
58
+ conversationId,
59
+ ...(opts?.replyToMessageId ? { replyToActivityId: opts.replyToMessageId } : {}),
60
+ };
61
+ return dispatchTeamsReply(target, text);
62
+ },
63
+ /** Update the given activity in place — the same transport `edit_reply` performs on the reply
64
+ * spine, resolving `serviceUrl` the same way `postText` does. It sends the RESOLVED conversation
65
+ * id, never the caller's, for the reason `requireConversationRef` states: the caller's may be the
66
+ * surface-key spelling, and the Connector accepts only the raw id. */
67
+ async editMessage(channelId, messageId, text) {
68
+ const { serviceUrl, conversationId } = await requireConversationRef(channelId);
69
+ await editTeamsMessage({ platform: "teams", channelId: conversationId, serviceUrl }, messageId, text);
70
+ },
71
+ };
72
+ //# sourceMappingURL=teams-message-ops.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"teams-message-ops.js","sourceRoot":"","sources":["../../../agent/channels/teams-message-ops.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,oGAAoG;AACpG,mGAAmG;AACnG,iGAAiG;AACjG,mDAAmD;AACnD,EAAE;AACF,iFAAiF;AACjF,4FAA4F;AAC5F,oEAAoE;AACpE,8FAA8F;AAC9F,+FAA+F;AAC/F,4FAA4F;AAC5F,8FAA8F;AAC9F,gGAAgG;AAChG,uCAAuC;AACvC,EAAE;AACF,gGAAgG;AAChG,sFAAsF;AACtF,iGAAiG;AACjG,+FAA+F;AAC/F,oGAAoG;AACpG,oGAAoG;AACpG,gBAAgB;AAChB,EAAE;AACF,kGAAkG;AAClG,mGAAmG;AACnG,kGAAkG;AAClG,2FAA2F;AAG3F,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAoB,MAAM,qBAAqB,CAAC;AAC7F,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AAEnE;;;;;;;;;GASG;AACH,KAAK,UAAU,sBAAsB,CAAC,SAAiB;IACrD,MAAM,GAAG,GAAG,MAAM,oBAAoB,CAAC,MAAM,0BAA0B,CAAC,SAAS,CAAC,CAAC,CAAC;IACpF,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,6DAA6D,SAAS,oEAAoE,CAC3I,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,CAAC,MAAM,eAAe,GAAsB;IAChD,QAAQ,EAAE,OAAO;IAEjB;iFAC6E;IAC7E,KAAK,CAAC,QAAQ,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI;QAClC,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,GAAG,MAAM,sBAAsB,CAAC,SAAS,CAAC,CAAC;QAC/E,MAAM,MAAM,GAAgB;YAC1B,QAAQ,EAAE,OAAO;YACjB,UAAU;YACV,cAAc;YACd,GAAG,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAChF,CAAC;QACF,OAAO,kBAAkB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC1C,CAAC;IAED;;;2EAGuE;IACvE,KAAK,CAAC,WAAW,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI;QAC1C,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,GAAG,MAAM,sBAAsB,CAAC,SAAS,CAAC,CAAC;QAC/E,MAAM,gBAAgB,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;IACxG,CAAC;CACF,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { UnifiedWakeOrigin, WakeOriginCodec } from "@open-everywhere/core/agent/adapter-api.js";
2
+ type TeamsOrigin = Extract<UnifiedWakeOrigin, {
3
+ platform: "teams";
4
+ }>;
5
+ /**
6
+ * Bot Connector `serviceUrl` host allowlist. The Teams reply POSTs a `client_credentials` bearer
7
+ * token to `${serviceUrl}/v3/conversations/...`, so the host must be a real Microsoft Bot
8
+ * Framework endpoint — not merely https. `serviceUrl` rides in off the inbound activity BODY, and
9
+ * eve's JWT verifier does NOT bind it to the token's `serviceurl` claim, so a scheme-only check
10
+ * (`startsWith("https://")`) would let a body-supplied `https://evil.example` exfiltrate the
11
+ * token. Constrain the host to the documented Bot Connector set. Fail-closed on any parse error.
12
+ */
13
+ export declare function isTrustedTeamsServiceUrl(serviceUrl: unknown): boolean;
14
+ export declare const teamsWakeOriginCodec: WakeOriginCodec<TeamsOrigin>;
15
+ export {};
16
+ //# sourceMappingURL=teams-origin-codec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"teams-origin-codec.d.ts","sourceRoot":"","sources":["../../../agent/channels/teams-origin-codec.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,4CAA4C,CAAC;AAErG,KAAK,WAAW,GAAG,OAAO,CAAC,iBAAiB,EAAE;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAOrE;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,OAAO,GAAG,OAAO,CAYrE;AA0BD,eAAO,MAAM,oBAAoB,EAAE,eAAe,CAAC,WAAW,CA8B7D,CAAC"}
@@ -0,0 +1,99 @@
1
+ // The Teams UnifiedWakeOrigin codec + the Bot-Connector serviceUrl host allowlist — MOVED VERBATIM
2
+ // out of core's channels/wake-origin-codecs.ts (OPE-114 stage 4). STORED-DATA COMPATIBILITY IS THE
3
+ // CONTRACT: a Teams origin is JSON-serialised into the durable operational store and fail-closed
4
+ // re-validated on read, so every behaviour here is frozen by core's
5
+ // scripts/wake-origin-fixtures-test.ts (run from the app root with this bundle's boot wiring —
6
+ // scripts/wake-origin-fixtures-boot.ts). Validator tolerances (typeof-only optional checks, extra
7
+ // unknown fields) are LOAD-BEARING for rows already on disk — never tighten them here.
8
+ //
9
+ // The `UnifiedWakeOrigin` TYPE union (including its teams arm) stays in core — it is the wire
10
+ // schema for stored rows; only the codec BEHAVIOUR lives in the bundle, registered by
11
+ // registerTeamsProvider() (../register.ts). `isTrustedTeamsServiceUrl` is exported for the teams
12
+ // post arm (BOTH of its dispatch paths re-validate the serviceUrl before POSTing the Connector
13
+ // token) and applied here on the stored-row `validate` arm — every route by which a serviceUrl can
14
+ // reach a token POST passes it. (There was a third route, the `dmOrigin` DM-delegation-anchor mint
15
+ // arm, which applied the same allowlist; it went with the anchor at OPE-368 — a removed route needs
16
+ // no check.) Core no longer re-exports the predicate (the stale unified-window re-export was removed
17
+ // in stage 4).
18
+ /** COLON-FREE by construction: a task windowId is `[^:]+` (core channels/window-key.ts), so any colon
19
+ * in a raw surface id (Teams' `19:...@thread.v2;messageid=` conversation ids) is folded to `_` —
20
+ * deterministic, so spawn + ingress still agree on identity. */
21
+ const noColon = (s) => s.replace(/:/g, "_");
22
+ /**
23
+ * Bot Connector `serviceUrl` host allowlist. The Teams reply POSTs a `client_credentials` bearer
24
+ * token to `${serviceUrl}/v3/conversations/...`, so the host must be a real Microsoft Bot
25
+ * Framework endpoint — not merely https. `serviceUrl` rides in off the inbound activity BODY, and
26
+ * eve's JWT verifier does NOT bind it to the token's `serviceurl` claim, so a scheme-only check
27
+ * (`startsWith("https://")`) would let a body-supplied `https://evil.example` exfiltrate the
28
+ * token. Constrain the host to the documented Bot Connector set. Fail-closed on any parse error.
29
+ */
30
+ export function isTrustedTeamsServiceUrl(serviceUrl) {
31
+ if (typeof serviceUrl !== "string")
32
+ return false;
33
+ let host;
34
+ try {
35
+ const u = new URL(serviceUrl);
36
+ if (u.protocol !== "https:")
37
+ return false;
38
+ host = u.hostname.toLowerCase();
39
+ }
40
+ catch {
41
+ return false;
42
+ }
43
+ // An entry beginning with "." is a dot-anchored suffix; otherwise an exact host.
44
+ return teamsServiceHostAllowlist().some((h) => (h.startsWith(".") ? host.endsWith(h) : host === h));
45
+ }
46
+ /** The GLOBAL (commercial) cloud Bot Connector hosts. Sovereign/national clouds use DIFFERENT
47
+ * hosts and are NOT in the default (keeping the default minimal, per the no-extrapolation rule):
48
+ * a GCC / GCC High / DoD / 21Vianet operator opts theirs in via OE_TEAMS_SERVICE_HOSTS. Verified
49
+ * values (Microsoft Learn, "Send proactive messages"): GCC `smba.infra.gcc.teams.microsoft.com`,
50
+ * GCC High `smba.infra.gov.teams.microsoft.us`, DoD `smba.infra.dod.teams.microsoft.us`. */
51
+ const DEFAULT_TEAMS_SERVICE_HOSTS = [
52
+ "smba.trafficmanager.net",
53
+ ".smba.trafficmanager.net",
54
+ "botframework.com",
55
+ ".botframework.com",
56
+ ];
57
+ /** The effective serviceUrl host allowlist = the global default PLUS any operator-supplied
58
+ * OE_TEAMS_SERVICE_HOSTS (comma-separated; each entry an exact host or a ".suffix"). This is how
59
+ * a sovereign-cloud deployment admits its own Bot Connector host without weakening the default —
60
+ * the reply's bearer token only ever POSTs to a MATCHED host, all Microsoft-owned. */
61
+ function teamsServiceHostAllowlist() {
62
+ const extra = (process.env.OE_TEAMS_SERVICE_HOSTS ?? "")
63
+ .split(",")
64
+ .map((s) => s.trim().toLowerCase())
65
+ .filter((s) => s.length > 0);
66
+ return extra.length > 0 ? [...DEFAULT_TEAMS_SERVICE_HOSTS, ...extra] : DEFAULT_TEAMS_SERVICE_HOSTS;
67
+ }
68
+ export const teamsWakeOriginCodec = {
69
+ validate: (o) =>
70
+ // The Bot Connector bearer token is POSTed to serviceUrl, so it must be a Microsoft Bot
71
+ // Framework host, not merely https (isTrustedTeamsServiceUrl) — the reply path re-validates
72
+ // regardless, but a bad host must never survive into the map in the first place.
73
+ isTrustedTeamsServiceUrl(o.serviceUrl) &&
74
+ typeof o.conversationId === "string" &&
75
+ o.conversationId.length > 0 &&
76
+ (o.replyToActivityId === undefined || typeof o.replyToActivityId === "string") &&
77
+ (o.tenantId === undefined || typeof o.tenantId === "string"),
78
+ threadRef: (origin) => ({ channelKey: noColon(`teams/${origin.conversationId}`), threadKey: noColon(origin.replyToActivityId ?? origin.conversationId) }),
79
+ // replyToActivityId present ⇔ the trigger sits in a reply chain (the chain IS the thread);
80
+ // absent = a plain conversation send.
81
+ isThreadScoped: (origin) => typeof origin.replyToActivityId === "string" && origin.replyToActivityId.length > 0,
82
+ containerId: (origin) => origin.conversationId,
83
+ // A `dmOrigin` arm lived here (OPE-306 seam, landed OPE-315) to mint a DM delegation anchor's reply
84
+ // origin from the binding's `target` (`serviceUrl` + `conversationId`). OPE-368 deleted the anchor
85
+ // and the interface member with it — a Teams personal/group chat now diverts into its own
86
+ // first-class window (OPE-357), so its origin is minted and STORED at dispatch time like every other
87
+ // wake. NOTHING WEAKENED BY THE REMOVAL: the serviceUrl host allowlist that arm applied at mint is
88
+ // ALSO applied by `validate` above (every stored row) and again by the post arm before any Connector
89
+ // token leaves the process, so `isTrustedTeamsServiceUrl` remains on every route to a token POST.
90
+ //
91
+ // NO `placementRef` AND NO `carryPlacement`, AND THAT ABSENCE IS THE DECLARATION (OPE-1826). Teams
92
+ // keeps its placement INSIDE the conversation identity: `threadRef` keys on `replyToActivityId`,
93
+ // which is the very activity a reply hangs under, so two origins that pass `sameUnifiedConversation`
94
+ // already agree about where the answer lands and there is no gap for a carry to fill. Declaring an
95
+ // axis here would be worse than absent — it would make two origins of ONE thread compare unequal on
96
+ // a field the thread key has already settled. The position is recorded, and held to account against
97
+ // the live registry, by the `origin-placement-carry` row in core's identity/surface-parity.ts.
98
+ };
99
+ //# sourceMappingURL=teams-origin-codec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"teams-origin-codec.js","sourceRoot":"","sources":["../../../agent/channels/teams-origin-codec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { PostArm, ReplyTarget, EditLocus } from "@open-everywhere/core/agent/adapter-api.js";
2
+ /** Exported for `teams-message-ops.ts` (OPE-899) — the SurfaceMessageOps registration reuses this
3
+ * exact shape (serviceUrl + conversationId + an optional reply anchor) rather than inventing a
4
+ * second one; see `dispatchTeamsReply`'s own export note. */
5
+ export type TeamsTarget = Extract<ReplyTarget, {
6
+ platform: "teams";
7
+ }>;
8
+ /** MID-TURN: a Bot-Framework message activity into the captured conversation reference. eve mints the
9
+ * Connector token from MICROSOFT_APP_ID/PASSWORD; check the id for an honest early error. Thread onto
10
+ * the trigger's root activity when the caller resolved one (a channel/group reply chains via
11
+ * replyToTeamsActivity — eve's exact reply semantics, matching the unified arm and channel.thread.post);
12
+ * a plain conversation send otherwise (personal chats, or no root id). */
13
+ /** Exported (OPE-899) so `teams-message-ops.ts`'s SurfaceMessageOps `postText` can post through the
14
+ * SAME transport this mid-turn reply arm uses, rather than a second copy of the Connector send.
15
+ * That module resolves its own `TeamsTarget` from the `conversation`-grain address store
16
+ * (`teams-conversation-ref.ts`, OPE-899) instead of from a live binding, but the target shape and
17
+ * every refusal below are identical either way. */
18
+ export declare function dispatchTeamsReply(target: TeamsTarget, text: string): Promise<string>;
19
+ /**
20
+ * EDIT (OPE-793): rewrite one of our own posted activities via eve's `updateTeamsActivity` (the Bot
21
+ * Framework update-activity call), on the SAME transport and credentials the post arms use. The
22
+ * Teams client shows an "Edited" marker on the activity, so the change is visible in the room.
23
+ *
24
+ * THE SAME TRUSTED-HOST ALLOWLIST BOTH POST ARMS APPLY. This call POSTs a client_credentials
25
+ * Connector bearer token to `locus.serviceUrl`, so it must be a Bot Framework host — the identical
26
+ * check, for the identical reason, and its absence here would be the OPE-315 gap re-opened on a new
27
+ * route. The serviceUrl reaches this arm from the session binding or the frozen wake origin (never
28
+ * model input), and it is re-validated anyway.
29
+ *
30
+ * NO SPLIT, like the Discord arm: one activity is one activity, so text past Teams' own cap is
31
+ * refused rather than truncated. Ownership was decided upstream against the own-post ledger.
32
+ */
33
+ /** Exported (OPE-899) so `teams-message-ops.ts`'s SurfaceMessageOps `editMessage` reuses this SAME
34
+ * update-activity transport rather than a second copy of it. */
35
+ export declare function editTeamsMessage(locus: EditLocus, messageId: string, text: string): Promise<void>;
36
+ export declare const teamsPostArm: PostArm;
37
+ //# sourceMappingURL=teams-post-arm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"teams-post-arm.d.ts","sourceRoot":"","sources":["../../../agent/channels/teams-post-arm.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EACV,OAAO,EAEP,WAAW,EAGX,SAAS,EAGV,MAAM,4CAA4C,CAAC;AAEpD;;8DAE8D;AAC9D,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,EAAE;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAGtE;;;;2EAI2E;AAC3E;;;;oDAIoD;AACpD,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA6B3F;AA2CD;;;;;;;;;;;;;GAaG;AACH;iEACiE;AACjE,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmCvG;AA8HD,eAAO,MAAM,YAAY,EAAE,OAe1B,CAAC"}
@@ -0,0 +1,280 @@
1
+ // teams-post-arm — Teams's POST arm for the post-arms registry (OPE-114 stage 1c). Absorbs, VERBATIM,
2
+ // Teams's arms out of the three reply/delivery spines: reply-post.ts (resolveReplyTarget's teams case
3
+ // + dispatchReply's send/reply branch + the teams splitTeamsMessageText chunking used by
4
+ // postTerminalProse), unified-reply.ts (liveTeamsPost, incl. the trusted-serviceUrl re-validation),
5
+ // and unified-redrive.ts (the teams describe/container cases). The transport is eve's own Bot-Framework
6
+ // Connector token (minted from MICROSOFT_APP_ID/PASSWORD) over a HOST-SIDE Connector fetch — see
7
+ // teams-connector.ts's header for why the send/reply/update calls are no longer eve's own
8
+ // `sendTeamsActivity`/`replyToTeamsActivity`/`updateTeamsActivity` (OPE-1237: eve's own transport
9
+ // throws away the response headers, so there is no way to honour a 429's `Retry-After` by wrapping
10
+ // its return value).
11
+ //
12
+ // ONE DELIBERATE DIVERGENCE FROM "VERBATIM" (OPE-315): dispatchTeamsReply now applies the
13
+ // isTrustedTeamsServiceUrl host allowlist its unified sibling always did. That was a real gap, not a
14
+ // stylistic one — see the check's own comment.
15
+ import { resolveTeamsAppId, splitTeamsMessageText, TEAMS_MESSAGE_TEXT_MAX_LENGTH } from "eve/channels/teams";
16
+ import { teamsConnectorWrite, sendTeamsActivityBackedOff, replyToTeamsActivityBackedOff, updateTeamsActivityBackedOff, } from "./teams-connector.js";
17
+ import { isTrustedTeamsServiceUrl } from "./teams-origin-codec.js";
18
+ import { teamsPersonalConversationRef, teamsSoleTenantRegion, rememberTeamsPersonalConversation, } from "./teams-conversation-ref.js";
19
+ import { restoreTeamsPersonId } from "./teams-id-restore.js";
20
+ import { markNotApplied, carryFailureSignals } from "@open-everywhere/core/agent/adapter-api.js";
21
+ /** MID-TURN: a Bot-Framework message activity into the captured conversation reference. eve mints the
22
+ * Connector token from MICROSOFT_APP_ID/PASSWORD; check the id for an honest early error. Thread onto
23
+ * the trigger's root activity when the caller resolved one (a channel/group reply chains via
24
+ * replyToTeamsActivity — eve's exact reply semantics, matching the unified arm and channel.thread.post);
25
+ * a plain conversation send otherwise (personal chats, or no root id). */
26
+ /** Exported (OPE-899) so `teams-message-ops.ts`'s SurfaceMessageOps `postText` can post through the
27
+ * SAME transport this mid-turn reply arm uses, rather than a second copy of the Connector send.
28
+ * That module resolves its own `TeamsTarget` from the `conversation`-grain address store
29
+ * (`teams-conversation-ref.ts`, OPE-899) instead of from a live binding, but the target shape and
30
+ * every refusal below are identical either way. */
31
+ export async function dispatchTeamsReply(target, text) {
32
+ // Both pre-flight refusals are DEFINITIVE (OPE-821): the call never leaves this host, so nothing
33
+ // reached the room and the post spine may release its dedupe claim for an identical retry.
34
+ if (!process.env.MICROSOFT_APP_ID && !process.env.TEAMS_APP_ID) {
35
+ throw markNotApplied(new Error("reply couldn't post to Teams — MICROSOFT_APP_ID is not set."));
36
+ }
37
+ // 🔴 THE SAME HOST ALLOWLIST THE UNIFIED ARM APPLIES (OPE-315). This path POSTs a client_credentials
38
+ // Connector bearer token to `target.serviceUrl`, and until now it was the ONE serviceUrl route with
39
+ // no host check: the stored-origin path is gated by the codec validator (teams-origin-codec.ts) and
40
+ // re-gated in liveTeamsPost, but a mid-turn target is assembled by resolveTarget from binding
41
+ // attributes with only a non-empty test. Narrow (it takes a rogue Bot Framework channel on the same
42
+ // bot registration to place an attacker host there) but the same class as the unified arm's, and
43
+ // the fix is three lines. Token-free error, mirroring liveTeamsPost's.
44
+ if (!isTrustedTeamsServiceUrl(target.serviceUrl)) {
45
+ throw markNotApplied(new Error(`reply couldn't post to Teams — serviceUrl is not a Bot Framework host (conversation ${target.conversationId})`));
46
+ }
47
+ const res = target.replyToActivityId
48
+ ? await replyToTeamsActivityBackedOff({
49
+ serviceUrl: target.serviceUrl,
50
+ conversationId: target.conversationId,
51
+ activityId: target.replyToActivityId,
52
+ body: { type: "message", text },
53
+ })
54
+ : await sendTeamsActivityBackedOff({
55
+ serviceUrl: target.serviceUrl,
56
+ conversationId: target.conversationId,
57
+ body: { type: "message", text },
58
+ });
59
+ return res.id;
60
+ }
61
+ /** UNIFIED: a Bot-Framework message activity into the frozen conversation reference — the SAME
62
+ * transport + env credentials the mid-turn reply arm uses. Anchored into the reply CHAIN via
63
+ * replyToTeamsActivity when the origin froze a thread-root activity id (teamsThreadRootActivityId),
64
+ * else a plain conversation send. Every target field rides the host-frozen wake origin, never the
65
+ * model. */
66
+ async function liveTeamsPost(origin, text) {
67
+ if (!process.env.MICROSOFT_APP_ID && !process.env.TEAMS_APP_ID) {
68
+ // DEFINITIVE, like the mid-turn arm's pre-flights: nothing left this host.
69
+ throw markNotApplied(new Error(`unified reply couldn't post to Teams — MICROSOFT_APP_ID is not set (conversation ${origin.conversationId})`));
70
+ }
71
+ // Belt-and-braces re-validation (isUnifiedWakeOrigin already enforces it): the Connector bearer
72
+ // token is POSTed to this host, so it must be a Microsoft Bot Framework endpoint — a scheme-only
73
+ // https check would let a body-supplied serviceUrl exfiltrate the token.
74
+ if (!isTrustedTeamsServiceUrl(origin.serviceUrl)) {
75
+ throw markNotApplied(new Error(`unified reply couldn't post to Teams — serviceUrl is not a Bot Framework host (conversation ${origin.conversationId})`));
76
+ }
77
+ let id;
78
+ try {
79
+ const res = origin.replyToActivityId
80
+ ? await replyToTeamsActivityBackedOff({
81
+ serviceUrl: origin.serviceUrl,
82
+ conversationId: origin.conversationId,
83
+ activityId: origin.replyToActivityId,
84
+ body: { type: "message", text },
85
+ })
86
+ : await sendTeamsActivityBackedOff({
87
+ serviceUrl: origin.serviceUrl,
88
+ conversationId: origin.conversationId,
89
+ body: { type: "message", text },
90
+ });
91
+ id = res.id;
92
+ }
93
+ catch (err) {
94
+ // Token-free error: never echo the transport error body (defensive parity with the other arms).
95
+ // AMBIGUOUS unless the Connector gave a status to say otherwise (OPE-821) — a send whose answer
96
+ // never arrived may still have reached the room, so the class carries over rather than being
97
+ // dropped by the re-wrap. A number is the only thing carried; no body, no URL, no token.
98
+ throw carryFailureSignals(err, new Error(`unified reply couldn't post to Teams — the Connector post failed (conversation ${origin.conversationId})`));
99
+ }
100
+ return id || "unknown";
101
+ }
102
+ /**
103
+ * EDIT (OPE-793): rewrite one of our own posted activities via eve's `updateTeamsActivity` (the Bot
104
+ * Framework update-activity call), on the SAME transport and credentials the post arms use. The
105
+ * Teams client shows an "Edited" marker on the activity, so the change is visible in the room.
106
+ *
107
+ * THE SAME TRUSTED-HOST ALLOWLIST BOTH POST ARMS APPLY. This call POSTs a client_credentials
108
+ * Connector bearer token to `locus.serviceUrl`, so it must be a Bot Framework host — the identical
109
+ * check, for the identical reason, and its absence here would be the OPE-315 gap re-opened on a new
110
+ * route. The serviceUrl reaches this arm from the session binding or the frozen wake origin (never
111
+ * model input), and it is re-validated anyway.
112
+ *
113
+ * NO SPLIT, like the Discord arm: one activity is one activity, so text past Teams' own cap is
114
+ * refused rather than truncated. Ownership was decided upstream against the own-post ledger.
115
+ */
116
+ /** Exported (OPE-899) so `teams-message-ops.ts`'s SurfaceMessageOps `editMessage` reuses this SAME
117
+ * update-activity transport rather than a second copy of it. */
118
+ export async function editTeamsMessage(locus, messageId, text) {
119
+ // The three pre-flight refusals are DEFINITIVE (OPE-802): the call never leaves this host, so the
120
+ // message cannot have changed and a fresh `reply` is the right remedy. The Connector failure below
121
+ // is not — a failed update may still have applied — so it stays unmarked and fails closed.
122
+ if (!process.env.MICROSOFT_APP_ID && !process.env.TEAMS_APP_ID) {
123
+ throw markNotApplied(new Error("edit_reply couldn't update the Teams message — MICROSOFT_APP_ID is not set."));
124
+ }
125
+ if (!locus.serviceUrl || !isTrustedTeamsServiceUrl(locus.serviceUrl)) {
126
+ throw markNotApplied(new Error(`edit_reply couldn't update the Teams message — serviceUrl is not a Bot Framework host (conversation ${locus.channelId})`));
127
+ }
128
+ if (text.length > TEAMS_MESSAGE_TEXT_MAX_LENGTH) {
129
+ throw markNotApplied(new Error(`edit_reply couldn't update the Teams message — the new text is ${text.length} characters and Teams caps one message at ${TEAMS_MESSAGE_TEXT_MAX_LENGTH}. An edit cannot split a message; shorten it, or post a new reply.`));
130
+ }
131
+ try {
132
+ await updateTeamsActivityBackedOff({
133
+ serviceUrl: locus.serviceUrl,
134
+ conversationId: locus.channelId,
135
+ activityId: messageId,
136
+ body: { type: "message", text },
137
+ });
138
+ }
139
+ catch (err) {
140
+ // Token-free error, matching both post arms: never echo the transport error body. `err` may now
141
+ // be the NAMED `RateLimitExhaustedError` (OPE-1237) rather than an ordinary Connector throw —
142
+ // `carryFailureSignals` copies its `status` (429, always AMBIGUOUS per platform-failure.ts) onto
143
+ // the wrapped error, the same treatment `liveTeamsPost`'s catch below gives it. That keeps the
144
+ // 429 case failing closed exactly as an unclassified Connector failure already did — this stays
145
+ // an improvement in what a caller reading `.status` can tell, never a behaviour change.
146
+ throw carryFailureSignals(err, new Error(`edit_reply couldn't update the Teams message — the Connector update failed (conversation ${locus.channelId})`));
147
+ }
148
+ }
149
+ /** Teams' own pre-flight: without an app id nothing can mint a Connector token. DEFINITIVE — the
150
+ * call never leaves this host — so the caller may retry an identical send once the id is set. */
151
+ function requireTeamsAppCredential(what) {
152
+ if (!process.env.MICROSOFT_APP_ID && !process.env.TEAMS_APP_ID) {
153
+ throw markNotApplied(new Error(`${what} couldn't reach Teams — MICROSOFT_APP_ID is not set.`));
154
+ }
155
+ }
156
+ /**
157
+ * Open (or re-open) the 1:1 chat between the bot and one person — `POST {serviceUrl}/v3/conversations`
158
+ * with the bot, the member and the tenant.
159
+ *
160
+ * IT IS THE ONLY WAY TO COLD-ADDRESS A TEAMS PERSON, and it is why OPE-887 exists: the Bot Framework
161
+ * has no `conversations.open` keyed on a bare user id, so the deployment must already know a REGION
162
+ * (`serviceUrl`) and a TENANT before it can ask. Both come from the proactive-address table
163
+ * (channels/teams-conversation-ref.ts), captured off real inbound activities.
164
+ *
165
+ * The create is IDEMPOTENT for a one-member, non-group conversation: Microsoft documents it as
166
+ * returning the EXISTING 1:1 chat rather than minting a second one. That is a documented fact and not
167
+ * a measured one — if it turned out to be false, the visible symptom would be a duplicate chat, never
168
+ * a lost message, and the stored reference below means the call happens at most once per person
169
+ * anyway.
170
+ *
171
+ * 🔴 THE REFUSAL THIS ARM MUST STAY HONEST ABOUT: Microsoft refuses the create when the Teams app is
172
+ * not installed for that person in the PERSONAL scope. So a Teams DM arm reaches the people who have
173
+ * the app, and NOT the whole tenant. The error names that, because "the message did not send" without
174
+ * the reason sends a caller looking in the wrong place.
175
+ */
176
+ async function openTeamsPersonalConversation(region, principalId) {
177
+ const appId = await resolveTeamsAppId();
178
+ const res = await teamsConnectorWrite({
179
+ operation: "conversations.create",
180
+ serviceUrl: region.serviceUrl,
181
+ path: "/v3/conversations",
182
+ method: "POST",
183
+ body: {
184
+ isGroup: false,
185
+ bot: { id: `28:${appId}` },
186
+ members: [{ id: principalId }],
187
+ channelData: { tenant: { id: region.tenantId } },
188
+ tenantId: region.tenantId,
189
+ },
190
+ });
191
+ const id = res.ok && res.body && typeof res.body === "object" ? res.body.id : undefined;
192
+ if (typeof id !== "string" || id.length === 0) {
193
+ // Token-free, and never the Connector's own body: the response can echo request material.
194
+ throw new Error(`couldn't open a Teams 1:1 chat with that account (Bot Framework answered ${res.status}). Microsoft refuses this when the Teams app is not installed for that person in the personal scope.`);
195
+ }
196
+ return id;
197
+ }
198
+ /**
199
+ * DM (OPE-887): deliver `text` to one person's 1:1 Teams chat. The THIRD provider to implement this
200
+ * member, after Slack and Discord, and the one whose transport needed a durable seam first.
201
+ *
202
+ * WHAT IT COSTS TO GET HERE, in one paragraph. `DirectMessageRequest` names WHO and never WHERE, which
203
+ * is right and which Slack and Discord satisfy from a bot token alone. Teams cannot: a send needs a
204
+ * regional `serviceUrl` and a Microsoft-minted conversation id, and neither is derivable from a person
205
+ * id. So the deployment captures both off inbound activities (channels/teams-conversation-ref.ts) and
206
+ * this arm reads them back. The caller's contract is unchanged.
207
+ *
208
+ * THREE OUTCOMES, and each is a real answer:
209
+ * 1. a 1:1 reference is stored ⇒ one send.
210
+ * 2. no 1:1 reference, but this deployment knows exactly one tenant and its region ⇒ open the chat,
211
+ * remember it, send.
212
+ * 3. neither ⇒ REFUSE, loudly and token-free. That is not a defect to paper over: this deployment
213
+ * genuinely has no address for that person, and a silent no-op would leave a consent ask
214
+ * apparently sent and actually nowhere.
215
+ *
216
+ * `actions` ARE IGNORED, deliberately and per the registry's "undefined = unsupported, never faked":
217
+ * this arm posts a plain message activity, and a Teams button would need an Adaptive Card plus an
218
+ * invoke route to receive the tap. Core's copy always stands on its own without a button
219
+ * ({@link DirectMessageAction}'s own rule), so the text delivered here is complete.
220
+ */
221
+ async function deliverTeamsDirectMessage(input) {
222
+ requireTeamsAppCredential("the direct message");
223
+ // 🔴 RESTORE THE RAW `29:` ID ONCE, HERE, BEFORE ANY USE (OPE-1078). A granted `teams:person/<id>`
224
+ // address carries the SURFACE-KEY spelling — the only one the resource-grant grammar admits — and
225
+ // every use below needs the raw Bot Framework id: the stored-reference lookup keys on it, and
226
+ // `openTeamsPersonalConversation` puts it in the Connector's `members` list, where a wrong spelling
227
+ // is a refused create rather than a delivered message. A caller that already holds the raw id (the
228
+ // recipient's own binding) is answered unchanged and pays no store read.
229
+ const principalId = await restoreTeamsPersonId(input.principalId.trim());
230
+ if (principalId.length === 0)
231
+ throw markNotApplied(new Error("the direct message couldn't reach Teams — no account id was given."));
232
+ const stored = await teamsPersonalConversationRef(principalId);
233
+ let ref;
234
+ if (stored) {
235
+ ref = stored;
236
+ }
237
+ else {
238
+ const region = await teamsSoleTenantRegion();
239
+ if (!region) {
240
+ // DEFINITIVE: nothing left this host. Naming the two ways an address is acquired is what lets an
241
+ // operator act — the alternative is a message that never arrives and no reason why.
242
+ throw markNotApplied(new Error("the direct message couldn't reach Teams — this deployment holds no Bot Framework conversation reference for that account, and no single Teams tenant it could open one in. A reference is captured when the person messages the bot, or when the bot sees a channel in exactly one tenant."));
243
+ }
244
+ ref = { ...region, conversationId: await openTeamsPersonalConversation(region, principalId) };
245
+ // Best-effort: a failed write costs a repeated create next time, never a failed send.
246
+ await rememberTeamsPersonalConversation(principalId, ref);
247
+ }
248
+ // READ-TIME allowlist, for the same reason every other Teams write applies one: this POSTs a
249
+ // Connector bearer token to the host. The stored reference was checked on the way in too.
250
+ if (!isTrustedTeamsServiceUrl(ref.serviceUrl)) {
251
+ throw markNotApplied(new Error("the direct message couldn't reach Teams — the stored serviceUrl is not a Bot Framework host."));
252
+ }
253
+ // Teams caps one activity, so a long notice is split exactly as every other Teams post is; the
254
+ // FIRST part's id is the returned anchor, matching the Discord arm.
255
+ const parts = splitTeamsMessageText(input.text);
256
+ let messageId = "";
257
+ for (const part of parts) {
258
+ const res = await sendTeamsActivityBackedOff({ serviceUrl: ref.serviceUrl, conversationId: ref.conversationId, body: { type: "message", text: part } });
259
+ if (messageId.length === 0)
260
+ messageId = res.id;
261
+ }
262
+ return { surfaceId: ref.conversationId, messageId: messageId || "unknown" };
263
+ }
264
+ export const teamsPostArm = {
265
+ platform: "teams",
266
+ resolveTarget: (bag) => {
267
+ // Proactive Teams sends need the Bot-Framework conversation reference (serviceUrl + conversation
268
+ // id) the inbound turn captured — you cannot cold-address a Teams chat.
269
+ const serviceUrl = bag.str("serviceUrl");
270
+ const conversationId = bag.str("conversationId");
271
+ return serviceUrl && conversationId ? { platform: "teams", serviceUrl, conversationId } : null;
272
+ },
273
+ dispatchReply: (target, text) => dispatchTeamsReply(target, text),
274
+ dispatchUnified: (origin, text, deps) => (deps.postTeams ?? liveTeamsPost)(origin, text),
275
+ describeRedriveTarget: (origin) => `teams conversation ${origin.conversationId}`,
276
+ redriveContainerId: (origin) => origin.conversationId,
277
+ editMessage: editTeamsMessage,
278
+ deliverDirectMessage: deliverTeamsDirectMessage,
279
+ };
280
+ //# sourceMappingURL=teams-post-arm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"teams-post-arm.js","sourceRoot":"","sources":["../../../agent/channels/teams-post-arm.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { type ReactionIngestOutcome, type ReactionSinkDeps } from "@open-everywhere/core/agent/adapter-api.js";
2
+ import type { TeamsReactionActivity } from "./teams-stop.js";
3
+ /** Injected seams, so parse + guard + record is unit-tested against stubs with no Bot registration. */
4
+ export interface TeamsReactionFeedbackDeps {
5
+ readonly sink: ReactionSinkDeps;
6
+ }
7
+ /** Production deps: the live audit/telemetry sink core already owns. There is no Teams-specific
8
+ * sink — the whole point of the seam is that the durable row is written in ONE place. */
9
+ export declare function liveTeamsReactionFeedbackDeps(): TeamsReactionFeedbackDeps;
10
+ /**
11
+ * Ingest every reaction one Teams `messageReaction` activity carries. Returns one outcome per
12
+ * reaction — added first, then removed — so a caller's log says what happened to each.
13
+ *
14
+ * It NEVER THROWS into the caller: core's sinks are best-effort by contract, and a lost feedback row
15
+ * must never cost the ingress its ack.
16
+ *
17
+ * An activity with nothing to ingest returns an EMPTY array. That is the honest answer for a bot's
18
+ * own reaction, an activity with no reactor, and an activity naming no conversation — none of the
19
+ * three is a reaction from a person on a message this deployment can key.
20
+ */
21
+ export declare function routeTeamsReactionFeedback(rx: TeamsReactionActivity, deps: TeamsReactionFeedbackDeps): Promise<readonly ReactionIngestOutcome[]>;
22
+ //# sourceMappingURL=teams-reaction-feedback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"teams-reaction-feedback.d.ts","sourceRoot":"","sources":["../../../agent/channels/teams-reaction-feedback.ts"],"names":[],"mappings":"AAmCA,OAAO,EAKL,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACtB,MAAM,4CAA4C,CAAC;AAEpD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAE7D,uGAAuG;AACvG,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;CACjC;AAED;0FAC0F;AAC1F,wBAAgB,6BAA6B,IAAI,yBAAyB,CAEzE;AAQD;;;;;;;;;;GAUG;AACH,wBAAsB,0BAA0B,CAC9C,EAAE,EAAE,qBAAqB,EACzB,IAAI,EAAE,yBAAyB,GAC9B,OAAO,CAAC,SAAS,qBAAqB,EAAE,CAAC,CAiD3C"}