@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.
@@ -369,7 +369,7 @@ describe("handleWecomWebhookRequest", () => {
369
369
  }
370
370
  });
371
371
 
372
- it("routes matrix bot callback by explicit account path", async () => {
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-matrix";
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 matrix" },
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)}&timestamp=${encodeURIComponent(timestamp)}&nonce=${encodeURIComponent(nonce)}`,
431
+ url: `/plugins/wecom/bot/acct-b?msg_signature=${encodeURIComponent(msg_signature)}&timestamp=${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)}&timestamp=${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 bot path when matrix explicit routes are registered", async () => {
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)}&timestamp=${encodeURIComponent(timestamp)}&nonce=${encodeURIComponent(nonce)}&echostr=${encodeURIComponent(echostr)}`,
675
+ url: `/plugins/wecom/agent/default?msg_signature=${encodeURIComponent(signature)}&timestamp=${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 type { WecomConfig, WecomBotConfig, WecomAgentConfig, WecomDmConfig } from "./types/index.js";
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 setWecomBotConfig(cfg: OpenClawConfig, bot: WecomBotConfig): OpenClawConfig {
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
- ...(cfg.channels?.wecom ?? {}),
102
+ ...matrixWecom,
47
103
  enabled: true,
48
- bot,
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
- ...(cfg.channels?.wecom ?? {}),
142
+ ...matrixWecom,
61
143
  enabled: true,
62
- agent,
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
- "🔗 回调 URL: https://您的域名/wecom/bot",
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
- "🔗 回调 URL: https://您的域名/wecom/agent",
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 wecom = getWecomConfig(cfg);
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 (wecom?.bot?.token) {
512
+ if (account.bot?.configured) {
355
513
  lines.push("📱 Bot 模式: 已配置");
356
- lines.push(` 回调 URL: https://您的域名/wecom/bot`);
514
+ lines.push(` 回调 URL: https://您的域名${accountWebhookPath("bot", accountId)}`);
357
515
  }
358
516
 
359
- if (wecom?.agent?.corpId) {
517
+ if (account.agent?.configured) {
360
518
  lines.push("🏢 Agent 模式: 已配置");
361
- lines.push(` 回调 URL: https://您的域名/wecom/agent`);
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 wecom = getWecomConfig(cfg);
382
- return (wecom?.bot?.dm?.policy ?? "pairing") as "pairing";
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
- return setWecomDmPolicy(cfg, "bot", { policy });
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
- return setWecomDmPolicy(cfg, "bot", { policy: "allowlist", allowFrom });
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 wecom = getWecomConfig(cfg);
408
- const botConfigured = Boolean(wecom?.bot?.token && wecom?.bot?.encodingAESKey);
409
- const agentConfigured = Boolean(
410
- wecom?.agent?.corpId && wecom?.agent?.corpSecret && wecom?.agent?.agentId,
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 ? statusParts.join(" + ") : "需要配置"}`,
585
+ `WeCom: ${configured ? `${statusSummary}${accountSuffix}` : "需要配置"}`,
423
586
  ],
424
587
  selectionHint: configured
425
- ? `configured · ${statusParts.join(" + ")}`
588
+ ? `configured · ${statusSummary}${accountSuffix}`
426
589
  : "enterprise-ready · dual-mode",
427
590
  quickstartScore: configured ? 1 : 8,
428
591
  };
429
592
  },
430
- configure: async ({ cfg, prompter }: { cfg: OpenClawConfig; prompter: WizardPrompter }) => {
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
- // 3. 配置 Bot
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
- // 4. 配置 Agent
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
- // 5. DM 策略
453
- next = await promptDmPolicy(next, prompter, configuredModes);
628
+ // 6. DM 策略
629
+ next = await promptDmPolicy(next, prompter, configuredModes, accountId);
454
630
 
455
- // 6. 启用通道
631
+ // 7. 启用通道
456
632
  next = setWecomEnabled(next, true);
457
633
 
458
- // 7. 汇总
459
- await showSummary(next, prompter);
634
+ // 8. 汇总
635
+ await showSummary(next, prompter, accountId);
460
636
 
461
- return { cfg: next, accountId: DEFAULT_ACCOUNT_ID };
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}`);
@@ -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 端点 */