@openclaw/feishu 2026.9.1-beta.1 → 2026.9.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.
Files changed (47) hide show
  1. package/dist/{accounts-RwSyseKr.js → accounts-cCMNFBKg.js} +1 -1
  2. package/dist/api.js +222 -284
  3. package/dist/{app-registration-BpfUzS9m.js → app-registration-BI99e_JI.js} +12 -9
  4. package/dist/{channel-Dpgf8wLD.js → channel-VytDDz_B.js} +244 -582
  5. package/dist/channel-plugin-api.js +1 -1
  6. package/dist/{channel.runtime-BIEERwaT.js → channel.runtime-CAD74kBO.js} +15 -19
  7. package/dist/{chat-C1nuW5UY.js → chat-DDhIcPQy.js} +13 -13
  8. package/dist/{client-DalLmpMf.js → client-DMbIL3UH.js} +3 -6
  9. package/dist/contract-api.js +2 -2
  10. package/dist/doctor-contract-api.js +1 -1
  11. package/dist/doctor-contract-w6FpMypv.js +465 -0
  12. package/dist/index.js +11 -42
  13. package/dist/{json-response-CheljwGr.js → json-response-DXajw_FU.js} +1 -1
  14. package/dist/{monitor.startup-D-gL82OO.js → monitor-rPJ_Mz4M.js} +62 -2
  15. package/dist/{monitor.account-DhhotI_Q.js → monitor.account-v0XIDyfs.js} +270 -299
  16. package/dist/{probe-CfhhR_0a.js → probe-D_SatdZl.js} +3 -3
  17. package/dist/{presentation-card-C8Z7Tgru.js → reply-delivery-result-CYaKkbUN.js} +334 -240
  18. package/dist/{security-audit-D6Fz2h6p.js → security-audit-DKfR4Cv3.js} +1 -1
  19. package/dist/{security-audit-shared-BgpY7AiJ.js → security-audit-shared-CK5rVR-1.js} +2 -1
  20. package/dist/security-contract-api.js +1 -1
  21. package/dist/session-binding-contract-api.js +1 -1
  22. package/dist/setup-api.js +1 -1
  23. package/dist/{subagent-hooks-KUi-7vUy.js → subagent-hooks-Buadpie_.js} +3 -6
  24. package/dist/subagent-hooks-api.js +1 -1
  25. package/dist/{targets-BUjQ1TcA.js → targets-BTQCYRZQ.js} +4 -4
  26. package/dist/{thread-bindings-CptM_YU6.js → thread-bindings-BiL1wGqK.js} +20 -9
  27. package/node_modules/@larksuiteoapi/node-sdk/node_modules/ws/lib/permessage-deflate.js +3 -1
  28. package/node_modules/@larksuiteoapi/node-sdk/node_modules/ws/package.json +1 -1
  29. package/node_modules/typebox/build/format/email.mjs +1 -1
  30. package/node_modules/typebox/build/format/idna/idn-hostname.mjs +19 -1
  31. package/node_modules/typebox/build/format/json_pointer.d.mts +2 -2
  32. package/node_modules/typebox/build/format/json_pointer.mjs +2 -2
  33. package/node_modules/typebox/build/format/json_pointer_uri_fragment.d.mts +2 -2
  34. package/node_modules/typebox/build/format/json_pointer_uri_fragment.mjs +2 -2
  35. package/node_modules/typebox/build/format/relative_json_pointer.d.mts +2 -2
  36. package/node_modules/typebox/build/format/relative_json_pointer.mjs +2 -2
  37. package/node_modules/typebox/build/system/memory/assign.mjs +2 -1
  38. package/node_modules/typebox/build/system/memory/create.mjs +3 -4
  39. package/node_modules/typebox/build/system/memory/discard.mjs +2 -2
  40. package/node_modules/typebox/build/system/memory/freeze.d.mts +4 -0
  41. package/node_modules/typebox/build/system/memory/freeze.mjs +6 -0
  42. package/node_modules/typebox/build/system/memory/update.mjs +3 -2
  43. package/node_modules/typebox/package.json +1 -1
  44. package/openclaw.plugin.json +45 -238
  45. package/package.json +6 -5
  46. package/dist/doctor-contract-bEQXIXyP.js +0 -158
  47. package/dist/monitor-BRg3sHH7.js +0 -62
package/dist/api.js CHANGED
@@ -1,12 +1,11 @@
1
- import { r as listEnabledFeishuAccounts } from "./accounts-RwSyseKr.js";
2
- import { a as setFeishuNamedAccountEnabled, i as feishuSetupAdapter, n as feishuSetupWizard, r as runFeishuLogin, t as feishuPlugin } from "./channel-Dpgf8wLD.js";
3
- import { A as feishuExternalToolResult, F as resolveFeishuToolAccount, M as unknownToolActionResult, N as createFeishuToolClient, P as resolveAnyEnabledFeishuToolsConfig, Q as parseFeishuMarkdown, Y as chunkFeishuMarkdown, j as toolExecutionErrorResult, k as registerFeishuDriveTools } from "./presentation-card-C8Z7Tgru.js";
1
+ import { a as setFeishuNamedAccountEnabled, i as feishuSetupAdapter, n as feishuSetupWizard, r as runFeishuLogin, t as feishuPlugin } from "./channel-VytDDz_B.js";
2
+ import { G as createFeishuToolClient, H as feishuExternalToolResult, K as resolveAnyEnabledFeishuToolsConfig, U as toolExecutionErrorResult, V as registerFeishuDriveTools, W as unknownToolActionResult, lt as parseFeishuMarkdown, ot as chunkFeishuMarkdown, q as resolveFeishuToolAccount } from "./reply-delivery-result-CYaKkbUN.js";
4
3
  import { a as parseFeishuTargetId, i as parseFeishuDirectConversationId, n as buildFeishuModelOverrideParentCandidates, r as parseFeishuConversationId, t as buildFeishuConversationId } from "./conversation-id-VYgGQ-GX.js";
5
- import { s as resolveConfiguredHttpTimeoutMs } from "./client-DalLmpMf.js";
4
+ import { s as resolveConfiguredHttpTimeoutMs } from "./client-DMbIL3UH.js";
6
5
  import { t as getFeishuRuntime } from "./runtime-C5JxBWZp.js";
7
- import { o as registerFeishuChatTools } from "./chat-C1nuW5UY.js";
8
- import { n as getFeishuThreadBindingManager, t as createFeishuThreadBindingManager } from "./thread-bindings-CptM_YU6.js";
9
- import { n as handleFeishuSubagentEnded, t as handleFeishuSubagentDeliveryTarget } from "./subagent-hooks-KUi-7vUy.js";
6
+ import { o as registerFeishuChatTools } from "./chat-DDhIcPQy.js";
7
+ import { n as getFeishuThreadBindingManager, t as createFeishuThreadBindingManager } from "./thread-bindings-BiL1wGqK.js";
8
+ import { n as handleFeishuSubagentEnded, t as handleFeishuSubagentDeliveryTarget } from "./subagent-hooks-Buadpie_.js";
10
9
  import { optionalPositiveIntegerSchema } from "openclaw/plugin-sdk/channel-actions";
