@yanhaidao/wecom 2.3.270 → 2.4.120
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/MENU_EVENT_CONF.md +500 -0
- package/MENU_EVENT_PLAN.md +440 -0
- package/README.md +80 -3
- package/UPSTREAM_CONFIG.md +170 -0
- package/UPSTREAM_PLAN.md +175 -0
- package/changelog/v2.4.12.md +37 -0
- package/package.json +1 -1
- package/scripts/wecom/README.md +123 -0
- package/scripts/wecom/menu-click-help.js +59 -0
- package/scripts/wecom/menu-click-help.py +55 -0
- package/src/agent/event-router.test.ts +421 -0
- package/src/agent/event-router.ts +272 -0
- package/src/agent/handler.event-filter.test.ts +65 -1
- package/src/agent/handler.ts +375 -21
- package/src/agent/script-runner.ts +186 -0
- package/src/agent/test-fixtures/invalid-json-script.mjs +1 -0
- package/src/agent/test-fixtures/reply-event-script.mjs +29 -0
- package/src/agent/test-fixtures/reply-event-script.py +17 -0
- package/src/app/account-runtime.ts +1 -1
- package/src/capability/agent/upstream-delivery-service.ts +96 -0
- package/src/capability/bot/sandbox-media.test.ts +221 -0
- package/src/capability/bot/sandbox-media.ts +176 -0
- package/src/capability/bot/stream-orchestrator.ts +19 -0
- package/src/channel.config.test.ts +33 -0
- package/src/channel.meta.test.ts +10 -0
- package/src/channel.ts +4 -1
- package/src/config/accounts.ts +16 -0
- package/src/config/schema.ts +58 -0
- package/src/context-store.ts +41 -8
- package/src/outbound.test.ts +211 -2
- package/src/outbound.ts +323 -70
- package/src/runtime/session-manager.test.ts +39 -0
- package/src/runtime/session-manager.ts +17 -0
- package/src/runtime/source-registry.ts +5 -0
- package/src/shared/media-asset.ts +78 -0
- package/src/shared/media-service.test.ts +111 -0
- package/src/shared/media-service.ts +42 -14
- package/src/target.ts +40 -0
- package/src/transport/agent-api/client.ts +233 -0
- package/src/transport/agent-api/core.ts +101 -5
- package/src/transport/agent-api/upstream-delivery.ts +45 -0
- package/src/transport/agent-api/upstream-media-upload.ts +70 -0
- package/src/transport/agent-api/upstream-reply.ts +43 -0
- package/src/types/account.ts +2 -0
- package/src/types/config.ts +74 -0
- package/src/types/message.ts +2 -0
- package/src/upstream/index.ts +150 -0
- package/src/upstream.test.ts +84 -0
- package/vitest.config.ts +15 -4
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
# 企业微信自定义菜单与 Click 事件配置指南
|
|
2
|
+
|
|
3
|
+
## 概述
|
|
4
|
+
|
|
5
|
+
本文档介绍如何在 OpenClaw 企业微信插件中配置自定义菜单,并处理 click 类型按钮的事件。
|
|
6
|
+
|
|
7
|
+
## 创建菜单
|
|
8
|
+
|
|
9
|
+
### API 接口
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
POST https://qyapi.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN&agentid=AGENTID
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### 菜单结构示例
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"button": [
|
|
20
|
+
{
|
|
21
|
+
"type": "click",
|
|
22
|
+
"name": "Python测试",
|
|
23
|
+
"key": "TEST_CLICK_PY"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"type": "click",
|
|
27
|
+
"name": "Node测试",
|
|
28
|
+
"key": "TEST_CLICK_JS"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "更多",
|
|
32
|
+
"sub_button": [
|
|
33
|
+
{
|
|
34
|
+
"type": "view",
|
|
35
|
+
"name": "打开网页",
|
|
36
|
+
"url": "https://work.weixin.qq.com"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"type": "click",
|
|
40
|
+
"name": "菜单信息",
|
|
41
|
+
"key": "MENU_INFO"
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 支持的按钮类型
|
|
50
|
+
|
|
51
|
+
| 类型 | 说明 |
|
|
52
|
+
|------|------|
|
|
53
|
+
| `click` | 点击推事件,触发事件推送 |
|
|
54
|
+
| `view` | 跳转URL,打开网页 |
|
|
55
|
+
| `scancode_push` | 扫码推事件 |
|
|
56
|
+
| `scancode_waitmsg` | 扫码推事件且弹出提示框 |
|
|
57
|
+
| `pic_sysphoto` | 弹出系统拍照发图 |
|
|
58
|
+
| `pic_photo_or_album` | 弹出拍照或者相册发图 |
|
|
59
|
+
| `pic_weixin` | 弹出企业微信相册发图器 |
|
|
60
|
+
| `location_select` | 弹出地理位置选择器 |
|
|
61
|
+
| `view_miniprogram` | 跳转到小程序 |
|
|
62
|
+
|
|
63
|
+
## 配置事件路由
|
|
64
|
+
|
|
65
|
+
在 `openclaw.json` 中配置 `eventRouting` 和 `scriptRuntime`:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"channels": {
|
|
70
|
+
"wecom": {
|
|
71
|
+
"accounts": {
|
|
72
|
+
"your-account": {
|
|
73
|
+
"agent": {
|
|
74
|
+
"eventRouting": {
|
|
75
|
+
"unmatchedAction": "forwardToAgent",
|
|
76
|
+
"routes": [
|
|
77
|
+
{
|
|
78
|
+
"id": "test-click-python",
|
|
79
|
+
"when": {
|
|
80
|
+
"eventType": "click",
|
|
81
|
+
"eventKey": "TEST_CLICK_PY"
|
|
82
|
+
},
|
|
83
|
+
"handler": {
|
|
84
|
+
"type": "python_script",
|
|
85
|
+
"entry": "/path/to/script.py"
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"id": "test-click-js",
|
|
90
|
+
"when": {
|
|
91
|
+
"eventType": "click",
|
|
92
|
+
"eventKey": "TEST_CLICK_JS"
|
|
93
|
+
},
|
|
94
|
+
"handler": {
|
|
95
|
+
"type": "node_script",
|
|
96
|
+
"entry": "/path/to/script.mjs"
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"id": "menu-info-echo",
|
|
101
|
+
"when": {
|
|
102
|
+
"eventType": "click",
|
|
103
|
+
"eventKey": "MENU_INFO"
|
|
104
|
+
},
|
|
105
|
+
"handler": {
|
|
106
|
+
"type": "builtin",
|
|
107
|
+
"name": "echo"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
},
|
|
112
|
+
"scriptRuntime": {
|
|
113
|
+
"enabled": true,
|
|
114
|
+
"allowPaths": ["/path/to/scripts"],
|
|
115
|
+
"defaultTimeoutMs": 10000,
|
|
116
|
+
"pythonCommand": "python3",
|
|
117
|
+
"nodeCommand": "node"
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## 事件路由配置说明
|
|
128
|
+
|
|
129
|
+
### unmatchedAction(未匹配事件的处理方式)
|
|
130
|
+
|
|
131
|
+
当收到的事件**没有匹配任何路由**时,由 `unmatchedAction` 决定如何处理:
|
|
132
|
+
|
|
133
|
+
- `ignore` - 未匹配的事件直接忽略,不处理也不回复
|
|
134
|
+
- `forwardToAgent` - 未匹配的事件传递给 Agent(AI)处理
|
|
135
|
+
|
|
136
|
+
**注意:** 这个配置只影响**未匹配路由**的事件。如果事件匹配了路由,则由路由的 handler 决定后续行为。
|
|
137
|
+
|
|
138
|
+
### 路由匹配条件 (when)
|
|
139
|
+
|
|
140
|
+
| 字段 | 说明 |
|
|
141
|
+
|------|------|
|
|
142
|
+
| `eventType` | 事件类型,如 `click`、`change_contact` |
|
|
143
|
+
| `eventKey` | 精确匹配事件 key |
|
|
144
|
+
| `eventKeyPrefix` | 前缀匹配事件 key |
|
|
145
|
+
| `eventKeyPattern` | 正则匹配事件 key |
|
|
146
|
+
| `changeType` | 通讯录变更类型,如 `create_user` |
|
|
147
|
+
|
|
148
|
+
### Handler 类型
|
|
149
|
+
|
|
150
|
+
| 类型 | 说明 |
|
|
151
|
+
|------|------|
|
|
152
|
+
| `builtin` | 内置处理器,目前支持 `echo` |
|
|
153
|
+
| `node_script` | Node.js 脚本 |
|
|
154
|
+
| `python_script` | Python 脚本 |
|
|
155
|
+
|
|
156
|
+
### `chainToAgent` 的两个来源
|
|
157
|
+
|
|
158
|
+
`chainToAgent` 现在只表达一个意思:当前事件处理完成后,是否继续进入默认 Agent(AI)流程。
|
|
159
|
+
|
|
160
|
+
这个开关有两个输入来源,但它们控制的是同一件事,不是两个不同功能:
|
|
161
|
+
|
|
162
|
+
#### 1. Handler 配置里的 `chainToAgent`
|
|
163
|
+
|
|
164
|
+
在 `openclaw.json` 的 handler 中配置:
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{
|
|
168
|
+
"handler": {
|
|
169
|
+
"type": "python_script",
|
|
170
|
+
"entry": "/path/to/script.py",
|
|
171
|
+
"chainToAgent": true
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**特点:**
|
|
177
|
+
- 这是静态配置,适合声明“这条路由处理完后一定继续走 Agent”
|
|
178
|
+
- 只有设为 `true` 才会产生强制效果
|
|
179
|
+
- 设为 `false` 和不写,在当前实现里效果相同,都不会阻止脚本返回 `true`
|
|
180
|
+
|
|
181
|
+
#### 2. 脚本返回里的 `chainToAgent`
|
|
182
|
+
|
|
183
|
+
脚本通过 stdout 返回 JSON:
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
{
|
|
187
|
+
"ok": true,
|
|
188
|
+
"action": "reply_text",
|
|
189
|
+
"reply": {
|
|
190
|
+
"text": "回复内容"
|
|
191
|
+
},
|
|
192
|
+
"chainToAgent": false // 脚本动态决定
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**特点:**
|
|
197
|
+
- 这是动态决策,适合脚本根据业务条件决定是否继续走 Agent
|
|
198
|
+
- 当 handler 没有把 `chainToAgent` 设为 `true` 时,以脚本返回为准
|
|
199
|
+
- 如果脚本不返回该字段,默认为 `false`
|
|
200
|
+
|
|
201
|
+
#### 实际合并规则
|
|
202
|
+
|
|
203
|
+
代码中的最终判断等价于:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
finalChainToAgent =
|
|
207
|
+
handler.chainToAgent === true || scriptResponse.chainToAgent === true;
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
可以把它理解为:
|
|
211
|
+
|
|
212
|
+
- `handler.chainToAgent` 是“静态放行开关”
|
|
213
|
+
- `scriptResponse.chainToAgent` 是“脚本运行后的动态放行结果”
|
|
214
|
+
- 任意一方明确返回 `true`,都会继续进入默认 Agent 流程
|
|
215
|
+
- 两边都不是 `true` 时,才会在当前路由处理后结束
|
|
216
|
+
|
|
217
|
+
#### 行为总结
|
|
218
|
+
|
|
219
|
+
| Handler 配置 | 脚本返回 | 最终行为 |
|
|
220
|
+
|-------------|---------|---------|
|
|
221
|
+
| `true` | `false` | `true` |
|
|
222
|
+
| `true` | `true` | `true` |
|
|
223
|
+
| `false` | `true` | `true` |
|
|
224
|
+
| 未设置 | `true` | `true` |
|
|
225
|
+
| 未设置 / `false` | `false` | `false` |
|
|
226
|
+
| 未设置 / `false` | 未返回 | `false` |
|
|
227
|
+
|
|
228
|
+
**关键点:** 当前实现里不存在“`false` 覆盖 `true`”。只有 `true` 会向上抬高最终结果。
|
|
229
|
+
|
|
230
|
+
#### 推荐做法
|
|
231
|
+
|
|
232
|
+
**场景 1:脚本完全控制**
|
|
233
|
+
- Handler 配置中**不设置** `chainToAgent`
|
|
234
|
+
- 脚本根据需要返回 `true` 或 `false`
|
|
235
|
+
|
|
236
|
+
```json
|
|
237
|
+
// openclaw.json
|
|
238
|
+
"handler": {
|
|
239
|
+
"type": "python_script",
|
|
240
|
+
"entry": "/path/to/script.py"
|
|
241
|
+
// 不写 chainToAgent
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
# script.py - 动态决定
|
|
247
|
+
if some_condition:
|
|
248
|
+
response["chainToAgent"] = True # 继续 AI 处理
|
|
249
|
+
else:
|
|
250
|
+
response["chainToAgent"] = False # 到此结束
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**场景 2:固定继续走 Agent**
|
|
254
|
+
- Handler 配置中设置 `"chainToAgent": true`
|
|
255
|
+
- 此时即使脚本返回 `false`,最终仍会继续进入默认 Agent 流程
|
|
256
|
+
|
|
257
|
+
```json
|
|
258
|
+
// openclaw.json - 固定走 AI 流程
|
|
259
|
+
"handler": {
|
|
260
|
+
"type": "python_script",
|
|
261
|
+
"entry": "/path/to/script.py",
|
|
262
|
+
"chainToAgent": true
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**场景 3:固定不继续走 Agent**
|
|
267
|
+
- 不要依赖 handler 里的 `"chainToAgent": false`
|
|
268
|
+
- 应该保持 handler 不写该字段,并让脚本稳定返回 `false`
|
|
269
|
+
|
|
270
|
+
也就是说:
|
|
271
|
+
|
|
272
|
+
- 想“固定继续”,可以用 handler 配置 `true`
|
|
273
|
+
- 想“固定停止”,应由脚本返回 `false` 来保证
|
|
274
|
+
|
|
275
|
+
## 脚本编写规范
|
|
276
|
+
|
|
277
|
+
脚本通过 `stdin` 接收 JSON 数据,通过 `stdout` 返回 JSON 响应。
|
|
278
|
+
|
|
279
|
+
### 输入格式 (envelope)
|
|
280
|
+
|
|
281
|
+
```json
|
|
282
|
+
{
|
|
283
|
+
"version": "1.0",
|
|
284
|
+
"channel": "wecom",
|
|
285
|
+
"accountId": "blue",
|
|
286
|
+
"receivedAt": 1775963707523,
|
|
287
|
+
"message": {
|
|
288
|
+
"msgType": "event",
|
|
289
|
+
"eventType": "click",
|
|
290
|
+
"eventKey": "TEST_CLICK_PY",
|
|
291
|
+
"changeType": null,
|
|
292
|
+
"fromUser": "GuanXiaoPeng",
|
|
293
|
+
"toUser": "corp-id",
|
|
294
|
+
"chatId": null,
|
|
295
|
+
"agentId": 1000015,
|
|
296
|
+
"createTime": 1775963707,
|
|
297
|
+
"msgId": "msg-id",
|
|
298
|
+
"raw": { /* 原始 XML 解析数据 */ }
|
|
299
|
+
},
|
|
300
|
+
"route": {
|
|
301
|
+
"matchedRuleId": "test-click-python",
|
|
302
|
+
"handlerType": "python_script"
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### 输出格式 (response)
|
|
308
|
+
|
|
309
|
+
```json
|
|
310
|
+
{
|
|
311
|
+
"ok": true,
|
|
312
|
+
"action": "reply_text",
|
|
313
|
+
"reply": {
|
|
314
|
+
"text": "回复内容"
|
|
315
|
+
},
|
|
316
|
+
"chainToAgent": false
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### action 类型
|
|
321
|
+
|
|
322
|
+
- `none` - 不回复
|
|
323
|
+
- `reply_text` - 回复文本消息
|
|
324
|
+
|
|
325
|
+
### Python 脚本示例
|
|
326
|
+
|
|
327
|
+
```python
|
|
328
|
+
#!/usr/bin/env python3
|
|
329
|
+
import json
|
|
330
|
+
import sys
|
|
331
|
+
|
|
332
|
+
def main():
|
|
333
|
+
payload = json.load(sys.stdin)
|
|
334
|
+
message = payload.get("message", {})
|
|
335
|
+
event_key = message.get("eventKey") or ""
|
|
336
|
+
from_user = message.get("fromUser", "")
|
|
337
|
+
|
|
338
|
+
response = {
|
|
339
|
+
"ok": True,
|
|
340
|
+
"action": "reply_text",
|
|
341
|
+
"reply": {
|
|
342
|
+
"text": f"收到点击事件: {event_key}\n来自用户: {from_user}"
|
|
343
|
+
},
|
|
344
|
+
"chainToAgent": False
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
json.dump(response, sys.stdout)
|
|
348
|
+
|
|
349
|
+
if __name__ == "__main__":
|
|
350
|
+
main()
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Node.js 脚本示例
|
|
354
|
+
|
|
355
|
+
```javascript
|
|
356
|
+
#!/usr/bin/env node
|
|
357
|
+
let raw = "";
|
|
358
|
+
process.stdin.setEncoding("utf8");
|
|
359
|
+
|
|
360
|
+
process.stdin.on("data", (chunk) => {
|
|
361
|
+
raw += chunk;
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
process.stdin.on("end", () => {
|
|
365
|
+
const payload = JSON.parse(raw || "{}");
|
|
366
|
+
const message = payload?.message ?? {};
|
|
367
|
+
const eventKey = message?.eventKey ?? "";
|
|
368
|
+
const fromUser = message?.fromUser ?? "";
|
|
369
|
+
|
|
370
|
+
const response = {
|
|
371
|
+
ok: true,
|
|
372
|
+
action: "reply_text",
|
|
373
|
+
reply: {
|
|
374
|
+
text: `收到点击事件: ${eventKey}\n来自用户: ${fromUser}`
|
|
375
|
+
},
|
|
376
|
+
chainToAgent: false
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
process.stdout.write(JSON.stringify(response));
|
|
380
|
+
});
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
## 常见踩坑
|
|
384
|
+
|
|
385
|
+
### 1. IP 白名单限制
|
|
386
|
+
|
|
387
|
+
**错误信息:**
|
|
388
|
+
```
|
|
389
|
+
{"errcode": 60020, "errmsg": "not allow to access from your ip"}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
**解决方案:**
|
|
393
|
+
- 在企业微信管理后台配置可信 IP 列表
|
|
394
|
+
- 或使用配置的代理服务器
|
|
395
|
+
|
|
396
|
+
### 2. 菜单名称长度限制
|
|
397
|
+
|
|
398
|
+
**错误信息:**
|
|
399
|
+
```
|
|
400
|
+
{"errcode": 40058, "errmsg": "button.name exceed max length 16"}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
**解决方案:**
|
|
404
|
+
- 一级菜单名称不超过 16 字节(约 16 个英文字符或 8 个中文字符)
|
|
405
|
+
- 子菜单名称不超过 40 字节
|
|
406
|
+
- 避免使用 emoji,会占用更多字节
|
|
407
|
+
|
|
408
|
+
### 3. 脚本路径未授权
|
|
409
|
+
|
|
410
|
+
**错误信息:**
|
|
411
|
+
```
|
|
412
|
+
script path is not allowed: /path/to/script.py
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
**解决方案:**
|
|
416
|
+
- 确保脚本路径在 `scriptRuntime.allowPaths` 配置的目录下
|
|
417
|
+
- 路径必须是绝对路径
|
|
418
|
+
|
|
419
|
+
### 4. 脚本运行时未启用
|
|
420
|
+
|
|
421
|
+
**错误信息:**
|
|
422
|
+
```
|
|
423
|
+
script runtime is disabled
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
**解决方案:**
|
|
427
|
+
- 确保 `scriptRuntime.enabled` 设置为 `true`
|
|
428
|
+
|
|
429
|
+
### 5. 脚本输出格式错误
|
|
430
|
+
|
|
431
|
+
**错误信息:**
|
|
432
|
+
```
|
|
433
|
+
script output is not valid JSON
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
**解决方案:**
|
|
437
|
+
- 确保脚本输出是有效的 JSON 格式
|
|
438
|
+
- 不要输出调试信息到 stdout
|
|
439
|
+
- 错误信息可以输出到 stderr
|
|
440
|
+
|
|
441
|
+
### 6. 脚本执行超时
|
|
442
|
+
|
|
443
|
+
**错误信息:**
|
|
444
|
+
```
|
|
445
|
+
script execution timed out after 5000ms
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
**解决方案:**
|
|
449
|
+
- 增加 `timeoutMs` 配置(handler 级别或 `defaultTimeoutMs` 全局)
|
|
450
|
+
- 优化脚本性能
|
|
451
|
+
|
|
452
|
+
### 7. Access Token 过期
|
|
453
|
+
|
|
454
|
+
**错误信息:**
|
|
455
|
+
```
|
|
456
|
+
{"errcode": 42001, "errmsg": "access_token expired"}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
**解决方案:**
|
|
460
|
+
- 重新获取 access_token
|
|
461
|
+
- access_token 有效期为 2 小时
|
|
462
|
+
|
|
463
|
+
### 8. 菜单不显示
|
|
464
|
+
|
|
465
|
+
**可能原因:**
|
|
466
|
+
- 应用未发布(需要发布后才对成员可见)
|
|
467
|
+
- 成员不在应用可见范围内
|
|
468
|
+
- 缓存问题(重新进入应用或等待几分钟)
|
|
469
|
+
|
|
470
|
+
## 调试技巧
|
|
471
|
+
|
|
472
|
+
### 1. 查看当前菜单
|
|
473
|
+
|
|
474
|
+
```bash
|
|
475
|
+
curl "https://qyapi.weixin.qq.com/cgi-bin/menu/get?access_token=TOKEN&agentid=AGENTID"
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### 2. 删除菜单
|
|
479
|
+
|
|
480
|
+
```bash
|
|
481
|
+
curl "https://qyapi.weixin.qq.com/cgi-bin/menu/delete?access_token=TOKEN&agentid=AGENTID"
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### 3. 本地测试脚本
|
|
485
|
+
|
|
486
|
+
```bash
|
|
487
|
+
# 准备测试数据
|
|
488
|
+
echo '{"version":"1.0","channel":"wecom","accountId":"blue","message":{"eventType":"click","eventKey":"TEST","fromUser":"test"},"route":{"matchedRuleId":"test","handlerType":"python_script"}}' | python3 script.py
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### 4. 查看 OpenClaw 日志
|
|
492
|
+
|
|
493
|
+
```bash
|
|
494
|
+
openclaw logs --tail 100
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
## 参考文档
|
|
498
|
+
|
|
499
|
+
- [企业微信创建菜单 API](https://developer.work.weixin.qq.com/document/path/90231)
|
|
500
|
+
- [企业微信接收事件推送](https://developer.work.weixin.qq.com/document/path/90240)
|