Skip to content

fix(feishu): render native presentation buttons#86017

Closed
NianJiuZst wants to merge 1 commit into
openclaw:mainfrom
NianJiuZst:codex/fix-feishu-native-buttons-85959
Closed

fix(feishu): render native presentation buttons#86017
NianJiuZst wants to merge 1 commit into
openclaw:mainfrom
NianJiuZst:codex/fix-feishu-native-buttons-85959

Conversation

@NianJiuZst

Copy link
Copy Markdown
Contributor

Summary

  • share the Feishu presentation-card block renderer between outbound payload rendering and message-tool card sends
  • render message-tool buttons blocks as native Feishu action elements while keeping select fallback text behavior
  • keep title-only fallbacks header-only across both direct channel sends and outbound renderPresentation, with regression coverage on both paths

Verification

  • node scripts/run-vitest.mjs extensions/feishu/src/channel.test.ts extensions/feishu/src/outbound.test.ts
  • pnpm build
  • node --input-type=module <<'PROOF'
    import { readdirSync } from 'node:fs';
    import path from 'node:path';
    import { pathToFileURL } from 'node:url';
    const file = readdirSync('dist').find((name) => name.startsWith('presentation-card-') && name.endsWith('.js'));
    const mod = await import(pathToFileURL(path.join(process.cwd(), 'dist', file)).href);
    const card = mod.t({
    presentation: {
    title: 'Approval',
    tone: 'success',
    blocks: [
    { type: 'text', text: 'Approve the request?' },
    {
    type: 'buttons',
    buttons: [
    { label: 'Approve', value: '/approve req_1 allow-once', style: 'success' },
    { label: 'Open runbook', url: 'https://example.com/runbook', style: 'primary' }
    ]
    }
    ]
    }
    });
    console.log(JSON.stringify(card, null, 2));
    PROOF

Real behavior proof

Behavior addressed: Feishu message-tool presentation buttons on channel sends were flattened into markdown fallback text instead of native interactive card actions.
Real environment tested: Local OpenClaw checkout after pnpm build, exercising the built Feishu presentation-card renderer from dist/ with a message-tool-style presentation payload.
Exact steps or command run after this patch:

node --input-type=module <<'EOF'
import { readdirSync } from 'node:fs';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
const file = readdirSync('dist').find((name) => name.startsWith('presentation-card-') && name.endsWith('.js'));
const mod = await import(pathToFileURL(path.join(process.cwd(), 'dist', file)).href);
const card = mod.t({
  presentation: {
    title: 'Approval',
    tone: 'success',
    blocks: [
      { type: 'text', text: 'Approve the request?' },
      {
        type: 'buttons',
        buttons: [
          { label: 'Approve', value: '/approve req_1 allow-once', style: 'success' },
          { label: 'Open runbook', url: 'https://example.com/runbook', style: 'primary' },
        ],
      },
    ],
  },
});
console.log(JSON.stringify(card, null, 2));
EOF

Evidence after fix:

{
  "schema": "2.0",
  "config": {
    "width_mode": "fill"
  },
  "header": {
    "title": {
      "tag": "plain_text",
      "content": "Approval"
    },
    "template": "green"
  },
  "body": {
    "elements": [
      {
        "tag": "markdown",
        "content": "Approve the request?"
      },
      {
        "tag": "action",
        "actions": [
          {
            "tag": "button",
            "text": {
              "tag": "plain_text",
              "content": "Approve"
            },
            "type": "primary",
            "value": {
              "oc": "ocf1",
              "k": "quick",
              "a": "feishu.payload.button",
              "q": "/approve req_1 allow-once"
            }
          },
          {
            "tag": "button",
            "text": {
              "tag": "plain_text",
              "content": "Open runbook"
            },
            "type": "primary",
            "url": "https://example.com/runbook"
          }
        ]
      }
    ]
  }
}

Observed result after fix: The rendered Feishu card now contains a native action row with interactive buttons, and button values are emitted as structured ocf1 envelopes instead of being flattened into markdown bullet text.
What was not tested: Live Feishu delivery and callback handling against a real tenant/app.

Fixes #85959

@openclaw-barnacle openclaw-barnacle Bot added channel: feishu Channel integration: feishu size: M proof: supplied External PR includes structured after-fix real behavior proof. labels May 24, 2026
@clawsweeper

clawsweeper Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge.

Latest ClawSweeper review: 2026-05-24 10:57 UTC / May 24, 2026, 6:57 AM ET.

Workflow note: Future ClawSweeper reviews update this same comment in place.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

PR Surface
Source +67, Tests +95. Total +162 across 5 files.

View PR surface stats
Area Files Added Removed Net
Source 3 232 165 +67
Tests 2 97 2 +95
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 5 329 167 +162

Summary
This PR extracts a shared Feishu presentation-card renderer, uses it for message-tool sends and outbound rendering, and adds Feishu channel/outbound tests for native button actions and title-only fallbacks.

Reproducibility: yes. by source inspection: current main’s Feishu message-tool path calls a fallback-only card builder, and the existing channel test expected buttons to become markdown - Run help text. I did not run local tests because this review is read-only.