11
10
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
12
11
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, readStringValue, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
@@ -167,23 +166,6 @@ const FeishuDocSchema = Type.Union([
167
166
  const MIN_COLUMN_WIDTH = 50;
168
167
  const MAX_COLUMN_WIDTH = 400;
169
168
  const DEFAULT_TABLE_WIDTH = 730;
170
- /**
171
- * Calculate adaptive column widths based on cell content length.
172
- *
173
- * Algorithm:
174
- * 1. For each column, find the max content length across all rows
175
- * 2. Weight CJK characters as 2x width (they render wider)
176
- * 3. Calculate proportional widths based on content length
177
- * 4. Apply min/max constraints
178
- * 5. Redistribute remaining space to fill total table width
179
- *
180
- * Total width is derived from the original column_width values returned
181
- * by the Convert API, ensuring tables match Feishu's expected dimensions.
182
- *
183
- * @param blocks - Array of blocks from Convert API
184
- * @param tableBlockId - The block_id of the table block
185
- * @returns Array of column widths in pixels
186
- */
187
169
  function normalizeChildBlockIds(children) {
188
170
  if (Array.isArray(children)) return children;
189
171
  return typeof children === "string" ? [children] : [];
@@ -288,86 +270,61 @@ function cleanBlocksForDescendant(blocks) {
288
270
  return cleanBlock;
289
271
  });
290
272
  }
291
- async function insertTableRow(client, docToken, blockId, rowIndex = -1) {
292
- const res = await client.docx.documentBlock.patch({
293
- path: {
294
- document_id: docToken,
295
- block_id: blockId
296
- },
297
- data: { insert_table_row: { row_index: rowIndex } }
298
- });
299
- if (res.code !== 0) throw new Error(res.msg);
300
- return {
301
- success: true,
302
- block: res.data?.block
303
- };
304
- }
305
- async function insertTableColumn(client, docToken, blockId, columnIndex = -1) {
306
- const res = await client.docx.documentBlock.patch({
307
- path: {
308
- document_id: docToken,
309
- block_id: blockId
310
- },
311
- data: { insert_table_column: { column_index: columnIndex } }
312
- });
313
- if (res.code !== 0) throw new Error(res.msg);
314
- return {
315
- success: true,
316
- block: res.data?.block
317
- };
318
- }
319
- async function deleteTableRows(client, docToken, blockId, rowStart, rowCount = 1) {
320
- const res = await client.docx.documentBlock.patch({
321
- path: {
322
- document_id: docToken,
323
- block_id: blockId
324
- },
325
- data: { delete_table_rows: {
326
- row_start_index: rowStart,
327
- row_end_index: rowStart + rowCount
328
- } }
329
- });
330
- if (res.code !== 0) throw new Error(res.msg);
331
- return {
332
- success: true,
333
- rows_deleted: rowCount,
334
- block: res.data?.block
335
- };
336
- }
337
- async function deleteTableColumns(client, docToken, blockId, columnStart, columnCount = 1) {
338
- const res = await client.docx.documentBlock.patch({
339
- path: {
340
- document_id: docToken,
341
- block_id: blockId
342
- },
343
- data: { delete_table_columns: {
344
- column_start_index: columnStart,
345
- column_end_index: columnStart + columnCount
346
- } }
347
- });
348
- if (res.code !== 0) throw new Error(res.msg);
349
- return {
350
- success: true,
351
- columns_deleted: columnCount,
352
- block: res.data?.block
353
- };
354
- }
355
- async function mergeTableCells(client, docToken, blockId, rowStart, rowEnd, columnStart, columnEnd) {
273
+ async function patchTable(client, params) {
274
+ const { doc_token, block_id } = params;
275
+ let data;
276
+ const counts = {};
277
+ switch (params.action) {
278
+ case "insert_table_row": {
279
+ const { row_index = -1 } = params;
280
+ data = { insert_table_row: { row_index } };
281
+ break;
282
+ }
283
+ case "insert_table_column": {
284
+ const { column_index = -1 } = params;
285
+ data = { insert_table_column: { column_index } };
286
+ break;
287
+ }
288
+ case "delete_table_rows": {
289
+ const { row_start, row_count = 1 } = params;
290
+ data = { delete_table_rows: {
291
+ row_start_index: row_start,
292
+ row_end_index: row_start + row_count
293
+ } };
294
+ counts.rows_deleted = row_count;
295
+ break;
296
+ }
297
+ case "delete_table_columns": {
298
+ const { column_start, column_count = 1 } = params;
299
+ data = { delete_table_columns: {
300
+ column_start_index: column_start,
301
+ column_end_index: column_start + column_count
302
+ } };
303
+ counts.columns_deleted = column_count;
304
+ break;
305
+ }
306
+ case "merge_table_cells": {
307
+ const { row_start, row_end, column_start, column_end } = params;
308
+ data = { merge_table_cells: {
309
+ row_start_index: row_start,
310
+ row_end_index: row_end,
311
+ column_start_index: column_start,
312
+ column_end_index: column_end
313
+ } };
314
+ break;
315
+ }
316
+ }
356
317
  const res = await client.docx.documentBlock.patch({
357
318
  path: {
358
- document_id: docToken,
359
- block_id: blockId
319
+ document_id: doc_token,
320
+ block_id
360
321
  },
361
- data: { merge_table_cells: {
362
- row_start_index: rowStart,
363
- row_end_index: rowEnd,
364
- column_start_index: columnStart,
365
- column_end_index: columnEnd
366
- } }
322
+ data
367
323
  });
368
324
  if (res.code !== 0) throw new Error(res.msg);
369
325
  return {
370
326
  success: true,
327
+ ...counts,
371
328
  block: res.data?.block
372
329
  };
373
330
  }
@@ -836,7 +793,7 @@ const BLOCK_TYPE_NAMES = {
836
793
  32: "TableCell"
837
794
  };
838
795
  const UNSUPPORTED_CREATE_TYPES = /* @__PURE__ */ new Set([31, 32]);
839
- /** Clean blocks for insertion (remove unsupported types and read-only fields) */
796
+ /** Remove block types unsupported by the children insertion API. */
840
797
  function cleanBlocksForInsert(blocks) {
841
798
  const skipped = [];
842
799
  return {
@@ -847,24 +804,21 @@ function cleanBlocksForInsert(blocks) {
847
804
  return false;
848
805
  }
849
806
  return true;
850
- }).map((block) => {
851
- if (block.block_type === 31 && block.table?.merge_info) {
852
- const { merge_info: _merge_info, ...tableRest } = block.table;
853
- return Object.assign({}, block, { table: tableRest });
854
- }
855
- return block;
856
807
  }),
857
808
  skipped
858
809
  };
859
810
  }
