@max1874/feishu 0.2.22 → 0.2.24

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.22",
3
+ "version": "0.2.24",
4
4
  "type": "module",
5
5
  "description": "OpenClaw Feishu/Lark channel plugin",
6
6
  "license": "MIT",
package/src/bot.ts CHANGED
@@ -752,12 +752,29 @@ export async function handleFeishuMessage(params: {
752
752
  ...mediaPayload,
753
753
  });
754
754
 
755
+ // Resolve replyToMode: per-chat-type override > global > default ("all")
756
+ const chatTypeKey = isGroup ? "group" : "direct";
757
+ const replyToMode =
758
+ feishuCfg?.replyToModeByChatType?.[chatTypeKey] ??
759
+ feishuCfg?.replyToMode ??
760
+ "all";
761
+
762
+ // Compute effective replyToMessageId based on mode
763
+ let effectiveReplyToMessageId: string | undefined;
764
+ if (replyToMode === "all") {
765
+ // Always thread: reply to the triggering message
766
+ effectiveReplyToMessageId = ctx.messageId;
767
+ } else {
768
+ // "off": only thread if the triggering message is already in a thread
769
+ effectiveReplyToMessageId = ctx.rootId;
770
+ }
771
+
755
772
  const { dispatcher, replyOptions, markDispatchIdle } = createFeishuReplyDispatcher({
756
773
  cfg,
757
774
  agentId: route.agentId,
758
775
  runtime: runtime as RuntimeEnv,
759
776
  chatId: ctx.chatId,
760
- replyToMessageId: ctx.messageId,
777
+ replyToMessageId: effectiveReplyToMessageId,
761
778
  mentionTargets: ctx.mentionTargets,
762
779
  });
763
780
 
@@ -33,6 +33,20 @@ const MarkdownConfigSchema = z
33
33
  // Message render mode: auto (default) = detect markdown, raw = plain text, card = always card
34
34
  const RenderModeSchema = z.enum(["auto", "raw", "card"]).optional();
35
35
 
36
+ // Reply-to (threading) mode: controls whether bot replies are sent as threaded replies.
37
+ // - "all" (default): always reply in thread (current behavior)
38
+ // - "off": reply in main chat; only thread if the triggering message was already in a thread
39
+ const ReplyToModeSchema = z.enum(["off", "all"]).optional();
40
+
41
+ // Per-chat-type overrides for replyToMode
42
+ const ReplyToModeByChatTypeSchema = z
43
+ .object({
44
+ direct: ReplyToModeSchema,
45
+ group: ReplyToModeSchema,
46
+ })
47
+ .strict()
48
+ .optional();
49
+
36
50
  const BlockStreamingCoalesceSchema = z
37
51
  .object({
38
52
  enabled: z.boolean().optional(),
@@ -90,6 +104,8 @@ export const FeishuConfigSchema = z
90
104
  mediaMaxMb: z.number().positive().optional(),
91
105
  heartbeat: ChannelHeartbeatVisibilitySchema,
92
106
  renderMode: RenderModeSchema, // raw = plain text (default), card = interactive card with markdown
107
+ replyToMode: ReplyToModeSchema, // "all" = always thread (default), "off" = main chat unless already in thread
108
+ replyToModeByChatType: ReplyToModeByChatTypeSchema, // per-chat-type overrides for replyToMode
93
109
  })
94
110
  .strict()
95
111
  .superRefine((value, ctx) => {
package/src/docx.ts CHANGED
@@ -508,6 +508,20 @@ async function grantFullAccess(client: Lark.Client, docToken: string, memberId:
508
508
  return res.data;
509
509
  }
510
510
 
511
+ /** Transfer document ownership to a user (open_id) */
512
+ async function transferOwner(client: Lark.Client, docToken: string, newOwnerOpenId: string) {
513
+ const res = await client.drive.permissionMember.transferOwner({
514
+ path: { token: docToken },
515
+ params: { type: "docx", need_notification: false, remove_old_owner: false },
516
+ data: {
517
+ member_type: "openid",
518
+ member_id: newOwnerOpenId,
519
+ },
520
+ });
521
+ if (res.code !== 0) throw new Error(res.msg);
522
+ return res.data;
523
+ }
524
+
511
525
  async function createDoc(
512
526
  client: Lark.Client,
513
527
  title: string,
@@ -526,16 +540,32 @@ async function createDoc(
526
540
  await setDocPermissionTenantEditable(client, docId);
527
541
  }
528
542
 
529
- // Auto-grant full_access to conversation participant (user in DM, chat group in group)
543
+ // Auto-transfer ownership to the sender, and grant group access if in group chat
544
+ let ownerTransferredTo: string | undefined;
530
545
  let grantedTo: string | undefined;
531
546
  const convCtx = getConversationContext();
532
547
  if (docId && convCtx) {
533
- const memberId = convCtx.chatType === "group" ? convCtx.chatId : convCtx.senderOpenId;
548
+ // Transfer ownership to the user who triggered this request
534
549
  try {
535
- await grantFullAccess(client, docId, memberId);
536
- grantedTo = memberId;
550
+ await transferOwner(client, docId, convCtx.senderOpenId);
551
+ ownerTransferredTo = convCtx.senderOpenId;
537
552
  } catch {
538
- // Non-fatal: doc is created, permission grant failed
553
+ // Fallback: grant full_access if transfer fails
554
+ try {
555
+ await grantFullAccess(client, docId, convCtx.senderOpenId);
556
+ grantedTo = convCtx.senderOpenId;
557
+ } catch {
558
+ // Non-fatal: doc is created, permission grant failed
559
+ }
560
+ }
561
+
562
+ // In group chats, also grant full_access to the chat group
563
+ if (convCtx.chatType === "group") {
564
+ try {
565
+ await grantFullAccess(client, docId, convCtx.chatId);
566
+ } catch {
567
+ // Non-fatal
568
+ }
539
569
  }
540
570
  }
541
571
 
@@ -544,6 +574,7 @@ async function createDoc(
544
574
  title: doc?.title,
545
575
  url: `https://feishu.cn/docx/${docId}`,
546
576
  permission: permission ?? "private",
577
+ ...(ownerTransferredTo && { owner_transferred_to: ownerTransferredTo }),
547
578
  ...(grantedTo && { granted_full_access_to: grantedTo }),
548
579
  };
549
580
  }