Skip to content

feat(msteams): support multiple bot accounts#104692

Closed
jimmypuckett wants to merge 46 commits into
openclaw:mainfrom
jimmypuckett:spinen/msteams-multi-account
Closed

feat(msteams): support multiple bot accounts#104692
jimmypuckett wants to merge 46 commits into
openclaw:mainfrom
jimmypuckett:spinen/msteams-multi-account

Conversation

@jimmypuckett

Copy link
Copy Markdown
Contributor

Related: #71058

What Problem This Solves

Resolves a problem where Microsoft Teams could only be configured as one bot identity per OpenClaw gateway. That made it difficult to run multiple Teams-visible agents, each with its own Teams app/bot registration, from the same OpenClaw instance in the same way operators can use account-specific routing with other channel integrations.

Why This Change Was Made

This adds first-class account support to the Microsoft Teams channel while preserving the legacy single-bot configuration path. channels.msteams.accounts.<id> now defines Teams bot accounts, channels.msteams.defaultAccount selects the implicit account when one is not provided, and existing root-level appId/appPassword/tenantId/webhook config continues to work as the default account.

The implementation keeps account identity fields explicit per named account: named accounts must provide their own appId, credential, and webhook port, while shared settings such as tenantId, webhook path, policies, Graph/SSO/delegated-auth settings, streaming behavior, and delivery settings can be inherited from the root and overridden per account. Inbound handling, proactive sends, replies, Graph-backed actions, SSO/delegated token storage, polls, sent-message cache, conversation state, probes, setup flows, and routing all carry the account context.

User Impact

Users can now configure multiple Microsoft Teams bot accounts in one OpenClaw gateway and bind each account to the agent that should answer:

{
  agents: {
    list: [
      { id: "main", workspace: "~/.openclaw/workspace-main" },
      { id: "support", workspace: "~/.openclaw/workspace-support" },
    ],
  },
  bindings: [
    { agentId: "main", match: { channel: "msteams", accountId: "default" } },
    { agentId: "support", match: { channel: "msteams", accountId: "support" } },
  ],
  channels: {
    msteams: {
      enabled: true,
      tenantId: "<TENANT_ID>",
      webhook: { path: "/api/messages" },
      defaultAccount: "default",
      accounts: {
        default: {
          appId: "<PRIMARY_CLIENT_ID>",
          appPassword: "<PRIMARY_CLIENT_SECRET>",
          webhook: { port: 3978 },
        },
        support: {
          appId: "<SUPPORT_CLIENT_ID>",
          appPassword: "<SUPPORT_CLIENT_SECRET>",
          webhook: { port: 3979 },
        },
      },
    },
  },
}

Existing single-bot configs remain valid and do not require bindings:

{
  channels: {
    msteams: {
      enabled: true,
      appId: "<CLIENT_ID>",
      appPassword: "<CLIENT_SECRET>",
      tenantId: "<TENANT_ID>",
      webhook: { port: 3978, path: "/api/messages" },
    },
  },
}

Evidence

  • Local multi-account live smoke: two separate Microsoft Teams bot apps were installed in a Teams group chat and both replied through the same OpenClaw gateway using separate Teams account identities and routes. Redacted screenshot available: teams_screenshot_dale_legal_blurred
  • Backward compatibility verified by tests covering legacy root-level single-bot config and default-account behavior.
  • Account scoping verified across inbound messages, outbound/proactive replies, Graph actions, SSO/delegated token storage, polls, sent-message cache, conversation state, setup/probe flows, and routing.
  • pnpm test:extension msteams
  • pnpm test:contracts:channels (45 files, 305 tests)
  • pnpm build
  • pnpm check
  • git diff --check
  • codex review --base upstream/main: no actionable correctness issues found.

AI-assisted: yes. I used Codex to help implement, audit, test, and review this change, and I reviewed the behavior and code paths before opening the PR.

If this gets merged, then I will focus on enhancements next.


Migration note: This PR replaces #97340 with the same published head commit, moved from spinen/openclaw to jimmypuckett/openclaw so OpenClaw maintainers and repository automation can update the contribution without transferring authorship. Prior review, proof, and CI history remain available on #97340.

@jimmypuckett
jimmypuckett requested a review from a team as a code owner July 11, 2026 20:32
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation channel: msteams Channel integration: msteams app: android App: android gateway Gateway runtime scripts Repository scripts size: XL labels Jul 11, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. labels Jul 11, 2026
@clawsweeper

clawsweeper Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 15, 2026, 10:25 PM ET / July 16, 2026, 02:25 UTC.

