fix(feishu): preserve button command values in fallback text and add Feishu comment guidance with callback privacy#94385
Conversation
|
Codex review: needs maintainer review before merge. Reviewed July 2, 2026, 3:36 PM ET / 19:36 UTC. Summary PR surface: Source +61, Tests +148, Docs +21. Total +230 across 6 files. Reproducibility: yes. Current main source and tests show command-valued Feishu comment buttons degrade to label-only fallback text; I did not run a live current-main Feishu repro in this read-only review. Review metrics: 1 noteworthy metric.
Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Mantis proof suggestion Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land the narrow fallback repair after maintainer acceptance of shared typed-command fallback visibility and a clear decision on whether the linked Feishu approval UX issue should close or keep a follow-up. Do we have a high-confidence way to reproduce the issue? Yes. Current main source and tests show command-valued Feishu comment buttons degrade to label-only fallback text; I did not run a live current-main Feishu repro in this read-only review. Is this the best way to solve the issue? Yes, with maintainer acceptance. The shared fallback renderer is the maintainable layer for typed command fallback, while Feishu-only guidance and marker handling stay inside the Feishu plugin instead of expanding the SDK surface. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against a004e18b4bbd. Label changesLabel justifications:
Evidence reviewedPR surface: Source +61, Tests +148, Docs +21. Total +230 across 6 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
Mantis Telegram Desktop ProofSummary: Mantis captured native Telegram Desktop before/after GIFs showing the command button fallback text.
Motion-trimmed clips: |
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
This could be resolved by updating ui. I'll prepare a PR if that sounds good. |
|
@xuwei-xy thanks for the suggestion. A UI-only fix could work for the Feishu guidance part, but the shared |
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
4bcf0a5 to
0628827
Compare
|
Addressed both P2 findings from the latest ClawSweeper review:
Changes: +33 / -14 across 3 files (
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
… degraded approval UX
…arrow Feishu interactive detection - P1: Skip rendering action.type === "callback" values in renderMessagePresentationFallbackText to avoid leaking opaque channel/plugin data into user-visible text. Command and legacy values are still rendered. - P2: Replace hasMessagePresentationBlocks/hasInteractiveReplyBlocks with isMessagePresentationInteractiveBlock so Feishu comment guidance only appears when the presentation actually contains buttons or selects, not for text-only blocks. - Update tests: callback button now shows label-only; all 137 tests pass.
…keep legacy value private
…ering and guidance
…buttons from command fallback
…ing rendered-command marker
…u plugin as local helper Keep the helper local to the only caller (Feishu outbound) instead of adding a new public plugin SDK API contract. The shared fallback renderer in renderMessagePresentationFallbackText already inlines the same command-visibility logic; a local helper is sufficient for the Feishu comment-thread guidance gate.
|
Maintainer pass complete; land-ready.
No known proof gaps. |
|
Merged via squash.
|




What Problem This Solves
When Feishu chat or document comments strip interactive blocks (buttons, cards), the fallback text rendered by
renderMessagePresentationFallbackTextonly preserved URL-type button targets. Command buttons (with.valueor.action.command/.action.type === "callback") were silently dropped — users saw a bare button label like- Approvewith no way to act on it. On Feishu comment threads, no guidance was offered to explain that interactive buttons are unsupported and cannot be used.The fix also originally exposed legacy opaque
valuefields as visible text, which crossed the callback privacy boundary. A follow-up review fix tightened this to only render explicit typedaction.type === "command"values.src/interactive/payload.ts:renderMessagePresentationFallbackTextnow callsresolveMessagePresentationControlValuefor each button to extract its command value when no URL is present andaction.type === "command". These values are rendered as inline code (e.g.,- Deny: \/approve req_1 deny`) so the user can copy and type them manually. Legacy opaquevaluefields andaction.type === "callback"` values remain private — only the button label is shown.extensions/feishu/src/outbound.ts: When interactive content with command-type actions is detected in a Feishu comment thread, appends a user-friendly guidance line:> Interactive buttons are unavailable in Feishu document comments. You can type the command shown above manually.The guidance is gated on a localhasRenderedCommandActionhelper (not exposed through public SDK) that checks for actualaction.type === "command"buttons — excluding buttons where URL/webApp overrides the command text in fallback rendering, and disabled buttons.payload.test.tsandoutbound.test.tsto assert the new fallback format.renderMessagePresentationFallbackTextdocumenting the typed command fallback semantics for SDK consumers.docs/plugins/message-presentation.mdwith a "Button value fallback visibility" section documenting the command/callback/legacy value privacy contract for plugin authors.Review Fixes
e8da4b3b39):ctx.payload.channelData?.feishu?.hasRenderedCommandActionfailedcheck-prod-types,check-test-types,check-additional-extension-package-boundary, andQA Smoke CIwith TS2339 becausechannelDatais typed as{}. Fixed by narrowing withisRecord()before reading the marker — matching the same pattern used inrenderFeishuPresentationPayloadat line 337. 1 file +6/-1.action.type === "callback"values are no longer rendered in fallback text. These are opaque channel/plugin data, not manual commands. Additionally, legacy.valuefields (without a typedaction) are also kept private — only explicitaction.type === "command"values are rendered as copyable text. This addresses themerge-risk: 🚨 security-boundaryconcern flagged by ClawSweeper.action.type === "command"buttons, instead of the broaderisMessagePresentationInteractiveBlockwhich matched any buttons/selects blocks. This ensures the "You can type the command shown above manually" text only appears when a copyable command is actually rendered in the fallback text — avoiding misleading guidance on label-only fallbacks (callback, legacy value, or URL-only buttons).url/webApp/web_app, the fallback renderer shows the URL instead of the command value. The guidance now excludes these buttons from the command-present check, so the "You can type the command shown above manually" instruction is only shown when a command is actually visible as copyable text in the fallback output. (4bcf0a59a6)docs/plugins/message-presentation.mddocumenting which button value types are rendered as copyable text (action.type === "command") vs. kept private (callback, legacy value, select option values). Channel adapters that add manual-command guidance are advised to derive the command-present check from the same source the fallback renderer uses.0e0fea4d3f):renderFeishuPresentationPayloadnow sets ahasRenderedCommandActionmarker inchannelData.feishuwhen a command button would be rendered in fallback text. ThesendPayloadcomment-target branch falls back to this marker when corerenderPresentationForDeliveryhas already stripped thepresentationfield before callingsendPayload. This ensures the Feishu comment guidance still appears for normal core delivery (not just directsendPayloadcalls). Regression test added covering the stripped-presentation + channelData-marker path (outbound.test.ts).4f6fc55b41): Per ClawSweeper review, movedhasRenderedCommandActionout of the public plugin SDK (src/plugin-sdk/interactive-runtime.ts→ -1 export) and intoextensions/feishu/src/outbound.tsas a local helper (~25 lines). Reverted the SDK surface budget bump (scripts/plugin-sdk-surface-report.mjs: 10401→10400, 5220→5219). Removed the now-dead exported function fromsrc/interactive/payload.ts(-29 lines). The shared fallback renderer inrenderMessagePresentationFallbackTextalready inlines the same command-visibility logic; a local helper is sufficient for the single Feishu call site. No public API contract burden.Verification
pnpm build— build clean (590s, all 116 plugins compiled, plugin SDK boundary pass)pnpm test extensions/feishu/src/outbound.test.ts src/interactive/payload.test.ts— 46 + 17 = 63 tests passedpnpm tsgo:prod— production types cleanpnpm check:test-types— test types cleanpnpm test:extensions:package-boundary:compile— 116/116 plugins compilednode_modules/.bin/oxlint --quiet— no new lint errorspnpm format:check— format clean on all changed filesnode scripts/plugin-sdk-surface-report.mjs— SDK budget green (559 exports ≤ 10400 budget)Evidence
Live Feishu E2E verification (2026-06-30)
Verified against a real Feishu production environment (appId:
cli_aac89d4a6cba9ce1) with 7 button-type scenarios sent to a document comment thread via the actualreplyCommentAPI fromextensions/feishu/src/drive.ts. Each message went through the PR'srenderMessagePresentationFallbackTextandhasRenderedCommandActiongate before delivery.批准/拒绝)批准: \/approve req_1 allow-once`` visible + guidance line shown忽略)- 忽略label-only, callback valueignore_123_secretNOT leaked + no guidance批准(老版本))- 批准(老版本)label-only, legacy value NOT leaked + no guidance- 查看文档: https://example.com/docsURL displayed correctlyProof command:
Proof output (all 7 sent with reply_ids):
Screenshots:


