@yanhaidao/wecom 2.3.160 → 2.3.180

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 (39) hide show
  1. package/README.md +235 -399
  2. package/SKILLS_CAL.md +895 -0
  3. package/SKILLS_DOC.md +2136 -0
  4. package/changelog/v2.3.18.md +22 -0
  5. package/index.ts +39 -3
  6. package/package.json +2 -3
  7. package/src/agent/handler.event-filter.test.ts +11 -0
  8. package/src/agent/handler.ts +732 -643
  9. package/src/app/account-runtime.ts +46 -20
  10. package/src/app/index.ts +19 -1
  11. package/src/capability/calendar/SKILLS_CHECKLIST.md +251 -0
  12. package/src/capability/calendar/client.ts +815 -0
  13. package/src/capability/calendar/index.ts +3 -0
  14. package/src/capability/calendar/schema.ts +417 -0
  15. package/src/capability/calendar/tool.ts +417 -0
  16. package/src/capability/calendar/types.ts +309 -0
  17. package/src/capability/doc/client.ts +567 -62
  18. package/src/capability/doc/schema.ts +419 -318
  19. package/src/capability/doc/tool.ts +1510 -1178
  20. package/src/capability/doc/types.ts +130 -14
  21. package/src/capability/mcp/index.ts +10 -0
  22. package/src/capability/mcp/schema.ts +107 -0
  23. package/src/capability/mcp/tool.ts +170 -0
  24. package/src/capability/mcp/transport.ts +394 -0
  25. package/src/channel.ts +70 -28
  26. package/src/config/schema.ts +71 -102
  27. package/src/outbound.test.ts +91 -14
  28. package/src/outbound.ts +143 -30
  29. package/src/runtime/reply-orchestrator.test.ts +35 -2
  30. package/src/runtime/reply-orchestrator.ts +14 -2
  31. package/src/runtime/session-manager.ts +20 -6
  32. package/src/runtime/source-registry.ts +165 -0
  33. package/src/transport/bot-ws/media.ts +269 -0
  34. package/src/transport/bot-ws/reply.test.ts +85 -17
  35. package/src/transport/bot-ws/reply.ts +109 -21
  36. package/src/transport/bot-ws/sdk-adapter.test.ts +64 -1
  37. package/src/transport/bot-ws/sdk-adapter.ts +88 -12
  38. package/.claude/settings.local.json +0 -11
  39. package/docs/update-content-fix.md +0 -135
@@ -391,19 +391,23 @@ export class WecomDocClient {
391
391
  // Need to check current auth status
392
392
  try {
393
393
  const currentAuth = await this.getDocAuth({ agent, docId });
394
- const viewerUserIds = new Set(
395
- (currentAuth.docMembers || [])
396
- .filter((m: any) => m.type === 1 && m.userid)
397
- .map((m: any) => m.userid)
398
- );
399
- const newCollaboratorUserIds = normalizeDocMemberEntryList(collaborators)
400
- .map(e => e.userid)
401
- .filter(Boolean) as string[];
394
+ // Build a map of viewer entries with their full structure (preserving type and other fields)
395
+ const viewerMap = new Map<string, any>();
396
+ (currentAuth.docMembers || [])
397
+ .filter((m: any) => m.userid)
398
+ .forEach((m: any) => viewerMap.set(m.userid, m));
402
399
 
403
- // Auto-add viewers who are being promoted to collaborators
404
- const autoRemoveViewers = newCollaboratorUserIds
405
- .filter(userid => viewerUserIds.has(userid))
406
- .map(userid => ({ userid, type: 1 }));
400
+ // Normalize new collaborators to get their userids
401
+ const newCollaboratorEntries = normalizeDocMemberEntryList(collaborators);
402
+
403
+ // Auto-add viewers who are being promoted to collaborators, preserving their original structure
404
+ const autoRemoveViewers = newCollaboratorEntries
405
+ .filter(entry => entry.userid && viewerMap.has(entry.userid))
406
+ .map(entry => {
407
+ // Preserve the original viewer's full structure (type, userid, etc.)
408
+ const originalViewer = viewerMap.get(entry.userid!);
409
+ return { ...originalViewer };
410
+ });
407
411
 
408
412
  if (autoRemoveViewers.length > 0) {
409
413
  finalRemoveViewers = autoRemoveViewers;
@@ -488,6 +492,16 @@ export class WecomDocClient {
488
492
  throw new Error("问题数量不能超过 200 个");
489
493
  }
490
494
 
495
+ // Auto-fill status fields for questions and options
496
+ questions.forEach((q: any) => {
497
+ if (q.status === undefined) q.status = 1;
498
+ if (Array.isArray(q.option_item)) {
499
+ q.option_item.forEach((opt: any) => {
500
+ if (opt.status === undefined) opt.status = 1;
501
+ });
502
+ }
503
+ });
504
+
491
505
  // Validate each question
492
506
  questions.forEach((q: any, index: number) => {
493
507
  if (!q.question_id || !Number.isInteger(q.question_id) || q.question_id < 1) {
@@ -505,6 +519,9 @@ export class WecomDocClient {
505
519
  if (q.must_reply === undefined || typeof q.must_reply !== 'boolean') {
506
520
  throw new Error(`第${index + 1}个问题:must_reply 必填且必须为布尔值`);
507
521
  }
522
+ if (q.status !== undefined && ![1, 2].includes(q.status)) {
523
+ throw new Error(`第${index + 1}个问题:status 必须为 1(正常) 或 2(删除)`);
524
+ }
508
525
 
509
526
  // Validate option_item for single/multiple/dropdown questions
510
527
  const requiresOptions = [2, 3, 15].includes(q.reply_type); // 单选/多选/下拉列表
@@ -520,6 +537,9 @@ export class WecomDocClient {
520
537
  if (!opt.value || readString(opt.value).length === 0) {
521
538
  throw new Error(`第${index + 1}个问题的第${optIndex + 1}个选项:value 必填`);
522
539
  }
540
+ if (opt.status !== undefined && ![1, 2].includes(opt.status)) {
541
+ throw new Error(`第${index + 1}个问题的第${optIndex + 1}个选项:status 必须为 1(正常) 或 2(删除)`);
542
+ }
523
543
  });
524
544
  }
525
545
 
@@ -546,6 +566,13 @@ export class WecomDocClient {
546
566
  console.warn("警告:timed_finish 与 timed_repeat_info 互斥,若都填优先定时重复");
547
567
  }
548
568
 
569
+ // Validate timed_repeat_info.enable=true requires fill_in_range
570
+ if (formSetting.timed_repeat_info?.enable) {
571
+ if (!formSetting.fill_in_range || (!formSetting.fill_in_range.userids?.length && !formSetting.fill_in_range.departmentids?.length)) {
572
+ throw new Error("timed_repeat_info 开启时,fill_in_range 必填(需指定 userids 或 departmentids)");
573
+ }
574
+ }
575
+
549
576
  // Build payload
550
577
  const payload: Record<string, unknown> = {
551
578
  form_info: {
@@ -563,8 +590,8 @@ export class WecomDocClient {
563
590
  if (normalizedFatherId) payload.fatherid = normalizedFatherId;
564
591
 
565
592
  const json = await this.postWecomDocApi({
566
- path: "/cgi-bin/wedoc/create_collect",
567
- actionLabel: "create_collect",
593
+ path: "/cgi-bin/wedoc/create_form",
594
+ actionLabel: "create_form",
568
595
  agent,
569
596
  body: payload,
570
597
  });
@@ -605,6 +632,16 @@ export class WecomDocClient {
605
632
  throw new Error("问题数量不能超过 200 个");
606
633
  }
607
634
 
635
+ // Auto-fill status fields for questions and options
636
+ questions.forEach((q: any) => {
637
+ if (q.status === undefined) q.status = 1;
638
+ if (Array.isArray(q.option_item)) {
639
+ q.option_item.forEach((opt: any) => {
640
+ if (opt.status === undefined) opt.status = 1;
641
+ });
642
+ }
643
+ });
644
+
608
645
  // Validate each question (same as createCollect)
609
646
  questions.forEach((q: any, index: number) => {
610
647
  if (!q.question_id || !Number.isInteger(q.question_id) || q.question_id < 1) {
@@ -658,8 +695,8 @@ export class WecomDocClient {
658
695
  }
659
696
 
660
697
  const json = await this.postWecomDocApi({
661
- path: "/cgi-bin/wedoc/modify_collect",
662
- actionLabel: "modify_collect",
698
+ path: "/cgi-bin/wedoc/modify_form",
699
+ actionLabel: "modify_form",
663
700
  agent,
664
701
  body: payload,
665
702
  });
@@ -696,6 +733,12 @@ export class WecomDocClient {
696
733
  .map((item) => Number(item))
697
734
  .filter((item) => Number.isFinite(item))
698
735
  : [];
736
+
737
+ // Official API limit: ≤100 answer IDs
738
+ if (normalizedAnswerIds.length > 100) {
739
+ throw new Error(`answer_ids 不能超过 100 个,当前:${normalizedAnswerIds.length}`);
740
+ }
741
+
699
742
  const payload: Record<string, unknown> = {
700
743
  repeated_id: normalizedRepeatedId,
701
744
  };
@@ -724,6 +767,32 @@ export class WecomDocClient {
724
767
  if (payload.length === 0) {
725
768
  throw new Error("requests required");
726
769
  }
770
+
771
+ // Validate each request per official API
772
+ payload.forEach((req: any, index: number) => {
773
+ const reqType = Number(req.req_type);
774
+
775
+ // req_type=2: Get submitted list - requires start_time and end_time (same day timestamps)
776
+ if (reqType === 2) {
777
+ if (!req.start_time || !req.end_time) {
778
+ throw new Error(`第${index + 1}个请求:req_type=2 时必须提供 start_time 和 end_time(当天时间戳)`);
779
+ }
780
+ // Validate timestamps are numbers
781
+ if (!Number.isFinite(Number(req.start_time)) || !Number.isFinite(Number(req.end_time))) {
782
+ throw new Error(`第${index + 1}个请求:start_time 和 end_time 必须是有效时间戳`);
783
+ }
784
+ // Validate end_time >= start_time
785
+ if (Number(req.end_time) < Number(req.start_time)) {
786
+ throw new Error(`第${index + 1}个请求:end_time 必须大于等于 start_time`);
787
+ }
788
+ }
789
+
790
+ // Validate repeated_id is present
791
+ if (!req.repeated_id) {
792
+ throw new Error(`第${index + 1}个请求:repeated_id 必填`);
793
+ }
794
+ });
795
+
727
796
  const json = await this.postWecomDocApi({
728
797
  path: "/cgi-bin/wedoc/get_form_statistic",
729
798
  actionLabel: "get_form_statistic",
@@ -758,30 +827,90 @@ export class WecomDocClient {
758
827
  }
759
828
 
760
829
  async updateDocContent(params: { agent: ResolvedAgentAccount; docId: string; requests: UpdateRequest[]; version?: number; batchMode?: boolean }) {
761
- const { agent, docId, requests, version, batchMode } = params;
830
+ const { agent, docId, requests, version } = params;
762
831
 
763
832
  // Validate requests structure basic check
764
833
  const requestList = readArray(requests);
765
834
  if (requestList.length === 0) {
766
835
  throw new Error("requests list cannot be empty");
767
836
  }
768
-
769
- const body: Record<string, unknown> = {
770
- docid: readString(docId),
771
- requests: requestList,
772
- };
773
- // version is optional but recommended for concurrency control
837
+
838
+ // Validate version difference (≤100 per official API)
774
839
  if (version !== undefined && version !== null) {
775
- body.version = Number(version);
840
+ const currentContent = await this.getDocContent({ agent, docId });
841
+ const versionDiff = Math.abs(currentContent.version - version);
842
+ if (versionDiff > 100) {
843
+ throw new Error(`version 与最新版本差值不能超过 100(当前版本:${currentContent.version},传入版本:${version},差值:${versionDiff})`);
844
+ }
776
845
  }
777
846
 
778
- const json = await this.postWecomDocApi({
779
- path: "/cgi-bin/wedoc/document/batch_update",
780
- actionLabel: "update_doc_content",
781
- agent,
782
- body,
783
- }) as BatchUpdateDocResponse;
784
- return { raw: json };
847
+ // Validate each request's ranges count (≤10 per official API)
848
+ requestList.forEach((req: any, index: number) => {
849
+ if (req.replace_text?.ranges && req.replace_text.ranges.length > 10) {
850
+ throw new Error(`第${index + 1}个操作:replace_text.ranges 不能超过 10 个`);
851
+ }
852
+ if (req.update_text_property?.ranges && req.update_text_property.ranges.length > 10) {
853
+ throw new Error(`第${index + 1}个操作:update_text_property.ranges 不能超过 10 个`);
854
+ }
855
+ // Validate insert_table limits
856
+ if (req.insert_table) {
857
+ const { rows, cols } = req.insert_table;
858
+ if (rows > 100) throw new Error(`第${index + 1}个操作:insert_table 行数不能超过 100`);
859
+ if (cols > 60) throw new Error(`第${index + 1}个操作:insert_table 列数不能超过 60`);
860
+ if (rows * cols > 1000) throw new Error(`第${index + 1}个操作:insert_table 单元格总数不能超过 1000`);
861
+ }
862
+ });
863
+
864
+ // Official API limit: ≤30 operations per batch
865
+ const MAX_OPERATIONS = 30;
866
+ if (requestList.length <= MAX_OPERATIONS) {
867
+ // Single batch
868
+ const body: Record<string, unknown> = {
869
+ docid: readString(docId),
870
+ requests: requestList,
871
+ };
872
+ if (version !== undefined && version !== null) {
873
+ body.version = Number(version);
874
+ }
875
+
876
+ const json = await this.postWecomDocApi({
877
+ path: "/cgi-bin/wedoc/document/batch_update",
878
+ actionLabel: "update_doc_content",
879
+ agent,
880
+ body,
881
+ }) as BatchUpdateDocResponse;
882
+ return { raw: json, batches: 1 };
883
+ }
884
+
885
+ // Auto-batch: split into multiple requests
886
+ // Note: Each batch updates the version, so we need to get latest version for each batch
887
+ const batches: BatchUpdateDocResponse[] = [];
888
+ for (let i = 0; i < requestList.length; i += MAX_OPERATIONS) {
889
+ const batchRequests = requestList.slice(i, i + MAX_OPERATIONS);
890
+
891
+ // Get latest version before each batch (except first if version provided)
892
+ let currentVersion = version;
893
+ if (i > 0 || currentVersion === undefined || currentVersion === null) {
894
+ const content = await this.getDocContent({ agent, docId });
895
+ currentVersion = content.version;
896
+ }
897
+
898
+ const body: Record<string, unknown> = {
899
+ docid: readString(docId),
900
+ requests: batchRequests,
901
+ version: currentVersion,
902
+ };
903
+
904
+ const json = await this.postWecomDocApi({
905
+ path: "/cgi-bin/wedoc/document/batch_update",
906
+ actionLabel: `update_doc_content_batch_${Math.floor(i / MAX_OPERATIONS) + 1}`,
907
+ agent,
908
+ body,
909
+ }) as BatchUpdateDocResponse;
910
+ batches.push(json);
911
+ }
912
+
913
+ return { raw: batches[batches.length - 1], batches: batches.length, allBatches: batches };
785
914
  }
786
915
 
787
916
  // --- Spreadsheet Operations ---
@@ -824,8 +953,9 @@ export class WecomDocClient {
824
953
  startRow?: number;
825
954
  startColumn?: number;
826
955
  gridData?: any;
956
+ requests?: any[]; // For direct batch_update with multiple operations
827
957
  }) {
828
- const { agent, docId, sheetId, startRow = 0, startColumn = 0, gridData } = params;
958
+ const { agent, docId, sheetId, startRow = 0, startColumn = 0, gridData, requests } = params;
829
959
 
830
960
  // Validate required docId
831
961
  const normalizedDocId = readString(docId);
@@ -839,15 +969,72 @@ export class WecomDocClient {
839
969
  throw new Error('sheetId is required');
840
970
  }
841
971
 
972
+ // Handle direct requests (for multiple operations)
973
+ if (requests && requests.length > 0) {
974
+ // Official API limit: ≤5 operations per batch
975
+ const MAX_OPERATIONS = 5;
976
+
977
+ // Validate each request
978
+ requests.forEach((req: any, index: number) => {
979
+ if (req.update_range_request?.grid_data?.rows) {
980
+ const rows = req.update_range_request.grid_data.rows;
981
+ const rowCount = rows.length;
982
+ const rowWidths = rows.map((row: any) => row.values?.length || 0);
983
+ const columnCount = rowWidths.length > 0 ? Math.max(...rowWidths) : 0;
984
+ const totalCells = rowWidths.reduce((sum: number, width: number) => sum + width, 0);
985
+
986
+ if (rowCount > 1000) throw new Error(`第${index + 1}个操作:行数不能超过 1000`);
987
+ if (columnCount > 200) throw new Error(`第${index + 1}个操作:列数不能超过 200`);
988
+ if (totalCells > 10000) throw new Error(`第${index + 1}个操作:单元格总数不能超过 10000`);
989
+ }
990
+ });
991
+
992
+ if (requests.length > MAX_OPERATIONS) {
993
+ throw new Error(`单次批量更新最多${MAX_OPERATIONS}个操作,当前:${requests.length}`);
994
+ }
995
+
996
+ const body = {
997
+ docid: normalizedDocId,
998
+ requests: requests
999
+ };
1000
+
1001
+ const json = await this.postWecomDocApi({
1002
+ path: "/cgi-bin/wedoc/spreadsheet/batch_update",
1003
+ actionLabel: "spreadsheet_batch_update",
1004
+ agent, body,
1005
+ });
1006
+ return {
1007
+ raw: json,
1008
+ docId: normalizedDocId,
1009
+ operations: requests.length
1010
+ };
1011
+ }
1012
+
1013
+ // Handle single gridData update
1014
+ if (!gridData) {
1015
+ throw new Error('gridData or requests is required');
1016
+ }
1017
+
842
1018
  // Build GridData per official API
843
1019
  // gridData.rows[i].values[j] must be: {cell_value: {text} | {link: {text, url}}, cell_format?: {...}}
844
- const rows = (gridData?.rows || []).map((row: any) => ({
1020
+ const rows = (gridData.rows || []).map((row: any) => ({
845
1021
  values: (row.values || []).map((cell: any) => {
846
1022
  // If already CellData format, use as-is
847
1023
  if (cell && typeof cell === 'object' && cell.cell_value) {
848
1024
  return cell;
849
1025
  }
850
- // Otherwise wrap primitive as CellValue
1026
+ // Support link simplified format: { url: '...', text: '...' }
1027
+ if (cell && typeof cell === 'object' && cell.url) {
1028
+ return {
1029
+ cell_value: {
1030
+ link: {
1031
+ url: String(cell.url),
1032
+ text: String(cell.text ?? cell.url)
1033
+ }
1034
+ }
1035
+ };
1036
+ }
1037
+ // Otherwise wrap primitive as CellValue with text
851
1038
  return { cell_value: { text: String(cell ?? '') } };
852
1039
  })
853
1040
  }));
@@ -874,8 +1061,7 @@ export class WecomDocClient {
874
1061
  rows: rows
875
1062
  };
876
1063
 
877
- // Build batch_update request per official API
878
- // Note: requests array length ≤ 5 per API spec
1064
+ // Build batch_update request per official API (single operation)
879
1065
  const body = {
880
1066
  docid: normalizedDocId,
881
1067
  requests: [{
@@ -893,7 +1079,7 @@ export class WecomDocClient {
893
1079
  });
894
1080
  return {
895
1081
  raw: json,
896
- docId: body.docid as string,
1082
+ docId: normalizedDocId,
897
1083
  updatedCells: json.data?.responses?.[0]?.update_range_response?.updated_cells || 0
898
1084
  };
899
1085
  }
@@ -978,13 +1164,18 @@ export class WecomDocClient {
978
1164
  return { raw: json, docId };
979
1165
  }
980
1166
 
981
- async smartTableGetSheets(params: { agent: ResolvedAgentAccount; docId: string }) {
982
- const { agent, docId } = params;
1167
+ async smartTableGetSheets(params: { agent: ResolvedAgentAccount; docId: string; sheet_id?: string; need_all_type_sheet?: boolean }) {
1168
+ const { agent, docId, sheet_id, need_all_type_sheet } = params;
1169
+ const payload: Record<string, unknown> = {
1170
+ docid: readString(docId),
1171
+ };
1172
+ if (sheet_id) payload.sheet_id = sheet_id;
1173
+ if (need_all_type_sheet !== undefined) payload.need_all_type_sheet = need_all_type_sheet;
983
1174
  const json = await this.postWecomDocApi({
984
1175
  path: "/cgi-bin/wedoc/smartsheet/get_sheet",
985
1176
  actionLabel: "smartsheet_get_sheet",
986
1177
  agent,
987
- body: { docid: readString(docId) },
1178
+ body: payload,
988
1179
  });
989
1180
  return {
990
1181
  raw: json,
@@ -1006,34 +1197,278 @@ export class WecomDocClient {
1006
1197
  const { agent, docId, sheetId, title } = params;
1007
1198
  return this.smartTableOperate({ agent, docId, operation: "update_sheet", bodyData: { properties: { sheet_id: sheetId, title } } });
1008
1199
  }
1009
- async smartTableAddView(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; view_title: string; view_type: string; property_gantt?: any; property_calendar?: any }) {
1010
- const { agent, docId, sheetId, view_title, view_type, property_gantt, property_calendar } = params;
1011
- return this.smartTableOperate({ agent, docId, operation: "add_view", bodyData: { sheet_id: sheetId, view_title, view_type, property_gantt, property_calendar } });
1200
+ async smartTableAddView(params: {
1201
+ agent: ResolvedAgentAccount;
1202
+ docId: string;
1203
+ sheetId: string;
1204
+ view_title: string;
1205
+ view_type: string;
1206
+ property?: any; // ViewProperty: sort_spec, filter_spec, group_spec, etc.
1207
+ property_gantt?: any; // Deprecated, use property instead
1208
+ property_calendar?: any; // Deprecated, use property instead
1209
+ }) {
1210
+ const { agent, docId, sheetId, view_title, view_type, property, property_gantt, property_calendar } = params;
1211
+ const payload: Record<string, unknown> = {
1212
+ docid: readString(docId),
1213
+ sheet_id: readString(sheetId),
1214
+ view_title: readString(view_title),
1215
+ view_type: readString(view_type),
1216
+ };
1217
+ if (property && typeof property === 'object') {
1218
+ payload.property = property;
1219
+ }
1220
+ // Support deprecated property_gantt/property_calendar for backward compatibility
1221
+ if (property_gantt) payload.property_gantt = property_gantt;
1222
+ if (property_calendar) payload.property_calendar = property_calendar;
1223
+
1224
+ const json = await this.postWecomDocApi({
1225
+ path: "/cgi-bin/wedoc/smartsheet/add_view",
1226
+ actionLabel: "smartsheet_add_view",
1227
+ agent,
1228
+ body: payload,
1229
+ });
1230
+ return {
1231
+ raw: json,
1232
+ view: json.view,
1233
+ };
1012
1234
  }
1013
1235
 
1014
- async smartTableUpdateView(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; view_id: string; view_title?: string; property_gantt?: any; property_calendar?: any }) {
1015
- const { agent, docId, sheetId, view_id, view_title, property_gantt, property_calendar } = params;
1016
- return this.smartTableOperate({ agent, docId, operation: "update_view", bodyData: { sheet_id: sheetId, view_id, view_title, property_gantt, property_calendar } });
1236
+ async smartTableUpdateView(params: {
1237
+ agent: ResolvedAgentAccount;
1238
+ docId: string;
1239
+ sheetId: string;
1240
+ view_id: string;
1241
+ view_title?: string;
1242
+ property?: any; // ViewProperty: sort_spec, filter_spec, group_spec, etc.
1243
+ property_gantt?: any; // Deprecated, use property instead
1244
+ property_calendar?: any; // Deprecated, use property instead
1245
+ }) {
1246
+ const { agent, docId, sheetId, view_id, view_title, property, property_gantt, property_calendar } = params;
1247
+ const payload: Record<string, unknown> = {
1248
+ docid: readString(docId),
1249
+ sheet_id: readString(sheetId),
1250
+ view_id: readString(view_id),
1251
+ };
1252
+ if (view_title) payload.view_title = readString(view_title);
1253
+ if (property && typeof property === 'object') {
1254
+ payload.property = property;
1255
+ }
1256
+ // Support deprecated property_gantt/property_calendar for backward compatibility
1257
+ if (property_gantt) payload.property_gantt = property_gantt;
1258
+ if (property_calendar) payload.property_calendar = property_calendar;
1259
+
1260
+ const json = await this.postWecomDocApi({
1261
+ path: "/cgi-bin/wedoc/smartsheet/update_view",
1262
+ actionLabel: "smartsheet_update_view",
1263
+ agent,
1264
+ body: payload,
1265
+ });
1266
+ return {
1267
+ raw: json,
1268
+ view: json.view,
1269
+ };
1017
1270
  }
1018
1271
 
1019
1272
  async smartTableDelView(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; view_ids: string[] }) {
1020
1273
  const { agent, docId, sheetId, view_ids } = params;
1021
- return this.smartTableOperate({ agent, docId, operation: "delete_views", bodyData: { sheet_id: sheetId, view_ids } });
1274
+
1275
+ if (!Array.isArray(view_ids) || view_ids.length === 0) {
1276
+ throw new Error("view_ids 必须是非空数组");
1277
+ }
1278
+
1279
+ return this.postWecomDocApi({
1280
+ path: "/cgi-bin/wedoc/smartsheet/delete_views",
1281
+ actionLabel: "smartsheet_del_view",
1282
+ agent,
1283
+ body: {
1284
+ docid: readString(docId),
1285
+ sheet_id: readString(sheetId),
1286
+ view_ids: view_ids,
1287
+ },
1288
+ });
1289
+ }
1290
+
1291
+ async smartTableGetViews(params: {
1292
+ agent: ResolvedAgentAccount;
1293
+ docId: string;
1294
+ sheetId: string;
1295
+ view_ids?: string[];
1296
+ offset?: number;
1297
+ limit?: number;
1298
+ }) {
1299
+ const { agent, docId, sheetId, view_ids, offset, limit } = params;
1300
+ const payload: Record<string, unknown> = {
1301
+ docid: readString(docId),
1302
+ sheet_id: readString(sheetId),
1303
+ };
1304
+ if (view_ids && Array.isArray(view_ids)) payload.view_ids = view_ids;
1305
+ if (offset !== undefined) payload.offset = offset;
1306
+ if (limit !== undefined) payload.limit = limit;
1307
+
1308
+ const json = await this.postWecomDocApi({
1309
+ path: "/cgi-bin/wedoc/smartsheet/get_views",
1310
+ actionLabel: "smartsheet_get_views",
1311
+ agent,
1312
+ body: payload,
1313
+ });
1314
+ return {
1315
+ raw: json,
1316
+ views: readArray(json.views),
1317
+ total: json.total,
1318
+ has_more: json.has_more,
1319
+ next: json.next,
1320
+ };
1022
1321
  }
1023
1322
 
1024
- async smartTableAddFields(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; fields: any[] }) {
1323
+ async smartTableAddFields(params: {
1324
+ agent: ResolvedAgentAccount;
1325
+ docId: string;
1326
+ sheetId: string;
1327
+ fields: any[];
1328
+ }) {
1025
1329
  const { agent, docId, sheetId, fields } = params;
1026
- return this.smartTableOperate({ agent, docId, operation: "add_fields", bodyData: { sheet_id: sheetId, fields } });
1330
+
1331
+ // Validate fields per official API spec
1332
+ if (!Array.isArray(fields) || fields.length === 0) {
1333
+ throw new Error("fields 必须是非空数组");
1334
+ }
1335
+
1336
+ // Validate each field has required field_title and field_type
1337
+ fields.forEach((field: any, index: number) => {
1338
+ if (!field.field_title) {
1339
+ throw new Error(`第${index + 1}个字段:field_title 必填`);
1340
+ }
1341
+ if (!field.field_type) {
1342
+ throw new Error(`第${index + 1}个字段:field_type 必填`);
1343
+ }
1344
+ // Validate field_type is valid enum value
1345
+ const validFieldTypes = [
1346
+ 'FIELD_TYPE_TEXT', 'FIELD_TYPE_NUMBER', 'FIELD_TYPE_CHECKBOX',
1347
+ 'FIELD_TYPE_DATE_TIME', 'FIELD_TYPE_IMAGE', 'FIELD_TYPE_ATTACHMENT',
1348
+ 'FIELD_TYPE_USER', 'FIELD_TYPE_URL', 'FIELD_TYPE_SELECT',
1349
+ 'FIELD_TYPE_CREATED_USER', 'FIELD_TYPE_MODIFIED_USER', 'FIELD_TYPE_CREATED_TIME',
1350
+ 'FIELD_TYPE_MODIFIED_TIME', 'FIELD_TYPE_PROGRESS', 'FIELD_TYPE_PHONE_NUMBER',
1351
+ 'FIELD_TYPE_EMAIL', 'FIELD_TYPE_SINGLE_SELECT', 'FIELD_TYPE_REFERENCE',
1352
+ 'FIELD_TYPE_LOCATION', 'FIELD_TYPE_CURRENCY', 'FIELD_TYPE_WWGROUP',
1353
+ 'FIELD_TYPE_AUTONUMBER', 'FIELD_TYPE_PERCENTAGE', 'FIELD_TYPE_BARCODE'
1354
+ ];
1355
+ if (!validFieldTypes.includes(field.field_type)) {
1356
+ throw new Error(`第${index + 1}个字段:field_type 必须是有效的字段类型(见 FieldType 枚举)`);
1357
+ }
1358
+ });
1359
+
1360
+ const json = await this.postWecomDocApi({
1361
+ path: "/cgi-bin/wedoc/smartsheet/add_fields",
1362
+ actionLabel: "smartsheet_add_fields",
1363
+ agent,
1364
+ body: {
1365
+ docid: readString(docId),
1366
+ sheet_id: readString(sheetId),
1367
+ fields: fields,
1368
+ },
1369
+ });
1370
+ return {
1371
+ raw: json,
1372
+ fields: readArray(json.fields),
1373
+ };
1374
+ }
1375
+
1376
+ async smartTableUpdateFields(params: {
1377
+ agent: ResolvedAgentAccount;
1378
+ docId: string;
1379
+ sheetId: string;
1380
+ fields: any[];
1381
+ }) {
1382
+ const { agent, docId, sheetId, fields } = params;
1383
+
1384
+ // Validate fields per official API spec
1385
+ if (!Array.isArray(fields) || fields.length === 0) {
1386
+ throw new Error("fields 必须是非空数组");
1387
+ }
1388
+
1389
+ // Validate each field has required field_id and field_type
1390
+ fields.forEach((field: any, index: number) => {
1391
+ if (!field.field_id) {
1392
+ throw new Error(`第${index + 1}个字段:field_id 必填`);
1393
+ }
1394
+ if (!field.field_type) {
1395
+ throw new Error(`第${index + 1}个字段:field_type 必填`);
1396
+ }
1397
+ // field_title is optional for update, but at least one of field_title or property_* must be provided
1398
+ if (!field.field_title && !Object.keys(field).some(key => key.startsWith('property_'))) {
1399
+ throw new Error(`第${index + 1}个字段:field_title 或 property_* 属性至少提供一个`);
1400
+ }
1401
+ });
1402
+
1403
+ const json = await this.postWecomDocApi({
1404
+ path: "/cgi-bin/wedoc/smartsheet/update_fields",
1405
+ actionLabel: "smartsheet_update_fields",
1406
+ agent,
1407
+ body: {
1408
+ docid: readString(docId),
1409
+ sheet_id: readString(sheetId),
1410
+ fields: fields,
1411
+ },
1412
+ });
1413
+ return {
1414
+ raw: json,
1415
+ fields: readArray(json.fields),
1416
+ };
1027
1417
  }
1028
1418
 
1029
1419
  async smartTableDelFields(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; field_ids: string[] }) {
1030
1420
  const { agent, docId, sheetId, field_ids } = params;
1031
- return this.smartTableOperate({ agent, docId, operation: "delete_fields", bodyData: { sheet_id: sheetId, field_ids } });
1421
+
1422
+ if (!Array.isArray(field_ids) || field_ids.length === 0) {
1423
+ throw new Error("field_ids 必须是非空数组");
1424
+ }
1425
+
1426
+ return this.postWecomDocApi({
1427
+ path: "/cgi-bin/wedoc/smartsheet/delete_fields",
1428
+ actionLabel: "smartsheet_del_fields",
1429
+ agent,
1430
+ body: {
1431
+ docid: readString(docId),
1432
+ sheet_id: readString(sheetId),
1433
+ field_ids: field_ids,
1434
+ },
1435
+ });
1032
1436
  }
1033
1437
 
1034
- async smartTableUpdateFields(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; fields: any[] }) {
1035
- const { agent, docId, sheetId, fields } = params;
1036
- return this.smartTableOperate({ agent, docId, operation: "update_fields", bodyData: { sheet_id: sheetId, fields } });
1438
+ async smartTableGetFields(params: {
1439
+ agent: ResolvedAgentAccount;
1440
+ docId: string;
1441
+ sheetId: string;
1442
+ view_id?: string;
1443
+ field_ids?: string[];
1444
+ field_titles?: string[];
1445
+ offset?: number;
1446
+ limit?: number;
1447
+ }) {
1448
+ const { agent, docId, sheetId, view_id, field_ids, field_titles, offset, limit } = params;
1449
+ const payload: Record<string, unknown> = {
1450
+ docid: readString(docId),
1451
+ sheet_id: readString(sheetId),
1452
+ };
1453
+ if (view_id) payload.view_id = view_id;
1454
+ if (field_ids && Array.isArray(field_ids)) payload.field_ids = field_ids;
1455
+ if (field_titles && Array.isArray(field_titles)) payload.field_titles = field_titles;
1456
+ if (offset !== undefined) payload.offset = offset;
1457
+ if (limit !== undefined) payload.limit = limit;
1458
+
1459
+ const json = await this.postWecomDocApi({
1460
+ path: "/cgi-bin/wedoc/smartsheet/get_fields",
1461
+ actionLabel: "smartsheet_get_fields",
1462
+ agent,
1463
+ body: payload,
1464
+ });
1465
+ return {
1466
+ raw: json,
1467
+ fields: readArray(json.fields),
1468
+ total: json.total,
1469
+ has_more: json.has_more,
1470
+ next: json.next,
1471
+ };
1037
1472
  }
1038
1473
 
1039
1474
  async smartTableAddGroup(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; name: string; children?: string[] }) {
@@ -1066,9 +1501,37 @@ export class WecomDocClient {
1066
1501
  return this.smartTableOperate({ agent, docId, operation: "update_external_records", bodyData: { sheet_id: sheetId, records } });
1067
1502
  }
1068
1503
 
1069
- async smartTableAddRecords(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; records: any[] }) {
1070
- const { agent, docId, sheetId, records } = params;
1071
- return this.smartTableOperate({ agent, docId, operation: "add_records", bodyData: { sheet_id: sheetId, records } });
1504
+ async smartTableAddRecords(params: {
1505
+ agent: ResolvedAgentAccount;
1506
+ docId: string;
1507
+ sheetId: string;
1508
+ records: any[];
1509
+ key_type?: string;
1510
+ }) {
1511
+ const { agent, docId, sheetId, records, key_type } = params;
1512
+
1513
+ // Validate records format per official API spec (doc2.txt line 1594-1601)
1514
+ if (!Array.isArray(records) || records.length === 0) {
1515
+ throw new Error("records 必须是非空数组");
1516
+ }
1517
+
1518
+ // Validate each record has values object
1519
+ records.forEach((record: any, index: number) => {
1520
+ if (!record.values || typeof record.values !== 'object') {
1521
+ throw new Error(`第${index + 1}条记录缺少 values 对象`);
1522
+ }
1523
+ });
1524
+
1525
+ const bodyData: Record<string, unknown> = {
1526
+ sheet_id: readString(sheetId),
1527
+ records: records,
1528
+ };
1529
+
1530
+ if (key_type) {
1531
+ bodyData.key_type = key_type;
1532
+ }
1533
+
1534
+ return this.smartTableOperate({ agent, docId, operation: "add_records", bodyData });
1072
1535
  }
1073
1536
 
1074
1537
  async smartTableUpdateRecords(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; records: any[] }) {
@@ -1081,9 +1544,51 @@ export class WecomDocClient {
1081
1544
  return this.smartTableOperate({ agent, docId, operation: "delete_records", bodyData: { sheet_id: sheetId, record_ids } });
1082
1545
  }
1083
1546
 
1084
- async smartTableGetRecords(params: { agent: ResolvedAgentAccount; docId: string; sheetId: string; record_ids?: string[]; offset?: number; limit?: number }) {
1085
- const { agent, docId, sheetId, record_ids, offset, limit } = params;
1086
- return this.smartTableOperate({ agent, docId, operation: "get_records", bodyData: { sheet_id: sheetId, record_ids, offset, limit } });
1547
+ async smartTableGetRecords(params: {
1548
+ agent: ResolvedAgentAccount;
1549
+ docId: string;
1550
+ sheetId: string;
1551
+ view_id?: string;
1552
+ record_ids?: string[];
1553
+ key_type?: string;
1554
+ field_titles?: string[];
1555
+ field_ids?: string[];
1556
+ sort?: any[];
1557
+ offset?: number;
1558
+ limit?: number;
1559
+ ver?: number;
1560
+ filter_spec?: any;
1561
+ }) {
1562
+ const { agent, docId, sheetId, view_id, record_ids, key_type, field_titles, field_ids, sort, offset, limit, ver, filter_spec } = params;
1563
+ const payload: Record<string, unknown> = {
1564
+ docid: readString(docId),
1565
+ sheet_id: readString(sheetId),
1566
+ };
1567
+ if (view_id) payload.view_id = view_id;
1568
+ if (record_ids && Array.isArray(record_ids)) payload.record_ids = record_ids;
1569
+ if (key_type) payload.key_type = key_type;
1570
+ if (field_titles && Array.isArray(field_titles)) payload.field_titles = field_titles;
1571
+ if (field_ids && Array.isArray(field_ids)) payload.field_ids = field_ids;
1572
+ if (sort && Array.isArray(sort)) payload.sort = sort;
1573
+ if (offset !== undefined) payload.offset = offset;
1574
+ if (limit !== undefined) payload.limit = limit;
1575
+ if (ver !== undefined) payload.ver = ver;
1576
+ if (filter_spec && typeof filter_spec === 'object') payload.filter_spec = filter_spec;
1577
+
1578
+ const json = await this.postWecomDocApi({
1579
+ path: "/cgi-bin/wedoc/smartsheet/get_records",
1580
+ actionLabel: "smartsheet_get_records",
1581
+ agent,
1582
+ body: payload,
1583
+ });
1584
+ return {
1585
+ raw: json,
1586
+ records: readArray(json.records),
1587
+ total: json.total,
1588
+ has_more: json.has_more,
1589
+ next: json.next,
1590
+ ver: json.ver,
1591
+ };
1087
1592
  }
1088
1593
 
1089
1594
  // --- Smartsheet Content Permissions ---
@@ -1173,13 +1678,13 @@ export class WecomDocClient {
1173
1678
  });
1174
1679
  }
1175
1680
 
1176
- async getDocAdvancedAccountList(params: { agent: ResolvedAgentAccount; offset?: number; limit?: number }) {
1177
- const { agent, offset, limit } = params;
1681
+ async getDocAdvancedAccountList(params: { agent: ResolvedAgentAccount; cursor?: number; limit?: number }) {
1682
+ const { agent, cursor, limit } = params;
1178
1683
  return this.postWecomDocApi({
1179
1684
  path: "/cgi-bin/meeting/vip/get_vip_user_list",
1180
1685
  actionLabel: "get_advanced_account_list",
1181
1686
  agent,
1182
- body: { cursor: offset ? String(offset) : undefined, limit: limit ?? 100 },
1687
+ body: { cursor: cursor !== undefined ? String(cursor) : undefined, limit: limit ?? 100 },
1183
1688
  });
1184
1689
  }
1185
1690