@yanhaidao/wecom 2.3.150 → 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 (43) hide show
  1. package/README.md +238 -385
  2. package/SKILLS_CAL.md +895 -0
  3. package/SKILLS_DOC.md +2136 -0
  4. package/changelog/v2.3.16.md +11 -0
  5. package/changelog/v2.3.18.md +22 -0
  6. package/index.ts +39 -3
  7. package/package.json +2 -3
  8. package/src/agent/handler.event-filter.test.ts +11 -0
  9. package/src/agent/handler.ts +732 -643
  10. package/src/app/account-runtime.ts +46 -20
  11. package/src/app/index.ts +19 -1
  12. package/src/capability/calendar/SKILLS_CHECKLIST.md +251 -0
  13. package/src/capability/calendar/client.ts +815 -0
  14. package/src/capability/calendar/index.ts +3 -0
  15. package/src/capability/calendar/schema.ts +417 -0
  16. package/src/capability/calendar/tool.ts +417 -0
  17. package/src/capability/calendar/types.ts +309 -0
  18. package/src/capability/doc/client.ts +567 -62
  19. package/src/capability/doc/schema.ts +419 -318
  20. package/src/capability/doc/tool.ts +1510 -1178
  21. package/src/capability/doc/types.ts +130 -14
  22. package/src/capability/mcp/index.ts +10 -0
  23. package/src/capability/mcp/schema.ts +107 -0
  24. package/src/capability/mcp/tool.ts +170 -0
  25. package/src/capability/mcp/transport.ts +394 -0
  26. package/src/channel.ts +70 -28
  27. package/src/config/schema.ts +71 -102
  28. package/src/outbound.test.ts +91 -14
  29. package/src/outbound.ts +143 -30
  30. package/src/runtime/reply-orchestrator.test.ts +35 -2
  31. package/src/runtime/reply-orchestrator.ts +14 -2
  32. package/src/runtime/session-manager.ts +20 -6
  33. package/src/runtime/source-registry.ts +165 -0
  34. package/src/target.ts +7 -4
  35. package/src/transport/bot-ws/inbound.test.ts +46 -0
  36. package/src/transport/bot-ws/inbound.ts +23 -5
  37. package/src/transport/bot-ws/media.ts +269 -0
  38. package/src/transport/bot-ws/reply.test.ts +85 -17
  39. package/src/transport/bot-ws/reply.ts +109 -21
  40. package/src/transport/bot-ws/sdk-adapter.test.ts +64 -1
  41. package/src/transport/bot-ws/sdk-adapter.ts +88 -12
  42. package/.claude/settings.local.json +0 -11
  43. package/docs/update-content-fix.md +0 -135
@@ -85,10 +85,10 @@ export interface Spacing {
85
85
  }
86
86
 
87
87
  export enum LineSpacingRule {
88
+ UNSPECIFIED = "LINE_SPACING_RULE_UNSPECIFIED",
88
89
  AUTO = "LINE_SPACING_RULE_AUTO",
89
90
  EXACT = "LINE_SPACING_RULE_EXACT",
90
- AT_LEAST = "LINE_SPACING_RULE_AT_LEAST",
91
- UNSPECIFIED = "PAGE_ORIENTATION_UNSPECIFIED" // Note: User text had a copy-paste error here, listing PAGE_ORIENTATION_UNSPECIFIED
91
+ AT_LEAST = "LINE_SPACING_RULE_AT_LEAST"
92
92
  }
93
93
 
