Skip to content

Feishu: include pending history media (images) in inbound context#54967

Closed
xuruiray wants to merge 2 commits intoopenclaw:mainfrom
xuruiray:feishu-history-media
Closed

Feishu: include pending history media (images) in inbound context#54967
xuruiray wants to merge 2 commits intoopenclaw:mainfrom
xuruiray:feishu-history-media

Conversation

@xuruiray
Copy link
Copy Markdown
Contributor

Problem

In Feishu group chats, users often post screenshots before mentioning the bot. Those screenshot messages are buffered as pending history (text placeholders like ![image] / <media:image>), but the bot does not include the actual media in the inbound agent context.

Changes

  • Extend FeishuMessageInfo to preserve rawContent returned by Feishu message APIs.
  • When a triggering message mentions the bot, best-effort resolve media for the most recent pending history entries that look like media messages (up to 3 messages / 10 media items), and prepend those media paths to the inbound media payload.

Notes

  • Best-effort only: failures are logged and ignored.
  • Keeps download cost bounded to avoid large latency spikes.

@openclaw-barnacle openclaw-barnacle Bot added channel: feishu Channel integration: feishu size: S labels Mar 26, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 26, 2026

Greptile Summary

This PR adds best-effort inclusion of media (e.g. screenshots posted before a bot mention) from pending group-history entries into the inbound agent context for Feishu group chats.\n\n- FeishuMessageInfo.rawContent (types.ts + send.ts): The raw body.content JSON string is now preserved alongside the already-parsed content field, enabling downstream callers to re-drive media extraction without an extra API round-trip.\n- History-media resolution block (bot.ts): When the bot is triggered in a group, the code scans the in-memory pending history for up to three most-recent entries matching /<media:|!\\[image\\]/, fetches each via getMessageFeishu to retrieve rawContent, resolves the actual media files via the existing resolveFeishuMediaList helper, and prepends the result to the triggering-message media list with path-based deduplication. Cost is bounded (3 messages / 10 media items) and every failure is caught, logged, and skipped.\n\nThe implementation is sound. The only observation is a defensive m?.path guard inside the deduplication filter that is unreachable given the FeishuMediaInfo[] type.

Confidence Score: 5/5

Safe to merge; the change is additive, best-effort, and falls back gracefully on any error.

All paths are bounded (3 candidates, 10 items max), every resolution failure is caught and logged, the deduplication is correct, and no existing behavior is altered when the feature isn't triggered. The only issue found is a trivially unnecessary optional-chain guard.

No files require special attention.

Important Files Changed

Filename Overview
extensions/feishu/src/bot.ts Adds best-effort resolution of media from pending group-history entries before bot dispatch. Logic is correctly gated, properly bounded (3 candidates / 10 items), and each resolution is wrapped in try/catch. One minor style nit: optional-chain guard on a non-nullable type.
extensions/feishu/src/send.ts Adds rawContent to the object returned by parseFeishuMessageItem. Simple, correct — the variable was already captured; this just exposes it on the returned struct.
extensions/feishu/src/types.ts Adds rawContent?: string to FeishuMessageInfo with clear JSDoc distinguishing it from the parsed content field. Clean and well-documented change.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/feishu/src/bot.ts
Line: 806-817

Comment:
**Unnecessary optional chaining on non-nullable type**

`m` is of type `FeishuMediaInfo` (non-nullable, drawn from `FeishuMediaInfo[]`), and `path` is a required `string` field on that type. The `m?.path` optional chain and the associated `if (!m?.path)` guard are therefore dead code — `m` can never be nullish here.

```suggestion
        mediaList = merged.filter((m) => {
          if (!m.path) {
            return false;
          }
          if (seen.has(m.path)) {
            return false;
          }
          seen.add(m.path);
          return true;
        });
```

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

Reviews (1): Last reviewed commit: "feishu: include pending history media (i..." | Re-trigger Greptile

Comment on lines +806 to +817
} catch (err) {
log(
`feishu[${account.accountId}]: failed to resolve pending history media for ${messageId}: ${String(err)}`,
);
}
}

