Skip to content

feat(voice-call): Microsoft Teams provider β€” CVI voice/video calls#91438

Closed
ahenawy wants to merge 62 commits into
openclaw:mainfrom
ahenawy:feat/avatar-lipsync
Closed

feat(voice-call): Microsoft Teams provider β€” CVI voice/video calls#91438
ahenawy wants to merge 62 commits into
openclaw:mainfrom
ahenawy:feat/avatar-lipsync

Conversation

@ahenawy

@ahenawy ahenawy commented Jun 8, 2026

Copy link
Copy Markdown

Summary

Adds the Microsoft Teams (msteams) provider to the voice-call extension β€” inbound voice + inbound video vision, group/meeting awareness, and outbound "call me back" β€” over an HMAC-authenticated WebSocket endpoint. Works in 1:1 and group/meeting calls, on both voice-call modes (streaming: STT β†’ agent β†’ TTS, and realtime: speech-to-speech).

This is a refreshed, standalone superset of the earlier Teams PRs (see Relationship below) and is intended to replace them. It also carries the CVI rendering drivers β€” emotion expression cues, the viseme lip-shape spike, and assistant visual sharing (show_to_caller β†’ the bot shows an image on its tile) β€” plus realtime continuous vision (sendImage, a small core plugin-sdk addition). The companion .NET worker renders; this PR sends the drivers.

✨ The big picture: a Conversational Video Interface (CVI) for OpenClaw

This PR is the foundation for AI Video Agents β€” bringing an interactive, face-to-face video interface to OpenClaw. The Conversational Video Interface (CVI) is the end-to-end pipeline for face-to-face AI, and its three pillars map directly onto what lands here:

CVI pillar What it means In OpenClaw (this PR)
Perception the agent sees inbound video.frame vision β€” look_at_screen + streaming auto-attach + realtime continuous vision (sendImage ambient frame push); camera + screen-share; per-participant attribution in meetings
Dialogue the agent converses realtime speech-to-speech (+ streaming STT→agent→TTS), barge-in, group "speak only when addressed" gate, recording-gated
Real-time rendering the agent is seen a lip-synced avatar video tile driven by this PR's expression (emotion β†’ smile/frown), viseme lip-shape (speech.marks), and visual-sharing (show_to_caller β†’ display.image) senders β€” the companion worker also adds head bob/sway, eye-blink, and gaze toward the active speaker (the tile is rendered by the companion .NET media worker; this PR sends the drivers)

Together these turn a Teams call into a true two-way video conversation with the agent: it watches what you show, talks with you in real time, and appears as an animated face. This is the centerpiece β€” perception and dialogue land in this PR; the rendering pillar runs in the companion media worker (source to be shared β€” see below), and richer continuous face-to-face video is the roadmap.

What's in this PR

Voice: msteams provider, caller allowlist (safe-by-default, closed unless matched), streaming + realtime paths, recording-status gate before any media-derived data is processed.

Inbound video (see): video.frame ingestion (buffered per source), the look_at_screen tool (realtime) + automatic per-turn frame attachment (streaming, so the agent is visually aware each turn), recording-gated and rate-limited. The agent sees the caller's camera and screen-share (VBSS). A per-call vision spend cap (msteams.maxVisionPerMinute, default 30, 0 = unlimited) bounds vision-model cost.

Group / meeting calls:

  • Per-participant attribution β€” each video.frame carries the participant it belongs to (participantId / participantName); the streaming path labels the attached image and look_at_screen names the participant, so the vision model can reason about who is showing what.
  • Speak only when addressed β€” without a gate, the assistant would reply to every utterance in a meeting and talk over participants. In a group call (2+ humans) it now stays silent until addressed by name β€” e.g. "Hey Aria, …" / any configured wakePhrases (case-insensitive, boundary-aware) β€” mirroring the chat channel's group @mention gate. Once addressed, a short follow-up window (followUpWindowMs) lets the back-and-forth continue without repeating the name, then it goes quiet again. 1:1 calls respond to everything (gate off). Deterministic on the streaming path (shouldRespondToGroupTurn keyed on the live participants count); instruction-based on realtime.

Realtime delegation & background tasks: in realtime (speech-to-speech) mode the model handles small talk itself and delegates real work to the OpenClaw agent β€” openclaw_agent_consult for quick in-line answers (a short "working on it" filler covers longer agent runs), and (under realtime.toolPolicy: "owner") openclaw_agent_task to run a long job in the background. A background result is delivered two ways: a Teams chat message (deliverVia:"message", the default) or an outbound voice call-back (deliverVia:"call"). Both are gated on recording status. If the caller has no Teams chat target (anonymous), a background task is refused up front rather than acking a delivery that can't happen.

Outbound (call me back): initiateCall places a Teams call via the provider's authenticated endpoint (SSRF-guarded); on the call-back the agent speaks the result, then hangs up (the result-delivery instruction is hardened so it states the result, not a generic greeting). If the callee doesn't answer, the bot leaves that same result as a Teams voicemail β€” which Teams' own transcription then emails to them β€” so the answer reaches the caller even when the call-back is missed. A configurable no-answer timeout (msteams.outbound.answerTimeoutMs, default 120s) finalizes a call that never connects, and timed-out call ids auto-expire so the dedupe set can't grow unbounded. tenantId inherited from channels.msteams.

