Skip to content

Spinen/msteams multi account#97340

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

Spinen/msteams multi account#97340
jimmypuckett wants to merge 41 commits into
openclaw:mainfrom
spinen: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.

@jimmypuckett
jimmypuckett requested a review from a team as a code owner June 28, 2026 01:27
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation channel: msteams Channel integration: msteams gateway Gateway runtime scripts Repository scripts size: XL labels Jun 28, 2026
@clawsweeper

clawsweeper Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs changes before merge. Reviewed July 11, 2026, 8:10 AM ET / 12:10 UTC.

Summary
Adds Microsoft Teams named accounts and default-account selection across configuration, listeners, routing, credentials, setup, Graph actions, and account-scoped persisted state while retaining the legacy root-level single-bot configuration.

PR surface: Source +1560, Tests +2547, Docs +186, Generated 0, Other +21. Total +4314 across 77 files.

Reproducibility: not applicable. for the requested feature: this adds a new Teams capability rather than fixing an established behavior contract. The current-head compile failure is directly reproducible through the extension package-boundary check.

Review metrics: 2 noteworthy metrics.

  • Public config surfaces: 2 added. accounts and defaultAccount become durable Teams configuration contracts.
  • Account-scoped state domains: 4 changed. Conversation references, SSO tokens, polls, and sent-message dedupe must remain isolated across upgrades.

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

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #71058
Summary: This PR is the candidate implementation for the canonical Microsoft Teams multi-account feature request.

Members:

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

Merge readiness
Overall: 🦐 gold shrimp
Proof: 🦞 diamond lobster ✨ media proof bonus
Patch quality: 🦐 gold shrimp
Result: needs maintainer review before merge.

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

Rank-up moves:

  • Pass accountId into the message-handler dependency object and rerun the Teams package-boundary compile.
  • Format extensions/msteams/src/monitor.ts and extensions/msteams/src/sent-message-cache.ts.
  • Obtain explicit Teams-area acceptance of the public account and listener contract.

Risk before merge

  • [P1] Merging establishes channels.msteams.accounts and defaultAccount as durable public configuration and upgrade contracts.
  • [P1] Each enabled named account currently requires a distinct webhook port, adding operator listener and reverse-proxy work while shared-listener path multiplexing remains deferred.
  • [P1] Conversation references, SSO and delegated tokens, polls, sent-message dedupe, routing, and bot credentials become account-sensitive; incorrect propagation could mix identity or state across accounts.

Maintainer options:

  1. Repair head and accept contract (recommended)
    Add the missing account context, format the reported files, rerun focused checks, and merge only after Teams-area contract approval.
  2. Revise the listener contract
    Pause and change the per-account listener topology before the public configuration ships.
  3. Close if unsupported
    Close if maintainers do not want bundled Teams multi-account behavior.
Copy recommended automerge instruction
@clawsweeper automerge

Special instructions:
Pass the selected accountId into the MSTeamsMessageHandlerDeps object in extensions/msteams/src/monitor.ts, format extensions/msteams/src/monitor.ts and extensions/msteams/src/sent-message-cache.ts with the repository formatter, and rerun the focused extension package-boundary and format checks.

Next step before merge

  • [P2] The immediate blocker is a narrow mechanical account-context omission plus deterministic formatting repair; the public product contract remains a separate maintainer decision afterward.

Maintainer decision needed

  • Question: Should Microsoft Teams adopt channels.msteams.accounts plus defaultAccount, with one webhook port per enabled named account and legacy root configuration treated as the implicit default account?
  • Rationale: The implementation is technically coherent and well proven, but it creates long-lived public configuration, listener topology, credential selection, setup, and persisted-state behavior that requires explicit Teams-area product ownership.
  • Likely owner: steipete — Peter Steinberger has the strongest merged-history ownership of the Teams runtime and bundled-plugin boundary, making this the best available final product-decision owner.
  • Options:
    • Accept after repair (recommended): Fix the exact-head compile and formatting blockers, then approve the proposed accounts-and-listener contract.
    • Require shared listener: Pause until named accounts use a maintainer-approved shared-listener and path-multiplexing model.
    • Decline bundled support: Close the PR if Teams multi-account operation should not become a bundled channel capability.

