Skip to content

feat(feishu): display group name instead of chat_id in session labels#51032

Merged
BunsDev merged 7 commits intoopenclaw:mainfrom
jnuyao:feat/feishu-group-name-final
Apr 27, 2026
Merged

feat(feishu): display group name instead of chat_id in session labels#51032
BunsDev merged 7 commits intoopenclaw:mainfrom
jnuyao:feat/feishu-group-name-final

Conversation

@jnuyao
Copy link
Copy Markdown
Contributor

@jnuyao jnuyao commented Mar 20, 2026

Summary

Resolves #35675Auto-populate session label with chat/group name from messaging providers.

When a Feishu group chat sends a message, the Chat page session dropdown currently shows the raw chat_id (e.g. oc_***). This PR resolves human-readable group names via the existing getChatInfo API and displays them instead.

Before → After

Field Before After
GroupSubject oc_*** (raw ID) "Engineering Team" (human-readable, falls back to ID)
ConversationLabel (not set) "Engineering Team" (set only when name is available)

image

After fixed:
image

Design

Architecture

Message arrives → group gating checks → resolveGroupName() → buildCtxPayloadForAgent
                                              │
                                     ┌────────┴────────┐
                                     │  Cache hit?      │
                                     │  (TTL 30 min)    │
                                     ├────Yes───────────┤→ return cached name
                                     │  No / Expired    │
                                     └────────┬─────────┘
                                              │
                                     getChatInfo(client, chatId)
                                              │
                                     ┌────────┴────────┐
                                     │  Success?        │
                                     │  name.trim()     │
                                     ├────Yes───────────┤→ cache name, return it
                                     │  No / Empty      │
                                     └────────┬─────────┘
                                              │
                                     cache "" sentinel → return undefined
                                     (GroupSubject falls back to chat_id)

Cache Design — Key Decisions

Decision Rationale
Account-scoped key (accountId:chatId) Multi-account deployments must not cross-pollute cache entries. Two accounts can have different permissions on the same chat.
Negative caching (empty-string sentinel) Prevents API hammering when a chat has no name or the call fails. Without this, every incoming message in a failed chat would trigger a new API call.
TTL = 30 minutes Balances freshness (group names rarely change) vs API load. Consistent with the existing senderNameCache pattern in bot-sender-name.ts.
Hard cap = 500 entries Bounds memory to ~50 KB worst case (500 × ~100 bytes per entry). Sufficient for most deployments.

Eviction Strategy — Two-Phase with Insertion-Order Awareness

The cache uses JavaScript Map, which iterates in insertion order. This creates a subtle correctness concern addressed below.

Phase 1 — Expire stale entries:

for (const [key, val] of cache) {
  if (val.expiresAt <= now) cache.delete(key);
}

Phase 2 — Evict oldest if still over cap:

const excess = cache.size - MAX_SIZE;
for (const key of cache.keys()) {
  if (removed >= excess) break;
  cache.delete(key);  // oldest by insertion order
  removed++;
}

Insertion-order correctness (setCacheEntry helper):

Map.set() on an existing key updates the value but preserves its original insertion position. If a stale entry is refreshed, it remains at the "oldest" position and Phase 2 would immediately evict it — dropping the just-written result.

Fix: setCacheEntry() does delete(key) then set(key, value), which moves the entry to the end of iteration order. The refreshed entry is now treated as newest and survives eviction.

Result-before-eviction guarantee:

As an additional safety net, resolveGroupName() reads the cache result into a local variable before calling evictGroupNameCache(). Even in edge cases, the return value is never lost.

Call-Site Placement

resolveGroupName() is called after all group gating checks (permission errors, cooldowns, dedup) but before buildCtxPayloadForAgent(). This ensures:

  • Dropped/rejected messages never trigger a wasted API call
  • The resolved name is available when building the context payload

Session Label Priority

OpenClaw resolves session labels with this priority: ConversationLabel > ThreadLabel > SenderName (DM) > GroupSubject (group).

This PR sets both:

  • GroupSubject: groupName || ctx.chatId (always has a value for groups)
  • ConversationLabel: groupName only when available (highest priority, shows the friendly name)

Files Changed

File Change
extensions/feishu/src/bot.ts Import getChatInfo; add cache + resolveGroupName(); call before buildCtxPayloadForAgent; update GroupSubject / ConversationLabel
extensions/feishu/src/bot-group-name.test.ts New — 7 unit tests

Test Coverage

# Test Case What It Validates
1 Successful API call Returns trimmed name, calls API once
2 API failure + logging Returns undefined, logs error with account ID
3 Whitespace-only name Treated as empty → undefined
4 Positive cache hit Second call served from cache, no extra API call
5 Negative cache hit Failed lookup cached, retry skipped within TTL
6 Missing name field ({ name: undefined }) Realistic mock — getChatInfo returns object but name is undefined
7 Cross-account isolation Same chatId under different accounts → separate cache entries and API calls

Run tests:

pnpm test:extension feishu

Checklist

AI Contribution Disclosure: This PR was developed with AI assistance. All code has been reviewed and tested by the author.

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

greptile-apps Bot commented Mar 20, 2026