if (historyMediaList.length > 0) {
// Prefer history media first so placeholders in history align with MediaPaths order.
const merged = [...historyMediaList, ...mediaList];
const seen = new Set<string>();
mediaList = merged.filter((m) => {
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.

P2 Unnecessary optional chaining on non-nullable type

m is of type FeishuMediaInfo (non-nullable, drawn from FeishuMediaInfo[]), and path is a required string field on that type. The m?.path optional chain and the associated if (!m?.path) guard are therefore dead code — m can never be nullish here.

Suggested change
} catch (err) {
log(
`feishu[${account.accountId}]: failed to resolve pending history media for ${messageId}: ${String(err)}`,
);
}
}
if (historyMediaList.length > 0) {
// Prefer history media first so placeholders in history align with MediaPaths order.
const merged = [...historyMediaList, ...mediaList];
const seen = new Set<string>();
mediaList = merged.filter((m) => {
mediaList = merged.filter((m) => {
if (!m.path) {
return false;
}
if (seen.has(m.path)) {
return false;
}
seen.add(m.path);
return true;
});
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/feishu/src/bot.ts
Line: 806-817

Comment:
**Unnecessary optional chaining on non-nullable type**

`m` is of type `FeishuMediaInfo` (non-nullable, drawn from `FeishuMediaInfo[]`), and `path` is a required `string` field on that type. The `m?.path` optional chain and the associated `if (!m?.path)` guard are therefore dead code — `m` can never be nullish here.

```suggestion
        mediaList = merged.filter((m) => {
          if (!m.path) {
            return false;
          }
          if (seen.has(m.path)) {
            return false;
          }
          seen.add(m.path);
          return true;
        });
```

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

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: 3f8ccc8d80

ℹ️ 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 thread extensions/feishu/src/bot.ts Outdated
Comment on lines +767 to +769
.filter((entry) =>
Boolean(entry.messageId) && /<media:|!\[image\]/.test(String(entry.body ?? "")),
)
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 Match pending image entries without placeholder text

When requireMention is enabled, pending history entries are recorded from ctx.content, and for Feishu image messages that content is the raw JSON body (for example with image_key), not <media:...> or ![image]. This filter therefore misses the common “image sent before mention” path, so the new history-media enrichment silently skips those screenshots and the inbound media context is still incomplete in the primary scenario this commit is trying to fix.

Useful? React with 👍 / 👎.

Comment on lines +802 to +804
historyMediaList.push(...resolved);
if (historyMediaList.length >= 10) {
break;
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 Apply media-item cap before bulk-resolving a message

The 10-item guard runs only after historyMediaList.push(...resolved), but resolveFeishuMediaList already downloads all media in that message (notably all embedded media in post content) before returning. In a group with a pending rich post containing many images, this can still download far more than 10 files and cause the latency spike this change explicitly tries to bound.

Useful? React with 👍 / 👎.

@xuruiray xuruiray force-pushed the feishu-history-media branch from b773278 to b2e9d51 Compare March 26, 2026 09:19
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: b2e9d512f2

ℹ️ 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 +768 to +770
(entry) =>
Boolean(entry.messageId) && /<media:|!\[image\]/.test(String(entry.body ?? "")),
)
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 Match pending media candidates by Feishu payload keys

This filter only selects pending entries whose rendered history text contains <media: or ![image], but pending group history is recorded from ctx.content, and for Feishu image/file/audio events that value is typically raw JSON (for example containing image_key/file_key). In mention-required groups, the common “media first, mention later” flow is therefore skipped, so the new history-media enrichment misses the exact pending screenshots/files it is intended to attach.

Useful? React with 👍 / 👎.

Comment on lines +803 to +805
historyMediaList.push(...resolved);
if (historyMediaList.length >= 10) {
break;
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 Enforce history media cap before resolving each message

The 10-item guard runs only after historyMediaList.push(...resolved), but resolveFeishuMediaList has already downloaded all media for that message (including every embedded asset in a rich post) before returning. In practice, one pending post with many attachments can still trigger far more than 10 downloads, so the intended latency/cost bound is not actually enforced.

Useful? React with 👍 / 👎.

@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented Apr 30, 2026

Codex review: needs changes before merge.

Summary
The PR preserves raw Feishu message content and prepends best-effort media resolved from recent pending Feishu group-history entries to the inbound agent media context.

Reproducibility: yes. Source inspection gives a high-confidence path: a mention-required Feishu group buffers a screenshot into pending history, then a bare bot mention arrives with no triggering-message media, and the proposed history-media block is reached too late.

Next step before merge
A narrow Feishu repair can address the concrete review defects on the PR branch or a replacement branch without a product decision.

Security
Cleared: No dependency, workflow, secret, package-resolution, or new code-execution change was found; the media-download bound issue is covered as a functional finding.

Review findings

  • [P2] Resolve history media before the empty-message skip — extensions/feishu/src/bot.ts:758-761
  • [P2] Enforce the media cap before resolving history posts — extensions/feishu/src/bot.ts:791-802
  • [P2] Recompute transcribed indexes after prepending history media — extensions/feishu/src/bot.ts:814-827
Review details

Best possible solution:

Land a Feishu-owned fix that preserves rawContent, resolves bounded pending-history media before skip and audio decisions, enforces caps before downloads, adds focused regressions and a changelog entry, and coordinates with #66432 only where a shared helper is useful.

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

Yes. Source inspection gives a high-confidence path: a mention-required Feishu group buffers a screenshot into pending history, then a bare bot mention arrives with no triggering-message media, and the proposed history-media block is reached too late.

Is this the best way to solve the issue?

No, not yet. The Feishu plugin is the right boundary and rawContent plumbing is useful, but the patch needs earlier media resolution, pre-download cap enforcement, and post-merge audio index handling.

Full review comments:

  • [P2] Resolve history media before the empty-message skip — extensions/feishu/src/bot.ts:758-761
    A bare bot mention after a screenshot can have empty content and no triggering-message media, so current main returns at the empty-message guard before this new history-media block can run. Move the pending-history media resolution before that guard, or include resolved history media in the skip decision.
    Confidence: 0.86
  • [P2] Enforce the media cap before resolving history posts — extensions/feishu/src/bot.ts:791-802
    The 10-item limit is checked only after resolveFeishuMediaList returns, but that helper downloads every embedded image/media in a rich post before returning. A single pending rich post can still fetch far more than 10 assets, defeating the promised latency bound.
    Confidence: 0.91
  • [P2] Recompute transcribed indexes after prepending history media — extensions/feishu/src/bot.ts:814-827
    Audio preflight computes MediaTranscribedIndexes from the triggering-message media order before this block prepends history media and rebuilds mediaPayload. If the triggering message is audio, the final MediaPaths order shifts and the transcribed index can point at the wrong item.
    Confidence: 0.82

Overall correctness: patch is incorrect
Overall confidence: 0.88

Acceptance criteria:

  • pnpm test extensions/feishu/src/bot.test.ts extensions/feishu/src/send.test.ts extensions/feishu/src/media.test.ts extensions/feishu/src/post.test.ts
  • pnpm check:changed

What I checked:

  • Pending group history is currently text-plus-message-id only: Current main records non-mentioned Feishu group messages into chatHistories with sender, body, timestamp, and messageId, but no resolved media paths. (extensions/feishu/src/bot.ts:618, 4556707cb7d8)
  • Empty-message guard can return before history media helps: Current main skips a triggering message when ctx.content is empty and the triggering-message mediaList is empty; a bare bot mention after a prior screenshot can hit this before a later history-media merge. (extensions/feishu/src/bot.ts:870, 4556707cb7d8)
  • Audio index is derived from the pre-merge media order: Current main builds mediaPayload and preflightAudioIndex from mediaList before final context construction, while the PR later prepends history media and rebuilds mediaPayload. (extensions/feishu/src/bot.ts:877, 4556707cb7d8)
  • Post media resolution downloads before a caller-side cap can stop it: resolveFeishuMediaList iterates embedded post image/media keys and downloads each before returning, so a 10-item cap checked after the helper call does not bound downloads for one rich post. (extensions/feishu/src/bot-content.ts:368, 4556707cb7d8)
  • PR diff adds the history-media block after inbound-history construction: The provided PR diff adds the pending-history media resolution block in extensions/feishu/src/bot.ts around lines 758-827, after the existing routing/context setup instead of before skip and audio-preflight decisions. (extensions/feishu/src/bot.ts:758, b2e9d512f2a8)
  • Related work overlaps but does not fully supersede this PR: Open PR fix(feishu): download images in Feishu thread history and inline attachment hints [AI-assisted] #66432 addresses Feishu thread-history media and rawContent plumbing; this PR is about pending group-history media before a bot mention, so the work should coordinate but not close as duplicate yet.

Likely related people:

  • steipete: Recent GitHub history shows repeated Feishu bot/send maintenance, topic-session work, audio preflight, and the shared pending-history helper used by this path. (role: recent maintainer and adjacent owner; confidence: high; commits: 521ea4ae5bcb, 29741f696a74, bb5e278f63bd; files: extensions/feishu/src/bot.ts, extensions/feishu/src/send.ts, src/auto-reply/reply/history.ts)
  • vincentkoc: Recent commits touched Feishu message parsing, mention handling, bot-content behavior, and security-sensitive Feishu channel paths near the media/context surface. (role: recent Feishu maintainer; confidence: medium; commits: c6cf37068cae, b642ebece9b0, 889bb8a78a87; files: extensions/feishu/src/send.ts, extensions/feishu/src/bot-content.ts, extensions/feishu/src/bot.ts)
  • doodlewind: The Feishu plugin was brought in through community plugin sync commits, making this a useful route for original Feishu plugin behavior context. (role: introduced Feishu plugin lineage; confidence: medium; commits: 2267d58afcc7, 5f6e1c19bd18, 5c2cb6c591e4; files: extensions/feishu/src/bot.ts, extensions/feishu/src/send.ts, extensions/feishu/src/types.ts)
  • hclsys: The empty-text skip that now interacts with this PR's placement was added in a recent Feishu fix, so they are relevant to the first review finding. (role: adjacent behavior author; confidence: medium; commits: 38aac708302a; files: extensions/feishu/src/bot.ts, extensions/feishu/src/bot.test.ts)

Remaining risk / open question:

Codex review notes: model gpt-5.5, reasoning high; reviewed against 4556707cb7d8.

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

Labels

channel: feishu Channel integration: feishu size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants