@yanhaidao/wecom 2.4.160 → 2.5.110

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.
Files changed (313) hide show
  1. package/dist/index.js +68 -0
  2. package/dist/src/accounts.js +20 -0
  3. package/dist/src/agent/handler.js +895 -0
  4. package/dist/src/agent/index.js +5 -0
  5. package/dist/src/app/account-runtime.js +216 -0
  6. package/dist/src/app/bootstrap.js +19 -0
  7. package/dist/src/app/index.js +118 -0
  8. package/dist/src/capability/agent/delivery-service.js +63 -0
  9. package/dist/src/capability/agent/fallback-policy.js +6 -0
  10. package/dist/src/capability/agent/ingress-service.js +33 -0
  11. package/dist/src/capability/agent/upstream-delivery-service.js +71 -0
  12. package/dist/src/capability/bot/dispatch-config.js +45 -0
  13. package/dist/src/capability/bot/fallback-delivery.js +147 -0
  14. package/dist/src/capability/bot/local-path-delivery.js +178 -0
  15. package/dist/src/capability/bot/sandbox-media.js +138 -0
  16. package/dist/src/capability/bot/service.js +49 -0
  17. package/dist/src/capability/bot/stream-delivery.js +321 -0
  18. package/dist/src/capability/bot/stream-finalizer.js +81 -0
  19. package/dist/src/capability/bot/stream-orchestrator.js +318 -0
  20. package/dist/src/capability/bot/types.js +1 -0
  21. package/{src/capability/calendar/client.ts → dist/src/capability/calendar/client.js} +118 -241
  22. package/{src/capability/calendar/schema.ts → dist/src/capability/calendar/schema.js} +0 -38
  23. package/dist/src/capability/calendar/tool.js +365 -0
  24. package/dist/src/capability/calendar/types.js +12 -0
  25. package/{src/capability/doc/client.ts → dist/src/capability/doc/client.js} +370 -605
  26. package/{src/capability/doc/schema.ts → dist/src/capability/doc/schema.js} +345 -394
  27. package/dist/src/capability/doc/tool.js +1556 -0
  28. package/dist/src/capability/doc/types.js +113 -0
  29. package/dist/src/capability/mcp/index.js +3 -0
  30. package/dist/src/capability/mcp/schema.js +102 -0
  31. package/dist/src/capability/mcp/tool.js +146 -0
  32. package/dist/src/capability/mcp/transport.js +293 -0
  33. package/dist/src/channel.js +224 -0
  34. package/dist/src/config/accounts.js +236 -0
  35. package/dist/src/config/derived-paths.js +31 -0
  36. package/dist/src/config/index.js +7 -0
  37. package/dist/src/config/media.js +110 -0
  38. package/dist/src/config/network.js +32 -0
  39. package/dist/src/config/routing.js +20 -0
  40. package/dist/src/config/runtime-config.js +25 -0
  41. package/dist/src/config/schema.js +4 -0
  42. package/{src/config-schema.ts → dist/src/config-schema.js} +1 -1
  43. package/dist/src/context-store.js +219 -0
  44. package/{src/crypto/aes.ts → dist/src/crypto/aes.js} +11 -28
  45. package/dist/src/crypto/index.js +9 -0
  46. package/{src/crypto/signature.ts → dist/src/crypto/signature.js} +3 -18
  47. package/{src/crypto/xml.ts → dist/src/crypto/xml.js} +3 -11
  48. package/dist/src/crypto.js +145 -0
  49. package/dist/src/domain/models.js +1 -0
  50. package/dist/src/domain/policies.js +32 -0
  51. package/{src/dynamic-agent.ts → dist/src/dynamic-agent.js} +36 -73
  52. package/dist/src/gateway-monitor.js +139 -0
  53. package/dist/src/http.js +114 -0
  54. package/{src/media.ts → dist/src/media.js} +21 -40
  55. package/dist/src/monitor/limits.js +7 -0
  56. package/dist/src/monitor/state.js +28 -0
  57. package/dist/src/monitor.js +84 -0
  58. package/dist/src/observability/audit-log.js +30 -0
  59. package/dist/src/observability/legacy-operational-event-store.js +22 -0
  60. package/dist/src/observability/raw-envelope-log.js +24 -0
  61. package/dist/src/observability/status-registry.js +9 -0
  62. package/dist/src/observability/transport-session-view.js +14 -0
  63. package/dist/src/onboarding.js +546 -0
  64. package/dist/src/outbound.js +557 -0
  65. package/dist/src/runtime/dispatcher.js +57 -0
  66. package/{src/runtime/index.ts → dist/src/runtime/index.js} +0 -1
  67. package/dist/src/runtime/outbound-intent.js +1 -0
  68. package/dist/src/runtime/reply-orchestrator.js +38 -0
  69. package/dist/src/runtime/routing-bridge.js +26 -0
  70. package/dist/src/runtime/session-manager.js +112 -0
  71. package/dist/src/runtime/source-registry.js +174 -0
  72. package/dist/src/runtime.js +1 -0
  73. package/dist/src/shared/command-auth.js +57 -0
  74. package/{src/shared/index.ts → dist/src/shared/index.js} +0 -1
  75. package/dist/src/shared/media-asset.js +65 -0
  76. package/dist/src/shared/media-service.js +59 -0
  77. package/dist/src/shared/media-types.js +1 -0
  78. package/{src/shared/xml-parser.ts → dist/src/shared/xml-parser.js} +72 -63
  79. package/dist/src/store/active-reply-store.js +41 -0
  80. package/dist/src/store/interfaces.js +1 -0
  81. package/dist/src/store/memory-store.js +33 -0
  82. package/dist/src/store/stream-batch-store.js +319 -0
  83. package/{src/target.ts → dist/src/target.js} +15 -48
  84. package/dist/src/transport/agent-api/client.js +168 -0
  85. package/dist/src/transport/agent-api/core.js +337 -0
  86. package/dist/src/transport/agent-api/delivery.js +28 -0
  87. package/dist/src/transport/agent-api/media-upload.js +4 -0
  88. package/dist/src/transport/agent-api/reply.js +24 -0
  89. package/dist/src/transport/agent-api/upstream-delivery.js +30 -0
  90. package/dist/src/transport/agent-api/upstream-media-upload.js +46 -0
  91. package/dist/src/transport/agent-api/upstream-reply.js +26 -0
  92. package/dist/src/transport/agent-callback/http-handler.js +30 -0
  93. package/dist/src/transport/agent-callback/inbound.js +4 -0
  94. package/dist/src/transport/agent-callback/reply.js +8 -0
  95. package/dist/src/transport/agent-callback/request-handler.js +189 -0
  96. package/dist/src/transport/agent-callback/session.js +15 -0
  97. package/dist/src/transport/bot-webhook/active-reply.js +27 -0
  98. package/dist/src/transport/bot-webhook/http-handler.js +31 -0
  99. package/dist/src/transport/bot-webhook/inbound-normalizer.js +496 -0
  100. package/dist/src/transport/bot-webhook/inbound.js +4 -0
  101. package/dist/src/transport/bot-webhook/message-shape.js +98 -0
  102. package/dist/src/transport/bot-webhook/protocol.js +124 -0
  103. package/dist/src/transport/bot-webhook/reply.js +9 -0
  104. package/dist/src/transport/bot-webhook/request-handler.js +285 -0
  105. package/dist/src/transport/bot-webhook/session.js +15 -0
  106. package/dist/src/transport/bot-ws/inbound.js +147 -0
  107. package/dist/src/transport/bot-ws/media.js +236 -0
  108. package/dist/src/transport/bot-ws/reply.js +310 -0
  109. package/dist/src/transport/bot-ws/sdk-adapter.js +257 -0
  110. package/dist/src/transport/bot-ws/session.js +15 -0
  111. package/dist/src/transport/http/common.js +78 -0
  112. package/dist/src/transport/http/registry.js +71 -0
  113. package/dist/src/transport/http/request-handler.js +51 -0
  114. package/{src/transport/index.ts → dist/src/transport/index.js} +2 -10
  115. package/dist/src/types/account.js +1 -0
  116. package/dist/src/types/config.js +1 -0
  117. package/dist/src/types/constants.js +28 -0
  118. package/dist/src/types/events.js +1 -0
  119. package/dist/src/types/index.js +1 -0
  120. package/dist/src/types/legacy-stream.js +1 -0
  121. package/dist/src/types/message.js +5 -0
  122. package/dist/src/types/runtime-context.js +1 -0
  123. package/dist/src/types/runtime.js +1 -0
  124. package/dist/src/types.js +1 -0
  125. package/dist/src/upstream/index.js +111 -0
  126. package/dist/src/wecom_msg_adapter/markdown_adapter.js +280 -0
  127. package/openclaw.plugin.json +15 -0
  128. package/package.json +18 -1
  129. package/.github/workflows/release.yml +0 -143
  130. package/GOVERNANCE.md +0 -26
  131. package/SKILLS_CAL.md +0 -895
  132. package/SKILLS_DOC.md +0 -2288
  133. package/UPSTREAM_CONFIG.md +0 -170
  134. package/UPSTREAM_PLAN.md +0 -175
  135. package/assets/01.bot-add.png +0 -0
  136. package/assets/01.bot-setp2.png +0 -0
  137. package/assets/01.image.jpg +0 -0
  138. package/assets/02.agent.add.png +0 -0
  139. package/assets/02.agent.api-set.png +0 -0
  140. package/assets/02.image.jpg +0 -0
  141. package/assets/03.agent.page.png +0 -0
  142. package/assets/03.bot.page.png +0 -0
  143. package/assets/link-me.jpg +0 -0
  144. package/assets/register.png +0 -0
  145. package/changelog/v2.2.28.md +0 -70
  146. package/changelog/v2.3.10.md +0 -17
  147. package/changelog/v2.3.11.md +0 -19
  148. package/changelog/v2.3.12.md +0 -25
  149. package/changelog/v2.3.13.md +0 -19
  150. package/changelog/v2.3.14.md +0 -48
  151. package/changelog/v2.3.15.md +0 -15
  152. package/changelog/v2.3.16.md +0 -11
  153. package/changelog/v2.3.18.md +0 -22
  154. package/changelog/v2.3.19.md +0 -73
  155. package/changelog/v2.3.2.md +0 -28
  156. package/changelog/v2.3.26.md +0 -21
  157. package/changelog/v2.3.27.md +0 -33
  158. package/changelog/v2.3.4.md +0 -20
  159. package/changelog/v2.3.9.md +0 -22
  160. package/changelog/v2.4.12.md +0 -37
  161. package/changelog/v2.4.16.md +0 -19
  162. package/compat-single-account.md +0 -148
  163. package/index.test.ts +0 -38
  164. package/scripts/test-proxy.ts +0 -70
  165. package/src/accounts.ts +0 -34
  166. package/src/agent/api-client.upload.test.ts +0 -109
  167. package/src/agent/handler.event-filter.test.ts +0 -100
  168. package/src/agent/handler.ts +0 -1105
  169. package/src/agent/index.ts +0 -12
  170. package/src/app/account-runtime.ts +0 -276
  171. package/src/app/bootstrap.ts +0 -29
  172. package/src/app/index.ts +0 -192
  173. package/src/capability/agent/delivery-service.ts +0 -87
  174. package/src/capability/agent/fallback-policy.ts +0 -13
  175. package/src/capability/agent/ingress-service.ts +0 -38
  176. package/src/capability/agent/upstream-delivery-service.ts +0 -96
  177. package/src/capability/bot/dispatch-config.ts +0 -47
  178. package/src/capability/bot/fallback-delivery.ts +0 -178
  179. package/src/capability/bot/local-path-delivery.ts +0 -215
  180. package/src/capability/bot/sandbox-media.test.ts +0 -221
  181. package/src/capability/bot/sandbox-media.ts +0 -176
  182. package/src/capability/bot/service.ts +0 -56
  183. package/src/capability/bot/stream-delivery.ts +0 -379
  184. package/src/capability/bot/stream-finalizer.ts +0 -120
  185. package/src/capability/bot/stream-orchestrator.ts +0 -371
  186. package/src/capability/bot/types.ts +0 -8
  187. package/src/capability/calendar/SKILLS_CHECKLIST.md +0 -251
  188. package/src/capability/calendar/tool.ts +0 -417
  189. package/src/capability/calendar/types.ts +0 -309
  190. package/src/capability/doc/tool.ts +0 -1629
  191. package/src/capability/doc/types.ts +0 -792
  192. package/src/capability/mcp/index.ts +0 -10
  193. package/src/capability/mcp/schema.ts +0 -107
  194. package/src/capability/mcp/tool.ts +0 -174
  195. package/src/capability/mcp/transport.ts +0 -394
  196. package/src/channel.config.test.ts +0 -147
  197. package/src/channel.lifecycle.test.ts +0 -255
  198. package/src/channel.meta.test.ts +0 -26
  199. package/src/channel.ts +0 -256
  200. package/src/config/accounts.resolve.test.ts +0 -75
  201. package/src/config/accounts.ts +0 -296
  202. package/src/config/derived-paths.test.ts +0 -111
  203. package/src/config/derived-paths.ts +0 -41
  204. package/src/config/index.ts +0 -26
  205. package/src/config/media.test.ts +0 -113
  206. package/src/config/media.ts +0 -139
  207. package/src/config/network.ts +0 -53
  208. package/src/config/routing.test.ts +0 -88
  209. package/src/config/routing.ts +0 -26
  210. package/src/config/runtime-config.ts +0 -46
  211. package/src/config/schema.ts +0 -90
  212. package/src/context-store.ts +0 -297
  213. package/src/crypto/index.ts +0 -24
  214. package/src/crypto.test.ts +0 -32
  215. package/src/crypto.ts +0 -176
  216. package/src/domain/models.ts +0 -7
  217. package/src/domain/policies.ts +0 -36
  218. package/src/dynamic-agent.account-scope.test.ts +0 -17
  219. package/src/gateway-monitor.ts +0 -181
  220. package/src/http.ts +0 -145
  221. package/src/media.test.ts +0 -82
  222. package/src/monitor/limits.ts +0 -7
  223. package/src/monitor/state.queue.test.ts +0 -185
  224. package/src/monitor/state.ts +0 -34
  225. package/src/monitor.active.test.ts +0 -245
  226. package/src/monitor.inbound-filter.test.ts +0 -63
  227. package/src/monitor.integration.test.ts +0 -208
  228. package/src/monitor.ts +0 -121
  229. package/src/monitor.webhook.test.ts +0 -774
  230. package/src/observability/audit-log.ts +0 -48
  231. package/src/observability/legacy-operational-event-store.ts +0 -36
  232. package/src/observability/raw-envelope-log.ts +0 -28
  233. package/src/observability/status-registry.ts +0 -13
  234. package/src/observability/transport-session-view.ts +0 -14
  235. package/src/onboarding.test.ts +0 -336
  236. package/src/onboarding.ts +0 -704
  237. package/src/outbound.test.ts +0 -1271
  238. package/src/outbound.ts +0 -746
  239. package/src/runtime/dispatcher.ts +0 -71
  240. package/src/runtime/outbound-intent.ts +0 -4
  241. package/src/runtime/reply-orchestrator.test.ts +0 -71
  242. package/src/runtime/reply-orchestrator.ts +0 -67
  243. package/src/runtime/routing-bridge.test.ts +0 -115
  244. package/src/runtime/routing-bridge.ts +0 -44
  245. package/src/runtime/session-manager.test.ts +0 -174
  246. package/src/runtime/session-manager.ts +0 -139
  247. package/src/runtime/source-registry.ts +0 -249
  248. package/src/runtime.ts +0 -14
  249. package/src/shared/command-auth.ts +0 -87
  250. package/src/shared/media-asset.ts +0 -78
  251. package/src/shared/media-service.test.ts +0 -111
  252. package/src/shared/media-service.ts +0 -84
  253. package/src/shared/media-types.ts +0 -5
  254. package/src/shared/xml-parser.test.ts +0 -50
  255. package/src/store/active-reply-store.ts +0 -42
  256. package/src/store/interfaces.ts +0 -11
  257. package/src/store/memory-store.ts +0 -43
  258. package/src/store/stream-batch-store.ts +0 -350
  259. package/src/transport/agent-api/client.ts +0 -277
  260. package/src/transport/agent-api/core.ts +0 -463
  261. package/src/transport/agent-api/delivery.ts +0 -41
  262. package/src/transport/agent-api/media-upload.ts +0 -11
  263. package/src/transport/agent-api/reply.ts +0 -39
  264. package/src/transport/agent-api/upstream-delivery.ts +0 -45
  265. package/src/transport/agent-api/upstream-media-upload.ts +0 -70
  266. package/src/transport/agent-api/upstream-reply.ts +0 -43
  267. package/src/transport/agent-callback/http-handler.ts +0 -47
  268. package/src/transport/agent-callback/inbound.ts +0 -5
  269. package/src/transport/agent-callback/reply.ts +0 -13
  270. package/src/transport/agent-callback/request-handler.ts +0 -244
  271. package/src/transport/agent-callback/session.ts +0 -23
  272. package/src/transport/bot-webhook/active-reply.ts +0 -39
  273. package/src/transport/bot-webhook/http-handler.ts +0 -48
  274. package/src/transport/bot-webhook/inbound-normalizer.test.ts +0 -433
  275. package/src/transport/bot-webhook/inbound-normalizer.ts +0 -558
  276. package/src/transport/bot-webhook/inbound.ts +0 -5
  277. package/src/transport/bot-webhook/message-shape.ts +0 -92
  278. package/src/transport/bot-webhook/protocol.ts +0 -148
  279. package/src/transport/bot-webhook/reply.ts +0 -15
  280. package/src/transport/bot-webhook/request-handler.ts +0 -394
  281. package/src/transport/bot-webhook/session.ts +0 -23
  282. package/src/transport/bot-ws/inbound.test.ts +0 -290
  283. package/src/transport/bot-ws/inbound.ts +0 -163
  284. package/src/transport/bot-ws/media.test.ts +0 -44
  285. package/src/transport/bot-ws/media.ts +0 -321
  286. package/src/transport/bot-ws/reply.test.ts +0 -450
  287. package/src/transport/bot-ws/reply.ts +0 -365
  288. package/src/transport/bot-ws/sdk-adapter.test.ts +0 -187
  289. package/src/transport/bot-ws/sdk-adapter.ts +0 -314
  290. package/src/transport/bot-ws/session.ts +0 -28
  291. package/src/transport/http/common.ts +0 -109
  292. package/src/transport/http/registry.ts +0 -92
  293. package/src/transport/http/request-handler.ts +0 -84
  294. package/src/types/account.ts +0 -70
  295. package/src/types/config.ts +0 -114
  296. package/src/types/constants.ts +0 -31
  297. package/src/types/events.ts +0 -21
  298. package/src/types/global.d.ts +0 -9
  299. package/src/types/index.ts +0 -17
  300. package/src/types/legacy-stream.ts +0 -50
  301. package/src/types/message.ts +0 -189
  302. package/src/types/runtime-context.ts +0 -28
  303. package/src/types/runtime.ts +0 -165
  304. package/src/types.ts +0 -41
  305. package/src/upstream/index.ts +0 -150
  306. package/src/upstream.test.ts +0 -84
  307. package/src/wecom_msg_adapter/markdown_adapter.ts +0 -331
  308. package/tsconfig.json +0 -22
  309. package/vitest.config.ts +0 -26
  310. /package/{src/capability/agent/index.ts → dist/src/capability/agent/index.js} +0 -0
  311. /package/{src/capability/bot/index.ts → dist/src/capability/bot/index.js} +0 -0
  312. /package/{src/capability/calendar/index.ts → dist/src/capability/calendar/index.js} +0 -0
  313. /package/{src/capability/index.ts → dist/src/capability/index.js} +0 -0
