Summary
The MS Teams adapter now has the full interactive round-trip (#155 — Button/Selection/Form → Adaptive Card actions, verified callback, in-place refresh). What remains before it is at full feature parity with google_chat is the one-way display richness: inline chart images and card theming. This is the Teams sibling of #154 (which did the same for google_chat) and is out of scope for #155 by design.
Current state (post-#155)
crates/triton-chat-msteams/src/surface_mapper.rs:
Component::Report is ignored. The text mapper drops it (Component::Report { .. } => {}) and the card path (render_card_content in lib.rs) only lifts interactive controls + Dashboard. So an agent that emits a rendered chart (e.g. peacock render_report) shows nothing on Teams.
Dashboard renders as a FactSet only (metric tiles). No chart image. Per architecture.md §8.7 this is arguably the intended native projection for Teams (ColumnSet/FactSet, skip the rasteriser), so the tiles are fine — but a genuinely rendered chart (a Report) still needs an Image element.
- No card theming. By contrast
google_chat's CardChrome pulls get_theme (logo, brand colour, header) and brands every card; Teams cards are unbranded.
- No image-serving route.
google_chat serves chart PNGs at a signed …/img/{token} route (serve_dashboard_png); msteams' router has only the /<name>/webhook route.
Proposed work
1. Inline Report → Adaptive Card Image.
- Add
report_from_result (mirror google_chat) to lift Component::Report { report_id, args }.
- On a reply carrying a
Report, dispatch render_report(report_id, args) through the upstream router with the resolved principal (identity + audit symmetry), take the returned PNG, and embed it in the card.
- Teams Adaptive Cards render images by URL, so this needs the hosting story below (a signed
…/img/{token} route serving the cached PNG), OR — if small enough — a data: URI Image (verify Teams' size limits before relying on it).
2. Signed image route.
- Add a
GET /<name>/img/{token} route that decodes a signed token and returns the cached PNG, mirroring google_chat's serve_dashboard_png (including the RENDER_REPORT_IMG_MARKER distinction between a dashboard-spec token and an upstream-rendered-PNG token, and the replay guard that a button/form token can't be replayed at …/img/).
3. Adaptive Card theming (get_theme → chrome).
- Add a msteams
CardChrome analogue: fetch get_theme from the report upstream and apply title/logo/brand colour to the Adaptive Card (card title/Image header, Action.Style: positive or a container style for brand colour — Adaptive Cards don't take arbitrary hex on buttons, so map brand → the closest native affordance).
4. (Optional) Dashboard-as-image.
- If a
Dashboard should ever render as a rasterised chart on Teams (rather than the native FactSet), reuse the same …/img/{token} path. Default remains the native FactSet per §8.7 — decide explicitly.
Out of scope
Acceptance criteria
References
Summary
The MS Teams adapter now has the full interactive round-trip (#155 —
Button/Selection/Form→ Adaptive Card actions, verified callback, in-place refresh). What remains before it is at full feature parity withgoogle_chatis the one-way display richness: inline chart images and card theming. This is the Teams sibling of #154 (which did the same for google_chat) and is out of scope for #155 by design.Current state (post-#155)
crates/triton-chat-msteams/src/surface_mapper.rs:Component::Reportis ignored. The text mapper drops it (Component::Report { .. } => {}) and the card path (render_card_contentinlib.rs) only lifts interactive controls +Dashboard. So an agent that emits a rendered chart (e.g. peacockrender_report) shows nothing on Teams.Dashboardrenders as aFactSetonly (metric tiles). No chart image. Per architecture.md §8.7 this is arguably the intended native projection for Teams (ColumnSet/FactSet, skip the rasteriser), so the tiles are fine — but a genuinely rendered chart (aReport) still needs anImageelement.google_chat'sCardChromepullsget_theme(logo, brand colour, header) and brands every card; Teams cards are unbranded.google_chatserves chart PNGs at a signed…/img/{token}route (serve_dashboard_png); msteams' router has only the/<name>/webhookroute.Proposed work
1. Inline
Report→ Adaptive CardImage.report_from_result(mirror google_chat) to liftComponent::Report { report_id, args }.Report, dispatchrender_report(report_id, args)through the upstream router with the resolved principal (identity + audit symmetry), take the returned PNG, and embed it in the card.…/img/{token}route serving the cached PNG), OR — if small enough — adata:URIImage(verify Teams' size limits before relying on it).2. Signed image route.
GET /<name>/img/{token}route that decodes a signed token and returns the cached PNG, mirroringgoogle_chat'sserve_dashboard_png(including theRENDER_REPORT_IMG_MARKERdistinction between a dashboard-spec token and an upstream-rendered-PNG token, and the replay guard that a button/form token can't be replayed at…/img/).3. Adaptive Card theming (
get_theme→ chrome).CardChromeanalogue: fetchget_themefrom the report upstream and apply title/logo/brand colour to the Adaptive Card (cardtitle/Imageheader,Action.Style: positiveor a container style for brand colour — Adaptive Cards don't take arbitrary hex on buttons, so map brand → the closest native affordance).4. (Optional) Dashboard-as-image.
Dashboardshould ever render as a rasterised chart on Teams (rather than the nativeFactSet), reuse the same…/img/{token}path. Default remains the nativeFactSetper §8.7 — decide explicitly.Out of scope
Dashboard→FactSetprojection (keep it; §8.7).Acceptance criteria
Reportrenders an Adaptive Card carrying the upstream-rendered chart image on Teams.…/img/{token}route; a forged/replayed token is rejected.get_themechrome (title/logo/brand) is applied to interactive + report cards; unset theme = today's unbranded rendering.Report→ image card shape; the image route round-trip against the real binary + a fake upstream that returns a PNG; a forged image-token rejection.References
crates/triton-chat-googlechat/src/{surface_mapper.rs,lib.rs}—report_from_result,dashboard_image_url,serve_dashboard_png,RENDER_REPORT_IMG_MARKER,CardChrome/get_theme.