Case study · Microsoft · Dynamics 365 Contact Center
Bringing a production chat widget into Copilot Studio
I built the customer-facing Live Chat Widget into Copilot Studio's agent test chat, then moved two products onto one chat renderer and one configuration contract.
- Role
- Software Engineer II
- Period
- 2026 to present
- Status
- Current work
This case study uses only what's on my public résumé and in Microsoft's public repositories. Internal code, architecture detail, and metrics stay inside Microsoft.
Overview
The Live Chat Widget is the chat window businesses put on their websites to talk with customers through Dynamics 365 Contact Center. Copilot Studio is where makers build the AI agents that answer in that window.
In 2026 I built the production widget into Copilot Studio's agent test chat, so a maker tests an agent in the same chat UI their customers will see.
Problem
Before this, makers tested agents in a chat surface that wasn't the one their customers would use.
Underneath that was a configuration problem. The admin center, Copilot Studio, and the runtime widget each had their own model of the widget's settings. A partial save in one product could erase settings another product had written.
Constraints
- The widget runs in an iframe inside Copilot Studio, and no credential was allowed to enter it.
- The work crossed two repositories owned by different product organizations.
- Customers were already running the shipping widget, so changes to shared code had to leave its runtime behavior alone.
- The integration needed its own release pipeline, routing to 13 production regions.
Architecture and technical decisions
Copilot Studio page
Host code
- Owns the auth token
- Opens the WebSocket
Live Chat Widget iframe
- Renders the chat
- Never holds a credential
Orchestration service
- Runs the agent conversation
- Credential boundary. The Copilot Studio host owns the auth token and the WebSocket. The widget iframe receives only a transferred MessagePort, over a versioned protocol that validates origins. No credential enters the iframe.
- Transport SDK. A TypeScript SDK for the orchestration service's WebSocket and SSE protocol. There was no client to build on, so I reverse-engineered the protocol with a local simulator.
- One renderer. Both products render chat through one shared React package. Build checks fail CI if either product reimplements shared behavior.
- One configuration contract. A versioned TypeScript package replaces the three configuration models. It handles v1 and v2 compatibility, preserves fields it doesn't recognize, and rejects unsafe CSS and URLs.
My contributions
- Took the integration from an empty repository to CDN deployment in six weeks: new React packages, the transport SDK, and the release pipeline.
- Implemented the embed protocol on both sides, in the Copilot Studio host and in the widget.
- Shipped the configuration contract into all three products, then built dark mode on top of it end to end: authored in the admin center, carried by the contract, and rendered at runtime, including theme-aware Adaptive Cards.
- Owned quality on the shipping widget. I debugged its opening animation frame by frame (it painted one frame at the size of the chat button), fixed attachments and transcript ordering, and shipped the fixes to the release branches customers run.
- Fixed accessibility gaps. Screen readers now announce new messages and sent files across minimize and restore, and citation numbers sit inside the control so voice-control users can target them.
- Took the live chat service's on-call rotation. I root-caused a high-severity outage within about an hour, wrote the RCA, and changed the C# messaging service so a failed background write no longer stops a chat from starting. The alert I built would have fired 11 minutes before existing monitoring.
- Shipped 10+ C#/.NET fixes to the messaging and orchestration services. One covered failed AI dialogs: the customer's next message used to get no reply, and now the failure becomes a plain message and the session re-arms so a retry gets an answer.
- Worked in Microsoft's public chat SDK repositories on GitHub: official releases including the chat widget's 2.0.0, security fixes across five packages, and hotfix workflows with OIDC trusted publishing to npm.
- Earlier in 2026, led frontend architecture for a new agent desktop: a Fluent UI v9 migration in one 243-file PR, a single state store, a three-tier Playwright test architecture, and a localization check that fails the build if hardcoded text comes back.
- Embedded that desktop inside Salesforce with a host-bridge postMessage protocol and a manual PKCE token exchange through Salesforce's iframe sandbox.
Challenges and tradeoffs
Converging the renderers meant changing code that customers were running. I started with a 391-file rename that changed no runtime behavior. Then I split the rest of the work into a planned stack of four PRs, so each step could be reviewed and checked against the shipping widget on its own.
Preserving fields the contract doesn't recognize is less strict than rejecting them. It's what lets three products on different release schedules share one configuration without erasing each other's settings.
A fix isn't done when it merges. Customers run release branches, so many of these fixes also went out as cherry-picks, and that only works if each change is small and safe on its own.
Outcomes
- Makers test agents in the same chat UI their customers see.
- A partial save in one product no longer erases another product's settings.
- Two products share one renderer, and CI stops either one from reimplementing shared behavior.
- After an AI failure, a customer's retry gets a reply.
- Months after I moved on from the agent desktop, the larger team that inherited it was still building on that foundation.
Technologies
- React
- TypeScript
- WebSocket and SSE
- postMessage and MessagePort
- C# / .NET
- Fluent UI
- Playwright
- Azure DevOps pipelines