@m1heng-clawd/feishu 0.1.13 → 0.1.15

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/src/channel.ts CHANGED
@@ -91,6 +91,7 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
91
91
  groupAllowFrom: { type: "array", items: { oneOf: [{ type: "string" }, { type: "number" }] } },
92
92
  requireMention: { type: "boolean" },
93
93
  groupCommandMentionBypass: { type: "string", enum: ["never", "single_bot", "always"] },
94
+ allowMentionlessInMultiBotGroup: { type: "boolean" },
94
95
  topicSessionMode: { type: "string", enum: ["disabled", "enabled"] },
95
96
  historyLimit: { type: "integer", minimum: 0 },
96
97
  dmHistoryLimit: { type: "integer", minimum: 0 },
@@ -114,6 +115,7 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
114
115
  connectionMode: { type: "string", enum: ["websocket", "webhook"] },
115
116
  mediaLocalRoots: { type: "array", items: { type: "string" } },
116
117
  groupCommandMentionBypass: { type: "string", enum: ["never", "single_bot", "always"] },
118
+ allowMentionlessInMultiBotGroup: { type: "boolean" },
117
119
  },
118
120
  },
119
121
  },
@@ -0,0 +1,433 @@
1
+ import type { ChatClient } from "./common.js";
2
+ import type { FeishuChatParams } from "./schemas.js";
3
+ import { runChatApiCall } from "./common.js";
4
+
5
+ const BLOCK_TYPE_NAMES: Record<number, string> = {
6
+ 1: "Page",
7
+ 2: "Text",
8
+ 3: "Heading1",
9
+ 4: "Heading2",
10
+ 5: "Heading3",
11
+ 12: "Bullet",
12
+ 13: "Ordered",
13
+ 14: "Code",
14
+ 15: "Quote",
15
+ 17: "Todo",
16
+ 18: "Bitable",
17
+ 21: "Diagram",
18
+ 22: "Divider",
19
+ 23: "File",
20
+ 27: "Image",
21
+ 30: "Sheet",
22
+ 31: "Table",
23
+ 32: "TableCell",
24
+ };
25
+
26
+ const STRUCTURED_BLOCK_TYPES = new Set([14, 18, 21, 23, 27, 30, 31, 32]);
27
+
28
+ async function getAnnouncement(client: ChatClient, chatId: string) {
29
+ // Use docx.chatAnnouncement.get first — it works for both doc and docx announcements
30
+ // and returns announcement_type in its response, avoiding the noisy 232097 error
31
+ // that would occur when calling the legacy im API on a docx announcement.
32
+ const infoRes = await runChatApiCall("docx.chatAnnouncement.get", () =>
33
+ (client as any).docx.chatAnnouncement.get({
34
+ path: { chat_id: chatId },
35
+ }),
36
+ );
37
+
38
+ const announcementType = (infoRes as any).data?.announcement_type;
39
+
40
+ if (announcementType === "doc") {
41
+ // Legacy doc format: fetch actual content via the im API
42
+ const docRes = await runChatApiCall("im.chatAnnouncement.get", () =>
43
+ (client as any).im.chatAnnouncement.get({
44
+ path: { chat_id: chatId },
45
+ }),
46
+ );
47
+ return {
48
+ announcement_type: "doc" as const,
49
+ ...(docRes as any).data,
50
+ };
51
+ }
52
+
53
+ // docx format (or unrecognised new format): fetch blocks
54
+ const blocksRes = await runChatApiCall("docx.chatAnnouncementBlock.list", () =>
55
+ (client as any).docx.chatAnnouncementBlock.list({
56
+ path: { chat_id: chatId },
57
+ }),
58
+ );
59
+
60
+ const blocks = (blocksRes as any).data?.items ?? [];
61
+ const blockCounts: Record<string, number> = {};
62
+ const structuredTypes: string[] = [];
63
+
64
+ for (const b of blocks) {
65
+ const type = b.block_type ?? 0;
66
+ const name = BLOCK_TYPE_NAMES[type] || `type_${type}`;
67
+ blockCounts[name] = (blockCounts[name] || 0) + 1;
68
+
69
+ if (STRUCTURED_BLOCK_TYPES.has(type) && !structuredTypes.includes(name)) {
70
+ structuredTypes.push(name);
71
+ }
72
+ }
73
+
74
+ let hint: string | undefined;
75
+ if (structuredTypes.length > 0) {
76
+ hint = `This announcement contains ${structuredTypes.join(", ")} which are NOT included in the basic info. Use action: "list_announcement_blocks" to get full content.`;
77
+ }
78
+
79
+ return {
80
+ announcement_type: "docx" as const,
81
+ info: (infoRes as any).data,
82
+ blocks,
83
+ block_count: blocks.length,
84
+ block_types: blockCounts,
85
+ ...(hint && { hint }),
86
+ };
87
+ }
88
+
89
+ async function listAnnouncementBlocks(client: ChatClient, chatId: string) {
90
+ const res = await runChatApiCall("docx.chatAnnouncementBlock.list", () =>
91
+ (client as any).docx.chatAnnouncementBlock.list({
92
+ path: { chat_id: chatId },
93
+ }),
94
+ );
95
+
96
+ return {
97
+ blocks: (res as any).data?.items ?? [],
98
+ };
99
+ }
100
+
101
+ async function getAnnouncementBlock(client: ChatClient, chatId: string, blockId: string) {
102
+ const res = await runChatApiCall("docx.chatAnnouncementBlock.get", () =>
103
+ (client as any).docx.chatAnnouncementBlock.get({
104
+ path: { chat_id: chatId, block_id: blockId },
105
+ }),
106
+ );
107
+
108
+ return {
109
+ block: (res as any).data?.block,
110
+ };
111
+ }
112
+
113
+ async function writeDocAnnouncement(client: ChatClient, chatId: string, content: string) {
114
+ const current = await runChatApiCall("im.chatAnnouncement.get", () =>
115
+ (client as any).im.chatAnnouncement.get({
116
+ path: { chat_id: chatId },
117
+ }),
118
+ );
119
+
120
+ const res = await runChatApiCall("im.chatAnnouncement.patch", () =>
121
+ (client as any).im.chatAnnouncement.patch({
122
+ path: { chat_id: chatId },
123
+ data: {
124
+ content,
125
+ revision: (current as any).data?.revision,
126
+ },
127
+ }),
128
+ );
129
+
130
+ return {
131
+ success: true,
132
+ announcement_type: "doc",
133
+ ...(res as any).data,
134
+ };
135
+ }
136
+
137
+ async function createAnnouncementBlockChild(
138
+ client: ChatClient,
139
+ chatId: string,
140
+ parentBlockId: string,
141
+ blockData: any,
142
+ ) {
143
+ const res = await runChatApiCall("docx.chatAnnouncementBlockChildren.create", () =>
144
+ (client as any).docx.chatAnnouncementBlockChildren.create({
145
+ path: { chat_id: chatId, block_id: parentBlockId },
146
+ data: blockData,
147
+ }),
148
+ );
149
+
150
+ return {
151
+ success: true,
152
+ block: (res as any).data,
153
+ };
154
+ }
155
+
156
+ async function createTextBlock(
157
+ client: ChatClient,
158
+ chatId: string,
159
+ parentBlockId: string,
160
+ text: string,
161
+ ) {
162
+ const blockData = {
163
+ children: [
164
+ {
165
+ block_type: 2,
166
+ text: {
167
+ elements: [
168
+ {
169
+ text_run: {
170
+ content: text,
171
+ },
172
+ },
173
+ ],
174
+ },
175
+ },
176
+ ],
177
+ };
178
+
179
+ return createAnnouncementBlockChild(client, chatId, parentBlockId, blockData);
180
+ }
181
+
182
+ async function batchUpdateAnnouncementBlocks(
183
+ client: ChatClient,
184
+ chatId: string,
185
+ requests: any[],
186
+ ) {
187
+ const info = await runChatApiCall("docx.chatAnnouncement.get", () =>
188
+ (client as any).docx.chatAnnouncement.get({
189
+ path: { chat_id: chatId },
190
+ }),
191
+ );
192
+
193
+ const res = await runChatApiCall("docx.chatAnnouncementBlock.batchUpdate", () =>
194
+ (client as any).docx.chatAnnouncementBlock.batchUpdate({
195
+ path: { chat_id: chatId },
196
+ params: {
197
+ revision_id: (info as any).data?.revision_id,
198
+ },
199
+ data: {
200
+ requests,
201
+ },
202
+ }),
203
+ );
204
+
205
+ return {
206
+ success: true,
207
+ ...(res as any).data,
208
+ };
209
+ }
210
+
211
+ // ============== New Chat Management Functions ==============
212
+
213
+ async function createChat(client: ChatClient, name: string, userIds?: string[], description?: string) {
214
+ const data: any = { name };
215
+ if (userIds && userIds.length > 0) {
216
+ data.user_id_list = userIds;
217
+ }
218
+ if (description) {
219
+ data.description = description;
220
+ }
221
+
222
+ const res = await runChatApiCall("im.chat.create", () =>
223
+ (client as any).im.chat.create({
224
+ data,
225
+ params: { user_id_type: "open_id" },
226
+ }),
227
+ );
228
+
229
+ return {
230
+ success: true,
231
+ chat_id: (res as any).data?.chat_id,
232
+ ...(res as any).data,
233
+ };
234
+ }
235
+
236
+ async function addMembers(client: ChatClient, chatId: string, userIds: string[]) {
237
+ const res = await runChatApiCall("im.chatMembers.create", () =>
238
+ (client as any).im.chatMembers.create({
239
+ path: { chat_id: chatId },
240
+ params: { member_id_type: "open_id" },
241
+ data: { id_list: userIds },
242
+ }),
243
+ );
244
+
245
+ return {
246
+ success: true,
247
+ chat_id: chatId,
248
+ added_user_ids: userIds,
249
+ ...(res as any).data,
250
+ };
251
+ }
252
+
253
+ async function checkBotInChat(client: ChatClient, chatId: string) {
254
+ try {
255
+ const res = await runChatApiCall("im.chat.get", () =>
256
+ (client as any).im.chat.get({ path: { chat_id: chatId } }),
257
+ );
258
+
259
+ return {
260
+ success: true,
261
+ chat_id: chatId,
262
+ in_chat: true,
263
+ chat_info: (res as any).data,
264
+ };
265
+ } catch (err: any) {
266
+ if (err?.message?.includes("90003")) {
267
+ return {
268
+ success: true,
269
+ chat_id: chatId,
270
+ in_chat: false,
271
+ error: "Bot is not in this chat",
272
+ };
273
+ }
274
+ throw err;
275
+ }
276
+ }
277
+
278
+ async function sendMessage(client: ChatClient, chatId: string, content: string) {
279
+ const res = await runChatApiCall("im.message.create", () =>
280
+ (client as any).im.message.create({
281
+ params: { receive_id_type: "chat_id" },
282
+ data: {
283
+ receive_id: chatId,
284
+ msg_type: "text",
285
+ content: JSON.stringify({ text: content }),
286
+ },
287
+ }),
288
+ );
289
+
290
+ return {
291
+ success: true,
292
+ message_id: (res as any).data?.message_id,
293
+ ...(res as any).data,
294
+ };
295
+ }
296
+
297
+ async function createSessionChat(
298
+ client: ChatClient,
299
+ name: string,
300
+ userIds: string[],
301
+ greeting?: string,
302
+ description?: string,
303
+ ) {
304
+ // Step 1: Create the chat
305
+ const createResult = await createChat(client, name, userIds, description);
306
+ const chatId = createResult.chat_id;
307
+
308
+ if (!chatId) {
309
+ return {
310
+ success: false,
311
+ error: "Failed to create chat - no chat_id returned",
312
+ create_result: createResult,
313
+ };
314
+ }
315
+
316
+ // Step 2: Send greeting message
317
+ const defaultGreeting = "Hello! I've created this group chat for us to collaborate.";
318
+ const greetingMessage = greeting || defaultGreeting;
319
+
320
+ let messageResult;
321
+ try {
322
+ messageResult = await sendMessage(client, chatId, greetingMessage);
323
+ } catch (err: any) {
324
+ // Even if message fails, the chat was created successfully
325
+ return {
326
+ success: true,
327
+ chat_id: chatId,
328
+ create_result: createResult,
329
+ message_error: err?.message || "Failed to send greeting message",
330
+ };
331
+ }
332
+
333
+ return {
334
+ success: true,
335
+ chat_id: chatId,
336
+ create_result: createResult,
337
+ message_result: messageResult,
338
+ };
339
+ }
340
+
341
+ async function deleteChat(client: ChatClient, chatId: string) {
342
+ const res = await runChatApiCall("im.chat.delete", () =>
343
+ (client as any).im.chat.delete({
344
+ path: { chat_id: chatId },
345
+ }),
346
+ );
347
+
348
+ return {
349
+ success: true,
350
+ chat_id: chatId,
351
+ message: "Chat has been successfully disbanded/deleted",
352
+ ...(res as any).data,
353
+ };
354
+ }
355
+
356
+ // Main action handler - MUST BE EXPORTED
357
+ export async function runChatAction(client: ChatClient, params: FeishuChatParams) {
358
+ switch (params.action) {
359
+ case "get_announcement_info":
360
+ case "get_announcement":
361
+ return getAnnouncement(client, params.chat_id);
362
+ case "list_announcement_blocks":
363
+ return listAnnouncementBlocks(client, params.chat_id);
364
+ case "get_announcement_block":
365
+ return getAnnouncementBlock(client, params.chat_id, params.block_id);
366
+ case "write_announcement": {
367
+ const current = await getAnnouncement(client, params.chat_id);
368
+ if (current.announcement_type === "doc") {
369
+ return writeDocAnnouncement(client, params.chat_id, params.content);
370
+ } else {
371
+ // For docx announcements, append a text block under the Page root block.
372
+ // Full replacement is not supported via API; use update_announcement_block to edit existing blocks.
373
+ const blocks: any[] = (current as any).blocks ?? [];
374
+ const pageBlock = blocks.find((b: any) => b.block_type === 1);
375
+ if (!pageBlock?.block_id) {
376
+ return { error: "Could not find the Page root block for docx announcement. Use list_announcement_blocks to inspect the structure." };
377
+ }
378
+ return createTextBlock(client, params.chat_id, pageBlock.block_id, params.content);
379
+ }
380
+ }
381
+ case "append_announcement": {
382
+ const current = await getAnnouncement(client, params.chat_id);
383
+ if (current.announcement_type === "doc") {
384
+ const existingContent = (current as any).content || "";
385
+ const newContent = existingContent + "\n" + params.content;
386
+ return writeDocAnnouncement(client, params.chat_id, newContent);
387
+ } else {
388
+ // For docx format, the parent block must be the Page root block (block_type: 1)
389
+ const blocks: any[] = (current as any).blocks ?? [];
390
+ const pageBlock = blocks.find((b: any) => b.block_type === 1);
391
+ if (!pageBlock?.block_id) {
392
+ return { error: "Could not find the Page root block for docx announcement. Use list_announcement_blocks to inspect the structure." };
393
+ }
394
+ return createTextBlock(client, params.chat_id, pageBlock.block_id, params.content);
395
+ }
396
+ }
397
+ case "update_announcement_block": {
398
+ const requests = [
399
+ {
400
+ block_id: params.block_id,
401
+ update_text_elements: {
402
+ elements: [{ text_run: { content: params.content } }],
403
+ },
404
+ },
405
+ ];
406
+ return batchUpdateAnnouncementBlocks(client, params.chat_id, requests);
407
+ }
408
+ // ============== New Chat Management Actions ==============
409
+ case "create_chat": {
410
+ return createChat(client, params.name, params.user_ids, params.description);
411
+ }
412
+ case "add_members": {
413
+ return addMembers(client, params.chat_id, params.user_ids);
414
+ }
415
+ case "check_bot_in_chat": {
416
+ return checkBotInChat(client, params.chat_id);
417
+ }
418
+ case "delete_chat": {
419
+ return deleteChat(client, params.chat_id);
420
+ }
421
+ case "create_session_chat": {
422
+ return createSessionChat(
423
+ client,
424
+ params.name,
425
+ params.user_ids,
426
+ params.greeting,
427
+ params.description,
428
+ );
429
+ }
430
+ default:
431
+ return { error: `Unknown action: ${(params as any).action}` };
432
+ }
433
+ }
@@ -0,0 +1,13 @@
1
+ import { createFeishuClient } from "../client.js";
2
+ import { errorResult, json, runFeishuApiCall, type FeishuApiResponse } from "../tools-common/feishu-api.js";
3
+
4
+ export type ChatClient = ReturnType<typeof createFeishuClient>;
5
+
6
+ export { json, errorResult };
7
+
8
+ export async function runChatApiCall<T extends FeishuApiResponse>(
9
+ context: string,
10
+ fn: () => Promise<T>,
11
+ ): Promise<T> {
12
+ return runFeishuApiCall(context, fn);
13
+ }
@@ -0,0 +1,2 @@
1
+ export { registerFeishuChatTools } from "./register.js";
2
+ export { FeishuChatSchema, type FeishuChatParams } from "./schemas.js";
@@ -0,0 +1,72 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
+ import type { ResolvedFeishuAccount } from "../types.js";
3
+ import { hasFeishuToolEnabledForAnyAccount, withFeishuToolClient } from "../tools-common/tool-exec.js";
4
+ import { runChatAction } from "./actions.js";
5
+ import { errorResult, json, type ChatClient } from "./common.js";
6
+ import { FeishuChatSchema, type FeishuChatParams } from "./schemas.js";
7
+
8
+ type ChatToolSpec<P> = {
9
+ name: string;
10
+ label: string;
11
+ description: string;
12
+ parameters: any;
13
+ requiredTool?: "chat";
14
+ run: (args: { client: ChatClient; account: ResolvedFeishuAccount }, params: P) => Promise<unknown>;
15
+ };
16
+
17
+ function registerChatTool<P>(api: OpenClawPluginApi, spec: ChatToolSpec<P>) {
18
+ api.registerTool(
19
+ {
20
+ name: spec.name,
21
+ label: spec.label,
22
+ description: spec.description,
23
+ parameters: spec.parameters,
24
+ async execute(_toolCallId, params) {
25
+ try {
26
+ return await withFeishuToolClient({
27
+ api,
28
+ toolName: spec.name,
29
+ requiredTool: spec.requiredTool,
30
+ run: async ({ client, account }) =>
31
+ json(await spec.run({ client: client as ChatClient, account }, params as P)),
32
+ });
33
+ } catch (err) {
34
+ return errorResult(err);
35
+ }
36
+ },
37
+ },
38
+ { name: spec.name },
39
+ );
40
+ }
41
+
42
+ export function registerFeishuChatTools(api: OpenClawPluginApi) {
43
+ if (!api.config) {
44
+ api.logger.debug?.("feishu_chat: No config available, skipping chat tools");
45
+ return;
46
+ }
47
+
48
+ if (!hasFeishuToolEnabledForAnyAccount(api.config)) {
49
+ api.logger.debug?.("feishu_chat: No Feishu accounts configured, skipping chat tools");
50
+ return;
51
+ }
52
+
53
+ const chatEnabled = hasFeishuToolEnabledForAnyAccount(api.config, "chat");
54
+ const registered: string[] = [];
55
+
56
+ if (chatEnabled) {
57
+ registerChatTool<FeishuChatParams>(api, {
58
+ name: "feishu_chat",
59
+ label: "Feishu Chat",
60
+ description:
61
+ "Feishu chat operations. Actions: get_announcement, get_announcement_info, list_announcement_blocks, get_announcement_block, write_announcement, append_announcement, update_announcement_block, create_chat, add_members, check_bot_in_chat, create_session_chat, delete_chat. Use to manage group chats and announcements.",
62
+ parameters: FeishuChatSchema,
63
+ requiredTool: "chat",
64
+ run: async ({ client }, params) => runChatAction(client, params),
65
+ });
66
+ registered.push("feishu_chat");
67
+ }
68
+
69
+ if (registered.length > 0) {
70
+ api.logger.debug?.(`feishu_chat: Registered ${registered.join(", ")}`);
71
+ }
72
+ }
@@ -0,0 +1,66 @@
1
+ import { Type, type Static } from "@sinclair/typebox";
2
+
3
+ export const FeishuChatSchema = Type.Union([
4
+ Type.Object({
5
+ action: Type.Literal("get_announcement_info"),
6
+ chat_id: Type.String({ description: "Chat ID to get announcement from" }),
7
+ }),
8
+ Type.Object({
9
+ action: Type.Literal("get_announcement"),
10
+ chat_id: Type.String({ description: "Chat ID to get announcement from" }),
11
+ }),
12
+ Type.Object({
13
+ action: Type.Literal("write_announcement"),
14
+ chat_id: Type.String({ description: "Chat ID to write announcement to" }),
15
+ content: Type.String({ description: "Content to write. For doc format: replaces the entire announcement. For docx format: appends a new text block under the page root." }),
16
+ }),
17
+ Type.Object({
18
+ action: Type.Literal("append_announcement"),
19
+ chat_id: Type.String({ description: "Chat ID to append announcement to" }),
20
+ content: Type.String({ description: "Markdown content to append to announcement" }),
21
+ }),
22
+ Type.Object({
23
+ action: Type.Literal("list_announcement_blocks"),
24
+ chat_id: Type.String({ description: "Chat ID to list announcement blocks from" }),
25
+ }),
26
+ Type.Object({
27
+ action: Type.Literal("get_announcement_block"),
28
+ chat_id: Type.String({ description: "Chat ID to get announcement block from" }),
29
+ block_id: Type.String({ description: "Block ID (from list_announcement_blocks)" }),
30
+ }),
31
+ Type.Object({
32
+ action: Type.Literal("update_announcement_block"),
33
+ chat_id: Type.String({ description: "Chat ID to update announcement block in" }),
34
+ block_id: Type.String({ description: "Block ID (from list_announcement_blocks)" }),
35
+ content: Type.String({ description: "New text content" }),
36
+ }),
37
+ // ============== New Chat Management Actions ==============
38
+ Type.Object({
39
+ action: Type.Literal("create_chat"),
40
+ name: Type.String({ description: "Group chat name" }),
41
+ user_ids: Type.Optional(Type.Array(Type.String(), { description: "List of user IDs to add to the group" })),
42
+ description: Type.Optional(Type.String({ description: "Group chat description" })),
43
+ }),
44
+ Type.Object({
45
+ action: Type.Literal("add_members"),
46
+ chat_id: Type.String({ description: "Chat ID to add members to" }),
47
+ user_ids: Type.Array(Type.String(), { description: "List of user IDs to add" }),
48
+ }),
49
+ Type.Object({
50
+ action: Type.Literal("check_bot_in_chat"),
51
+ chat_id: Type.String({ description: "Chat ID to check" }),
52
+ }),
53
+ Type.Object({
54
+ action: Type.Literal("delete_chat"),
55
+ chat_id: Type.String({ description: "Chat ID to delete/dismiss" }),
56
+ }),
57
+ Type.Object({
58
+ action: Type.Literal("create_session_chat"),
59
+ name: Type.String({ description: "Session group name" }),
60
+ user_ids: Type.Array(Type.String(), { description: "List of user IDs to invite" }),
61
+ greeting: Type.Optional(Type.String({ description: "Greeting message to send (default: Hello! I've created this group chat for us to collaborate.)" })),
62
+ description: Type.Optional(Type.String({ description: "Group description" })),
63
+ }),
64
+ ]);
65
+
66
+ export type FeishuChatParams = Static<typeof FeishuChatSchema>;
package/src/client.ts CHANGED
@@ -1,6 +1,17 @@
1
1
  import * as Lark from "@larksuiteoapi/node-sdk";
2
+ import { HttpsProxyAgent } from "https-proxy-agent";
2
3
  import type { FeishuDomain, ResolvedFeishuAccount } from "./types.js";
3
4
 
5
+ function getWsProxyAgent(): HttpsProxyAgent<string> | undefined {
6
+ const proxyUrl =
7
+ process.env.https_proxy ||
8
+ process.env.HTTPS_PROXY ||
9
+ process.env.http_proxy ||
10
+ process.env.HTTP_PROXY;
11
+ if (!proxyUrl) return undefined;
12
+ return new HttpsProxyAgent(proxyUrl);
13
+ }
14
+
4
15
  // Multi-account client cache
5
16
  const clientCache = new Map<
6
17
  string,
@@ -77,11 +88,13 @@ export function createFeishuWSClient(account: ResolvedFeishuAccount): Lark.WSCli
77
88
  throw new Error(`Feishu credentials not configured for account "${accountId}"`);
78
89
  }
79
90
 
91
+ const agent = getWsProxyAgent();
80
92
  return new Lark.WSClient({
81
93
  appId,
82
94
  appSecret,
83
95
  domain: resolveDomain(domain),
84
96
  loggerLevel: Lark.LoggerLevel.info,
97
+ ...(agent ? { agent } : {}),
85
98
  });
