* feat(msteams): deliver native Adaptive Card approvals
Exec and gateway plugin approvals now render as Adaptive Cards in Microsoft
Teams with token-bound approve/deny actions, mirroring the Google Chat card
pattern. Card submits are intercepted before message-text serialization,
authorized against channels.msteams.allowFrom/defaultTo AAD object IDs via
the existing approval auth, claimed once, resolved over the gateway, and the
card is updated in place to its terminal state. Native delivery gates on the
top-level approvals.exec/approvals.plugin forwarding config; the /approve
text fallback remains.
* chore(msteams): shrink assertion-safety baseline after send.ts cast removal
* chore(msteams): record approval-native adapter seam in chained-assertion ledger
* fix(msteams): surface a text approval fallback when card delivery fails
When the native route suppressed the local text prompt, a failed Adaptive
Card send only logged, leaving the pending approval invisible. On delivery
error, send a plain-text /approve prompt to the planned target so the
operator always has a visible approval path. Addresses the ClawSweeper P1
on #129997 channel-locally; #130040 tracks the shared-boundary fix.
* fix(msteams): use one Teams API runtime entrypoint
* test(msteams): exercise packaged CJS loader
* test(msteams): run packaged build through CLI
* chore(msteams): document SDK assertion safety
* fix(msteams): require dot boundary for shared-link host suffix match
Look-alike hosts such as evil1drv.ms, notonedrive.com, or
fakeonedrive.live.com satisfied the bare endsWith() check in
isGraphSharedLinkUrl and were rewritten to the Graph shares endpoint.
Match bare suffixes on a label boundary instead: exact host or a
dot-prefixed suffix.
* test(msteams): cover lookalike download routing
* fix(msteams): require https for shared links
---------
Co-authored-by: sallyom <somalley@redhat.com>
Table-driven regressions prove personal and group sends never carry a
Teams thread suffix while channels preserve ;messageid, and that agent
react actions route to the prepared Graph target per conversation kind
with exact result payloads and inbound reaction event labels.
Parity proof for Microsoft Lobster patches 0038/0026 via
giodl73-repo/lobster-plugins-and-patches.
* fix(msteams): preserve replies across ingress replay
Recovered Teams channel and group-chat responses now preserve reply and quote context across durable ingress replay. Discovery metadata also advertises the existing group and reaction capabilities.
* chore(msteams): document replay assertion safety
* test(msteams): normalize replay delivery errors
* fix(msteams): dedupe poll vote selections before maxSelections truncation
normalizeMSTeamsPollSelections truncated the raw selections to
maxSelections before deduplicating, so a duplicated choice (e.g. an
Adaptive Card posting "0,0,1") consumed multiple slots and starved
later distinct choices: with maxSelections=2 the stored vote became
["0"] and choice "1" was silently dropped. Deduplicate first, then
truncate.
* fix(msteams): simplify poll selection normalization
---------
Co-authored-by: ClawSweeper <steipete+clawsweeper@gmail.com>
* refactor: consolidate coercion ownership
Centralize four canonical coercion helpers, migrate exact core and plugin duplicates through narrow Plugin SDK facades, and enforce declaration and plugin-normalization ownership boundaries.
The sweep adds eight focused SDK exports while deleting more production and tooling code than it adds. User-visible behavior is unchanged except for safer equivalent object and UI parsing at existing boundaries.
* fix: guard integer option ownership
Register resolveIntegerOption with the canonical function owner and extend the declaration-guard fixture so future local duplicates fail validation.
* fix: keep integer helpers on numeric facade
Remove the unshipped duplicate string-coerce exports and route every affected plugin consumer through the existing number-runtime contract.
* fix: point numeric coercion to number runtime
Make boundary and declaration diagnostics recommend the canonical numeric facade, with failing-before coverage for both guidance paths.
Unset heartbeat.target now resolves "owner": elected heartbeat notifications deliver to the operator's DM resolved from commands.ownerAllowFrom or the channel allowFrom (first concrete entry; wildcards and channel-scoped wildcards excluded; configured owners exhausted across channels before any channel-local fallback). Delivery requires the channel's own classifier to positively prove a direct destination — every bundled messaging plugin now ships an inferTargetChatType contract — and unproven or group-shaped destinations fail closed to the visible no-route state. The first implicitly-routed delivery carries a one-line self-explanation naming the target: "none" opt-out. Explicit target "last" remains as the follow-the-conversation opt-in. Refines the unreleased #121892 default before it ships; refs #121880.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Keep raw commands, paths, and provider errors out of ordinary chat while preserving explicit raw diagnostics and structured admin history.
Default command progress is status-only; `/verbose full` and `commandText: "raw"` retain diagnostic detail.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* refactor(test): exercise real Teams monitor lifecycle
* refactor(test): use real gateway lifecycle state
* refactor(test): use real restart delivery queue
* test: type Teams SDK boundary fixture