Summary
The PR adds first-class multi-account Microsoft Teams bots with account-aware configuration, routing, credentials, setup, Graph operations, persisted state, tests, and documentation while retaining root-level single-bot behavior.

Reproducibility: not applicable. this is a new Microsoft Teams capability rather than a report of broken established behavior; current main and current docs clearly expose only one root-level bot identity.

Review metrics: 2 noteworthy metrics.

  • Public config/default surfaces: 2 keys added; 2 policy defaults moved into account resolution. accounts and defaultAccount become public configuration while DM/group defaults shift from direct schema defaults to inherited effective-account resolution, making upgrade behavior maintainer-visible.
  • Persisted account scopes: 4 state families scoped. Conversation references, polls, sent-message dedupe records, and SSO tokens now depend on account identity, so isolation and default-account compatibility matter before merge.

Stored data model
Persistent data-model change detected: persistent cache schema: extensions/msteams/src/secret-contract.test.ts, serialized state: extensions/msteams/src/sent-message-cache.ts, serialized state: extensions/msteams/src/sso-token-store.ts, unknown-data-model-change: src/config/bundled-channel-config-metadata.generated.ts, unknown-truncated-pull-files. Confirm migration or upgrade compatibility proof before merge.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #71058
Summary: The open feature issue is the canonical product-decision record, this PR is its candidate implementation, and the earlier contribution PR was replaced by this branch.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🦞 diamond lobster
Proof: 🦞 diamond lobster ✨ media proof bonus
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Risk before merge

  • [P1] Merging establishes a hybrid root-plus-account inheritance model; an incorrect default-account or promotion rule could change an existing installation's effective credentials, policies, or webhook listener.
  • [P1] Graph, delegated OAuth, SSO, and SecretRef resolution become account-sensitive, so any missed caller could authorize or execute with the wrong bot identity.
  • [P1] Conversation references, polls, sent-message markers, and SSO tokens gain account namespaces while the default account retains legacy keys; the fresh-install and upgrade invariants need explicit maintainer acceptance.
  • [P1] The current head is not cleanly mergeable with latest main, and the latest gate failure needs fresh attribution after any sponsored rebase.

Maintainer options:

  1. Sponsor and refresh
    Approve the exact account/default/inheritance contract, resolve current-main conflicts, and rerun both legacy-upgrade and multi-account live proof before merge.
  2. Close pending sponsorship (recommended)
    Close this technically strong implementation without rejecting the idea, and reopen or replace it when a maintainer sponsors the permanent contract.

Next step before merge

  • No automated repair is appropriate because the only remaining blocker is maintainer sponsorship of the permanent product and compatibility contract.

Maintainer decision needed

  • Question: Should Microsoft Teams adopt the permanent hybrid channels.msteams.accounts plus root-inheritance/default-account contract proposed by this PR?
  • Rationale: The implementation is technically coherent and proven, but only a maintainer can accept the long-term public config, credential fallback, setup, and persisted-state compatibility commitments.
  • Likely owner: steipete — This person is the documented project decision owner and has the strongest merged history across the affected Microsoft Teams architecture and state paths.
  • Options:
    • Sponsor the account contract: Confirm these inheritance and compatibility semantics, then refresh the PR on current main and rerun fresh-install and upgrade proof.
    • Defer adoption (recommended): Close the implementation PR while retaining the canonical feature issue until a maintainer sponsors or narrows the permanent contract.

Security
Cleared: The account-scoped credential and state changes are security-sensitive but no concrete secret exposure, authorization bypass, unsafe dependency, or supply-chain defect was found in the proposed patch.

Review details

Best possible solution:

Keep #71058 as the product-decision record; if a maintainer sponsors this exact hybrid account contract, reopen or refresh the implementation on current main and require clean fresh-install, legacy-upgrade, credential-isolation, and state-isolation proof before merge.

Do we have a high-confidence way to reproduce the issue?

Not applicable: this is a new Microsoft Teams capability rather than a report of broken established behavior; current main and current docs clearly expose only one root-level bot identity.

Is this the best way to solve the issue?

Unclear as a product decision: the implementation follows OpenClaw's established account-routing pattern and has strong coverage, but the exact root-inheritance, default selection, and legacy compatibility contract needs maintainer approval before it can be considered the best permanent solution.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 08ecf63bb608.

Label changes

