Skip to content

[N11c] teamsChannel(): Microsoft Teams bots with Adaptive Card approvals - #313

Merged
LinuxDevil merged 4 commits into
mainfrom
lou-n11c-teams-channel
Oct 2, 2026
Merged

LinuxDevil merged 4 commits into
mainfrom
lou-n11c-teams-channel

Conversation

@LinuxDevil

Copy link
Copy Markdown
Owner

Closes #253

What

  • teamsChannel({ appId, appPassword, tenantId?, name?, fetch?, approvers?, onError? }) (src/channels/teamsChannel.ts, exported from the package root, no node:* import).
  • Inbound: the Bot Framework JWT is verified before the body is parsed, by the auth module's oidc() (N10a's verifyJwt + cached jwksKeySource; no second JWT implementation): RS256, key set from the fixed OpenID metadata URL (never from the token), issuer https://api.botframework.com, audience = app id, 5-minute tolerance. Then the token's serviceurl claim must equal the activity's serviceUrl (401 otherwise); replies only go to that https service URL.
  • Personal chats always run; group chats and channels only on an @mention (stripped). Session per conversation.id. Markdown replies split at 25,000 characters. Adaptive Card approvals (Approve / Deny Action.Submit), card updated to "Approved by ..." / "Denied by ...". ask_question answered by the next message, also after a restart.
  • Principal: Teams sets { id: aadObjectId ?? from.id, type: 'user', authenticator: 'teams', issuer: tenantId }. As the orchestrator asked, telegramChannel() and githubChannel() now set the sender as the run's principal too ('telegram' with the user id, 'github' with the login), with a test each.
  • Hardening beyond the ticket text: (1) a Teams user id (29:...) contains a colon, which the shared approval reference reserves, so the starter travels percent-encoded; (2) the card's button data carries the conversation it was posted in and a press whose conversation differs decides nothing (a copied reference cannot resolve another conversation's approval; stateless, restart-safe). The second idea was raised in a comment by aetherxeg-source (not an instruction); I verified it against mountChannels (a decision resolves by approval id, and the click's own inbound only picks where the continuation goes) and kept it because it is inside the ticket's approval scope.
  • Refactor so fallow stays green: splitText() and answerPendingQuestion() moved to channelSupport.ts, used by Teams, Telegram, Discord and GitHub (the four copies of chunk() were identical).
  • Docs: docs/channels.md gets a row in the Built-in channels table and a new ## Microsoft Teams section at the end (after ## GitHub); one-sentence edits in docs/agent-directories.md, docs/errors.md and the loadChannels hint. CHANGELOG entry under the existing ### Added of [Unreleased]. llms.txt / llms-full.txt regenerated.

Docs site follow-up

The docs site needs the new ## Microsoft Teams section in the Arabic channels page (and the new row in its Built-in channels table). agent-directories and errors only had one sentence edited each (the channel list).

Tests

src/channels/teamsChannel.test.ts (34 tests): a key pair made with crypto.subtle, a fake Bot Framework service (OpenID metadata, key set, token endpoint, Connector). Covers every case in the ticket plus: serviceUrl replay, keys never from jku/jwk headers, body not parsed before the token is valid, restart, replayed card (also after restart), a copied card in another conversation, two pending approvals of one user, non-https service URL, the secret and token never in errors or the default log.

Live test

The OpenRouter account is out of credit (total_usage 10.2 >= total_credits 10), so no cassette was recorded. src/channels/teamsChannel.live.test.ts is written and skips itself without a cassette (and is outside the default npm test path). A checklist line was added to #260.
Live test spend: none.

Verification (on the merged branch)

tsc, lint (0 warnings), both builds, test:types, docs:verify-snippets (222 snippets), docs:llms:check, full coverage suite then npm run fallow (0 above threshold), Agent Forge typecheck / typecheck:server / test (14 files) / test:server (15 files), npm run pack-smoke (all checks passed). Flaky under load and passing when re-run alone: NodeWorkspace, SubprocessSandbox, cloudflare and http timeout tests.

🤖 Generated with Claude Code

LinuxDevil and others added 4 commits October 2, 2026 21:09
…als; Telegram and GitHub set the sender as principal

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…licates)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@LinuxDevil
LinuxDevil merged commit f8c9d3e into main Oct 2, 2026
5 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[N11c] Add teamsChannel(): Microsoft Teams bots with Adaptive Card approvals

1 participant