@openclaw/feishu 2026.2.22 → 2026.2.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/feishu",
3
- "version": "2026.2.22",
3
+ "version": "2026.2.23",
4
4
  "description": "OpenClaw Feishu/Lark channel plugin (community maintained by @m1heng)",
5
5
  "type": "module",
6
6
  "dependencies": {
package/src/bot.ts CHANGED
@@ -720,10 +720,10 @@ export async function handleFeishuMessage(params: {
720
720
  // When topicSessionMode is enabled, messages within a topic (identified by root_id)
721
721
  // get a separate session from the main group chat.
722
722
  let peerId = isGroup ? ctx.chatId : ctx.senderOpenId;
723
+ let topicSessionMode: "enabled" | "disabled" = "disabled";
723
724
  if (isGroup && ctx.rootId) {
724
725
  const groupConfig = resolveFeishuGroupConfig({ cfg: feishuCfg, groupId: ctx.chatId });
725
- const topicSessionMode =
726
- groupConfig?.topicSessionMode ?? feishuCfg?.topicSessionMode ?? "disabled";
726
+ topicSessionMode = groupConfig?.topicSessionMode ?? feishuCfg?.topicSessionMode ?? "disabled";
727
727
  if (topicSessionMode === "enabled") {
728
728
  // Use chatId:topic:rootId as peer ID for topic-scoped sessions
729
729
  peerId = `${ctx.chatId}:topic:${ctx.rootId}`;
@@ -739,6 +739,14 @@ export async function handleFeishuMessage(params: {
739
739
  kind: isGroup ? "group" : "direct",
740
740
  id: peerId,
741
741
  },
742
+ // Add parentPeer for binding inheritance in topic mode
743
+ parentPeer:
744
+ isGroup && ctx.rootId && topicSessionMode === "enabled"
745
+ ? {
746
+ kind: "group",
747
+ id: ctx.chatId,
748
+ }
749
+ : null,
742
750
  });
743
751
 
744
752
  // Dynamic agent creation for DM users
package/src/media.ts CHANGED
@@ -7,7 +7,7 @@ import { createFeishuClient } from "./client.js";
7
7
  import { normalizeFeishuExternalKey } from "./external-keys.js";
8
8
  import { getFeishuRuntime } from "./runtime.js";
9
9
  import { assertFeishuMessageApiSuccess, toFeishuSendResult } from "./send-result.js";
10
- import { resolveReceiveIdType, normalizeFeishuTarget } from "./targets.js";
10
+ import { resolveFeishuSendTarget } from "./send-target.js";
11
11
 
12
12
  export type DownloadImageResult = {
13
13
  buffer: Buffer;
@@ -268,18 +268,11 @@ export async function sendImageFeishu(params: {
268
268
  accountId?: string;
269
269
  }): Promise<SendMediaResult> {
270
270
  const { cfg, to, imageKey, replyToMessageId, accountId } = params;
271
- const account = resolveFeishuAccount({ cfg, accountId });
272
- if (!account.configured) {
273
- throw new Error(`Feishu account "${account.accountId}" not configured`);
274
- }
275
-
276
- const client = createFeishuClient(account);
277
- const receiveId = normalizeFeishuTarget(to);
278
- if (!receiveId) {
279
- throw new Error(`Invalid Feishu target: ${to}`);
280
- }
281
-
282
- const receiveIdType = resolveReceiveIdType(receiveId);
271
+ const { client, receiveId, receiveIdType } = resolveFeishuSendTarget({
272
+ cfg,
273
+ to,
274
+ accountId,
275
+ });
283
276
  const content = JSON.stringify({ image_key: imageKey });
284
277
 
285
278
  if (replyToMessageId) {
@@ -320,18 +313,11 @@ export async function sendFileFeishu(params: {
320
313
  }): Promise<SendMediaResult> {
321
314
  const { cfg, to, fileKey, replyToMessageId, accountId } = params;
322
315
  const msgType = params.msgType ?? "file";
323
- const account = resolveFeishuAccount({ cfg, accountId });
324
- if (!account.configured) {
325
- throw new Error(`Feishu account "${account.accountId}" not configured`);
326
- }
327
-
328
- const client = createFeishuClient(account);
329
- const receiveId = normalizeFeishuTarget(to);
330
- if (!receiveId) {
331
- throw new Error(`Invalid Feishu target: ${to}`);
332
- }
333
-
334
- const receiveIdType = resolveReceiveIdType(receiveId);
316
+ const { client, receiveId, receiveIdType } = resolveFeishuSendTarget({
317
+ cfg,
318
+ to,
319
+ accountId,
320
+ });
335
321
  const content = JSON.stringify({ file_key: fileKey });
336
322
 
337
323
  if (replyToMessageId) {
@@ -0,0 +1,25 @@
1
+ import type { ClawdbotConfig } from "openclaw/plugin-sdk";
2
+ import { resolveFeishuAccount } from "./accounts.js";
3
+ import { createFeishuClient } from "./client.js";
4
+ import { resolveReceiveIdType, normalizeFeishuTarget } from "./targets.js";
5
+
6
+ export function resolveFeishuSendTarget(params: {
7
+ cfg: ClawdbotConfig;
8
+ to: string;
9
+ accountId?: string;
10
+ }) {
11
+ const account = resolveFeishuAccount({ cfg: params.cfg, accountId: params.accountId });
12
+ if (!account.configured) {
13
+ throw new Error(`Feishu account "${account.accountId}" not configured`);
14
+ }
15
+ const client = createFeishuClient(account);
16
+ const receiveId = normalizeFeishuTarget(params.to);
17
+ if (!receiveId) {
18
+ throw new Error(`Invalid Feishu target: ${params.to}`);
19
+ }
20
+ return {
21
+ client,
22
+ receiveId,
23
+ receiveIdType: resolveReceiveIdType(receiveId),
24
+ };
25
+ }
package/src/send.ts CHANGED
@@ -5,8 +5,8 @@ import type { MentionTarget } from "./mention.js";
5
5
  import { buildMentionedMessage, buildMentionedCardContent } from "./mention.js";
6
6
  import { getFeishuRuntime } from "./runtime.js";
7
7
  import { assertFeishuMessageApiSuccess, toFeishuSendResult } from "./send-result.js";
8
- import { resolveReceiveIdType, normalizeFeishuTarget } from "./targets.js";
9
- import type { FeishuSendResult, ResolvedFeishuAccount } from "./types.js";
8
+ import { resolveFeishuSendTarget } from "./send-target.js";
9
+ import type { FeishuSendResult } from "./types.js";
10
10
 
11
11
  export type FeishuMessageInfo = {
12
12
  messageId: string;
@@ -128,18 +128,7 @@ export async function sendMessageFeishu(
128
128
  params: SendFeishuMessageParams,
129
129
  ): Promise<FeishuSendResult> {
130
130
  const { cfg, to, text, replyToMessageId, mentions, accountId } = params;
131
- const account = resolveFeishuAccount({ cfg, accountId });
132
- if (!account.configured) {
133
- throw new Error(`Feishu account "${account.accountId}" not configured`);
134
- }
135
-
136
- const client = createFeishuClient(account);
137
- const receiveId = normalizeFeishuTarget(to);
138
- if (!receiveId) {
139
- throw new Error(`Invalid Feishu target: ${to}`);
140
- }
141
-
142
- const receiveIdType = resolveReceiveIdType(receiveId);
131
+ const { client, receiveId, receiveIdType } = resolveFeishuSendTarget({ cfg, to, accountId });
143
132
  const tableMode = getFeishuRuntime().channel.text.resolveMarkdownTableMode({
144
133
  cfg,
145
134
  channel: "feishu",
@@ -188,18 +177,7 @@ export type SendFeishuCardParams = {
188
177
 
189
178
  export async function sendCardFeishu(params: SendFeishuCardParams): Promise<FeishuSendResult> {
190
179
  const { cfg, to, card, replyToMessageId, accountId } = params;
191
- const account = resolveFeishuAccount({ cfg, accountId });
192
- if (!account.configured) {
193
- throw new Error(`Feishu account "${account.accountId}" not configured`);
194
- }
195
-
196
- const client = createFeishuClient(account);
197
- const receiveId = normalizeFeishuTarget(to);
198
- if (!receiveId) {
199
- throw new Error(`Invalid Feishu target: ${to}`);
200
- }
201
-
202
- const receiveIdType = resolveReceiveIdType(receiveId);
180
+ const { client, receiveId, receiveIdType } = resolveFeishuSendTarget({ cfg, to, accountId });
203
181
  const content = JSON.stringify(card);
204
182
 
205
183
  if (replyToMessageId) {
@@ -132,6 +132,26 @@ export class FeishuStreamingSession {
132
132
  this.log?.(`Started streaming: cardId=${cardId}, messageId=${sendRes.data.message_id}`);
133
133
  }
134
134
 
135
+ private async updateCardContent(text: string, onError?: (error: unknown) => void): Promise<void> {
136
+ if (!this.state) {
137
+ return;
138
+ }
139
+ const apiBase = resolveApiBase(this.creds.domain);
140
+ this.state.sequence += 1;
141
+ await fetch(`${apiBase}/cardkit/v1/cards/${this.state.cardId}/elements/content/content`, {
142
+ method: "PUT",
143
+ headers: {
144
+ Authorization: `Bearer ${await getToken(this.creds)}`,
145
+ "Content-Type": "application/json",
146
+ },
147
+ body: JSON.stringify({
148
+ content: text,
149
+ sequence: this.state.sequence,
150
+ uuid: `s_${this.state.cardId}_${this.state.sequence}`,
151
+ }),
152
+ }).catch((error) => onError?.(error));
153
+ }
154
+
135
155
  async update(text: string): Promise<void> {
136
156
  if (!this.state || this.closed) {
137
157
  return;
@@ -150,20 +170,7 @@ export class FeishuStreamingSession {
150
170
  return;
151
171
  }
152
172
  this.state.currentText = text;
153
- this.state.sequence += 1;
154
- const apiBase = resolveApiBase(this.creds.domain);
155
- await fetch(`${apiBase}/cardkit/v1/cards/${this.state.cardId}/elements/content/content`, {
156
- method: "PUT",
157
- headers: {
158
- Authorization: `Bearer ${await getToken(this.creds)}`,
159
- "Content-Type": "application/json",
160
- },
161
- body: JSON.stringify({
162
- content: text,
163
- sequence: this.state.sequence,
164
- uuid: `s_${this.state.cardId}_${this.state.sequence}`,
165
- }),
166
- }).catch((e) => this.log?.(`Update failed: ${String(e)}`));
173
+ await this.updateCardContent(text, (e) => this.log?.(`Update failed: ${String(e)}`));
167
174
  });
168
175
  await this.queue;
169
176
  }
@@ -181,19 +188,7 @@ export class FeishuStreamingSession {
181
188
 
182
189
  // Only send final update if content differs from what's already displayed
183
190
  if (text && text !== this.state.currentText) {
184
- this.state.sequence += 1;
185
- await fetch(`${apiBase}/cardkit/v1/cards/${this.state.cardId}/elements/content/content`, {
186
- method: "PUT",
187
- headers: {
188
- Authorization: `Bearer ${await getToken(this.creds)}`,
189
- "Content-Type": "application/json",
190
- },
191
- body: JSON.stringify({
192
- content: text,
193
- sequence: this.state.sequence,
194
- uuid: `s_${this.state.cardId}_${this.state.sequence}`,
195
- }),
196
- }).catch(() => {});
191
+ await this.updateCardContent(text);
197
192
  this.state.currentText = text;
198
193
  }
199
194