Label justifications:

  • P2: This is a valuable but non-emergency Microsoft Teams capability with a bounded organizational use case and substantial review surface.
  • merge-risk: 🚨 compatibility: The PR changes public config, setup, defaults, and legacy root-level behavior in ways that can affect existing Teams installations during upgrade.
  • merge-risk: 🚨 auth-provider: The selected account now controls bot, Graph, delegated OAuth, SSO, environment fallback, and SecretRef credentials.
  • merge-risk: 🚨 session-state: The selected account now namespaces conversation, poll, sent-message, and SSO state that previously had one Teams owner.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • feature: ✨ showcase: ClawSweeper spotlight: unusually compelling feature idea for maintainer attention. Running multiple distinct Teams bot identities from one gateway materially improves agent identity and routing for organizational multi-agent deployments.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (screenshot): The redacted live Teams smoke shows two distinct bot applications replying through one gateway after the change, supplemented by account-isolation tests and exact-head validation; private identifiers and credentials should remain redacted in any follow-up evidence.
  • proof: sufficient: Contributor real behavior proof is sufficient. The redacted live Teams smoke shows two distinct bot applications replying through one gateway after the change, supplemented by account-isolation tests and exact-head validation; private identifiers and credentials should remain redacted in any follow-up evidence.
  • proof: 📸 screenshot: Contributor real behavior proof includes screenshot evidence. The redacted live Teams smoke shows two distinct bot applications replying through one gateway after the change, supplemented by account-isolation tests and exact-head validation; private identifiers and credentials should remain redacted in any follow-up evidence.
Evidence reviewed

What I checked:

  • New configuration contract: The patch adds channels.msteams.accounts and channels.msteams.defaultAccount, making account selection and root-to-account inheritance part of the public configuration contract. (src/config/types.msteams.ts:205, b686b0171453)
  • Current behavior remains single-account: Current main and the published Microsoft Teams documentation still configure one root-level bot identity; the requested multi-account behavior is not already implemented. Public docs: docs/channels/msteams.md. (docs/channels/msteams.md:75, 08ecf63bb608)
  • Credential routing is account-sensitive: The proposed Graph token resolver selects account-specific credentials, disables default environment fallback for named accounts, and scopes delegated credentials by account. (extensions/msteams/src/graph.ts:217, b686b0171453)
  • Persisted state changes ownership semantics: The patch namespaces non-default account conversation records while preserving legacy default-account keys, so the permanent account contract affects upgrade and state-isolation behavior. (extensions/msteams/src/conversation-store-state.ts:229, b686b0171453)
  • Strong real behavior proof: The PR provides a redacted live Microsoft Teams smoke showing two separate bot applications replying through one gateway, and the existing proof review classified that evidence as sufficient. (b686b0171453)
  • Review continuity: The latest completed ClawSweeper cycle reviewed head b89fa3f with no findings; subsequent feature-branch commits are upstream merges, conflict reconciliation, and generated docs-map refresh rather than a newly identified product approval. (b686b0171453)

