feishu-user-plugin 1.3.0 → 1.3.2

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/official.js CHANGED
@@ -1,10 +1,21 @@
1
1
  const lark = require('@larksuiteoapi/node-sdk');
2
2
 
3
+ // Redirect all Lark SDK logs to stderr.
4
+ // The SDK's defaultLogger.error uses console.log (stdout), which corrupts
5
+ // MCP's JSON-RPC stdio transport and causes session disconnects.
6
+ const stderrLogger = {
7
+ error: (...msg) => console.error('[lark-sdk][error]:', ...msg),
8
+ warn: (...msg) => console.error('[lark-sdk][warn]:', ...msg),
9
+ info: () => {},
10
+ debug: () => {},
11
+ trace: () => {},
12
+ };
13
+
3
14
  class LarkOfficialClient {
4
15
  constructor(appId, appSecret) {
5
16
  this.appId = appId;
6
17
  this.appSecret = appSecret;
7
- this.client = new lark.Client({ appId, appSecret, disableTokenCache: false });
18
+ this.client = new lark.Client({ appId, appSecret, disableTokenCache: false, logger: stderrLogger, loggerLevel: lark.LoggerLevel.warn });
8
19
  this._uat = null;
9
20
  this._uatRefresh = null;
10
21
  this._uatExpires = 0;
@@ -90,6 +101,37 @@ class LarkOfficialClient {
90
101
  return data;
91
102
  }
92
103
 
104
+ // Generic UAT REST helper. Returns parsed JSON ({code, msg, data}).
105
+ async _uatREST(method, path, { body, query } = {}) {
106
+ const qs = query ? '?' + new URLSearchParams(query).toString() : '';
107
+ const url = 'https://open.feishu.cn' + path + qs;
108
+ return this._withUAT(async (uat) => {
109
+ const headers = { 'Authorization': `Bearer ${uat}` };
110
+ const init = { method, headers };
111
+ if (body !== undefined) {
112
+ headers['content-type'] = 'application/json';
113
+ init.body = JSON.stringify(body);
114
+ }
115
+ const res = await fetch(url, init);
116
+ return res.json();
117
+ });
118
+ }
119
+
120
+ // Try UAT first (for resources likely owned by the user), fall back to app SDK on failure.
121
+ // Returns SDK-shaped {code, msg, data}. Both paths yield the same shape.
122
+ async _asUserOrApp({ uatPath, method = 'GET', body, query, sdkFn, label }) {
123
+ if (this.hasUAT) {
124
+ try {
125
+ const data = await this._uatREST(method, uatPath, { body, query });
126
+ if (data.code === 0) return data;
127
+ console.error(`[feishu-user-plugin] ${label} as user failed (${data.code}: ${data.msg}), retrying as app`);
128
+ } catch (err) {
129
+ console.error(`[feishu-user-plugin] ${label} as user threw (${err.message}), retrying as app`);
130
+ }
131
+ }
132
+ return this._safeSDKCall(sdkFn, label);
133
+ }
134
+
93
135
  async listChatsAsUser({ pageSize = 20, pageToken } = {}) {
94
136
  const params = new URLSearchParams({ page_size: String(pageSize) });
95
137
  if (pageToken) params.set('page_token', pageToken);
@@ -234,20 +276,19 @@ class LarkOfficialClient {
234
276
 
235
277
  // --- IM: Pins ---
236
278
 
237
- async pinMessage(messageId) {
238
- const res = await this._safeSDKCall(
239
- () => this.client.im.pin.create({ data: { message_id: messageId } }),
240
- 'pinMessage'
241
- );
242
- return { pin: res.data.pin };
243
- }
244
-
245
- async unpinMessage(messageId) {
279
+ async pinMessage(messageId, pinned = true) {
280
+ if (pinned) {
281
+ const res = await this._safeSDKCall(
282
+ () => this.client.im.pin.create({ data: { message_id: messageId } }),
283
+ 'pinMessage'
284
+ );
285
+ return { pin: res.data.pin };
286
+ }
246
287
  await this._safeSDKCall(
247
288
  () => this.client.im.pin.delete({ data: { message_id: messageId } }),
248
289
  'unpinMessage'
249
290
  );
250
- return { deleted: true };
291
+ return { unpinned: true };
251
292
  }
252
293
 
253
294
  // --- IM: Chat Management ---
@@ -361,61 +402,77 @@ class LarkOfficialClient {
361
402
  }
362
403
 
363
404
  async readDoc(documentId) {
364
- const res = await this._safeSDKCall(
365
- () => this.client.docx.document.rawContent({ path: { document_id: documentId }, params: { lang: 0 } }),
366
- 'readDoc'
367
- );
405
+ const res = await this._asUserOrApp({
406
+ uatPath: `/open-apis/docx/v1/documents/${documentId}/raw_content`,
407
+ query: { lang: '0' },
408
+ sdkFn: () => this.client.docx.document.rawContent({ path: { document_id: documentId }, params: { lang: 0 } }),
409
+ label: 'readDoc',
410
+ });
368
411
  return { content: res.data.content };
369
412
  }
370
413
 
371
414
  async createDoc(title, folderId) {
372
- const res = await this._safeSDKCall(
373
- () => this.client.docx.document.create({ data: { title, folder_token: folderId || '' } }),
374
- 'createDoc'
375
- );
415
+ const res = await this._asUserOrApp({
416
+ uatPath: `/open-apis/docx/v1/documents`,
417
+ method: 'POST',
418
+ body: { title, folder_token: folderId || '' },
419
+ sdkFn: () => this.client.docx.document.create({ data: { title, folder_token: folderId || '' } }),
420
+ label: 'createDoc',
421
+ });
376
422
  return { documentId: res.data.document?.document_id };
377
423
  }
378
424
 
379
425
  async getDocBlocks(documentId) {
380
- const res = await this._safeSDKCall(
381
- () => this.client.docx.documentBlock.list({ path: { document_id: documentId }, params: { page_size: 500 } }),
382
- 'getDocBlocks'
383
- );
426
+ const res = await this._asUserOrApp({
427
+ uatPath: `/open-apis/docx/v1/documents/${documentId}/blocks`,
428
+ query: { page_size: '500' },
429
+ sdkFn: () => this.client.docx.documentBlock.list({ path: { document_id: documentId }, params: { page_size: 500 } }),
430
+ label: 'getDocBlocks',
431
+ });
384
432
  return { items: res.data.items || [] };
385
433
  }
386
434
 
387
435
  async createDocBlock(documentId, parentBlockId, children, index) {
388
436
  const data = { children };
389
437
  if (index !== undefined) data.index = index;
390
- const res = await this._safeSDKCall(
391
- () => this.client.docx.documentBlockChildren.create({
438
+ const res = await this._asUserOrApp({
439
+ uatPath: `/open-apis/docx/v1/documents/${documentId}/blocks/${parentBlockId}/children`,
440
+ method: 'POST',
441
+ body: data,
442
+ sdkFn: () => this.client.docx.documentBlockChildren.create({
392
443
  path: { document_id: documentId, block_id: parentBlockId },
393
444
  data,
394
445
  }),
395
- 'createDocBlock'
396
- );
446
+ label: 'createDocBlock',
447
+ });
397
448
  return { blocks: res.data.children || [] };
398
449
  }
399
450
 
400
451
  async updateDocBlock(documentId, blockId, updateBody) {
401
- const res = await this._safeSDKCall(
402
- () => this.client.docx.documentBlock.patch({
452
+ const res = await this._asUserOrApp({
453
+ uatPath: `/open-apis/docx/v1/documents/${documentId}/blocks/${blockId}`,
454
+ method: 'PATCH',
455
+ body: updateBody,
456
+ sdkFn: () => this.client.docx.documentBlock.patch({
403
457
  path: { document_id: documentId, block_id: blockId },
404
458
  data: updateBody,
405
459
  }),
406
- 'updateDocBlock'
407
- );
460
+ label: 'updateDocBlock',
461
+ });
408
462
  return { block: res.data.block };
409
463
  }
410
464
 
411
465
  async deleteDocBlocks(documentId, parentBlockId, startIndex, endIndex) {
412
- const res = await this._safeSDKCall(
413
- () => this.client.docx.documentBlockChildren.batchDelete({
466
+ await this._asUserOrApp({
467
+ uatPath: `/open-apis/docx/v1/documents/${documentId}/blocks/${parentBlockId}/children/batch_delete`,
468
+ method: 'DELETE',
469
+ body: { start_index: startIndex, end_index: endIndex },
470
+ sdkFn: () => this.client.docx.documentBlockChildren.batchDelete({
414
471
  path: { document_id: documentId, block_id: parentBlockId },
415
472
  data: { start_index: startIndex, end_index: endIndex },
416
473
  }),
417
- 'deleteDocBlocks'
418
- );
474
+ label: 'deleteDocBlocks',
475
+ });
419
476
  return { deleted: true };
420
477
  }
421
478
 
@@ -435,15 +492,22 @@ class LarkOfficialClient {
435
492
  const data = {};
436
493
  if (name) data.name = name;
437
494
  if (folderId) data.folder_token = folderId;
438
- const res = await this._safeSDKCall(
439
- () => this.client.bitable.app.create({ data }),
440
- 'createBitable'
441
- );
495
+ const res = await this._asUserOrApp({
496
+ uatPath: `/open-apis/bitable/v1/apps`,
497
+ method: 'POST',
498
+ body: data,
499
+ sdkFn: () => this.client.bitable.app.create({ data }),
500
+ label: 'createBitable',
501
+ });
442
502
  return { appToken: res.data.app?.app_token, name: res.data.app?.name, url: res.data.app?.url };
443
503
  }
444
504
 
445
505
  async listBitableTables(appToken) {
446
- const res = await this._safeSDKCall(() => this.client.bitable.appTable.list({ path: { app_token: appToken } }), 'listTables');
506
+ const res = await this._asUserOrApp({
507
+ uatPath: `/open-apis/bitable/v1/apps/${appToken}/tables`,
508
+ sdkFn: () => this.client.bitable.appTable.list({ path: { app_token: appToken } }),
509
+ label: 'listTables',
510
+ });
447
511
  return { items: res.data.items || [] };
448
512
  }
449
513
 
@@ -451,39 +515,54 @@ class LarkOfficialClient {
451
515
  const data = { table: { name } };
452
516
  if (fields && fields.length > 0) data.table.default_view_name = name;
453
517
  if (fields && fields.length > 0) data.table.fields = fields;
454
- const res = await this._safeSDKCall(
455
- () => this.client.bitable.appTable.create({ path: { app_token: appToken }, data }),
456
- 'createTable'
457
- );
518
+ const res = await this._asUserOrApp({
519
+ uatPath: `/open-apis/bitable/v1/apps/${appToken}/tables`,
520
+ method: 'POST',
521
+ body: data,
522
+ sdkFn: () => this.client.bitable.appTable.create({ path: { app_token: appToken }, data }),
523
+ label: 'createTable',
524
+ });
458
525
  return { tableId: res.data.table_id };
459
526
  }
460
527
 
461
528
  async listBitableFields(appToken, tableId) {
462
- const res = await this._safeSDKCall(() => this.client.bitable.appTableField.list({ path: { app_token: appToken, table_id: tableId } }), 'listFields');
529
+ const res = await this._asUserOrApp({
530
+ uatPath: `/open-apis/bitable/v1/apps/${appToken}/tables/${tableId}/fields`,
531
+ sdkFn: () => this.client.bitable.appTableField.list({ path: { app_token: appToken, table_id: tableId } }),
532
+ label: 'listFields',
533
+ });
463
534
  return { items: res.data.items || [] };
464
535
  }
465
536
 
466
537
  async createBitableField(appToken, tableId, fieldConfig) {
467
- const res = await this._safeSDKCall(
468
- () => this.client.bitable.appTableField.create({ path: { app_token: appToken, table_id: tableId }, data: fieldConfig }),
469
- 'createField'
470
- );
538
+ const res = await this._asUserOrApp({
539
+ uatPath: `/open-apis/bitable/v1/apps/${appToken}/tables/${tableId}/fields`,
540
+ method: 'POST',
541
+ body: fieldConfig,
542
+ sdkFn: () => this.client.bitable.appTableField.create({ path: { app_token: appToken, table_id: tableId }, data: fieldConfig }),
543
+ label: 'createField',
544
+ });
471
545
  return { field: res.data.field };
472
546
  }
473
547
 
474
548
  async updateBitableField(appToken, tableId, fieldId, fieldConfig) {
475
- const res = await this._safeSDKCall(
476
- () => this.client.bitable.appTableField.update({ path: { app_token: appToken, table_id: tableId, field_id: fieldId }, data: fieldConfig }),
477
- 'updateField'
478
- );
549
+ const res = await this._asUserOrApp({
550
+ uatPath: `/open-apis/bitable/v1/apps/${appToken}/tables/${tableId}/fields/${fieldId}`,
551
+ method: 'PUT',
552
+ body: fieldConfig,
553
+ sdkFn: () => this.client.bitable.appTableField.update({ path: { app_token: appToken, table_id: tableId, field_id: fieldId }, data: fieldConfig }),
554
+ label: 'updateField',
555
+ });
479
556
  return { field: res.data.field };
480
557
  }
481
558
 
482
559
  async deleteBitableField(appToken, tableId, fieldId) {
483
- const res = await this._safeSDKCall(
484
- () => this.client.bitable.appTableField.delete({ path: { app_token: appToken, table_id: tableId, field_id: fieldId } }),
485
- 'deleteField'
486
- );
560
+ const res = await this._asUserOrApp({
561
+ uatPath: `/open-apis/bitable/v1/apps/${appToken}/tables/${tableId}/fields/${fieldId}`,
562
+ method: 'DELETE',
563
+ sdkFn: () => this.client.bitable.appTableField.delete({ path: { app_token: appToken, table_id: tableId, field_id: fieldId } }),
564
+ label: 'deleteField',
565
+ });
487
566
  return { fieldId: res.data.field_id, deleted: res.data.deleted };
488
567
  }
489
568
 
@@ -491,87 +570,172 @@ class LarkOfficialClient {
491
570
  const data = {};
492
571
  if (filter) data.filter = filter;
493
572
  if (sort) data.sort = sort;
494
- if (pageSize) data.page_size = pageSize;
495
- if (pageToken) data.page_token = pageToken;
496
- const res = await this._safeSDKCall(
497
- () => this.client.bitable.appTableRecord.search({ path: { app_token: appToken, table_id: tableId }, data }),
498
- 'searchRecords'
499
- );
573
+ const query = {};
574
+ if (pageSize) query.page_size = String(pageSize);
575
+ if (pageToken) query.page_token = pageToken;
576
+ const res = await this._asUserOrApp({
577
+ uatPath: `/open-apis/bitable/v1/apps/${appToken}/tables/${tableId}/records/search`,
578
+ method: 'POST',
579
+ body: data,
580
+ query,
581
+ sdkFn: () => this.client.bitable.appTableRecord.search({
582
+ path: { app_token: appToken, table_id: tableId },
583
+ params: { page_size: pageSize, ...(pageToken ? { page_token: pageToken } : {}) },
584
+ data,
585
+ }),
586
+ label: 'searchRecords',
587
+ });
500
588
  return { items: res.data.items || [], total: res.data.total, hasMore: res.data.has_more };
501
589
  }
502
590
 
503
591
  async createBitableRecord(appToken, tableId, fields) {
504
- const res = await this._safeSDKCall(
505
- () => this.client.bitable.appTableRecord.create({ path: { app_token: appToken, table_id: tableId }, data: { fields } }),
506
- 'createRecord'
507
- );
592
+ const res = await this._asUserOrApp({
593
+ uatPath: `/open-apis/bitable/v1/apps/${appToken}/tables/${tableId}/records`,
594
+ method: 'POST',
595
+ body: { fields },
596
+ sdkFn: () => this.client.bitable.appTableRecord.create({ path: { app_token: appToken, table_id: tableId }, data: { fields } }),
597
+ label: 'createRecord',
598
+ });
508
599
  return { recordId: res.data.record?.record_id };
509
600
  }
510
601
 
511
602
  async updateBitableRecord(appToken, tableId, recordId, fields) {
512
- const res = await this._safeSDKCall(
513
- () => this.client.bitable.appTableRecord.update({ path: { app_token: appToken, table_id: tableId, record_id: recordId }, data: { fields } }),
514
- 'updateRecord'
515
- );
603
+ const res = await this._asUserOrApp({
604
+ uatPath: `/open-apis/bitable/v1/apps/${appToken}/tables/${tableId}/records/${recordId}`,
605
+ method: 'PUT',
606
+ body: { fields },
607
+ sdkFn: () => this.client.bitable.appTableRecord.update({ path: { app_token: appToken, table_id: tableId, record_id: recordId }, data: { fields } }),
608
+ label: 'updateRecord',
609
+ });
516
610
  return { recordId: res.data.record?.record_id };
517
611
  }
518
612
 
519
613
  async deleteBitableRecord(appToken, tableId, recordId) {
520
- const res = await this._safeSDKCall(
521
- () => this.client.bitable.appTableRecord.delete({ path: { app_token: appToken, table_id: tableId, record_id: recordId } }),
522
- 'deleteRecord'
523
- );
614
+ const res = await this._asUserOrApp({
615
+ uatPath: `/open-apis/bitable/v1/apps/${appToken}/tables/${tableId}/records/${recordId}`,
616
+ method: 'DELETE',
617
+ sdkFn: () => this.client.bitable.appTableRecord.delete({ path: { app_token: appToken, table_id: tableId, record_id: recordId } }),
618
+ label: 'deleteRecord',
619
+ });
524
620
  return { deleted: res.data.deleted };
525
621
  }
526
622
 
527
623
  async batchCreateBitableRecords(appToken, tableId, records) {
528
- const res = await this._safeSDKCall(
529
- () => this.client.bitable.appTableRecord.batchCreate({ path: { app_token: appToken, table_id: tableId }, data: { records } }),
530
- 'batchCreateRecords'
531
- );
624
+ const res = await this._asUserOrApp({
625
+ uatPath: `/open-apis/bitable/v1/apps/${appToken}/tables/${tableId}/records/batch_create`,
626
+ method: 'POST',
627
+ body: { records },
628
+ sdkFn: () => this.client.bitable.appTableRecord.batchCreate({ path: { app_token: appToken, table_id: tableId }, data: { records } }),
629
+ label: 'batchCreateRecords',
630
+ });
532
631
  return { records: res.data.records || [] };
533
632
  }
534
633
 
535
634
  async batchUpdateBitableRecords(appToken, tableId, records) {
536
- const res = await this._safeSDKCall(
537
- () => this.client.bitable.appTableRecord.batchUpdate({ path: { app_token: appToken, table_id: tableId }, data: { records } }),
538
- 'batchUpdateRecords'
539
- );
635
+ const res = await this._asUserOrApp({
636
+ uatPath: `/open-apis/bitable/v1/apps/${appToken}/tables/${tableId}/records/batch_update`,
637
+ method: 'POST',
638
+ body: { records },
639
+ sdkFn: () => this.client.bitable.appTableRecord.batchUpdate({ path: { app_token: appToken, table_id: tableId }, data: { records } }),
640
+ label: 'batchUpdateRecords',
641
+ });
540
642
  return { records: res.data.records || [] };
541
643
  }
542
644
 
543
645
  async batchDeleteBitableRecords(appToken, tableId, recordIds) {
544
- const res = await this._safeSDKCall(
545
- () => this.client.bitable.appTableRecord.batchDelete({ path: { app_token: appToken, table_id: tableId }, data: { records: recordIds } }),
546
- 'batchDeleteRecords'
547
- );
646
+ const res = await this._asUserOrApp({
647
+ uatPath: `/open-apis/bitable/v1/apps/${appToken}/tables/${tableId}/records/batch_delete`,
648
+ method: 'POST',
649
+ body: { records: recordIds },
650
+ sdkFn: () => this.client.bitable.appTableRecord.batchDelete({ path: { app_token: appToken, table_id: tableId }, data: { records: recordIds } }),
651
+ label: 'batchDeleteRecords',
652
+ });
548
653
  return { records: res.data.records || [] };
549
654
  }
550
655
 
551
656
  async listBitableViews(appToken, tableId) {
552
- const res = await this._safeSDKCall(
553
- () => this.client.bitable.appTableView.list({ path: { app_token: appToken, table_id: tableId }, params: { page_size: 50 } }),
554
- 'listViews'
555
- );
657
+ const res = await this._asUserOrApp({
658
+ uatPath: `/open-apis/bitable/v1/apps/${appToken}/tables/${tableId}/views`,
659
+ query: { page_size: '50' },
660
+ sdkFn: () => this.client.bitable.appTableView.list({ path: { app_token: appToken, table_id: tableId }, params: { page_size: 50 } }),
661
+ label: 'listViews',
662
+ });
556
663
  return { items: res.data.items || [] };
557
664
  }
558
665
 
559
666
  async getBitableRecord(appToken, tableId, recordId) {
560
- const res = await this._safeSDKCall(
561
- () => this.client.bitable.appTableRecord.get({ path: { app_token: appToken, table_id: tableId, record_id: recordId } }),
562
- 'getRecord'
563
- );
667
+ const res = await this._asUserOrApp({
668
+ uatPath: `/open-apis/bitable/v1/apps/${appToken}/tables/${tableId}/records/${recordId}`,
669
+ sdkFn: () => this.client.bitable.appTableRecord.get({ path: { app_token: appToken, table_id: tableId, record_id: recordId } }),
670
+ label: 'getRecord',
671
+ });
564
672
  return { record: res.data.record };
565
673
  }
566
674
 
567
675
  async deleteBitableTable(appToken, tableId) {
568
- await this._safeSDKCall(
569
- () => this.client.bitable.appTable.delete({ path: { app_token: appToken, table_id: tableId } }),
570
- 'deleteTable'
571
- );
676
+ await this._asUserOrApp({
677
+ uatPath: `/open-apis/bitable/v1/apps/${appToken}/tables/${tableId}`,
678
+ method: 'DELETE',
679
+ sdkFn: () => this.client.bitable.appTable.delete({ path: { app_token: appToken, table_id: tableId } }),
680
+ label: 'deleteTable',
681
+ });
682
+ return { deleted: true };
683
+ }
684
+
685
+ async getBitableMeta(appToken) {
686
+ const res = await this._asUserOrApp({
687
+ uatPath: `/open-apis/bitable/v1/apps/${appToken}`,
688
+ sdkFn: () => this.client.bitable.app.get({ path: { app_token: appToken } }),
689
+ label: 'getBitableMeta',
690
+ });
691
+ return { app: res.data.app };
692
+ }
693
+
694
+ async updateBitableTable(appToken, tableId, name) {
695
+ const res = await this._asUserOrApp({
696
+ uatPath: `/open-apis/bitable/v1/apps/${appToken}/tables/${tableId}`,
697
+ method: 'PATCH',
698
+ body: { name },
699
+ sdkFn: () => this.client.bitable.appTable.patch({ path: { app_token: appToken, table_id: tableId }, data: { name } }),
700
+ label: 'updateTable',
701
+ });
702
+ return { name: res.data.name };
703
+ }
704
+
705
+ async createBitableView(appToken, tableId, viewName, viewType = 'grid') {
706
+ const res = await this._asUserOrApp({
707
+ uatPath: `/open-apis/bitable/v1/apps/${appToken}/tables/${tableId}/views`,
708
+ method: 'POST',
709
+ body: { view_name: viewName, view_type: viewType },
710
+ sdkFn: () => this.client.bitable.appTableView.create({ path: { app_token: appToken, table_id: tableId }, data: { view_name: viewName, view_type: viewType } }),
711
+ label: 'createView',
712
+ });
713
+ return { view: res.data.view };
714
+ }
715
+
716
+ async deleteBitableView(appToken, tableId, viewId) {
717
+ await this._asUserOrApp({
718
+ uatPath: `/open-apis/bitable/v1/apps/${appToken}/tables/${tableId}/views/${viewId}`,
719
+ method: 'DELETE',
720
+ sdkFn: () => this.client.bitable.appTableView.delete({ path: { app_token: appToken, table_id: tableId, view_id: viewId } }),
721
+ label: 'deleteView',
722
+ });
572
723
  return { deleted: true };
573
724
  }
574
725
 
726
+ async copyBitable(appToken, name, folderId) {
727
+ const data = { name };
728
+ if (folderId) data.folder_token = folderId;
729
+ const res = await this._asUserOrApp({
730
+ uatPath: `/open-apis/bitable/v1/apps/${appToken}/copy`,
731
+ method: 'POST',
732
+ body: data,
733
+ sdkFn: () => this.client.bitable.app.copy({ path: { app_token: appToken }, data }),
734
+ label: 'copyBitable',
735
+ });
736
+ return { app: res.data.app };
737
+ }
738
+
575
739
  // --- Wiki ---
576
740
 
577
741
  async listWikiSpaces() {
@@ -613,10 +777,14 @@ class LarkOfficialClient {
613
777
  }
614
778
 
615
779
  async createFolder(name, parentToken) {
616
- const res = await this._safeSDKCall(
617
- () => this.client.drive.file.createFolder({ data: { name, folder_token: parentToken || '' } }),
618
- 'createFolder'
619
- );
780
+ const body = { name, folder_token: parentToken || '' };
781
+ const res = await this._asUserOrApp({
782
+ uatPath: `/open-apis/drive/v1/files/create_folder`,
783
+ method: 'POST',
784
+ body,
785
+ sdkFn: () => this.client.drive.file.createFolder({ data: body }),
786
+ label: 'createFolder',
787
+ });
620
788
  return { token: res.data.token };
621
789
  }
622
790
 
@@ -679,111 +847,6 @@ class LarkOfficialClient {
679
847
  return allChats;
680
848
  }
681
849
 
682
- // --- Calendar ---
683
-
684
- async listCalendars() {
685
- const res = await this._safeSDKCall(
686
- () => this.client.calendar.calendar.list({ params: { page_size: 50 } }),
687
- 'listCalendars'
688
- );
689
- return { items: res.data.calendar_list || [] };
690
- }
691
-
692
- async createCalendarEvent(calendarId, event) {
693
- const res = await this._safeSDKCall(
694
- () => this.client.calendar.calendarEvent.create({
695
- path: { calendar_id: calendarId },
696
- data: event,
697
- }),
698
- 'createCalendarEvent'
699
- );
700
- return { event: res.data.event };
701
- }
702
-
703
- async listCalendarEvents(calendarId, { startTime, endTime, pageSize = 50, pageToken } = {}) {
704
- const params = { page_size: pageSize };
705
- if (startTime) params.start_time = startTime;
706
- if (endTime) params.end_time = endTime;
707
- if (pageToken) params.page_token = pageToken;
708
- const res = await this._safeSDKCall(
709
- () => this.client.calendar.calendarEvent.list({
710
- path: { calendar_id: calendarId },
711
- params,
712
- }),
713
- 'listCalendarEvents'
714
- );
715
- return { items: res.data.items || [], hasMore: res.data.has_more, pageToken: res.data.page_token };
716
- }
717
-
718
- async deleteCalendarEvent(calendarId, eventId) {
719
- await this._safeSDKCall(
720
- () => this.client.calendar.calendarEvent.delete({
721
- path: { calendar_id: calendarId, event_id: eventId },
722
- }),
723
- 'deleteCalendarEvent'
724
- );
725
- return { deleted: true };
726
- }
727
-
728
- async getFreeBusy(userIds, startTime, endTime) {
729
- const res = await this._safeSDKCall(
730
- () => this.client.calendar.freebusy.list({
731
- data: {
732
- time_min: startTime,
733
- time_max: endTime,
734
- user_id: { user_ids: userIds, id_type: 'open_id' },
735
- },
736
- }),
737
- 'getFreeBusy'
738
- );
739
- return { freebusyList: res.data.freebusy_list || [] };
740
- }
741
-
742
- // --- Tasks ---
743
-
744
- async createTask(task) {
745
- const res = await this._safeSDKCall(
746
- () => this.client.task.task.create({ data: task }),
747
- 'createTask'
748
- );
749
- return { task: res.data.task };
750
- }
751
-
752
- async getTask(taskId) {
753
- const res = await this._safeSDKCall(
754
- () => this.client.task.task.get({ path: { task_id: taskId } }),
755
- 'getTask'
756
- );
757
- return { task: res.data.task };
758
- }
759
-
760
- async listTasks({ pageSize = 50, pageToken } = {}) {
761
- const res = await this._safeSDKCall(
762
- () => this.client.task.task.list({ params: { page_size: pageSize, page_token: pageToken } }),
763
- 'listTasks'
764
- );
765
- return { items: res.data.items || [], hasMore: res.data.has_more, pageToken: res.data.page_token };
766
- }
767
-
768
- async updateTask(taskId, task) {
769
- const res = await this._safeSDKCall(
770
- () => this.client.task.task.patch({
771
- path: { task_id: taskId },
772
- data: task,
773
- }),
774
- 'updateTask'
775
- );
776
- return { task: res.data.task };
777
- }
778
-
779
- async completeTask(taskId) {
780
- const res = await this._safeSDKCall(
781
- () => this.client.task.task.complete({ path: { task_id: taskId } }),
782
- 'completeTask'
783
- );
784
- return { completed: true };
785
- }
786
-
787
850
  // --- Safe SDK Call (extracts real Feishu error from AxiosError) ---
788
851
 
789
852
  async _safeSDKCall(fn, label = 'API') {
@@ -869,7 +932,7 @@ class LarkOfficialClient {
869
932
  if (!m) return null;
870
933
  let body = m.body?.content || '';
871
934
  try { body = JSON.parse(body); } catch {}
872
- return {
935
+ const out = {
873
936
  messageId: m.message_id,
874
937
  chatId: m.chat_id,
875
938
  senderId: m.sender?.id,
@@ -879,6 +942,8 @@ class LarkOfficialClient {
879
942
  createTime: this._normalizeTimestamp(m.create_time),
880
943
  updateTime: this._normalizeTimestamp(m.update_time),
881
944
  };
945
+ if (Array.isArray(m.mentions) && m.mentions.length > 0) out.mentions = m.mentions;
946
+ return out;
882
947
  }
883
948
 
884
949
  _normalizeTimestamp(ts) {