From 8a2273e210a60d106eef2eedee60848c6fb17b09 Mon Sep 17 00:00:00 2001 From: songlei Date: Sat, 28 Feb 2026 13:21:22 +0800 Subject: [PATCH] feat(feishu): support optional header in streaming cards (openclaw#22826) Add an optional `header` parameter to `FeishuStreamingSession.start()` so that streaming cards can display a colored title bar, matching the appearance of non-streaming interactive cards. The Card Kit API already supports `header` alongside `streaming_mode`, but the current implementation omits it, producing headerless cards. This change is fully backward-compatible: when `header` is not provided, behavior is identical to before. Closes #13267 (partial) Co-authored-by: Claude Opus 4.6 --- extensions/feishu/src/streaming-card.ts | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/extensions/feishu/src/streaming-card.ts b/extensions/feishu/src/streaming-card.ts index 1929eac0d94..f67926f4eb4 100644 --- a/extensions/feishu/src/streaming-card.ts +++ b/extensions/feishu/src/streaming-card.ts @@ -9,6 +9,13 @@ import type { FeishuDomain } from "./types.js"; type Credentials = { appId: string; appSecret: string; domain?: FeishuDomain }; type CardState = { cardId: string; messageId: string; sequence: number; currentText: string }; +/** Optional header for streaming cards (title bar with color template) */ +export type StreamingCardHeader = { + title: string; + /** Color template: blue, green, red, orange, purple, indigo, wathet, turquoise, yellow, grey, carmine, violet, lime */ + template?: string; +}; + // Token cache (keyed by domain + appId) const tokenCache = new Map(); @@ -99,14 +106,19 @@ export class FeishuStreamingSession { async start( receiveId: string, receiveIdType: "open_id" | "user_id" | "union_id" | "email" | "chat_id" = "chat_id", - options?: { replyToMessageId?: string; replyInThread?: boolean; rootId?: string }, + options?: { + replyToMessageId?: string; + replyInThread?: boolean; + rootId?: string; + header?: StreamingCardHeader; + }, ): Promise { if (this.state) { return; } const apiBase = resolveApiBase(this.creds.domain); - const cardJson = { + const cardJson: Record = { schema: "2.0", config: { streaming_mode: true, @@ -117,6 +129,12 @@ export class FeishuStreamingSession { elements: [{ tag: "markdown", content: "⏳ Thinking...", element_id: "content" }], }, }; + if (options?.header) { + cardJson.header = { + title: { tag: "plain_text", content: options.header.title }, + template: options.header.template ?? "blue", + }; + } // Create card entity const { response: createRes, release: releaseCreate } = await fetchWithSsrFGuard({