Security
Cleared: The feature extends existing Teams credential and state handling without adding dependencies, workflow permissions, downloaded execution, or a new secret-exposure path.

Review findings

  • [P1] Pass the selected account into the message handler — extensions/msteams/src/monitor.ts:313
Review details

Best possible solution:

Pass the resolved account identity through every inbound handler construction, format the reported files, prove the exact head with focused package-boundary and Teams checks, then explicitly accept or revise the accounts-and-per-port-listener contract before shipping it.

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

Not applicable for the requested feature: this adds a new Teams capability rather than fixing an established behavior contract. The current-head compile failure is directly reproducible through the extension package-boundary check.

Is this the best way to solve the issue?

No, the current head is not yet the best complete solution because one inbound dependency construction drops required account context. The broader accounts-and-bindings approach matches established channel patterns and has strong live proof once that omission is repaired and the listener contract is accepted.

Full review comments:

  • [P1] Pass the selected account into the message handler — extensions/msteams/src/monitor.ts:313
    MSTeamsMessageHandlerDeps requires accountId, but this construction omits it, so the bundled Teams package fails its current-head TypeScript boundary compile. Pass the resolved account here so inbound routing, authorization, replies, and account-scoped state consistently use the selected bot identity.
    Confidence: 0.99

Overall correctness: patch is incorrect
Overall confidence: 0.96

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 1285fd6db457.

Label changes

Label changes:

  • add merge-risk: 🚨 compatibility: The PR adds public Teams configuration and per-account listener requirements while retaining legacy root configuration as an upgrade path.
  • add merge-risk: 🚨 session-state: Conversation, poll, dedupe, and SSO state keys become account-scoped and must not collide with or orphan default-account state.
  • add merge-risk: 🚨 auth-provider: Named accounts select separate bot, Graph, delegated-auth, and SSO credentials, so missing account propagation can use the wrong identity.
  • add proof: sufficient: Contributor real behavior proof is sufficient. The redacted live Teams screenshot directly shows two distinct bot identities responding in one chat after the multi-account change.
  • add proof: 📸 screenshot: Contributor real behavior proof includes screenshot evidence. The redacted live Teams screenshot directly shows two distinct bot identities responding in one chat after the multi-account change.
  • add rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦞 diamond lobster and patch quality is 🦐 gold shrimp.
  • add status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (screenshot): The redacted live Teams screenshot directly shows two distinct bot identities responding in one chat after the multi-account change.

Label justifications:

  • P2: This is a meaningful Teams capability with a narrow current merge blocker, not an urgent user-facing regression.
  • merge-risk: 🚨 compatibility: The PR adds public Teams configuration and per-account listener requirements while retaining legacy root configuration as an upgrade path.
  • merge-risk: 🚨 session-state: Conversation, poll, dedupe, and SSO state keys become account-scoped and must not collide with or orphan default-account state.
  • merge-risk: 🚨 auth-provider: Named accounts select separate bot, Graph, delegated-auth, and SSO credentials, so missing account propagation can use the wrong identity.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦞 diamond lobster and patch quality is 🦐 gold shrimp.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (screenshot): The redacted live Teams screenshot directly shows two distinct bot identities responding in one chat after the multi-account change.
  • proof: sufficient: Contributor real behavior proof is sufficient. The redacted live Teams screenshot directly shows two distinct bot identities responding in one chat after the multi-account change.
  • proof: 📸 screenshot: Contributor real behavior proof includes screenshot evidence. The redacted live Teams screenshot directly shows two distinct bot identities responding in one chat after the multi-account change.
Evidence reviewed

PR surface:

Source +1560, Tests +2547, Docs +186, Generated 0, Other +21. Total +4314 across 77 files.

View PR surface stats
Area Files Added Removed Net
Source 43 2001 441 +1560
Tests 24 2572 25 +2547
Docs 7 212 26 +186
Config 0 0 0 0
Generated 1 10 10 0
Other 2 23 2 +21
Total 77 4818 504 +4314

Acceptance criteria:

  • [P1] pnpm format:check.
  • [P1] pnpm test:extensions:package-boundary.

What I checked:

Likely related people:

  • steipete: Peter Steinberger introduced the original Teams monitor path, moved Teams into the bundled plugin, and has the largest current-history contribution count in extensions/msteams. (role: feature owner and recent area contributor; confidence: high; commits: 8875dbd44910, d9f9e93deeac, bcbfb357bec7; files: extensions/msteams/src/monitor.ts, extensions/msteams)
  • Vincent Koc: Recent current-main work substantially touched the Teams plugin and configuration schema, including the lines surrounding current Teams configuration and runtime behavior. (role: recent area contributor; confidence: high; commits: 38c49b1f9dff, e085fa1a3ffd; files: extensions/msteams, src/config/zod-schema.providers-core.ts)
  • BradGroux: Brad Groux authors the Microsoft ecosystem tracker, has prior merged Teams history, and was explicitly asked in the discussion to evaluate the public Teams contract. (role: adjacent owner; confidence: medium; files: extensions/msteams)
  • Aamir Jawaid: Aamir Jawaid introduced the current ActivityHandler dependency construction around the exact blocker location, making this person relevant to the inbound runtime seam. (role: introduced adjacent runtime structure; confidence: medium; commits: 04c29825356f; files: extensions/msteams/src/monitor.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 (15 earlier review cycles; latest 8 shown)
  • reviewed 2026-07-07T11:27:09.124Z sha fd75ca6 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-09T00:30:03.571Z sha a196890 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-09T00:40:20.503Z sha a196890 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-09T02:48:37.712Z sha a196890 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-09T12:31:29.488Z sha 4048638 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-10T18:54:01.797Z sha 7f4c317 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-10T19:05:11.273Z sha 7f4c317 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-10T19:36:15.760Z sha fb787aa :: needs maintainer review before merge. :: none

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. 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. labels Jun 28, 2026
@clawsweeper clawsweeper Bot added the rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. label Jul 6, 2026
…i-account

# Conflicts:
#	src/config/bundled-channel-config-metadata.generated.ts
#	src/routing/resolve-route.test.ts
…i-account

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

# Conflicts:
#	extensions/msteams/src/conversation-store-state.test.ts
#	extensions/msteams/src/send-context.test.ts
#	src/config/bundled-channel-config-metadata.generated.ts
@jimmypuckett

jimmypuckett commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Maintainer note: these existing PRs were opened from the Spinen organization fork, and I learned that GitHub's normal "allow edits from maintainers" flow may not work reliably for org-owned forks.

For future OpenClaw PRs, I'll open from my personal fork so maintainer edits work normally.

For this existing batch, I want to make collaboration as easy as possible. I've invited @steipete as a temporary Write collaborator on spinen/openclaw in case direct branch edits are useful. I'm also happy to grant temporary access to another specific maintainer, quickly apply/cherry-pick requested changes myself, or support a maintainer takeover PR if that is the fastest path.

My goal is to make these branches easy to finish, not to create extra process.

@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. and removed rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. labels Jul 10, 2026
…i-account

# Conflicts:
#	extensions/msteams/src/channel.actions.test.ts
#	extensions/msteams/src/channel.ts
#	extensions/msteams/src/graph-group-management.test.ts
#	extensions/msteams/src/graph-group-management.ts
#	extensions/msteams/src/graph-members.ts
#	extensions/msteams/src/graph-messages.actions.test.ts
#	extensions/msteams/src/graph-messages.ts
#	extensions/msteams/src/monitor.ts
@clawsweeper

clawsweeper Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper status: review started.

I am starting a fresh review of this pull request: Spinen/msteams multi account This is item 1/1 in the current shard. Shard 0/1.

This placeholder means the worker is alive and reading the current context. I will edit this same comment with the actual review when the claws are done clicking.

Crustacean status: shell secured, claws on keyboard, evidence pebbles being sorted.

@clawsweeper clawsweeper Bot removed proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. feature: ✨ showcase ClawSweeper spotlight: unusually compelling feature idea for maintainer attention. labels Jul 11, 2026
@jimmypuckett

Copy link
Copy Markdown
Contributor Author

Superseded by #104692 from my personal fork, using the same published head commit. This move preserves my authorship while enabling maintainer edits and repository automation. Please continue review on #104692; this PR remains available for prior discussion, proof, and CI history.

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

Labels

app: android App: android channel: msteams Channel integration: msteams docs Improvements or additions to documentation 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: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. scripts Repository scripts size: XL status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant