@m1heng-clawd/feishu 0.1.9 → 0.1.11

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/docx.ts CHANGED
@@ -1,11 +1,9 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
3
- import { createFeishuClient } from "./client.js";
4
- import { resolveFeishuAccount, listEnabledFeishuAccounts } from "./accounts.js";
5
3
  import type * as Lark from "@larksuiteoapi/node-sdk";
6
- import { Readable } from "stream";
7
4
  import { FeishuDocSchema, type FeishuDocParams } from "./doc-schema.js";
8
- import { resolveToolsConfig } from "./tools-config.js";
5
+ import { appendDoc, createAndWriteDoc, createDoc, writeDoc } from "./doc-write-service.js";
6
+ import { hasFeishuToolEnabledForAnyAccount, withFeishuToolClient } from "./tools-common/tool-exec.js";
9
7
 
10
8
  // ============ Helpers ============
11
9
 
@@ -16,20 +14,6 @@ function json(data: unknown) {
16
14
  };
17
15
  }
18
16
 
19
- /** Extract image URLs from markdown content */
20
- function extractImageUrls(markdown: string): string[] {
21
- const regex = /!\[[^\]]*\]\(([^)]+)\)/g;
22
- const urls: string[] = [];
23
- let match;
24
- while ((match = regex.exec(markdown)) !== null) {
25
- const url = match[1].trim();
26
- if (url.startsWith("http://") || url.startsWith("https://")) {
27
- urls.push(url);
28
- }
29
- }
30
- return urls;
31
- }
32
-
33
17
  const BLOCK_TYPE_NAMES: Record<number, string> = {
34
18
  1: "Page",
35
19
  2: "Text",
@@ -51,160 +35,10 @@ const BLOCK_TYPE_NAMES: Record<number, string> = {
51
35
  32: "TableCell",
52
36
  };
53
37
 
54
- // Block types that cannot be created via documentBlockChildren.create API
55
- const UNSUPPORTED_CREATE_TYPES = new Set([31, 32]);
56
-
57
- /** Clean blocks for insertion (remove unsupported types and read-only fields) */
58
- function cleanBlocksForInsert(blocks: any[]): { cleaned: any[]; skipped: string[] } {
59
- const skipped: string[] = [];
60
- const cleaned = blocks
61
- .filter((block) => {
62
- if (UNSUPPORTED_CREATE_TYPES.has(block.block_type)) {
63
- const typeName = BLOCK_TYPE_NAMES[block.block_type] || `type_${block.block_type}`;
64
- skipped.push(typeName);
65
- return false;
66
- }
67
- return true;
68
- })
69
- .map((block) => {
70
- if (block.block_type === 31 && block.table?.merge_info) {
71
- const { merge_info, ...tableRest } = block.table;
72
- return { ...block, table: tableRest };
73
- }
74
- return block;
75
- });
76
- return { cleaned, skipped };
77
- }
78
-
79
- // ============ Core Functions ============
80
-
81
- async function convertMarkdown(client: Lark.Client, markdown: string) {
82
- const res = await client.docx.document.convert({
83
- data: { content_type: "markdown", content: markdown },
84
- });
85
- if (res.code !== 0) throw new Error(res.msg);
86
- return {
87
- blocks: res.data?.blocks ?? [],
88
- firstLevelBlockIds: res.data?.first_level_block_ids ?? [],
89
- };
90
- }
91
-
92
- async function insertBlocks(
93
- client: Lark.Client,
94
- docToken: string,
95
- blocks: any[],
96
- parentBlockId?: string,
97
- ): Promise<{ children: any[]; skipped: string[] }> {
98
- const { cleaned, skipped } = cleanBlocksForInsert(blocks);
99
- const blockId = parentBlockId ?? docToken;
100
-
101
- if (cleaned.length === 0) {
102
- return { children: [], skipped };
103
- }
104
-
105
- const res = await client.docx.documentBlockChildren.create({
106
- path: { document_id: docToken, block_id: blockId },
107
- data: { children: cleaned },
108
- });
109
- if (res.code !== 0) throw new Error(res.msg);
110
- return { children: res.data?.children ?? [], skipped };
111
- }
112
-
113
- async function clearDocumentContent(client: Lark.Client, docToken: string) {
114
- const existing = await client.docx.documentBlock.list({
115
- path: { document_id: docToken },
116
- });
117
- if (existing.code !== 0) throw new Error(existing.msg);
118
-
119
- const childIds =
120
- existing.data?.items
121
- ?.filter((b) => b.parent_id === docToken && b.block_type !== 1)
122
- .map((b) => b.block_id) ?? [];
123
-
124
- if (childIds.length > 0) {
125
- const res = await client.docx.documentBlockChildren.batchDelete({
126
- path: { document_id: docToken, block_id: docToken },
127
- data: { start_index: 0, end_index: childIds.length },
128
- });
129
- if (res.code !== 0) throw new Error(res.msg);
130
- }
131
-
132
- return childIds.length;
133
- }
134
-
135
- async function uploadImageToDocx(
136
- client: Lark.Client,
137
- blockId: string,
138
- imageBuffer: Buffer,
139
- fileName: string,
140
- ): Promise<string> {
141
- const res = await client.drive.media.uploadAll({
142
- data: {
143
- file_name: fileName,
144
- parent_type: "docx_image",
145
- parent_node: blockId,
146
- size: imageBuffer.length,
147
- file: Readable.from(imageBuffer) as any,
148
- },
149
- });
150
-
151
- const fileToken = res?.file_token;
152
- if (!fileToken) {
153
- throw new Error("Image upload failed: no file_token returned");
154
- }
155
- return fileToken;
156
- }
157
-
158
- async function downloadImage(url: string): Promise<Buffer> {
159
- const response = await fetch(url);
160
- if (!response.ok) {
161
- throw new Error(`Failed to download image: ${response.status} ${response.statusText}`);
162
- }
163
- return Buffer.from(await response.arrayBuffer());
164
- }
165
-
166
- async function processImages(
167
- client: Lark.Client,
168
- docToken: string,
169
- markdown: string,
170
- insertedBlocks: any[],
171
- ): Promise<number> {
172
- const imageUrls = extractImageUrls(markdown);
173
- if (imageUrls.length === 0) return 0;
174
-
175
- const imageBlocks = insertedBlocks.filter((b) => b.block_type === 27);
176
-
177
- let processed = 0;
178
- for (let i = 0; i < Math.min(imageUrls.length, imageBlocks.length); i++) {
179
- const url = imageUrls[i];
180
- const blockId = imageBlocks[i].block_id;
181
-
182
- try {
183
- const buffer = await downloadImage(url);
184
- const urlPath = new URL(url).pathname;
185
- const fileName = urlPath.split("/").pop() || `image_${i}.png`;
186
- const fileToken = await uploadImageToDocx(client, blockId, buffer, fileName);
187
-
188
- await client.docx.documentBlock.patch({
189
- path: { document_id: docToken, block_id: blockId },
190
- data: {
191
- replace_image: { token: fileToken },
192
- },
193
- });
194
-
195
- processed++;
196
- } catch (err) {
197
- console.error(`Failed to process image ${url}:`, err);
198
- }
199
- }
200
-
201
- return processed;
202
- }
38
+ const STRUCTURED_BLOCK_TYPES = new Set([14, 18, 21, 23, 27, 30, 31, 32]);
203
39
 
204
40
  // ============ Actions ============
205
41
 
206
- const STRUCTURED_BLOCK_TYPES = new Set([14, 18, 21, 23, 27, 30, 31, 32]);
207
-
208
42
  async function readDoc(client: Lark.Client, docToken: string) {
209
43
  const [contentRes, infoRes, blocksRes] = await Promise.all([
210
44
  client.docx.document.rawContent({ path: { document_id: docToken } }),
@@ -243,61 +77,6 @@ async function readDoc(client: Lark.Client, docToken: string) {
243
77
  };
244
78
  }
245
79
 
246
- async function createDoc(client: Lark.Client, title: string, folderToken?: string) {
247
- const res = await client.docx.document.create({
248
- data: { title, folder_token: folderToken },
249
- });
250
- if (res.code !== 0) throw new Error(res.msg);
251
- const doc = res.data?.document;
252
- return {
253
- document_id: doc?.document_id,
254
- title: doc?.title,
255
- url: `https://feishu.cn/docx/${doc?.document_id}`,
256
- };
257
- }
258
-
259
- async function writeDoc(client: Lark.Client, docToken: string, markdown: string) {
260
- const deleted = await clearDocumentContent(client, docToken);
261
-
262
- const { blocks } = await convertMarkdown(client, markdown);
263
- if (blocks.length === 0) {
264
- return { success: true, blocks_deleted: deleted, blocks_added: 0, images_processed: 0 };
265
- }
266
-
267
- const { children: inserted, skipped } = await insertBlocks(client, docToken, blocks);
268
- const imagesProcessed = await processImages(client, docToken, markdown, inserted);
269
-
270
- return {
271
- success: true,
272
- blocks_deleted: deleted,
273
- blocks_added: inserted.length,
274
- images_processed: imagesProcessed,
275
- ...(skipped.length > 0 && {
276
- warning: `Skipped unsupported block types: ${skipped.join(", ")}. Tables are not supported via this API.`,
277
- }),
278
- };
279
- }
280
-
281
- async function appendDoc(client: Lark.Client, docToken: string, markdown: string) {
282
- const { blocks } = await convertMarkdown(client, markdown);
283
- if (blocks.length === 0) {
284
- throw new Error("Content is empty");
285
- }
286
-
287
- const { children: inserted, skipped } = await insertBlocks(client, docToken, blocks);
288
- const imagesProcessed = await processImages(client, docToken, markdown, inserted);
289
-
290
- return {
291
- success: true,
292
- blocks_added: inserted.length,
293
- images_processed: imagesProcessed,
294
- block_ids: inserted.map((b: any) => b.block_id),
295
- ...(skipped.length > 0 && {
296
- warning: `Skipped unsupported block types: ${skipped.join(", ")}. Tables are not supported via this API.`,
297
- }),
298
- };
299
- }
300
-
301
80
  async function updateBlock(
302
81
  client: Lark.Client,
303
82
  docToken: string,
@@ -393,88 +172,105 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
393
172
  return;
394
173
  }
395
174
 
396
- // Check if any account is configured
397
- const accounts = listEnabledFeishuAccounts(api.config);
398
- if (accounts.length === 0) {
175
+ if (!hasFeishuToolEnabledForAnyAccount(api.config)) {
399
176
  api.logger.debug?.("feishu_doc: No Feishu accounts configured, skipping doc tools");
400
177
  return;
401
178
  }
402
179
 
403
- // Use first account's config for tools configuration
404
- const firstAccount = accounts[0];
405
- const toolsCfg = resolveToolsConfig(firstAccount.config.tools);
406
-
407
- // Helper to get client for the default account
408
- const getClient = () => createFeishuClient(firstAccount);
180
+ // Registration happens once; account selection happens per execute() call.
181
+ const docEnabled = hasFeishuToolEnabledForAnyAccount(api.config, "doc");
182
+ const scopesEnabled = hasFeishuToolEnabledForAnyAccount(api.config, "scopes");
409
183
  const registered: string[] = [];
410
184
 
411
185
  // Main document tool with action-based dispatch
412
- if (toolsCfg.doc) {
186
+ if (docEnabled) {
413
187
  api.registerTool(
414
- {
415
- name: "feishu_doc",
416
- label: "Feishu Doc",
417
- description:
418
- "Feishu document operations. Actions: read, write, append, create, list_blocks, get_block, update_block, delete_block",
419
- parameters: FeishuDocSchema,
420
- async execute(_toolCallId, params) {
421
- const p = params as FeishuDocParams;
422
- try {
423
- const client = getClient();
424
- switch (p.action) {
425
- case "read":
426
- return json(await readDoc(client, p.doc_token));
427
- case "write":
428
- return json(await writeDoc(client, p.doc_token, p.content));
429
- case "append":
430
- return json(await appendDoc(client, p.doc_token, p.content));
431
- case "create":
432
- return json(await createDoc(client, p.title, p.folder_token));
433
- case "list_blocks":
434
- return json(await listBlocks(client, p.doc_token));
435
- case "get_block":
436
- return json(await getBlock(client, p.doc_token, p.block_id));
437
- case "update_block":
438
- return json(await updateBlock(client, p.doc_token, p.block_id, p.content));
439
- case "delete_block":
440
- return json(await deleteBlock(client, p.doc_token, p.block_id));
441
- default:
442
- return json({ error: `Unknown action: ${(p as any).action}` });
188
+ {
189
+ name: "feishu_doc",
190
+ label: "Feishu Doc",
191
+ description:
192
+ 'Feishu document operations. Actions: read, write, append, create, create_and_write, list_blocks, get_block, update_block, delete_block. Use "create_and_write" for atomic create + content write.',
193
+ parameters: FeishuDocSchema,
194
+ async execute(_toolCallId, params) {
195
+ const p = params as FeishuDocParams;
196
+ try {
197
+ return await withFeishuToolClient({
198
+ api,
199
+ toolName: "feishu_doc",
200
+ requiredTool: "doc",
201
+ run: async ({ client, account }) => {
202
+ const mediaMaxBytes = (account.config?.mediaMaxMb ?? 30) * 1024 * 1024;
203
+ switch (p.action) {
204
+ case "read":
205
+ return json(await readDoc(client, p.doc_token));
206
+ case "write":
207
+ return json(await writeDoc(client, p.doc_token, p.content, mediaMaxBytes));
208
+ case "append":
209
+ return json(await appendDoc(client, p.doc_token, p.content, mediaMaxBytes));
210
+ case "create":
211
+ return json(await createDoc(client, p.title, p.folder_token));
212
+ case "create_and_write":
213
+ return json(
214
+ await createAndWriteDoc(
215
+ client,
216
+ p.title,
217
+ p.content,
218
+ mediaMaxBytes,
219
+ p.folder_token,
220
+ ),
221
+ );
222
+ case "list_blocks":
223
+ return json(await listBlocks(client, p.doc_token));
224
+ case "get_block":
225
+ return json(await getBlock(client, p.doc_token, p.block_id));
226
+ case "update_block":
227
+ return json(await updateBlock(client, p.doc_token, p.block_id, p.content));
228
+ case "delete_block":
229
+ return json(await deleteBlock(client, p.doc_token, p.block_id));
230
+ default:
231
+ return json({ error: `Unknown action: ${(p as any).action}` });
232
+ }
233
+ },
234
+ });
235
+ } catch (err) {
236
+ return json({ error: err instanceof Error ? err.message : String(err) });
443
237
  }
444
- } catch (err) {
445
- return json({ error: err instanceof Error ? err.message : String(err) });
446
- }
238
+ },
447
239
  },
448
- },
449
- { name: "feishu_doc" },
450
- );
240
+ { name: "feishu_doc" },
241
+ );
451
242
  registered.push("feishu_doc");
452
243
  }
453
244
 
454
245
  // Keep feishu_app_scopes as independent tool
455
- if (toolsCfg.scopes) {
246
+ if (scopesEnabled) {
456
247
  api.registerTool(
457
- {
458
- name: "feishu_app_scopes",
459
- label: "Feishu App Scopes",
460
- description:
461
- "List current app permissions (scopes). Use to debug permission issues or check available capabilities.",
462
- parameters: Type.Object({}),
463
- async execute() {
464
- try {
465
- const result = await listAppScopes(getClient());
466
- return json(result);
467
- } catch (err) {
468
- return json({ error: err instanceof Error ? err.message : String(err) });
469
- }
248
+ {
249
+ name: "feishu_app_scopes",
250
+ label: "Feishu App Scopes",
251
+ description:
252
+ "List current app permissions (scopes). Use to debug permission issues or check available capabilities.",
253
+ parameters: Type.Object({}),
254
+ async execute() {
255
+ try {
256
+ const result = await withFeishuToolClient({
257
+ api,
258
+ toolName: "feishu_app_scopes",
259
+ requiredTool: "scopes",
260
+ run: async ({ client }) => listAppScopes(client),
261
+ });
262
+ return json(result);
263
+ } catch (err) {
264
+ return json({ error: err instanceof Error ? err.message : String(err) });
265
+ }
266
+ },
470
267
  },
471
- },
472
- { name: "feishu_app_scopes" },
473
- );
268
+ { name: "feishu_app_scopes" },
269
+ );
474
270
  registered.push("feishu_app_scopes");
475
271
  }
476
272
 
477
273
  if (registered.length > 0) {
478
- api.logger.info?.(`feishu_doc: Registered ${registered.join(", ")}`);
274
+ api.logger.debug?.(`feishu_doc: Registered ${registered.join(", ")}`);
479
275
  }
480
276
  }
@@ -11,6 +11,11 @@ const FileType = Type.Union([
11
11
  Type.Literal("shortcut"),
12
12
  ]);
13
13
 
14
+ const DocType = Type.Union([
15
+ Type.Literal("docx", { description: "New generation document (default)" }),
16
+ Type.Literal("doc", { description: "Legacy document" }),
17
+ ]);
18
+
14
19
  export const FeishuDriveSchema = Type.Union([
15
20
  Type.Object({
16
21
  action: Type.Literal("list"),
@@ -41,6 +46,21 @@ export const FeishuDriveSchema = Type.Union([
41
46
  file_token: Type.String({ description: "File token to delete" }),
42
47
  type: FileType,
43
48
  }),
49
+ Type.Object({
50
+ action: Type.Literal("import_document"),
51
+ title: Type.String({
52
+ description: "Document title",
53
+ }),
54
+ content: Type.String({
55
+ description: "Markdown content to import. Supports full Markdown syntax including tables, lists, code blocks, etc.",
56
+ }),
57
+ folder_token: Type.Optional(
58
+ Type.String({
59
+ description: "Target folder token (optional, defaults to root). Use 'list' to find folder tokens.",
60
+ }),
61
+ ),
62
+ doc_type: Type.Optional(DocType),
63
+ }),
44
64
  ]);
45
65
 
46
66
  export type FeishuDriveParams = Static<typeof FeishuDriveSchema>;
package/src/drive.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
- import { createFeishuClient } from "./client.js";
3
- import { listEnabledFeishuAccounts } from "./accounts.js";
4
2
  import type * as Lark from "@larksuiteoapi/node-sdk";
5
3
  import { FeishuDriveSchema, type FeishuDriveParams } from "./drive-schema.js";
6
- import { resolveToolsConfig } from "./tools-config.js";
4
+ import { hasFeishuToolEnabledForAnyAccount, withFeishuToolClient } from "./tools-common/tool-exec.js";
5
+ import { createAndWriteDoc } from "./doc-write-service.js";
7
6
 
8
7
  // ============ Helpers ============
9
8
 
@@ -147,6 +146,24 @@ async function deleteFile(client: Lark.Client, fileToken: string, type: string)
147
146
  };
148
147
  }
149
148
 
149
+ // ============ Import Document Functions ============
150
+
151
+ /**
152
+ * Import markdown content as a new Feishu document
153
+ * Uses create + write approach for reliable content import.
154
+ * Note: docType parameter is accepted for API compatibility but docx is always used.
155
+ */
156
+ async function importDocument(
157
+ client: Lark.Client,
158
+ title: string,
159
+ content: string,
160
+ mediaMaxBytes: number,
161
+ folderToken?: string,
162
+ _docType?: "docx" | "doc",
163
+ ) {
164
+ return createAndWriteDoc(client, title, content, mediaMaxBytes, folderToken);
165
+ }
166
+
150
167
  // ============ Tool Registration ============
151
168
 
152
169
  export function registerFeishuDriveTools(api: OpenClawPluginApi) {
@@ -155,46 +172,59 @@ export function registerFeishuDriveTools(api: OpenClawPluginApi) {
155
172
  return;
156
173
  }
157
174
 
158
- const accounts = listEnabledFeishuAccounts(api.config);
159
- if (accounts.length === 0) {
175
+ if (!hasFeishuToolEnabledForAnyAccount(api.config)) {
160
176
  api.logger.debug?.("feishu_drive: No Feishu accounts configured, skipping drive tools");
161
177
  return;
162
178
  }
163
179
 
164
- const firstAccount = accounts[0];
165
- const toolsCfg = resolveToolsConfig(firstAccount.config.tools);
166
- if (!toolsCfg.drive) {
180
+ if (!hasFeishuToolEnabledForAnyAccount(api.config, "drive")) {
167
181
  api.logger.debug?.("feishu_drive: drive tool disabled in config");
168
182
  return;
169
183
  }
170
184
 
171
- const getClient = () => createFeishuClient(firstAccount);
172
-
173
185
  api.registerTool(
174
186
  {
175
187
  name: "feishu_drive",
176
188
  label: "Feishu Drive",
177
189
  description:
178
- "Feishu cloud storage operations. Actions: list, info, create_folder, move, delete",
190
+ "Feishu cloud storage operations. Actions: list, info, create_folder, move, delete, import_document. Use 'import_document' to create documents from Markdown with better structure preservation than block-by-block writing.",
179
191
  parameters: FeishuDriveSchema,
180
192
  async execute(_toolCallId, params) {
181
193
  const p = params as FeishuDriveParams;
182
194
  try {
183
- const client = getClient();
184
- switch (p.action) {
185
- case "list":
186
- return json(await listFolder(client, p.folder_token));
187
- case "info":
188
- return json(await getFileInfo(client, p.file_token));
189
- case "create_folder":
190
- return json(await createFolder(client, p.name, p.folder_token));
191
- case "move":
192
- return json(await moveFile(client, p.file_token, p.type, p.folder_token));
193
- case "delete":
194
- return json(await deleteFile(client, p.file_token, p.type));
195
- default:
196
- return json({ error: `Unknown action: ${(p as any).action}` });
197
- }
195
+ return await withFeishuToolClient({
196
+ api,
197
+ toolName: "feishu_drive",
198
+ requiredTool: "drive",
199
+ run: async ({ client, account }) => {
200
+ const mediaMaxBytes = (account.config?.mediaMaxMb ?? 30) * 1024 * 1024;
201
+ switch (p.action) {
202
+ case "list":
203
+ return json(await listFolder(client, p.folder_token));
204
+ case "info":
205
+ return json(await getFileInfo(client, p.file_token));
206
+ case "create_folder":
207
+ return json(await createFolder(client, p.name, p.folder_token));
208
+ case "move":
209
+ return json(await moveFile(client, p.file_token, p.type, p.folder_token));
210
+ case "delete":
211
+ return json(await deleteFile(client, p.file_token, p.type));
212
+ case "import_document":
213
+ return json(
214
+ await importDocument(
215
+ client,
216
+ p.title,
217
+ p.content,
218
+ mediaMaxBytes,
219
+ p.folder_token,
220
+ (p as any).doc_type || "docx",
221
+ ),
222
+ );
223
+ default:
224
+ return json({ error: `Unknown action: ${(p as any).action}` });
225
+ }
226
+ },
227
+ });
198
228
  } catch (err) {
199
229
  return json({ error: err instanceof Error ? err.message : String(err) });
200
230
  }
@@ -203,5 +233,5 @@ export function registerFeishuDriveTools(api: OpenClawPluginApi) {
203
233
  { name: "feishu_drive" },
204
234
  );
205
235
 
206
- api.logger.info?.(`feishu_drive: Registered feishu_drive tool`);
236
+ api.logger.debug?.("feishu_drive: Registered feishu_drive tool");
207
237
  }
@@ -19,16 +19,18 @@ export async function maybeCreateDynamicAgent(params: {
19
19
  runtime: PluginRuntime;
20
20
  senderOpenId: string;
21
21
  dynamicCfg: DynamicAgentCreationConfig;
22
+ accountId?: string;
22
23
  log: (msg: string) => void;
23
24
  }): Promise<MaybeCreateDynamicAgentResult> {
24
- const { cfg, runtime, senderOpenId, dynamicCfg, log } = params;
25
+ const { cfg, runtime, senderOpenId, dynamicCfg, accountId, log } = params;
25
26
 
26
27
  // Check if there's already a binding for this user
27
28
  const existingBindings = cfg.bindings ?? [];
28
29
  const hasBinding = existingBindings.some(
29
30
  (b) =>
30
31
  b.match?.channel === "feishu" &&
31
- b.match?.peer?.kind === "dm" &&
32
+ (!accountId || b.match?.accountId === accountId) &&
33
+ b.match?.peer?.kind === "direct" &&
32
34
  b.match?.peer?.id === senderOpenId,
33
35
  );
34
36
 
@@ -66,7 +68,8 @@ export async function maybeCreateDynamicAgent(params: {
66
68
  agentId,
67
69
  match: {
68
70
  channel: "feishu",
69
- peer: { kind: "dm", id: senderOpenId },
71
+ ...(accountId ? { accountId } : {}),
72
+ peer: { kind: "direct", id: senderOpenId },
70
73
  },
71
74
  },
72
75
  ],
@@ -108,7 +111,8 @@ export async function maybeCreateDynamicAgent(params: {
108
111
  agentId,
109
112
  match: {
110
113
  channel: "feishu",
111
- peer: { kind: "dm", id: senderOpenId },
114
+ ...(accountId ? { accountId } : {}),
115
+ peer: { kind: "direct", id: senderOpenId },
112
116
  },
113
117
  },
114
118
  ],