@yanhaidao/wecom 2.3.260 → 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 +90 -8
- package/UPSTREAM_CONFIG.md +170 -0
- package/UPSTREAM_PLAN.md +175 -0
- package/changelog/v2.3.27.md +33 -0
- package/changelog/v2.4.12.md +37 -0
- package/index.test.ts +5 -1
- package/package.json +17 -17
- 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/app/index.ts +6 -3
- 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/capability/mcp/tool.ts +7 -3
- package/src/channel.config.test.ts +33 -0
- package/src/channel.meta.test.ts +14 -0
- package/src/channel.ts +33 -60
- package/src/config/accounts.ts +16 -0
- package/src/config/schema.ts +58 -0
- package/src/context-store.ts +41 -8
- package/src/onboarding.test.ts +42 -24
- package/src/onboarding.ts +598 -553
- package/src/outbound.test.ts +211 -2
- package/src/outbound.ts +340 -81
- 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/transport/bot-ws/media.test.ts +8 -8
- package/src/transport/bot-ws/media.ts +51 -2
- package/src/transport/bot-ws/sdk-adapter.ts +6 -6
- 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,55 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import json
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def main() -> int:
|
|
7
|
+
# 从 stdin 读取平台注入的事件 envelope(JSON)
|
|
8
|
+
try:
|
|
9
|
+
payload = json.load(sys.stdin)
|
|
10
|
+
except Exception:
|
|
11
|
+
# 输入异常时返回可读错误,避免脚本抛异常导致无输出
|
|
12
|
+
json.dump(
|
|
13
|
+
{
|
|
14
|
+
"ok": False,
|
|
15
|
+
"action": "reply_text",
|
|
16
|
+
"reply": {"text": "事件脚本解析输入失败,请联系管理员。"},
|
|
17
|
+
"chainToAgent": True,
|
|
18
|
+
},
|
|
19
|
+
sys.stdout,
|
|
20
|
+
ensure_ascii=False,
|
|
21
|
+
)
|
|
22
|
+
return 0
|
|
23
|
+
|
|
24
|
+
message = payload.get("message", {}) if isinstance(payload, dict) else {}
|
|
25
|
+
event_type = str(message.get("eventType") or "unknown")
|
|
26
|
+
event_key = str(message.get("eventKey") or "")
|
|
27
|
+
from_user = str(message.get("fromUser") or "")
|
|
28
|
+
|
|
29
|
+
# 这里给出联调用的回显文本,业务可改为真正逻辑
|
|
30
|
+
lines = [
|
|
31
|
+
"已收到菜单事件",
|
|
32
|
+
f"eventType={event_type}",
|
|
33
|
+
f"eventKey={event_key or '<empty>'}",
|
|
34
|
+
]
|
|
35
|
+
if from_user:
|
|
36
|
+
lines.append(f"fromUser={from_user}")
|
|
37
|
+
lines.append("")
|
|
38
|
+
lines.append("你可以在 openclaw 配置里把这个 eventKey 路由到具体业务脚本。")
|
|
39
|
+
|
|
40
|
+
json.dump(
|
|
41
|
+
{
|
|
42
|
+
"ok": True,
|
|
43
|
+
"action": "reply_text",
|
|
44
|
+
"reply": {"text": "\n".join(lines)},
|
|
45
|
+
"chainToAgent": False,
|
|
46
|
+
"audit": {"tags": ["menu", "click", event_type]},
|
|
47
|
+
},
|
|
48
|
+
sys.stdout,
|
|
49
|
+
ensure_ascii=False,
|
|
50
|
+
)
|
|
51
|
+
return 0
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
if __name__ == "__main__":
|
|
55
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { routeAgentInboundEvent } from "./event-router.js";
|
|
5
|
+
import type { ResolvedAgentAccount, WecomAgentInboundMessage } from "../types/index.js";
|
|
6
|
+
import type { WecomRuntimeAuditEvent } from "../types/runtime-context.js";
|
|
7
|
+
|
|
8
|
+
function createAgent(overrides?: Partial<ResolvedAgentAccount>): ResolvedAgentAccount {
|
|
9
|
+
return {
|
|
10
|
+
accountId: "default",
|
|
11
|
+
configured: true,
|
|
12
|
+
callbackConfigured: true,
|
|
13
|
+
apiConfigured: true,
|
|
14
|
+
corpId: "corp-1",
|
|
15
|
+
corpSecret: "secret",
|
|
16
|
+
agentId: 1001,
|
|
17
|
+
token: "token",
|
|
18
|
+
encodingAESKey: "aes",
|
|
19
|
+
eventEnabled: true,
|
|
20
|
+
allowedEventTypes: ["click", "change_contact"],
|
|
21
|
+
config: {
|
|
22
|
+
corpId: "corp-1",
|
|
23
|
+
corpSecret: "secret",
|
|
24
|
+
agentId: 1001,
|
|
25
|
+
token: "token",
|
|
26
|
+
encodingAESKey: "aes",
|
|
27
|
+
},
|
|
28
|
+
...overrides,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe("routeAgentInboundEvent", () => {
|
|
33
|
+
it("ignores unmatched events when unmatchedAction is ignore", async () => {
|
|
34
|
+
const agent = createAgent({
|
|
35
|
+
config: {
|
|
36
|
+
corpId: "corp-1",
|
|
37
|
+
corpSecret: "secret",
|
|
38
|
+
agentId: 1001,
|
|
39
|
+
token: "token",
|
|
40
|
+
encodingAESKey: "aes",
|
|
41
|
+
eventRouting: {
|
|
42
|
+
unmatchedAction: "ignore",
|
|
43
|
+
routes: [],
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const result = await routeAgentInboundEvent({
|
|
49
|
+
agent,
|
|
50
|
+
msgType: "event",
|
|
51
|
+
eventType: "click",
|
|
52
|
+
fromUser: "zhangsan",
|
|
53
|
+
msg: { MsgType: "event", Event: "click", EventKey: "MENU_X" },
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
expect(result.handled).toBe(true);
|
|
57
|
+
expect(result.chainToAgent).toBe(false);
|
|
58
|
+
expect(result.reason).toBe("unmatched_event_ignored");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("proxies unmatched events to agent when unmatchedAction is forwardToAgent", async () => {
|
|
62
|
+
const agent = createAgent({
|
|
63
|
+
config: {
|
|
64
|
+
corpId: "corp-1",
|
|
65
|
+
corpSecret: "secret",
|
|
66
|
+
agentId: 1001,
|
|
67
|
+
token: "token",
|
|
68
|
+
encodingAESKey: "aes",
|
|
69
|
+
eventRouting: {
|
|
70
|
+
unmatchedAction: "forwardToAgent",
|
|
71
|
+
routes: [],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const result = await routeAgentInboundEvent({
|
|
77
|
+
agent,
|
|
78
|
+
msgType: "event",
|
|
79
|
+
eventType: "click",
|
|
80
|
+
fromUser: "zhangsan",
|
|
81
|
+
msg: { MsgType: "event", Event: "click", EventKey: "MENU_X" },
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
expect(result.handled).toBe(false);
|
|
85
|
+
expect(result.chainToAgent).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("matches builtin echo routes using eventKey", async () => {
|
|
89
|
+
const agent = createAgent({
|
|
90
|
+
config: {
|
|
91
|
+
corpId: "corp-1",
|
|
92
|
+
corpSecret: "secret",
|
|
93
|
+
agentId: 1001,
|
|
94
|
+
token: "token",
|
|
95
|
+
encodingAESKey: "aes",
|
|
96
|
+
eventRouting: {
|
|
97
|
+
routes: [
|
|
98
|
+
{
|
|
99
|
+
id: "menu-help",
|
|
100
|
+
when: { eventType: "click", eventKey: "MENU_HELP" },
|
|
101
|
+
handler: { type: "builtin", name: "echo" },
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const result = await routeAgentInboundEvent({
|
|
109
|
+
agent,
|
|
110
|
+
msgType: "event",
|
|
111
|
+
eventType: "click",
|
|
112
|
+
fromUser: "zhangsan",
|
|
113
|
+
msg: { MsgType: "event", Event: "click", EventKey: "MENU_HELP" },
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
expect(result.handled).toBe(true);
|
|
117
|
+
expect(result.replyText).toContain("event=click");
|
|
118
|
+
expect(result.replyText).toContain("eventKey=MENU_HELP");
|
|
119
|
+
expect(result.matchedRouteId).toBe("menu-help");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("matches change_contact routes using changeType", async () => {
|
|
123
|
+
const agent = createAgent({
|
|
124
|
+
config: {
|
|
125
|
+
corpId: "corp-1",
|
|
126
|
+
corpSecret: "secret",
|
|
127
|
+
agentId: 1001,
|
|
128
|
+
token: "token",
|
|
129
|
+
encodingAESKey: "aes",
|
|
130
|
+
eventRouting: {
|
|
131
|
+
routes: [
|
|
132
|
+
{
|
|
133
|
+
id: "contact-create-user",
|
|
134
|
+
when: { eventType: "change_contact", changeType: "create_user" },
|
|
135
|
+
handler: { type: "builtin", name: "echo" },
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const msg: WecomAgentInboundMessage = {
|
|
143
|
+
MsgType: "event",
|
|
144
|
+
Event: "change_contact",
|
|
145
|
+
ChangeType: "create_user",
|
|
146
|
+
};
|
|
147
|
+
const result = await routeAgentInboundEvent({
|
|
148
|
+
agent,
|
|
149
|
+
msgType: "event",
|
|
150
|
+
eventType: "change_contact",
|
|
151
|
+
fromUser: "sys",
|
|
152
|
+
msg,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
expect(result.handled).toBe(true);
|
|
156
|
+
expect(result.replyText).toContain("changeType=create_user");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("passes full event params to node scripts", async () => {
|
|
160
|
+
const fixturePath = path.resolve("src/agent/test-fixtures/reply-event-script.mjs");
|
|
161
|
+
const agent = createAgent({
|
|
162
|
+
config: {
|
|
163
|
+
corpId: "corp-1",
|
|
164
|
+
corpSecret: "secret",
|
|
165
|
+
agentId: 1001,
|
|
166
|
+
token: "token",
|
|
167
|
+
encodingAESKey: "aes",
|
|
168
|
+
eventRouting: {
|
|
169
|
+
routes: [
|
|
170
|
+
{
|
|
171
|
+
id: "script-click",
|
|
172
|
+
when: { eventType: "click", eventKeyPrefix: "MENU_" },
|
|
173
|
+
handler: { type: "node_script", entry: fixturePath },
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
scriptRuntime: {
|
|
178
|
+
enabled: true,
|
|
179
|
+
allowPaths: [path.resolve("src/agent/test-fixtures")],
|
|
180
|
+
nodeCommand: process.execPath,
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const result = await routeAgentInboundEvent({
|
|
186
|
+
agent,
|
|
187
|
+
msgType: "event",
|
|
188
|
+
eventType: "click",
|
|
189
|
+
fromUser: "zhangsan",
|
|
190
|
+
msg: {
|
|
191
|
+
ToUserName: "corp-1",
|
|
192
|
+
FromUserName: "zhangsan",
|
|
193
|
+
MsgType: "event",
|
|
194
|
+
Event: "click",
|
|
195
|
+
EventKey: "MENU_HELP",
|
|
196
|
+
AgentID: 1001,
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
expect(result.handled).toBe(true);
|
|
201
|
+
expect(result.replyText).toBe("script:click:MENU_HELP:");
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("supports scripts that explicitly continue the default pipeline", async () => {
|
|
205
|
+
const fixturePath = path.resolve("src/agent/test-fixtures/reply-event-script.mjs");
|
|
206
|
+
const agent = createAgent({
|
|
207
|
+
config: {
|
|
208
|
+
corpId: "corp-1",
|
|
209
|
+
corpSecret: "secret",
|
|
210
|
+
agentId: 1001,
|
|
211
|
+
token: "token",
|
|
212
|
+
encodingAESKey: "aes",
|
|
213
|
+
eventRouting: {
|
|
214
|
+
routes: [
|
|
215
|
+
{
|
|
216
|
+
id: "script-click-pass",
|
|
217
|
+
when: { eventType: "click", eventKey: "PASS_TO_DEFAULT" },
|
|
218
|
+
handler: { type: "node_script", entry: fixturePath },
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
},
|
|
222
|
+
scriptRuntime: {
|
|
223
|
+
enabled: true,
|
|
224
|
+
allowPaths: [path.resolve("src/agent/test-fixtures")],
|
|
225
|
+
nodeCommand: process.execPath,
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const result = await routeAgentInboundEvent({
|
|
231
|
+
agent,
|
|
232
|
+
msgType: "event",
|
|
233
|
+
eventType: "click",
|
|
234
|
+
fromUser: "zhangsan",
|
|
235
|
+
msg: {
|
|
236
|
+
MsgType: "event",
|
|
237
|
+
Event: "click",
|
|
238
|
+
EventKey: "PASS_TO_DEFAULT",
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
expect(result.handled).toBe(true);
|
|
243
|
+
expect(result.chainToAgent).toBe(true);
|
|
244
|
+
expect(result.replyText).toBeUndefined();
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("passes full event params to python scripts", async () => {
|
|
248
|
+
const fixturePath = path.resolve("src/agent/test-fixtures/reply-event-script.py");
|
|
249
|
+
const agent = createAgent({
|
|
250
|
+
config: {
|
|
251
|
+
corpId: "corp-1",
|
|
252
|
+
corpSecret: "secret",
|
|
253
|
+
agentId: 1001,
|
|
254
|
+
token: "token",
|
|
255
|
+
encodingAESKey: "aes",
|
|
256
|
+
eventRouting: {
|
|
257
|
+
routes: [
|
|
258
|
+
{
|
|
259
|
+
id: "script-python-click",
|
|
260
|
+
when: { eventType: "click", eventKey: "MENU_PY" },
|
|
261
|
+
handler: { type: "python_script", entry: fixturePath },
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
},
|
|
265
|
+
scriptRuntime: {
|
|
266
|
+
enabled: true,
|
|
267
|
+
allowPaths: [path.resolve("src/agent/test-fixtures")],
|
|
268
|
+
pythonCommand: "python3",
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const result = await routeAgentInboundEvent({
|
|
274
|
+
agent,
|
|
275
|
+
msgType: "event",
|
|
276
|
+
eventType: "click",
|
|
277
|
+
fromUser: "zhangsan",
|
|
278
|
+
msg: {
|
|
279
|
+
MsgType: "event",
|
|
280
|
+
Event: "click",
|
|
281
|
+
EventKey: "MENU_PY",
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
expect(result.handled).toBe(true);
|
|
286
|
+
expect(result.replyText).toBe("python:click:MENU_PY:");
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("records audit events for successful script execution", async () => {
|
|
290
|
+
const fixturePath = path.resolve("src/agent/test-fixtures/reply-event-script.mjs");
|
|
291
|
+
const auditEvents: WecomRuntimeAuditEvent[] = [];
|
|
292
|
+
const agent = createAgent({
|
|
293
|
+
config: {
|
|
294
|
+
corpId: "corp-1",
|
|
295
|
+
corpSecret: "secret",
|
|
296
|
+
agentId: 1001,
|
|
297
|
+
token: "token",
|
|
298
|
+
encodingAESKey: "aes",
|
|
299
|
+
eventRouting: {
|
|
300
|
+
routes: [
|
|
301
|
+
{
|
|
302
|
+
id: "script-audit-success",
|
|
303
|
+
when: { eventType: "click", eventKey: "MENU_AUDIT" },
|
|
304
|
+
handler: { type: "node_script", entry: fixturePath },
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
},
|
|
308
|
+
scriptRuntime: {
|
|
309
|
+
enabled: true,
|
|
310
|
+
allowPaths: [path.resolve("src/agent/test-fixtures")],
|
|
311
|
+
nodeCommand: process.execPath,
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
await routeAgentInboundEvent({
|
|
317
|
+
agent,
|
|
318
|
+
msgType: "event",
|
|
319
|
+
eventType: "click",
|
|
320
|
+
fromUser: "zhangsan",
|
|
321
|
+
msg: {
|
|
322
|
+
MsgType: "event",
|
|
323
|
+
Event: "click",
|
|
324
|
+
EventKey: "MENU_AUDIT",
|
|
325
|
+
},
|
|
326
|
+
auditSink: (event) => auditEvents.push(event),
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
expect(auditEvents).toHaveLength(1);
|
|
330
|
+
expect(auditEvents[0]?.category).toBe("inbound");
|
|
331
|
+
expect(auditEvents[0]?.summary).toContain("event route script ok");
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it("captures invalid script output as a routed error and audits it", async () => {
|
|
335
|
+
const fixturePath = path.resolve("src/agent/test-fixtures/invalid-json-script.mjs");
|
|
336
|
+
const auditEvents: WecomRuntimeAuditEvent[] = [];
|
|
337
|
+
const agent = createAgent({
|
|
338
|
+
config: {
|
|
339
|
+
corpId: "corp-1",
|
|
340
|
+
corpSecret: "secret",
|
|
341
|
+
agentId: 1001,
|
|
342
|
+
token: "token",
|
|
343
|
+
encodingAESKey: "aes",
|
|
344
|
+
eventRouting: {
|
|
345
|
+
routes: [
|
|
346
|
+
{
|
|
347
|
+
id: "script-invalid-json",
|
|
348
|
+
when: { eventType: "click", eventKey: "MENU_BAD_JSON" },
|
|
349
|
+
handler: { type: "node_script", entry: fixturePath },
|
|
350
|
+
},
|
|
351
|
+
],
|
|
352
|
+
},
|
|
353
|
+
scriptRuntime: {
|
|
354
|
+
enabled: true,
|
|
355
|
+
allowPaths: [path.resolve("src/agent/test-fixtures")],
|
|
356
|
+
nodeCommand: process.execPath,
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const result = await routeAgentInboundEvent({
|
|
362
|
+
agent,
|
|
363
|
+
msgType: "event",
|
|
364
|
+
eventType: "click",
|
|
365
|
+
fromUser: "zhangsan",
|
|
366
|
+
msg: {
|
|
367
|
+
MsgType: "event",
|
|
368
|
+
Event: "click",
|
|
369
|
+
EventKey: "MENU_BAD_JSON",
|
|
370
|
+
},
|
|
371
|
+
auditSink: (event) => auditEvents.push(event),
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
expect(result.handled).toBe(true);
|
|
375
|
+
expect(result.chainToAgent).toBe(false);
|
|
376
|
+
expect(result.reason).toBe("script_node_script_error");
|
|
377
|
+
expect(result.error).toContain("not valid JSON");
|
|
378
|
+
expect(auditEvents).toHaveLength(1);
|
|
379
|
+
expect(auditEvents[0]?.category).toBe("runtime-error");
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it("treats invalid eventKeyPattern as non-match instead of throwing", async () => {
|
|
383
|
+
const auditEvents: WecomRuntimeAuditEvent[] = [];
|
|
384
|
+
const agent = createAgent({
|
|
385
|
+
config: {
|
|
386
|
+
corpId: "corp-1",
|
|
387
|
+
corpSecret: "secret",
|
|
388
|
+
agentId: 1001,
|
|
389
|
+
token: "token",
|
|
390
|
+
encodingAESKey: "aes",
|
|
391
|
+
eventRouting: {
|
|
392
|
+
unmatchedAction: "ignore",
|
|
393
|
+
routes: [
|
|
394
|
+
{
|
|
395
|
+
id: "invalid-pattern",
|
|
396
|
+
when: { eventType: "click", eventKeyPattern: "(*" },
|
|
397
|
+
handler: { type: "builtin", name: "echo" },
|
|
398
|
+
},
|
|
399
|
+
],
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
const result = await routeAgentInboundEvent({
|
|
405
|
+
agent,
|
|
406
|
+
msgType: "event",
|
|
407
|
+
eventType: "click",
|
|
408
|
+
fromUser: "zhangsan",
|
|
409
|
+
msg: { MsgType: "event", Event: "click", EventKey: "MENU_X" },
|
|
410
|
+
auditSink: (event) => auditEvents.push(event),
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
expect(result.handled).toBe(true);
|
|
414
|
+
expect(result.chainToAgent).toBe(false);
|
|
415
|
+
expect(result.reason).toBe("unmatched_event_ignored");
|
|
416
|
+
expect(auditEvents).toHaveLength(1);
|
|
417
|
+
expect(auditEvents[0]?.category).toBe("runtime-error");
|
|
418
|
+
expect(auditEvents[0]?.summary).toContain("invalid route eventKeyPattern");
|
|
419
|
+
expect(auditEvents[0]?.error).toContain("routeId=invalid-pattern");
|
|
420
|
+
});
|
|
421
|
+
});
|