86
99
  }
87
100
 
@@ -4,6 +4,7 @@ export { z };
4
4
  const DmPolicySchema = z.enum(["open", "pairing", "allowlist"]);
5
5
  const GroupPolicySchema = z.enum(["open", "allowlist", "disabled"]);
6
6
  const GroupCommandMentionBypassSchema = z.enum(["never", "single_bot", "always"]).optional();
7
+ const AllowMentionlessInMultiBotGroupSchema = z.boolean().optional();
7
8
  const FeishuDomainSchema = z.union([
8
9
  z.enum(["feishu", "lark"]),
9
10
  z.string().url().startsWith("https://"),
@@ -88,6 +89,8 @@ const FeishuToolsConfigSchema = z
88
89
  perm: z.boolean().optional(), // Permission management (default: false, sensitive)
89
90
  scopes: z.boolean().optional(), // App scopes diagnostic (default: true)
90
91
  task: z.boolean().optional(), // Task operations (default: true)
92
+ chat: z.boolean().optional(), // Chat management operations (default: true)
93
+ urgent: z.boolean().optional(), // Buzz/urgent notifications (default: true)
91
94
  })
92
95
  .strict()
93
96
  .optional();
@@ -106,6 +109,7 @@ export const FeishuGroupSchema = z
106
109
  .object({
107
110
  requireMention: z.boolean().optional(),
108
111
  groupCommandMentionBypass: GroupCommandMentionBypassSchema,
112
+ allowMentionlessInMultiBotGroup: AllowMentionlessInMultiBotGroupSchema,
109
113
  tools: ToolPolicySchema,
110
114
  skills: z.array(z.string()).optional(),
111
115
  enabled: z.boolean().optional(),
@@ -140,6 +144,7 @@ export const FeishuAccountConfigSchema = z
140
144
  groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
141
145
  requireMention: z.boolean().optional(),
142
146
  groupCommandMentionBypass: GroupCommandMentionBypassSchema,
147
+ allowMentionlessInMultiBotGroup: AllowMentionlessInMultiBotGroupSchema,
143
148
  groups: z.record(z.string(), FeishuGroupSchema.optional()).optional(),
144
149
  historyLimit: z.number().int().min(0).optional(),
145
150
  dmHistoryLimit: z.number().int().min(0).optional(),
@@ -177,6 +182,7 @@ export const FeishuConfigSchema = z
177
182
  groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
178
183
  requireMention: z.boolean().optional().default(true),
179
184
  groupCommandMentionBypass: GroupCommandMentionBypassSchema.default("single_bot"),
185
+ allowMentionlessInMultiBotGroup: AllowMentionlessInMultiBotGroupSchema.default(false),
180
186
  groups: z.record(z.string(), FeishuGroupSchema.optional()).optional(),
181
187
  topicSessionMode: TopicSessionModeSchema,
182
188
  historyLimit: z.number().int().min(0).optional(),