Greptile Summary

This PR resolves human-readable group chat names from the Feishu getChatInfo API and uses them to populate GroupSubject and ConversationLabel in the agent context payload, replacing the raw chat_id (e.g. oc_***) in session labels. The implementation is well-structured — multi-account cache keying, negative caching, and insertion-order-aware two-phase eviction are all solid — with good test coverage across 7 unit tests.

Key findings:

  • TTL eviction gap: evictGroupNameCache() (which runs Phase 1 — removing expired entries) is only invoked when groupNameCache.size > GROUP_NAME_CACHE_MAX_SIZE. In typical deployments with far fewer than 500 group chats, expired entries will never be garbage-collected from the Map, even though they are correctly skipped on read. This is a memory leak for long-running processes.
  • No account.configured guard: bot-sender-name.ts (the explicit reference pattern) short-circuits with if (!account.configured) { return {}; } before making any API call. resolveGroupName omits this check, which could cause unnecessary API calls (and a 30-minute negative-cache penalty) for unconfigured accounts.
  • Minor style issues: A missing blank line before export type FeishuMessageEvent and a double blank line at the call site.

Confidence Score: 3/5

  • Safe to merge with the caveat that stale cache entries will accumulate in memory over time in long-running deployments with fewer than 500 group chats.
  • The core feature works correctly — group names are resolved, cached, and fall back gracefully to the raw chat ID on any failure. However, the TTL-based cleanup path is effectively dead code in most deployments (only runs when the cache hits 500 entries), which is a genuine memory leak for long-running servers. The missing account.configured guard is a minor divergence from the established pattern but won't cause visible breakage. These issues prevent a higher confidence score.
  • extensions/feishu/src/bot.ts — specifically the eviction trigger condition at line 162 and the missing configured guard at line 120.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/feishu/src/bot.ts
Line: 162-164

Comment:
**TTL-based eviction only runs when cache is at capacity**

`evictGroupNameCache()` (which includes Phase 1 — removing expired entries) is only called when `groupNameCache.size > GROUP_NAME_CACHE_MAX_SIZE`. This means stale entries that have exceeded their 30-minute TTL will remain in memory indefinitely as long as the cache never reaches 500 entries. In a typical single-account deployment with far fewer than 500 group chats, expired entries will never be purged.

This is the opposite of the intended TTL behaviour: the cache will always serve entries within their 30-minute window (since `resolveGroupName` checks `expiresAt` on read), but the underlying `Map` will silently accumulate stale objects forever.

Consider calling `evictGroupNameCache()` unconditionally (or periodically), and only making the Phase 2 eviction conditional on the size check:

```ts
// Always run Phase 1 (TTL cleanup); only gate Phase 2 on size
evictGroupNameCache();
```

Or, alternatively, call Phase 1 unconditionally at the top of `resolveGroupName` and only trigger the full eviction when the cap is exceeded.

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/feishu/src/bot.ts
Line: 167-168

Comment:
**Missing blank line before `export type FeishuMessageEvent`**

There is no blank line separating the end of `resolveGroupName` from the `FeishuMessageEvent` type declaration, making the module-level code hard to scan visually.

```suggestion
  return result;
}

export type FeishuMessageEvent = {
```

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/feishu/src/bot.ts
Line: 120-126

Comment:
**No `account.configured` guard before making the API call**

`bot-sender-name.ts` (the pattern this PR is explicitly modelled after) guards its API call with:

```ts
if (!account.configured) {
  return {};
}
```

`resolveGroupName` makes no such check. If an unconfigured account somehow reaches this code path (e.g., account added but keys not yet set), `createFeishuClient` may produce a client that issues requests with invalid credentials, causing a noisy `getChatInfo` failure that gets negative-cached for 30 minutes. Adding the guard here keeps the behaviour consistent and avoids an unnecessary API round-trip.

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/feishu/src/bot.ts
Line: 1002-1005

Comment:
**Double blank line is a minor style nit**

There are two consecutive blank lines between the `resolveGroupName` call block and the `buildCtxPayloadForAgent` comment. A single blank line is the convention in the rest of the file.

```suggestion
    }

    // --- Shared context builder for dispatch ---
```

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

Last reviewed commit: "feat(feishu): displa..."

Comment thread extensions/feishu/src/bot.ts Outdated
Comment thread extensions/feishu/src/bot.ts Outdated
Comment thread extensions/feishu/src/bot.ts
Comment thread extensions/feishu/src/bot.ts Outdated
@jnuyao jnuyao force-pushed the feat/feishu-group-name-final branch from ff6206f to bccb2c9 Compare March 20, 2026 12:28
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: bccb2c9238

ℹ️ 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-group-name.test.ts Outdated
Comment thread extensions/feishu/src/bot.ts Outdated
@jnuyao jnuyao force-pushed the feat/feishu-group-name-final branch from bccb2c9 to c0983cd Compare March 20, 2026 12:36
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: c0983cdc93

ℹ️ 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
Resolve human-readable Feishu group names via getChatInfo and display
them in the Chat page session dropdown, replacing raw chat_id values.