Avatar expression cues: a cheap, dependency-free lexical heuristic (expression.ts, no extra model call) infers a coarse emotion β€” neutral / happy / sad / surprised β€” from the assistant's reply text and sends it to the worker as an additive expression message, on both the streaming (playTts) and realtime (assistant transcript, cued early + self-correcting) paths. The avatar shapes its mouth (smile / frown / round "O") accordingly. Best-effort and cosmetic: a send failure never disrupts the call, audio is untouched, and an older worker ignores the message.

Avatar viseme lip-shape (spike): an additive speech.marks message carries a viseme timeline so the avatar mouth changes shape per sound (wide "ee", round "oo", closed "m/b/p"), blended over the worker's RMS openness. The spike derives marks from the reply text (estimateVisemes); the full pass uses real ElevenLabs /with-timestamps character timing (visemesFromAlignment, landed) instead of an even spread. Streaming path only; falls back to RMS-only when absent.

Assistant visual sharing (show_to_caller): the bot can show the caller an image on its video tile mid-call. A realtime tool show_to_caller runs the agent (so its screenshot / image-generation tools are available) to produce one image; consultRealtimeVoiceAgent returns the produced trusted-local file path (remote URLs excluded β€” no SSRF surface), and the bridge reads it (image-type + 4 MB cap) and sends an additive display.image message the worker renders fullscreen for a few seconds, then returns to the avatar. Recording-gated like the other agent tools.

Realtime continuous vision (sendImage): a new RealtimeVoiceBridge.sendImage (a small core plugin-sdk addition + the openai/azure realtime provider) pushes the latest changed inbound frame into the realtime session as an ambient input_image conversation item with no forced response, every ~6 s, budget-capped and recording-gated β€” so the realtime model is continuously visually aware, not just on look_at_screen.

Sessions & memory: caller identity (Teams AAD object id, else a per-call-unique id) keys the session, so per-caller memory works and distinct anonymous callers never collide; sessions end cleanly on socket close.

Code quality / refactoring

Shared types & single-source defaults:

  • Shared MsteamsVideoFrame type (msteams-video-frame.ts) β€” the provider and the realtime path reference one type instead of a structural duplicate (type-only import, no runtime cycle).
  • Single gate-defaults resolver β€” resolveGroupCallGateConfig is the one source of the group-call defaults, used by the config layer, runtime wiring, and provider (no triplicated ?? true / ?? [...] / ?? 12000).
  • One PCM sample-rate constant β€” MSTEAMS_PCM_SAMPLE_RATE_HZ (16 kHz) defined once; provider/realtime/tts alias it.

