@mocrane/wecom 2026.3.20 → 2026.3.24

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 CHANGED
@@ -22,7 +22,7 @@
22
22
  <a id="sec-1"></a>
23
23
  ## 💡 核心价值:为什么选择本插件?
24
24
 
25
- ### 独创架构:Bot + Agent 双模融合 (Original Design by YanHaidao)
25
+ ### 独创架构:Bot + Agent 双模融合
26
26
 
27
27
  传统的企微插件通常只能在 "只能聊天的机器人 (Bot)" 和 "只能推送的自建应用 (Agent)" 之间二选一。
28
28
  本插件采用 **双模并行架构**,同时压榨两种模式的极限能力:
@@ -30,7 +30,7 @@
30
30
  * **Bot 通道 (智能体)**:负责 **实时对话**。提供毫秒级流式响应(打字机效果),零延迟交互。
31
31
  * **Agent 通道 (自建应用)**:负责 **能力兜底**。当需要发送图片/文件、进行全员广播、或 Bot 对话超时(>6分钟)时,无缝切换到 Agent 通道接管。
32
32
 
33
- ### 🚀 企业级:多账号(Multi-account)矩阵隔离 (Original Design)
33
+ ### 🚀 企业级:多账号(Multi-account)矩阵隔离
34
34
 
35
35
  本插件支持 **无限扩展的账号矩阵**,这是本插件区别于普通插件的核心壁垒:
36
36
 
@@ -332,8 +332,8 @@ openclaw channels status
332
332
  7. 记录回调 Token 和 EncodingAESKey
333
333
 
334
334
  <div align="center">
335
- <img src="https://cdn.jsdelivr.net/npm/@mocrane/wecom@latest/assets/03.bot.page.png" width="45%" alt="Bot Config" />
336
- <img src="https://cdn.jsdelivr.net/npm/@mocrane/wecom@latest/assets/03.agent.page.png" width="45%" alt="Agent Config" />
335
+ <img src="https://raw.githubusercontent.com/TencentCloud-Lighthouse/openclaw-wecom/main/assets/03.bot.page.png" width="45%" alt="Bot Config" />
336
+ <img src="https://raw.githubusercontent.com/TencentCloud-Lighthouse/openclaw-wecom/main/assets/03.agent.page.png" width="45%" alt="Agent Config" />
337
337
  </div>
338
338
 
339
339
  ---
@@ -476,13 +476,12 @@ Agent 输出 `{"template_card": ...}` 时自动渲染为交互卡片:
476
476
 
477
477
  <a id="sec-legal"></a>
478
478
 
479
- ## ⚖️ 授权与原创声明
479
+ ## ⚖️ 授权与致谢
480
480
 
481
- 本项目采用 **ISC License** 开源协议,并在此强调以下要求:
481
+ 本项目采用 **ISC License** 开源协议。
482
482
 
483
- 1. **保留署名**:根据 ISC 协议,您在任何分发、修改或使用本项目(或其部分逻辑)时,**必须**在显著位置完整保留本项目的版权声明(Copyright Notice)。
484
- 2. **尊重原创**:本项目包含的“Bot/Agent 自动化互补架构”、“长对话超时接力”、“WeCom 全媒体流自动化处理”等核心逻辑均为作者 **YanHaidao** 独立思考与实践的原创成果。
485
- 3. **维权申明**:对于恶意删除署名、像素级抄袭、混淆视听的恶意搬运行为,作者保留在社区公示及通过法律途径维权的权利。
483
+ - **开源协议**:您在分发、修改或使用本项目时,请遵循 ISC 协议的相关要求。
484
+ - **致谢**:本项目最初基于 **YanHaidao** 的企业微信插件 fork 而来,感谢其在早期版本中的工作与贡献。当前版本已由 **mocrane** 独立重构与维护。
486
485
 
487
486
  ---
488
487
 
package/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
- import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
2
+ import { emptyPluginConfigSchema, ensureConfigHelpers } from "./src/compat/plugin-sdk-shim.js";
3
3
 
4
4
  import { handleWecomWebhookRequest } from "./src/monitor.js";
5
5
  import { setWecomRuntime } from "./src/runtime.js";