860
811
  /** Max blocks per documentBlockChildren.create request */
861
812
  const MAX_CONVERT_RETRY_DEPTH = 8;
813
+ function assertFeishuDocApiSuccess(response) {
814
+ if (response.code !== 0) throw new Error(response.msg);
815
+ }
862
816
  async function convertMarkdown(client, markdown) {
863
817
  const res = await client.docx.document.convert({ data: {
864
818
  content_type: "markdown",
865
819
  content: markdown
866
820
  } });
867
- if (res.code !== 0) throw new Error(res.msg);
821
+ assertFeishuDocApiSuccess(res);
868
822
  return {
869
823
  blocks: res.data?.blocks ?? [],
870
824
  firstLevelBlockIds: res.data?.first_level_block_ids ?? []
@@ -909,7 +863,8 @@ function normalizeConvertedBlockTree(blocks, firstLevelIds) {
909
863
  const parentId = typeof block?.parent_id === "string" ? block.parent_id : "";
910
864
  return !childIds.has(blockId) && (!parentId || !byId.has(parentId));
911
865
  }).toSorted((a, b) => (originalOrder.get(a.block_id ?? "__missing__") ?? 0) - (originalOrder.get(b.block_id ?? "__missing__") ?? 0)).map((block) => block.block_id).filter((blockId) => typeof blockId === "string");
912
- const uniqueRootIds = uniqueStrings((firstLevelIds && firstLevelIds.length > 0 ? firstLevelIds : inferredTopLevelIds).filter((id) => typeof id === "string" && byId.has(id)));
866
+ const rootIds = (firstLevelIds && firstLevelIds.length > 0 ? firstLevelIds : inferredTopLevelIds).filter((id) => typeof id === "string" && byId.has(id));
867
+ const uniqueRootIds = uniqueStrings(rootIds);
913
868
  const orderedBlocks = [];
914
869
  const visited = /* @__PURE__ */ new Set();
915
870
  const visit = (blockId) => {
@@ -947,7 +902,7 @@ async function insertBlocks(client, docToken, blocks, parentBlockId, index) {
947
902
  ...index !== void 0 ? { index: index + offset } : {}
948
903
  }
949
904
  });
950
- if (res.code !== 0) throw new Error(res.msg);
905
+ assertFeishuDocApiSuccess(res);
951
906
  allInserted.push(...res.data?.children ?? []);
952
907
  }