94
94
  export interface Indent {
@@ -330,7 +330,7 @@ export interface Range {
330
330
 
331
331
  export interface ReplaceTextRequest {
332
332
  text: string;
333
- ranges: Range[];
333
+ ranges: Range[]; // 最多 10 个范围
334
334
  }
335
335
 
336
336
  export interface InsertTextRequest {
@@ -365,13 +365,7 @@ export interface InsertParagraphRequest {
365
365
 
366
366
  export interface TextProperty {
367
367
  bold?: boolean;
368
- italics?: boolean; // User text says "italics", Schema says "italic". User text for RunProperty says "italics", UpdateTextProperty example says "bold" but doesn't list italics explicitly in example, but RunProperty does. Standard WeCom API is "italics"? My schema says "italic". I will use "italics" as per user provided text for RunProperty, but UpdateTextProperty might differ.
369
- // User text for TextProperty example: bold, color, background_color.
370
- // RunProperty has "italics".
371
- // I will check the user provided TextProperty definition again.
372
- // "blod" (typo in user text), color, background_color.
373
- // It doesn't list italics in TextProperty section, but RunProperty does.
374
- // I will support what is likely correct.
368
+ italics?: boolean;
375
369
  underline?: boolean;
376
370
  strikethrough?: boolean;
377
371
  color?: string;
@@ -381,7 +375,7 @@ export interface TextProperty {
381
375
 
382
376
  export interface UpdateTextPropertyRequest {
383
377
  text_property: TextProperty;
384
- ranges: Range[];
378
+ ranges: Range[]; // 最多 10 个范围
385
379
  }
386
380
 
387
381
  export interface UpdateRequest {
@@ -398,6 +392,15 @@ export interface UpdateRequest {
398
392
  export interface BatchUpdateDocResponse {
399
393
  errcode: number;
400
394
  errmsg: string;
395
+ responses?: Array<{
396
+ insert_text_response?: { end_location: Location };
397
+ delete_content_response?: { end_location: Location };
398
+ replace_text_response?: { occurances: number };
399
+ insert_image_response?: { image_id: string };
400
+ insert_table_response?: { table_id: string };
401
+ insert_paragraph_response?: { paragraph_id: string };
402
+ update_text_property_response?: Record<string, never>;
403
+ }>;
401
404
  }
402
405
 
403
406
  export interface GetDocContentResponse {
@@ -412,20 +415,20 @@ export interface GetDocContentResponse {
412
415
  export interface FormQuestionOption {
413
416
  key: number; // 必填,选项 key 从 1 开始
414
417
  value: string; // 必填,选项内容
415
- status?: number; // 1 正常,2 删除
418
+ status?: number; // 1 正常,2 删除。创建时不传则自动填充为 1
416
419
  }
417
420
 
418
421
  export interface FormQuestion {
419
422
  question_id: number; // 必填,问题 ID 从 1 开始(家校从 2 开始)
420
423
  title: string; // 必填,问题标题
421
424
  pos: number; // 必填,问题序号从 1 开始
422
- status?: number; // 1 正常,2 删除
425
+ status?: number; // 1 正常,2 删除。创建时不传则自动填充为 1
423
426
  reply_type: number; // 必填,问题类型(1-22)
424
427
  must_reply: boolean; // 必填,是否必答
425
428
  note?: string; // 可选,备注
426
429
  placeholder?: string; // 可选,输入提示
427
430
  question_extend_setting?: FormQuestionExtendSetting; // 可选,题型扩展设置
428
- option_item?: FormQuestionOption[]; // 单选/多选/下拉列表必填
431
+ option_item?: FormQuestionOption[]; // 单选/多选/下拉列表必填,创建时不传 status 则自动填充为 1
429
432
  }
430
433
 
431
434
  export interface FormQuestionExtendSetting {
@@ -674,3 +677,116 @@ export interface GetSheetRangeDataResponse {
674
677
  result: GridData;
675
678
  };
676
679
  }
680
+
681
+ // --- Smart Table Records & Fields Types ---
682
+
683
+ export interface SmartTableRecord {
684
+ record_id: string;
685
+ create_time?: string;
686
+ update_time?: string;
687
+ values: Record<string, any>;
688
+ creator_name?: string;
689
+ updater_name?: string;
690
+ }
691
+
692
+ export interface SmartTableGetRecordsResponse {
693
+ errcode: number;
694
+ errmsg: string;
695
+ records?: SmartTableRecord[];
696
+ total?: number;
697
+ has_more?: boolean;
698
+ next?: number;
699
+ ver?: number;
700
+ }
701
+
702
+ export interface SmartTableField {
703
+ field_id: string;
704
+ field_title: string;
705
+ field_type: string;
706
+ property_number?: any;
707
+ property_checkbox?: any;
708
+ property_date_time?: any;
709
+ property_attachment?: any;
710
+ property_user?: any;
711
+ property_url?: any;
712
+ property_select?: any;
713
+ property_created_time?: any;
714
+ property_modified_time?: any;
715
+ property_progress?: any;
716
+ property_single_select?: any;
717
+ property_reference?: any;
718
+ property_location?: any;
719
+ property_auto_number?: any;
720
+ property_currency?: any;
721
+ property_ww_group?: any;
722
+ property_percentage?: any;
723
+ property_barcode?: any;
724
+ property_image?: any;
725
+ property_phone_number?: any;
726
+ property_email?: any;
727
+ }
728
+
729
+ export interface SmartTableGetFieldsResponse {
730
+ errcode: number;
731
+ errmsg: string;
732
+ fields?: SmartTableField[];
733
+ total?: number;
734
+ has_more?: boolean;
735
+ next?: number;
736
+ }
737
+
738
+ export interface SmartTableView {
739
+ view_id: string;
740
+ view_title: string;
741
+ view_type: string;
742
+ is_visible?: boolean;
743
+ type?: string;
744
+ property?: any;
745
+ }
746
+
747
+ export interface SmartTableGetViewsResponse {
748
+ errcode: number;
749
+ errmsg: string;
750
+ views?: SmartTableView[];
751
+ total?: number;
752
+ has_more?: boolean;
753
+ next?: number;
754
+ }
755
+
756
+ export interface SmartTableFieldGroup {
757
+ field_group_id: string;
758
+ name: string;
759
+ children?: { field_id: string }[];
760
+ }
761
+
762
+ export interface SmartTableGetGroupsResponse {
763
+ errcode: number;
764
+ errmsg: string;
765
+ field_groups?: SmartTableFieldGroup[];
766
+ total?: number;
767
+ has_more?: boolean;
768
+ next?: number;
769
+ }
770
+
771
+ export interface SmartTableSheetPriv {
772
+ sheet_id: string;
773
+ priv: number;
774
+ can_insert_record?: boolean;
775
+ can_delete_record?: boolean;
776
+ can_create_modify_delete_view?: boolean;
777
+ field_priv?: any;
778
+ record_priv?: any;
779
+ clear?: boolean;
780
+ }
781
+
782
+ export interface SmartTableGetSheetPrivResponse {
783
+ errcode: number;
784
+ errmsg: string;
785
+ rule_list?: any[];
786
+ }
787
+
788
+ export interface SmartTableCreateRuleResponse {
789
+ errcode: number;
790
+ errmsg: string;
791
+ rule_id?: number;
792
+ }
@@ -0,0 +1,10 @@
1
+ export { createWeComMcpToolFactory } from "./tool.js";
2
+ export {
3
+ clearWecomMcpAccountCache,
4
+ clearWecomMcpCategoryCache,
5
+ McpHttpError,
6
+ McpRpcError,
7
+ sendJsonRpc,
8
+ type McpToolInfo,
9
+ } from "./transport.js";
10
+ export { cleanSchemaForGemini } from "./schema.js";
@@ -0,0 +1,107 @@
1
+ const GEMINI_UNSUPPORTED_KEYWORDS = new Set([
2
+ "patternProperties",
3
+ "additionalProperties",
4
+ "$schema",
5
+ "$id",
6
+ "$ref",
7
+ "$defs",
8
+ "definitions",
9
+ "examples",
10
+ "minLength",
11
+ "maxLength",
12
+ "minimum",
13
+ "maximum",
14
+ "multipleOf",
15
+ "pattern",
16
+ "format",
17
+ "minItems",
18
+ "maxItems",
19
+ "uniqueItems",
20
+ "minProperties",
21
+ "maxProperties",
22
+ ]);
23
+
24
+ export function cleanSchemaForGemini(schema: unknown): unknown {
25
+ if (!schema || typeof schema !== "object") return schema;
26
+ if (Array.isArray(schema)) return schema.map(cleanSchemaForGemini);
27
+
28
+ const obj = schema as Record<string, unknown>;
29
+ const defs: Record<string, unknown> = {
30
+ ...(obj.$defs && typeof obj.$defs === "object" ? (obj.$defs as Record<string, unknown>) : {}),
31
+ ...(obj.definitions && typeof obj.definitions === "object"
32
+ ? (obj.definitions as Record<string, unknown>)
33
+ : {}),
34
+ };
35
+ return cleanWithDefs(obj, defs, new Set());
36
+ }
37
+
38
+ function cleanWithDefs(
39
+ schema: unknown,
40
+ defs: Record<string, unknown>,
41
+ refStack: Set<string>,
42
+ ): unknown {
43
+ if (!schema || typeof schema !== "object") return schema;
44
+ if (Array.isArray(schema)) return schema.map((item) => cleanWithDefs(item, defs, refStack));
45
+
46
+ const obj = schema as Record<string, unknown>;
47
+ if (obj.$defs && typeof obj.$defs === "object") {
48
+ Object.assign(defs, obj.$defs as Record<string, unknown>);
49
+ }
50
+ if (obj.definitions && typeof obj.definitions === "object") {
51
+ Object.assign(defs, obj.definitions as Record<string, unknown>);
52
+ }
53
+
54
+ if (typeof obj.$ref === "string") {
55
+ const ref = obj.$ref;
56
+ if (refStack.has(ref)) return {};
57
+ const match = ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/);
58
+ if (match?.[1] && defs[match[1]]) {
59
+ const nextStack = new Set(refStack);
60
+ nextStack.add(ref);
61
+ return cleanWithDefs(defs[match[1]], defs, nextStack);
62
+ }
63
+ return {};
64
+ }
65
+
66
+ const cleaned: Record<string, unknown> = {};
67
+ for (const [key, value] of Object.entries(obj)) {
68
+ if (GEMINI_UNSUPPORTED_KEYWORDS.has(key)) continue;
69
+ if (key === "const") {
70
+ cleaned.enum = [value];
71
+ continue;
72
+ }
73
+ if (key === "properties" && value && typeof value === "object" && !Array.isArray(value)) {
74
+ cleaned[key] = Object.fromEntries(
75
+ Object.entries(value as Record<string, unknown>).map(([propKey, propValue]) => [
76
+ propKey,
77
+ cleanWithDefs(propValue, defs, refStack),
78
+ ]),
79
+ );
80
+ continue;
81
+ }
82
+ if (key === "items" && value) {
83
+ cleaned[key] = Array.isArray(value)
84
+ ? value.map((item) => cleanWithDefs(item, defs, refStack))
85
+ : cleanWithDefs(value, defs, refStack);
86
+ continue;
87
+ }
88
+ if ((key === "anyOf" || key === "oneOf" || key === "allOf") && Array.isArray(value)) {
89
+ const nonNull = value.filter((variant) => {
90
+ if (!variant || typeof variant !== "object") return true;
91
+ return (variant as Record<string, unknown>).type !== "null";
92
+ });
93
+ if (nonNull.length === 1) {
94
+ const single = cleanWithDefs(nonNull[0], defs, refStack);
95
+ if (single && typeof single === "object" && !Array.isArray(single)) {
96
+ Object.assign(cleaned, single as Record<string, unknown>);
97
+ }
98
+ } else {
99
+ cleaned[key] = nonNull.map((variant) => cleanWithDefs(variant, defs, refStack));
100
+ }
101
+ continue;
102
+ }
103
+ cleaned[key] = value;
104
+ }
105
+
106
+ return cleaned;
107
+ }
@@ -0,0 +1,170 @@
1
+ import type { OpenClawPluginToolContext } from "openclaw/plugin-sdk";
2
+ import { resolveWecomSourceSnapshot } from "../../runtime/source-registry.js";
3
+ import { cleanSchemaForGemini } from "./schema.js";
4
+ import { clearWecomMcpCategoryCache, sendJsonRpc, type McpToolInfo } from "./transport.js";
5
+
6
+ type WecomMcpParams = {
7
+ action: "list" | "call";
8
+ category: string;
9
+ method?: string;
10
+ args?: string | Record<string, unknown>;
11
+ };
12
+
13
+ const BIZ_CACHE_CLEAR_ERROR_CODES = new Set([850002]);
14
+
15
+ function textResult(data: unknown) {
16
+ return {
17
+ content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
18
+ };
19
+ }
20
+
21
+ function errorResult(error: unknown) {
22
+ if (error && typeof error === "object" && "errcode" in error) {
23
+ const errcode = Number((error as { errcode?: number }).errcode ?? 0);
24
+ const errmsg = String((error as { errmsg?: string }).errmsg ?? `错误码: ${errcode}`);
25
+ return textResult({ error: errmsg, errcode });
26
+ }
27
+ return textResult({
28
+ error: error instanceof Error ? error.message : String(error),
29
+ });
30
+ }
31
+
32
+ function parseArgs(args: string | Record<string, unknown> | undefined): Record<string, unknown> {
33
+ if (!args) return {};
34
+ if (typeof args === "object") return args;
35
+ try {
36
+ return JSON.parse(args) as Record<string, unknown>;
37
+ } catch (error) {
38
+ const detail = error instanceof SyntaxError ? error.message : String(error);
39
+ throw new Error(`args 不是合法的 JSON: ${args} (${detail})`);
40
+ }
41
+ }
42
+
43
+ function extractToolAccountId(ctx: OpenClawPluginToolContext): string | undefined {
44
+ const explicit = String((ctx as { accountId?: string }).accountId ?? "").trim();
45
+ if (explicit) return explicit;
46
+ const agentAccountId = String(ctx.agentAccountId ?? "").trim();
47
+ return agentAccountId || undefined;
48
+ }
49
+
50
+ async function handleList(accountId: string, category: string): Promise<unknown> {
51
+ const result = (await sendJsonRpc(accountId, category, "tools/list")) as
52
+ | { tools?: McpToolInfo[] }
53
+ | undefined;
54
+ const tools = result?.tools ?? [];
55
+ return {
56
+ accountId,
57
+ category,
58
+ count: tools.length,
59
+ tools: tools.map((tool) => ({
60
+ name: tool.name,
61
+ description: tool.description ?? "",
62
+ inputSchema: tool.inputSchema ? cleanSchemaForGemini(tool.inputSchema) : undefined,
63
+ })),
64
+ };
65
+ }
66
+
67
+ function checkBizErrorAndClearCache(result: unknown, accountId: string, category: string): void {
68
+ if (!result || typeof result !== "object") return;
69
+ const content = (result as { content?: Array<{ type: string; text?: string }> }).content;
70
+ if (!Array.isArray(content)) return;
71
+ for (const item of content) {
72
+ if (item.type !== "text" || !item.text) continue;
73
+ try {
74
+ const parsed = JSON.parse(item.text) as { errcode?: number };
75
+ if (typeof parsed.errcode === "number" && BIZ_CACHE_CLEAR_ERROR_CODES.has(parsed.errcode)) {
76
+ clearWecomMcpCategoryCache(accountId, category);
77
+ return;
78
+ }
79
+ } catch {
80
+ // Ignore non-JSON content.
81
+ }
82
+ }
83
+ }
84
+
85
+ async function handleCall(
86
+ accountId: string,
87
+ category: string,
88
+ method: string,
89
+ args: Record<string, unknown>,
90
+ ): Promise<unknown> {
91
+ const result = await sendJsonRpc(accountId, category, "tools/call", {
92
+ name: method,
93
+ arguments: args,
94
+ });
95
+ checkBizErrorAndClearCache(result, accountId, category);
96
+ return result;
97
+ }
98
+
99
+ export function createWeComMcpToolFactory() {
100
+ return (toolContext: OpenClawPluginToolContext) => {
101
+ if (toolContext.messageChannel !== "wecom") {
102
+ return null;
103
+ }
104
+ const accountId = extractToolAccountId(toolContext);
105
+ const source = resolveWecomSourceSnapshot({
106
+ accountId,
107
+ sessionKey: toolContext.sessionKey,
108
+ sessionId: toolContext.sessionId,
109
+ });
110
+ if (!source || source.source !== "bot-ws") {
111
+ return null;
112
+ }
113
+
114
+ return {
115
+ name: "wecom_mcp",
116
+ label: "WeCom MCP",
117
+ description:
118
+ "企业微信 Bot WS MCP 工具。仅在 WeCom Bot WS 会话中可用,用于列出和调用企业微信 MCP 能力。",
119
+ parameters: {
120
+ type: "object" as const,
121
+ properties: {
122
+ action: {
123
+ type: "string",
124
+ enum: ["list", "call"],
125
+ description: "操作类型:list 或 call",
126
+ },
127
+ category: {
128
+ type: "string",
129
+ description: "MCP 品类,如 contact、todo、meeting、doc",
130
+ },
131
+ method: {
132
+ type: "string",
133
+ description: "action=call 时要调用的工具方法名",
134
+ },
135
+ args: {
136
+ type: "string",
137
+ description: "action=call 时传入的 JSON 字符串参数,默认 {}",
138
+ },
139
+ },
140
+ required: ["action", "category"],
141
+ },
142
+ async execute(_toolCallId: string, rawParams: unknown) {
143
+ try {
144
+ const params = rawParams as WecomMcpParams;
145
+ const effectiveAccountId = extractToolAccountId(toolContext);
146
+ if (!effectiveAccountId) {
147
+ throw new Error("当前会话缺少 WeCom accountId,无法调用 wecom_mcp。");
148
+ }
149
+
150
+ if (params.action === "list") {
151
+ return textResult(await handleList(effectiveAccountId, params.category));
152
+ }
153
+ if (!params.method) {
154
+ return textResult({ error: "action=call 时必须提供 method" });
155
+ }
156
+ return textResult(
157
+ await handleCall(
158
+ effectiveAccountId,
159
+ params.category,
160
+ params.method,
161
+ parseArgs(params.args),
162
+ ),
163
+ );
164
+ } catch (error) {
165
+ return errorResult(error);
166
+ }
167
+ },
168
+ };
169
+ };
170
+ }