Changes:
- Import getChatInfo from chat.ts
- Add resolveGroupName() with account-scoped TTL cache (30 min)
- Guard with account.configured check (consistent with bot-sender-name.ts)
- Two-phase eviction enforcing a hard 500-entry cap
- Unconditional Phase 1 (TTL cleanup) on every resolve call
- setCacheEntry() with delete-before-set for correct Map insertion order
- Negative caching (empty-string sentinel) to avoid API hammering
- Set GroupSubject (with fallback to chat_id) in buildCtxPayloadForAgent
- Set ConversationLabel only for non-topic group sessions to preserve
  ThreadLabel disambiguation in group_topic / group_topic_sender flows
- 7 unit tests covering success, failure, cache, and isolation paths

Closes openclaw#35675
@jnuyao jnuyao force-pushed the feat/feishu-group-name-final branch from c0983cd to 9ef7c5e Compare March 20, 2026 12:49
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: 9ef7c5e6f2

ℹ️ 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
@jnuyao
Copy link
Copy Markdown
Contributor Author

jnuyao commented Mar 20, 2026

Hi @BunsDev — this PR includes a detailed design write-up covering cache strategy, eviction correctness, and multi-account isolation. I've also verified the end-to-end behavior on a live Feishu deployment. Would love your review when you free. Thanks!

CI note: The 1 remaining failed checks are pre-existing and unrelated to this PR.

@BunsDev BunsDev self-assigned this Apr 27, 2026
Copy link
Copy Markdown
Member

@BunsDev BunsDev left a comment

Choose a reason for hiding this comment

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

Approved after maintainer follow-up.

Bug/behavior: Feishu group sessions were showing raw chat IDs in the chat/session label surface instead of a human-readable group name for PR #51032 / issue #35675.

What changed: the PR now resolves Feishu group names through getChatInfo, keeps the lookup account-scoped and cached, preserves topic/thread label priority, and defers the group-name lookup until after the broadcast dedup claim so duplicate account deliveries do not waste Feishu API calls.

Evidence: local pnpm test extensions/feishu/src/bot-group-name.test.ts extensions/feishu/src/bot.broadcast.test.ts passed, local pnpm check:changed passed, all review threads are resolved, and GitHub CI is green on c154dc0a41 with mergeStateStatus: CLEAN.

@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented Apr 27, 2026

Codex automated review: keeping this open.

Current main still uses the raw Feishu chat_id as the group session subject and does not resolve inbound Feishu group names for session labels. This PR remains a plausible focused implementation candidate, but it overlaps with open PR #38958 and still needs maintainer review/cleanup before merge.

Best possible solution:

Keep this PR open for maintainer review. The best path is to compare it with #38958, pick the canonical Feishu display-name implementation, remove unrelated root guidance changes, preserve thread-label priority, place group-name lookup after broadcast dedup, and land the focused Feishu code/tests if that remains the chosen approach.

What I checked:

  • Current Feishu inbound context still labels groups by ID: buildCtxPayloadForAgent sets GroupSubject to ctx.chatId for group messages and does not supply ConversationLabel, so current main still produces raw Feishu group IDs as the derived label input. (extensions/feishu/src/bot.ts:1065, d3fd275aa5fc)
  • Session label pipeline would consume a friendly group name if provided: Label resolution prefers ConversationLabel, then ThreadLabel, then group metadata including GroupSubject; session metadata persists that resolved label into origin.label. The missing piece is Feishu populating a friendly group name before finalizing inbound context. (src/channels/conversation-label.ts:27, d3fd275aa5fc)
  • Existing Feishu API wrapper can retrieve chat names: getChatInfo already calls client.im.chat.get and returns name, which supports the PR's proposed implementation path without a new core API. (extensions/feishu/src/chat.ts:16, d3fd275aa5fc)
  • Current main has no group-name resolver/test surface from this PR: A targeted search found no resolveGroupName, clearGroupNameCache, or bot-group-name test on current main; only the generic conversation-label code and unrelated Feishu comment-handler ConversationLabel usage matched. (d3fd275aa5fc)
  • Related PR context shows overlapping but not landed work: The provided GitHub context shows this PR resolves closed feature request Feature: Auto-populate session label with chat/group name from messaging providers #35675, while open PR feat(feishu): configurable group and DM display-name resolution #38958 separately tracks broader Feishu group/DM display-name resolution. Since neither PR is merged into current main, this PR should not be closed as implemented.

Remaining risk / open question:

  • The PR overlaps with open PR feat(feishu): configurable group and DM display-name resolution #38958, so maintainers should choose one canonical Feishu display-name path before merging.
  • The provided PR file list includes an unrelated root AGENTS.md deletion outside the Feishu feature surface.
  • Unresolved review feedback says the group-name lookup should move behind the broadcast dedup claim to avoid unnecessary getChatInfo calls in multi-account broadcast groups.

Codex Review notes: model gpt-5.5, reasoning high; reviewed against d3fd275aa5fc.

@BunsDev BunsDev merged commit 2a08848 into openclaw:main Apr 27, 2026
65 checks passed
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: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Auto-populate session label with chat/group name from messaging providers

2 participants