953
908
  return {
@@ -1026,21 +981,18 @@ async function insertBlocksWithDescendant(client, docToken, blocks, firstLevelBl
1026
981
  }
1027
982
  async function clearDocumentContent(client, docToken) {
1028
983
  const existing = await client.docx.documentBlock.list({ path: { document_id: docToken } });
1029
- if (existing.code !== 0) throw new Error(existing.msg);
984
+ assertFeishuDocApiSuccess(existing);
1030
985
  const childIds = existing.data?.items?.filter((b) => b.parent_id === docToken && b.block_type !== 1).map((b) => b.block_id) ?? [];
1031
- if (childIds.length > 0) {
1032
- const res = await client.docx.documentBlockChildren.batchDelete({
1033
- path: {
1034
- document_id: docToken,
1035
- block_id: docToken
1036
- },
1037
- data: {
1038
- start_index: 0,
1039
- end_index: childIds.length
1040
- }
1041
- });
1042
- if (res.code !== 0) throw new Error(res.msg);
1043
- }
986
+ if (childIds.length > 0) assertFeishuDocApiSuccess(await client.docx.documentBlockChildren.batchDelete({
987
+ path: {
988
+ document_id: docToken,
989
+ block_id: docToken
990
+ },
991
+ data: {
992
+ start_index: 0,
993
+ end_index: childIds.length
994
+ }
995
+ }));
1044
996
  return childIds.length;
1045
997
  }
1046
998
  async function uploadImageToDocx(client, blockId, imageBuffer, fileName, docToken) {
@@ -1070,13 +1022,13 @@ async function processImages(client, docToken, images, insertedBlocks, maxBytes,
1070
1022
  remoteReadTimeoutMs: imageReadTimeoutMs
1071
1023
  });
1072
1024
  const fileToken = await uploadImageToDocx(client, blockId, upload.buffer, upload.fileName, docToken);
1073
- await client.docx.documentBlock.patch({
1025
+ assertFeishuDocApiSuccess(await client.docx.documentBlock.patch({
1074
1026
  path: {
1075
1027
  document_id: docToken,
1076
1028
  block_id: blockId
1077
1029
  },
1078
1030
  data: { replace_image: { token: fileToken } }
1079
- });
1031
+ }));
1080
1032
  processed++;
1081
1033
  } catch (err) {
1082
1034
  console.error(`Failed to process image ${url}:`, err);
@@ -1133,14 +1085,13 @@ async function uploadImageBlock(client, docToken, maxBytes, imageReadTimeoutMs,
1133
1085
  const imageBlockId = insertRes.data?.children?.find((b) => b.block_type === 27)?.block_id;
1134
1086
  if (!imageBlockId) throw new Error("Failed to create image block");
1135
1087
  const fileToken = await uploadImageToDocx(client, imageBlockId, upload.buffer, upload.fileName, docToken);
1136
- const patchRes = await client.docx.documentBlock.patch({
1088
+ assertFeishuDocApiSuccess(await client.docx.documentBlock.patch({
1137
1089
  path: {
1138
1090
  document_id: docToken,
1139
1091
  block_id: imageBlockId
1140
1092
  },
1141
1093
  data: { replace_image: { token: fileToken } }
1142
- });
1143
- if (patchRes.code !== 0) throw new Error(patchRes.msg);
1094
+ }));
1144
1095
  return {
1145
1096
  success: true,
1146
1097
  block_id: imageBlockId,
@@ -1168,21 +1119,18 @@ async function uploadFileBlock(client, docToken, maxBytes, localRoots, url, file
1168
1119
  document_id: docToken,
1169
1120
  block_id: parentId
1170
1121
  } });
1171
- if (childrenRes.code !== 0) throw new Error(childrenRes.msg);
1122
+ assertFeishuDocApiSuccess(childrenRes);
1172
1123
  const placeholderIdx = (childrenRes.data?.items ?? []).findIndex((item) => item.block_id === placeholderBlock.block_id);
1173
- if (placeholderIdx >= 0) {
1174
- const deleteRes = await client.docx.documentBlockChildren.batchDelete({
1175
- path: {
1176
- document_id: docToken,
1177
- block_id: parentId
1178
- },
1179
- data: {
1180
- start_index: placeholderIdx,
1181
- end_index: placeholderIdx + 1
1182
- }
1183
- });
1184
- if (deleteRes.code !== 0) throw new Error(deleteRes.msg);
1185
- }
1124
+ if (placeholderIdx >= 0) assertFeishuDocApiSuccess(await client.docx.documentBlockChildren.batchDelete({
1125
+ path: {
1126
+ document_id: docToken,
1127
+ block_id: parentId
1128
+ },
1129
+ data: {
1130
+ start_index: placeholderIdx,
1131
+ end_index: placeholderIdx + 1
1132
+ }
1133
+ }));
1186
1134
  const fileToken = (await client.drive.media.uploadAll({ data: {
1187
1135
  file_name: upload.fileName,
1188
1136
  parent_type: "docx_file",
@@ -1215,7 +1163,7 @@ async function readDoc(client, docToken) {
1215
1163
  client.docx.document.get({ path: { document_id: docToken } }),
1216
1164
  client.docx.documentBlock.list({ path: { document_id: docToken } })
1217
1165
  ]);
1218
- if (contentRes.code !== 0) throw new Error(contentRes.msg);
1166
+ assertFeishuDocApiSuccess(contentRes);
1219
1167
  const blocks = blocksRes.data?.items ?? [];
1220
1168
  const blockCounts = {};
1221
1169
  const structuredTypes = [];
@@ -1241,7 +1189,7 @@ async function createDoc(client, title, folderToken, options) {
1241
1189
  title,
1242
1190
  folder_token: folderToken
1243
1191
  } });
1244
- if (res.code !== 0) throw new Error(res.msg);
1192
+ assertFeishuDocApiSuccess(res);
1245
1193
  const doc = res.data?.document;
1246
1194
  const docToken = doc?.document_id;
1247
1195
  if (!docToken) throw new Error("Document creation succeeded but no document_id was returned");
@@ -1251,23 +1199,25 @@ async function createDoc(client, title, folderToken, options) {
1251
1199
  let requesterPermissionAdded = false;
1252
1200
  let requesterPermissionSkippedReason;
1253
1201
  let requesterPermissionError;
1254
- if (shouldGrantToRequester) if (!requesterOpenId) requesterPermissionSkippedReason = "trusted requester identity unavailable";
1255
- else try {
1256
- await client.drive.permissionMember.create({
1257
- path: { token: docToken },
1258
- params: {
1259
- type: "docx",
1260
- need_notification: false
1261
- },
1262
- data: {
1263
- member_type: "openid",
1264
- member_id: requesterOpenId,
1265
- perm: requesterPermType
1266
- }
1267
- });
1268
- requesterPermissionAdded = true;
1269
- } catch (err) {
1270
- requesterPermissionError = formatErrorMessage(err);
1202
+ if (shouldGrantToRequester) {
1203
+ if (!requesterOpenId) requesterPermissionSkippedReason = "trusted requester identity unavailable";
1204
+ else try {
1205
+ assertFeishuDocApiSuccess(await client.drive.permissionMember.create({
1206
+ path: { token: docToken },
1207
+ params: {
1208
+ type: "docx",
1209
+ need_notification: false
1210
+ },
1211
+ data: {
1212
+ member_type: "openid",
1213
+ member_id: requesterOpenId,
1214
+ perm: requesterPermType
1215
+ }
1216
+ }));
1217
+ requesterPermissionAdded = true;
1218
+ } catch (err) {
1219
+ requesterPermissionError = formatErrorMessage(err);
1220
+ }
1271
1221
  }
1272
1222
  return {
1273
1223
  document_id: docToken,
@@ -1326,11 +1276,12 @@ async function insertDoc(client, docToken, markdown, afterBlockId, maxBytes, ima
1326
1276
  document_id: docToken,
1327
1277
  block_id: afterBlockId
1328
1278
  } });
1329
- if (blockInfo.code !== 0) throw new Error(blockInfo.msg);
1279
+ assertFeishuDocApiSuccess(blockInfo);
1330
1280
  const parentId = blockInfo.data?.block?.parent_id ?? docToken;
1331
1281
  const items = [];
1332
1282
  let pageToken;
1333
- do {
1283
+ const seenPageTokens = /* @__PURE__ */ new Set();
1284
+ while (true) {
1334
1285
  const childrenRes = await client.docx.documentBlockChildren.get({
1335
1286
  path: {
1336
1287
  document_id: docToken,
@@ -1338,10 +1289,15 @@ async function insertDoc(client, docToken, markdown, afterBlockId, maxBytes, ima
1338
1289
  },
1339
1290
  params: pageToken ? { page_token: pageToken } : {}
1340
1291
  });
1341
- if (childrenRes.code !== 0) throw new Error(childrenRes.msg);
1292
+ assertFeishuDocApiSuccess(childrenRes);
1342
1293
  items.push(...childrenRes.data?.items ?? []);
1343
- pageToken = childrenRes.data?.page_token ?? void 0;
1344
- } while (pageToken);
1294
+ if (childrenRes.data?.has_more !== true) break;
1295
+ const nextPageToken = childrenRes.data.page_token?.trim();
1296
+ if (!nextPageToken) throw new Error(`Feishu document children pagination is missing its next page token for parent block "${parentId}"`);
1297
+ if (seenPageTokens.has(nextPageToken)) throw new Error(`Feishu document children pagination repeated token for parent block "${parentId}"`);
1298
+ seenPageTokens.add(nextPageToken);
1299
+ pageToken = nextPageToken;
1300
+ }
1345
1301
  const blockIndex = items.findIndex((item) => item.block_id === afterBlockId);
1346
1302
  if (blockIndex === -1) throw new Error(`after_block_id "${afterBlockId}" was not found among the children of parent block "${parentId}". Use list_blocks to verify the block ID.`);
1347
1303
  const { blocks, inserted, imagesProcessed } = await convertInsertAndProcessDocx({
@@ -1383,7 +1339,7 @@ async function createTable(client, docToken, rowSize, columnSize, parentBlockId,
1383
1339
  } }
1384
1340
  }] }
1385
1341
  });
1386
- if (res.code !== 0) throw new Error(res.msg);
1342
+ assertFeishuDocApiSuccess(res);
1387
1343
  const tableBlock = res.data?.children?.find((b) => b.block_type === 31);
1388
1344
  const cells = normalizeInsertedChildBlocks(tableBlock?.children);