Extracted shared helpers (kill copy-paste in the provider + realtime paths):

  • createStreamingCallState β€” one STT-session + per-call-state + connect/flush/fail factory shared by the inbound and outbound session-start paths.
  • runMsteamsConsult β€” one consult invocation (model + thinking-level resolution + consultRealtimeVoiceAgent) shared by the consult / look_at_screen / background-task handlers.
  • readArgText (in utils.ts) β€” shared tool-arg reader (replaced readArgString / readConsultArgText).
  • describeMsteamsVideoFrameOwner β€” one place builds the "<name>'s camera/shared screen" vision-attribution string for both streaming and realtime.
  • clearTimedOutOutbound helper + auto-expiry β€” one place forgets a timed-out outbound id and cancels its (unref'd) expiry timer; called from late-connect, session-end, and teardown.

Correctness/robustness:

  • Outbound realtime vision wired β€” getLatestFrame now passed on call-backs too (was inbound-only), so look_at_screen works if the callee shares video.
  • Validated TTS merge (TtsConfigSchema.parse, not a bare cast) and surfaced audio.frame send failures (streamPcmFrames try/catch β†’ stop on WS failure) instead of silently succeeding.
  • Pure, unit-tested gate core (group-call-gate.ts: isAddressed, shouldRespondToGroupTurn).

Relationship to #89724, #91107, #91176

Safety & compliance

  • Recording gate: no audio-derived data and no video frame is processed before the call's recording status is active.
  • Allowlist: inbound rejected unless the caller matches; default posture is closed.
  • SSRF-guarded outbound place-call; HMAC-signed WS upgrade; shared secret via secretref.

Real behavior proof

  • Behavior or issue addressed: Inbound Teams voice + video (look_at_screen) in 1:1 and group calls, the group "speak only when addressed" gate, a Teams-chat task callback, and an outbound voice call-back.

  • Real environment tested: Live 1:1 and 3-party group Microsoft Teams calls against a running OpenClaw gateway (realtime path, vision-capable model). Real calls, not mocks.

  • Exact steps or command run after this patch:

    1. pnpm openclaw gateway run with the msteams provider + realtime + outbound enabled.
    2. 1:1: shared a screen / turned on camera, asked "what's on my screen / can you see me?".
    3. Group (user + colleague + assistant): shared a screen, then turned the camera on, and asked the assistant to describe what it saw.
    4. From Teams chat / mid-call asked for a result "in the background, then call me back".
  • Evidence (copied live output, redacted):

    be-seen avatar tile:         the bot joined the call as a video participant publishing its avatar
                                  (the OpenClaw lobster) β€” a live Teams call shows the rendered avatar tile
                                  alongside the caller's camera (rendering pillar; the companion worker
                                  renders the NV12 tile, this PR sends the expression/viseme/display drivers).
    look_at_screen (screen):     "Your screen is showing the OpenClaw website in Chrome β€” the OpenClaw
                                  logo, the tagline 'The AI that actually does things,' and a Quick Start
                                  install section with a one-line command."
    look_at_screen (after I switched the shared window to GitHub):
                                 "Now I see the GitHub repository page for openclaw/openclaw in Chrome β€”
                                  the repo file list and stats." (proves the scene-change fix: switching
                                  the shared content is now detected, not stuck on the previous page.)
    look_at_screen (camera):     "I see you holding a phone in front of your face, wearing a light-coloured
                                  shirt; a modern office with desks and bright ceiling lights behind you."
    "Hey assistant" gate:        addressing the bot by its wake phrase ("Hey assistant, …") triggered a
                                  response; the speak-only-when-addressed gate works on a live call.
    outbound "call me back":     the bot placed a Teams VOICE call that STATED THE RESULT, e.g. "here's the
                                  Dubai time you asked for β€” it's 10:36 AM on Tuesday, June 9" (not a hello).
    unanswered call-back:        when the callee didn't pick up, the call went to Teams voicemail and the
                                  bot left the SAME result; Teams' own voicemail transcription then emailed
                                  it to the callee (audio.mp3 + transcript) β€” Microsoft platform behaviour,
                                  not our code, but it confirms the call-back speaks the result.
    
  • Observed result: On a live call the agent described the shared screen, correctly tracked a content switch (openclaw.ai β†’ the openclaw GitHub page), described the camera, responded to the "Hey assistant" wake phrase, and stated the resolved result on the outbound call-back (incl. to voicemail when unanswered). The avatar renders as a live video tile.

  • Attached evidence (redacted, from the live call):

    1. Be-seen avatar β€” the bot publishes its avatar as a live video tile

    2. Outbound "call me back" β€” the bot rings the caller on Teams

    3. Result delivery even to voicemail β€” Teams transcribes the call-back's spoken result

    4. Screen recording of the live call

    5. show_to_caller β€” the bot screenshots a page and displays it on its own video tile mid-call

  • Agent transcripts (from the gateway logs, redacted):

    Screen share: "Your screen is showing the OpenClaw website in Chrome β€” the OpenClaw logo, the tagline 'The AI that actually does things,' and a Quick Start install section."

    After I switched the shared window to GitHub: "Now I see the GitHub repository page for openclaw/openclaw in Chrome β€” the repo file list and stats." (the scene-change fix: switching the shared content is detected, not stuck on the previous page.)

    Camera: "I see you holding a phone in front of your face, wearing a light-coloured shirt; a modern office with desks and bright ceiling lights behind you."

    Outbound call-back: "Here's the current local time in Dubai you asked for β€” it's 10:36 AM on Tuesday, June 9." (the call-back states the resolved result, not a generic hello.)

  • show_to_caller display path (gateway + worker logs, redacted): on a live call the bot browsed to the OpenClaw GitHub page, screenshotted it, and rendered it on its own video tile.

    Gateway: voice-realtime-show:<callId> consult ran, produced the screenshot file, and sent display.image.

    Worker: [display] showing image for 5000 ms β€” the screenshot was rendered fullscreen on the bot's video tile, then it returned to the avatar.

  • What was not tested: None β€” all paths in this PR were exercised on live Microsoft Teams calls (including show_to_caller, proven above).

  • Proof limitations or environment constraints: Evidence is copied live output, redacted to avoid publishing screen/camera frames, call identifiers, and account details.

Config (example)

"voice-call": { "config": {
  "msteams": {
    "port": 8443, "path": "/voice/msteams/stream",
    "sharedSecret": "<secretref>", "requireRecordingStatus": true,
    "maxVisionPerMinute": 30,
    "outbound": { "enabled": true, "workerBaseUrl": "<authenticated endpoint>", "answerTimeoutMs": 120000 },
    "groupCall": { "requireAddress": true, "wakePhrases": ["assistant", "<bot name>"], "followUpWindowMs": 12000 }
  },
  "realtime": { "enabled": true }
} }

tenantId is read from channels.msteams.tenantId. groupCall only affects calls with 2+ humans; 1:1 always responds. maxVisionPerMinute (default 30, 0 = unlimited) caps the per-call vision spend across all three consumers β€” look_at_screen, streaming auto-attach, and the realtime ambient sendImage push.

Companion media worker & follow-ups

  • Companion media worker β€” source to be shared. The avatar render + inbound-media bridge runs in a .NET Windows media worker (Microsoft Graph Communications + Microsoft.Skype.Bots.Media, Windows-only) that pairs with this provider over the HMAC WebSocket bridge. It renders the be-seen avatar β€” RMS lip-sync, subtle head bob/sway, eye-blink, attention gaze toward the active speaker, emotion-shaped and viseme-shaped mouths (driven by this PR's expression / speech.marks senders), and the display.image tile (show_to_caller) β€” and does adaptive inbound-video sampling (caller-speech + scene-change β†’ faster sampling) with static-frame dedup. Per the maintainer's suggestion the worker source will be shared; candidate home is openclaw/openclaw (vendored subdirectory) or a dedicated openclaw/openclaw-windows-node repo. The bridge protocol is already versioned and additive, so either layout works β€” it's a repo-layout / release-ownership decision, not a code one.
  • Continuous face-to-face CVI (the masterpiece direction): always-on two-way video β€” streaming perception (not just sampled frames), sub-second dialogue, and continuous photorealistic/animated rendering driven by viseme timing β€” built on the perception + dialogue foundation in this PR.
  • Assistant visual sharing β€” PiP overlay (the fullscreen path ships in this PR; a picture-in-picture inset over the avatar is the follow-up).
  • Viseme full pass β€” the spike renders end-to-end with text-estimated marks; swapping in ElevenLabs /with-timestamps real timing needs the TTS-path threading (optional speechMarks on the telephony result β†’ playTts), additive/opt-in so other channels are untouched.
  • Optional dominant-speaker camera selection (opt-in).
  • Outbound call-back result-delivery robustness (the call-back connects; delivering the resolved result is being hardened β€” instruction tightened, deterministic delivery is a follow-up).

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation channel: msteams Channel integration: msteams channel: voice-call Channel integration: voice-call extensions: openai size: XL triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels Jun 8, 2026
@clawsweeper

clawsweeper Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 1, 2026, 9:34 AM ET / 13:34 UTC.

Summary
The PR adds a Microsoft Teams voice/video CVI provider to the voice-call plugin, including an HMAC media WebSocket bridge, realtime image/display support, outbound call-back flow, docs, and tests.

PR surface: Source +4073, Tests +2999, Docs +177. Total +7249 across 47 files.

Reproducibility: not applicable. this is a new feature PR, not a current-main bug with a reproduction path.

Review metrics: 3 noteworthy metrics.

  • Diff Size: +7295/-46 across 47 files. The PR spans provider runtime, tests, docs, plugin metadata, OpenAI realtime, and SDK surfaces, so maintainers need broad review context before merge.
  • Config/Default Surfaces: 1 provider id added, 1 msteams config block added, multiple new operator fields. New provider/config/default surfaces are compatibility-sensitive because they affect setup, operator action, and runtime startup behavior.
  • Public API Surfaces: 1 optional realtime voice bridge method and 1 image input type added. The plugin SDK bridge contract is a public extension seam, so optional additions still require compatibility and documentation acceptance.

Stored data model
Persistent data-model change detected: serialized state: extensions/openai/realtime-voice-provider.test.ts, serialized state: extensions/voice-call/src/msteams-realtime.ts, serialized state: src/talk/session-runtime.ts. Confirm migration or upgrade compatibility proof before merge.

Root-cause cluster
Relationship: canonical
Canonical: #91438
Summary: this PR is the focused foundation branch; closed subset PRs are superseded here, while broader #94978 overlaps but is still dirty and not a safe replacement.

Members:

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

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

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

Rank-up moves:

  • Resolve the current merge conflicts and refresh checks on the latest base.
  • Get explicit maintainer sign-off on the provider, companion worker contract, SDK hook, config defaults, and media security boundary.

Mantis proof suggestion
Independent visual proof would materially help maintainers review the live Teams CVI/media path even though contributor proof is sufficient. A maintainer can ask Mantis to capture proof by posting this exact PR comment:

@openclaw-mantis visual task: verify a real Teams call can use msteams voice, screen vision, show_to_caller display, and outbound call-back with redacted diagnostics.

Risk before merge

  • [P1] The live PR is currently DIRTY against main, so maintainers need a conflict refresh and current-head CI before any merge decision.
  • [P1] Merging accepts a new bundled Teams voice/video provider and operator-facing config that depends on a companion Windows/.NET media worker outside this PR's source tree.
  • [P1] Merging expands the public realtime voice bridge contract with ambient image context, so plugin SDK compatibility and provider semantics need explicit maintainer acceptance.
  • [P1] The bridge handles media, shared secrets, recording-status compliance, outbound worker calls, local image display, and SSRF-guarded remote image fetching; green tests do not replace security-owner review of that boundary.

Maintainer options:

  1. Accept As Foundation After Owner Review (recommended)
    Resolve conflicts, rerun current-head checks, and land only after maintainers explicitly sign off on the bundled provider, companion-worker contract, config defaults, SDK hook, and media security boundary.
  2. Split Provider From CVI Surface
    If the Teams provider is wanted but display, ambient image, or SDK shape is not settled, ask for a narrower provider-first PR and move the CVI display/API pieces to follow-up work.
  3. Pause For Broader Branch Only If It Becomes Viable
    Pause or close this branch only if feat(msteams): Microsoft Teams voice (CVI) + video + chat + governanceΒ #94978 or another replacement becomes conflict-free, proof-positive, and clearly selected as the canonical landing path.

Next step before merge

  • [P2] The remaining work is maintainer product/API/security and canonical-branch review, not a safe automated repair task.

Security
Needs attention: No discrete exploit was proven, but the PR adds a new security-sensitive media bridge and display boundary that needs explicit owner review before merge.

Review details

Best possible solution:

Choose this PR as the canonical Teams CVI foundation only after conflicts are resolved, current-head checks pass, and maintainers explicitly accept the provider, SDK, companion-worker, session, and security boundaries.

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

Not applicable: this is a new feature PR, not a current-main bug with a reproduction path.

Is this the best way to solve the issue?

Unclear as a final product decision: the implementation is a credible focused foundation and not obsolete, but maintainers still need to choose the canonical Teams/CVI/API/security shape before merge.

AGENTS.md: found and applied where relevant.

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

Label changes

Label changes:

  • add status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (recording): The PR body supplies live Teams call proof with screenshots, a screen recording, copied output, and redacted logs for the changed behavior.
  • remove status: πŸ‘€ ready for maintainer look: Current PR status label is status: ⏳ waiting on author.

Label justifications:

  • P2: This is a substantial opt-in feature with limited blast radius and no current-main regression, data-loss, crash-loop, or confirmed security-bypass signal.
  • merge-risk: 🚨 compatibility: The PR adds a provider id, msteams config/default surface, and optional SDK method that can affect provider/plugin compatibility and operator setup.
  • merge-risk: 🚨 session-state: The Teams realtime path introduces call/session identity, callback lifecycle, sessionScope, and active-call status behavior that affects conversation continuity.
  • merge-risk: 🚨 security-boundary: The PR processes Teams media through HMAC WebSockets, SecretRef-backed shared secrets, recording gates, SSRF-guarded worker calls, and local/remote display-image handling.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (recording): The PR body supplies live Teams call proof with screenshots, a screen recording, copied output, and redacted logs for the changed behavior.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body supplies live Teams call proof with screenshots, a screen recording, copied output, and redacted logs for the changed behavior.
  • proof: πŸŽ₯ video: Contributor real behavior proof includes video or recording evidence. The PR body supplies live Teams call proof with screenshots, a screen recording, copied output, and redacted logs for the changed behavior.
Evidence reviewed

PR surface:

Source +4073, Tests +2999, Docs +177. Total +7249 across 47 files.

View PR surface stats
Area Files Added Removed Net
Source 28 4112 39 +4073
Tests 15 3003 4 +2999
Docs 4 180 3 +177
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 47 7295 46 +7249

Security concerns:

  • [medium] Review the new media bridge trust boundary β€” extensions/voice-call/src/msteams-media-stream.ts:1
    The Teams bridge accepts media and lifecycle messages over an HMAC WebSocket, gates recording state, and relays audio/video into agent paths; this is a new security boundary even with schema tests and loopback defaults.
    Confidence: 0.86
  • [medium] Review outbound worker SSRF and trust assumptions β€” extensions/voice-call/src/providers/msteams.ts:1117
    Outbound call placement intentionally permits private-network worker hosts after SSRF guarding, so maintainers should explicitly accept the operator-configured worker trust model.
    Confidence: 0.82
  • [medium] Review local and remote display-image handling β€” src/talk/agent-consult-runtime.ts:45
    show_to_caller can read trusted local media and SSRF-guard remote URLs before rendering to a live call, so the trustLocalMedia and image-size boundaries need security-owner acceptance.
    Confidence: 0.83

Acceptance criteria:

  • [P1] Resolve PR conflicts against current main before merge.
  • [P1] Rerun current-head CI after conflict resolution.
  • [P1] Review live Teams proof artifacts and decide whether independent Mantis proof is needed.
  • [P1] Confirm SDK docs/API baseline and companion-worker contract are acceptable.

What I checked:

  • AGENTS policy applied: Root AGENTS.md and scoped extensions/docs/plugin-sdk guides require whole-surface review for plugin, config, SDK, session, and security-boundary changes. (AGENTS.md:1, be2c4c65abd9)
  • Live PR state: GitHub reports this PR open at head 02f7aed with +7295/-46 across 47 files, proof labels, P2, and merge-risk labels; live mergeStateStatus is DIRTY. (02f7aedd4c25)
  • Current main lacks provider: Current main voice-call provider enum contains telnyx, twilio, plivo, and mock only, so the msteams provider is not already implemented on main. (extensions/voice-call/src/types.ts:9, be2c4c65abd9)
  • Current main lacks SDK image hook: Current main RealtimeVoiceBridge has audio, message, tool-result, mark, close, and connection methods but no sendImage method. (src/talk/provider-types.ts:185, be2c4c65abd9)
  • PR adds SDK image contract: The PR adds RealtimeVoiceImageInput and optional RealtimeVoiceBridge.sendImage for ambient visual context. (src/talk/provider-types.ts:185, 02f7aedd4c25)
  • PR adds Teams config surface: The PR adds MsteamsConfigSchema with port, bindAddress, path, sharedSecret, recording gate, outbound, group call, and vision-budget fields. (extensions/voice-call/src/config.ts:83, 02f7aedd4c25)

Likely related people:

  • steipete: Recent GitHub path history shows repeated docs, plugin SDK, session/state, voice-call, and msteams-adjacent refactors around the surfaces this PR extends. (role: adjacent owner; confidence: medium; commits: 6868cde4d45f, 85e6940202a1, 1507a9701b83; files: extensions/voice-call/src/config.ts, src/talk/provider-types.ts, docs/plugins/voice-call.md)
  • vincentkoc: Recent commit history includes voice-call path normalization, voice model/provider routing, OpenAI realtime startup, and current Teams/message-channel work near this PR's runtime and provider surfaces. (role: recent area contributor; confidence: medium; commits: 905c9759a716, 27b15a19e84c, bc7c2baa5c0f; files: extensions/voice-call/src/config.ts, extensions/openai/realtime-voice-provider.ts, extensions/msteams/src/monitor-handler/message-handler.ts)
  • heyitsaamir: GitHub commit history shows a major msteams SDK migration and channel streaming work in the existing Teams plugin, adjacent to this PR's Teams-specific behavior and delivery semantics. (role: feature-area contributor; confidence: medium; commits: 04c29825356f; files: extensions/msteams/src/monitor-handler/message-handler.ts)
  • eleqtrizit: Recent Teams channel history includes service URL trust-boundary hardening, which is relevant to this PR's new Teams media and outbound worker trust boundaries. (role: security-boundary contributor; confidence: medium; commits: 62550710bfec; files: extensions/msteams/src/monitor-handler/message-handler.ts)
  • mushuiyu886: Recent voice-call history includes canonical session-key and routing ownership work, adjacent to this PR's call/session identity and sessionScope behavior. (role: recent session-routing contributor; confidence: medium; commits: 65fec9d787e3; files: extensions/voice-call/src/config.ts, docs/plugins/voice-call.md)
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.

@clawsweeper clawsweeper Bot added rating: πŸ¦ͺ silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: πŸ“£ needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. labels Jun 8, 2026
@ahenawy
ahenawy force-pushed the feat/avatar-lipsync branch from b431b9e to 5a9d226 Compare June 9, 2026 07:58
@clawsweeper clawsweeper Bot added rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. and removed rating: πŸ¦ͺ silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: πŸ“£ needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 9, 2026
@ahenawy
ahenawy marked this pull request as ready for review June 9, 2026 08:35
@ahenawy
ahenawy requested a review from a team as a code owner June 9, 2026 08:35
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: πŸŽ₯ video Contributor real behavior proof includes video or recording evidence. rating: πŸ§‚ unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. labels Jun 9, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. label Jun 9, 2026
@ahenawy
ahenawy force-pushed the feat/avatar-lipsync branch from 5a9d226 to f11e881 Compare June 9, 2026 09:04
@openclaw-barnacle openclaw-barnacle Bot added triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. and removed proof: sufficient ClawSweeper judged the real behavior proof convincing. labels Jun 9, 2026
@ahenawy
ahenawy force-pushed the feat/avatar-lipsync branch from f11e881 to 011ee29 Compare June 9, 2026 09:12
@openclaw-barnacle openclaw-barnacle Bot removed the triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. label Jun 9, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 10, 2026
@ahenawy

ahenawy commented Jun 10, 2026

Copy link
Copy Markdown
Author

@clawsweeper review

Rebased onto latest upstream/main (0 behind) at 5c8499dc12. The author-actionable findings from the recent reviews are resolved on this head β€” summary so they can be marked done:

Finding Resolution
[P1] Gate realtime caller audio on recording status pushAudio drops caller audio until recording is active, matching the streaming path and every other media-derived path
[P1] Preserve local-media trust boundary (src/talk/agent-consult-runtime.ts) the consult collector allows remote http(s) URLs (SSRF-guarded downstream) but surfaces local paths only when trusted; show_to_caller opts in via trustLocalMedia; arbitrary local paths from other consults are dropped + regression test
[P1] Visual tools must honor realtime.toolPolicy look_at_screen is hidden under none; show_to_caller is exposed only under owner; + none / safe-read-only / owner tests
[P3] Live view β€” undefined. ambient frame label omitted when a frame has no participant name
[P2] Realtime session keys ignore sessionScope the realtime subagent key derives from sessionScope + the caller's AAD id (per-call fallback for anonymous); + regression test (same caller across two calls shares the key)
[P1] Outbound realtime callback lifecycle manager-driven hangup now closes + removes the realtime bridge and hangs up the Teams session (disposeRealtimeCall / close(reason)); the streaming notify auto-hangup is explicitly replaced by conversational delivery and documented; + test
[P2] getCallStatus ignored realtime-only calls now checks realtimeCalls too, so an active outbound callback isn't reaped as terminal on restore; + test
[P3] Public Teams voice docs lag behavior docs/plugins/voice-call.md updated for group calls, outbound call-back, inbound video, and the CVI avatar / show_to_caller / continuous-vision paths

All gating checks were green before the rebase (check-lint, check-additional-extension-bundled, Real behavior proof); they re-run on the new head.

The remaining item is the maintainer product/API-acceptance decision for the new msteams provider/config and the sendImage SDK surface β€” justification in the follow-up comment below.

@ahenawy

ahenawy commented Jun 10, 2026

Copy link
Copy Markdown
Author

Maintainer sign-off context β€” new msteams config + sendImage SDK surface

Addressing the merge-risk: compatibility / security-boundary / session-state labels β€” the new operator/API surface that needs an explicit maintainer accept rather than an author code fix.

1. New msteams provider config (13 fields) β€” additive + opt-in

Entirely gated behind the new provider id provider: "msteams". No existing config keys change; the telnyx / twilio / plivo / mock providers and any install that does not enable msteams are untouched. The block is strict (additionalProperties: false), so an operator typo fails closed instead of silently mis-binding.

Safe-by-default posture:

  • inboundPolicy defaults to allowlist (closed; never open); an empty allowFrom accepts no one.
  • requireRecordingStatus defaults to true β€” no media-derived data is processed before Teams reports active recording (Microsoft Media Access API obligation).
  • bindAddress is loopback by default; sharedSecret is SecretRef-backed; the WebSocket upgrade is HMAC-SHA256-verified and payload-capped.
  • maxVisionPerMinute (default 30) caps per-call vision spend; outbound is disabled unless explicitly enabled.

Compatibility: new optional surface only β€” no migration needed for existing users; no openclaw doctor change required.

2. RealtimeVoiceBridge.sendImage β€” one optional SDK method

The single core plugin-sdk addition (deliberately held for a maintainer decision). It pushes an ambient input_image conversation item into a realtime session with no forced response, used for CVI continuous vision β€” recording-gated and budget-capped. It is optional on the contract: third-party realtime providers can ignore it; the bundled openai / Azure providers implement it. Additive β€” existing realtime audio behavior is unchanged.

3. Security / session-state boundary (the labels)

  • security-boundary: recording gate on every media path (audio uplink, ambient vision, look_at_screen / show_to_caller / background task); SSRF-guarded outbound place-call and display-image fetches (local display paths surfaced only when trusted); HMAC-verified WS; allowlist-closed inbound.
  • session-state: realtime subagent memory honors sessionScope (per-caller continuity); getCallStatus tracks realtime-only calls so an active outbound callback is not reaped on restore; manager-driven hangup fully disposes the realtime bridge and the Teams session.
  • compatibility: the provider, config, and SDK method are all additive and opt-in.

This is the one remaining long pole that can't be closed in code β€” it's a product/API acceptance decision. If you'd prefer to land the base msteams voice/video provider first and defer the CVI display path and/or sendImage, I'm happy to split them into a follow-up (ClawSweeper's option #3).

@clawsweeper

clawsweeper Bot commented Jun 10, 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 added rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. 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 Jun 10, 2026
streamPcmFrames relied on session.send throwing to abort playback when the
Teams worker socket closed mid-frame, but session.send -> sendTo dropped the
frame silently and returned, so playback advanced seq/timestamps and let
manager.speak treat the audio as delivered on a dead socket.

- sendTo / MsteamsSession.send now return whether the message was delivered
  (false on a closed/missing socket); best-effort control frames ignore it
- streamPcmFrames throws when an audio.frame is dropped, so playTts/speak
  finalize the turn instead of reporting false success
- test: session.send is true while open, false once the socket has closed
@ahenawy

ahenawy commented Jun 10, 2026

Copy link
Copy Markdown
Author

Update β€” rebased on latest main; ClawSweeper findings addressed + config/SDK surface rationale

Rebased onto current main (conflict-free β€” the change set doesn't overlap any file changed upstream). Head b21929155f.

Review findings β€” fixed

1. getCallStatus omitted realtime calls β†’ an active outbound realtime callback was reported terminal and reaped on manager restore.

  • Fix: extensions/voice-call/src/providers/msteams.ts:1302 now returns in-progress when either calls or realtimeCalls holds the id. Outbound realtime calls live only in realtimeCalls (with a CallRecord from manager.initiateCall), so the streaming-only check misreported them.
  • Test: extensions/voice-call/src/providers/msteams.test.ts:973 β€” an inbound realtime session.start reports in-progress with no streaming calls entry.

2. Closed-socket sends could report playback success on a dead socket.

  • Root cause: streamPcmFrames relied on session.send throwing to abort playback, but session.send β†’ sendTo (extensions/voice-call/src/msteams-media-stream.ts:554) dropped the frame silently and returned, so playback kept advancing seq/timestampMs and manager.speak treated the audio as delivered on a dead socket.
  • Fix: sendTo / MsteamsSession.send now return whether the message was delivered (false on a closed/missing socket); streamPcmFrames throws when an audio.frame is dropped (extensions/voice-call/src/providers/msteams.ts:1272), so playTts/speak finalize the turn instead of reporting false success. Best-effort control frames (pong, expression, speech.marks, assistant.cancel) ignore the boolean.
  • Test: extensions/voice-call/src/msteams-media-stream.test.ts β€” session.send returns true while the socket is open and false once it has closed (the contract streamPcmFrames relies on).

Also on the branch: realtime visual tools honor realtime.toolPolicy (test in msteams-realtime.test.ts); realtime subagent memory honors sessionScope; manager-driven hangups close the realtime bridge and the Teams session via teardownCall (extensions/voice-call/src/providers/msteams.ts). The close+forget path is unified in one disposeRealtimeCall helper, which also fixed two outboundRealtimeInternalIds leaks (stop() and the caller-hangup path).

New config / SDK surface β€” compatibility rationale

Flagged for maintainer signoff (merge-risk: compatibility / security-boundary / session-state). All additions are additive and off by default:

  • sendImage is an optional SDK method. Declared sendImage?(image: RealtimeVoiceImageInput) at src/talk/provider-types.ts:206 and always invoked through optional chaining (src/talk/session-runtime.ts:99), so existing RealtimeVoiceBridge implementations are unaffected; only extensions/openai/realtime-voice-provider.ts:476 implements it. No existing provider contract changes.
  • msteams config is opt-in. msteams: MsteamsConfigSchema.optional() (extensions/voice-call/src/config.ts:520) plus an additive provider enum value (:508). Absent config = feature off; no existing key is removed, narrowed, or made fail-closed.
  • No new secret mechanism. msteams.sharedSecret reuses the shared SecretInputSchema / SecretRef surface (config.ts:97), identical to the existing authToken input (:79).
  • Privacy default is fail-closed. requireRecordingStatus defaults true (config.ts:807) β€” call audio/video is processed only while Teams reports active recording.

No openclaw doctor migration is required: no shipped config shape changes, only new optional keys.

Validation

  • Full msteams + msteams-realtime + msteams-media-stream suites green; the closed-socket and realtime-status cases each have a focused test.
  • oxlint clean on touched files; tsgo:prod clean.

@ahenawy

ahenawy commented Jun 10, 2026

Copy link
Copy Markdown
Author

@clawsweeper review

The last two re-reviews crashed before completing ("off-meta tidepool / review did not complete"). Requesting a fresh review of head b21929155f β€” both [P2]s are fixed with tests on this head: getCallStatus now includes realtimeCalls, and closed-socket audio sends fail visibly (sendTo/session.send return delivery; streamPcmFrames aborts on a dropped frame).

@clawsweeper

clawsweeper Bot commented Jun 10, 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:

- msteams-realtime.test.ts: the mock session's `send` returns boolean (was the
  number from Array.push) now that MsteamsSession.send reports delivery
- msteams.test.ts: the getCallStatus realtime test reads the provider from
  setup()'s non-undefined return instead of the module-level optional
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. 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: 🌊 off-meta tidepool PR readiness rating does not apply to this item. labels Jun 10, 2026
ahenawy added a commit to ahenawy/openclaw that referenced this pull request Jun 14, 2026
…enclaw#91438 + openclaw#92081)

docs/channels/msteams.md never carried the Teams integration's feature config β€”
add the channel governance keys (dlp / auditChannel / transcribeVoiceMessages),
a Governance section (DLP redaction, audit-log mirror, voice-message
transcription), the 'Ask OpenClaw about this' message action, and a Voice/CVI
summary that cross-links the voice-call plugin doc. In docs/plugins/voice-call.md
fill the msteams-voice gaps: greet-by-name + deterministic verbal interrupts
(EN/AR), bilingual, meetingRecap, DTMF/IVR, outbound notify modes
(defaultMode/notifyHangupDelaySec/greet-on-answer), and the realtime echo /
self-answer guard (suppressInputDuringPlayback + window/RMS knobs). Covers the
foundation (openclaw#91438) features too since that PR is frozen.

Co-Authored-By: Claude Fable 5 <[email protected]>
@openclaw-barnacle

Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle

Copy link
Copy Markdown

Closing due to inactivity.
If you believe this PR should be revived, post in #clawtributors on Discord to talk to a maintainer.
That channel is the escape hatch for high-quality PRs that get auto-closed.

@steipete

Copy link
Copy Markdown
Contributor

Thanks, Alaaeldin. Peter has decided to keep this work alive. Since this branch was written, current main gained the shared meeting-bot and MeetingPlatformAdapter architecture, so the right landing path is now a rewrite of the Graph media work as a transport inside teams-meetings, reusing the shared meeting and Talk engines rather than adding Teams as a voice-call provider. We'll preserve your credit and link the staged implementation PRs back here. Your live Teams proof and the lifecycle/security fixes you made during review remain important inputs to that design.

This PR was auto-closed by the stale bot on July 23; reopening it as the focused foundation record while the staged path is reviewed. The broader plan lives on #94978.

@clawsweeper

clawsweeper Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper status: review started.

I am starting a fresh review of this pull request: feat(voice-call): Microsoft Teams provider β€” CVI voice/video calls 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.

@steipete

Copy link
Copy Markdown
Contributor

Closing with the same maintainer decision as #94978 (comment) follow-up there: the required Microsoft.Graph.Communications.Calls.Media SDK is proprietary/closed-source, and openclaw core only merges fully open-source work β€” so the Graph-media transport cannot land in this repo. The supported path is an external ClawHub plugin built on the public plugin-SDK seams (meeting-runtime, realtime-voice, transcripts); SDK gaps that block an external implementation are welcome as focused issues and are mergeable core work. What would change this decision: a fully open-source real-time media path for Teams. Thanks for the work here, Alaaeldin β€” the constraint is Microsoft's licensing, not your implementation.

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

Labels

channel: msteams Channel integration: msteams channel: voice-call Channel integration: voice-call docs Improvements or additions to documentation extensions: openai feature: ✨ showcase ClawSweeper spotlight: unusually compelling feature idea for maintainer attention. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. 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: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. proof: πŸŽ₯ video Contributor real behavior proof includes video or recording evidence. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: XL stale Marked as stale due to inactivity 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.

5 participants