@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 +1 -1
- package/src/bot.ts +10 -7
- package/src/docx.ts +56 -2
- package/src/runtime.ts +19 -0
package/package.json
CHANGED
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
|
|
767
|
-
ctx:
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
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.
|
|
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
|
+
}
|