@mocrane/wecom 2026.3.20 → 2026.3.24-beta
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 +8 -9
- package/index.ts +8 -3
- package/package.json +1 -1
- package/src/channel.ts +7 -4
- package/src/compat/plugin-sdk-shim.ts +210 -0
- package/src/mcp/transport.ts +1 -1
- package/src/mcp-config.ts +4 -5
- package/src/onboarding.ts +45 -6
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
<a id="sec-1"></a>
|
|
23
23
|
## 💡 核心价值:为什么选择本插件?
|
|
24
24
|
|
|
25
|
-
### 独创架构:Bot + Agent 双模融合
|
|
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)矩阵隔离
|
|
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://
|
|
336
|
-
<img src="https://
|
|
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
|
-
|
|
484
|
-
|
|
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 "
|
|
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
|
-
|
|
51
|
+
systemPrompt: [
|
|
47
52
|
"【发送文件/图片/视频/语音】",
|
|
48
53
|
"当你需要向用户发送文件、图片、视频或语音时,必须在回复中单独一行使用 MEDIA: 指令,后面跟文件的本地路径。",
|
|
49
54
|
"格式:MEDIA: /文件的绝对路径",
|
package/package.json
CHANGED
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 "
|
|
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
|
+
}
|
package/src/mcp/transport.ts
CHANGED
|
@@ -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 "
|
|
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 {
|
|
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:
|
|
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(
|