@max1874/feishu 0.2.17 → 0.2.19

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.17",
3
+ "version": "0.2.19",
4
4
  "type": "module",
5
5
  "description": "OpenClaw Feishu/Lark channel plugin",
6
6
  "license": "MIT",
package/src/bot.ts CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  type HistoryEntry,
8
8
  } from "openclaw/plugin-sdk";
9
9
  import type { FeishuConfig, FeishuMessageContext, FeishuMediaInfo } from "./types.js";
10
- import { getFeishuRuntime } from "./runtime.js";
10
+ import { getFeishuRuntime, runWithConversationContext } from "./runtime.js";
11
11
  import { createFeishuClient } from "./client.js";
12
12
  import {
13
13
  resolveFeishuGroupConfig,
@@ -763,12 +763,15 @@ export async function handleFeishuMessage(params: {
763
763
 
764
764
  log(`feishu: dispatching to agent (session=${route.sessionKey})`);
765
765
 
766
- const { queuedFinal, counts } = await core.channel.reply.dispatchReplyFromConfig({
767
- ctx: ctxPayload,
768
- cfg,
769
- dispatcher,
770
- replyOptions,
771
- });
766
+ const { queuedFinal, counts } = await runWithConversationContext(
767
+ { senderOpenId: ctx.senderOpenId, chatId: ctx.chatId, chatType: isGroup ? "group" : "p2p" },
768
+ () => core.channel.reply.dispatchReplyFromConfig({
769
+ ctx: ctxPayload,
770
+ cfg,
771
+ dispatcher,
772
+ replyOptions,
773
+ }),
774
+ );
772
775
 
773
776
  markDispatchIdle();
774
777
 
package/src/docx.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
3
3
  import { createFeishuClient } from "./client.js";
4
+ import { getConversationContext } from "./runtime.js";
4
5
  import type { FeishuConfig } from "./types.js";
5
6
  import type * as Lark from "@larksuiteoapi/node-sdk";
6
7
  import { Readable } from "stream";
@@ -490,6 +491,36 @@ async function setDocPermissionTenantEditable(client: Lark.Client, docToken: str
490
491
 
491
492
  export type DocPermission = "private" | "tenant_editable";
492
493
 
494
+ /** Grant full_access to a user (ou_*) or chat group (oc_*) */
495
+ async function grantFullAccess(client: Lark.Client, docToken: string, memberId: string) {
496
+ const isChat = memberId.startsWith("oc_");
497
+ const res = await client.drive.permissionMember.create({
498
+ path: { token: docToken },
499
+ params: { type: "docx", need_notification: false },
500
+ data: {
501
+ member_type: isChat ? "openchat" : "openid",
502
+ member_id: memberId,
503
+ perm: "full_access",
504
+ type: isChat ? "chat" : "user",
505
+ },
506
+ });
507
+ if (res.code !== 0) throw new Error(res.msg);
508
+ return res.data;
509
+ }
510
+
511
+ const DEFAULT_COVER_TOKEN = "KeMcb0lJdosqhtx4aAglZjaWgug";
512
+
513
+ async function setDocCover(client: Lark.Client, docId: string, coverToken: string) {
514
+ // SDK lacks document.patch — use client.request() which auto-injects tenant_access_token and domain
515
+ await (client as any).request({
516
+ method: "PATCH",
517
+ url: `/open-apis/docx/v1/documents/${docId}`,
518
+ data: {
519
+ cover: { token: coverToken, offset_ratio_x: 0, offset_ratio_y: 0 },
520
+ },
521
+ });
522
+ }
523
+
493
524
  async function createDoc(
494
525
  client: Lark.Client,
495
526
  title: string,
@@ -503,16 +534,39 @@ async function createDoc(
503
534
  const doc = res.data?.document;
504
535
  const docId = doc?.document_id;
505
536
 
506
- // Set permission if requested
537
+ // Set link sharing permission if requested
507
538
  if (permission === "tenant_editable" && docId) {
508
539
  await setDocPermissionTenantEditable(client, docId);
509
540
  }
510
541
 
542
+ // Auto-grant full_access to conversation participant (user in DM, chat group in group)
543
+ let grantedTo: string | undefined;
544
+ const convCtx = getConversationContext();
545
+ if (docId && convCtx) {
546
+ const memberId = convCtx.chatType === "group" ? convCtx.chatId : convCtx.senderOpenId;
547
+ try {
548
+ await grantFullAccess(client, docId, memberId);
549
+ grantedTo = memberId;
550
+ } catch {
551
+ // Non-fatal: doc is created, permission grant failed
552
+ }
553
+ }
554
+
555
+ // Set default cover
556
+ if (docId) {
557
+ try {
558
+ await setDocCover(client, docId, DEFAULT_COVER_TOKEN);
559
+ } catch {
560
+ // Non-fatal: doc is created, cover set failed
561
+ }
562
+ }
563
+
511
564
  return {
512
565
  document_id: docId,
513
566
  title: doc?.title,
514
567
  url: `https://feishu.cn/docx/${docId}`,
515
568
  permission: permission ?? "private",
569
+ ...(grantedTo && { granted_full_access_to: grantedTo }),
516
570
  };
517
571
  }
518
572
 
@@ -854,7 +908,7 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
854
908
  name: "feishu_doc_create",
855
909
  label: "Feishu Doc Create",
856
910
  description:
857
- "Create a new empty Feishu document. Use permission='tenant_editable' to allow organization members to edit via link.",
911
+ "Create a new empty Feishu document. Automatically grants full_access (manage) permission to the current conversation participant.",
858
912
  parameters: CreateDocSchema,
859
913
  async execute(_toolCallId, params) {
860
914
  const { title, folder_token, permission } = params as {
package/src/runtime.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { PluginRuntime } from "openclaw/plugin-sdk";
2
+ import { AsyncLocalStorage } from "node:async_hooks";
2
3
 
3
4
  let runtime: PluginRuntime | null = null;
4
5
 
@@ -12,3 +13,21 @@ export function getFeishuRuntime(): PluginRuntime {
12
13
  }
13
14
  return runtime;
14
15
  }
16
+
17
+ // --- Conversation context (tracked via AsyncLocalStorage for concurrency safety) ---
18
+
19
+ export interface FeishuConversationContext {
20
+ senderOpenId: string;
21
+ chatId: string;
22
+ chatType: "group" | "p2p";
23
+ }
24
+
25
+ const conversationStore = new AsyncLocalStorage<FeishuConversationContext>();
26
+
27
+ export function runWithConversationContext<T>(ctx: FeishuConversationContext, fn: () => T): T {
28
+ return conversationStore.run(ctx, fn);
29
+ }
30
+
31
+ export function getConversationContext(): FeishuConversationContext | undefined {
32
+ return conversationStore.getStore();
33
+ }