@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.
- package/README.md +238 -385
- package/SKILLS_CAL.md +895 -0
- package/SKILLS_DOC.md +2136 -0
- package/changelog/v2.3.16.md +11 -0
- package/changelog/v2.3.18.md +22 -0
- package/index.ts +39 -3
- package/package.json +2 -3
- package/src/agent/handler.event-filter.test.ts +11 -0
- package/src/agent/handler.ts +732 -643
- package/src/app/account-runtime.ts +46 -20
- package/src/app/index.ts +19 -1
- package/src/capability/calendar/SKILLS_CHECKLIST.md +251 -0
- package/src/capability/calendar/client.ts +815 -0
- package/src/capability/calendar/index.ts +3 -0
- package/src/capability/calendar/schema.ts +417 -0
- package/src/capability/calendar/tool.ts +417 -0
- package/src/capability/calendar/types.ts +309 -0
- package/src/capability/doc/client.ts +567 -62
- package/src/capability/doc/schema.ts +419 -318
- package/src/capability/doc/tool.ts +1510 -1178
- package/src/capability/doc/types.ts +130 -14
- package/src/capability/mcp/index.ts +10 -0
- package/src/capability/mcp/schema.ts +107 -0
- package/src/capability/mcp/tool.ts +170 -0
- package/src/capability/mcp/transport.ts +394 -0
- package/src/channel.ts +70 -28
- package/src/config/schema.ts +71 -102
- package/src/outbound.test.ts +91 -14
- package/src/outbound.ts +143 -30
- package/src/runtime/reply-orchestrator.test.ts +35 -2
- package/src/runtime/reply-orchestrator.ts +14 -2
- package/src/runtime/session-manager.ts +20 -6
- package/src/runtime/source-registry.ts +165 -0
- package/src/target.ts +7 -4
- package/src/transport/bot-ws/inbound.test.ts +46 -0
- package/src/transport/bot-ws/inbound.ts +23 -5
- package/src/transport/bot-ws/media.ts +269 -0
- package/src/transport/bot-ws/reply.test.ts +85 -17
- package/src/transport/bot-ws/reply.ts +109 -21
- package/src/transport/bot-ws/sdk-adapter.test.ts +64 -1
- package/src/transport/bot-ws/sdk-adapter.ts +88 -12
- package/.claude/settings.local.json +0 -11
- 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;
|
|
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
|
+
}
|