@max1874/feishu 0.2.24 → 0.2.26

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": "@max1874/feishu",
3
- "version": "0.2.24",
3
+ "version": "0.2.26",
4
4
  "type": "module",
5
5
  "description": "OpenClaw Feishu/Lark channel plugin",
6
6
  "license": "MIT",
package/src/bot.ts CHANGED
@@ -775,6 +775,7 @@ export async function handleFeishuMessage(params: {
775
775
  runtime: runtime as RuntimeEnv,
776
776
  chatId: ctx.chatId,
777
777
  replyToMessageId: effectiveReplyToMessageId,
778
+ replyInThread: !!effectiveReplyToMessageId,
778
779
  mentionTargets: ctx.mentionTargets,
779
780
  });
780
781
 
package/src/channel.ts CHANGED
@@ -55,6 +55,7 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
55
55
  messageToolHints: () => [
56
56
  "- Feishu targeting: omit `target` to reply to the current conversation (auto-inferred). Explicit targets: `user:open_id` or `chat:chat_id`.",
57
57
  "- Feishu supports interactive cards for rich messages.",
58
+ "- Use feishu_chat_messages to read message history. Omit chat_id to read from the current conversation.",
58
59
  ],
59
60
  },
60
61
  groups: {
package/src/docx.ts CHANGED
@@ -1247,5 +1247,113 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
1247
1247
  { name: "feishu_app_scopes" },
1248
1248
  );
1249
1249
 
1250
- api.logger.info?.(`feishu_doc: Registered 14 document/wiki tools`);
1250
+ // Tool 15: feishu_chat_messages
1251
+ const ChatMessagesSchema = Type.Object({
1252
+ chat_id: Type.Optional(
1253
+ Type.String({
1254
+ description: "Chat ID (oc_xxx). Omit to use the current conversation.",
1255
+ }),
1256
+ ),
1257
+ start_time: Type.Optional(
1258
+ Type.String({ description: "Start time, Unix timestamp in seconds" }),
1259
+ ),
1260
+ end_time: Type.Optional(
1261
+ Type.String({ description: "End time, Unix timestamp in seconds" }),
1262
+ ),
1263
+ sort_type: Type.Optional(
1264
+ Type.Union([Type.Literal("ByCreateTimeAsc"), Type.Literal("ByCreateTimeDesc")], {
1265
+ description: "Sort order (default: ByCreateTimeDesc)",
1266
+ }),
1267
+ ),
1268
+ page_size: Type.Optional(
1269
+ Type.Number({ description: "Page size, 1-50 (default 20)" }),
1270
+ ),
1271
+ page_token: Type.Optional(
1272
+ Type.String({ description: "Pagination token from previous response" }),
1273
+ ),
1274
+ });
1275
+
1276
+ api.registerTool(
1277
+ {
1278
+ name: "feishu_chat_messages",
1279
+ label: "Feishu Chat Messages",
1280
+ description:
1281
+ "Read message history from a Feishu chat (group or DM). " +
1282
+ "Omit chat_id to read from the current conversation. " +
1283
+ "Returns messages with sender info, content, and timestamps. " +
1284
+ "Requires im:message scope; bot must be a member of the chat.",
1285
+ parameters: ChatMessagesSchema,
1286
+ async execute(_toolCallId, params) {
1287
+ const {
1288
+ chat_id,
1289
+ start_time,
1290
+ end_time,
1291
+ sort_type,
1292
+ page_size,
1293
+ page_token,
1294
+ } = params as {
1295
+ chat_id?: string;
1296
+ start_time?: string;
1297
+ end_time?: string;
1298
+ sort_type?: "ByCreateTimeAsc" | "ByCreateTimeDesc";
1299
+ page_size?: number;
1300
+ page_token?: string;
1301
+ };
1302
+ try {
1303
+ const container_id = chat_id || getConversationContext()?.chatId;
1304
+ if (!container_id) {
1305
+ return json({
1306
+ error:
1307
+ "No chat_id provided and no current conversation context available.",
1308
+ });
1309
+ }
1310
+
1311
+ const client = getClient();
1312
+ const res = await client.im.message.list({
1313
+ params: {
1314
+ container_id_type: "chat",
1315
+ container_id,
1316
+ ...(start_time && { start_time }),
1317
+ ...(end_time && { end_time }),
1318
+ ...(sort_type && { sort_type }),
1319
+ ...(page_size && { page_size }),
1320
+ ...(page_token && { page_token }),
1321
+ },
1322
+ });
1323
+
1324
+ if (res.code !== 0) throw new Error(res.msg);
1325
+
1326
+ const messages = (res.data?.items ?? []).map((m) => {
1327
+ let content: unknown = m.body?.content;
1328
+ if (typeof content === "string") {
1329
+ try {
1330
+ content = JSON.parse(content);
1331
+ } catch {
1332
+ // keep raw string
1333
+ }
1334
+ }
1335
+ return {
1336
+ message_id: m.message_id,
1337
+ msg_type: m.msg_type,
1338
+ sender: m.sender,
1339
+ content,
1340
+ create_time: m.create_time,
1341
+ deleted: m.deleted,
1342
+ };
1343
+ });
1344
+
1345
+ return json({
1346
+ messages,
1347
+ has_more: res.data?.has_more ?? false,
1348
+ page_token: res.data?.page_token,
1349
+ });
1350
+ } catch (err) {
1351
+ return json({ error: err instanceof Error ? err.message : String(err) });
1352
+ }
1353
+ },
1354
+ },
1355
+ { name: "feishu_chat_messages" },
1356
+ );
1357
+
1358
+ api.logger.info?.(`feishu_doc: Registered 15 document/wiki/messaging tools`);
1251
1359
  }