PR rating
Overall: 🐚 platinum hermit
Proof: 🐚 platinum hermit
Patch quality: 🐚 platinum hermit
Summary: The patch is focused and well-covered for the renderer path, with sufficient local real-output proof but no live Feishu tenant proof.

Rank-up moves:

  • Optionally add a redacted live Feishu delivery/callback transcript if maintainers want transport-level proof before landing.
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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.

Real behavior proof
Sufficient (live_output): The PR body includes after-fix copied output from the built Feishu presentation-card renderer showing native action buttons and structured ocf1 values; live Feishu delivery was explicitly not tested.

Risk before merge

  • The PR body includes built-renderer output but not a live Feishu tenant delivery/callback proof, so maintainers may still want one before landing the visible channel behavior.

Maintainer options:

  1. Decide the mitigation before merge
    Land the shared Feishu presentation-card renderer if CI stays green, with optional live Feishu proof for extra confidence on delivered card rendering and callbacks.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge
No automated repair is needed because the PR already contains a focused implementation and I found no discrete actionable patch defect.

Security
Cleared: The diff stays within Feishu plugin rendering/tests, adds no dependencies or workflow changes, and preserves URL filtering plus structured interaction envelopes.

Review details

Best possible solution:

Land the shared Feishu presentation-card renderer if CI stays green, with optional live Feishu proof for extra confidence on delivered card rendering and callbacks.

Do we have a high-confidence way to reproduce the issue?

Yes, by source inspection: current main’s Feishu message-tool path calls a fallback-only card builder, and the existing channel test expected buttons to become markdown - Run help text. I did not run local tests because this review is read-only.

Is this the best way to solve the issue?

Yes, the proposed shape is the narrow maintainable fix: it shares the existing native Feishu presentation block mapping instead of keeping separate outbound and message-tool renderers. The only remaining proof gap is live Feishu delivery, not a code defect I can point to in the diff.

Label changes:

  • add P2: This fixes a user-visible Feishu channel bug with limited blast radius and focused plugin-local changes.
  • add proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix copied output from the built Feishu presentation-card renderer showing native action buttons and structured ocf1 values; live Feishu delivery was explicitly not tested.
  • add rating: 🐚 platinum hermit: Current PR rating is 🐚 platinum hermit because proof is 🐚 platinum hermit, patch quality is 🐚 platinum hermit, and The patch is focused and well-covered for the renderer path, with sufficient local real-output proof but no live Feishu tenant proof.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body includes after-fix copied output from the built Feishu presentation-card renderer showing native action buttons and structured ocf1 values; live Feishu delivery was explicitly not tested.

Label justifications:

  • P2: This fixes a user-visible Feishu channel bug with limited blast radius and focused plugin-local changes.
  • rating: 🐚 platinum hermit: Current PR rating is 🐚 platinum hermit because proof is 🐚 platinum hermit, patch quality is 🐚 platinum hermit, and The patch is focused and well-covered for the renderer path, with sufficient local real-output proof but no live Feishu tenant proof.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body includes after-fix copied output from the built Feishu presentation-card renderer showing native action buttons and structured ocf1 values; live Feishu delivery was explicitly not tested.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix copied output from the built Feishu presentation-card renderer showing native action buttons and structured ocf1 values; live Feishu delivery was explicitly not tested.

What I checked:

  • Current main fallback-only message-tool renderer: buildFeishuPresentationCard currently builds a single markdown body element from renderMessagePresentationFallbackText, so message-tool buttons blocks are degraded to label text instead of native Feishu action rows. (extensions/feishu/src/channel.ts:186, 62b51a6295ee)
  • Current main call site uses the fallback-only builder: The Feishu message action handler normalizes ctx.params.presentation and calls buildFeishuPresentationCard for send and thread-reply, which is the path reported by the linked issue. (extensions/feishu/src/channel.ts:805, 62b51a6295ee)
  • Adjacent native implementation exists on main: The outbound renderer already maps presentation buttons blocks to Feishu { tag: "action", actions: [...] } elements with URL filtering and structured ocf1 button values. (extensions/feishu/src/outbound.ts:224, 62b51a6295ee)
  • PR shared renderer maps buttons natively: The PR head adds buildFeishuCardElementForBlock, which renders buttons blocks as Feishu action elements and creates structured feishu.payload.button envelopes for value buttons. (extensions/feishu/src/presentation-card.ts:91, 71fb0931f84a)
  • PR wires the shared renderer into both paths: The PR head imports buildFeishuPresentationCard in channel.ts and uses buildFeishuPresentationCardElements in outbound card construction, removing the duplicate fallback-only message-tool path. (extensions/feishu/src/channel.ts:70, 71fb0931f84a)
  • Regression coverage matches the bug: The PR updates the channel test to expect a native Feishu action row for message-tool presentation buttons and adds title-only fallback coverage in channel and outbound tests. (extensions/feishu/src/channel.test.ts:398, 71fb0931f84a)