1389
1345
  return {
@@ -1401,7 +1357,7 @@ async function writeTableCells(client, docToken, tableBlockId, values) {
1401
1357
  document_id: docToken,
1402
1358
  block_id: tableBlockId
1403
1359
  } });
1404
- if (tableRes.code !== 0) throw new Error(tableRes.msg);
1360
+ assertFeishuDocApiSuccess(tableRes);
1405
1361
  const tableBlock = tableRes.data?.block;
1406
1362
  if (tableBlock?.block_type !== 31) throw new Error("table_block_id is not a table block");
1407
1363
  const tableData = tableBlock.table;
@@ -1421,21 +1377,18 @@ async function writeTableCells(client, docToken, tableBlockId, values) {
1421
1377
  document_id: docToken,
1422
1378
  block_id: cellId
1423
1379
  } });
1424
- if (childrenRes.code !== 0) throw new Error(childrenRes.msg);
1380
+ assertFeishuDocApiSuccess(childrenRes);
1425
1381
  const existingChildren = childrenRes.data?.items ?? [];
1426
- if (existingChildren.length > 0) {
1427
- const delRes = await client.docx.documentBlockChildren.batchDelete({
1428
- path: {
1429
- document_id: docToken,
1430
- block_id: cellId
1431
- },
1432
- data: {
1433
- start_index: 0,
1434
- end_index: existingChildren.length
1435
- }
1436
- });
1437
- if (delRes.code !== 0) throw new Error(delRes.msg);
1438
- }
1382
+ if (existingChildren.length > 0) assertFeishuDocApiSuccess(await client.docx.documentBlockChildren.batchDelete({
1383
+ path: {
1384
+ document_id: docToken,
1385
+ block_id: cellId
1386
+ },
1387
+ data: {
1388
+ start_index: 0,
1389
+ end_index: existingChildren.length
1390
+ }
1391
+ }));
1439
1392
  const converted = await convertMarkdown(client, rowValues[c] ?? "");
1440
1393
  const { orderedBlocks } = normalizeConvertedBlockTree(converted.blocks, converted.firstLevelBlockIds);
1441
1394
  if (orderedBlocks.length > 0) await insertBlocks(client, docToken, orderedBlocks, cellId);
@@ -1464,19 +1417,17 @@ async function createTableWithValues(client, docToken, rowSize, columnSize, valu
1464
1417
  };
1465
1418
  }
1466
1419
  async function updateBlock(client, docToken, blockId, content) {
1467
- const blockInfo = await client.docx.documentBlock.get({ path: {
1420
+ assertFeishuDocApiSuccess(await client.docx.documentBlock.get({ path: {
1468
1421
  document_id: docToken,
1469
1422
  block_id: blockId
1470
- } });
1471
- if (blockInfo.code !== 0) throw new Error(blockInfo.msg);
1472
- const res = await client.docx.documentBlock.patch({
1423
+ } }));
1424
+ assertFeishuDocApiSuccess(await client.docx.documentBlock.patch({
1473
1425
  path: {
1474
1426
  document_id: docToken,
1475
1427
  block_id: blockId
1476
1428
  },
1477
1429
  data: { update_text_elements: { elements: [{ text_run: { content } }] } }
1478
- });
1479
- if (res.code !== 0) throw new Error(res.msg);
1430
+ }));
1480
1431
  return {
1481
1432
  success: true,
1482
1433
  block_id: blockId
@@ -1487,16 +1438,16 @@ async function deleteBlock(client, docToken, blockId) {
1487
1438
  document_id: docToken,
1488
1439
  block_id: blockId
1489
1440
  } });
1490
- if (blockInfo.code !== 0) throw new Error(blockInfo.msg);
1441
+ assertFeishuDocApiSuccess(blockInfo);
1491
1442
  const parentId = blockInfo.data?.block?.parent_id ?? docToken;
1492
1443
  const children = await client.docx.documentBlockChildren.get({ path: {
1493
1444
  document_id: docToken,
1494
1445
  block_id: parentId
1495
1446
  } });
1496
- if (children.code !== 0) throw new Error(children.msg);
1447
+ assertFeishuDocApiSuccess(children);
1497
1448
  const index = (children.data?.items ?? []).findIndex((item) => item.block_id === blockId);
1498
1449
  if (index === -1) throw new Error("Block not found");
1499
- const res = await client.docx.documentBlockChildren.batchDelete({
1450
+ assertFeishuDocApiSuccess(await client.docx.documentBlockChildren.batchDelete({
1500
1451
  path: {
1501
1452
  document_id: docToken,
1502
1453
  block_id: parentId
@@ -1505,8 +1456,7 @@ async function deleteBlock(client, docToken, blockId) {
1505
1456
  start_index: index,
1506
1457
  end_index: index + 1
1507
1458
  }
1508
- });
1509
- if (res.code !== 0) throw new Error(res.msg);
1459
+ }));
1510
1460
  return {
1511
1461
  success: true,
1512
1462
  deleted_block_id: blockId
@@ -1514,7 +1464,7 @@ async function deleteBlock(client, docToken, blockId) {
1514
1464
  }
1515
1465
  async function listBlocks(client, docToken) {
1516
1466
  const res = await client.docx.documentBlock.list({ path: { document_id: docToken } });
1517
- if (res.code !== 0) throw new Error(res.msg);
1467
+ assertFeishuDocApiSuccess(res);
1518
1468
  return { blocks: res.data?.items ?? [] };
1519
1469
  }
1520
1470
  async function getBlock(client, docToken, blockId) {
@@ -1522,12 +1472,12 @@ async function getBlock(client, docToken, blockId) {
1522
1472
  document_id: docToken,
1523
1473
  block_id: blockId
1524
1474
  } });
1525
- if (res.code !== 0) throw new Error(res.msg);
1475
+ assertFeishuDocApiSuccess(res);
1526
1476
  return { block: res.data?.block };
1527
1477
  }