@@ -34,13 +34,15 @@ export type CreateFeishuReplyDispatcherParams = {
34
34
  runtime: RuntimeEnv;
35
35
  chatId: string;
36
36
  replyToMessageId?: string;
37
+ /** When true, reply only appears in thread (not in main chat timeline) */
38
+ replyInThread?: boolean;
37
39
  /** Mention targets, will be auto-included in replies */
38
40
  mentionTargets?: MentionTarget[];
39
41
  };
40
42
 
41
43
  export function createFeishuReplyDispatcher(params: CreateFeishuReplyDispatcherParams) {
42
44
  const core = getFeishuRuntime();
43
- const { cfg, agentId, chatId, replyToMessageId, mentionTargets } = params;
45
+ const { cfg, agentId, chatId, replyToMessageId, replyInThread, mentionTargets } = params;
44
46
 
45
47
  const prefixContext = createReplyPrefixContext({
46
48
  cfg,
@@ -127,6 +129,7 @@ export function createFeishuReplyDispatcher(params: CreateFeishuReplyDispatcherP
127
129
  to: chatId,
128
130
  text: chunk,
129
131
  replyToMessageId,
132
+ replyInThread,
130
133
  mentions: isFirstChunk ? mentionTargets : undefined,
131
134
  });
132
135
  isFirstChunk = false;
@@ -142,6 +145,7 @@ export function createFeishuReplyDispatcher(params: CreateFeishuReplyDispatcherP
142
145
  to: chatId,
143
146
  text: chunk,
144
147
  replyToMessageId,
148
+ replyInThread,
145
149
  mentions: isFirstChunk ? mentionTargets : undefined,
146
150
  });
147
151
  isFirstChunk = false;
package/src/send.ts CHANGED
@@ -201,12 +201,14 @@ export type SendFeishuMessageParams = {
201
201
  to: string;
202
202
  text: string;
203
203
  replyToMessageId?: string;
204
+ /** When true, reply only appears in thread (not in main chat timeline) */
205
+ replyInThread?: boolean;
204
206
  /** Mention target users */
205
207
  mentions?: MentionTarget[];
206
208
  };
207
209
 
208
210
  export async function sendMessageFeishu(params: SendFeishuMessageParams): Promise<FeishuSendResult> {
209
- const { cfg, to, text, replyToMessageId, mentions } = params;
211
+ const { cfg, to, text, replyToMessageId, replyInThread, mentions } = params;
210
212
  const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
211
213
  if (!feishuCfg) {
212
214
  throw new Error("Feishu channel not configured");
@@ -239,6 +241,7 @@ export async function sendMessageFeishu(params: SendFeishuMessageParams): Promis
239
241
  data: {
240
242
  content,
241
243
  msg_type: "text",
244
+ ...(replyInThread ? { reply_in_thread: true } : {}),
242
245
  },
243
246
  });
244
247
 
@@ -276,10 +279,11 @@ export type SendFeishuCardParams = {
276
279
  to: string;
277
280
  card: Record<string, unknown>;
278
281
  replyToMessageId?: string;
282
+ replyInThread?: boolean;
279
283
  };
280
284
 
281
285
  export async function sendCardFeishu(params: SendFeishuCardParams): Promise<FeishuSendResult> {
282
- const { cfg, to, card, replyToMessageId } = params;
286
+ const { cfg, to, card, replyToMessageId, replyInThread } = params;
283
287
  const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
284
288
  if (!feishuCfg) {
285
289
  throw new Error("Feishu channel not configured");
@@ -300,6 +304,7 @@ export async function sendCardFeishu(params: SendFeishuCardParams): Promise<Feis
300
304
  data: {
301
305
  content,
302
306
  msg_type: "interactive",
307
+ ...(replyInThread ? { reply_in_thread: true } : {}),
303
308
  },
304
309
  });
305
310
 
@@ -383,17 +388,18 @@ export async function sendMarkdownCardFeishu(params: {
383
388
  to: string;
384
389
  text: string;
385
390
  replyToMessageId?: string;
391
+ replyInThread?: boolean;
386
392
  /** Mention target users */
387
393
  mentions?: MentionTarget[];
388
394
  }): Promise<FeishuSendResult> {
389
- const { cfg, to, text, replyToMessageId, mentions } = params;
395
+ const { cfg, to, text, replyToMessageId, replyInThread, mentions } = params;
390
396
  // Build message content (with @mention support)
391
397
  let cardText = text;
392
398
  if (mentions && mentions.length > 0) {
393
399
  cardText = buildMentionedCardContent(mentions, text);
394
400
  }
395
401
  const card = buildMarkdownCard(cardText);
396
- return sendCardFeishu({ cfg, to, card, replyToMessageId });
402
+ return sendCardFeishu({ cfg, to, card, replyToMessageId, replyInThread });
397
403
  }
398
404
 
399
405
  /**