@@ -22,6 +22,11 @@ const plugin = {
22
22
  * 5. 注入 MEDIA 指令提示词(仅 wecom 通道),指导 LLM 使用 MEDIA: 语法发送文件。
23
23
  */
24
24
  register(api: OpenClawPluginApi) {
25
+ // 初始化兼容层:确保 deleteAccountFromConfigSection 等函数在
26
+ // gateway 启动前绑定完成(register 执行与 gateway startAccount 之间
27
+ // 有充足的异步间隙)。
28
+ void ensureConfigHelpers();
29
+
25
30
  setWecomRuntime(api.runtime);
26
31
  api.registerChannel({ plugin: wecomPlugin });
27
32
  const routes = ["/plugins/wecom", "/wecom"];
@@ -38,12 +43,12 @@ const plugin = {
38
43
  api.registerTool(createWeComMcpTool(), { name: "wecom_mcp" });
39
44
 
40
45
  // 注入媒体发送指令和文件大小限制提示词(与官方 @wecom/wecom-openclaw-plugin 保持一致)。
41
- // 仅 wecom 通道注入,避免影响其他通道。
46
+ // 仅 wecom 通道注入,避免污染其他通道(如 Telegram/Discord)的 system prompt。
42
47
  // deliver 回调中保留兜底正则解析,作为双重保障。
43
48
  api.on("before_prompt_build", (_event, ctx) => {
44
49
  if (ctx.channelId !== "wecom") return;
45
50
  return {
46
- prependContext: [
51
+ systemPrompt: [
47
52
  "【发送文件/图片/视频/语音】",
48
53
  "当你需要向用户发送文件、图片、视频或语音时,必须在回复中单独一行使用 MEDIA: 指令,后面跟文件的本地路径。",
49
54
  "格式:MEDIA: /文件的绝对路径",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mocrane/wecom",
3
- "version": "2026.3.20",
3
+ "version": "2026.3.24",
4
4
  "type": "module",
5
5
  "description": "OpenClaw WeCom (WeChat Work) intelligent bot channel plugin",
6
6
  "main": "index.ts",
package/src/channel.ts CHANGED
@@ -3,10 +3,11 @@ import type {
3
3
  ChannelPlugin,
4
4
  OpenClawConfig,
5
5
  } from "openclaw/plugin-sdk";
6
+
6
7
  import {
7
8
  deleteAccountFromConfigSection,
8
9
  setAccountEnabledInConfigSection,
9
- } from "openclaw/plugin-sdk";
10
+ } from "./compat/plugin-sdk-shim.js";
10
11
 
11
12
  import {
12
13
  DEFAULT_ACCOUNT_ID,
@@ -39,10 +40,12 @@ function normalizeWecomMessagingTarget(raw: string): string | undefined {
39
40
  return trimmed.replace(/^(wecom-agent|wecom|wechatwork|wework|qywx):/i, "").trim() || undefined;
40
41
  }
41
42
 
43
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- onboarding 在 >=3.22 中已重命名为 setupWizard,
44
+ // 但我们仍设置旧字段以兼容 <3.22 版本的 OpenClaw。
42
45
  export const wecomPlugin: ChannelPlugin<ResolvedWecomAccount> = {
43
46
  id: "wecom",
44
47
  meta,
45
- onboarding: wecomOnboardingAdapter,
48
+ onboarding: wecomOnboardingAdapter as any,
46
49
  setup: {
47
50
  resolveAccountId: ({ cfg, accountId }) => {
48
51
  return accountId?.trim() || resolveDefaultWecomAccountId(cfg as OpenClawConfig) || DEFAULT_ACCOUNT_ID;
@@ -111,14 +114,14 @@ export const wecomPlugin: ChannelPlugin<ResolvedWecomAccount> = {
111
114
  accountId,
112
115
  enabled,
113
116
  allowTopLevel: true,
114
- }),
117
+ }) as OpenClawConfig,
115
118
  deleteAccount: ({ cfg, accountId }) =>
116
119
  deleteAccountFromConfigSection({
117
120
  cfg: cfg as OpenClawConfig,
118
121
  sectionKey: "wecom",
119
122
  accountId,
120
123
  clearBaseFields: ["bot", "agent"],
121
- }),
124
+ }) as OpenClawConfig,
122
125
  isConfigured: (account, cfg) => {
123
126
  if (!account.configured) {
124
127
  return false;
@@ -0,0 +1,210 @@
1
+ /**
2
+ * OpenClaw Plugin SDK 兼容 Shim — 同步接口
3
+ *
4
+ * 解决 v2026.3.23 中 plugin-sdk 子路径重构导致部分符号从主入口消失的问题。
5
+ *
6
+ * 策略:
7
+ * 1. 纯类型 → 直接从 `openclaw/plugin-sdk` 主入口重导出(所有版本均可)
8
+ * 2. DEFAULT_ACCOUNT_ID → 硬编码常量 "default"(所有版本一致)
9
+ * 3. deleteAccountFromConfigSection / setAccountEnabledInConfigSection
10
+ * → 通过在构建/加载时探测子路径,回退到主入口 compat 层
11
+ * 4. readJsonFileWithFallback / writeJsonFileAtomically / withFileLock
12
+ * → 异步解析(仅 mcp-config.ts 需要,该模块中函数本身就是 async)
13
+ * 5. promptAccountId → 异步解析(仅 onboarding.ts 需要,configure 回调本身是 async)
14
+ */
15
+
16
+ // ─── 类型重导出 ───
17
+ export type { OpenClawConfig } from "openclaw/plugin-sdk";
18
+ export type { PluginRuntime } from "openclaw/plugin-sdk";
19
+ export type { OpenClawPluginApi } from "openclaw/plugin-sdk";
20
+ export type { ChannelPlugin, ChannelConfigSchema } from "openclaw/plugin-sdk";
21
+ export type { ChannelAccountSnapshot } from "openclaw/plugin-sdk";
22
+ export type { ChannelGatewayContext } from "openclaw/plugin-sdk";
23
+ export type { WizardPrompter } from "openclaw/plugin-sdk";
24
+ export type {
25
+ ChannelOutboundAdapter,
26
+ ChannelOutboundContext,
27
+ } from "openclaw/plugin-sdk";
28
+
29
+ // ─── 值:emptyPluginConfigSchema(主入口始终导出) ───
30
+ export { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
31
+
32
+ // ─── DEFAULT_ACCOUNT_ID ───
33
+ export const DEFAULT_ACCOUNT_ID = "default";
34
+
35
+ // ─── 安全动态导入 ───
36
+ async function tryImport<T>(specifier: string): Promise<T | undefined> {
37
+ try {
38
+ return await import(specifier) as T;
39
+ } catch {
40
+ return undefined;
41
+ }
42
+ }
43
+
44
+ // ────────────────────────────────────────────────────
45
+ // deleteAccountFromConfigSection / setAccountEnabledInConfigSection
46
+ // 这些函数在 channel.ts 的 config 回调中同步使用。
47
+ // 使用 "eager init + cache" 模式:模块加载时立即解析并缓存。
48
+ // ────────────────────────────────────────────────────
49
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
50
+ type AnyFn = (...args: any[]) => any;
51
+
52
+ let _deleteAccountFromConfigSection: AnyFn | undefined;
53
+ let _setAccountEnabledInConfigSection: AnyFn | undefined;
54
+
55
+ // 立即触发的异步自解析
56
+ const _configHelpersReady = (async () => {
57
+ for (const subpath of [
58
+ "openclaw/plugin-sdk/core",
59
+ "openclaw/plugin-sdk/channel-plugin-common",
60
+ ]) {
61
+ const mod = await tryImport<Record<string, AnyFn>>(subpath);
62
+ if (mod?.deleteAccountFromConfigSection && mod?.setAccountEnabledInConfigSection) {
63
+ _deleteAccountFromConfigSection = mod.deleteAccountFromConfigSection;
64
+ _setAccountEnabledInConfigSection = mod.setAccountEnabledInConfigSection;
65
+ return;
66
+ }
67
+ }
68
+ // 如果子路径都没有,抛出明确错误
69
+ throw new Error(
70
+ "[wecom-compat] Cannot resolve config section helpers. " +
71
+ "Ensure openclaw >=2026.2.24 is installed.",
72
+ );
73
+ })();
74
+
75
+ /**
76
+ * 等待 config section helpers 解析完成。
77
+ * 在使用 deleteAccountFromConfigSection / setAccountEnabledInConfigSection 之前调用。
78
+ */
79
+ export async function ensureConfigHelpers(): Promise<void> {
80
+ await _configHelpersReady;
81
+ }
82
+
83
+ /** 同步获取 deleteAccountFromConfigSection(须确保 ensureConfigHelpers 已完成) */
84
+ export function deleteAccountFromConfigSection(...args: unknown[]): unknown {
85
+ if (!_deleteAccountFromConfigSection) {
86
+ throw new Error("[wecom-compat] Config helpers not initialized. Call ensureConfigHelpers() first.");
87
+ }
88
+ return _deleteAccountFromConfigSection(...args);
89
+ }
90
+
91
+ /** 同步获取 setAccountEnabledInConfigSection(须确保 ensureConfigHelpers 已完成) */
92
+ export function setAccountEnabledInConfigSection(...args: unknown[]): unknown {
93
+ if (!_setAccountEnabledInConfigSection) {
94
+ throw new Error("[wecom-compat] Config helpers not initialized. Call ensureConfigHelpers() first.");
95
+ }
96
+ return _setAccountEnabledInConfigSection(...args);
97
+ }
98
+
99
+ // ────────────────────────────────────────────────────
100
+ // promptAccountId (仅 onboarding.ts 需要,异步调用)
101
+ // ────────────────────────────────────────────────────
102
+ type PromptAccountIdFn = (params: {
103
+ cfg: unknown;
104
+ prompter: unknown;
105
+ label: string;
106
+ currentId: string;
107
+ listAccountIds: (cfg: unknown) => string[];
108
+ defaultAccountId: string;
109
+ }) => Promise<string>;
110
+
111
+ let _promptAccountId: PromptAccountIdFn | undefined;
112
+
113
+ export async function resolvePromptAccountId(): Promise<PromptAccountIdFn> {
114
+ if (_promptAccountId) return _promptAccountId;
115
+
116
+ for (const subpath of [
117
+ "openclaw/plugin-sdk/matrix",
118
+ "openclaw/plugin-sdk/channel-setup",
119
+ "openclaw/plugin-sdk/setup",
120
+ ]) {
121
+ const mod = await tryImport<{ promptAccountId?: PromptAccountIdFn }>(subpath);
122
+ if (mod?.promptAccountId) {
123
+ _promptAccountId = mod.promptAccountId;
124
+ return _promptAccountId;
125
+ }
126
+ }
127
+
128
+ // 兜底实现
129
+ _promptAccountId = async (params) => params.currentId || params.defaultAccountId;
130
+ return _promptAccountId;
131
+ }
132
+
133
+ // ────────────────────────────────────────────────────
134
+ // readJsonFileWithFallback / writeJsonFileAtomically / withFileLock
135
+ // (仅 mcp-config.ts 需要,函数本身为 async)
136
+ // ────────────────────────────────────────────────────
137
+ type FileLockFn = <T>(
138
+ filePath: string,
139
+ options: unknown,
140
+ fn: () => Promise<T>,
141
+ ) => Promise<T>;
142
+
143
+ type ReadJsonFn = <T>(
144
+ filePath: string,
145
+ fallback: T,
146
+ ) => Promise<{ value: T; exists: boolean }>;
147
+
148
+ type WriteJsonFn = (filePath: string, value: unknown) => Promise<void>;
149
+
150
+ export type FileIoHelpers = {
151
+ withFileLock: FileLockFn;
152
+ readJsonFileWithFallback: ReadJsonFn;
153
+ writeJsonFileAtomically: WriteJsonFn;
154
+ };
155
+
156
+ let _fileIo: FileIoHelpers | undefined;
157
+
158
+ export async function resolveFileIoHelpers(): Promise<FileIoHelpers> {
159
+ if (_fileIo) return _fileIo;
160
+
161
+ const jsonStore = await tryImport<Partial<FileIoHelpers>>("openclaw/plugin-sdk/json-store");
162
+ const msteams = await tryImport<{ withFileLock?: FileLockFn }>("openclaw/plugin-sdk/msteams");
163
+
164
+ const readFn = jsonStore?.readJsonFileWithFallback;
165
+ const writeFn = jsonStore?.writeJsonFileAtomically;
166
+ const lockFn = msteams?.withFileLock;
167
+
168
+ if (readFn && writeFn && lockFn) {
169
+ _fileIo = { readJsonFileWithFallback: readFn, writeJsonFileAtomically: writeFn, withFileLock: lockFn };
170
+ return _fileIo;
171
+ }
172
+
173
+ // ── Node.js 原生回退实现 ──
174
+ const fs = await import("node:fs/promises");
175
+ const nodePath = await import("node:path");
176
+
177
+ const fallbackRead: ReadJsonFn = async <T>(filePath: string, fallback: T) => {
178
+ try {
179
+ const raw = await fs.readFile(filePath, "utf-8");
180
+ return { value: JSON.parse(raw) as T, exists: true };
181
+ } catch (err) {
182
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") {
183
+ return { value: fallback, exists: false };
184
+ }
185
+ return { value: fallback, exists: false };
186
+ }
187
+ };
188
+
189
+ const fallbackWrite: WriteJsonFn = async (filePath: string, value: unknown) => {
190
+ const dir = nodePath.dirname(filePath);
191
+ await fs.mkdir(dir, { recursive: true, mode: 0o700 });
192
+ const content = JSON.stringify(value, null, 2) + "\n";
193
+ const tmpPath = `${filePath}.tmp.${process.pid}`;
194
+ await fs.writeFile(tmpPath, content, { mode: 0o600 });
195
+ await fs.rename(tmpPath, filePath);
196
+ };
197
+
198
+ const fallbackLock: FileLockFn = async <T>(
199
+ _filePath: string,
200
+ _options: unknown,
201
+ fn: () => Promise<T>,
202
+ ): Promise<T> => fn();
203
+
204
+ _fileIo = {
205
+ readJsonFileWithFallback: readFn ?? fallbackRead,
206
+ writeJsonFileAtomically: writeFn ?? fallbackWrite,
207
+ withFileLock: lockFn ?? fallbackLock,
208
+ };
209
+ return _fileIo;
210
+ }
@@ -14,7 +14,7 @@ import { readFileSync } from "node:fs";
14
14
  import { dirname, resolve } from "node:path";
15
15
  import { fileURLToPath } from "node:url";
16
16
  import { generateReqId } from "@wecom/aibot-node-sdk";
17
- import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk";
17
+ import { DEFAULT_ACCOUNT_ID } from "../compat/plugin-sdk-shim.js";
18
18
  import { getWsClient } from "../ws-adapter.js";
19
19
  import { withTimeout } from "../timeout.js";
20
20
 
package/src/mcp-config.ts CHANGED
@@ -11,11 +11,7 @@ import os from "os";
11
11
  import path from "path";
12
12
  import type { WSClient } from "@wecom/aibot-node-sdk";
13
13
  import { generateReqId } from "@wecom/aibot-node-sdk";
14
- import {
15
- readJsonFileWithFallback,
16
- writeJsonFileAtomically,
17
- withFileLock,
18
- } from "openclaw/plugin-sdk";
14
+ import { resolveFileIoHelpers } from "./compat/plugin-sdk-shim.js";
19
15
  import type { WecomRuntimeEnv } from "./monitor/types.js";
20
16
  import { withTimeout } from "./timeout.js";
21
17
 
@@ -122,6 +118,9 @@ async function saveMcpConfigToPluginJson(
122
118
  },
123
119
  };
124
120
 
121
+ const { withFileLock, readJsonFileWithFallback, writeJsonFileAtomically } =
122
+ await resolveFileIoHelpers();
123
+
125
124
  await withFileLock(wecomConfigPath, lockOptions, async () => {
126
125
  // 读取现有配置(不存在时使用空对象)
127
126
  const { value: pluginJson } = await readJsonFileWithFallback<Record<string, unknown>>(
package/src/onboarding.ts CHANGED
@@ -4,12 +4,50 @@
4
4
  */
5
5
 
6
6
  import type {
7
- ChannelOnboardingAdapter,
8
- ChannelOnboardingDmPolicy,
9
7
  OpenClawConfig,
10
8
  WizardPrompter,
11
9
  } from "openclaw/plugin-sdk";
12
- import { DEFAULT_ACCOUNT_ID, promptAccountId } from "openclaw/plugin-sdk";
10
+ import {
11
+ DEFAULT_ACCOUNT_ID,
12
+ resolvePromptAccountId,
13
+ } from "./compat/plugin-sdk-shim.js";
14
+
15
+ // ─── 类型兼容 ───
16
+ // v2026.3.2 使用 ChannelOnboardingAdapter / ChannelOnboardingDmPolicy(来自 onboarding-types.ts)
17
+ // v2026.3.22+ 重命名为 ChannelSetupWizardAdapter / ChannelSetupDmPolicy(setup-wizard-types.ts)
18
+ // 且 ChannelPlugin.onboarding → ChannelPlugin.setupWizard
19
+ // 为同时支持新旧版本,此处直接声明本地接口。
20
+ type ChannelOnboardingDmPolicy = {
21
+ label: string;
22
+ channel: string;
23
+ policyKey: string;
24
+ allowFromKey: string;
25
+ getCurrent: (cfg: OpenClawConfig, accountId?: string) => string;
26
+ setPolicy: (cfg: OpenClawConfig, policy: string, accountId?: string) => OpenClawConfig;
27
+ promptAllowFrom?: (params: {
28
+ cfg: OpenClawConfig;
29
+ prompter: WizardPrompter;
30
+ accountId?: string;
31
+ }) => Promise<OpenClawConfig>;
32
+ };
33
+
34
+ type ChannelOnboardingAdapter = {
35
+ channel: string;
36
+ dmPolicy?: ChannelOnboardingDmPolicy;
37
+ getStatus: (ctx: { cfg: OpenClawConfig }) => Promise<{
38
+ channel: string;
39
+ configured: boolean;
40
+ statusLines: string[];
41
+ selectionHint?: string;
42
+ quickstartScore?: number;
43
+ }>;
44
+ configure: (ctx: {
45
+ cfg: OpenClawConfig;
46
+ prompter: WizardPrompter;
47
+ accountOverrides: Record<string, string | undefined>;
48
+ shouldPromptAccountIds: boolean;
49
+ }) => Promise<{ cfg: OpenClawConfig; accountId?: string }>;
50
+ };
13
51
  import { listWecomAccountIds, resolveDefaultWecomAccountId, resolveWecomAccount, resolveWecomAccounts } from "./config/index.js";
14
52
  import type { WecomConfig, WecomBotConfig, WecomAgentConfig, WecomDmConfig, WecomAccountConfig } from "./types/index.js";
15
53
 
@@ -299,12 +337,13 @@ async function resolveOnboardingAccountId(params: {
299
337
  const override = params.accountOverride?.trim();
300
338
  let accountId = override || defaultAccountId;
301
339
  if (!override && params.shouldPromptAccountIds) {
340
+ const promptAccountId = await resolvePromptAccountId();
302
341
  accountId = await promptAccountId({
303
342
  cfg: params.cfg,
304
343
  prompter: params.prompter,
305
344
  label: "WeCom",
306
345
  currentId: accountId,
307
- listAccountIds: (cfg) => listWecomAccountIds(cfg),
346
+ listAccountIds: (cfg) => listWecomAccountIds(cfg as OpenClawConfig),
308
347
  defaultAccountId,
309
348
  });
310
349
  }
@@ -690,9 +729,9 @@ const dmPolicy: ChannelOnboardingDmPolicy = {
690
729
  const account = resolveWecomAccount({ cfg });
691
730
  return (account.bot?.config.dm?.policy ?? "pairing") as "pairing";
692
731
  },
693
- setPolicy: (cfg: OpenClawConfig, policy: "pairing" | "allowlist" | "open" | "disabled") => {
732
+ setPolicy: (cfg: OpenClawConfig, policy: string) => {
694
733
  const accountId = resolveDefaultWecomAccountId(cfg);
695
- return setWecomDmPolicy(cfg, "bot", { policy }, accountId);
734
+ return setWecomDmPolicy(cfg, "bot", { policy: policy as "pairing" | "allowlist" | "open" | "disabled" }, accountId);
696
735
  },
697
736
  promptAllowFrom: async ({ cfg, prompter }: { cfg: OpenClawConfig; prompter: WizardPrompter }) => {
698
737
  const allowFromStr = String(