@@ -1,1271 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
- import type { BotWsPushHandle } from "./app/index.js";
3
-
4
- vi.mock("./transport/agent-api/core.js", () => ({
5
- sendText: vi.fn(),
6
- sendMedia: vi.fn(),
7
- uploadMedia: vi.fn(),
8
- }));
9
-
10
- describe("wecomOutbound", () => {
11
- const createBotWsHandle = (overrides: Partial<BotWsPushHandle> = {}): BotWsPushHandle => ({
12
- isConnected: () => true,
13
- sendMarkdown: vi.fn().mockResolvedValue(undefined),
14
- replyCommand: vi.fn().mockResolvedValue({ errcode: 0 }),
15
- sendMedia: vi.fn().mockResolvedValue({ ok: true, messageId: "ws-media-1" }),
16
- ...overrides,
17
- });
18
-
19
- beforeEach(async () => {
20
- const runtime = await import("./runtime.js");
21
- runtime.setWecomRuntime({
22
- channel: {
23
- text: {
24
- chunkText: (text: string) => [text],
25
- },
26
- },
27
- } as any);
28
- });
29
-
30
- afterEach(async () => {
31
- const runtime = await import("./runtime.js");
32
- const sourceRegistry = await import("./runtime/source-registry.js");
33
- runtime.unregisterBotWsPushHandle("default");
34
- runtime.unregisterBotWsPushHandle("acct-ws");
35
- runtime.unregisterActiveBotWsReplyHandle({
36
- accountId: "default",
37
- sessionKey: "agent:ops_bot:wecom:default:dm:zhangsan",
38
- peerKind: "direct",
39
- peerId: "zhangsan",
40
- });
41
- runtime.unregisterActiveBotWsReplyHandle({
42
- accountId: "acct-ws",
43
- sessionKey: "agent:ops_bot:wecom:acct-ws:dm:lisi",
44
- peerKind: "direct",
45
- peerId: "lisi",
46
- });
47
- sourceRegistry.clearWecomSourceAccount("default");
48
- sourceRegistry.clearWecomSourceAccount("acct-ws");
49
- vi.unstubAllGlobals();
50
- });
51
-
52
- it("does not crash when called with core outbound params", async () => {
53
- const { wecomOutbound } = await import("./outbound.js");
54
- await expect(
55
- wecomOutbound.sendMedia({
56
- cfg: {},
57
- to: "wr-test-chat",
58
- text: "caption",
59
- mediaUrl: "https://example.com/media.png",
60
- } as any),
61
- ).rejects.toThrow(/requires Agent mode for account=default/i);
62
- });
63
-
64
- it("throws explicit error when outbound accountId does not exist", async () => {
65
- const { wecomOutbound } = await import("./outbound.js");
66
- const cfg = {
67
- channels: {
68
- wecom: {
69
- enabled: true,
70
- defaultAccount: "acct-a",
71
- accounts: {
72
- "acct-a": {
73
- enabled: true,
74
- agent: {
75
- corpId: "corp-a",
76
- corpSecret: "secret-a",
77
- agentId: 10001,
78
- token: "token-a",
79
- encodingAESKey: "aes-a",
80
- },
81
- },
82
- },
83
- },
84
- },
85
- };
86
- await expect(
87
- wecomOutbound.sendText({
88
- cfg,
89
- accountId: "acct-missing",
90
- to: "user:zhangsan",
91
- text: "hello",
92
- } as any),
93
- ).rejects.toThrow(/account "acct-missing" not found/i);
94
- });
95
-
96
- it("routes sendText to agent chatId/userid", async () => {
97
- const { wecomOutbound } = await import("./outbound.js");
98
- const api = await import("./transport/agent-api/core.js");
99
- const now = vi.spyOn(Date, "now").mockReturnValue(123);
100
- (api.sendText as any).mockResolvedValue(undefined);
101
-
102
- const cfg = {
103
- channels: {
104
- wecom: {
105
- enabled: true,
106
- agent: {
107
- corpId: "corp",
108
- corpSecret: "secret",
109
- agentId: 1000002,
110
- token: "token",
111
- encodingAESKey: "aes",
112
- },
113
- },
114
- },
115
- };
116
-
117
- // Chat ID (wr/wc) is intentionally NOT supported for Agent outbound.
118
- await expect(
119
- wecomOutbound.sendText({ cfg, to: "wr123", text: "hello" } as any),
120
- ).rejects.toThrow(/不支持向群 chatId 发送/);
121
- expect(api.sendText).not.toHaveBeenCalled();
122
-
123
- // Test: User ID (Default)
124
- const userResult = await wecomOutbound.sendText({
125
- cfg,
126
- to: "userid123",
127
- text: "hi",
128
- } as any);
129
- expect(api.sendText).toHaveBeenCalledWith(
130
- expect.objectContaining({
131
- chatId: undefined,
132
- toUser: "userid123",
133
- toParty: undefined,
134
- toTag: undefined,
135
- text: "hi",
136
- }),
137
- );
138
- expect(userResult.messageId).toBe("agent-123");
139
-
140
- (api.sendText as any).mockClear();
141
-
142
- // Test: User ID explicit
143
- await wecomOutbound.sendText({ cfg, to: "user:zhangsan", text: "hi" } as any);
144
- expect(api.sendText).toHaveBeenCalledWith(
145
- expect.objectContaining({ toUser: "zhangsan", toParty: undefined }),
146
- );
147
-
148
- (api.sendText as any).mockClear();
149
-
150
- // Test: Numeric targets default to User ID
151
- await wecomOutbound.sendText({ cfg, to: "1001", text: "hi party" } as any);
152
- expect(api.sendText).toHaveBeenCalledWith(
153
- expect.objectContaining({ toUser: "1001", toParty: undefined }),
154
- );
155
-
156
- (api.sendText as any).mockClear();
157
-
158
- // Test: Party ID Explicit
159
- await wecomOutbound.sendText({ cfg, to: "party:2002", text: "hi party 2" } as any);
160
- expect(api.sendText).toHaveBeenCalledWith(
161
- expect.objectContaining({ toUser: undefined, toParty: "2002" }),
162
- );
163
-
164
- (api.sendText as any).mockClear();
165
-
166
- // Test: Tag ID Explicit
167
- await wecomOutbound.sendText({ cfg, to: "tag:1", text: "hi tag" } as any);
168
- expect(api.sendText).toHaveBeenCalledWith(
169
- expect.objectContaining({ toUser: undefined, toTag: "1" }),
170
- );
171
-
172
- now.mockRestore();
173
- });
174
-
175
- it("suppresses /new ack for bot sessions but not agent sessions", async () => {
176
- const { wecomOutbound } = await import("./outbound.js");
177
- const api = await import("./transport/agent-api/core.js");
178
- const sourceRegistry = await import("./runtime/source-registry.js");
179
- const now = vi.spyOn(Date, "now").mockReturnValue(456);
180
- (api.sendText as any).mockResolvedValue(undefined);
181
- (api.sendText as any).mockClear();
182
-
183
- const cfg = {
184
- channels: {
185
- wecom: {
186
- enabled: true,
187
- agent: {
188
- corpId: "corp",
189
- corpSecret: "secret",
190
- agentId: 1000002,
191
- token: "token",
192
- encodingAESKey: "aes",
193
- },
194
- },
195
- },
196
- };
197
-
198
- const ack = "✅ New session started · model: openai-codex/gpt-5.2";
199
-
200
- // Bot 会话(wecom:...)应抑制,避免私信回执
201
- const r1 = await wecomOutbound.sendText({ cfg, to: "wecom:userid123", text: ack } as any);
202
- expect(api.sendText).not.toHaveBeenCalled();
203
- expect(r1.messageId).toBe("suppressed-456");
204
-
205
- (api.sendText as any).mockClear();
206
-
207
- sourceRegistry.registerWecomSourceSnapshot({
208
- accountId: "default",
209
- source: "agent-callback",
210
- sessionKey: "agent:ops_bot:wecom:default:dm:userid123",
211
- peerKind: "direct",
212
- peerId: "userid123",
213
- });
214
-
215
- // Agent 会话允许发送回执,即使 target 是普通 wecom:user:...
216
- await wecomOutbound.sendText({
217
- cfg,
218
- accountId: "default",
219
- sessionKey: "agent:ops_bot:wecom:default:dm:userid123",
220
- to: "wecom:user:userid123",
221
- text: ack,
222
- } as any);
223
- expect(api.sendText).toHaveBeenCalledWith(
224
- expect.objectContaining({
225
- toUser: "userid123",
226
- text: "✅ 已开启新会话(模型:openai-codex/gpt-5.2)",
227
- }),
228
- );
229
-
230
- now.mockRestore();
231
- });
232
-
233
- it("prefers Bot WS active push for text when ws is the active bot transport", async () => {
234
- const { wecomOutbound } = await import("./outbound.js");
235
- const runtime = await import("./runtime.js");
236
- const api = await import("./transport/agent-api/core.js");
237
- const sendMarkdown = vi.fn().mockResolvedValue(undefined);
238
- const now = vi.spyOn(Date, "now").mockReturnValue(789);
239
- runtime.registerBotWsPushHandle(
240
- "acct-ws",
241
- createBotWsHandle({
242
- sendMarkdown,
243
- }),
244
- );
245
- (api.sendText as any).mockClear();
246
-
247
- const cfg = {
248
- channels: {
249
- wecom: {
250
- enabled: true,
251
- defaultAccount: "acct-ws",
252
- accounts: {
253
- "acct-ws": {
254
- enabled: true,
255
- bot: {
256
- primaryTransport: "ws",
257
- ws: {
258
- botId: "bot-1",
259
- secret: "secret-1",
260
- },
261
- },
262
- agent: {
263
- corpId: "corp-ws",
264
- corpSecret: "agent-secret",
265
- agentId: 10001,
266
- token: "token-ws",
267
- encodingAESKey: "aes-ws",
268
- },
269
- },
270
- },
271
- },
272
- },
273
- };
274
-
275
- const result = await wecomOutbound.sendText({
276
- cfg,
277
- accountId: "acct-ws",
278
- to: "user:lisi",
279
- text: "hello ws",
280
- } as any);
281
-
282
- expect(sendMarkdown).toHaveBeenCalledWith("lisi", "hello ws");
283
- expect(api.sendText).not.toHaveBeenCalled();
284
- expect(result.messageId).toBe("bot-ws-789");
285
-
286
- now.mockRestore();
287
- });
288
-
289
- it("keeps agent-source sessions on the Agent text path even when ws is primary", async () => {
290
- const { wecomOutbound } = await import("./outbound.js");
291
- const runtime = await import("./runtime.js");
292
- const api = await import("./transport/agent-api/core.js");
293
- const sourceRegistry = await import("./runtime/source-registry.js");
294
- const sendMarkdown = vi.fn().mockResolvedValue(undefined);
295
- runtime.registerBotWsPushHandle(
296
- "acct-ws",
297
- createBotWsHandle({
298
- sendMarkdown,
299
- }),
300
- );
301
- sourceRegistry.registerWecomSourceSnapshot({
302
- accountId: "acct-ws",
303
- source: "agent-callback",
304
- sessionKey: "agent:ops_bot:wecom:acct-ws:dm:lisi",
305
- });
306
- (api.sendText as any).mockResolvedValue(undefined);
307
- (api.sendText as any).mockClear();
308
-
309
- const cfg = {
310
- channels: {
311
- wecom: {
312
- enabled: true,
313
- defaultAccount: "acct-ws",
314
- accounts: {
315
- "acct-ws": {
316
- enabled: true,
317
- bot: {
318
- primaryTransport: "ws",
319
- ws: {
320
- botId: "bot-1",
321
- secret: "secret-1",
322
- },
323
- },
324
- agent: {
325
- corpId: "corp-ws",
326
- corpSecret: "agent-secret",
327
- agentId: 10001,
328
- token: "token-ws",
329
- encodingAESKey: "aes-ws",
330
- },
331
- },
332
- },
333
- },
334
- },
335
- };
336
-
337
- await wecomOutbound.sendText({
338
- cfg,
339
- accountId: "acct-ws",
340
- sessionKey: "agent:ops_bot:wecom:acct-ws:dm:lisi",
341
- to: "user:lisi",
342
- text: "hello agent",
343
- } as any);
344
-
345
- expect(sendMarkdown).not.toHaveBeenCalled();
346
- expect(api.sendText).toHaveBeenCalledWith(
347
- expect.objectContaining({
348
- toUser: "lisi",
349
- text: "hello agent",
350
- }),
351
- );
352
- });
353
-
354
- it("keeps agent-source peer targets on the Agent text path without sessionKey", async () => {
355
- const { wecomOutbound } = await import("./outbound.js");
356
- const runtime = await import("./runtime.js");
357
- const api = await import("./transport/agent-api/core.js");
358
- const sourceRegistry = await import("./runtime/source-registry.js");
359
- const sendMarkdown = vi.fn().mockResolvedValue(undefined);
360
- runtime.registerBotWsPushHandle(
361
- "acct-ws",
362
- createBotWsHandle({
363
- sendMarkdown,
364
- }),
365
- );
366
- sourceRegistry.registerWecomSourceSnapshot({
367
- accountId: "acct-ws",
368
- source: "agent-callback",
369
- peerKind: "direct",
370
- peerId: "lisi",
371
- });
372
- (api.sendText as any).mockResolvedValue(undefined);
373
- (api.sendText as any).mockClear();
374
-
375
- const cfg = {
376
- channels: {
377
- wecom: {
378
- enabled: true,
379
- defaultAccount: "acct-ws",
380
- accounts: {
381
- "acct-ws": {
382
- enabled: true,
383
- bot: {
384
- primaryTransport: "ws",
385
- ws: {
386
- botId: "bot-1",
387
- secret: "secret-1",
388
- },
389
- },
390
- agent: {
391
- corpId: "corp-ws",
392
- corpSecret: "agent-secret",
393
- agentId: 10001,
394
- token: "token-ws",
395
- encodingAESKey: "aes-ws",
396
- },
397
- },
398
- },
399
- },
400
- },
401
- };
402
-
403
- await wecomOutbound.sendText({
404
- cfg,
405
- accountId: "acct-ws",
406
- to: "user:lisi",
407
- text: "hello peer",
408
- } as any);
409
-
410
- expect(sendMarkdown).not.toHaveBeenCalled();
411
- expect(api.sendText).toHaveBeenCalledWith(
412
- expect.objectContaining({
413
- toUser: "lisi",
414
- text: "hello peer",
415
- }),
416
- );
417
- });
418
-
419
- it("does not silently fall back to Agent when Bot WS active push is configured but unavailable", async () => {
420
- const { wecomOutbound } = await import("./outbound.js");
421
- const api = await import("./transport/agent-api/core.js");
422
- (api.sendText as any).mockClear();
423
-
424
- const cfg = {
425
- channels: {
426
- wecom: {
427
- enabled: true,
428
- bot: {
429
- primaryTransport: "ws",
430
- ws: {
431
- botId: "bot-1",
432
- secret: "secret-1",
433
- },
434
- },
435
- agent: {
436
- corpId: "corp",
437
- corpSecret: "secret",
438
- agentId: 1000002,
439
- token: "token",
440
- encodingAESKey: "aes",
441
- },
442
- },
443
- },
444
- };
445
-
446
- await expect(
447
- wecomOutbound.sendText({
448
- cfg,
449
- to: "user:zhangsan",
450
- text: "hello",
451
- } as any),
452
- ).rejects.toThrow(/no live ws runtime is registered/i);
453
- expect(api.sendText).not.toHaveBeenCalled();
454
- });
455
-
456
- it("prefers Bot WS for outbound media when ws is the active bot transport", async () => {
457
- const { wecomOutbound } = await import("./outbound.js");
458
- const runtime = await import("./runtime.js");
459
- const api = await import("./transport/agent-api/core.js");
460
- const sendMedia = vi.fn().mockResolvedValue({ ok: true, messageId: "ws-media-1" });
461
- runtime.registerBotWsPushHandle(
462
- "default",
463
- createBotWsHandle({
464
- sendMedia,
465
- }),
466
- );
467
- (api.uploadMedia as any).mockResolvedValue("media-1");
468
- (api.sendMedia as any).mockResolvedValue(undefined);
469
- (api.sendMedia as any).mockClear();
470
-
471
- const cfg = {
472
- channels: {
473
- wecom: {
474
- enabled: true,
475
- bot: {
476
- primaryTransport: "ws",
477
- ws: {
478
- botId: "bot-1",
479
- secret: "secret-1",
480
- },
481
- },
482
- agent: {
483
- corpId: "corp",
484
- corpSecret: "secret",
485
- agentId: 1000002,
486
- token: "token",
487
- encodingAESKey: "aes",
488
- },
489
- },
490
- },
491
- };
492
-
493
- await wecomOutbound.sendMedia({
494
- cfg,
495
- to: "user:zhangsan",
496
- text: "caption",
497
- mediaUrl: "https://example.com/media.png",
498
- } as any);
499
-
500
- expect(sendMedia).toHaveBeenCalledWith({
501
- chatId: "zhangsan",
502
- maxBytes: 80 * 1024 * 1024,
503
- mediaUrl: "https://example.com/media.png",
504
- mediaLocalRoots: expect.any(Array),
505
- text: "caption",
506
- });
507
- expect(api.sendMedia).not.toHaveBeenCalled();
508
- });
509
-
510
- it("marks the active bot-ws reply handle when same-session text is sent via active push", async () => {
511
- const { wecomOutbound } = await import("./outbound.js");
512
- const runtime = await import("./runtime.js");
513
- const api = await import("./transport/agent-api/core.js");
514
- const sendMarkdown = vi.fn().mockResolvedValue(undefined);
515
- const markExternalActivity = vi.fn();
516
- runtime.registerBotWsPushHandle(
517
- "acct-ws",
518
- createBotWsHandle({
519
- sendMarkdown,
520
- }),
521
- );
522
- runtime.registerActiveBotWsReplyHandle({
523
- accountId: "acct-ws",
524
- sessionKey: "agent:ops_bot:wecom:acct-ws:dm:lisi",
525
- peerKind: "direct",
526
- peerId: "lisi",
527
- handle: {
528
- context: {
529
- transport: "bot-ws",
530
- accountId: "acct-ws",
531
- raw: { transport: "bot-ws", envelopeType: "ws", body: {} },
532
- },
533
- deliver: vi.fn(),
534
- markExternalActivity,
535
- } as any,
536
- });
537
- (api.sendText as any).mockClear();
538
-
539
- const cfg = {
540
- channels: {
541
- wecom: {
542
- enabled: true,
543
- defaultAccount: "acct-ws",
544
- accounts: {
545
- "acct-ws": {
546
- enabled: true,
547
- bot: {
548
- primaryTransport: "ws",
549
- ws: {
550
- botId: "bot-1",
551
- secret: "secret-1",
552
- },
553
- },
554
- agent: {
555
- corpId: "corp-ws",
556
- corpSecret: "agent-secret",
557
- agentId: 10001,
558
- token: "token-ws",
559
- encodingAESKey: "aes-ws",
560
- },
561
- },
562
- },
563
- },
564
- },
565
- };
566
-
567
- await wecomOutbound.sendText({
568
- cfg,
569
- accountId: "acct-ws",
570
- sessionKey: "agent:ops_bot:wecom:acct-ws:dm:lisi",
571
- to: "user:lisi",
572
- text: "hello ws",
573
- } as any);
574
-
575
- expect(sendMarkdown).toHaveBeenCalledWith("lisi", "hello ws");
576
- expect(markExternalActivity).toHaveBeenCalledTimes(1);
577
- expect(api.sendText).not.toHaveBeenCalled();
578
- });
579
-
580
- it("keeps agent-source sessions on the Agent media path even when ws is primary", async () => {
581
- const { wecomOutbound } = await import("./outbound.js");
582
- const runtime = await import("./runtime.js");
583
- const api = await import("./transport/agent-api/core.js");
584
- const sourceRegistry = await import("./runtime/source-registry.js");
585
- const sendMedia = vi.fn().mockResolvedValue({ ok: true, messageId: "ws-media-1" });
586
- runtime.registerBotWsPushHandle(
587
- "default",
588
- createBotWsHandle({
589
- sendMedia,
590
- }),
591
- );
592
- sourceRegistry.registerWecomSourceSnapshot({
593
- accountId: "default",
594
- source: "agent-callback",
595
- sessionKey: "agent:ops_bot:wecom:default:dm:zhangsan",
596
- });
597
- (api.uploadMedia as any).mockResolvedValue("media-1");
598
- (api.sendMedia as any).mockResolvedValue(undefined);
599
- (api.sendMedia as any).mockClear();
600
- vi.stubGlobal(
601
- "fetch",
602
- vi.fn().mockResolvedValue({
603
- ok: true,
604
- arrayBuffer: async () => new Uint8Array([1, 2, 3]).buffer,
605
- headers: new Headers({ "content-type": "image/png" }),
606
- }),
607
- );
608
-
609
- const cfg = {
610
- channels: {
611
- wecom: {
612
- enabled: true,
613
- bot: {
614
- primaryTransport: "ws",
615
- ws: {
616
- botId: "bot-1",
617
- secret: "secret-1",
618
- },
619
- },
620
- agent: {
621
- corpId: "corp",
622
- corpSecret: "secret",
623
- agentId: 1000002,
624
- token: "token",
625
- encodingAESKey: "aes",
626
- },
627
- },
628
- },
629
- };
630
-
631
- await wecomOutbound.sendMedia({
632
- cfg,
633
- sessionKey: "agent:ops_bot:wecom:default:dm:zhangsan",
634
- to: "user:zhangsan",
635
- text: "caption",
636
- mediaUrl: "https://example.com/media.png",
637
- } as any);
638
-
639
- expect(sendMedia).not.toHaveBeenCalled();
640
- expect(api.sendMedia).toHaveBeenCalledTimes(1);
641
- });
642
-
643
- it("marks the active bot-ws reply handle when same-session media is sent via active push", async () => {
644
- const { wecomOutbound } = await import("./outbound.js");
645
- const runtime = await import("./runtime.js");
646
- const sendMedia = vi.fn().mockResolvedValue({ ok: true, messageId: "ws-media-1" });
647
- const markExternalActivity = vi.fn();
648
- runtime.registerBotWsPushHandle(
649
- "default",
650
- createBotWsHandle({
651
- sendMedia,
652
- }),
653
- );
654
- runtime.registerActiveBotWsReplyHandle({
655
- accountId: "default",
656
- sessionKey: "agent:ops_bot:wecom:default:dm:zhangsan",
657
- peerKind: "direct",
658
- peerId: "zhangsan",
659
- handle: {
660
- context: {
661
- transport: "bot-ws",
662
- accountId: "default",
663
- raw: { transport: "bot-ws", envelopeType: "ws", body: {} },
664
- },
665
- deliver: vi.fn(),
666
- markExternalActivity,
667
- } as any,
668
- });
669
-
670
- const cfg = {
671
- channels: {
672
- wecom: {
673
- enabled: true,
674
- bot: {
675
- primaryTransport: "ws",
676
- ws: {
677
- botId: "bot-1",
678
- secret: "secret-1",
679
- },
680
- },
681
- agent: {
682
- corpId: "corp",
683
- corpSecret: "secret",
684
- agentId: 1000002,
685
- token: "token",
686
- encodingAESKey: "aes",
687
- },
688
- },
689
- },
690
- };
691
-
692
- await wecomOutbound.sendMedia({
693
- cfg,
694
- sessionKey: "agent:ops_bot:wecom:default:dm:zhangsan",
695
- to: "user:zhangsan",
696
- text: "caption",
697
- mediaUrl: "https://example.com/media.png",
698
- } as any);
699
-
700
- expect(sendMedia).toHaveBeenCalledTimes(1);
701
- expect(markExternalActivity).toHaveBeenCalledTimes(1);
702
- });
703
-
704
- it("keeps agent-source peer targets on the Agent media path without sessionKey", async () => {
705
- const { wecomOutbound } = await import("./outbound.js");
706
- const runtime = await import("./runtime.js");
707
- const api = await import("./transport/agent-api/core.js");
708
- const sourceRegistry = await import("./runtime/source-registry.js");
709
- const sendMedia = vi.fn().mockResolvedValue({ ok: true, messageId: "ws-media-1" });
710
- runtime.registerBotWsPushHandle(
711
- "default",
712
- createBotWsHandle({
713
- sendMedia,
714
- }),
715
- );
716
- sourceRegistry.registerWecomSourceSnapshot({
717
- accountId: "default",
718
- source: "agent-callback",
719
- peerKind: "direct",
720
- peerId: "zhangsan",
721
- });
722
- (api.uploadMedia as any).mockResolvedValue("media-1");
723
- (api.sendMedia as any).mockResolvedValue(undefined);
724
- (api.sendMedia as any).mockClear();
725
- vi.stubGlobal(
726
- "fetch",
727
- vi.fn().mockResolvedValue({
728
- ok: true,
729
- arrayBuffer: async () => new Uint8Array([1, 2, 3]).buffer,
730
- headers: new Headers({ "content-type": "image/png" }),
731
- }),
732
- );
733
-
734
- const cfg = {
735
- channels: {
736
- wecom: {
737
- enabled: true,
738
- bot: {
739
- primaryTransport: "ws",
740
- ws: {
741
- botId: "bot-1",
742
- secret: "secret-1",
743
- },
744
- },
745
- agent: {
746
- corpId: "corp",
747
- corpSecret: "secret",
748
- agentId: 1000002,
749
- token: "token",
750
- encodingAESKey: "aes",
751
- },
752
- },
753
- },
754
- };
755
-
756
- await wecomOutbound.sendMedia({
757
- cfg,
758
- to: "user:zhangsan",
759
- text: "caption",
760
- mediaUrl: "https://example.com/media.png",
761
- } as any);
762
-
763
- expect(sendMedia).not.toHaveBeenCalled();
764
- expect(api.sendMedia).toHaveBeenCalledTimes(1);
765
- });
766
-
767
- it("merges configured media local roots into Bot WS sends", async () => {
768
- const { wecomOutbound } = await import("./outbound.js");
769
- const runtime = await import("./runtime.js");
770
- const sendMedia = vi.fn().mockResolvedValue({ ok: true, messageId: "ws-media-merged" });
771
- runtime.registerBotWsPushHandle(
772
- "default",
773
- createBotWsHandle({
774
- sendMedia,
775
- }),
776
- );
777
-
778
- const cfg = {
779
- channels: {
780
- wecom: {
781
- enabled: true,
782
- bot: {
783
- primaryTransport: "ws",
784
- ws: {
785
- botId: "bot-1",
786
- secret: "secret-1",
787
- },
788
- },
789
- media: {
790
- localRoots: ["/tmp/downloads"],
791
- },
792
- },
793
- },
794
- };
795
-
796
- await wecomOutbound.sendMedia({
797
- cfg,
798
- to: "user:zhangsan",
799
- mediaUrl: "/tmp/workspace-agent/01.png",
800
- mediaLocalRoots: ["/tmp/workspace-agent"],
801
- } as any);
802
-
803
- expect(sendMedia).toHaveBeenCalledWith(
804
- expect.objectContaining({
805
- chatId: "zhangsan",
806
- mediaUrl: "/tmp/workspace-agent/01.png",
807
- mediaLocalRoots: expect.arrayContaining(["/tmp/workspace-agent", "/tmp/downloads"]),
808
- text: undefined,
809
- }),
810
- );
811
- });
812
-
813
- it("passes account-aware mediaMaxMb to Bot WS media sends", async () => {
814
- const { wecomOutbound } = await import("./outbound.js");
815
- const runtime = await import("./runtime.js");
816
- const sendMedia = vi.fn().mockResolvedValue({ ok: true, messageId: "ws-media-limit" });
817
- runtime.registerBotWsPushHandle(
818
- "acct-ws",
819
- createBotWsHandle({
820
- sendMedia,
821
- }),
822
- );
823
-
824
- const cfg = {
825
- agents: {
826
- defaults: {
827
- mediaMaxMb: 12,
828
- },
829
- },
830
- channels: {
831
- wecom: {
832
- enabled: true,
833
- mediaMaxMb: 24,
834
- accounts: {
835
- "acct-ws": {
836
- enabled: true,
837
- mediaMaxMb: 36,
838
- bot: {
839
- primaryTransport: "ws",
840
- ws: {
841
- botId: "bot-1",
842
- secret: "secret-1",
843
- },
844
- },
845
- },
846
- },
847
- },
848
- },
849
- };
850
-
851
- await wecomOutbound.sendMedia({
852
- cfg,
853
- accountId: "acct-ws",
854
- to: "user:zhangsan",
855
- mediaUrl: "https://example.com/media.png",
856
- } as any);
857
-
858
- expect(sendMedia).toHaveBeenCalledWith(
859
- expect.objectContaining({
860
- chatId: "zhangsan",
861
- maxBytes: 36 * 1024 * 1024,
862
- }),
863
- );
864
- });
865
-
866
- it("does not fall back to Agent media when Bot WS conversation media delivery fails", async () => {
867
- const { wecomOutbound } = await import("./outbound.js");
868
- const runtime = await import("./runtime.js");
869
- const api = await import("./transport/agent-api/core.js");
870
- const sendMedia = vi.fn().mockResolvedValue({ ok: false, error: "upload failed" });
871
- runtime.registerBotWsPushHandle(
872
- "default",
873
- createBotWsHandle({
874
- sendMedia,
875
- }),
876
- );
877
- (api.uploadMedia as any).mockResolvedValue("media-1");
878
- (api.sendMedia as any).mockResolvedValue(undefined);
879
- (api.sendMedia as any).mockClear();
880
- vi.stubGlobal(
881
- "fetch",
882
- vi.fn().mockResolvedValue({
883
- ok: true,
884
- arrayBuffer: async () => new Uint8Array([1, 2, 3]).buffer,
885
- headers: new Headers({ "content-type": "image/png" }),
886
- }),
887
- );
888
-
889
- const cfg = {
890
- channels: {
891
- wecom: {
892
- enabled: true,
893
- bot: {
894
- primaryTransport: "ws",
895
- ws: {
896
- botId: "bot-1",
897
- secret: "secret-1",
898
- },
899
- },
900
- agent: {
901
- corpId: "corp",
902
- corpSecret: "secret",
903
- agentId: 1000002,
904
- token: "token",
905
- encodingAESKey: "aes",
906
- },
907
- },
908
- },
909
- };
910
-
911
- await expect(
912
- wecomOutbound.sendMedia({
913
- cfg,
914
- to: "user:zhangsan",
915
- text: "caption",
916
- mediaUrl: "https://example.com/media.png",
917
- } as any),
918
- ).rejects.toThrow(/Bot WS media delivery failed/i);
919
-
920
- expect(sendMedia).toHaveBeenCalledTimes(1);
921
- expect(api.sendMedia).not.toHaveBeenCalled();
922
- });
923
-
924
- it("keeps explicit agent targets on the Agent media path", async () => {
925
- const { wecomOutbound } = await import("./outbound.js");
926
- const runtime = await import("./runtime.js");
927
- const api = await import("./transport/agent-api/core.js");
928
- const sendMedia = vi.fn().mockResolvedValue({ ok: true, messageId: "ws-media-1" });
929
- runtime.registerBotWsPushHandle(
930
- "default",
931
- createBotWsHandle({
932
- sendMedia,
933
- }),
934
- );
935
- (api.uploadMedia as any).mockResolvedValue("media-1");
936
- (api.sendMedia as any).mockResolvedValue(undefined);
937
- (api.sendMedia as any).mockClear();
938
- vi.stubGlobal(
939
- "fetch",
940
- vi.fn().mockResolvedValue({
941
- ok: true,
942
- arrayBuffer: async () => new Uint8Array([1, 2, 3]).buffer,
943
- headers: new Headers({ "content-type": "image/png" }),
944
- }),
945
- );
946
-
947
- const cfg = {
948
- channels: {
949
- wecom: {
950
- enabled: true,
951
- bot: {
952
- primaryTransport: "ws",
953
- ws: {
954
- botId: "bot-1",
955
- secret: "secret-1",
956
- },
957
- },
958
- agent: {
959
- corpId: "corp",
960
- corpSecret: "secret",
961
- agentId: 1000002,
962
- token: "token",
963
- encodingAESKey: "aes",
964
- },
965
- },
966
- },
967
- };
968
-
969
- await wecomOutbound.sendMedia({
970
- cfg,
971
- to: "wecom-agent:default:user:zhangsan",
972
- text: "caption",
973
- mediaUrl: "https://example.com/media.png",
974
- } as any);
975
-
976
- expect(sendMedia).not.toHaveBeenCalled();
977
- expect(api.sendMedia).toHaveBeenCalledTimes(1);
978
- });
979
-
980
- it("routes explicit upstream agent text targets to the upstream delivery path", async () => {
981
- const { wecomOutbound } = await import("./outbound.js");
982
- const api = await import("./transport/agent-api/core.js");
983
- const client = await import("./transport/agent-api/client.js");
984
- const upstreamSpy = vi.spyOn(client, "sendUpstreamAgentApiText").mockResolvedValue(undefined);
985
- (api.sendText as any).mockClear();
986
-
987
- const cfg = {
988
- channels: {
989
- wecom: {
990
- enabled: true,
991
- agent: {
992
- corpId: "corp-main",
993
- corpSecret: "secret-main",
994
- agentId: 1000002,
995
- token: "token-main",
996
- encodingAESKey: "aes-main",
997
- upstreamCorps: {
998
- partner: {
999
- corpId: "corp-up",
1000
- agentId: 2000001,
1001
- },
1002
- },
1003
- },
1004
- },
1005
- },
1006
- };
1007
-
1008
- await wecomOutbound.sendText({
1009
- cfg,
1010
- to: "wecom-agent-upstream:default:corp-up:zhangsan",
1011
- text: "hello upstream",
1012
- } as any);
1013
-
1014
- expect(upstreamSpy).toHaveBeenCalledWith(
1015
- expect.objectContaining({
1016
- toUser: "zhangsan",
1017
- text: "hello upstream",
1018
- upstreamAgent: expect.objectContaining({
1019
- corpId: "corp-up",
1020
- agentId: 2000001,
1021
- }),
1022
- primaryAgent: expect.objectContaining({
1023
- corpId: "corp-main",
1024
- agentId: 1000002,
1025
- }),
1026
- }),
1027
- );
1028
- expect(api.sendText).not.toHaveBeenCalled();
1029
-
1030
- upstreamSpy.mockRestore();
1031
- });
1032
-
1033
- it("routes plain agent targets to upstream delivery when session source snapshot carries upstream corp", async () => {
1034
- const { wecomOutbound } = await import("./outbound.js");
1035
- const api = await import("./transport/agent-api/core.js");
1036
- const client = await import("./transport/agent-api/client.js");
1037
- const sourceRegistry = await import("./runtime/source-registry.js");
1038
- const upstreamSpy = vi.spyOn(client, "sendUpstreamAgentApiText").mockResolvedValue(undefined);
1039
- (api.sendText as any).mockClear();
1040
-
1041
- sourceRegistry.registerWecomSourceSnapshot({
1042
- accountId: "default",
1043
- source: "agent-callback",
1044
- sessionKey: "agent:test-agent-blue:wecom:blue:direct:zhangsan",
1045
- peerKind: "direct",
1046
- peerId: "zhangsan",
1047
- upstreamCorpId: "corp-up",
1048
- });
1049
-
1050
- const cfg = {
1051
- channels: {
1052
- wecom: {
1053
- enabled: true,
1054
- agent: {
1055
- corpId: "corp-main",
1056
- corpSecret: "secret-main",
1057
- agentId: 1000002,
1058
- token: "token-main",
1059
- encodingAESKey: "aes-main",
1060
- upstreamCorps: {
1061
- partner: {
1062
- corpId: "corp-up",
1063
- agentId: 2000001,
1064
- },
1065
- },
1066
- },
1067
- },
1068
- },
1069
- };
1070
-
1071
- await wecomOutbound.sendText({
1072
- cfg,
1073
- accountId: "default",
1074
- sessionKey: "agent:test-agent-blue:wecom:blue:direct:zhangsan",
1075
- to: "wecom-agent:default:user:zhangsan",
1076
- text: "hello upstream by snapshot",
1077
- } as any);
1078
-
1079
- expect(upstreamSpy).toHaveBeenCalledWith(
1080
- expect.objectContaining({
1081
- toUser: "zhangsan",
1082
- text: "hello upstream by snapshot",
1083
- upstreamAgent: expect.objectContaining({
1084
- corpId: "corp-up",
1085
- agentId: 2000001,
1086
- }),
1087
- }),
1088
- );
1089
- expect(api.sendText).not.toHaveBeenCalled();
1090
-
1091
- upstreamSpy.mockRestore();
1092
- });
1093
-
1094
- it("routes plain agent media targets to upstream delivery when peer context carries upstream corp", async () => {
1095
- const { wecomOutbound } = await import("./outbound.js");
1096
- const api = await import("./transport/agent-api/core.js");
1097
- const client = await import("./transport/agent-api/client.js");
1098
- const upstreamUpload = await import("./transport/agent-api/upstream-media-upload.js");
1099
- const contextStore = await import("./context-store.js");
1100
- const upstreamSendSpy = vi.spyOn(client, "sendUpstreamAgentApiMedia").mockResolvedValue(undefined);
1101
- const upstreamUploadSpy = vi
1102
- .spyOn(upstreamUpload, "uploadUpstreamAgentApiMedia")
1103
- .mockResolvedValue("media-up-1");
1104
- (api.sendMedia as any).mockClear();
1105
- vi.stubGlobal(
1106
- "fetch",
1107
- vi.fn().mockResolvedValue({
1108
- ok: true,
1109
- arrayBuffer: async () => new Uint8Array([1, 2, 3]).buffer,
1110
- headers: new Headers({ "content-type": "text/markdown" }),
1111
- }),
1112
- );
1113
-
1114
- contextStore.setPeerContext("default", "zhangsan", {
1115
- peerKind: "direct",
1116
- upstreamCorpId: "corp-up",
1117
- });
1118
-
1119
- const cfg = {
1120
- channels: {
1121
- wecom: {
1122
- enabled: true,
1123
- agent: {
1124
- corpId: "corp-main",
1125
- corpSecret: "secret-main",
1126
- agentId: 1000002,
1127
- token: "token-main",
1128
- encodingAESKey: "aes-main",
1129
- upstreamCorps: {
1130
- partner: {
1131
- corpId: "corp-up",
1132
- agentId: 2000001,
1133
- },
1134
- },
1135
- },
1136
- },
1137
- },
1138
- };
1139
-
1140
- await wecomOutbound.sendMedia({
1141
- cfg,
1142
- accountId: "default",
1143
- to: "wecom-agent:default:user:zhangsan",
1144
- text: "caption",
1145
- mediaUrl: "https://example.com/file.md",
1146
- } as any);
1147
-
1148
- expect(upstreamUploadSpy).toHaveBeenCalledWith(
1149
- expect.objectContaining({
1150
- upstreamAgent: expect.objectContaining({
1151
- corpId: "corp-up",
1152
- agentId: 2000001,
1153
- }),
1154
- filename: "file.md",
1155
- }),
1156
- );
1157
- expect(upstreamSendSpy).toHaveBeenCalledWith(
1158
- expect.objectContaining({
1159
- toUser: "zhangsan",
1160
- mediaId: "media-up-1",
1161
- mediaType: "file",
1162
- upstreamAgent: expect.objectContaining({
1163
- corpId: "corp-up",
1164
- agentId: 2000001,
1165
- }),
1166
- }),
1167
- );
1168
- expect(api.sendMedia).not.toHaveBeenCalled();
1169
-
1170
- upstreamSendSpy.mockRestore();
1171
- upstreamUploadSpy.mockRestore();
1172
- });
1173
-
1174
- it("uses account-scoped agent config in matrix mode", async () => {
1175
- const { wecomOutbound } = await import("./outbound.js");
1176
- const api = await import("./transport/agent-api/core.js");
1177
- (api.sendText as any).mockResolvedValue(undefined);
1178
- (api.sendText as any).mockClear();
1179
-
1180
- const cfg = {
1181
- channels: {
1182
- wecom: {
1183
- enabled: true,
1184
- defaultAccount: "acct-a",
1185
- accounts: {
1186
- "acct-a": {
1187
- enabled: true,
1188
- agent: {
1189
- corpId: "corp-a",
1190
- corpSecret: "secret-a",
1191
- agentId: 10001,
1192
- token: "token-a",
1193
- encodingAESKey: "aes-a",
1194
- },
1195
- },
1196
- "acct-b": {
1197
- enabled: true,
1198
- agent: {
1199
- corpId: "corp-b",
1200
- corpSecret: "secret-b",
1201
- agentId: 10002,
1202
- token: "token-b",
1203
- encodingAESKey: "aes-b",
1204
- },
1205
- },
1206
- },
1207
- },
1208
- },
1209
- };
1210
-
1211
- await wecomOutbound.sendText({
1212
- cfg,
1213
- accountId: "acct-b",
1214
- to: "user:lisi",
1215
- text: "hello b",
1216
- } as any);
1217
- expect(api.sendText).toHaveBeenCalledWith(
1218
- expect.objectContaining({
1219
- toUser: "lisi",
1220
- agent: expect.objectContaining({
1221
- accountId: "acct-b",
1222
- agentId: 10002,
1223
- corpId: "corp-b",
1224
- }),
1225
- }),
1226
- );
1227
- });
1228
-
1229
- it("rejects outbound when target account has matrix conflict", async () => {
1230
- const { wecomOutbound } = await import("./outbound.js");
1231
- const cfg = {
1232
- channels: {
1233
- wecom: {
1234
- enabled: true,
1235
- defaultAccount: "acct-a",
1236
- accounts: {
1237
- "acct-a": {
1238
- enabled: true,
1239
- agent: {
1240
- corpId: "corp-shared",
1241
- corpSecret: "secret-a",
1242
- agentId: 10001,
1243
- token: "token-a",
1244
- encodingAESKey: "aes-a",
1245
- },
1246
- },
1247
- "acct-b": {
1248
- enabled: true,
1249
- agent: {
1250
- corpId: "corp-shared",
1251
- corpSecret: "secret-b",
1252
- agentId: 10001,
1253
- token: "token-b",
1254
- encodingAESKey: "aes-b",
1255
- },
1256
- },
1257
- },
1258
- },
1259
- },
1260
- };
1261
-
1262
- await expect(
1263
- wecomOutbound.sendText({
1264
- cfg,
1265
- accountId: "acct-b",
1266
- to: "user:lisi",
1267
- text: "hello",
1268
- } as any),
1269
- ).rejects.toThrow(/duplicate wecom agent identity/i);
1270
- });
1271
- });