@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 +1 -1
- package/src/bot.ts +10 -2
- package/src/media.ts +11 -25
- package/src/send-target.ts +25 -0
- package/src/send.ts +4 -26
- package/src/streaming-card.ts +22 -27
package/package.json
CHANGED
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
|
-
|
|
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 {
|
|
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
|
|
272
|
-
|
|
273
|
-
|
|
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
|
|
324
|
-
|
|
325
|
-
|
|
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 {
|
|
9
|
-
import type { FeishuSendResult
|
|
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
|
|
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
|
|
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) {
|
package/src/streaming-card.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
|