@yanhaidao/wecom 2.3.9 → 2.3.10
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 +25 -12
- package/changelog/v2.3.10.md +17 -0
- package/compat-single-account.md +2 -2
- package/package.json +1 -1
- package/src/config/accounts.resolve.test.ts +39 -2
- package/src/config/accounts.ts +29 -7
- package/src/config/schema.ts +6 -1
- package/src/onboarding.test.ts +52 -3
- package/src/onboarding.ts +14 -14
- package/src/outbound.test.ts +1 -1
- package/src/transport/bot-ws/inbound.ts +0 -1
- package/src/transport/bot-ws/reply.ts +20 -5
- package/src/types/config.ts +6 -1
package/README.md
CHANGED
|
@@ -30,8 +30,10 @@
|
|
|
30
30
|
## 💡 核心价值:为什么选择本插件?
|
|
31
31
|
|
|
32
32
|
### 🎉 重大特性一览
|
|
33
|
-
1.
|
|
34
|
-
2.
|
|
33
|
+
1. **防断连黑科技** (v2.3.10 新增):针对 DeepSeek R1 等长时间 <think> 的推理模型,首创 **4秒前置保活 ACK** 机制。彻底断绝模型限速或慢思考导致的 WebSocket 5秒超时重试风暴与消息卡死现象。
|
|
34
|
+
2. **无需域名,极低门槛**:全面支持基于 WebSocket 的长连接(Bot WS)模式接入企业微信机器人,**彻底打通无公网 IP、无备案域名的内网服务器**与企微的实时对话桥梁!
|
|
35
|
+
3. **主动发消息,能力全覆盖**:基于 Agent 模式,全面支持**主动触达**,轻松实现早报定时任务、服务器异常报警、自动每日总结。
|
|
36
|
+
4. **向导自动路由自动适配** (v2.3.10 新增):在终端执行 `openclaw channels add` 时,若是单企微账号接入,将**静默触发自动 Agent 路由绑定**,丝滑跳过全局冗杂的路由分配步骤。
|
|
35
37
|
|
|
36
38
|
### 🔧 全新统一运行时架构 (Unified Runtime)
|
|
37
39
|
插件现已采用全新解耦架构:
|
|
@@ -154,7 +156,7 @@ openclaw channels add
|
|
|
154
156
|
},
|
|
155
157
|
"agent": {
|
|
156
158
|
"corpId": "CORP_ID",
|
|
157
|
-
"
|
|
159
|
+
"agentSecret": "AGENT_SECRET",
|
|
158
160
|
"agentId": 1000001,
|
|
159
161
|
"token": "AGENT_TOKEN",
|
|
160
162
|
"encodingAESKey": "AGENT_AES_KEY",
|
|
@@ -189,6 +191,10 @@ openclaw channels add
|
|
|
189
191
|
}
|
|
190
192
|
```
|
|
191
193
|
|
|
194
|
+
说明:
|
|
195
|
+
- 新配置推荐使用 `agent.agentSecret`
|
|
196
|
+
- 历史配置里的 `agent.corpSecret` 仍兼容读取,但后续文档统一使用 `agentSecret`
|
|
197
|
+
|
|
192
198
|
### 1.3 高级网络配置(公网出口代理)
|
|
193
199
|
如果您的服务器使用 **动态 IP** (如家庭宽带、内网穿透) 或 **无公网 IP**,首先使用Bot模式的ws接入方式。
|
|
194
200
|
|
|
@@ -422,18 +428,25 @@ openclaw channels status --deep
|
|
|
422
428
|
|
|
423
429
|
## 七、📮 联系我 与 版本协议
|
|
424
430
|
|
|
425
|
-
###
|
|
431
|
+
### 最近更新
|
|
432
|
+
|
|
433
|
+
近期保持高频迭代,最近版本如下:
|
|
434
|
+
|
|
435
|
+
#### v2.3.10(2026-03-10)
|
|
436
|
+
|
|
437
|
+
- onboarding 默认收敛为 `Bot + WS + 开放私聊`。
|
|
438
|
+
- 修复 `Bot WS` 长文本双重回复问题。
|
|
439
|
+
- 修复首个自定义接入标识时报 `default not found`。
|
|
440
|
+
- Agent 新配置统一使用 `agentSecret`。
|
|
426
441
|
|
|
427
|
-
|
|
442
|
+
#### v2.3.9(2026-03-09)
|
|
428
443
|
|
|
429
|
-
-
|
|
430
|
-
-
|
|
431
|
-
-
|
|
432
|
-
-
|
|
433
|
-
- **Agent 排障日志增强**:补充原始 callback、解密摘要、发送请求与响应日志,更容易定位“能接收但不能回复”的问题。
|
|
434
|
-
- **配置收敛**:移除 `bot.enabled` / `agent.enabled` 冗余字段,减少误导。
|
|
444
|
+
- Bot 默认接入改为 `WebSocket`,无需域名更易上手。
|
|
445
|
+
- 完善中文 onboarding,减少重复提示。
|
|
446
|
+
- 恢复 `Bot WS` 流式输出能力。
|
|
447
|
+
- 增强 Agent 回调与发送日志,排障更直接。
|
|
435
448
|
|
|
436
|
-
详细版本记录见 `changelog/v2.3.9.md`。
|
|
449
|
+
详细版本记录见 `changelog/v2.3.10.md` 与 `changelog/v2.3.9.md`。
|
|
437
450
|
|
|
438
451
|
微信交流群(扫码入群):
|
|
439
452
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# OpenClaw WeCom 插件 v2.3.10 变更简报
|
|
2
|
+
|
|
3
|
+
> [!TIP]
|
|
4
|
+
> **默认更易用、修复更直接的版本。**
|
|
5
|
+
|
|
6
|
+
## 2026-03-10(v2.3.10)
|
|
7
|
+
- 【消息防丢修复】🐛 **[重要修复]** 针对由于模型 API 限速或超长思考(如 DeepSeek R1)导致的企微 WebSocket 5秒超时断连问题,引入了 4 秒前置保活机制(自动下发"⏳ 正在思考中..."),彻底阻断了因为模型响应慢而造成的“消息卡死不再回复”。
|
|
8
|
+
- 【双重回复修复】🐛 **[重要修复]** 修复 `Bot WS` 长文本场景下可能被超时截断并触发兜底通道进行二次重复回复的边界异常。
|
|
9
|
+
- 【向导自动路由】✨ **[体验升级]** 重构了企业微信的渠道交互配置向导。在单账号场景下将静默触发自动路由绑定,丝滑跳过 OpenClaw 全局冗长的 Agent 路由分配询问。
|
|
10
|
+
- 【账号兜底修复】🧩 修复企业微信 onboarding 在首个账号非字面量 `default` 时,后续流程报 `WeCom account "default" not found` 的问题。
|
|
11
|
+
- 【默认选项收敛】🚀 onboarding 的默认回车选项已变更为更普适的 `Bot` 模式、`WS` 接入和 `开放模式` 策略。
|
|
12
|
+
- 【字段命名收敛】📝 Agent 新配置统一推荐使用 `agentSecret`,历史 `corpSecret` 保持兼容读取,保障平滑升级。
|
|
13
|
+
- 【文档与提示精简】📘 README、向导交互文案与示例结构已全面统一为更符合直觉的精简说明。
|
|
14
|
+
|
|
15
|
+
## 验证结果
|
|
16
|
+
- `bunx vitest run extensions/wecom/src/onboarding.test.ts extensions/wecom/src/channel.meta.test.ts`
|
|
17
|
+
- `pnpm build`
|
package/compat-single-account.md
CHANGED
|
@@ -31,7 +31,7 @@ openclaw config set channels.wecom.bot.dm.allowFrom '["*"]'
|
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
33
|
openclaw config set channels.wecom.agent.corpId "YOUR_CORP_ID"
|
|
34
|
-
openclaw config set channels.wecom.agent.
|
|
34
|
+
openclaw config set channels.wecom.agent.agentSecret "YOUR_AGENT_SECRET"
|
|
35
35
|
openclaw config set channels.wecom.agent.agentId 1000001
|
|
36
36
|
openclaw config set channels.wecom.agent.token "YOUR_CALLBACK_TOKEN"
|
|
37
37
|
openclaw config set channels.wecom.agent.encodingAESKey "YOUR_CALLBACK_AES_KEY"
|
|
@@ -71,7 +71,7 @@ openclaw channels status
|
|
|
71
71
|
|
|
72
72
|
"agent": {
|
|
73
73
|
"corpId": "YOUR_CORP_ID",
|
|
74
|
-
"
|
|
74
|
+
"agentSecret": "YOUR_AGENT_SECRET",
|
|
75
75
|
"agentId": 1000001,
|
|
76
76
|
"token": "YOUR_CALLBACK_TOKEN",
|
|
77
77
|
"encodingAESKey": "YOUR_CALLBACK_AES_KEY",
|
package/package.json
CHANGED
|
@@ -13,8 +13,11 @@ describe("resolveWecomAccount", () => {
|
|
|
13
13
|
"acct-a": {
|
|
14
14
|
enabled: true,
|
|
15
15
|
bot: {
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
primaryTransport: "webhook",
|
|
17
|
+
webhook: {
|
|
18
|
+
token: "token-a",
|
|
19
|
+
encodingAESKey: "aes-a",
|
|
20
|
+
},
|
|
18
21
|
},
|
|
19
22
|
},
|
|
20
23
|
},
|
|
@@ -35,4 +38,38 @@ describe("resolveWecomAccount", () => {
|
|
|
35
38
|
expect(account.enabled).toBe(true);
|
|
36
39
|
expect(account.configured).toBe(true);
|
|
37
40
|
});
|
|
41
|
+
|
|
42
|
+
it("treats literal default as an alias for configured default account", () => {
|
|
43
|
+
const account = resolveWecomAccount({ cfg, accountId: "default" });
|
|
44
|
+
expect(account.accountId).toBe("acct-a");
|
|
45
|
+
expect(account.enabled).toBe(true);
|
|
46
|
+
expect(account.configured).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("accepts agentSecret for fresh configs and normalizes it for runtime use", () => {
|
|
50
|
+
const agentCfg: OpenClawConfig = {
|
|
51
|
+
channels: {
|
|
52
|
+
wecom: {
|
|
53
|
+
enabled: true,
|
|
54
|
+
defaultAccount: "acct-agent",
|
|
55
|
+
accounts: {
|
|
56
|
+
"acct-agent": {
|
|
57
|
+
enabled: true,
|
|
58
|
+
agent: {
|
|
59
|
+
corpId: "corp-id",
|
|
60
|
+
agentSecret: "agent-secret",
|
|
61
|
+
agentId: 1000001,
|
|
62
|
+
token: "token",
|
|
63
|
+
encodingAESKey: "1234567890123456789012345678901234567890123",
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
} as OpenClawConfig;
|
|
70
|
+
|
|
71
|
+
const account = resolveWecomAccount({ cfg: agentCfg });
|
|
72
|
+
expect(account.agent?.apiConfigured).toBe(true);
|
|
73
|
+
expect(account.agent?.corpSecret).toBe("agent-secret");
|
|
74
|
+
});
|
|
38
75
|
});
|
package/src/config/accounts.ts
CHANGED
|
@@ -73,14 +73,15 @@ function resolveAgentAccount(
|
|
|
73
73
|
): ResolvedAgentAccount {
|
|
74
74
|
const agentId = toNumber(config.agentId);
|
|
75
75
|
const callbackConfigured = Boolean(config.token && config.encodingAESKey);
|
|
76
|
-
const
|
|
76
|
+
const normalizedAgentSecret = config.agentSecret?.trim() || config.corpSecret?.trim() || "";
|
|
77
|
+
const apiConfigured = Boolean(config.corpId && normalizedAgentSecret && agentId);
|
|
77
78
|
return {
|
|
78
79
|
accountId,
|
|
79
80
|
configured: callbackConfigured || apiConfigured,
|
|
80
81
|
callbackConfigured,
|
|
81
82
|
apiConfigured,
|
|
82
83
|
corpId: config.corpId,
|
|
83
|
-
corpSecret:
|
|
84
|
+
corpSecret: normalizedAgentSecret,
|
|
84
85
|
agentId,
|
|
85
86
|
token: config.token,
|
|
86
87
|
encodingAESKey: config.encodingAESKey,
|
|
@@ -113,6 +114,15 @@ function toResolvedAccount(params: {
|
|
|
113
114
|
};
|
|
114
115
|
}
|
|
115
116
|
|
|
117
|
+
function createMissingResolvedAccount(accountId: string): ResolvedWecomAccount {
|
|
118
|
+
return {
|
|
119
|
+
accountId,
|
|
120
|
+
enabled: false,
|
|
121
|
+
configured: false,
|
|
122
|
+
config: {},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
116
126
|
export function detectMode(config: WecomConfig | undefined): ResolvedMode {
|
|
117
127
|
if (!config || config.enabled === false) return "disabled";
|
|
118
128
|
if (config.accounts && Object.keys(config.accounts).length > 0) {
|
|
@@ -260,12 +270,24 @@ export function resolveWecomAccount(params: {
|
|
|
260
270
|
accountId?: string | null;
|
|
261
271
|
}): ResolvedWecomAccount {
|
|
262
272
|
const resolved = resolveWecomAccounts(params.cfg);
|
|
263
|
-
const
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
273
|
+
const explicitAccountId = params.accountId?.trim();
|
|
274
|
+
const accountId = explicitAccountId || resolved.defaultAccountId;
|
|
275
|
+
const direct = resolved.accounts[accountId];
|
|
276
|
+
if (direct) {
|
|
277
|
+
return direct;
|
|
267
278
|
}
|
|
268
|
-
|
|
279
|
+
|
|
280
|
+
// Treat the literal "default" as an alias for the configured default account.
|
|
281
|
+
// This keeps generic onboarding flows working even when the first WeCom account
|
|
282
|
+
// was created under a custom id like "haidao" instead of a literal "default".
|
|
283
|
+
if (explicitAccountId === DEFAULT_ACCOUNT_ID) {
|
|
284
|
+
const fallback = resolved.accounts[resolved.defaultAccountId];
|
|
285
|
+
if (fallback) {
|
|
286
|
+
return fallback;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return createMissingResolvedAccount(accountId);
|
|
269
291
|
}
|
|
270
292
|
|
|
271
293
|
export function isWecomEnabled(cfg: OpenClawConfig): boolean {
|
package/src/config/schema.ts
CHANGED
|
@@ -67,13 +67,18 @@ const botSchema = z
|
|
|
67
67
|
const agentSchema = z
|
|
68
68
|
.object({
|
|
69
69
|
corpId: z.string(),
|
|
70
|
-
|
|
70
|
+
agentSecret: z.string().optional(),
|
|
71
|
+
corpSecret: z.string().optional(),
|
|
71
72
|
agentId: z.union([z.number(), z.string()]).optional(),
|
|
72
73
|
token: z.string(),
|
|
73
74
|
encodingAESKey: z.string(),
|
|
74
75
|
welcomeText: z.string().optional(),
|
|
75
76
|
dm: dmSchema,
|
|
76
77
|
})
|
|
78
|
+
.refine((value) => Boolean(value.agentSecret?.trim() || value.corpSecret?.trim()), {
|
|
79
|
+
path: ["agentSecret"],
|
|
80
|
+
message: "agentSecret 不能为空",
|
|
81
|
+
})
|
|
77
82
|
.optional();
|
|
78
83
|
|
|
79
84
|
const dynamicAgentsSchema = z
|
package/src/onboarding.test.ts
CHANGED
|
@@ -168,7 +168,7 @@ describe("wecom onboarding", () => {
|
|
|
168
168
|
it("uses plugin-owned chinese account selection and no generic dm adapter", async () => {
|
|
169
169
|
const prompter = createPrompter({
|
|
170
170
|
select: vi.fn(async ({ message }: { message: string }) => {
|
|
171
|
-
if (message === "
|
|
171
|
+
if (message === "请选择企业微信接入标识(英文):") {
|
|
172
172
|
return "__new__";
|
|
173
173
|
}
|
|
174
174
|
if (message === "请选择您要配置的接入模式:") {
|
|
@@ -180,7 +180,7 @@ describe("wecom onboarding", () => {
|
|
|
180
180
|
throw new Error(`Unexpected select prompt: ${message}`);
|
|
181
181
|
}) as WizardPrompter["select"],
|
|
182
182
|
text: vi.fn(async ({ message }: { message: string }) => {
|
|
183
|
-
if (message === "
|
|
183
|
+
if (message === "请输入新的企业微信接入标识(英文):") {
|
|
184
184
|
return "HaiDao";
|
|
185
185
|
}
|
|
186
186
|
if (message === "请输入 BotId(机器人 ID):") {
|
|
@@ -213,7 +213,56 @@ describe("wecom onboarding", () => {
|
|
|
213
213
|
const noteText = (prompter.note as ReturnType<typeof vi.fn>).mock.calls
|
|
214
214
|
.map(([message]) => String(message))
|
|
215
215
|
.join("\n");
|
|
216
|
-
expect(noteText).toContain("
|
|
216
|
+
expect(noteText).toContain("接入标识已规范化为:haidao");
|
|
217
217
|
expect(wecomOnboardingAdapter.dmPolicy).toBeUndefined();
|
|
218
218
|
});
|
|
219
|
+
|
|
220
|
+
it("writes agentSecret for fresh agent onboarding", async () => {
|
|
221
|
+
const prompter = createPrompter({
|
|
222
|
+
select: vi.fn(async ({ message }: { message: string }) => {
|
|
223
|
+
if (message === "请选择您要配置的接入模式:") {
|
|
224
|
+
return "agent";
|
|
225
|
+
}
|
|
226
|
+
if (message === "请选择私聊 (DM) 访问策略:") {
|
|
227
|
+
return "open";
|
|
228
|
+
}
|
|
229
|
+
throw new Error(`Unexpected select prompt: ${message}`);
|
|
230
|
+
}) as WizardPrompter["select"],
|
|
231
|
+
text: vi.fn(async ({ message }: { message: string }) => {
|
|
232
|
+
if (message === "请输入 CorpID (企业ID):") {
|
|
233
|
+
return "corp-id";
|
|
234
|
+
}
|
|
235
|
+
if (message === "请输入 AgentID (应用ID):") {
|
|
236
|
+
return "1000001";
|
|
237
|
+
}
|
|
238
|
+
if (message === "请输入应用 Secret:") {
|
|
239
|
+
return "agent-secret";
|
|
240
|
+
}
|
|
241
|
+
if (message === "请输入 Token (回调令牌):") {
|
|
242
|
+
return "callback-token";
|
|
243
|
+
}
|
|
244
|
+
if (message === "请输入 EncodingAESKey (回调加密密钥):") {
|
|
245
|
+
return "1234567890123456789012345678901234567890123";
|
|
246
|
+
}
|
|
247
|
+
if (message === "欢迎语 (可选):") {
|
|
248
|
+
return "欢迎使用智能助手";
|
|
249
|
+
}
|
|
250
|
+
throw new Error(`Unexpected text prompt: ${message}`);
|
|
251
|
+
}) as WizardPrompter["text"],
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const result = await wecomOnboardingAdapter.configure({
|
|
255
|
+
cfg: {} as OpenClawConfig,
|
|
256
|
+
runtime: createRuntime(),
|
|
257
|
+
prompter,
|
|
258
|
+
options: {},
|
|
259
|
+
accountOverrides: {},
|
|
260
|
+
shouldPromptAccountIds: false,
|
|
261
|
+
forceAllowFrom: false,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const agent = result.cfg.channels?.wecom?.accounts?.default?.agent;
|
|
265
|
+
expect(agent?.agentSecret).toBe("agent-secret");
|
|
266
|
+
expect(agent?.corpSecret).toBeUndefined();
|
|
267
|
+
});
|
|
219
268
|
});
|
package/src/onboarding.ts
CHANGED
|
@@ -250,26 +250,26 @@ async function resolveOnboardingAccountId(params: {
|
|
|
250
250
|
if (!override && params.shouldPromptAccountIds) {
|
|
251
251
|
const existingIds = listWecomAccountIds(params.cfg);
|
|
252
252
|
const choice = await params.prompter.select({
|
|
253
|
-
message: "
|
|
253
|
+
message: "请选择企业微信接入标识(英文):",
|
|
254
254
|
options: [
|
|
255
255
|
...existingIds.map((id) => ({
|
|
256
256
|
value: id,
|
|
257
|
-
label: id === DEFAULT_ACCOUNT_ID ? "default
|
|
257
|
+
label: id === DEFAULT_ACCOUNT_ID ? "default(默认标识)" : id,
|
|
258
258
|
})),
|
|
259
|
-
{ value: "__new__", label: "
|
|
259
|
+
{ value: "__new__", label: "新增接入标识" },
|
|
260
260
|
],
|
|
261
261
|
initialValue: accountId,
|
|
262
262
|
});
|
|
263
263
|
if (choice === "__new__") {
|
|
264
264
|
const entered = await params.prompter.text({
|
|
265
|
-
message: "
|
|
266
|
-
validate: (value: string | undefined) => (value?.trim() ? undefined : "
|
|
265
|
+
message: "请输入新的企业微信接入标识(英文):",
|
|
266
|
+
validate: (value: string | undefined) => (value?.trim() ? undefined : "接入标识不能为空"),
|
|
267
267
|
});
|
|
268
268
|
const normalized = normalizeAccountId(String(entered));
|
|
269
269
|
if (String(entered).trim() !== normalized) {
|
|
270
270
|
await params.prompter.note(
|
|
271
|
-
|
|
272
|
-
"
|
|
271
|
+
`接入标识已规范化为:${normalized}`,
|
|
272
|
+
"企业微信接入标识",
|
|
273
273
|
);
|
|
274
274
|
}
|
|
275
275
|
accountId = normalized;
|
|
@@ -325,7 +325,7 @@ async function promptMode(prompter: WizardPrompter): Promise<WecomMode> {
|
|
|
325
325
|
hint: "Bot 默认 WS 易上手,Agent 负责应用回调、主动推送和媒体发送",
|
|
326
326
|
},
|
|
327
327
|
],
|
|
328
|
-
initialValue: "
|
|
328
|
+
initialValue: "bot",
|
|
329
329
|
});
|
|
330
330
|
return choice as WecomMode;
|
|
331
331
|
}
|
|
@@ -433,10 +433,10 @@ async function configureAgentMode(
|
|
|
433
433
|
).trim();
|
|
434
434
|
const agentId = Number(agentIdStr);
|
|
435
435
|
|
|
436
|
-
const
|
|
436
|
+
const agentSecret = String(
|
|
437
437
|
await prompter.text({
|
|
438
|
-
message: "
|
|
439
|
-
validate: (value: string | undefined) => (value?.trim() ? undefined : "Secret 不能为空"),
|
|
438
|
+
message: "请输入应用 Secret:",
|
|
439
|
+
validate: (value: string | undefined) => (value?.trim() ? undefined : "应用 Secret 不能为空"),
|
|
440
440
|
}),
|
|
441
441
|
).trim();
|
|
442
442
|
|
|
@@ -478,7 +478,7 @@ async function configureAgentMode(
|
|
|
478
478
|
|
|
479
479
|
const agentConfig: WecomAgentConfig = {
|
|
480
480
|
corpId,
|
|
481
|
-
|
|
481
|
+
agentSecret,
|
|
482
482
|
agentId,
|
|
483
483
|
token,
|
|
484
484
|
encodingAESKey,
|
|
@@ -506,7 +506,7 @@ async function promptDmPolicy(
|
|
|
506
506
|
{ value: "open", label: "开放模式", hint: "任何人可发起" },
|
|
507
507
|
{ value: "disabled", label: "禁用私聊", hint: "不接受私聊消息" },
|
|
508
508
|
],
|
|
509
|
-
initialValue: "
|
|
509
|
+
initialValue: "open",
|
|
510
510
|
});
|
|
511
511
|
|
|
512
512
|
const policy = policyChoice as "pairing" | "allowlist" | "open" | "disabled";
|
|
@@ -560,7 +560,7 @@ async function showSummary(cfg: OpenClawConfig, prompter: WizardPrompter, accoun
|
|
|
560
560
|
lines.push(" 出站能力: Agent API(主动发送 / 补送 / 媒体)");
|
|
561
561
|
}
|
|
562
562
|
|
|
563
|
-
lines.push(`
|
|
563
|
+
lines.push(` 接入标识: ${accountId}`);
|
|
564
564
|
lines.push(" 运维检查: openclaw channels status --deep");
|
|
565
565
|
lines.push(" 关键日志: [wecom-runtime] [wecom-ws] [wecom-http] [wecom-agent-delivery]");
|
|
566
566
|
|
package/src/outbound.test.ts
CHANGED
|
@@ -16,7 +16,7 @@ describe("wecomOutbound", () => {
|
|
|
16
16
|
text: "caption",
|
|
17
17
|
mediaUrl: "https://example.com/media.png",
|
|
18
18
|
} as any),
|
|
19
|
-
).rejects.toThrow(/
|
|
19
|
+
).rejects.toThrow(/requires Agent mode for account=default/i);
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
it("throws explicit error when outbound accountId does not exist", async () => {
|
|
@@ -15,6 +15,16 @@ export function createBotWsReplyHandle(params: {
|
|
|
15
15
|
return streamId;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
let ackSent = false;
|
|
19
|
+
const ackTimer = setTimeout(() => {
|
|
20
|
+
if (ackSent) return;
|
|
21
|
+
ackSent = true;
|
|
22
|
+
params.client.replyStream(params.frame, resolveStreamId(), "⏳ 正在思考中...\n\n", false)
|
|
23
|
+
.catch(() => { /* ignore */ });
|
|
24
|
+
}, 4000);
|
|
25
|
+
|
|
26
|
+
const cleanupTimer = () => clearTimeout(ackTimer);
|
|
27
|
+
|
|
18
28
|
return {
|
|
19
29
|
context: {
|
|
20
30
|
transport: "bot-ws",
|
|
@@ -29,17 +39,22 @@ export function createBotWsReplyHandle(params: {
|
|
|
29
39
|
},
|
|
30
40
|
},
|
|
31
41
|
deliver: async (payload: ReplyPayload, info) => {
|
|
32
|
-
if (payload.isReasoning)
|
|
33
|
-
|
|
34
|
-
}
|
|
42
|
+
if (payload.isReasoning) return;
|
|
43
|
+
|
|
35
44
|
const text = payload.text?.trim();
|
|
36
|
-
if (!text)
|
|
37
|
-
|
|
45
|
+
if (!text) return;
|
|
46
|
+
|
|
47
|
+
if (!ackSent) {
|
|
48
|
+
cleanupTimer();
|
|
49
|
+
ackSent = true;
|
|
38
50
|
}
|
|
51
|
+
|
|
39
52
|
await params.client.replyStream(params.frame, resolveStreamId(), text, info.kind === "final");
|
|
40
53
|
params.onDeliver?.();
|
|
41
54
|
},
|
|
42
55
|
fail: async (error: unknown) => {
|
|
56
|
+
cleanupTimer();
|
|
57
|
+
ackSent = true;
|
|
43
58
|
const message = error instanceof Error ? error.message : String(error);
|
|
44
59
|
await params.client.replyStream(params.frame, resolveStreamId(), `WeCom WS reply failed: ${message}`, true);
|
|
45
60
|
params.onFail?.(error);
|
package/src/types/config.ts
CHANGED
|
@@ -49,7 +49,12 @@ export type WecomBotConfig = {
|
|
|
49
49
|
|
|
50
50
|
export type WecomAgentConfig = {
|
|
51
51
|
corpId: string;
|
|
52
|
-
|
|
52
|
+
agentSecret?: string;
|
|
53
|
+
/**
|
|
54
|
+
* Deprecated compatibility alias for old configs.
|
|
55
|
+
* New configs should use `agentSecret`.
|
|
56
|
+
*/
|
|
57
|
+
corpSecret?: string;
|
|
53
58
|
agentId?: number | string;
|
|
54
59
|
token: string;
|
|
55
60
|
encodingAESKey: string;
|