Skip to content

fix(msteams): handle Adaptive Card Action.Submit invoke activities#60431

Merged
BradGroux merged 3 commits intoopenclaw:mainfrom
BradGroux:bgod/fix-55384-adaptive-card-invoke
Apr 4, 2026
Merged

fix(msteams): handle Adaptive Card Action.Submit invoke activities#60431
BradGroux merged 3 commits intoopenclaw:mainfrom
BradGroux:bgod/fix-55384-adaptive-card-invoke

Conversation

@BradGroux
Copy link
Copy Markdown
Contributor

Fixes #55384

When a user clicks an Action.Submit button on an Adaptive Card, the invoke activity is no longer silently dropped. The handler now detects adaptiveCard/action invoke activities and forwards the value payload to the agent as a message.

Changes:

  • Added serializeAdaptiveCardActionValue() helper to extract/serialize the action payload
  • Added adaptiveCard/action invoke detection in registerMSTeamsHandlers
  • Added test in monitor-handler.adaptive-card.test.ts

Testing:

  • pnpm test -- extensions/msteams/src/monitor-handler.adaptive-card.test.ts — 1/1 pass
  • pnpm check — clean

Copilot AI review requested due to automatic review settings April 3, 2026 16:57
@openclaw-barnacle openclaw-barnacle bot added channel: msteams Channel integration: msteams size: M maintainer Maintainer-authored PR labels Apr 3, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 3, 2026

Greptile Summary

This PR adds handling for adaptiveCard/action invoke activities in the MS Teams extension, forwarding the card payload to the agent as a text message. Two bugs in the implementation need fixing before this is ready to merge:

  • serializeAdaptiveCardActionValue only guards undefined but not nullJSON.stringify(null) returns the truthy string \"null\", which gets forwarded to the agent as if it were a real user message.
  • When the serialized value is falsy, the code logs "skipping" but omits a return, so execution falls through to originalRun.call(handler, context) and the adaptiveCard/action invoke is still dispatched through the normal message chain.

Confidence Score: 3/5

Not safe to merge — two logic bugs in the new handler path that cause incorrect behavior for edge-case payloads.

Two P1 bugs: null payloads are silently forwarded as the string "null", and the no-value path unintentionally re-invokes the normal handler chain instead of being dropped.

extensions/msteams/src/monitor-handler.ts — both bugs are in the new serializeAdaptiveCardActionValue function and the adaptiveCard/action block in registerMSTeamsHandlers.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/msteams/src/monitor-handler.ts
Line: 56-58

Comment:
**`null` value passes through as the string `"null"`**

`null` is not `undefined`, so the `null` guard does not fire — `typeof null === "object"`, which falls through to `JSON.stringify(null)` and returns the truthy string `"null"`. That string is then forwarded to the agent as the message body, which is almost certainly not intended.

```suggestion
  if (value == null) {
    return null;
  }
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/msteams/src/monitor-handler.ts
Line: 420

Comment:
**No-value path falls through to `originalRun`**

When `text` is falsy the code logs "skipping" but then falls through to `return originalRun.call(handler, context)`, passing the `adaptiveCard/action` invoke to the normal handler chain. That chain dispatches the activity as a message, which contradicts the intent of "skipping". A missing `return` here means the invoke is still processed.

```suggestion
        deps.log.debug?.("skipping adaptive card action invoke without value payload");
        return;
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(msteams): handle Adaptive Card Actio..." | Re-trigger Greptile

Comment on lines +56 to +58
if (value === undefined) {
return null;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 null value passes through as the string "null"

null is not undefined, so the null guard does not fire — typeof null === "object", which falls through to JSON.stringify(null) and returns the truthy string "null". That string is then forwarded to the agent as the message body, which is almost certainly not intended.

Suggested change
if (value === undefined) {
return null;
}
if (value == null) {
return null;
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/msteams/src/monitor-handler.ts
Line: 56-58

Comment:
**`null` value passes through as the string `"null"`**

`null` is not `undefined`, so the `null` guard does not fire — `typeof null === "object"`, which falls through to `JSON.stringify(null)` and returns the truthy string `"null"`. That string is then forwarded to the agent as the message body, which is almost certainly not intended.

```suggestion
  if (value == null) {
    return null;
  }
```

How can I resolve this? If you propose a fix, please make it concise.

});
return;
}
deps.log.debug?.("skipping adaptive card action invoke without value payload");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 No-value path falls through to originalRun

When text is falsy the code logs "skipping" but then falls through to return originalRun.call(handler, context), passing the adaptiveCard/action invoke to the normal handler chain. That chain dispatches the activity as a message, which contradicts the intent of "skipping". A missing return here means the invoke is still processed.

Suggested change
deps.log.debug?.("skipping adaptive card action invoke without value payload");
deps.log.debug?.("skipping adaptive card action invoke without value payload");
return;
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/msteams/src/monitor-handler.ts
Line: 420

Comment:
**No-value path falls through to `originalRun`**

When `text` is falsy the code logs "skipping" but then falls through to `return originalRun.call(handler, context)`, passing the `adaptiveCard/action` invoke to the normal handler chain. That chain dispatches the activity as a message, which contradicts the intent of "skipping". A missing `return` here means the invoke is still processed.

```suggestion
        deps.log.debug?.("skipping adaptive card action invoke without value payload");
        return;
```

How can I resolve this? If you propose a fix, please make it concise.

@BradGroux BradGroux self-assigned this Apr 3, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes Microsoft Teams Adaptive Card Action.Submit interactions being dropped by detecting adaptiveCard/action invoke activities and forwarding their value payload to the existing message handler, so the agent receives the button-click data.

Changes:

  • Added serializeAdaptiveCardActionValue() to serialize invoke.value for Adaptive Card actions.
  • Intercepted adaptiveCard/action invoke activities in registerMSTeamsHandlers and re-routed them through the message pipeline.
  • Added a Vitest regression test covering the forwarding behavior.

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 1 comment.

File Description
pnpm-lock.yaml Updates the workspace importers list (includes extensions/stepfun).
extensions/msteams/src/monitor-handler.ts Detects adaptiveCard/action invokes and forwards serialized value to the message handler.
extensions/msteams/src/monitor-handler.adaptive-card.test.ts Adds a regression test ensuring Adaptive Card action payloads reach the agent.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

Comment on lines +56 to +60
if (value === undefined) {
return null;
}
try {
return JSON.stringify(value);
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

serializeAdaptiveCardActionValue() will forward null payloads as the literal string "null" (since JSON.stringify(null) returns "null"), which can result in a synthetic message being dispatched for Action.Submit clicks that carry no usable data. Consider treating null the same as undefined, and also guard against JSON.stringify(...) returning undefined (e.g., for values like functions/symbols) by normalizing that to null as well.

Suggested change
if (value === undefined) {
return null;
}
try {
return JSON.stringify(value);
if (value == null) {
return null;
}
try {
const serialized = JSON.stringify(value);
return typeof serialized === "string" ? serialized : null;

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9ad63a41ce

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

if (ctx.activity?.type === "invoke" && ctx.activity?.name === "adaptiveCard/action") {
const text = serializeAdaptiveCardActionValue(ctx.activity?.value);
if (text) {
await handleTeamsMessage({
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Wrap adaptive invoke forwarding in existing error guard

The new adaptiveCard/action branch calls handleTeamsMessage(...) directly, but unlike the normal onMessage path it is not inside the try/catch that reports failures via runtime.error. If this call throws (for example from debounce/runtime wiring failures), the exception bubbles out of registerMSTeamsHandlers, and createMSTeamsAdapter.process suppresses invoke errors for type === "invoke", so the action is silently dropped with no diagnostic. Reusing the same guarded wrapper as onMessage avoids losing observability for this new path.

Useful? React with 👍 / 👎.

@BradGroux BradGroux force-pushed the bgod/fix-55384-adaptive-card-invoke branch 2 times, most recently from 906afb5 to 568c540 Compare April 3, 2026 18:12
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 568c54018b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +407 to +410
if (ctx.activity?.type === "invoke" && ctx.activity?.name === "adaptiveCard/action") {
const text = serializeAdaptiveCardActionValue(ctx.activity?.value);
if (text) {
await handleTeamsMessage({
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Mark adaptive invokes as mentioned before message forwarding

Forwarding adaptiveCard/action invokes through handleTeamsMessage without any mention override means channel submissions still fail under default policy: resolveMSTeamsReplyPolicy defaults requireMention to true, and wasMSTeamsBotMentioned only checks mention entities. Adaptive invoke payloads typically lack mention entities, so the message handler’s mention gate drops these synthetic messages and the button click is ignored in channels. Please set a mention/bypass signal for this converted activity path (or skip mention gating for this invoke type).

Useful? React with 👍 / 👎.

@BradGroux BradGroux force-pushed the bgod/fix-55384-adaptive-card-invoke branch from 568c540 to 7f31e42 Compare April 3, 2026 18:40
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7f31e42582

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +412 to +415
activity: {
...ctx.activity,
type: "message",
text,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve thread anchor when converting adaptive invokes

This conversion only changes type/text, so the synthetic message keeps the invoke activity ID. Downstream Teams reply threading uses activity.id as the anchor (for example via conversation references and default reply target logic), but adaptive-card clicks in channel threads typically identify the clicked card message via replyToId, not the invoke ID itself. In that case replies can be posted out-of-thread or fail to thread correctly. When constructing this message-shaped activity, carry the thread anchor from replyToId (or explicitly set the reply target) instead of leaving the invoke ID as the message ID.

Useful? React with 👍 / 👎.

@BradGroux BradGroux force-pushed the bgod/fix-55384-adaptive-card-invoke branch from 7f31e42 to a38ea0f Compare April 3, 2026 21:48
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a38ea0ff30

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +410 to +414
await handleTeamsMessage({
...ctx,
activity: {
...ctx.activity,
type: "message",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve adaptive action thread target in converted message

This invoke-to-message conversion reuses ctx.sendActivity, but that sender was created from the original invoke context where replyToActivityId is bound to the invoke activity.id; as a result, replies from this path can target the invoke ID instead of the clicked card message (replyToId) in channel threads. In practice this can post out-of-thread (or fail to thread) for adaptive card submits. Ensure this path explicitly preserves the thread anchor (for example by routing with a context/proactive reference keyed to replyToId, or by setting outbound replyToId explicitly).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 93d5a92276

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

if (ctx.activity?.type === "invoke" && ctx.activity?.name === "adaptiveCard/action") {
const text = serializeAdaptiveCardActionValue(ctx.activity?.value);
if (text) {
await handleTeamsMessage({
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bypass debounce for adaptive card submit events

Forwarding adaptiveCard/action through handleTeamsMessage(...) routes these synthetic submits into the normal inbound debouncer, and when messages.inbound.debounceMs is non-zero the handler can merge multiple rapid button clicks from the same sender/conversation into one combined payload (the flush path concatenates texts and keeps only the last context in extensions/msteams/src/monitor-handler/message-handler.ts). For adaptive actions this changes semantics (distinct submits become one turn), so quick multi-click workflows can execute the wrong action or lose submissions.

Useful? React with 👍 / 👎.

@BradGroux BradGroux merged commit 06c6ff6 into openclaw:main Apr 4, 2026
42 of 80 checks passed
KimGLee pushed a commit to KimGLee/openclaw that referenced this pull request Apr 4, 2026
…penclaw#60431)

* fix(msteams): handle Adaptive Card Action.Submit invoke activities (openclaw#55384)

* ci: retrigger checks

---------

Co-authored-by: Brad Groux <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: msteams Channel integration: msteams maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[msteams] Adaptive Card Action.Submit invoke activities silently dropped

2 participants