1528
1478
  async function listAppScopes(client) {
1529
1479
  const res = await client.application.scope.list({});
1530
- if (res.code !== 0) throw new Error(res.msg);
1480
+ assertFeishuDocApiSuccess(res);
1531
1481
  const scopes = res.data?.scopes ?? [];
1532
1482
  const granted = scopes.filter((s) => s.grant_status === 1);
1533
1483
  const pending = scopes.filter((s) => s.grant_status !== 1);
@@ -1545,10 +1495,7 @@ async function listAppScopes(client) {
1545
1495
  }
1546
1496
  function registerFeishuDocTools(api) {
1547
1497
  if (!api.config) return;
1548
- const accounts = listEnabledFeishuAccounts(api.config);
1549
- if (accounts.length === 0) return;
1550
- const toolsCfg = resolveAnyEnabledFeishuToolsConfig(accounts);
1551
- const registered = [];
1498
+ const toolsCfg = resolveAnyEnabledFeishuToolsConfig(api.config);
1552
1499
  const getClient = (params, defaultAccountId) => createFeishuToolClient({
1553
1500
  api,
1554
1501
  executeParams: params,
@@ -1576,80 +1523,75 @@ function registerFeishuDocTools(api) {
1576
1523
  label: "Doc"
1577
1524
  }
1578
1525
  }));
1579
- if (toolsCfg.doc) {
1580
- api.registerTool((ctx) => {
1581
- const defaultAccountId = ctx.agentAccountId;
1582
- const mediaLocalRoots = resolveDocToolLocalRoots(ctx);
1583
- const trustedRequesterOpenId = ctx.messageChannel === "feishu" ? normalizeOptionalString(ctx.requesterSenderId) : void 0;
1584
- return {
1585
- name: "feishu_doc",
1586
- resultContentSource: "network",
1587
- label: "Feishu Doc",
1588
- description: "Feishu document operations. Actions: read, write, append, insert, create, list_blocks, get_block, update_block, delete_block, create_table, write_table_cells, create_table_with_values, insert_table_row, insert_table_column, delete_table_rows, delete_table_columns, merge_table_cells, upload_image, upload_file, color_text",
1589
- parameters: FeishuDocSchema,
1590
- async execute(_toolCallId, params) {
1591
- const p = params;
1592
- try {
1593
- if (p.action === "create" && Object.hasOwn(p, "content")) return feishuExternalToolResult({ error: "Feishu document creation does not support content. Call action \"create\" first, then call action \"write\" with the returned document_id as doc_token." });
1594
- const client = getClient(p, defaultAccountId);
1595
- switch (p.action) {
1596
- case "read": return feishuExternalToolResult(await readDoc(client, p.doc_token));
1597
- case "write": return feishuExternalToolResult(await writeDoc(client, p.doc_token, p.content, getMediaMaxBytes(p, defaultAccountId), getImageReadTimeoutMs(p, defaultAccountId), api.logger));
1598
- case "append": return feishuExternalToolResult(await appendDoc(client, p.doc_token, p.content, getMediaMaxBytes(p, defaultAccountId), getImageReadTimeoutMs(p, defaultAccountId), api.logger));
1599
- case "insert": return feishuExternalToolResult(await insertDoc(client, p.doc_token, p.content, p.after_block_id, getMediaMaxBytes(p, defaultAccountId), getImageReadTimeoutMs(p, defaultAccountId), api.logger));
1600
- case "create": return feishuExternalToolResult(await createDoc(client, p.title, p.folder_token, {
1601
- grantToRequester: p.grant_to_requester,
1602
- requesterOpenId: trustedRequesterOpenId
1603
- }));
1604
- case "list_blocks": return feishuExternalToolResult(await listBlocks(client, p.doc_token));
1605
- case "get_block": return feishuExternalToolResult(await getBlock(client, p.doc_token, p.block_id));
1606
- case "update_block": return feishuExternalToolResult(await updateBlock(client, p.doc_token, p.block_id, p.content));
1607
- case "delete_block": return feishuExternalToolResult(await deleteBlock(client, p.doc_token, p.block_id));
1608
- case "create_table": return feishuExternalToolResult(await createTable(client, p.doc_token, p.row_size, p.column_size, p.parent_block_id, p.column_width));
1609
- case "write_table_cells": return feishuExternalToolResult(await writeTableCells(client, p.doc_token, p.table_block_id, p.values));
1610
- case "create_table_with_values": return feishuExternalToolResult(await createTableWithValues(client, p.doc_token, p.row_size, p.column_size, p.values, p.parent_block_id, p.column_width));
1611
- case "upload_image": return feishuExternalToolResult(await uploadImageBlock(client, p.doc_token, getMediaMaxBytes(p, defaultAccountId), getImageReadTimeoutMs(p, defaultAccountId), mediaLocalRoots, p.url, p.file_path, p.parent_block_id, p.filename, p.index, p.image));
1612
- case "upload_file": return feishuExternalToolResult(await uploadFileBlock(client, p.doc_token, getMediaMaxBytes(p, defaultAccountId), mediaLocalRoots, p.url, p.file_path, p.parent_block_id, p.filename));
1613
- case "color_text": return feishuExternalToolResult(await updateColorText(client, p.doc_token, p.block_id, p.content));
1614
- case "insert_table_row": return feishuExternalToolResult(await insertTableRow(client, p.doc_token, p.block_id, p.row_index));
1615
- case "insert_table_column": return feishuExternalToolResult(await insertTableColumn(client, p.doc_token, p.block_id, p.column_index));
1616
- case "delete_table_rows": return feishuExternalToolResult(await deleteTableRows(client, p.doc_token, p.block_id, p.row_start, p.row_count));
1617
- case "delete_table_columns": return feishuExternalToolResult(await deleteTableColumns(client, p.doc_token, p.block_id, p.column_start, p.column_count));
1618
- case "merge_table_cells": return feishuExternalToolResult(await mergeTableCells(client, p.doc_token, p.block_id, p.row_start, p.row_end, p.column_start, p.column_end));
1619
- default: return feishuExternalToolResult({ error: "Unknown action" });
1620
- }
1621
- } catch (err) {
1622
- return feishuExternalToolResult({ error: formatErrorMessage(err) });
1623
- }
1624
- }
1625
- };
1626
- }, { name: "feishu_doc" });
1627
- registered.push("feishu_doc");
1628
- }
1629
- if (toolsCfg.scopes) {
1630
- api.registerTool((ctx) => ({
1631
- name: "feishu_app_scopes",
1526
+ if (toolsCfg.doc) api.registerTool((ctx) => {
1527
+ const defaultAccountId = ctx.agentAccountId;
1528
+ const mediaLocalRoots = resolveDocToolLocalRoots(ctx);
1529
+ const trustedRequesterOpenId = ctx.messageChannel === "feishu" ? normalizeOptionalString(ctx.requesterSenderId) : void 0;
1530
+ return {
1531
+ name: "feishu_doc",
1632
1532
  resultContentSource: "network",
1633
- label: "Feishu App Scopes",
1634
- description: "List current app permissions (scopes). Use to debug permission issues or check available capabilities.",
1635
- parameters: Type.Object({}),
1636
- async execute() {
1533
+ label: "Feishu Doc",
1534
+ description: "Feishu document operations. Actions: read, write, append, insert, create, list_blocks, get_block, update_block, delete_block, create_table, write_table_cells, create_table_with_values, insert_table_row, insert_table_column, delete_table_rows, delete_table_columns, merge_table_cells, upload_image, upload_file, color_text",
1535
+ parameters: FeishuDocSchema,
1536
+ async execute(_toolCallId, params) {
1537
+ const p = params;
1637
1538
  try {
1638
- return feishuExternalToolResult(await listAppScopes(createFeishuToolClient({
1639
- api,
1640
- defaultAccountId: ctx.agentAccountId,
1641
- requiredTool: {
1642
- family: "scopes",
1643
- label: "App Scopes"
1644
- }
1645
- })));
1539
+ if (p.action === "create" && Object.hasOwn(p, "content")) return feishuExternalToolResult({ error: "Feishu document creation does not support content. Call action \"create\" first, then call action \"write\" with the returned document_id as doc_token." });
1540
+ const client = getClient(p, defaultAccountId);
1541
+ switch (p.action) {
1542
+ case "read": return feishuExternalToolResult(await readDoc(client, p.doc_token));
1543
+ case "write": return feishuExternalToolResult(await writeDoc(client, p.doc_token, p.content, getMediaMaxBytes(p, defaultAccountId), getImageReadTimeoutMs(p, defaultAccountId), api.logger));
1544
+ case "append": return feishuExternalToolResult(await appendDoc(client, p.doc_token, p.content, getMediaMaxBytes(p, defaultAccountId), getImageReadTimeoutMs(p, defaultAccountId), api.logger));
1545
+ case "insert": return feishuExternalToolResult(await insertDoc(client, p.doc_token, p.content, p.after_block_id, getMediaMaxBytes(p, defaultAccountId), getImageReadTimeoutMs(p, defaultAccountId), api.logger));
1546
+ case "create": return feishuExternalToolResult(await createDoc(client, p.title, p.folder_token, {
1547
+ grantToRequester: p.grant_to_requester,
1548
+ requesterOpenId: trustedRequesterOpenId
1549
+ }));
1550
+ case "list_blocks": return feishuExternalToolResult(await listBlocks(client, p.doc_token));
1551
+ case "get_block": return feishuExternalToolResult(await getBlock(client, p.doc_token, p.block_id));
1552
+ case "update_block": return feishuExternalToolResult(await updateBlock(client, p.doc_token, p.block_id, p.content));
1553
+ case "delete_block": return feishuExternalToolResult(await deleteBlock(client, p.doc_token, p.block_id));
1554
+ case "create_table": return feishuExternalToolResult(await createTable(client, p.doc_token, p.row_size, p.column_size, p.parent_block_id, p.column_width));
1555
+ case "write_table_cells": return feishuExternalToolResult(await writeTableCells(client, p.doc_token, p.table_block_id, p.values));
1556
+ case "create_table_with_values": return feishuExternalToolResult(await createTableWithValues(client, p.doc_token, p.row_size, p.column_size, p.values, p.parent_block_id, p.column_width));
1557
+ case "upload_image": return feishuExternalToolResult(await uploadImageBlock(client, p.doc_token, getMediaMaxBytes(p, defaultAccountId), getImageReadTimeoutMs(p, defaultAccountId), mediaLocalRoots, p.url, p.file_path, p.parent_block_id, p.filename, p.index, p.image));
1558
+ case "upload_file": return feishuExternalToolResult(await uploadFileBlock(client, p.doc_token, getMediaMaxBytes(p, defaultAccountId), mediaLocalRoots, p.url, p.file_path, p.parent_block_id, p.filename));
1559
+ case "color_text": return feishuExternalToolResult(await updateColorText(client, p.doc_token, p.block_id, p.content));
1560
+ case "insert_table_row":
1561
+ case "insert_table_column":
1562
+ case "delete_table_rows":
1563
+ case "delete_table_columns":
1564
+ case "merge_table_cells": return feishuExternalToolResult(await patchTable(client, p));
1565
+ default: return feishuExternalToolResult({ error: "Unknown action" });
1566
+ }
1646
1567
  } catch (err) {
1647
1568
  return feishuExternalToolResult({ error: formatErrorMessage(err) });
1648
1569
  }
1649
1570
  }
1650
- }), { name: "feishu_app_scopes" });
1651
- registered.push("feishu_app_scopes");
1652
- }
1571
+ };
1572
+ }, { name: "feishu_doc" });
1573
+ if (toolsCfg.scopes) api.registerTool((ctx) => ({
1574
+ name: "feishu_app_scopes",
1575
+ resultContentSource: "network",
1576
+ label: "Feishu App Scopes",
1577
+ description: "List current app permissions (scopes). Use to debug permission issues or check available capabilities.",
1578
+ parameters: Type.Object({}),
1579
+ async execute() {
1580
+ try {
1581
+ const result = await listAppScopes(createFeishuToolClient({
1582
+ api,
1583
+ defaultAccountId: ctx.agentAccountId,
1584
+ requiredTool: {
1585
+ family: "scopes",
1586
+ label: "App Scopes"
1587
+ }
1588
+ }));
1589
+ return feishuExternalToolResult(result);
1590
+ } catch (err) {
1591
+ return feishuExternalToolResult({ error: formatErrorMessage(err) });
1592
+ }
1593
+ }
1594
+ }), { name: "feishu_app_scopes" });
1653
1595
  }
1654
1596
  //#endregion
1655
1597
  //#region extensions/feishu/src/wiki-schema.ts
@@ -1837,9 +1779,7 @@ async function renameNode(client, spaceId, nodeToken, title) {
1837
1779
  }
1838
1780
  function registerFeishuWikiTools(api) {
1839
1781
  if (!api.config) return;
1840
- const accounts = listEnabledFeishuAccounts(api.config);
1841
- if (accounts.length === 0) return;
1842
- if (!resolveAnyEnabledFeishuToolsConfig(accounts).wiki) return;
1782
+ if (!resolveAnyEnabledFeishuToolsConfig(api.config).wiki) return;
1843
1783
  api.registerTool((ctx) => {
1844
1784
  const defaultAccountId = ctx.agentAccountId;
1845
1785
  return {
@@ -1989,9 +1929,7 @@ async function removeMember(client, token, type, memberType, memberId) {
1989
1929
  }
1990
1930
  function registerFeishuPermTools(api) {
1991
1931
  if (!api.config) return;
1992
- const accounts = listEnabledFeishuAccounts(api.config);
1993
- if (accounts.length === 0) return;
1994
- if (!resolveAnyEnabledFeishuToolsConfig(accounts).perm) return;
1932
+ if (!resolveAnyEnabledFeishuToolsConfig(api.config).perm) return;
1995
1933
  api.registerTool((ctx) => {
1996
1934
  const defaultAccountId = ctx.agentAccountId;
1997
1935
  return {
@@ -2339,7 +2277,7 @@ async function createApp(client, name, folderToken, logger) {
2339
2277
  url: res.data?.app?.url,
2340
2278
  cleaned_placeholder_rows: cleanedRows,
2341
2279
  cleaned_default_fields: cleanedFields,
2342
- hint: tableId ? `Table created. Use app_token="${appToken}" and table_id="${tableId}" for other bitable tools.` : "Table created. Use feishu_bitable_get_meta to get table_id and field details."
2280
+ hint: tableId ? `Table created. Use app_token="${appToken}" and table_id="${tableId}" for other bitable tools.` : "Application created, but table metadata was not retrieved. Inspect the existing application using the returned app_token or URL; do not create it again."
2343
2281
  };
2344
2282
  }
2345
2283
  async function createField(client, appToken, tableId, fieldName, fieldType, property) {
@@ -2383,13 +2321,15 @@ async function updateRecord(client, appToken, tableId, recordId, fields) {
2383
2321
  });
2384
2322
  return { record: res.data?.record };
2385
2323
  }
2324
+ const BITABLE_APP_TOKEN_DESCRIPTION = "Bitable application token (the /base/ URL identifier, or app_token from metadata). Not the node token in a /wiki/ URL.";
2325
+ const BITABLE_RECORD_FIELDS_DESCRIPTION = "Field values keyed by field name. Format by type: Text='string', Number=123, SingleSelect='Option', MultiSelect=['A','B'], DateTime=timestamp_ms, User=[{id:'ou_xxx'}], URL={text:'Display',link:'https://...'}";
2386
2326
  const GetMetaSchema = Type.Object({ url: Type.String({ description: "Bitable URL. Supports both formats: /base/XXX?table=YYY or /wiki/XXX?table=YYY" }) });
2387
2327
  const ListFieldsSchema = Type.Object({
2388
- app_token: Type.String({ description: "Bitable app token (use feishu_bitable_get_meta to get from URL)" }),
2328
+ app_token: Type.String({ description: BITABLE_APP_TOKEN_DESCRIPTION }),
2389
2329
  table_id: Type.String({ description: "Table ID (from URL: ?table=YYY)" })
2390
2330
  });
2391
2331
  const ListRecordsSchema = Type.Object({
2392
- app_token: Type.String({ description: "Bitable app token (use feishu_bitable_get_meta to get from URL)" }),
2332
+ app_token: Type.String({ description: BITABLE_APP_TOKEN_DESCRIPTION }),
2393
2333
  table_id: Type.String({ description: "Table ID (from URL: ?table=YYY)" }),
2394
2334
  page_size: optionalPositiveIntegerSchema({
2395
2335
  description: "Number of records per page (1-500, default 100)",
@@ -2398,7 +2338,7 @@ const ListRecordsSchema = Type.Object({
2398
2338
  page_token: Type.Optional(Type.String({ description: "Pagination token from previous response" }))
2399
2339
  });
2400
2340
  const GetRecordSchema = Type.Object({
2401
- app_token: Type.String({ description: "Bitable app token (use feishu_bitable_get_meta to get from URL)" }),
2341
+ app_token: Type.String({ description: BITABLE_APP_TOKEN_DESCRIPTION }),
2402
2342
  table_id: Type.String({ description: "Table ID (from URL: ?table=YYY)" }),
2403
2343
  record_id: Type.String({ description: "Record ID to retrieve" })
2404
2344
  });
@@ -2411,16 +2351,16 @@ const BitableFieldValueSchema = Type.Unsafe({ type: [
2411
2351
  "null"
2412
2352
  ] });
2413
2353
  const CreateRecordSchema = Type.Object({
2414
- app_token: Type.String({ description: "Bitable app token (use feishu_bitable_get_meta to get from URL)" }),
2354
+ app_token: Type.String({ description: BITABLE_APP_TOKEN_DESCRIPTION }),
2415
2355
  table_id: Type.String({ description: "Table ID (from URL: ?table=YYY)" }),
2416
- fields: Type.Record(Type.String(), BitableFieldValueSchema, { description: "Field values keyed by field name. Format by type: Text='string', Number=123, SingleSelect='Option', MultiSelect=['A','B'], DateTime=timestamp_ms, User=[{id:'ou_xxx'}], URL={text:'Display',link:'https://...'}" })
2356
+ fields: Type.Record(Type.String(), BitableFieldValueSchema, { description: BITABLE_RECORD_FIELDS_DESCRIPTION })
2417
2357
  });
2418
2358
  const CreateAppSchema = Type.Object({
2419
2359
  name: Type.String({ description: "Name for the new Bitable application" }),
2420
2360
  folder_token: Type.Optional(Type.String({ description: "Optional folder token to place the Bitable in a specific folder" }))
2421
2361
  });
2422
2362
  const CreateFieldSchema = Type.Object({
2423
- app_token: Type.String({ description: "Bitable app token (use feishu_bitable_get_meta to get from URL, or feishu_bitable_create_app to create new)" }),
2363
+ app_token: Type.String({ description: BITABLE_APP_TOKEN_DESCRIPTION }),
2424
2364
  table_id: Type.String({ description: "Table ID (from URL: ?table=YYY)" }),
2425
2365
  field_name: Type.String({ description: "Name for the new field" }),
2426
2366
  field_type: Type.Number({
@@ -2430,16 +2370,14 @@ const CreateFieldSchema = Type.Object({
2430
2370
  property: Type.Optional(Type.Record(Type.String(), BitableFieldValueSchema, { description: "Field-specific properties (e.g., options for SingleSelect, format for Number)" }))
2431
2371
  });
2432
2372
  const UpdateRecordSchema = Type.Object({
2433
- app_token: Type.String({ description: "Bitable app token (use feishu_bitable_get_meta to get from URL)" }),
2373
+ app_token: Type.String({ description: BITABLE_APP_TOKEN_DESCRIPTION }),
2434
2374
  table_id: Type.String({ description: "Table ID (from URL: ?table=YYY)" }),
2435
2375
  record_id: Type.String({ description: "Record ID to update" }),
2436
- fields: Type.Record(Type.String(), BitableFieldValueSchema, { description: "Field values to update (same format as create_record)" })
2376
+ fields: Type.Record(Type.String(), BitableFieldValueSchema, { description: BITABLE_RECORD_FIELDS_DESCRIPTION })
2437
2377
  });
2438
2378
  function registerFeishuBitableTools(api) {
2439
2379
  if (!api.config) return;
2440
- const accounts = listEnabledFeishuAccounts(api.config);
2441
- if (accounts.length === 0) return;
2442
- if (!resolveAnyEnabledFeishuToolsConfig(accounts).bitable) return;
2380
+ if (!resolveAnyEnabledFeishuToolsConfig(api.config).bitable) return;
2443
2381
  const getClient = (params, defaultAccountId) => createFeishuToolClient({
2444
2382
  api,
2445
2383
  executeParams: params,