@dingxiang-me/openclaw-wechat 2.1.0 → 2.3.0
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/CHANGELOG.md +72 -0
- package/README.en.md +181 -14
- package/README.md +201 -16
- package/docs/channels/wecom.md +137 -1
- package/openclaw.plugin.json +688 -6
- package/package.json +204 -4
- package/scripts/wecom-agent-selfcheck.mjs +775 -0
- package/scripts/wecom-bot-longconn-probe.mjs +582 -0
- package/scripts/wecom-bot-selfcheck.mjs +952 -0
- package/scripts/wecom-callback-matrix.mjs +224 -0
- package/scripts/wecom-doctor.mjs +1407 -0
- package/scripts/wecom-e2e-scenario.mjs +333 -0
- package/scripts/wecom-migrate.mjs +261 -0
- package/scripts/wecom-quickstart.mjs +1824 -0
- package/scripts/wecom-release-check.mjs +232 -0
- package/scripts/wecom-remote-e2e.mjs +310 -0
- package/scripts/wecom-selfcheck.mjs +1255 -0
- package/scripts/wecom-smoke.sh +74 -0
- package/src/core/delivery-router.js +21 -0
- package/src/core.js +619 -30
- package/src/wecom/account-config-core.js +27 -1
- package/src/wecom/account-config.js +19 -2
- package/src/wecom/agent-dispatch-executor.js +11 -0
- package/src/wecom/agent-dispatch-handlers.js +61 -8
- package/src/wecom/agent-inbound-guards.js +24 -0
- package/src/wecom/agent-inbound-processor.js +34 -2
- package/src/wecom/agent-late-reply-runtime.js +30 -2
- package/src/wecom/agent-text-sender.js +2 -0
- package/src/wecom/api-client-core.js +27 -19
- package/src/wecom/api-client-media.js +16 -7
- package/src/wecom/api-client-send-text.js +4 -0
- package/src/wecom/api-client-send-typed.js +4 -1
- package/src/wecom/api-client-senders.js +41 -3
- package/src/wecom/api-client.js +1 -0
- package/src/wecom/bot-dispatch-fallback.js +18 -3
- package/src/wecom/bot-dispatch-handlers.js +47 -10
- package/src/wecom/bot-inbound-dispatch-runtime.js +3 -0
- package/src/wecom/bot-inbound-executor-helpers.js +11 -1
- package/src/wecom/bot-inbound-executor.js +24 -0
- package/src/wecom/bot-inbound-guards.js +31 -1
- package/src/wecom/channel-config-schema.js +132 -0
- package/src/wecom/channel-plugin.js +348 -7
- package/src/wecom/command-handlers.js +102 -11
- package/src/wecom/command-status-text.js +206 -0
- package/src/wecom/doc-client.js +7 -1
- package/src/wecom/inbound-content-handler-file-video-link.js +4 -0
- package/src/wecom/inbound-content-handler-image-voice.js +6 -0
- package/src/wecom/inbound-content.js +5 -0
- package/src/wecom/installer-api.js +910 -0
- package/src/wecom/media-download.js +2 -2
- package/src/wecom/migration-diagnostics.js +816 -0
- package/src/wecom/network-config.js +91 -0
- package/src/wecom/observability-metrics.js +9 -3
- package/src/wecom/outbound-agent-delivery.js +313 -0
- package/src/wecom/outbound-agent-media-sender.js +37 -7
- package/src/wecom/outbound-agent-push.js +1 -0
- package/src/wecom/outbound-delivery.js +129 -12
- package/src/wecom/outbound-stream-msg-item.js +25 -2
- package/src/wecom/outbound-webhook-delivery.js +19 -0
- package/src/wecom/outbound-webhook-media.js +30 -6
- package/src/wecom/pending-reply-manager.js +143 -0
- package/src/wecom/plugin-account-policy-services.js +26 -0
- package/src/wecom/plugin-base-services.js +58 -0
- package/src/wecom/plugin-constants.js +1 -1
- package/src/wecom/plugin-delivery-inbound-services.js +25 -0
- package/src/wecom/plugin-processing-deps.js +7 -0
- package/src/wecom/plugin-route-runtime-deps.js +1 -0
- package/src/wecom/plugin-services.js +87 -0
- package/src/wecom/policy-resolvers.js +93 -20
- package/src/wecom/quickstart-metadata.js +1247 -0
- package/src/wecom/reasoning-visibility.js +104 -0
- package/src/wecom/register-runtime.js +10 -0
- package/src/wecom/reliable-delivery-persistence.js +138 -0
- package/src/wecom/reliable-delivery.js +642 -0
- package/src/wecom/reply-output-policy.js +171 -0
- package/src/wecom/text-inbound-scheduler.js +6 -1
- package/src/wecom/workspace-auto-sender.js +2 -0
|
@@ -0,0 +1,1247 @@
|
|
|
1
|
+
import { collectWecomMigrationDiagnostics, WECOM_MIGRATION_COMMAND } from "./migration-diagnostics.js";
|
|
2
|
+
|
|
3
|
+
function deepClone(value) {
|
|
4
|
+
return JSON.parse(JSON.stringify(value));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function asObject(value) {
|
|
8
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function mergeDeep(base, patch) {
|
|
12
|
+
if (Array.isArray(patch)) return patch.slice();
|
|
13
|
+
if (!patch || typeof patch !== "object") return patch;
|
|
14
|
+
const out = { ...asObject(base) };
|
|
15
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
16
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
17
|
+
out[key] = mergeDeep(asObject(base?.[key]), value);
|
|
18
|
+
} else if (Array.isArray(value)) {
|
|
19
|
+
out[key] = value.slice();
|
|
20
|
+
} else {
|
|
21
|
+
out[key] = value;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizeAccountId(accountId = "default") {
|
|
28
|
+
const normalized = String(accountId ?? "default").trim().toLowerCase();
|
|
29
|
+
return normalized || "default";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizeAllowList(values = []) {
|
|
33
|
+
const out = [];
|
|
34
|
+
const seen = new Set();
|
|
35
|
+
const sourceValues = Array.isArray(values)
|
|
36
|
+
? values
|
|
37
|
+
: String(values ?? "")
|
|
38
|
+
.split(/[,\n]/)
|
|
39
|
+
.map((item) => item.trim());
|
|
40
|
+
for (const rawValue of sourceValues) {
|
|
41
|
+
const normalized = String(rawValue ?? "").trim();
|
|
42
|
+
if (!normalized || seen.has(normalized)) continue;
|
|
43
|
+
seen.add(normalized);
|
|
44
|
+
out.push(normalized);
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function uniqueStrings(values = []) {
|
|
50
|
+
return Array.from(
|
|
51
|
+
new Set(
|
|
52
|
+
values
|
|
53
|
+
.map((item) => String(item ?? "").trim())
|
|
54
|
+
.filter(Boolean),
|
|
55
|
+
),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function pickFirstNonEmptyString(...values) {
|
|
60
|
+
for (const value of values) {
|
|
61
|
+
const trimmed = String(value ?? "").trim();
|
|
62
|
+
if (trimmed) return trimmed;
|
|
63
|
+
}
|
|
64
|
+
return "";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function buildAccountContainer(accountId, config) {
|
|
68
|
+
const normalizedAccountId = normalizeAccountId(accountId);
|
|
69
|
+
if (normalizedAccountId === "default") return config;
|
|
70
|
+
return {
|
|
71
|
+
defaultAccount: normalizedAccountId,
|
|
72
|
+
accounts: {
|
|
73
|
+
[normalizedAccountId]: config,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function dedupeCheckItems(items = []) {
|
|
79
|
+
const seen = new Set();
|
|
80
|
+
const out = [];
|
|
81
|
+
for (const item of items) {
|
|
82
|
+
const id = String(item?.id ?? "").trim();
|
|
83
|
+
if (!id || seen.has(id)) continue;
|
|
84
|
+
seen.add(id);
|
|
85
|
+
out.push(item);
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function withDmMode(baseConfig, dmMode = "pairing") {
|
|
91
|
+
return {
|
|
92
|
+
...baseConfig,
|
|
93
|
+
dm: {
|
|
94
|
+
mode: String(dmMode ?? "pairing").trim().toLowerCase() || "pairing",
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function resolveStarterAccountConfig(starterConfig, accountId = "default") {
|
|
100
|
+
const normalizedAccountId = normalizeAccountId(accountId);
|
|
101
|
+
const channelConfig = asObject(starterConfig?.channels?.wecom);
|
|
102
|
+
if (normalizedAccountId === "default") return channelConfig;
|
|
103
|
+
return asObject(channelConfig?.accounts?.[normalizedAccountId]);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function detectWecomAccountCapabilities(config = {}, accountId = "default") {
|
|
107
|
+
const normalizedAccountId = normalizeAccountId(accountId);
|
|
108
|
+
const channelConfig = asObject(config?.channels?.wecom);
|
|
109
|
+
const configuredDefaultAccountId = normalizeAccountId(channelConfig?.defaultAccount ?? "default");
|
|
110
|
+
const accountConfig =
|
|
111
|
+
normalizedAccountId === "default"
|
|
112
|
+
? mergeDeep(channelConfig, asObject(channelConfig?.accounts?.[configuredDefaultAccountId]))
|
|
113
|
+
: asObject(channelConfig?.accounts?.[normalizedAccountId] ?? channelConfig?.[normalizedAccountId]);
|
|
114
|
+
const legacyAgent = asObject(accountConfig?.agent);
|
|
115
|
+
const bot = asObject(accountConfig?.bot);
|
|
116
|
+
const longConnection = asObject(bot?.longConnection);
|
|
117
|
+
const hasAgent =
|
|
118
|
+
Boolean(pickFirstNonEmptyString(accountConfig?.corpId, legacyAgent?.corpId)) &&
|
|
119
|
+
Boolean(pickFirstNonEmptyString(accountConfig?.corpSecret, legacyAgent?.corpSecret)) &&
|
|
120
|
+
Number.isFinite(Number(accountConfig?.agentId ?? legacyAgent?.agentId));
|
|
121
|
+
const hasBotLongConnection =
|
|
122
|
+
Boolean(pickFirstNonEmptyString(longConnection?.botId, longConnection?.botid, accountConfig?.botId, accountConfig?.botid)) &&
|
|
123
|
+
Boolean(pickFirstNonEmptyString(longConnection?.secret, accountConfig?.secret));
|
|
124
|
+
const hasBotWebhook =
|
|
125
|
+
Boolean(pickFirstNonEmptyString(bot?.token, bot?.callbackToken, accountConfig?.token)) &&
|
|
126
|
+
Boolean(pickFirstNonEmptyString(bot?.encodingAesKey, bot?.callbackAesKey, accountConfig?.encodingAesKey));
|
|
127
|
+
const hasGroupAllowlist =
|
|
128
|
+
String(accountConfig?.groupPolicy ?? accountConfig?.groupChat?.policy ?? "").trim().toLowerCase() === "allowlist" ||
|
|
129
|
+
(Array.isArray(accountConfig?.groupAllowFrom) && accountConfig.groupAllowFrom.length > 0);
|
|
130
|
+
return {
|
|
131
|
+
hasAgent,
|
|
132
|
+
hasBotLongConnection,
|
|
133
|
+
hasBotWebhook,
|
|
134
|
+
hasBot: hasBotLongConnection || hasBotWebhook,
|
|
135
|
+
hasGroupAllowlist,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const DEFAULT_ALLOWLIST_GROUP_CHAT_ID = "wr-your-chat-id";
|
|
140
|
+
const DEFAULT_ALLOWLIST_MEMBERS = Object.freeze(["ops_lead", "oncall_user"]);
|
|
141
|
+
|
|
142
|
+
const QUICKSTART_GROUP_PROFILE_DEFINITIONS = {
|
|
143
|
+
inherit: {
|
|
144
|
+
id: "inherit",
|
|
145
|
+
label: "保持默认",
|
|
146
|
+
summary: "不额外写入群聊策略,保留当前默认行为。",
|
|
147
|
+
recommendedForModes: ["bot_long_connection", "agent_callback", "hybrid"],
|
|
148
|
+
notes: [
|
|
149
|
+
"适合先把私聊或 Bot 对话跑通,再按实际群场景补群策略。",
|
|
150
|
+
],
|
|
151
|
+
buildPatch() {
|
|
152
|
+
return {};
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
mention_only: {
|
|
156
|
+
id: "mention_only",
|
|
157
|
+
label: "仅 @ 触发",
|
|
158
|
+
summary: "群里保持开放,但只在 @ 机器人时触发。",
|
|
159
|
+
recommendedForModes: ["bot_long_connection", "hybrid"],
|
|
160
|
+
notes: [
|
|
161
|
+
"Bot 模式通常最适合这类配置,能显著降低群噪音。",
|
|
162
|
+
],
|
|
163
|
+
buildPatch() {
|
|
164
|
+
return {
|
|
165
|
+
groupPolicy: "open",
|
|
166
|
+
groupChat: {
|
|
167
|
+
enabled: true,
|
|
168
|
+
triggerMode: "mention",
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
open_direct: {
|
|
174
|
+
id: "open_direct",
|
|
175
|
+
label: "群聊直出",
|
|
176
|
+
summary: "群里所有成员都可直接触发,无需 @。",
|
|
177
|
+
recommendedForModes: ["agent_callback"],
|
|
178
|
+
notes: [
|
|
179
|
+
"更适合自建应用群回调场景;企业微信 Bot 平台通常仍只会按 mention 回调。",
|
|
180
|
+
],
|
|
181
|
+
buildPatch() {
|
|
182
|
+
return {
|
|
183
|
+
groupPolicy: "open",
|
|
184
|
+
groupChat: {
|
|
185
|
+
enabled: true,
|
|
186
|
+
triggerMode: "direct",
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
allowlist_template: {
|
|
192
|
+
id: "allowlist_template",
|
|
193
|
+
label: "群白名单模板",
|
|
194
|
+
summary: "预置 allowlist、按群覆盖和拒绝文案,适合值班群/运营群。",
|
|
195
|
+
recommendedForModes: ["agent_callback", "hybrid", "bot_long_connection"],
|
|
196
|
+
notes: [
|
|
197
|
+
"可配合 --group-chat-id 和 --group-allow 直接生成一份可落地模板。",
|
|
198
|
+
],
|
|
199
|
+
buildPatch({ groupChatId = "", groupAllow = [] } = {}) {
|
|
200
|
+
const normalizedAllow = normalizeAllowList(groupAllow);
|
|
201
|
+
const allowList = normalizedAllow.length > 0 ? normalizedAllow : [...DEFAULT_ALLOWLIST_MEMBERS];
|
|
202
|
+
const targetChatId = String(groupChatId ?? "").trim() || DEFAULT_ALLOWLIST_GROUP_CHAT_ID;
|
|
203
|
+
return {
|
|
204
|
+
groupPolicy: "allowlist",
|
|
205
|
+
groupChat: {
|
|
206
|
+
enabled: true,
|
|
207
|
+
triggerMode: "mention",
|
|
208
|
+
},
|
|
209
|
+
groupAllowFrom: allowList,
|
|
210
|
+
groups: {
|
|
211
|
+
[targetChatId]: {
|
|
212
|
+
policy: "allowlist",
|
|
213
|
+
triggerMode: "mention",
|
|
214
|
+
allowFrom: allowList,
|
|
215
|
+
rejectMessage: "当前群仅限值班同学触发。",
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
deny: {
|
|
222
|
+
id: "deny",
|
|
223
|
+
label: "关闭群聊",
|
|
224
|
+
summary: "显式关闭群聊处理,只保留私聊/点对点路径。",
|
|
225
|
+
recommendedForModes: ["bot_long_connection", "agent_callback", "hybrid"],
|
|
226
|
+
notes: [
|
|
227
|
+
"适合先只开放私聊,再逐步灰度群能力。",
|
|
228
|
+
],
|
|
229
|
+
buildPatch() {
|
|
230
|
+
return {
|
|
231
|
+
groupPolicy: "deny",
|
|
232
|
+
groupChat: {
|
|
233
|
+
enabled: false,
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const QUICKSTART_PLACEHOLDER_RULES = Object.freeze([
|
|
241
|
+
{
|
|
242
|
+
id: "corpId",
|
|
243
|
+
match: ({ path, value }) => path.endsWith(".corpId") && value === "ww-your-corp-id",
|
|
244
|
+
label: "CorpId",
|
|
245
|
+
category: "agent",
|
|
246
|
+
action: "替换成企业微信 CorpId",
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
id: "corpSecret",
|
|
250
|
+
match: ({ path, value }) => path.endsWith(".corpSecret") && value === "your-app-secret",
|
|
251
|
+
label: "CorpSecret",
|
|
252
|
+
category: "agent",
|
|
253
|
+
action: "替换成自建应用 Secret",
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
id: "agentId",
|
|
257
|
+
match: ({ path, value }) => path.endsWith(".agentId") && Number(value) === 1000002,
|
|
258
|
+
label: "AgentId",
|
|
259
|
+
category: "agent",
|
|
260
|
+
action: "确认并替换成真实 AgentId",
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
id: "callbackToken",
|
|
264
|
+
match: ({ path, value }) => path.endsWith(".callbackToken") && value === "your-callback-token",
|
|
265
|
+
label: "Callback Token",
|
|
266
|
+
category: "agent",
|
|
267
|
+
action: "替换成企业微信回调 Token",
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
id: "callbackAesKey",
|
|
271
|
+
match: ({ path, value }) => path.endsWith(".callbackAesKey") && value === "your-callback-aes-key",
|
|
272
|
+
label: "Callback AES Key",
|
|
273
|
+
category: "agent",
|
|
274
|
+
action: "替换成企业微信回调 EncodingAESKey",
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
id: "botId",
|
|
278
|
+
match: ({ path, value }) => path.endsWith(".botId") && value === "your-bot-id",
|
|
279
|
+
label: "BotID",
|
|
280
|
+
category: "bot",
|
|
281
|
+
action: "替换成 Bot 长连接 BotID",
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
id: "botSecret",
|
|
285
|
+
match: ({ path, value }) => path.endsWith(".secret") && value === "your-bot-secret",
|
|
286
|
+
label: "Bot Secret",
|
|
287
|
+
category: "bot",
|
|
288
|
+
action: "替换成 Bot 长连接 Secret",
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
id: "groupChatId",
|
|
292
|
+
match: ({ path, value }) => path.includes(".groups.") && value === "wr-your-chat-id",
|
|
293
|
+
label: "群 ChatId",
|
|
294
|
+
category: "group",
|
|
295
|
+
action: "替换成真实群 ChatId",
|
|
296
|
+
},
|
|
297
|
+
]);
|
|
298
|
+
|
|
299
|
+
const QUICKSTART_MODE_DEFINITIONS = {
|
|
300
|
+
bot_long_connection: {
|
|
301
|
+
id: "bot_long_connection",
|
|
302
|
+
label: "Bot 长连接",
|
|
303
|
+
recommended: true,
|
|
304
|
+
requiresPublicWebhook: false,
|
|
305
|
+
supportsPairing: true,
|
|
306
|
+
docsAnchor: "#bot-long-connection",
|
|
307
|
+
summary: "最快跑通对话;无需公网 callback,优先推荐。",
|
|
308
|
+
requiredConfigPaths: [
|
|
309
|
+
"channels.wecom.bot.enabled",
|
|
310
|
+
"channels.wecom.bot.longConnection.enabled",
|
|
311
|
+
"channels.wecom.bot.longConnection.botId",
|
|
312
|
+
"channels.wecom.bot.longConnection.secret",
|
|
313
|
+
],
|
|
314
|
+
checks: [
|
|
315
|
+
"npm run wecom:bot:selfcheck -- --account default",
|
|
316
|
+
"npm run wecom:bot:longconn:probe -- --json",
|
|
317
|
+
],
|
|
318
|
+
notes: [
|
|
319
|
+
"适合先验证收消息、回消息和 Bot PDF/语音链路。",
|
|
320
|
+
"如需跨会话主动发送,建议后续再补 Agent 模式。",
|
|
321
|
+
],
|
|
322
|
+
buildConfig({ accountId = "default", dmMode = "pairing" } = {}) {
|
|
323
|
+
return {
|
|
324
|
+
channels: {
|
|
325
|
+
wecom: {
|
|
326
|
+
enabled: true,
|
|
327
|
+
...buildAccountContainer(
|
|
328
|
+
accountId,
|
|
329
|
+
withDmMode(
|
|
330
|
+
{
|
|
331
|
+
bot: {
|
|
332
|
+
enabled: true,
|
|
333
|
+
longConnection: {
|
|
334
|
+
enabled: true,
|
|
335
|
+
botId: "your-bot-id",
|
|
336
|
+
secret: "your-bot-secret",
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
dmMode,
|
|
341
|
+
),
|
|
342
|
+
),
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
agent_callback: {
|
|
349
|
+
id: "agent_callback",
|
|
350
|
+
label: "自建应用回调",
|
|
351
|
+
recommended: false,
|
|
352
|
+
requiresPublicWebhook: true,
|
|
353
|
+
supportsPairing: true,
|
|
354
|
+
docsAnchor: "#callback-url",
|
|
355
|
+
summary: "适合需要主动发送、自建应用菜单和 Agent API 的场景。",
|
|
356
|
+
requiredConfigPaths: [
|
|
357
|
+
"channels.wecom.corpId",
|
|
358
|
+
"channels.wecom.corpSecret",
|
|
359
|
+
"channels.wecom.agentId",
|
|
360
|
+
"channels.wecom.callbackToken",
|
|
361
|
+
"channels.wecom.callbackAesKey",
|
|
362
|
+
"channels.wecom.webhookPath",
|
|
363
|
+
],
|
|
364
|
+
checks: [
|
|
365
|
+
"npm run wecom:agent:selfcheck -- --account default",
|
|
366
|
+
"npm run wecom:selfcheck -- --account default",
|
|
367
|
+
],
|
|
368
|
+
notes: [
|
|
369
|
+
"需要稳定公网域名,不建议正式环境依赖临时隧道。",
|
|
370
|
+
"适合要发图片、文件、群主动通知和 wecom_doc 的团队。",
|
|
371
|
+
],
|
|
372
|
+
buildConfig({ accountId = "default", dmMode = "pairing" } = {}) {
|
|
373
|
+
return {
|
|
374
|
+
channels: {
|
|
375
|
+
wecom: {
|
|
376
|
+
enabled: true,
|
|
377
|
+
...buildAccountContainer(
|
|
378
|
+
accountId,
|
|
379
|
+
withDmMode(
|
|
380
|
+
{
|
|
381
|
+
corpId: "ww-your-corp-id",
|
|
382
|
+
corpSecret: "your-app-secret",
|
|
383
|
+
agentId: 1000002,
|
|
384
|
+
callbackToken: "your-callback-token",
|
|
385
|
+
callbackAesKey: "your-callback-aes-key",
|
|
386
|
+
webhookPath: "/wecom/callback",
|
|
387
|
+
},
|
|
388
|
+
dmMode,
|
|
389
|
+
),
|
|
390
|
+
),
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
hybrid: {
|
|
397
|
+
id: "hybrid",
|
|
398
|
+
label: "Bot + Agent 双通道",
|
|
399
|
+
recommended: false,
|
|
400
|
+
requiresPublicWebhook: true,
|
|
401
|
+
supportsPairing: true,
|
|
402
|
+
docsAnchor: "#callback-url",
|
|
403
|
+
summary: "同时保留 Bot 长连接的接入顺滑和 Agent 的主动发送能力。",
|
|
404
|
+
requiredConfigPaths: [
|
|
405
|
+
"channels.wecom.corpId",
|
|
406
|
+
"channels.wecom.corpSecret",
|
|
407
|
+
"channels.wecom.agentId",
|
|
408
|
+
"channels.wecom.callbackToken",
|
|
409
|
+
"channels.wecom.callbackAesKey",
|
|
410
|
+
"channels.wecom.bot.enabled",
|
|
411
|
+
"channels.wecom.bot.longConnection.enabled",
|
|
412
|
+
"channels.wecom.bot.longConnection.botId",
|
|
413
|
+
"channels.wecom.bot.longConnection.secret",
|
|
414
|
+
],
|
|
415
|
+
checks: [
|
|
416
|
+
"npm run wecom:agent:selfcheck -- --account default",
|
|
417
|
+
"npm run wecom:bot:selfcheck -- --account default",
|
|
418
|
+
],
|
|
419
|
+
notes: [
|
|
420
|
+
"推荐给既要在 Bot 场景里对话,又要保留 Agent 主动发送/文档工具的部署。",
|
|
421
|
+
"可先按 Bot 长连接上线,再补 Agent 公网 callback。",
|
|
422
|
+
],
|
|
423
|
+
buildConfig({ accountId = "default", dmMode = "pairing" } = {}) {
|
|
424
|
+
return {
|
|
425
|
+
channels: {
|
|
426
|
+
wecom: {
|
|
427
|
+
enabled: true,
|
|
428
|
+
...buildAccountContainer(
|
|
429
|
+
accountId,
|
|
430
|
+
withDmMode(
|
|
431
|
+
{
|
|
432
|
+
corpId: "ww-your-corp-id",
|
|
433
|
+
corpSecret: "your-app-secret",
|
|
434
|
+
agentId: 1000002,
|
|
435
|
+
callbackToken: "your-callback-token",
|
|
436
|
+
callbackAesKey: "your-callback-aes-key",
|
|
437
|
+
webhookPath: "/wecom/callback",
|
|
438
|
+
bot: {
|
|
439
|
+
enabled: true,
|
|
440
|
+
longConnection: {
|
|
441
|
+
enabled: true,
|
|
442
|
+
botId: "your-bot-id",
|
|
443
|
+
secret: "your-bot-secret",
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
dmMode,
|
|
448
|
+
),
|
|
449
|
+
),
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
};
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
export const WECOM_QUICKSTART_RECOMMENDED_MODE = "bot_long_connection";
|
|
458
|
+
export const WECOM_QUICKSTART_DEFAULT_GROUP_PROFILE = "inherit";
|
|
459
|
+
export const WECOM_QUICKSTART_SETUP_COMMAND = "npm run wecom:quickstart -- --json";
|
|
460
|
+
export const WECOM_QUICKSTART_WRITE_COMMAND = "npm run wecom:quickstart -- --write";
|
|
461
|
+
export const WECOM_QUICKSTART_WIZARD_COMMAND = "npm run wecom:quickstart -- --wizard";
|
|
462
|
+
export const WECOM_QUICKSTART_RUN_CHECKS_COMMAND = "npm run wecom:quickstart -- --run-checks";
|
|
463
|
+
export const WECOM_QUICKSTART_FORCE_CHECKS_COMMAND = "npm run wecom:quickstart -- --run-checks --force-checks";
|
|
464
|
+
export const WECOM_QUICKSTART_APPLY_REPAIR_COMMAND = "npm run wecom:quickstart -- --run-checks --apply-repair";
|
|
465
|
+
export const WECOM_QUICKSTART_CONFIRM_REPAIR_COMMAND = "npm run wecom:quickstart -- --run-checks --confirm-repair";
|
|
466
|
+
export const WECOM_QUICKSTART_MIGRATION_COMMAND = WECOM_MIGRATION_COMMAND;
|
|
467
|
+
export const WECOM_DOCTOR_COMMAND = "npm run wecom:doctor -- --json";
|
|
468
|
+
|
|
469
|
+
export function buildWecomSourceCheckOrder({
|
|
470
|
+
source = "fresh",
|
|
471
|
+
capabilities = {},
|
|
472
|
+
selectedMode = WECOM_QUICKSTART_RECOMMENDED_MODE,
|
|
473
|
+
} = {}) {
|
|
474
|
+
const checks = [];
|
|
475
|
+
const pushCheck = (id, title, detail, command) => {
|
|
476
|
+
checks.push({
|
|
477
|
+
id,
|
|
478
|
+
title,
|
|
479
|
+
detail,
|
|
480
|
+
command,
|
|
481
|
+
});
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
pushCheck(
|
|
485
|
+
"doctor_offline",
|
|
486
|
+
"先跑离线 doctor",
|
|
487
|
+
"先验证安装结构、迁移结果和本地插件布局,不依赖公网回调或网络出口。",
|
|
488
|
+
"npm run wecom:doctor -- --skip-network --skip-local-webhook --json",
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
if (source === "official-wecom") {
|
|
492
|
+
if (capabilities.hasBot || selectedMode !== "agent_callback") {
|
|
493
|
+
pushCheck("bot_selfcheck", "检查 Bot 基础配置", "优先确认扁平 Bot 配置已迁到当前结构。", "npm run wecom:bot:selfcheck -- --account default");
|
|
494
|
+
}
|
|
495
|
+
if (capabilities.hasBotLongConnection || selectedMode !== "agent_callback") {
|
|
496
|
+
pushCheck("bot_longconn_probe", "探测 Bot 长连接", "在真正收发消息前,先验证长连接握手和代理链路。", "npm run wecom:bot:longconn:probe -- --json");
|
|
497
|
+
}
|
|
498
|
+
if (capabilities.hasAgent || selectedMode !== "bot_long_connection") {
|
|
499
|
+
pushCheck("agent_selfcheck", "检查 Agent 配置", "如果来源里同时带了 Agent 能力,再确认 corpId/corpSecret/agentId。", "npm run wecom:agent:selfcheck -- --account default");
|
|
500
|
+
pushCheck("channel_selfcheck", "检查综合回包能力", "最后确认 WeCom 总体 readiness 和当前账号摘要。", "npm run wecom:selfcheck -- --account default");
|
|
501
|
+
}
|
|
502
|
+
return dedupeCheckItems(checks);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (source === "sunnoy-wecom") {
|
|
506
|
+
if (capabilities.hasBot || selectedMode !== "agent_callback") {
|
|
507
|
+
pushCheck("bot_selfcheck", "检查 Bot 基础配置", "先确认 Bot 兼容字段已迁到当前结构。", "npm run wecom:bot:selfcheck -- --account default");
|
|
508
|
+
}
|
|
509
|
+
if (capabilities.hasAgent || selectedMode !== "bot_long_connection") {
|
|
510
|
+
pushCheck("agent_selfcheck", "检查 Agent 配置", "确认 Agent 兼容字段和主动发送能力都已保留。", "npm run wecom:agent:selfcheck -- --account default");
|
|
511
|
+
}
|
|
512
|
+
if (capabilities.hasBotLongConnection || selectedMode !== "agent_callback") {
|
|
513
|
+
pushCheck("bot_longconn_probe", "探测 Bot 长连接", "sunnoy 来源常带代理/出网配置,优先验证长连接网络。", "npm run wecom:bot:longconn:probe -- --json");
|
|
514
|
+
}
|
|
515
|
+
pushCheck("doctor_online", "最后跑联网 doctor", "把代理、apiBaseUrl、公网回调和真实网络探测一起验证。", "npm run wecom:doctor -- --json");
|
|
516
|
+
return dedupeCheckItems(checks);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (source === "legacy-openclaw-wechat") {
|
|
520
|
+
if (capabilities.hasAgent || selectedMode !== "bot_long_connection") {
|
|
521
|
+
pushCheck("agent_selfcheck", "检查 Agent 配置", "legacy 来源常含 agent.* 兼容块,先确认 Agent 已迁正。", "npm run wecom:agent:selfcheck -- --account default");
|
|
522
|
+
}
|
|
523
|
+
if (capabilities.hasBot || selectedMode !== "agent_callback") {
|
|
524
|
+
pushCheck("bot_selfcheck", "检查 Bot 基础配置", "确认旧版 Bot 字段已并到当前 bot.longConnection / bot.* 结构。", "npm run wecom:bot:selfcheck -- --account default");
|
|
525
|
+
}
|
|
526
|
+
if (capabilities.hasBotLongConnection || selectedMode === "bot_long_connection" || selectedMode === "hybrid") {
|
|
527
|
+
pushCheck("bot_longconn_probe", "探测 Bot 长连接", "如果保留了 Bot 长连接,再额外验证 websocket 侧是否正常。", "npm run wecom:bot:longconn:probe -- --json");
|
|
528
|
+
}
|
|
529
|
+
pushCheck("channel_selfcheck", "检查综合回包能力", "最后确认当前账号在迁移后仍能收、回、发。", "npm run wecom:selfcheck -- --account default");
|
|
530
|
+
return dedupeCheckItems(checks);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (source === "mixed-source") {
|
|
534
|
+
if (capabilities.hasBot || selectedMode !== "agent_callback") {
|
|
535
|
+
pushCheck("bot_selfcheck", "检查 Bot 基础配置", "混合来源先分别确认 Bot 侧字段是否已经收口。", "npm run wecom:bot:selfcheck -- --account default");
|
|
536
|
+
}
|
|
537
|
+
if (capabilities.hasAgent || selectedMode !== "bot_long_connection") {
|
|
538
|
+
pushCheck("agent_selfcheck", "检查 Agent 配置", "混合来源还要单独确认 Agent 兼容字段没有漏迁。", "npm run wecom:agent:selfcheck -- --account default");
|
|
539
|
+
}
|
|
540
|
+
pushCheck("channel_selfcheck", "检查综合回包能力", "在继续落盘或上线前,先跑一次综合自检确认总体状态。", "npm run wecom:selfcheck -- --account default");
|
|
541
|
+
return dedupeCheckItems(checks);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (selectedMode === "agent_callback") {
|
|
545
|
+
pushCheck("agent_selfcheck", "检查 Agent 配置", "确认 callback、Agent API 和当前账号配置都完整。", "npm run wecom:agent:selfcheck -- --account default");
|
|
546
|
+
pushCheck("channel_selfcheck", "检查综合回包能力", "最后确认当前账号 readiness。", "npm run wecom:selfcheck -- --account default");
|
|
547
|
+
return dedupeCheckItems(checks);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (selectedMode === "hybrid") {
|
|
551
|
+
pushCheck("agent_selfcheck", "检查 Agent 配置", "先确认 Agent 主动发送和 callback 路径。", "npm run wecom:agent:selfcheck -- --account default");
|
|
552
|
+
pushCheck("bot_selfcheck", "检查 Bot 基础配置", "再确认 Bot 对话入口和 webhook/长连接结构。", "npm run wecom:bot:selfcheck -- --account default");
|
|
553
|
+
pushCheck("bot_longconn_probe", "探测 Bot 长连接", "如果启用了长连接,最后做一次 websocket 握手验证。", "npm run wecom:bot:longconn:probe -- --json");
|
|
554
|
+
pushCheck("channel_selfcheck", "检查综合回包能力", "最后确认双通道综合状态。", "npm run wecom:selfcheck -- --account default");
|
|
555
|
+
return dedupeCheckItems(checks);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
pushCheck("bot_selfcheck", "检查 Bot 基础配置", "确认 Bot 长连接必填项、webhook 配置和当前账号摘要。", "npm run wecom:bot:selfcheck -- --account default");
|
|
559
|
+
pushCheck("bot_longconn_probe", "探测 Bot 长连接", "在真正发消息前先验证 websocket 握手。", "npm run wecom:bot:longconn:probe -- --json");
|
|
560
|
+
return dedupeCheckItems(checks);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export function buildWecomSourceRepairDefaults({ source = "fresh" } = {}) {
|
|
564
|
+
if (source === "official-wecom") {
|
|
565
|
+
return { doctorFixMode: "auto", preserveNetworkCompatibility: false, removeLegacyFieldAliases: true, preferOfflineDoctor: true };
|
|
566
|
+
}
|
|
567
|
+
if (source === "sunnoy-wecom") {
|
|
568
|
+
return { doctorFixMode: "confirm", preserveNetworkCompatibility: true, removeLegacyFieldAliases: true, preferOfflineDoctor: false };
|
|
569
|
+
}
|
|
570
|
+
if (source === "legacy-openclaw-wechat") {
|
|
571
|
+
return { doctorFixMode: "auto", preserveNetworkCompatibility: true, removeLegacyFieldAliases: true, preferOfflineDoctor: true };
|
|
572
|
+
}
|
|
573
|
+
if (source === "mixed-source") {
|
|
574
|
+
return { doctorFixMode: "confirm", preserveNetworkCompatibility: true, removeLegacyFieldAliases: false, preferOfflineDoctor: true };
|
|
575
|
+
}
|
|
576
|
+
return { doctorFixMode: "off", preserveNetworkCompatibility: true, removeLegacyFieldAliases: false, preferOfflineDoctor: true };
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function buildWecomSourcePlaceholderHint({ source = "fresh", selectedMode = WECOM_QUICKSTART_RECOMMENDED_MODE } = {}) {
|
|
580
|
+
if (source === "official-wecom") {
|
|
581
|
+
return {
|
|
582
|
+
title: "替换官方来源占位项",
|
|
583
|
+
detail: "先确认官方插件风格的扁平 Bot 配置已经归一化,再补齐 starter config 里的真实 BotID / Secret。",
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
if (source === "sunnoy-wecom") {
|
|
587
|
+
return {
|
|
588
|
+
title: "替换 sunnoy 来源占位项",
|
|
589
|
+
detail: "先确认 `network.egressProxyUrl / apiBaseUrl` 等兼容字段保留正确,再补齐 starter config 中的真实凭据。",
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
if (source === "legacy-openclaw-wechat") {
|
|
593
|
+
return {
|
|
594
|
+
title: "替换 legacy 来源占位项",
|
|
595
|
+
detail: "先审阅 legacy agent/bot 兼容块,再把 starter config 里的 CorpId / Secret / Token / AES Key 替换成真实值。",
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
if (source === "mixed-source") {
|
|
599
|
+
return {
|
|
600
|
+
title: "替换 mixed-source 占位项",
|
|
601
|
+
detail: "当前配置混合了多种来源字段,建议先跑 migrate/doctor 审阅 patch,再决定是否一次性替换所有占位项。",
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
if (selectedMode === "agent_callback") {
|
|
605
|
+
return {
|
|
606
|
+
title: "替换 Agent 占位项",
|
|
607
|
+
detail: "优先补齐 CorpId / Secret / AgentId / callback Token / AES Key,再做公网回调验证。",
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
if (selectedMode === "hybrid") {
|
|
611
|
+
return {
|
|
612
|
+
title: "替换双通道占位项",
|
|
613
|
+
detail: "优先补齐 Agent callback 和 Bot 长连接两套凭据,避免只配一半导致 hybrid 自检误报。",
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
return {
|
|
617
|
+
title: "替换 Bot 占位项",
|
|
618
|
+
detail: "先补齐 Bot 长连接的 BotID / Secret,再继续运行 Bot 自检和长连接探针。",
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
export function buildWecomSourcePlaybook({
|
|
623
|
+
source = "fresh",
|
|
624
|
+
selectedMode = WECOM_QUICKSTART_RECOMMENDED_MODE,
|
|
625
|
+
config = {},
|
|
626
|
+
accountId = "default",
|
|
627
|
+
} = {}) {
|
|
628
|
+
const capabilities = detectWecomAccountCapabilities(config, accountId);
|
|
629
|
+
const checkOrder = buildWecomSourceCheckOrder({ source, capabilities, selectedMode });
|
|
630
|
+
const repairDefaults = buildWecomSourceRepairDefaults({ source });
|
|
631
|
+
const placeholderHint = buildWecomSourcePlaceholderHint({ source, selectedMode });
|
|
632
|
+
const notes = [];
|
|
633
|
+
if (source === "official-wecom") {
|
|
634
|
+
notes.push("当前来源更接近官方插件,优先保留 Bot 长连接入口和扁平 Bot 兼容字段的迁移语义。");
|
|
635
|
+
} else if (source === "sunnoy-wecom") {
|
|
636
|
+
notes.push("当前来源更接近 sunnoy 配置,优先保留网络兼容字段和双通道能力,再决定是否自动修复。");
|
|
637
|
+
} else if (source === "legacy-openclaw-wechat") {
|
|
638
|
+
notes.push("当前来源更接近旧版 OpenClaw-Wechat,建议先审阅 legacy agent/bot 兼容块。");
|
|
639
|
+
} else if (source === "mixed-source") {
|
|
640
|
+
notes.push("当前配置混合了多种来源字段,建议优先审阅 migration patch,而不是直接覆盖配置。");
|
|
641
|
+
}
|
|
642
|
+
if (repairDefaults.doctorFixMode === "confirm") {
|
|
643
|
+
notes.push("当前来源默认只给修复建议;若需要自动应用本地 patch,请显式确认 doctor --fix。");
|
|
644
|
+
} else if (repairDefaults.doctorFixMode === "auto") {
|
|
645
|
+
notes.push("当前来源默认允许自动附带 doctor --fix。");
|
|
646
|
+
}
|
|
647
|
+
return {
|
|
648
|
+
source,
|
|
649
|
+
capabilities,
|
|
650
|
+
checkOrder,
|
|
651
|
+
checkOrderSummary: checkOrder.map((item) => item.title).join(" -> "),
|
|
652
|
+
repairDefaults,
|
|
653
|
+
placeholderHint,
|
|
654
|
+
notes,
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function resolveQuickstartSelection({
|
|
659
|
+
mode = WECOM_QUICKSTART_RECOMMENDED_MODE,
|
|
660
|
+
groupProfile = WECOM_QUICKSTART_DEFAULT_GROUP_PROFILE,
|
|
661
|
+
} = {}) {
|
|
662
|
+
const normalizedMode = String(mode ?? "").trim().toLowerCase();
|
|
663
|
+
const normalizedGroupProfile =
|
|
664
|
+
String(groupProfile ?? "").trim().toLowerCase() || WECOM_QUICKSTART_DEFAULT_GROUP_PROFILE;
|
|
665
|
+
const modeDefinition =
|
|
666
|
+
QUICKSTART_MODE_DEFINITIONS[normalizedMode] ?? QUICKSTART_MODE_DEFINITIONS[WECOM_QUICKSTART_RECOMMENDED_MODE];
|
|
667
|
+
const groupProfileDefinition =
|
|
668
|
+
QUICKSTART_GROUP_PROFILE_DEFINITIONS[normalizedGroupProfile] ??
|
|
669
|
+
QUICKSTART_GROUP_PROFILE_DEFINITIONS[WECOM_QUICKSTART_DEFAULT_GROUP_PROFILE];
|
|
670
|
+
return {
|
|
671
|
+
modeDefinition,
|
|
672
|
+
groupProfileDefinition,
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function collectQuickstartPlaceholders(value, path = "", out = []) {
|
|
677
|
+
if (Array.isArray(value)) {
|
|
678
|
+
value.forEach((item, index) => {
|
|
679
|
+
collectQuickstartPlaceholders(item, `${path}[${index}]`, out);
|
|
680
|
+
});
|
|
681
|
+
return out;
|
|
682
|
+
}
|
|
683
|
+
if (!value || typeof value !== "object") {
|
|
684
|
+
const normalizedValue = typeof value === "string" ? String(value).trim() : value;
|
|
685
|
+
for (const rule of QUICKSTART_PLACEHOLDER_RULES) {
|
|
686
|
+
if (!rule.match({ path, value: normalizedValue })) continue;
|
|
687
|
+
out.push({
|
|
688
|
+
id: rule.id,
|
|
689
|
+
path,
|
|
690
|
+
label: rule.label,
|
|
691
|
+
category: rule.category,
|
|
692
|
+
currentValue: normalizedValue,
|
|
693
|
+
action: rule.action,
|
|
694
|
+
});
|
|
695
|
+
break;
|
|
696
|
+
}
|
|
697
|
+
return out;
|
|
698
|
+
}
|
|
699
|
+
for (const [key, child] of Object.entries(value)) {
|
|
700
|
+
const childPath = path ? `${path}.${key}` : key;
|
|
701
|
+
collectQuickstartPlaceholders(child, childPath, out);
|
|
702
|
+
}
|
|
703
|
+
return out;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function uniquePaths(values = []) {
|
|
707
|
+
return Array.from(
|
|
708
|
+
new Set(
|
|
709
|
+
values
|
|
710
|
+
.map((item) => String(item ?? "").trim())
|
|
711
|
+
.filter(Boolean),
|
|
712
|
+
),
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function buildModeFirstRunGoal(modeDefinition) {
|
|
717
|
+
if (modeDefinition?.id === "bot_long_connection") {
|
|
718
|
+
return "先把 Bot 收消息、回消息和长连接健康跑通。";
|
|
719
|
+
}
|
|
720
|
+
if (modeDefinition?.id === "agent_callback") {
|
|
721
|
+
return "先把自建应用 callback、主动发送和公网回调验证跑通。";
|
|
722
|
+
}
|
|
723
|
+
if (modeDefinition?.id === "hybrid") {
|
|
724
|
+
return "先同时打通 Bot 对话入口和 Agent 主动发送链路。";
|
|
725
|
+
}
|
|
726
|
+
return "先把 WeCom 最小可用收发链路跑通。";
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function buildModeRequiredAdminSteps(modeDefinition) {
|
|
730
|
+
if (modeDefinition?.id === "bot_long_connection") {
|
|
731
|
+
return [
|
|
732
|
+
"在企业微信 Bot 后台启用长连接并拿到 BotID / Secret。",
|
|
733
|
+
];
|
|
734
|
+
}
|
|
735
|
+
if (modeDefinition?.id === "agent_callback") {
|
|
736
|
+
return [
|
|
737
|
+
"在企业微信自建应用后台配置回调 URL、Token 和 EncodingAESKey。",
|
|
738
|
+
"为 callback 路径准备稳定公网入口并完成 URL 验证。",
|
|
739
|
+
];
|
|
740
|
+
}
|
|
741
|
+
if (modeDefinition?.id === "hybrid") {
|
|
742
|
+
return [
|
|
743
|
+
"在企业微信自建应用后台配置回调 URL、Token 和 EncodingAESKey。",
|
|
744
|
+
"在企业微信 Bot 后台启用长连接并拿到 BotID / Secret。",
|
|
745
|
+
];
|
|
746
|
+
}
|
|
747
|
+
return [];
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function buildModeSuccessChecks(modeDefinition) {
|
|
751
|
+
return (modeDefinition?.checks ?? []).map((command, index) => ({
|
|
752
|
+
id: `${modeDefinition?.id || "mode"}-check-${index + 1}`,
|
|
753
|
+
command,
|
|
754
|
+
summary: `检查 ${index + 1}: ${command}`,
|
|
755
|
+
}));
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function buildSetupChecklist({
|
|
759
|
+
modeDefinition,
|
|
760
|
+
groupProfileDefinition,
|
|
761
|
+
accountId = "default",
|
|
762
|
+
starterConfig,
|
|
763
|
+
placeholders = [],
|
|
764
|
+
groupChatId = "",
|
|
765
|
+
groupAllow = [],
|
|
766
|
+
sourcePlaybook = null,
|
|
767
|
+
} = {}) {
|
|
768
|
+
const checklist = [];
|
|
769
|
+
const warnings = [];
|
|
770
|
+
const normalizedAccountId = normalizeAccountId(accountId);
|
|
771
|
+
const accountConfigPath =
|
|
772
|
+
normalizedAccountId === "default" ? "channels.wecom" : `channels.wecom.accounts.${normalizedAccountId}`;
|
|
773
|
+
const targetGroupChatId = String(groupChatId ?? "").trim();
|
|
774
|
+
const normalizedGroupAllow = normalizeAllowList(groupAllow);
|
|
775
|
+
|
|
776
|
+
if (placeholders.length > 0) {
|
|
777
|
+
checklist.push({
|
|
778
|
+
id: "fill-placeholders",
|
|
779
|
+
kind: "edit",
|
|
780
|
+
title: sourcePlaybook?.placeholderHint?.title || "替换模板占位项",
|
|
781
|
+
detail:
|
|
782
|
+
sourcePlaybook?.placeholderHint?.detail ||
|
|
783
|
+
`当前 starter config 里还有 ${placeholders.length} 个待填字段。`,
|
|
784
|
+
paths: placeholders.map((item) => item.path),
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
if (modeDefinition?.requiresPublicWebhook) {
|
|
789
|
+
checklist.push({
|
|
790
|
+
id: "public-webhook",
|
|
791
|
+
kind: "wecom-admin",
|
|
792
|
+
title: "准备公网 callback",
|
|
793
|
+
detail: `为 ${accountConfigPath}.webhookPath 准备稳定公网地址,并在企业微信后台完成 URL 验证。`,
|
|
794
|
+
paths: [`${accountConfigPath}.webhookPath`],
|
|
795
|
+
});
|
|
796
|
+
} else {
|
|
797
|
+
checklist.push({
|
|
798
|
+
id: "no-public-webhook",
|
|
799
|
+
kind: "note",
|
|
800
|
+
title: "无需公网 callback",
|
|
801
|
+
detail: "当前模式优先走 Bot 长连接,可先不准备公网回调地址。",
|
|
802
|
+
paths: [],
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
if (groupProfileDefinition?.id === "allowlist_template") {
|
|
807
|
+
checklist.push({
|
|
808
|
+
id: "group-allowlist",
|
|
809
|
+
kind: "group-policy",
|
|
810
|
+
title: "确认群白名单模板",
|
|
811
|
+
detail: targetGroupChatId
|
|
812
|
+
? `已写入群 ${targetGroupChatId} 的 allowlist 模板,请确认成员列表和拒绝文案。`
|
|
813
|
+
: "请把模板里的 wr-your-chat-id 和示例成员替换成真实群配置。",
|
|
814
|
+
paths: [
|
|
815
|
+
`${accountConfigPath}.groupPolicy`,
|
|
816
|
+
`${accountConfigPath}.groupAllowFrom`,
|
|
817
|
+
`${accountConfigPath}.groups`,
|
|
818
|
+
],
|
|
819
|
+
});
|
|
820
|
+
if (!targetGroupChatId) {
|
|
821
|
+
warnings.push("群白名单模板仍使用默认 chatId,占位项需要替换。");
|
|
822
|
+
}
|
|
823
|
+
if (normalizedGroupAllow.length === 0) {
|
|
824
|
+
warnings.push("群白名单模板仍使用示例成员 ops_lead/oncall_user,请替换成真实成员。");
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
if (groupProfileDefinition?.id === "open_direct" && modeDefinition?.id === "bot_long_connection") {
|
|
829
|
+
warnings.push("企业微信 Bot 群回调通常仍只支持 @ 触发;open_direct 更适合自建应用回调模式。");
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const checkItems =
|
|
833
|
+
Array.isArray(sourcePlaybook?.checkOrder) && sourcePlaybook.checkOrder.length > 0
|
|
834
|
+
? sourcePlaybook.checkOrder
|
|
835
|
+
: (modeDefinition?.checks ?? []).map((command, index) => ({
|
|
836
|
+
id: `mode-check-${index + 1}`,
|
|
837
|
+
title: `运行体检 ${index + 1}`,
|
|
838
|
+
detail: command,
|
|
839
|
+
command,
|
|
840
|
+
}));
|
|
841
|
+
|
|
842
|
+
checkItems.forEach((item, index) => {
|
|
843
|
+
checklist.push({
|
|
844
|
+
id: `verify-${index + 1}`,
|
|
845
|
+
kind: "verify",
|
|
846
|
+
title: item.title || `运行体检 ${index + 1}`,
|
|
847
|
+
detail: item.detail || item.command || `检查 ${index + 1}`,
|
|
848
|
+
command: item.command,
|
|
849
|
+
paths: [],
|
|
850
|
+
});
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
return {
|
|
854
|
+
checklist,
|
|
855
|
+
warnings,
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function buildSetupActions({
|
|
860
|
+
modeDefinition,
|
|
861
|
+
groupProfileDefinition,
|
|
862
|
+
accountId = "default",
|
|
863
|
+
placeholders = [],
|
|
864
|
+
groupChatId = "",
|
|
865
|
+
commands = {},
|
|
866
|
+
diagnostics = null,
|
|
867
|
+
sourcePlaybook = null,
|
|
868
|
+
} = {}) {
|
|
869
|
+
const actions = [];
|
|
870
|
+
const normalizedAccountId = normalizeAccountId(accountId);
|
|
871
|
+
const accountConfigPath =
|
|
872
|
+
normalizedAccountId === "default" ? "channels.wecom" : `channels.wecom.accounts.${normalizedAccountId}`;
|
|
873
|
+
|
|
874
|
+
if (diagnostics?.installState === "stale_package") {
|
|
875
|
+
actions.push({
|
|
876
|
+
id: "upgrade-plugin-package",
|
|
877
|
+
kind: "upgrade_package",
|
|
878
|
+
title: "升级 npm 插件版本",
|
|
879
|
+
detail: diagnostics.installStateSummary,
|
|
880
|
+
paths: ["plugins.installs.openclaw-wechat.version"],
|
|
881
|
+
command: "npm install @dingxiang-me/openclaw-wechat@latest",
|
|
882
|
+
recommended: true,
|
|
883
|
+
blocking: false,
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
if (placeholders.length > 0) {
|
|
888
|
+
actions.push({
|
|
889
|
+
id: "fill-config-placeholders",
|
|
890
|
+
kind: "fill_config",
|
|
891
|
+
title: sourcePlaybook?.placeholderHint?.title || "替换 starter config 占位项",
|
|
892
|
+
detail:
|
|
893
|
+
sourcePlaybook?.placeholderHint?.detail ||
|
|
894
|
+
`当前有 ${placeholders.length} 个待填字段,先补齐真实 CorpId / Secret / Token / AES Key。`,
|
|
895
|
+
paths: uniquePaths(placeholders.map((item) => item.path)),
|
|
896
|
+
command: commands.preview,
|
|
897
|
+
recommended: true,
|
|
898
|
+
blocking: true,
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
if (sourcePlaybook?.source && !["fresh", "native-openclaw-wechat", "unknown"].includes(sourcePlaybook.source)) {
|
|
903
|
+
actions.push({
|
|
904
|
+
id: "review-source-playbook",
|
|
905
|
+
kind: "review_setup_profile",
|
|
906
|
+
title: "确认来源专属接入策略",
|
|
907
|
+
detail: sourcePlaybook.checkOrderSummary || uniqueStrings(sourcePlaybook.notes ?? []).join(" "),
|
|
908
|
+
paths: uniquePaths((diagnostics?.detectedLegacyFields ?? []).map((item) => item.path)),
|
|
909
|
+
command: commands.migrate,
|
|
910
|
+
recommended: true,
|
|
911
|
+
blocking: diagnostics?.migrationState === "mixed_layout",
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
if (modeDefinition?.requiresPublicWebhook) {
|
|
916
|
+
actions.push({
|
|
917
|
+
id: "configure-wecom-admin-callback",
|
|
918
|
+
kind: "open_wecom_admin",
|
|
919
|
+
title: "在企业微信后台配置回调",
|
|
920
|
+
detail: `为 ${accountConfigPath}.webhookPath 配置稳定公网地址,并完成 URL 验证。`,
|
|
921
|
+
paths: [`${accountConfigPath}.webhookPath`, `${accountConfigPath}.callbackToken`, `${accountConfigPath}.callbackAesKey`],
|
|
922
|
+
command: "",
|
|
923
|
+
recommended: true,
|
|
924
|
+
blocking: true,
|
|
925
|
+
});
|
|
926
|
+
} else {
|
|
927
|
+
actions.push({
|
|
928
|
+
id: "collect-bot-longconn-credentials",
|
|
929
|
+
kind: "open_wecom_admin",
|
|
930
|
+
title: "在企业微信后台拿到 Bot 长连接凭据",
|
|
931
|
+
detail: "确认机器人已开通长连接,并复制 BotID / Secret。",
|
|
932
|
+
paths: [`${accountConfigPath}.bot.longConnection.botId`, `${accountConfigPath}.bot.longConnection.secret`],
|
|
933
|
+
command: "",
|
|
934
|
+
recommended: true,
|
|
935
|
+
blocking: true,
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
if (groupProfileDefinition?.id === "allowlist_template") {
|
|
940
|
+
actions.push({
|
|
941
|
+
id: "review-group-policy-template",
|
|
942
|
+
kind: "fill_config",
|
|
943
|
+
title: "确认群白名单模板",
|
|
944
|
+
detail: groupChatId
|
|
945
|
+
? `确认群 ${groupChatId} 的 allowlist、触发方式和拒绝文案。`
|
|
946
|
+
: "替换模板里的示例群 ChatId 和成员白名单。",
|
|
947
|
+
paths: [
|
|
948
|
+
`${accountConfigPath}.groupPolicy`,
|
|
949
|
+
`${accountConfigPath}.groupAllowFrom`,
|
|
950
|
+
`${accountConfigPath}.groups`,
|
|
951
|
+
],
|
|
952
|
+
command: commands.preview,
|
|
953
|
+
recommended: true,
|
|
954
|
+
blocking: false,
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
actions.push({
|
|
959
|
+
id: "write-starter-config",
|
|
960
|
+
kind: "write_patch",
|
|
961
|
+
title: "写入 starter config",
|
|
962
|
+
detail: "把生成的 starter config 合并进 openclaw.json。",
|
|
963
|
+
paths: [accountConfigPath],
|
|
964
|
+
command: commands.write,
|
|
965
|
+
recommended: true,
|
|
966
|
+
blocking: false,
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
if (Array.isArray(diagnostics?.envTemplate?.lines) && diagnostics.envTemplate.lines.length > 0) {
|
|
970
|
+
actions.push({
|
|
971
|
+
id: "set-wecom-env-template",
|
|
972
|
+
kind: "set_env",
|
|
973
|
+
title: "整理 WeCom 环境变量模板",
|
|
974
|
+
detail: "将 Secret 和回调参数整理进 env.vars 或系统环境变量,减少 JSON 内联密钥。",
|
|
975
|
+
paths: [],
|
|
976
|
+
command: diagnostics.migrationCommand,
|
|
977
|
+
recommended: false,
|
|
978
|
+
blocking: false,
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
const checkItems =
|
|
983
|
+
Array.isArray(sourcePlaybook?.checkOrder) && sourcePlaybook.checkOrder.length > 0
|
|
984
|
+
? sourcePlaybook.checkOrder
|
|
985
|
+
: (modeDefinition?.checks ?? []).map((command, index) => ({
|
|
986
|
+
id: `mode-check-${index + 1}`,
|
|
987
|
+
title: `运行体检 ${index + 1}`,
|
|
988
|
+
detail: command,
|
|
989
|
+
command,
|
|
990
|
+
}));
|
|
991
|
+
|
|
992
|
+
checkItems.forEach((item, index) => {
|
|
993
|
+
actions.push({
|
|
994
|
+
id: `run-check-${index + 1}`,
|
|
995
|
+
kind: "run_check",
|
|
996
|
+
title: item.title || `运行体检 ${index + 1}`,
|
|
997
|
+
detail: item.detail || item.command || `检查 ${index + 1}`,
|
|
998
|
+
paths: [],
|
|
999
|
+
command: item.command,
|
|
1000
|
+
recommended: true,
|
|
1001
|
+
blocking: false,
|
|
1002
|
+
});
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
if (diagnostics?.migrationState === "legacy_config" || diagnostics?.migrationState === "mixed_layout") {
|
|
1006
|
+
const migrationTitle =
|
|
1007
|
+
diagnostics?.migrationSource === "official-wecom"
|
|
1008
|
+
? "迁移官方 WeCom 插件配置"
|
|
1009
|
+
: diagnostics?.migrationSource === "sunnoy-wecom"
|
|
1010
|
+
? "迁移 sunnoy WeCom 插件配置"
|
|
1011
|
+
: diagnostics?.migrationSource === "legacy-openclaw-wechat"
|
|
1012
|
+
? "迁移 legacy OpenClaw-Wechat 配置"
|
|
1013
|
+
: diagnostics?.migrationSource === "mixed-source"
|
|
1014
|
+
? "审阅 mixed-source WeCom 配置并迁移"
|
|
1015
|
+
: "迁移 legacy WeCom 配置";
|
|
1016
|
+
actions.push({
|
|
1017
|
+
id: "migrate-legacy-layout",
|
|
1018
|
+
kind: "apply_patch",
|
|
1019
|
+
title: migrationTitle,
|
|
1020
|
+
detail: [diagnostics?.migrationSourceSummary, diagnostics?.migrationStateSummary].filter(Boolean).join(" "),
|
|
1021
|
+
paths: uniquePaths((diagnostics.detectedLegacyFields ?? []).map((item) => item.path)),
|
|
1022
|
+
command: diagnostics.migrationCommand,
|
|
1023
|
+
recommended: true,
|
|
1024
|
+
blocking: diagnostics.migrationState === "mixed_layout",
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
actions.push({
|
|
1029
|
+
id: "apply-repair-patch",
|
|
1030
|
+
kind: "apply_patch",
|
|
1031
|
+
title: "检查失败后应用 repair patch",
|
|
1032
|
+
detail: "若 selfcheck 失败,可预览并应用自动生成的 repair patch。",
|
|
1033
|
+
paths: [accountConfigPath],
|
|
1034
|
+
command: commands.confirmRepair,
|
|
1035
|
+
recommended: false,
|
|
1036
|
+
blocking: false,
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
actions.push({
|
|
1040
|
+
id: "restart-openclaw-gateway",
|
|
1041
|
+
kind: "restart_gateway",
|
|
1042
|
+
title: "重启 OpenClaw gateway",
|
|
1043
|
+
detail: "写完配置或应用迁移 patch 后,重启 gateway 让新配置生效。",
|
|
1044
|
+
paths: [],
|
|
1045
|
+
command: "openclaw gateway restart",
|
|
1046
|
+
recommended: true,
|
|
1047
|
+
blocking: false,
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
return actions;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
export const WECOM_QUICKSTART_MODES = Object.freeze(
|
|
1054
|
+
Object.values(QUICKSTART_MODE_DEFINITIONS).map((mode) =>
|
|
1055
|
+
Object.freeze({
|
|
1056
|
+
id: mode.id,
|
|
1057
|
+
label: mode.label,
|
|
1058
|
+
recommended: mode.recommended,
|
|
1059
|
+
requiresPublicWebhook: mode.requiresPublicWebhook,
|
|
1060
|
+
supportsPairing: mode.supportsPairing,
|
|
1061
|
+
docsAnchor: mode.docsAnchor,
|
|
1062
|
+
summary: mode.summary,
|
|
1063
|
+
firstRunGoal: buildModeFirstRunGoal(mode),
|
|
1064
|
+
requiredAdminSteps: Object.freeze(buildModeRequiredAdminSteps(mode)),
|
|
1065
|
+
successChecks: Object.freeze(buildModeSuccessChecks(mode)),
|
|
1066
|
+
requiredConfigPaths: Object.freeze([...mode.requiredConfigPaths]),
|
|
1067
|
+
checks: Object.freeze([...mode.checks]),
|
|
1068
|
+
notes: Object.freeze([...mode.notes]),
|
|
1069
|
+
}),
|
|
1070
|
+
),
|
|
1071
|
+
);
|
|
1072
|
+
|
|
1073
|
+
export const WECOM_QUICKSTART_GROUP_PROFILES = Object.freeze(
|
|
1074
|
+
Object.values(QUICKSTART_GROUP_PROFILE_DEFINITIONS).map((profile) =>
|
|
1075
|
+
Object.freeze({
|
|
1076
|
+
id: profile.id,
|
|
1077
|
+
label: profile.label,
|
|
1078
|
+
summary: profile.summary,
|
|
1079
|
+
recommendedForModes: Object.freeze([...(profile.recommendedForModes ?? [])]),
|
|
1080
|
+
notes: Object.freeze([...(profile.notes ?? [])]),
|
|
1081
|
+
}),
|
|
1082
|
+
),
|
|
1083
|
+
);
|
|
1084
|
+
|
|
1085
|
+
export function listWecomQuickstartModes() {
|
|
1086
|
+
return WECOM_QUICKSTART_MODES.map((mode) => ({
|
|
1087
|
+
...mode,
|
|
1088
|
+
requiredAdminSteps: [...mode.requiredAdminSteps],
|
|
1089
|
+
successChecks: mode.successChecks.map((item) => ({ ...item })),
|
|
1090
|
+
requiredConfigPaths: [...mode.requiredConfigPaths],
|
|
1091
|
+
checks: [...mode.checks],
|
|
1092
|
+
notes: [...mode.notes],
|
|
1093
|
+
}));
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
export function listWecomQuickstartGroupProfiles() {
|
|
1097
|
+
return WECOM_QUICKSTART_GROUP_PROFILES.map((profile) => ({
|
|
1098
|
+
...profile,
|
|
1099
|
+
recommendedForModes: [...profile.recommendedForModes],
|
|
1100
|
+
notes: [...profile.notes],
|
|
1101
|
+
}));
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
export function buildWecomQuickstartConfig({
|
|
1105
|
+
mode = WECOM_QUICKSTART_RECOMMENDED_MODE,
|
|
1106
|
+
accountId = "default",
|
|
1107
|
+
dmMode = "pairing",
|
|
1108
|
+
groupProfile = WECOM_QUICKSTART_DEFAULT_GROUP_PROFILE,
|
|
1109
|
+
groupChatId = "",
|
|
1110
|
+
groupAllow = [],
|
|
1111
|
+
} = {}) {
|
|
1112
|
+
const { modeDefinition: definition, groupProfileDefinition } = resolveQuickstartSelection({
|
|
1113
|
+
mode,
|
|
1114
|
+
groupProfile,
|
|
1115
|
+
});
|
|
1116
|
+
const starterConfig = deepClone(definition.buildConfig({ accountId, dmMode }));
|
|
1117
|
+
const targetAccountConfig = resolveStarterAccountConfig(starterConfig, accountId);
|
|
1118
|
+
if (targetAccountConfig && groupProfileDefinition) {
|
|
1119
|
+
const patch = groupProfileDefinition.buildPatch({
|
|
1120
|
+
mode: definition.id,
|
|
1121
|
+
accountId,
|
|
1122
|
+
dmMode,
|
|
1123
|
+
groupChatId,
|
|
1124
|
+
groupAllow,
|
|
1125
|
+
});
|
|
1126
|
+
const merged = mergeDeep(targetAccountConfig, patch);
|
|
1127
|
+
for (const key of Object.keys(targetAccountConfig)) {
|
|
1128
|
+
delete targetAccountConfig[key];
|
|
1129
|
+
}
|
|
1130
|
+
Object.assign(targetAccountConfig, merged);
|
|
1131
|
+
}
|
|
1132
|
+
return starterConfig;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
export function buildWecomQuickstartSetupPlan({
|
|
1136
|
+
mode = WECOM_QUICKSTART_RECOMMENDED_MODE,
|
|
1137
|
+
accountId = "default",
|
|
1138
|
+
dmMode = "pairing",
|
|
1139
|
+
groupProfile = WECOM_QUICKSTART_DEFAULT_GROUP_PROFILE,
|
|
1140
|
+
groupChatId = "",
|
|
1141
|
+
groupAllow = [],
|
|
1142
|
+
currentConfig = {},
|
|
1143
|
+
} = {}) {
|
|
1144
|
+
const { modeDefinition, groupProfileDefinition } = resolveQuickstartSelection({
|
|
1145
|
+
mode,
|
|
1146
|
+
groupProfile,
|
|
1147
|
+
});
|
|
1148
|
+
const starterConfig = buildWecomQuickstartConfig({
|
|
1149
|
+
mode: modeDefinition.id,
|
|
1150
|
+
accountId,
|
|
1151
|
+
dmMode,
|
|
1152
|
+
groupProfile: groupProfileDefinition.id,
|
|
1153
|
+
groupChatId,
|
|
1154
|
+
groupAllow,
|
|
1155
|
+
});
|
|
1156
|
+
const placeholders = collectQuickstartPlaceholders(starterConfig);
|
|
1157
|
+
const diagnostics = collectWecomMigrationDiagnostics({
|
|
1158
|
+
config: asObject(currentConfig),
|
|
1159
|
+
accountId,
|
|
1160
|
+
});
|
|
1161
|
+
const sourcePlaybook = buildWecomSourcePlaybook({
|
|
1162
|
+
source: diagnostics.migrationSource,
|
|
1163
|
+
selectedMode: modeDefinition.id,
|
|
1164
|
+
config: mergeDeep(asObject(currentConfig), starterConfig),
|
|
1165
|
+
accountId,
|
|
1166
|
+
});
|
|
1167
|
+
const { checklist, warnings } = buildSetupChecklist({
|
|
1168
|
+
modeDefinition,
|
|
1169
|
+
groupProfileDefinition,
|
|
1170
|
+
accountId,
|
|
1171
|
+
starterConfig,
|
|
1172
|
+
placeholders,
|
|
1173
|
+
groupChatId,
|
|
1174
|
+
groupAllow,
|
|
1175
|
+
sourcePlaybook,
|
|
1176
|
+
});
|
|
1177
|
+
const commands = {
|
|
1178
|
+
preview: WECOM_QUICKSTART_SETUP_COMMAND,
|
|
1179
|
+
runChecks: WECOM_QUICKSTART_RUN_CHECKS_COMMAND,
|
|
1180
|
+
forceChecks: WECOM_QUICKSTART_FORCE_CHECKS_COMMAND,
|
|
1181
|
+
applyRepair: WECOM_QUICKSTART_APPLY_REPAIR_COMMAND,
|
|
1182
|
+
confirmRepair: WECOM_QUICKSTART_CONFIRM_REPAIR_COMMAND,
|
|
1183
|
+
migrate: WECOM_QUICKSTART_MIGRATION_COMMAND,
|
|
1184
|
+
wizard: WECOM_QUICKSTART_WIZARD_COMMAND,
|
|
1185
|
+
write: WECOM_QUICKSTART_WRITE_COMMAND,
|
|
1186
|
+
};
|
|
1187
|
+
const actions = buildSetupActions({
|
|
1188
|
+
modeDefinition,
|
|
1189
|
+
groupProfileDefinition,
|
|
1190
|
+
accountId,
|
|
1191
|
+
placeholders,
|
|
1192
|
+
groupChatId,
|
|
1193
|
+
commands,
|
|
1194
|
+
diagnostics,
|
|
1195
|
+
sourcePlaybook,
|
|
1196
|
+
});
|
|
1197
|
+
|
|
1198
|
+
if (sourcePlaybook?.repairDefaults?.doctorFixMode === "confirm") {
|
|
1199
|
+
warnings.push("当前来源默认只给修复建议;若需要自动应用本地 patch,请显式确认 doctor --fix / confirm-repair。");
|
|
1200
|
+
}
|
|
1201
|
+
if (sourcePlaybook?.source === "mixed-source") {
|
|
1202
|
+
warnings.push("当前配置混合了多种来源字段,建议优先审阅 migration patch,再决定是否直接覆盖 starter config。");
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
return {
|
|
1206
|
+
mode: {
|
|
1207
|
+
id: modeDefinition.id,
|
|
1208
|
+
label: modeDefinition.label,
|
|
1209
|
+
requiresPublicWebhook: modeDefinition.requiresPublicWebhook,
|
|
1210
|
+
summary: modeDefinition.summary,
|
|
1211
|
+
firstRunGoal: buildModeFirstRunGoal(modeDefinition),
|
|
1212
|
+
requiredAdminSteps: buildModeRequiredAdminSteps(modeDefinition),
|
|
1213
|
+
successChecks: buildModeSuccessChecks(modeDefinition),
|
|
1214
|
+
},
|
|
1215
|
+
groupProfile: {
|
|
1216
|
+
id: groupProfileDefinition.id,
|
|
1217
|
+
label: groupProfileDefinition.label,
|
|
1218
|
+
summary: groupProfileDefinition.summary,
|
|
1219
|
+
},
|
|
1220
|
+
accountId: normalizeAccountId(accountId),
|
|
1221
|
+
dmMode: String(dmMode ?? "pairing").trim().toLowerCase() || "pairing",
|
|
1222
|
+
installState: diagnostics.installState,
|
|
1223
|
+
installStateSummary: diagnostics.installStateSummary,
|
|
1224
|
+
migrationState: diagnostics.migrationState,
|
|
1225
|
+
migrationStateSummary: diagnostics.migrationStateSummary,
|
|
1226
|
+
migrationSource: diagnostics.migrationSource,
|
|
1227
|
+
migrationSourceSummary: diagnostics.migrationSourceSummary,
|
|
1228
|
+
migrationSourceSignals: diagnostics.migrationSourceSignals,
|
|
1229
|
+
detectedLegacyFields: diagnostics.detectedLegacyFields,
|
|
1230
|
+
migration: {
|
|
1231
|
+
source: diagnostics.migrationSource,
|
|
1232
|
+
sourceSummary: diagnostics.migrationSourceSummary,
|
|
1233
|
+
sourceSignals: diagnostics.migrationSourceSignals,
|
|
1234
|
+
command: diagnostics.migrationCommand,
|
|
1235
|
+
recommendedActions: diagnostics.recommendedActions,
|
|
1236
|
+
configPatch: diagnostics.configPatch,
|
|
1237
|
+
envTemplate: diagnostics.envTemplate,
|
|
1238
|
+
},
|
|
1239
|
+
sourcePlaybook,
|
|
1240
|
+
commands,
|
|
1241
|
+
actions,
|
|
1242
|
+
starterConfig,
|
|
1243
|
+
placeholders,
|
|
1244
|
+
checklist,
|
|
1245
|
+
warnings,
|
|
1246
|
+
};
|
|
1247
|
+
}
|