@openclaw-china/shared 2026.3.29 → 2026.4.23
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/package.json +1 -1
- package/src/cli/china-setup.ts +68 -59
- package/src/cli/install-hint.ts +16 -16
- package/src/cron/index.ts +3 -3
package/package.json
CHANGED
package/src/cli/china-setup.ts
CHANGED
|
@@ -51,7 +51,14 @@ type ConfigRoot = {
|
|
|
51
51
|
[key: string]: unknown;
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
-
export type ChannelId =
|
|
54
|
+
export type ChannelId =
|
|
55
|
+
| "dingtalk"
|
|
56
|
+
| "feishu-china"
|
|
57
|
+
| "wecom"
|
|
58
|
+
| "wecom-app"
|
|
59
|
+
| "wecom-kf"
|
|
60
|
+
| "qqbot-china"
|
|
61
|
+
| "wechat-mp";
|
|
55
62
|
|
|
56
63
|
export type RegisterChinaSetupCliOptions = {
|
|
57
64
|
channels?: readonly ChannelId[];
|
|
@@ -68,37 +75,38 @@ const GUIDES_BASE = "https://github.com/BytePioneer-AI/openclaw-china/tree/main/
|
|
|
68
75
|
const OPENCLAW_HOME = join(homedir(), ".openclaw");
|
|
69
76
|
const DEFAULT_PLUGIN_PATH = join(OPENCLAW_HOME, "extensions");
|
|
70
77
|
const LEGACY_PLUGIN_PATH = join(OPENCLAW_HOME, "plugins");
|
|
71
|
-
const CONFIG_FILE_PATH = join(OPENCLAW_HOME, "openclaw.json");
|
|
72
|
-
const ANSI_RESET = "\u001b[0m";
|
|
73
|
-
const ANSI_LINK = "\u001b[1;4;96m";
|
|
74
|
-
const ANSI_BORDER = "\u001b[92m";
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
"
|
|
78
|
-
|
|
79
|
-
"wecom
|
|
80
|
-
"wecom-
|
|
78
|
+
const CONFIG_FILE_PATH = join(OPENCLAW_HOME, "openclaw.json");
|
|
79
|
+
const ANSI_RESET = "\u001b[0m";
|
|
80
|
+
const ANSI_LINK = "\u001b[1;4;96m";
|
|
81
|
+
const ANSI_BORDER = "\u001b[92m";
|
|
82
|
+
const QQBOT_CHANNEL_ID = "qqbot-china" as const;
|
|
83
|
+
const CHANNEL_ORDER: readonly ChannelId[] = [
|
|
84
|
+
"dingtalk",
|
|
85
|
+
QQBOT_CHANNEL_ID,
|
|
86
|
+
"wecom",
|
|
87
|
+
"wecom-app",
|
|
88
|
+
"wecom-kf",
|
|
81
89
|
"wechat-mp",
|
|
82
90
|
"feishu-china",
|
|
83
91
|
];
|
|
84
92
|
const CHANNEL_DISPLAY_LABELS: Record<ChannelId, string> = {
|
|
85
93
|
dingtalk: "DingTalk(钉钉)",
|
|
86
|
-
"feishu-china": "Feishu(飞书)",
|
|
87
|
-
wecom: "WeCom(企业微信-智能机器人)",
|
|
88
|
-
"wecom-app": "WeCom App(自建应用-可接入微信)",
|
|
89
|
-
"wecom-kf": "WeCom KF(微信客服)",
|
|
90
|
-
"wechat-mp": "WeChat MP(微信公众号)",
|
|
91
|
-
qqbot: "QQBot(QQ 机器人)",
|
|
92
|
-
};
|
|
93
|
-
const CHANNEL_GUIDE_LINKS: Record<ChannelId, string> = {
|
|
94
|
+
"feishu-china": "Feishu(飞书)",
|
|
95
|
+
wecom: "WeCom(企业微信-智能机器人)",
|
|
96
|
+
"wecom-app": "WeCom App(自建应用-可接入微信)",
|
|
97
|
+
"wecom-kf": "WeCom KF(微信客服)",
|
|
98
|
+
"wechat-mp": "WeChat MP(微信公众号)",
|
|
99
|
+
"qqbot-china": "QQBot(QQ 机器人)",
|
|
100
|
+
};
|
|
101
|
+
const CHANNEL_GUIDE_LINKS: Record<ChannelId, string> = {
|
|
94
102
|
dingtalk: `${GUIDES_BASE}/dingtalk/configuration.md`,
|
|
95
103
|
"feishu-china": "https://github.com/BytePioneer-AI/openclaw-china/blob/main/README.md",
|
|
96
104
|
wecom: `${GUIDES_BASE}/wecom/configuration.md`,
|
|
97
|
-
"wecom-app": `${GUIDES_BASE}/wecom-app/configuration.md`,
|
|
98
|
-
"wecom-kf": "https://github.com/BytePioneer-AI/openclaw-china/blob/main/extensions/wecom-kf/README.md",
|
|
99
|
-
"wechat-mp": `${GUIDES_BASE}/wechat-mp/configuration.md`,
|
|
100
|
-
qqbot: `${GUIDES_BASE}/qqbot/configuration.md`,
|
|
101
|
-
};
|
|
105
|
+
"wecom-app": `${GUIDES_BASE}/wecom-app/configuration.md`,
|
|
106
|
+
"wecom-kf": "https://github.com/BytePioneer-AI/openclaw-china/blob/main/extensions/wecom-kf/README.md",
|
|
107
|
+
"wechat-mp": `${GUIDES_BASE}/wechat-mp/configuration.md`,
|
|
108
|
+
"qqbot-china": `${GUIDES_BASE}/qqbot/configuration.md`,
|
|
109
|
+
};
|
|
102
110
|
const CHINA_CLI_STATE_KEY = Symbol.for("@openclaw-china/china-cli-state");
|
|
103
111
|
|
|
104
112
|
type ChinaCliState = {
|
|
@@ -272,11 +280,11 @@ function cloneConfig(cfg: ConfigRoot): ConfigRoot {
|
|
|
272
280
|
}
|
|
273
281
|
}
|
|
274
282
|
|
|
275
|
-
function getChannelConfig(cfg: ConfigRoot, channelId: ChannelId): ConfigRecord {
|
|
276
|
-
const channels = isRecord(cfg.channels) ? cfg.channels : {};
|
|
277
|
-
const existing = channels[channelId];
|
|
278
|
-
return isRecord(existing) ? existing : {};
|
|
279
|
-
}
|
|
283
|
+
function getChannelConfig(cfg: ConfigRoot, channelId: ChannelId): ConfigRecord {
|
|
284
|
+
const channels = isRecord(cfg.channels) ? cfg.channels : {};
|
|
285
|
+
const existing = channels[channelId];
|
|
286
|
+
return isRecord(existing) ? existing : {};
|
|
287
|
+
}
|
|
280
288
|
|
|
281
289
|
function getGatewayAuthToken(cfg: ConfigRoot): string | undefined {
|
|
282
290
|
if (!isRecord(cfg.gateway)) {
|
|
@@ -328,15 +336,15 @@ function hasWecomWsCredentialPair(channelCfg: ConfigRecord): boolean {
|
|
|
328
336
|
return hasCredentialPair(channelCfg, "botId", "secret");
|
|
329
337
|
}
|
|
330
338
|
|
|
331
|
-
function isChannelConfigured(cfg: ConfigRoot, channelId: ChannelId): boolean {
|
|
332
|
-
const channelCfg = getChannelConfig(cfg, channelId);
|
|
333
|
-
switch (channelId) {
|
|
339
|
+
function isChannelConfigured(cfg: ConfigRoot, channelId: ChannelId): boolean {
|
|
340
|
+
const channelCfg = getChannelConfig(cfg, channelId);
|
|
341
|
+
switch (channelId) {
|
|
334
342
|
case "dingtalk":
|
|
335
343
|
return hasNonEmptyString(channelCfg.clientId) && hasNonEmptyString(channelCfg.clientSecret);
|
|
336
344
|
case "feishu-china":
|
|
337
345
|
return hasNonEmptyString(channelCfg.appId) && hasNonEmptyString(channelCfg.appSecret);
|
|
338
|
-
case "qqbot":
|
|
339
|
-
return hasNonEmptyString(channelCfg.appId) && hasNonEmptyString(channelCfg.clientSecret);
|
|
346
|
+
case "qqbot-china":
|
|
347
|
+
return hasNonEmptyString(channelCfg.appId) && hasNonEmptyString(channelCfg.clientSecret);
|
|
340
348
|
case "wecom":
|
|
341
349
|
return hasWecomWsCredentialPair(channelCfg);
|
|
342
350
|
case "wecom-app":
|
|
@@ -359,21 +367,22 @@ function withConfiguredSuffix(cfg: ConfigRoot, channelId: ChannelId): string {
|
|
|
359
367
|
return isChannelConfigured(cfg, channelId) ? `${base}(已配置)` : base;
|
|
360
368
|
}
|
|
361
369
|
|
|
362
|
-
function mergeChannelConfig(
|
|
363
|
-
cfg: ConfigRoot,
|
|
364
|
-
channelId: ChannelId,
|
|
365
|
-
patch: ConfigRecord
|
|
366
|
-
): ConfigRoot {
|
|
367
|
-
const channels = isRecord(cfg.channels) ? { ...cfg.channels } : {};
|
|
368
|
-
const existing = getChannelConfig(cfg, channelId);
|
|
369
|
-
|
|
370
|
-
...existing,
|
|
371
|
-
...patch,
|
|
372
|
-
enabled: true,
|
|
373
|
-
};
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
370
|
+
function mergeChannelConfig(
|
|
371
|
+
cfg: ConfigRoot,
|
|
372
|
+
channelId: ChannelId,
|
|
373
|
+
patch: ConfigRecord
|
|
374
|
+
): ConfigRoot {
|
|
375
|
+
const channels = isRecord(cfg.channels) ? { ...cfg.channels } : {};
|
|
376
|
+
const existing = getChannelConfig(cfg, channelId);
|
|
377
|
+
const nextChannelConfig = {
|
|
378
|
+
...existing,
|
|
379
|
+
...patch,
|
|
380
|
+
enabled: true,
|
|
381
|
+
};
|
|
382
|
+
channels[channelId] = nextChannelConfig;
|
|
383
|
+
return {
|
|
384
|
+
...cfg,
|
|
385
|
+
channels,
|
|
377
386
|
};
|
|
378
387
|
}
|
|
379
388
|
|
|
@@ -795,10 +804,10 @@ async function configureWechatMp(prompter: SetupPrompter, cfg: ConfigRoot): Prom
|
|
|
795
804
|
});
|
|
796
805
|
}
|
|
797
806
|
|
|
798
|
-
async function configureQQBot(prompter: SetupPrompter, cfg: ConfigRoot): Promise<ConfigRoot> {
|
|
799
|
-
section("配置 QQBot(QQ 机器人)");
|
|
800
|
-
showGuideLink(
|
|
801
|
-
const existing = getChannelConfig(cfg,
|
|
807
|
+
async function configureQQBot(prompter: SetupPrompter, cfg: ConfigRoot): Promise<ConfigRoot> {
|
|
808
|
+
section("配置 QQBot(QQ 机器人)");
|
|
809
|
+
showGuideLink(QQBOT_CHANNEL_ID);
|
|
810
|
+
const existing = getChannelConfig(cfg, QQBOT_CHANNEL_ID);
|
|
802
811
|
const existingAsr = isRecord(existing.asr) ? existing.asr : {};
|
|
803
812
|
|
|
804
813
|
const appId = await prompter.askText({
|
|
@@ -838,10 +847,10 @@ async function configureQQBot(prompter: SetupPrompter, cfg: ConfigRoot): Promise
|
|
|
838
847
|
});
|
|
839
848
|
}
|
|
840
849
|
|
|
841
|
-
return mergeChannelConfig(cfg,
|
|
842
|
-
appId,
|
|
843
|
-
clientSecret,
|
|
844
|
-
asr,
|
|
850
|
+
return mergeChannelConfig(cfg, QQBOT_CHANNEL_ID, {
|
|
851
|
+
appId,
|
|
852
|
+
clientSecret,
|
|
853
|
+
asr,
|
|
845
854
|
});
|
|
846
855
|
}
|
|
847
856
|
|
|
@@ -863,8 +872,8 @@ async function configureSingleChannel(
|
|
|
863
872
|
return configureWecomKf(prompter, cfg);
|
|
864
873
|
case "wechat-mp":
|
|
865
874
|
return configureWechatMp(prompter, cfg);
|
|
866
|
-
case "qqbot":
|
|
867
|
-
return configureQQBot(prompter, cfg);
|
|
875
|
+
case "qqbot-china":
|
|
876
|
+
return configureQQBot(prompter, cfg);
|
|
868
877
|
default:
|
|
869
878
|
return cfg;
|
|
870
879
|
}
|
package/src/cli/install-hint.ts
CHANGED
|
@@ -18,16 +18,16 @@ const ANSI_RESET = "\u001b[0m";
|
|
|
18
18
|
const ANSI_BOLD = "\u001b[1m";
|
|
19
19
|
const ANSI_LINK = "\u001b[1;4;96m";
|
|
20
20
|
const ANSI_BORDER = "\u001b[92m";
|
|
21
|
-
const SUPPORTED_CHANNELS: readonly ChannelId[] = [
|
|
22
|
-
"dingtalk",
|
|
23
|
-
"feishu-china",
|
|
24
|
-
"wecom",
|
|
25
|
-
"wecom-app",
|
|
26
|
-
"wecom-kf",
|
|
27
|
-
"wechat-mp",
|
|
28
|
-
"qqbot",
|
|
29
|
-
];
|
|
30
|
-
const CHINA_INSTALL_HINT_SHOWN_KEY = Symbol.for("@openclaw-china/china-install-hint-shown");
|
|
21
|
+
const SUPPORTED_CHANNELS: readonly ChannelId[] = [
|
|
22
|
+
"dingtalk",
|
|
23
|
+
"feishu-china",
|
|
24
|
+
"wecom",
|
|
25
|
+
"wecom-app",
|
|
26
|
+
"wecom-kf",
|
|
27
|
+
"wechat-mp",
|
|
28
|
+
"qqbot-china",
|
|
29
|
+
];
|
|
30
|
+
const CHINA_INSTALL_HINT_SHOWN_KEY = Symbol.for("@openclaw-china/china-install-hint-shown");
|
|
31
31
|
|
|
32
32
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
33
33
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -42,12 +42,12 @@ function hasAnyEnabledChinaChannel(config: unknown): boolean {
|
|
|
42
42
|
if (!isRecord(channels)) {
|
|
43
43
|
return false;
|
|
44
44
|
}
|
|
45
|
-
|
|
46
|
-
return SUPPORTED_CHANNELS.some((channelId) => {
|
|
47
|
-
const channelConfig = channels[channelId];
|
|
48
|
-
return isRecord(channelConfig) && channelConfig.enabled === true;
|
|
49
|
-
});
|
|
50
|
-
}
|
|
45
|
+
|
|
46
|
+
return SUPPORTED_CHANNELS.some((channelId) => {
|
|
47
|
+
const channelConfig = channels[channelId];
|
|
48
|
+
return isRecord(channelConfig) && channelConfig.enabled === true;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
51
|
|
|
52
52
|
function hasShownInstallHint(): boolean {
|
|
53
53
|
const root = globalThis as Record<PropertyKey, unknown>;
|
package/src/cron/index.ts
CHANGED
|
@@ -13,9 +13,9 @@ When creating or updating a cron task, always store a fixed delivery target in t
|
|
|
13
13
|
- Do not include tool directives, "NO_REPLY", or heartbeat markers in payload.message.
|
|
14
14
|
- Job name is never a message target.
|
|
15
15
|
- During cron run, must return plain text only and never call the message tool.
|
|
16
|
-
- Use top-level delivery with announce mode:
|
|
17
|
-
delivery.mode="announce"
|
|
18
|
-
delivery.channel=<OriginatingChannel> (example: "qqbot")
|
|
16
|
+
- Use top-level delivery with announce mode:
|
|
17
|
+
delivery.mode="announce"
|
|
18
|
+
delivery.channel=<OriginatingChannel> (example: "qqbot")
|
|
19
19
|
delivery.to=<OriginatingTo> (examples: "user:<openid>" / "group:<group_openid>")
|
|
20
20
|
delivery.accountId=<AccountId> when available
|
|
21
21
|
- Never set delivery.channel="last" for multi-channel environments.
|