Likely related people:

  • steipete: The repository identifies this person as the project maintainer, and Microsoft Teams history shows repeated ownership of channel architecture, SQLite state, setup, policy, and presentation changes. (role: feature owner and likely decision owner; confidence: high; commits: ab96520, e4b5027, a2b2c4a; files: extensions/msteams/src/channel.ts, extensions/msteams/src/conversation-store-state.ts, extensions/msteams/src/monitor.ts)
  • vincentkoc: Recent merged history includes Microsoft Teams channel-runtime and outbound resolver refactors adjacent to the account-aware runtime boundaries changed here. (role: recent area contributor; confidence: medium; commits: c4bae0f, 03415bb; files: extensions/msteams/src/channel.ts, extensions/msteams/src/outbound.ts)
  • sudie-codes: Merged history introduced several Graph-backed Teams actions that this PR makes account-aware, including member, channel, message, and group-management operations. (role: Microsoft Teams Graph feature contributor; confidence: medium; commits: 4e67e7c, 928a512, 0f19271; files: extensions/msteams/src/channel.ts, extensions/msteams/src/graph-members.ts, extensions/msteams/src/graph-teams.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (34 earlier review cycles; latest 8 shown)
  • reviewed 2026-07-14T22:24:28.724Z sha b89fa3f :: needs maintainer review before merge. :: none
  • reviewed 2026-07-14T22:42:07.214Z sha b89fa3f :: needs maintainer review before merge. :: none
  • reviewed 2026-07-14T22:58:50.438Z sha b89fa3f :: needs maintainer review before merge. :: none
  • reviewed 2026-07-14T23:16:53.341Z sha b89fa3f :: needs maintainer review before merge. :: none
  • reviewed 2026-07-14T23:33:58.239Z sha b89fa3f :: needs maintainer review before merge. :: none
  • reviewed 2026-07-14T23:51:44.249Z sha b89fa3f :: needs maintainer review before merge. :: none
  • reviewed 2026-07-15T00:07:59.367Z sha b89fa3f :: needs maintainer review before merge. :: none
  • reviewed 2026-07-15T00:25:08.651Z sha b89fa3f :: needs maintainer review before merge. :: none

@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. feature: ✨ showcase ClawSweeper spotlight: unusually compelling feature idea for maintainer attention. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jul 11, 2026
@jimmypuckett jimmypuckett changed the title Spinen/msteams multi account feat(msteams): support multiple bot accounts Jul 11, 2026
@jimmypuckett

Copy link
Copy Markdown
Contributor Author

@steipete I know that you get tagged/requested to view PR's all the time, but you are listed as the first "Likely related people". I know this is a large PR, and I'm ready to break it into the parts you need to review. We're using OC in our office & need to expose the agents to MS Teams as separate applications, so this is a very important feature to us. There are existing discussions about this from others as well. Getting MS Teams to be in closer feature parity with the other channels would be great.

@jimmypuckett
jimmypuckett force-pushed the spinen/msteams-multi-account branch from a19dadb to 1ab9746 Compare July 12, 2026 11:48
@openclaw-barnacle openclaw-barnacle Bot removed the app: android App: android label Jul 12, 2026
@clawsweeper clawsweeper Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jul 12, 2026
@clawsweeper clawsweeper Bot added status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jul 13, 2026
@jimmypuckett

Copy link
Copy Markdown
Contributor Author

Validation update for exact head e1f35fc10e228c2e8157b2506443f4067cc287bd:

  • Fixed the review finding by keying the no-store-path Teams turn queue by resolved session identity instead of the process-wide global key.
  • Added regressions proving same-session turns remain serialized and unrelated Teams sessions can dispatch concurrently when no store path is available.
  • pnpm test extensions/msteams/src/monitor-handler/message-handler.authz.test.ts — 24 passed.
  • pnpm check:test-types — passed.
  • pnpm run lint:extensions:bundled — passed.
  • Exact-head GitHub CI — 76 passed, 0 failed, 0 pending.

@jimmypuckett

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot removed the rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. label Jul 13, 2026
…-account-clean-rebuild

# Conflicts:
#	extensions/msteams/src/sent-message-cache.ts
#	src/config/bundled-channel-config-metadata.generated.ts
…-account-clean-rebuild

# Conflicts:
#	src/config/bundled-channel-config-metadata.generated.ts
…-account-clean-rebuild

# Conflicts:
#	src/config/config-misc.test.ts
#	src/config/zod-schema.providers-core.ts

Copy link
Copy Markdown
Contributor Author

Validation update for exact head b89fa3f6e77ef03c62a78571831a02b831c88b4b:

  • Resolved the current upstream/main merge and preserved the Teams multi-account/config behavior.
  • Split the three Teams test files that crossed the repository max-lines ceiling; focused suite: 4 files, 92 tests passed.
  • pnpm deadcode:exports — passed.
  • pnpm check:max-lines-ratchet --base upstream/main — passed.
  • pnpm lint --threads=8 — passed.
  • pnpm check:test-types — passed.
  • pnpm run test:extensions:package-boundary:compile — 121/121 plugins passed.
  • Exact-head GitHub CI — 70 passed, 0 failed, 0 pending.
  • Branch diff audit — no CHANGELOG.md change and no unrelated upstream repair files in the PR diff.

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

…-account-clean-rebuild

# Conflicts:
#	extensions/msteams/src/monitor-handler/message-handler.authz.test.ts
…-account-clean-rebuild

# Conflicts:
#	src/config/bundled-channel-config-metadata.generated.ts
@jimmypuckett

Copy link
Copy Markdown
Contributor Author

Superseded by #112811. This PR became detached: its internal head b686b01 no longer matched the fork branch ref 603892d, and those commits were not in the same forward ancestry. #112811 was rebuilt from current upstream with preserved Teams multi-account history, independently verified matching fork/PR heads, full local validation, and GitHub MERGEABLE status. Closing this detached predecessor to keep one active review target.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: msteams Channel integration: msteams docs Improvements or additions to documentation feature: ✨ showcase ClawSweeper spotlight: unusually compelling feature idea for maintainer attention. gateway Gateway runtime merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. P2 Normal backlog priority with limited blast radius. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. scripts Repository scripts size: XL status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant