fix(feishu): custom interactive card button-click round-trip - #43048
fix(feishu): custom interactive card button-click round-trip#43048Silver-Aurora wants to merge 1 commit into
Conversation
|
Thanks for the PR! The card action fixes are well-analyzed and the session key bug (Bug 2) is clearly explained. A few observations on the approach: SDK monkey-patching fragility - Patching
The |
|
@kyssta-exe Thanks for the thorough review! SDK fragility — agreed, added a comment noting the lark-oapi version (1.5.3) the pass-through behaviour was tested against, so future maintainers have a reference point if the SDK's validation logic changes. chat_type edge case — One more fix — while testing end-to-end I noticed the synthetic text used a Thanks again! |
f245e7c to
1c157cc
Compare
e67d51d to
c3793d9
Compare
teknium1
left a comment
There was a problem hiding this comment.
Thanks for tracing the full callback-to-reply path. The current main implementation still drops operator.union_id, forces the card action through a group source, and anchors the response to the card token (plugins/platforms/feishu/adapter.py:2992-3009), so the core fix addresses live behavior.
Problems
tests/gateway/test_feishu_approval_buttons.py:438still asserts/card button. The changed synthetic text atplugins/platforms/feishu/adapter.pyright-side line 2895 will make that existing test fail.- The diff changes callback parsing, response construction, session identity, and reply fallback without adding regression coverage for those paths.
Suggested changes
- Update the existing non-approval card-action test for the natural-language payload and add assertions for
message_id=None, DM/group source type, andunion_idpropagation. - Add focused tests for string-valued
action.value, the toast response, and reply fallback code99992354. The patch comment cites lark-oapi 1.5.3, whilepyproject.toml:252pins 1.6.8.
Automated hermes-sweeper review.
| @@ -2869,20 +2892,20 @@ async def _handle_card_action_event(self, data: Any) -> None: | |||
| action_tag = str(getattr(action, "tag", "") or "button") | |||
| action_value = getattr(action, "value", {}) or {} | |||
|
|
|||
| synthetic_text = f"/card {action_tag}" | |||
| synthetic_text = f"Card button '{action_tag}' clicked" | |||
There was a problem hiding this comment.
This changes the synthetic payload, but tests/gateway/test_feishu_approval_buttons.py:438 still asserts that it contains /card button; update that test in this PR and assert the intended natural-language payload.
| # Relies on lark-oapi's parse() treating Union types as pass-through | ||
| # (tested with lark-oapi 1.5.3). | ||
| if CallBackAction is not None: | ||
| CallBackAction._types["value"] = Union[str, Dict[str, Any]] |
There was a problem hiding this comment.
Please add regression coverage for this private SDK-model mutation. The repository pins lark-oapi==1.6.8 in pyproject.toml:252, while the comment documents behavior tested on 1.5.3.
8596f53 to
10dbc40
Compare
|
@teknium1 Thanks for the thorough sweep! All suggested changes applied: Fixed:
One note on the string-valued All 41 pre-existing tests continue to pass; the 3 failures ( |
Four fixes for the Feishu card-action callback path: 1. CallBackAction._types monkey-patch: accept both string and dict button values to prevent 200671 errors (tested with lark-oapi 1.6.8). 2. CallBackToast response: immediate visual feedback on custom button clicks (card stays intact for multi-button panels). 3. Synthetic text: natural language instead of /card, plus operator.union_id and chat_info.type for proper session routing. 4. message_id=None + 99992354 fallback: prevent agent responses from being silently dropped due to invalid reply target IDs. Regression tests cover message_id, natural-language text, DM/group source type, union_id propagation, toast response, action-value data, and the 99992354 fallback code.
10dbc40 to
93056fe
Compare
🔮 The vision
Agents should be able to send arbitrary interactive Feishu cards
and have button clicks flow back as natural conversation turns in the
same session — and have the agent's reply actually delivered.
Think multi-button confirmations, inline selectors, wizard panels —
all generated by the agent at runtime via
im_send_card.Three bugs in the card-action callback path make this impossible today.
This PR fixes all three, completing the full round-trip.
🐛 Bug 1 —
200671when buttonvalueis a plain stringThe Feishu API spec says
valuecan be a string or an object,but the
lark-oapiSDK model types it asDict[str, Any]only.When a card has
"value": "yes", the SDK'sUnmarshalExceptionfires before
_on_card_action_triggeris reached, Feishu receivesa non-200 response, and the client shows
出错了 code:200671.Fix: Monkey-patch
CallBackAction._types["value"]toUnion[str, Dict[str, Any]]at import time (both static and lazypaths). The SDK's
parse()routine only validateslist/dict/set/object—Unionpasses through unvalidated, accepting bothforms without touching SDK internals.
🐛 Bug 2 — Card actions always create new isolated sessions
In
_handle_card_action_event(), two mistakes inSessionSourceconstruction cause the session key to never match the active
conversation:
chat_typeevent_chat_type="group"hardcoded…group:oc_xxxinstead of…dm:oc_xxxunion_idunion_id=None)user_id_alt→ per-user isolation brokenFix: Use
chat_info["type"](already correctly resolved byget_chat_info()) and pass throughoperator.union_id.🐛 Bug 3 — Agent responses silently dropped (
99992354)The synthetic
MessageEventwas using the card action token (c-xxx)as
message_id. The gateway (_reply_anchor_for_event()) interpretsa non-empty
message_idas a reply target — but neither card tokensnor UUIDs are valid Feishu
open_message_idvalues, so Feishu rejectsthe send with
99992354. The agent response is generated, sent, andsilently discarded.
Fix (two layers):
Root cause: Set
message_id=Noneon the synthetic event._reply_anchor_for_event()returnsNone, so the gateway sends asa plain (non-reply) message — no invalid ID ever reaches Feishu.
Defense-in-depth: Add
99992354to_FEISHU_REPLY_FALLBACK_CODESso any invalid reply target gracefully falls back to a new message.
🍰 Bonus — Toast feedback for custom buttons
Added
CallBackToastto the callback response so custom buttons getthe same immediate visual feedback that approval cards already have.
✅ New in this revision (2026-07-14)
main(226e8de).TestNonApprovalCardAction:test_message_id_is_none,test_text_includes_action_value_data,test_dm_source_type,test_union_id_propagation,test_99992354_in_fallback_codes, updatedtest_routes_as_synthetic_command,plus
test_toast_for_non_approval_buttoninTestCardActionCallbackResponse.🔒 No breaking changes
hermes_action) and prompt-update cards(
hermes_update_prompt_action) are handled in dedicated branches of_on_card_action_trigger()and never reach the changed paths.CallBackAction._typespatch only relaxes the type constraint(dict → dict | str), never tightens it.
_import()) are patched._is_card_action_duplicate) is unaffected bythe
message_idchange.✅ Verified
"value": "yes")