@yanhaidao/wecom 2.2.28 → 2.3.3
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 +33 -28
- package/assets/register.png +0 -0
- package/changelog/v2.3.2.md +28 -0
- package/package.json +1 -1
- package/src/agent/api-client.ts +76 -34
- package/src/agent/api-client.upload.test.ts +110 -0
- package/src/agent/handler.ts +4 -4
- package/src/channel.lifecycle.test.ts +24 -6
- package/src/channel.ts +11 -6
- package/src/config/network.ts +9 -5
- package/src/gateway-monitor.ts +51 -20
- package/src/http.ts +16 -2
- package/src/media.test.ts +28 -1
- package/src/media.ts +59 -1
- package/src/monitor.active.test.ts +9 -6
- package/src/monitor.integration.test.ts +4 -2
- package/src/monitor.ts +511 -82
- package/src/monitor.webhook.test.ts +104 -11
- package/src/onboarding.ts +219 -43
- package/src/outbound.ts +6 -0
- package/src/types/constants.ts +7 -3
|
@@ -369,7 +369,7 @@ describe("handleWecomWebhookRequest", () => {
|
|
|
369
369
|
}
|
|
370
370
|
});
|
|
371
371
|
|
|
372
|
-
it("routes
|
|
372
|
+
it("routes bot callback by explicit plugin account path", async () => {
|
|
373
373
|
const token = "MATRIX-TOKEN";
|
|
374
374
|
const encodingAESKey = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG";
|
|
375
375
|
|
|
@@ -390,7 +390,7 @@ describe("handleWecomWebhookRequest", () => {
|
|
|
390
390
|
config: { channels: { wecom: { accounts: {} } } } as OpenClawConfig,
|
|
391
391
|
runtime: {},
|
|
392
392
|
core: {} as any,
|
|
393
|
-
path: "/wecom/bot/acct-a",
|
|
393
|
+
path: "/plugins/wecom/bot/acct-a",
|
|
394
394
|
});
|
|
395
395
|
const unregisterB = registerWecomWebhookTarget({
|
|
396
396
|
account: {
|
|
@@ -409,12 +409,12 @@ describe("handleWecomWebhookRequest", () => {
|
|
|
409
409
|
config: { channels: { wecom: { accounts: {} } } } as OpenClawConfig,
|
|
410
410
|
runtime: {},
|
|
411
411
|
core: {} as any,
|
|
412
|
-
path: "/wecom/bot/acct-b",
|
|
412
|
+
path: "/plugins/wecom/bot/acct-b",
|
|
413
413
|
});
|
|
414
414
|
|
|
415
415
|
try {
|
|
416
416
|
const timestamp = "1700000999";
|
|
417
|
-
const nonce = "nonce-
|
|
417
|
+
const nonce = "nonce-plugin-account";
|
|
418
418
|
const plain = JSON.stringify({
|
|
419
419
|
msgid: "MATRIX-MSG-1",
|
|
420
420
|
aibotid: "BOT_B",
|
|
@@ -422,13 +422,94 @@ describe("handleWecomWebhookRequest", () => {
|
|
|
422
422
|
from: { userid: "USERID_B" },
|
|
423
423
|
response_url: "RESPONSEURL",
|
|
424
424
|
msgtype: "text",
|
|
425
|
-
text: { content: "hello
|
|
425
|
+
text: { content: "hello plugin account path" },
|
|
426
426
|
});
|
|
427
427
|
const encrypt = encryptWecomPlaintext({ encodingAESKey, receiveId: "", plaintext: plain });
|
|
428
428
|
const msg_signature = computeWecomMsgSignature({ token, timestamp, nonce, encrypt });
|
|
429
429
|
const req = createMockRequest({
|
|
430
430
|
method: "POST",
|
|
431
|
-
url: `/wecom/bot/acct-b?msg_signature=${encodeURIComponent(msg_signature)}×tamp=${encodeURIComponent(timestamp)}&nonce=${encodeURIComponent(nonce)}`,
|
|
431
|
+
url: `/plugins/wecom/bot/acct-b?msg_signature=${encodeURIComponent(msg_signature)}×tamp=${encodeURIComponent(timestamp)}&nonce=${encodeURIComponent(nonce)}`,
|
|
432
|
+
body: { encrypt },
|
|
433
|
+
});
|
|
434
|
+
const res = createMockResponse();
|
|
435
|
+
const handled = await handleWecomWebhookRequest(req, res);
|
|
436
|
+
expect(handled).toBe(true);
|
|
437
|
+
expect(res._getStatusCode()).toBe(200);
|
|
438
|
+
|
|
439
|
+
const json = JSON.parse(res._getData()) as any;
|
|
440
|
+
const replyPlain = decryptWecomEncrypted({
|
|
441
|
+
encodingAESKey,
|
|
442
|
+
receiveId: "",
|
|
443
|
+
encrypt: json.encrypt,
|
|
444
|
+
});
|
|
445
|
+
const reply = JSON.parse(replyPlain) as any;
|
|
446
|
+
expect(reply.stream?.content).toBe("B处理中");
|
|
447
|
+
} finally {
|
|
448
|
+
unregisterA();
|
|
449
|
+
unregisterB();
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it("routes bot callback by explicit plugin namespace path", async () => {
|
|
454
|
+
const token = "MATRIX-TOKEN-PLUGIN";
|
|
455
|
+
const encodingAESKey = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG";
|
|
456
|
+
|
|
457
|
+
const unregisterA = registerWecomWebhookTarget({
|
|
458
|
+
account: {
|
|
459
|
+
accountId: "acct-a",
|
|
460
|
+
enabled: true,
|
|
461
|
+
configured: true,
|
|
462
|
+
token,
|
|
463
|
+
encodingAESKey,
|
|
464
|
+
receiveId: "",
|
|
465
|
+
config: {
|
|
466
|
+
token,
|
|
467
|
+
encodingAESKey,
|
|
468
|
+
streamPlaceholderContent: "A处理中",
|
|
469
|
+
} as any,
|
|
470
|
+
} as any,
|
|
471
|
+
config: { channels: { wecom: { accounts: {} } } } as OpenClawConfig,
|
|
472
|
+
runtime: {},
|
|
473
|
+
core: {} as any,
|
|
474
|
+
path: "/plugins/wecom/bot/acct-a",
|
|
475
|
+
});
|
|
476
|
+
const unregisterB = registerWecomWebhookTarget({
|
|
477
|
+
account: {
|
|
478
|
+
accountId: "acct-b",
|
|
479
|
+
enabled: true,
|
|
480
|
+
configured: true,
|
|
481
|
+
token,
|
|
482
|
+
encodingAESKey,
|
|
483
|
+
receiveId: "",
|
|
484
|
+
config: {
|
|
485
|
+
token,
|
|
486
|
+
encodingAESKey,
|
|
487
|
+
streamPlaceholderContent: "B处理中",
|
|
488
|
+
} as any,
|
|
489
|
+
} as any,
|
|
490
|
+
config: { channels: { wecom: { accounts: {} } } } as OpenClawConfig,
|
|
491
|
+
runtime: {},
|
|
492
|
+
core: {} as any,
|
|
493
|
+
path: "/plugins/wecom/bot/acct-b",
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
try {
|
|
497
|
+
const timestamp = "1700001000";
|
|
498
|
+
const nonce = "nonce-matrix-plugin";
|
|
499
|
+
const plain = JSON.stringify({
|
|
500
|
+
msgid: "MATRIX-MSG-PLUGIN-1",
|
|
501
|
+
aibotid: "BOT_B",
|
|
502
|
+
chattype: "single",
|
|
503
|
+
from: { userid: "USERID_B_PLUGIN" },
|
|
504
|
+
response_url: "RESPONSEURL",
|
|
505
|
+
msgtype: "text",
|
|
506
|
+
text: { content: "hello matrix plugin path" },
|
|
507
|
+
});
|
|
508
|
+
const encrypt = encryptWecomPlaintext({ encodingAESKey, receiveId: "", plaintext: plain });
|
|
509
|
+
const msg_signature = computeWecomMsgSignature({ token, timestamp, nonce, encrypt });
|
|
510
|
+
const req = createMockRequest({
|
|
511
|
+
method: "POST",
|
|
512
|
+
url: `/plugins/wecom/bot/acct-b?msg_signature=${encodeURIComponent(msg_signature)}×tamp=${encodeURIComponent(timestamp)}&nonce=${encodeURIComponent(nonce)}`,
|
|
432
513
|
body: { encrypt },
|
|
433
514
|
});
|
|
434
515
|
const res = createMockResponse();
|
|
@@ -501,7 +582,7 @@ describe("handleWecomWebhookRequest", () => {
|
|
|
501
582
|
}
|
|
502
583
|
});
|
|
503
584
|
|
|
504
|
-
it("rejects legacy
|
|
585
|
+
it("rejects legacy paths and accountless plugin paths", async () => {
|
|
505
586
|
const token = "MATRIX-TOKEN-3";
|
|
506
587
|
const encodingAESKey = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG";
|
|
507
588
|
const unregister = registerWecomWebhookTarget({
|
|
@@ -517,7 +598,7 @@ describe("handleWecomWebhookRequest", () => {
|
|
|
517
598
|
config: { channels: { wecom: { accounts: { "acct-a": { bot: {} } } } } } as OpenClawConfig,
|
|
518
599
|
runtime: {},
|
|
519
600
|
core: {} as any,
|
|
520
|
-
path: "/wecom/bot/acct-a",
|
|
601
|
+
path: "/plugins/wecom/bot/acct-a",
|
|
521
602
|
});
|
|
522
603
|
try {
|
|
523
604
|
const req = createMockRequest({
|
|
@@ -531,6 +612,18 @@ describe("handleWecomWebhookRequest", () => {
|
|
|
531
612
|
expect(JSON.parse(res._getData())).toMatchObject({
|
|
532
613
|
error: "wecom_matrix_path_required",
|
|
533
614
|
});
|
|
615
|
+
|
|
616
|
+
const pluginReq = createMockRequest({
|
|
617
|
+
method: "GET",
|
|
618
|
+
url: "/plugins/wecom/bot?timestamp=t&nonce=n&msg_signature=s&echostr=e",
|
|
619
|
+
});
|
|
620
|
+
const pluginRes = createMockResponse();
|
|
621
|
+
const pluginHandled = await handleWecomWebhookRequest(pluginReq, pluginRes);
|
|
622
|
+
expect(pluginHandled).toBe(true);
|
|
623
|
+
expect(pluginRes._getStatusCode()).toBe(401);
|
|
624
|
+
expect(JSON.parse(pluginRes._getData())).toMatchObject({
|
|
625
|
+
error: "wecom_matrix_path_required",
|
|
626
|
+
});
|
|
534
627
|
} finally {
|
|
535
628
|
unregister();
|
|
536
629
|
}
|
|
@@ -557,7 +650,7 @@ describe("handleWecomWebhookRequest", () => {
|
|
|
557
650
|
},
|
|
558
651
|
config: { channels: { wecom: { accounts: {} } } } as OpenClawConfig,
|
|
559
652
|
runtime: {},
|
|
560
|
-
path: "/wecom/agent",
|
|
653
|
+
path: "/plugins/wecom/agent/default",
|
|
561
654
|
} as any);
|
|
562
655
|
const unregisterB = registerAgentWebhookTarget({
|
|
563
656
|
agent: {
|
|
@@ -573,13 +666,13 @@ describe("handleWecomWebhookRequest", () => {
|
|
|
573
666
|
},
|
|
574
667
|
config: { channels: { wecom: { accounts: {} } } } as OpenClawConfig,
|
|
575
668
|
runtime: {},
|
|
576
|
-
path: "/wecom/agent",
|
|
669
|
+
path: "/plugins/wecom/agent/default",
|
|
577
670
|
} as any);
|
|
578
671
|
|
|
579
672
|
try {
|
|
580
673
|
const req = createMockRequest({
|
|
581
674
|
method: "GET",
|
|
582
|
-
url: `/wecom/agent?msg_signature=${encodeURIComponent(signature)}×tamp=${encodeURIComponent(timestamp)}&nonce=${encodeURIComponent(nonce)}&echostr=${encodeURIComponent(echostr)}`,
|
|
675
|
+
url: `/plugins/wecom/agent/default?msg_signature=${encodeURIComponent(signature)}×tamp=${encodeURIComponent(timestamp)}&nonce=${encodeURIComponent(nonce)}&echostr=${encodeURIComponent(echostr)}`,
|
|
583
676
|
});
|
|
584
677
|
const res = createMockResponse();
|
|
585
678
|
const handled = await handleWecomWebhookRequest(req, res);
|
package/src/onboarding.ts
CHANGED
|
@@ -9,8 +9,9 @@ import type {
|
|
|
9
9
|
OpenClawConfig,
|
|
10
10
|
WizardPrompter,
|
|
11
11
|
} from "openclaw/plugin-sdk";
|
|
12
|
-
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk";
|
|
13
|
-
import
|
|
12
|
+
import { DEFAULT_ACCOUNT_ID, promptAccountId } from "openclaw/plugin-sdk";
|
|
13
|
+
import { listWecomAccountIds, resolveDefaultWecomAccountId, resolveWecomAccount, resolveWecomAccounts } from "./config/index.js";
|
|
14
|
+
import type { WecomConfig, WecomBotConfig, WecomAgentConfig, WecomDmConfig, WecomAccountConfig } from "./types/index.js";
|
|
14
15
|
|
|
15
16
|
const channel = "wecom" as const;
|
|
16
17
|
|
|
@@ -37,29 +38,118 @@ function setWecomEnabled(cfg: OpenClawConfig, enabled: boolean): OpenClawConfig
|
|
|
37
38
|
} as OpenClawConfig;
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
function
|
|
41
|
+
function shouldUseAccountScopedConfig(wecom: WecomConfig | undefined, accountId: string): boolean {
|
|
42
|
+
void wecom;
|
|
43
|
+
void accountId;
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function ensureMatrixAccounts(wecom: WecomConfig): WecomConfig {
|
|
48
|
+
const accounts = wecom.accounts ?? {};
|
|
49
|
+
if (Object.keys(accounts).length > 0) {
|
|
50
|
+
return wecom;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!wecom.bot && !wecom.agent) {
|
|
54
|
+
return wecom;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const { bot: legacyBot, agent: legacyAgent, ...rest } = wecom;
|
|
58
|
+
const defaultAccount: WecomAccountConfig = {
|
|
59
|
+
enabled: true,
|
|
60
|
+
...(legacyBot ? { bot: legacyBot } : {}),
|
|
61
|
+
...(legacyAgent ? { agent: legacyAgent } : {}),
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
...rest,
|
|
66
|
+
defaultAccount: rest.defaultAccount?.trim() || DEFAULT_ACCOUNT_ID,
|
|
67
|
+
accounts: {
|
|
68
|
+
[DEFAULT_ACCOUNT_ID]: defaultAccount,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function accountWebhookPath(kind: "bot" | "agent", accountId: string): string {
|
|
74
|
+
const recommendedBase = kind === "bot" ? "/plugins/wecom/bot" : "/plugins/wecom/agent";
|
|
75
|
+
return `${recommendedBase}/${accountId}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function setWecomBotConfig(cfg: OpenClawConfig, bot: WecomBotConfig, accountId: string): OpenClawConfig {
|
|
79
|
+
const wecom = getWecomConfig(cfg) ?? {};
|
|
80
|
+
if (!shouldUseAccountScopedConfig(wecom, accountId)) {
|
|
81
|
+
return {
|
|
82
|
+
...cfg,
|
|
83
|
+
channels: {
|
|
84
|
+
...cfg.channels,
|
|
85
|
+
wecom: {
|
|
86
|
+
...wecom,
|
|
87
|
+
enabled: true,
|
|
88
|
+
bot,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
} as OpenClawConfig;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const matrixWecom = ensureMatrixAccounts(wecom);
|
|
95
|
+
const accounts = matrixWecom.accounts ?? {};
|
|
96
|
+
const existingAccount = accounts[accountId] ?? {};
|
|
41
97
|
return {
|
|
42
98
|
...cfg,
|
|
43
99
|
channels: {
|
|
44
100
|
...cfg.channels,
|
|
45
101
|
wecom: {
|
|
46
|
-
...
|
|
102
|
+
...matrixWecom,
|
|
47
103
|
enabled: true,
|
|
48
|
-
|
|
104
|
+
defaultAccount: matrixWecom.defaultAccount?.trim() || DEFAULT_ACCOUNT_ID,
|
|
105
|
+
accounts: {
|
|
106
|
+
...accounts,
|
|
107
|
+
[accountId]: {
|
|
108
|
+
...existingAccount,
|
|
109
|
+
enabled: existingAccount.enabled ?? true,
|
|
110
|
+
bot,
|
|
111
|
+
},
|
|
112
|
+
},
|
|
49
113
|
},
|
|
50
114
|
},
|
|
51
115
|
} as OpenClawConfig;
|
|
52
116
|
}
|
|
53
117
|
|
|
54
|
-
function setWecomAgentConfig(cfg: OpenClawConfig, agent: WecomAgentConfig): OpenClawConfig {
|
|
118
|
+
function setWecomAgentConfig(cfg: OpenClawConfig, agent: WecomAgentConfig, accountId: string): OpenClawConfig {
|
|
119
|
+
const wecom = getWecomConfig(cfg) ?? {};
|
|
120
|
+
if (!shouldUseAccountScopedConfig(wecom, accountId)) {
|
|
121
|
+
return {
|
|
122
|
+
...cfg,
|
|
123
|
+
channels: {
|
|
124
|
+
...cfg.channels,
|
|
125
|
+
wecom: {
|
|
126
|
+
...wecom,
|
|
127
|
+
enabled: true,
|
|
128
|
+
agent,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
} as OpenClawConfig;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const matrixWecom = ensureMatrixAccounts(wecom);
|
|
135
|
+
const accounts = matrixWecom.accounts ?? {};
|
|
136
|
+
const existingAccount = accounts[accountId] ?? {};
|
|
55
137
|
return {
|
|
56
138
|
...cfg,
|
|
57
139
|
channels: {
|
|
58
140
|
...cfg.channels,
|
|
59
141
|
wecom: {
|
|
60
|
-
...
|
|
142
|
+
...matrixWecom,
|
|
61
143
|
enabled: true,
|
|
62
|
-
|
|
144
|
+
defaultAccount: matrixWecom.defaultAccount?.trim() || DEFAULT_ACCOUNT_ID,
|
|
145
|
+
accounts: {
|
|
146
|
+
...accounts,
|
|
147
|
+
[accountId]: {
|
|
148
|
+
...existingAccount,
|
|
149
|
+
enabled: existingAccount.enabled ?? true,
|
|
150
|
+
agent,
|
|
151
|
+
},
|
|
152
|
+
},
|
|
63
153
|
},
|
|
64
154
|
},
|
|
65
155
|
} as OpenClawConfig;
|
|
@@ -69,8 +159,49 @@ function setWecomDmPolicy(
|
|
|
69
159
|
cfg: OpenClawConfig,
|
|
70
160
|
mode: "bot" | "agent",
|
|
71
161
|
dm: WecomDmConfig,
|
|
162
|
+
accountId: string,
|
|
72
163
|
): OpenClawConfig {
|
|
73
164
|
const wecom = getWecomConfig(cfg) ?? {};
|
|
165
|
+
if (shouldUseAccountScopedConfig(wecom, accountId)) {
|
|
166
|
+
const matrixWecom = ensureMatrixAccounts(wecom);
|
|
167
|
+
const accounts = matrixWecom.accounts ?? {};
|
|
168
|
+
const existingAccount = accounts[accountId] ?? {};
|
|
169
|
+
const nextAccount: WecomAccountConfig =
|
|
170
|
+
mode === "bot"
|
|
171
|
+
? {
|
|
172
|
+
...existingAccount,
|
|
173
|
+
bot: {
|
|
174
|
+
...existingAccount.bot,
|
|
175
|
+
dm,
|
|
176
|
+
},
|
|
177
|
+
}
|
|
178
|
+
: {
|
|
179
|
+
...existingAccount,
|
|
180
|
+
agent: {
|
|
181
|
+
...existingAccount.agent,
|
|
182
|
+
dm,
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
return {
|
|
186
|
+
...cfg,
|
|
187
|
+
channels: {
|
|
188
|
+
...cfg.channels,
|
|
189
|
+
wecom: {
|
|
190
|
+
...matrixWecom,
|
|
191
|
+
enabled: true,
|
|
192
|
+
defaultAccount: matrixWecom.defaultAccount?.trim() || DEFAULT_ACCOUNT_ID,
|
|
193
|
+
accounts: {
|
|
194
|
+
...accounts,
|
|
195
|
+
[accountId]: {
|
|
196
|
+
...nextAccount,
|
|
197
|
+
enabled: nextAccount.enabled ?? true,
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
} as OpenClawConfig;
|
|
203
|
+
}
|
|
204
|
+
|
|
74
205
|
if (mode === "bot") {
|
|
75
206
|
return {
|
|
76
207
|
...cfg,
|
|
@@ -101,6 +232,28 @@ function setWecomDmPolicy(
|
|
|
101
232
|
} as OpenClawConfig;
|
|
102
233
|
}
|
|
103
234
|
|
|
235
|
+
async function resolveOnboardingAccountId(params: {
|
|
236
|
+
cfg: OpenClawConfig;
|
|
237
|
+
prompter: WizardPrompter;
|
|
238
|
+
accountOverride?: string;
|
|
239
|
+
shouldPromptAccountIds: boolean;
|
|
240
|
+
}): Promise<string> {
|
|
241
|
+
const defaultAccountId = resolveDefaultWecomAccountId(params.cfg);
|
|
242
|
+
const override = params.accountOverride?.trim();
|
|
243
|
+
let accountId = override || defaultAccountId;
|
|
244
|
+
if (!override && params.shouldPromptAccountIds) {
|
|
245
|
+
accountId = await promptAccountId({
|
|
246
|
+
cfg: params.cfg,
|
|
247
|
+
prompter: params.prompter,
|
|
248
|
+
label: "WeCom",
|
|
249
|
+
currentId: accountId,
|
|
250
|
+
listAccountIds: (cfg) => listWecomAccountIds(cfg),
|
|
251
|
+
defaultAccountId,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
return accountId.trim() || DEFAULT_ACCOUNT_ID;
|
|
255
|
+
}
|
|
256
|
+
|
|
104
257
|
// ============================================================
|
|
105
258
|
// 欢迎与引导
|
|
106
259
|
// ============================================================
|
|
@@ -155,13 +308,15 @@ async function promptMode(prompter: WizardPrompter): Promise<WecomMode> {
|
|
|
155
308
|
async function configureBotMode(
|
|
156
309
|
cfg: OpenClawConfig,
|
|
157
310
|
prompter: WizardPrompter,
|
|
311
|
+
accountId: string,
|
|
158
312
|
): Promise<OpenClawConfig> {
|
|
313
|
+
const recommendedPath = accountWebhookPath("bot", accountId);
|
|
159
314
|
await prompter.note(
|
|
160
315
|
[
|
|
161
316
|
"正在配置 Bot 模式...",
|
|
162
317
|
"",
|
|
163
318
|
"💡 操作指南: 请在企微后台【管理工具 -> 智能机器人】开启 API 模式。",
|
|
164
|
-
|
|
319
|
+
`🔗 回调 URL (推荐): https://您的域名${recommendedPath}`,
|
|
165
320
|
"",
|
|
166
321
|
"请先在后台填入回调 URL,然后获取以下信息。",
|
|
167
322
|
].join("\n"),
|
|
@@ -206,7 +361,7 @@ async function configureBotMode(
|
|
|
206
361
|
welcomeText: welcomeText?.trim() || undefined,
|
|
207
362
|
};
|
|
208
363
|
|
|
209
|
-
return setWecomBotConfig(cfg, botConfig);
|
|
364
|
+
return setWecomBotConfig(cfg, botConfig, accountId);
|
|
210
365
|
}
|
|
211
366
|
|
|
212
367
|
// ============================================================
|
|
@@ -216,7 +371,9 @@ async function configureBotMode(
|
|
|
216
371
|
async function configureAgentMode(
|
|
217
372
|
cfg: OpenClawConfig,
|
|
218
373
|
prompter: WizardPrompter,
|
|
374
|
+
accountId: string,
|
|
219
375
|
): Promise<OpenClawConfig> {
|
|
376
|
+
const recommendedPath = accountWebhookPath("agent", accountId);
|
|
220
377
|
await prompter.note(
|
|
221
378
|
[
|
|
222
379
|
"正在配置 Agent 模式...",
|
|
@@ -256,7 +413,7 @@ async function configureAgentMode(
|
|
|
256
413
|
await prompter.note(
|
|
257
414
|
[
|
|
258
415
|
"💡 操作指南: 请在自建应用详情页进入【接收消息 -> 设置API接收】。",
|
|
259
|
-
|
|
416
|
+
`🔗 回调 URL (推荐): https://您的域名${recommendedPath}`,
|
|
260
417
|
"",
|
|
261
418
|
"请先在后台填入回调 URL,然后获取以下信息。",
|
|
262
419
|
].join("\n"),
|
|
@@ -297,7 +454,7 @@ async function configureAgentMode(
|
|
|
297
454
|
welcomeText: welcomeText?.trim() || undefined,
|
|
298
455
|
};
|
|
299
456
|
|
|
300
|
-
return setWecomAgentConfig(cfg, agentConfig);
|
|
457
|
+
return setWecomAgentConfig(cfg, agentConfig, accountId);
|
|
301
458
|
}
|
|
302
459
|
|
|
303
460
|
// ============================================================
|
|
@@ -308,6 +465,7 @@ async function promptDmPolicy(
|
|
|
308
465
|
cfg: OpenClawConfig,
|
|
309
466
|
prompter: WizardPrompter,
|
|
310
467
|
modes: ("bot" | "agent")[],
|
|
468
|
+
accountId: string,
|
|
311
469
|
): Promise<OpenClawConfig> {
|
|
312
470
|
const policyChoice = await prompter.select({
|
|
313
471
|
message: "请选择私聊 (DM) 访问策略:",
|
|
@@ -338,7 +496,7 @@ async function promptDmPolicy(
|
|
|
338
496
|
|
|
339
497
|
let result = cfg;
|
|
340
498
|
for (const mode of modes) {
|
|
341
|
-
result = setWecomDmPolicy(result, mode, dm);
|
|
499
|
+
result = setWecomDmPolicy(result, mode, dm, accountId);
|
|
342
500
|
}
|
|
343
501
|
return result;
|
|
344
502
|
}
|
|
@@ -347,20 +505,22 @@ async function promptDmPolicy(
|
|
|
347
505
|
// 配置汇总
|
|
348
506
|
// ============================================================
|
|
349
507
|
|
|
350
|
-
async function showSummary(cfg: OpenClawConfig, prompter: WizardPrompter): Promise<void> {
|
|
351
|
-
const
|
|
508
|
+
async function showSummary(cfg: OpenClawConfig, prompter: WizardPrompter, accountId: string): Promise<void> {
|
|
509
|
+
const account = resolveWecomAccount({ cfg, accountId });
|
|
352
510
|
const lines: string[] = ["✅ 配置已保存!", ""];
|
|
353
511
|
|
|
354
|
-
if (
|
|
512
|
+
if (account.bot?.configured) {
|
|
355
513
|
lines.push("📱 Bot 模式: 已配置");
|
|
356
|
-
lines.push(` 回调 URL: https
|
|
514
|
+
lines.push(` 回调 URL: https://您的域名${accountWebhookPath("bot", accountId)}`);
|
|
357
515
|
}
|
|
358
516
|
|
|
359
|
-
if (
|
|
517
|
+
if (account.agent?.configured) {
|
|
360
518
|
lines.push("🏢 Agent 模式: 已配置");
|
|
361
|
-
lines.push(` 回调 URL: https
|
|
519
|
+
lines.push(` 回调 URL: https://您的域名${accountWebhookPath("agent", accountId)}`);
|
|
362
520
|
}
|
|
363
521
|
|
|
522
|
+
lines.push(` 账号 ID: ${accountId}`);
|
|
523
|
+
|
|
364
524
|
lines.push("");
|
|
365
525
|
lines.push("⚠️ 请确保您已在企微后台填写了正确的回调 URL,");
|
|
366
526
|
lines.push(" 并点击了后台的『保存』按钮完成验证。");
|
|
@@ -378,11 +538,12 @@ const dmPolicy: ChannelOnboardingDmPolicy = {
|
|
|
378
538
|
policyKey: "channels.wecom.bot.dm.policy",
|
|
379
539
|
allowFromKey: "channels.wecom.bot.dm.allowFrom",
|
|
380
540
|
getCurrent: (cfg: OpenClawConfig) => {
|
|
381
|
-
const
|
|
382
|
-
return (
|
|
541
|
+
const account = resolveWecomAccount({ cfg });
|
|
542
|
+
return (account.bot?.config.dm?.policy ?? "pairing") as "pairing";
|
|
383
543
|
},
|
|
384
544
|
setPolicy: (cfg: OpenClawConfig, policy: "pairing" | "allowlist" | "open" | "disabled") => {
|
|
385
|
-
|
|
545
|
+
const accountId = resolveDefaultWecomAccountId(cfg);
|
|
546
|
+
return setWecomDmPolicy(cfg, "bot", { policy }, accountId);
|
|
386
547
|
},
|
|
387
548
|
promptAllowFrom: async ({ cfg, prompter }: { cfg: OpenClawConfig; prompter: WizardPrompter }) => {
|
|
388
549
|
const allowFromStr = String(
|
|
@@ -392,7 +553,8 @@ const dmPolicy: ChannelOnboardingDmPolicy = {
|
|
|
392
553
|
}),
|
|
393
554
|
).trim();
|
|
394
555
|
const allowFrom = allowFromStr.split(",").map((s) => s.trim()).filter(Boolean);
|
|
395
|
-
|
|
556
|
+
const accountId = resolveDefaultWecomAccountId(cfg);
|
|
557
|
+
return setWecomDmPolicy(cfg, "bot", { policy: "allowlist", allowFrom }, accountId);
|
|
396
558
|
},
|
|
397
559
|
};
|
|
398
560
|
|
|
@@ -404,60 +566,74 @@ export const wecomOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
|
404
566
|
channel,
|
|
405
567
|
dmPolicy,
|
|
406
568
|
getStatus: async ({ cfg }: { cfg: OpenClawConfig }) => {
|
|
407
|
-
const
|
|
408
|
-
const
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
);
|
|
412
|
-
const configured = botConfigured || agentConfigured;
|
|
569
|
+
const resolved = resolveWecomAccounts(cfg);
|
|
570
|
+
const accounts = Object.values(resolved.accounts).filter((account) => account.enabled !== false);
|
|
571
|
+
const botConfigured = accounts.some((account) => Boolean(account.bot?.configured));
|
|
572
|
+
const agentConfigured = accounts.some((account) => Boolean(account.agent?.configured));
|
|
573
|
+
const configured = accounts.some((account) => account.configured);
|
|
413
574
|
|
|
414
575
|
const statusParts: string[] = [];
|
|
415
576
|
if (botConfigured) statusParts.push("Bot ✓");
|
|
416
577
|
if (agentConfigured) statusParts.push("Agent ✓");
|
|
578
|
+
const accountSuffix = accounts.length > 1 ? ` · ${accounts.length} accounts` : "";
|
|
579
|
+
const statusSummary = statusParts.length > 0 ? statusParts.join(" + ") : "已配置";
|
|
417
580
|
|
|
418
581
|
return {
|
|
419
582
|
channel,
|
|
420
583
|
configured,
|
|
421
584
|
statusLines: [
|
|
422
|
-
`WeCom: ${configured ?
|
|
585
|
+
`WeCom: ${configured ? `${statusSummary}${accountSuffix}` : "需要配置"}`,
|
|
423
586
|
],
|
|
424
587
|
selectionHint: configured
|
|
425
|
-
? `configured · ${
|
|
588
|
+
? `configured · ${statusSummary}${accountSuffix}`
|
|
426
589
|
: "enterprise-ready · dual-mode",
|
|
427
590
|
quickstartScore: configured ? 1 : 8,
|
|
428
591
|
};
|
|
429
592
|
},
|
|
430
|
-
configure: async ({
|
|
593
|
+
configure: async ({
|
|
594
|
+
cfg,
|
|
595
|
+
prompter,
|
|
596
|
+
accountOverrides,
|
|
597
|
+
shouldPromptAccountIds,
|
|
598
|
+
}) => {
|
|
431
599
|
// 1. 欢迎
|
|
432
600
|
await showWelcome(prompter);
|
|
433
601
|
|
|
434
|
-
// 2.
|
|
602
|
+
// 2. 账号选择
|
|
603
|
+
const accountId = await resolveOnboardingAccountId({
|
|
604
|
+
cfg,
|
|
605
|
+
prompter,
|
|
606
|
+
accountOverride: accountOverrides.wecom,
|
|
607
|
+
shouldPromptAccountIds,
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
// 3. 模式选择
|
|
435
611
|
const mode = await promptMode(prompter);
|
|
436
612
|
|
|
437
613
|
let next = cfg;
|
|
438
614
|
const configuredModes: ("bot" | "agent")[] = [];
|
|
439
615
|
|
|
440
|
-
//
|
|
616
|
+
// 4. 配置 Bot
|
|
441
617
|
if (mode === "bot" || mode === "both") {
|
|
442
|
-
next = await configureBotMode(next, prompter);
|
|
618
|
+
next = await configureBotMode(next, prompter, accountId);
|
|
443
619
|
configuredModes.push("bot");
|
|
444
620
|
}
|
|
445
621
|
|
|
446
|
-
//
|
|
622
|
+
// 5. 配置 Agent
|
|
447
623
|
if (mode === "agent" || mode === "both") {
|
|
448
|
-
next = await configureAgentMode(next, prompter);
|
|
624
|
+
next = await configureAgentMode(next, prompter, accountId);
|
|
449
625
|
configuredModes.push("agent");
|
|
450
626
|
}
|
|
451
627
|
|
|
452
|
-
//
|
|
453
|
-
next = await promptDmPolicy(next, prompter, configuredModes);
|
|
628
|
+
// 6. DM 策略
|
|
629
|
+
next = await promptDmPolicy(next, prompter, configuredModes, accountId);
|
|
454
630
|
|
|
455
|
-
//
|
|
631
|
+
// 7. 启用通道
|
|
456
632
|
next = setWecomEnabled(next, true);
|
|
457
633
|
|
|
458
|
-
//
|
|
459
|
-
await showSummary(next, prompter);
|
|
634
|
+
// 8. 汇总
|
|
635
|
+
await showSummary(next, prompter, accountId);
|
|
460
636
|
|
|
461
|
-
return { cfg: next, accountId
|
|
637
|
+
return { cfg: next, accountId };
|
|
462
638
|
},
|
|
463
639
|
};
|
package/src/outbound.ts
CHANGED
|
@@ -178,6 +178,12 @@ export const wecomOutbound: ChannelOutboundAdapter = {
|
|
|
178
178
|
amr: "audio/amr", mp4: "video/mp4", pdf: "application/pdf", doc: "application/msword",
|
|
179
179
|
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
180
180
|
xls: "application/vnd.ms-excel", xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
181
|
+
ppt: "application/vnd.ms-powerpoint", pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
182
|
+
txt: "text/plain", csv: "text/csv", tsv: "text/tab-separated-values", md: "text/markdown", json: "application/json",
|
|
183
|
+
xml: "application/xml", yaml: "application/yaml", yml: "application/yaml",
|
|
184
|
+
zip: "application/zip", rar: "application/vnd.rar", "7z": "application/x-7z-compressed",
|
|
185
|
+
tar: "application/x-tar", gz: "application/gzip", tgz: "application/gzip",
|
|
186
|
+
rtf: "application/rtf", odt: "application/vnd.oasis.opendocument.text",
|
|
181
187
|
};
|
|
182
188
|
contentType = mimeTypes[ext] || "application/octet-stream";
|
|
183
189
|
console.log(`[wecom-outbound] Reading local file: ${mediaUrl}, ext=${ext}, contentType=${contentType}`);
|
package/src/types/constants.ts
CHANGED
|
@@ -4,12 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
/** 固定 Webhook 路径 */
|
|
6
6
|
export const WEBHOOK_PATHS = {
|
|
7
|
-
/** Bot
|
|
7
|
+
/** Bot 模式历史兼容路径(不再维护) */
|
|
8
8
|
BOT: "/wecom",
|
|
9
|
-
/** Bot
|
|
9
|
+
/** Bot 模式历史备用兼容路径(不再维护) */
|
|
10
10
|
BOT_ALT: "/wecom/bot",
|
|
11
|
-
/** Agent
|
|
11
|
+
/** Agent 模式历史兼容路径(不再维护) */
|
|
12
12
|
AGENT: "/wecom/agent",
|
|
13
|
+
/** Bot 模式(唯一支持路径前缀) */
|
|
14
|
+
BOT_PLUGIN: "/plugins/wecom/bot",
|
|
15
|
+
/** Agent 模式(唯一支持路径前缀) */
|
|
16
|
+
AGENT_PLUGIN: "/plugins/wecom/agent",
|
|
13
17
|
} as const;
|
|
14
18
|
|
|
15
19
|
/** 企业微信 API 端点 */
|