Likely related people:

  • m1heng: extensions/feishu/package.json describes the Feishu/Lark plugin as community maintained by this handle, making them the clearest Feishu-specific routing candidate. (role: listed plugin owner; confidence: medium; commits: 2267d58afcc7; files: extensions/feishu/package.json)
  • steipete: Recent presentation capability work touched Feishu channel/outbound paths and the message-presentation docs that define this behavior. (role: recent presentation surface contributor; confidence: high; commits: ad861d4c9df8; files: extensions/feishu/src/channel.ts, extensions/feishu/src/outbound.ts, docs/plugins/message-presentation.md)
  • vincentkoc: The native Feishu interactive card payload send implementation is adjacent to the renderer this PR reuses. (role: native Feishu card contributor; confidence: medium; commits: 2161b46032e0; files: extensions/feishu/src/channel.ts, extensions/feishu/src/outbound.ts, extensions/feishu/src/outbound.test.ts)
  • NVIDIAN: Recent Feishu message-tool reply work touched channel.ts and channel.test.ts, the same message-action surface changed here. (role: recent Feishu message-tool contributor; confidence: medium; commits: aecd4fba7e8d; files: extensions/feishu/src/channel.ts, extensions/feishu/src/channel.test.ts)

Codex review notes: model gpt-5.5, reasoning high; reviewed against 62b51a6295ee.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P2 Normal backlog priority with limited blast radius. labels May 24, 2026
@clawsweeper

clawsweeper Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper PR egg

✨ Hatched: 💎 rare Moonlit Test Hopper

Hatched PR egg: 💎 rare Moonlit Test Hopper

Hatch command

Comment @clawsweeper hatch when this PR is hatchable.

Hatchability rules:

  • Merged PRs are hatchable.
  • Open PRs are hatchable when they are status: 👀 ready for maintainer look, status: 🚀 automerge armed, or labeled clawsweeper:automerge.
  • Closed unmerged PRs are hatchable only when one of those hatchable labels is still present in the durable record.

Rarity: 💎 rare.
Trait: watches the merge queue.
Image traits: location artifact grotto; accessory little merge flag; palette seafoam, black, and opal; mood curious; pose guarding a tiny green check; shell starlit enamel shell; lighting clean product lighting; background tiny shells and proof notes.
Share on X: post this hatch
Copy: My PR egg hatched a 💎 rare Moonlit Test Hopper in ClawSweeper.

What is this egg doing here?
  • Eggs appear after the PR passes real-behavior proof. It is here for vibes, not verdicts: it does not change labels, ratings, merge decisions, or automation.
  • The shell reacts to review momentum: open follow-up work warms it up, re-review makes it wobble, and a clean final review lets it hatch.
  • Hatchability usually comes from sufficient real-behavior proof, no blocking P0/P1/P2 findings, no security attention needed, and clean correctness. A merged PR is already final, so merge makes the egg hatchable independently.
  • The hatch is seeded from this repository and PR number, so the same PR keeps the same creature; the reviewed head SHA can only change safe visual details.
  • Rarity is just collectible sparkle: 🥚 common, 🌱 uncommon, 💎 rare, ✨ glimmer, and 🌈 legendary.

@NianJiuZst

Copy link
Copy Markdown
Contributor Author

@clawsweeper hatch

@clawsweeper

clawsweeper Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper PR egg hatch requested.

I queued a comment sync for this PR. If the egg is hatchable, ClawSweeper will generate the image once and update the existing review comment.
Action: PR egg hatch queued (workflow sweep.yml, event repository_dispatch).
The ASCII egg stays as the fallback.

@Takhoffman

Copy link
Copy Markdown
Contributor

@clawsweeper automerge

@clawsweeper

clawsweeper Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

🦞🔧
ClawSweeper automerge is enabled.

Draft PRs stay fix-only until GitHub marks them ready for review. Pause with /clawsweeper stop.

Automerge progress:

  • 2026-05-25 17:21:54 UTC review queued 71fb0931f84a (queued)

@clawsweeper clawsweeper Bot added the clawsweeper:automerge Maintainer opted this PR into bounded ClawSweeper-reviewed automerge label May 25, 2026
@clawsweeper

clawsweeper Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper 🐠 reef update

Thanks for the work on this. ClawSweeper could not push to this branch with the permissions available, so it opened a narrow replacement PR to keep the fix swimming forward without losing the contributor trail. not your fault, just GitHub branch-permission tides.

Why replacement: ClawSweeper could not update the source PR branch directly; GitHub did not grant sufficient push rights to the bot for that branch.
Replacement PR: #86588
Why close: this run explicitly closes the superseded source PR after the credited replacement PR is open, so review continues in one place.
This closeout is intentional for this run: the replacement PR is now the active review lane.
Attribution is preserved in the replacement PR body and release-note trail.
Co-author credit kept:

fish notes: model gpt-5.5, reasoning high; reviewed against 261d5a8.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: feishu Channel integration: feishu clawsweeper:automerge Maintainer opted this PR into bounded ClawSweeper-reviewed automerge P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: M status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feishu] Presentation buttons block not rendered as native Feishu interactive card buttons

2 participants