@yanhaidao/wecom 2.3.180 → 2.3.260

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 (39) hide show
  1. package/.github/workflows/release.yml +23 -4
  2. package/README.md +87 -2
  3. package/SKILLS_DOC.md +272 -120
  4. package/changelog/v2.3.19.md +73 -0
  5. package/changelog/v2.3.26.md +21 -0
  6. package/package.json +2 -2
  7. package/src/agent/handler.ts +5 -3
  8. package/src/app/account-runtime.ts +5 -1
  9. package/src/app/index.ts +117 -0
  10. package/src/capability/bot/stream-orchestrator.ts +1 -1
  11. package/src/capability/doc/client.ts +228 -9
  12. package/src/capability/doc/tool.ts +14 -7
  13. package/src/channel.ts +1 -1
  14. package/src/config/index.ts +7 -1
  15. package/src/config/media.test.ts +113 -0
  16. package/src/config/media.ts +130 -5
  17. package/src/config/schema.ts +3 -0
  18. package/src/context-store.ts +264 -0
  19. package/src/onboarding.ts +1 -1
  20. package/src/outbound.test.ts +565 -5
  21. package/src/outbound.ts +94 -7
  22. package/src/runtime/dispatcher.ts +24 -5
  23. package/src/runtime/routing-bridge.test.ts +115 -0
  24. package/src/runtime/routing-bridge.ts +26 -1
  25. package/src/runtime/session-manager.test.ts +135 -0
  26. package/src/runtime/session-manager.ts +40 -8
  27. package/src/runtime/source-registry.ts +79 -0
  28. package/src/runtime.ts +3 -0
  29. package/src/target.ts +20 -8
  30. package/src/transport/bot-webhook/inbound-normalizer.ts +4 -4
  31. package/src/transport/bot-ws/media.test.ts +44 -0
  32. package/src/transport/bot-ws/media.ts +7 -4
  33. package/src/transport/bot-ws/reply.test.ts +131 -1
  34. package/src/transport/bot-ws/reply.ts +15 -3
  35. package/src/transport/bot-ws/sdk-adapter.ts +2 -1
  36. package/src/transport/http/registry.ts +1 -1
  37. package/src/types/config.ts +3 -0
  38. package/src/types/runtime.ts +1 -0
  39. package/src/wecom_msg_adapter/markdown_adapter.ts +331 -0
@@ -29,8 +29,23 @@ describe("wecomOutbound", () => {
29
29
 
30
30
  afterEach(async () => {
31
31
  const runtime = await import("./runtime.js");
32
+ const sourceRegistry = await import("./runtime/source-registry.js");
32
33
  runtime.unregisterBotWsPushHandle("default");
33
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");
34
49
  vi.unstubAllGlobals();
35
50
  });
36
51
 
@@ -132,10 +147,10 @@ describe("wecomOutbound", () => {
132
147
 
133
148
  (api.sendText as any).mockClear();
134
149
 
135
- // Test: Party ID (Numeric)
150
+ // Test: Numeric targets default to User ID
136
151
  await wecomOutbound.sendText({ cfg, to: "1001", text: "hi party" } as any);
137
152
  expect(api.sendText).toHaveBeenCalledWith(
138
- expect.objectContaining({ toUser: undefined, toParty: "1001" }),
153
+ expect.objectContaining({ toUser: "1001", toParty: undefined }),
139
154
  );
140
155
 
141
156
  (api.sendText as any).mockClear();
@@ -256,6 +271,136 @@ describe("wecomOutbound", () => {
256
271
  now.mockRestore();
257
272
  });
258
273
 
274
+ it("keeps agent-source sessions on the Agent text path even when ws is primary", async () => {
275
+ const { wecomOutbound } = await import("./outbound.js");
276
+ const runtime = await import("./runtime.js");
277
+ const api = await import("./transport/agent-api/core.js");
278
+ const sourceRegistry = await import("./runtime/source-registry.js");
279
+ const sendMarkdown = vi.fn().mockResolvedValue(undefined);
280
+ runtime.registerBotWsPushHandle(
281
+ "acct-ws",
282
+ createBotWsHandle({
283
+ sendMarkdown,
284
+ }),
285
+ );
286
+ sourceRegistry.registerWecomSourceSnapshot({
287
+ accountId: "acct-ws",
288
+ source: "agent-callback",
289
+ sessionKey: "agent:ops_bot:wecom:acct-ws:dm:lisi",
290
+ });
291
+ (api.sendText as any).mockResolvedValue(undefined);
292
+ (api.sendText as any).mockClear();
293
+
294
+ const cfg = {
295
+ channels: {
296
+ wecom: {
297
+ enabled: true,
298
+ defaultAccount: "acct-ws",
299
+ accounts: {
300
+ "acct-ws": {
301
+ enabled: true,
302
+ bot: {
303
+ primaryTransport: "ws",
304
+ ws: {
305
+ botId: "bot-1",
306
+ secret: "secret-1",
307
+ },
308
+ },
309
+ agent: {
310
+ corpId: "corp-ws",
311
+ corpSecret: "agent-secret",
312
+ agentId: 10001,
313
+ token: "token-ws",
314
+ encodingAESKey: "aes-ws",
315
+ },
316
+ },
317
+ },
318
+ },
319
+ },
320
+ };
321
+
322
+ await wecomOutbound.sendText({
323
+ cfg,
324
+ accountId: "acct-ws",
325
+ sessionKey: "agent:ops_bot:wecom:acct-ws:dm:lisi",
326
+ to: "user:lisi",
327
+ text: "hello agent",
328
+ } as any);
329
+
330
+ expect(sendMarkdown).not.toHaveBeenCalled();
331
+ expect(api.sendText).toHaveBeenCalledWith(
332
+ expect.objectContaining({
333
+ toUser: "lisi",
334
+ text: "hello agent",
335
+ }),
336
+ );
337
+ });
338
+
339
+ it("keeps agent-source peer targets on the Agent text path without sessionKey", async () => {
340
+ const { wecomOutbound } = await import("./outbound.js");
341
+ const runtime = await import("./runtime.js");
342
+ const api = await import("./transport/agent-api/core.js");
343
+ const sourceRegistry = await import("./runtime/source-registry.js");
344
+ const sendMarkdown = vi.fn().mockResolvedValue(undefined);
345
+ runtime.registerBotWsPushHandle(
346
+ "acct-ws",
347
+ createBotWsHandle({
348
+ sendMarkdown,
349
+ }),
350
+ );
351
+ sourceRegistry.registerWecomSourceSnapshot({
352
+ accountId: "acct-ws",
353
+ source: "agent-callback",
354
+ peerKind: "direct",
355
+ peerId: "lisi",
356
+ });
357
+ (api.sendText as any).mockResolvedValue(undefined);
358
+ (api.sendText as any).mockClear();
359
+
360
+ const cfg = {
361
+ channels: {
362
+ wecom: {
363
+ enabled: true,
364
+ defaultAccount: "acct-ws",
365
+ accounts: {
366
+ "acct-ws": {
367
+ enabled: true,
368
+ bot: {
369
+ primaryTransport: "ws",
370
+ ws: {
371
+ botId: "bot-1",
372
+ secret: "secret-1",
373
+ },
374
+ },
375
+ agent: {
376
+ corpId: "corp-ws",
377
+ corpSecret: "agent-secret",
378
+ agentId: 10001,
379
+ token: "token-ws",
380
+ encodingAESKey: "aes-ws",
381
+ },
382
+ },
383
+ },
384
+ },
385
+ },
386
+ };
387
+
388
+ await wecomOutbound.sendText({
389
+ cfg,
390
+ accountId: "acct-ws",
391
+ to: "user:lisi",
392
+ text: "hello peer",
393
+ } as any);
394
+
395
+ expect(sendMarkdown).not.toHaveBeenCalled();
396
+ expect(api.sendText).toHaveBeenCalledWith(
397
+ expect.objectContaining({
398
+ toUser: "lisi",
399
+ text: "hello peer",
400
+ }),
401
+ );
402
+ });
403
+
259
404
  it("does not silently fall back to Agent when Bot WS active push is configured but unavailable", async () => {
260
405
  const { wecomOutbound } = await import("./outbound.js");
261
406
  const api = await import("./transport/agent-api/core.js");
@@ -339,24 +484,101 @@ describe("wecomOutbound", () => {
339
484
 
340
485
  expect(sendMedia).toHaveBeenCalledWith({
341
486
  chatId: "zhangsan",
487
+ maxBytes: 80 * 1024 * 1024,
342
488
  mediaUrl: "https://example.com/media.png",
343
- mediaLocalRoots: undefined,
489
+ mediaLocalRoots: expect.any(Array),
344
490
  text: "caption",
345
491
  });
346
492
  expect(api.sendMedia).not.toHaveBeenCalled();
347
493
  });
348
494
 
349
- it("falls back to Agent media when Bot WS media delivery returns a non-fatal failure", async () => {
495
+ it("marks the active bot-ws reply handle when same-session text is sent via active push", async () => {
350
496
  const { wecomOutbound } = await import("./outbound.js");
351
497
  const runtime = await import("./runtime.js");
352
498
  const api = await import("./transport/agent-api/core.js");
353
- const sendMedia = vi.fn().mockResolvedValue({ ok: false, error: "upload failed" });
499
+ const sendMarkdown = vi.fn().mockResolvedValue(undefined);
500
+ const markExternalActivity = vi.fn();
501
+ runtime.registerBotWsPushHandle(
502
+ "acct-ws",
503
+ createBotWsHandle({
504
+ sendMarkdown,
505
+ }),
506
+ );
507
+ runtime.registerActiveBotWsReplyHandle({
508
+ accountId: "acct-ws",
509
+ sessionKey: "agent:ops_bot:wecom:acct-ws:dm:lisi",
510
+ peerKind: "direct",
511
+ peerId: "lisi",
512
+ handle: {
513
+ context: {
514
+ transport: "bot-ws",
515
+ accountId: "acct-ws",
516
+ raw: { transport: "bot-ws", envelopeType: "ws", body: {} },
517
+ },
518
+ deliver: vi.fn(),
519
+ markExternalActivity,
520
+ } as any,
521
+ });
522
+ (api.sendText as any).mockClear();
523
+
524
+ const cfg = {
525
+ channels: {
526
+ wecom: {
527
+ enabled: true,
528
+ defaultAccount: "acct-ws",
529
+ accounts: {
530
+ "acct-ws": {
531
+ enabled: true,
532
+ bot: {
533
+ primaryTransport: "ws",
534
+ ws: {
535
+ botId: "bot-1",
536
+ secret: "secret-1",
537
+ },
538
+ },
539
+ agent: {
540
+ corpId: "corp-ws",
541
+ corpSecret: "agent-secret",
542
+ agentId: 10001,
543
+ token: "token-ws",
544
+ encodingAESKey: "aes-ws",
545
+ },
546
+ },
547
+ },
548
+ },
549
+ },
550
+ };
551
+
552
+ await wecomOutbound.sendText({
553
+ cfg,
554
+ accountId: "acct-ws",
555
+ sessionKey: "agent:ops_bot:wecom:acct-ws:dm:lisi",
556
+ to: "user:lisi",
557
+ text: "hello ws",
558
+ } as any);
559
+
560
+ expect(sendMarkdown).toHaveBeenCalledWith("lisi", "hello ws");
561
+ expect(markExternalActivity).toHaveBeenCalledTimes(1);
562
+ expect(api.sendText).not.toHaveBeenCalled();
563
+ });
564
+
565
+ it("keeps agent-source sessions on the Agent media path even when ws is primary", async () => {
566
+ const { wecomOutbound } = await import("./outbound.js");
567
+ const runtime = await import("./runtime.js");
568
+ const api = await import("./transport/agent-api/core.js");
569
+ const sourceRegistry = await import("./runtime/source-registry.js");
570
+ const sendMedia = vi.fn().mockResolvedValue({ ok: true, messageId: "ws-media-1" });
354
571
  runtime.registerBotWsPushHandle(
355
572
  "default",
356
573
  createBotWsHandle({
357
574
  sendMedia,
358
575
  }),
359
576
  );
577
+ sourceRegistry.registerWecomSourceSnapshot({
578
+ accountId: "default",
579
+ source: "agent-callback",
580
+ sessionKey: "agent:ops_bot:wecom:default:dm:zhangsan",
581
+ });
360
582
  (api.uploadMedia as any).mockResolvedValue("media-1");
361
583
  (api.sendMedia as any).mockResolvedValue(undefined);
362
584
  (api.sendMedia as any).mockClear();
@@ -393,12 +615,350 @@ describe("wecomOutbound", () => {
393
615
 
394
616
  await wecomOutbound.sendMedia({
395
617
  cfg,
618
+ sessionKey: "agent:ops_bot:wecom:default:dm:zhangsan",
619
+ to: "user:zhangsan",
620
+ text: "caption",
621
+ mediaUrl: "https://example.com/media.png",
622
+ } as any);
623
+
624
+ expect(sendMedia).not.toHaveBeenCalled();
625
+ expect(api.sendMedia).toHaveBeenCalledTimes(1);
626
+ });
627
+
628
+ it("marks the active bot-ws reply handle when same-session media is sent via active push", async () => {
629
+ const { wecomOutbound } = await import("./outbound.js");
630
+ const runtime = await import("./runtime.js");
631
+ const sendMedia = vi.fn().mockResolvedValue({ ok: true, messageId: "ws-media-1" });
632
+ const markExternalActivity = vi.fn();
633
+ runtime.registerBotWsPushHandle(
634
+ "default",
635
+ createBotWsHandle({
636
+ sendMedia,
637
+ }),
638
+ );
639
+ runtime.registerActiveBotWsReplyHandle({
640
+ accountId: "default",
641
+ sessionKey: "agent:ops_bot:wecom:default:dm:zhangsan",
642
+ peerKind: "direct",
643
+ peerId: "zhangsan",
644
+ handle: {
645
+ context: {
646
+ transport: "bot-ws",
647
+ accountId: "default",
648
+ raw: { transport: "bot-ws", envelopeType: "ws", body: {} },
649
+ },
650
+ deliver: vi.fn(),
651
+ markExternalActivity,
652
+ } as any,
653
+ });
654
+
655
+ const cfg = {
656
+ channels: {
657
+ wecom: {
658
+ enabled: true,
659
+ bot: {
660
+ primaryTransport: "ws",
661
+ ws: {
662
+ botId: "bot-1",
663
+ secret: "secret-1",
664
+ },
665
+ },
666
+ agent: {
667
+ corpId: "corp",
668
+ corpSecret: "secret",
669
+ agentId: 1000002,
670
+ token: "token",
671
+ encodingAESKey: "aes",
672
+ },
673
+ },
674
+ },
675
+ };
676
+
677
+ await wecomOutbound.sendMedia({
678
+ cfg,
679
+ sessionKey: "agent:ops_bot:wecom:default:dm:zhangsan",
396
680
  to: "user:zhangsan",
397
681
  text: "caption",
398
682
  mediaUrl: "https://example.com/media.png",
399
683
  } as any);
400
684
 
401
685
  expect(sendMedia).toHaveBeenCalledTimes(1);
686
+ expect(markExternalActivity).toHaveBeenCalledTimes(1);
687
+ });
688
+
689
+ it("keeps agent-source peer targets on the Agent media path without sessionKey", async () => {
690
+ const { wecomOutbound } = await import("./outbound.js");
691
+ const runtime = await import("./runtime.js");
692
+ const api = await import("./transport/agent-api/core.js");
693
+ const sourceRegistry = await import("./runtime/source-registry.js");
694
+ const sendMedia = vi.fn().mockResolvedValue({ ok: true, messageId: "ws-media-1" });
695
+ runtime.registerBotWsPushHandle(
696
+ "default",
697
+ createBotWsHandle({
698
+ sendMedia,
699
+ }),
700
+ );
701
+ sourceRegistry.registerWecomSourceSnapshot({
702
+ accountId: "default",
703
+ source: "agent-callback",
704
+ peerKind: "direct",
705
+ peerId: "zhangsan",
706
+ });
707
+ (api.uploadMedia as any).mockResolvedValue("media-1");
708
+ (api.sendMedia as any).mockResolvedValue(undefined);
709
+ (api.sendMedia as any).mockClear();
710
+ vi.stubGlobal(
711
+ "fetch",
712
+ vi.fn().mockResolvedValue({
713
+ ok: true,
714
+ arrayBuffer: async () => new Uint8Array([1, 2, 3]).buffer,
715
+ headers: new Headers({ "content-type": "image/png" }),
716
+ }),
717
+ );
718
+
719
+ const cfg = {
720
+ channels: {
721
+ wecom: {
722
+ enabled: true,
723
+ bot: {
724
+ primaryTransport: "ws",
725
+ ws: {
726
+ botId: "bot-1",
727
+ secret: "secret-1",
728
+ },
729
+ },
730
+ agent: {
731
+ corpId: "corp",
732
+ corpSecret: "secret",
733
+ agentId: 1000002,
734
+ token: "token",
735
+ encodingAESKey: "aes",
736
+ },
737
+ },
738
+ },
739
+ };
740
+
741
+ await wecomOutbound.sendMedia({
742
+ cfg,
743
+ to: "user:zhangsan",
744
+ text: "caption",
745
+ mediaUrl: "https://example.com/media.png",
746
+ } as any);
747
+
748
+ expect(sendMedia).not.toHaveBeenCalled();
749
+ expect(api.sendMedia).toHaveBeenCalledTimes(1);
750
+ });
751
+
752
+ it("merges configured media local roots into Bot WS sends", async () => {
753
+ const { wecomOutbound } = await import("./outbound.js");
754
+ const runtime = await import("./runtime.js");
755
+ const sendMedia = vi.fn().mockResolvedValue({ ok: true, messageId: "ws-media-merged" });
756
+ runtime.registerBotWsPushHandle(
757
+ "default",
758
+ createBotWsHandle({
759
+ sendMedia,
760
+ }),
761
+ );
762
+
763
+ const cfg = {
764
+ channels: {
765
+ wecom: {
766
+ enabled: true,
767
+ bot: {
768
+ primaryTransport: "ws",
769
+ ws: {
770
+ botId: "bot-1",
771
+ secret: "secret-1",
772
+ },
773
+ },
774
+ media: {
775
+ localRoots: ["/tmp/downloads"],
776
+ },
777
+ },
778
+ },
779
+ };
780
+
781
+ await wecomOutbound.sendMedia({
782
+ cfg,
783
+ to: "user:zhangsan",
784
+ mediaUrl: "/tmp/workspace-agent/01.png",
785
+ mediaLocalRoots: ["/tmp/workspace-agent"],
786
+ } as any);
787
+
788
+ expect(sendMedia).toHaveBeenCalledWith(
789
+ expect.objectContaining({
790
+ chatId: "zhangsan",
791
+ mediaUrl: "/tmp/workspace-agent/01.png",
792
+ mediaLocalRoots: expect.arrayContaining(["/tmp/workspace-agent", "/tmp/downloads"]),
793
+ text: undefined,
794
+ }),
795
+ );
796
+ });
797
+
798
+ it("passes account-aware mediaMaxMb to Bot WS media sends", async () => {
799
+ const { wecomOutbound } = await import("./outbound.js");
800
+ const runtime = await import("./runtime.js");
801
+ const sendMedia = vi.fn().mockResolvedValue({ ok: true, messageId: "ws-media-limit" });
802
+ runtime.registerBotWsPushHandle(
803
+ "acct-ws",
804
+ createBotWsHandle({
805
+ sendMedia,
806
+ }),
807
+ );
808
+
809
+ const cfg = {
810
+ agents: {
811
+ defaults: {
812
+ mediaMaxMb: 12,
813
+ },
814
+ },
815
+ channels: {
816
+ wecom: {
817
+ enabled: true,
818
+ mediaMaxMb: 24,
819
+ accounts: {
820
+ "acct-ws": {
821
+ enabled: true,
822
+ mediaMaxMb: 36,
823
+ bot: {
824
+ primaryTransport: "ws",
825
+ ws: {
826
+ botId: "bot-1",
827
+ secret: "secret-1",
828
+ },
829
+ },
830
+ },
831
+ },
832
+ },
833
+ },
834
+ };
835
+
836
+ await wecomOutbound.sendMedia({
837
+ cfg,
838
+ accountId: "acct-ws",
839
+ to: "user:zhangsan",
840
+ mediaUrl: "https://example.com/media.png",
841
+ } as any);
842
+
843
+ expect(sendMedia).toHaveBeenCalledWith(
844
+ expect.objectContaining({
845
+ chatId: "zhangsan",
846
+ maxBytes: 36 * 1024 * 1024,
847
+ }),
848
+ );
849
+ });
850
+
851
+ it("does not fall back to Agent media when Bot WS conversation media delivery fails", async () => {
852
+ const { wecomOutbound } = await import("./outbound.js");
853
+ const runtime = await import("./runtime.js");
854
+ const api = await import("./transport/agent-api/core.js");
855
+ const sendMedia = vi.fn().mockResolvedValue({ ok: false, error: "upload failed" });
856
+ runtime.registerBotWsPushHandle(
857
+ "default",
858
+ createBotWsHandle({
859
+ sendMedia,
860
+ }),
861
+ );
862
+ (api.uploadMedia as any).mockResolvedValue("media-1");
863
+ (api.sendMedia as any).mockResolvedValue(undefined);
864
+ (api.sendMedia as any).mockClear();
865
+ vi.stubGlobal(
866
+ "fetch",
867
+ vi.fn().mockResolvedValue({
868
+ ok: true,
869
+ arrayBuffer: async () => new Uint8Array([1, 2, 3]).buffer,
870
+ headers: new Headers({ "content-type": "image/png" }),
871
+ }),
872
+ );
873
+
874
+ const cfg = {
875
+ channels: {
876
+ wecom: {
877
+ enabled: true,
878
+ bot: {
879
+ primaryTransport: "ws",
880
+ ws: {
881
+ botId: "bot-1",
882
+ secret: "secret-1",
883
+ },
884
+ },
885
+ agent: {
886
+ corpId: "corp",
887
+ corpSecret: "secret",
888
+ agentId: 1000002,
889
+ token: "token",
890
+ encodingAESKey: "aes",
891
+ },
892
+ },
893
+ },
894
+ };
895
+
896
+ await expect(
897
+ wecomOutbound.sendMedia({
898
+ cfg,
899
+ to: "user:zhangsan",
900
+ text: "caption",
901
+ mediaUrl: "https://example.com/media.png",
902
+ } as any),
903
+ ).rejects.toThrow(/Bot WS media delivery failed/i);
904
+
905
+ expect(sendMedia).toHaveBeenCalledTimes(1);
906
+ expect(api.sendMedia).not.toHaveBeenCalled();
907
+ });
908
+
909
+ it("keeps explicit agent targets on the Agent media path", async () => {
910
+ const { wecomOutbound } = await import("./outbound.js");
911
+ const runtime = await import("./runtime.js");
912
+ const api = await import("./transport/agent-api/core.js");
913
+ const sendMedia = vi.fn().mockResolvedValue({ ok: true, messageId: "ws-media-1" });
914
+ runtime.registerBotWsPushHandle(
915
+ "default",
916
+ createBotWsHandle({
917
+ sendMedia,
918
+ }),
919
+ );
920
+ (api.uploadMedia as any).mockResolvedValue("media-1");
921
+ (api.sendMedia as any).mockResolvedValue(undefined);
922
+ (api.sendMedia as any).mockClear();
923
+ vi.stubGlobal(
924
+ "fetch",
925
+ vi.fn().mockResolvedValue({
926
+ ok: true,
927
+ arrayBuffer: async () => new Uint8Array([1, 2, 3]).buffer,
928
+ headers: new Headers({ "content-type": "image/png" }),
929
+ }),
930
+ );
931
+
932
+ const cfg = {
933
+ channels: {
934
+ wecom: {
935
+ enabled: true,
936
+ bot: {
937
+ primaryTransport: "ws",
938
+ ws: {
939
+ botId: "bot-1",
940
+ secret: "secret-1",
941
+ },
942
+ },
943
+ agent: {
944
+ corpId: "corp",
945
+ corpSecret: "secret",
946
+ agentId: 1000002,
947
+ token: "token",
948
+ encodingAESKey: "aes",
949
+ },
950
+ },
951
+ },
952
+ };
953
+
954
+ await wecomOutbound.sendMedia({
955
+ cfg,
956
+ to: "wecom-agent:default:user:zhangsan",
957
+ text: "caption",
958
+ mediaUrl: "https://example.com/media.png",
959
+ } as any);
960
+
961
+ expect(sendMedia).not.toHaveBeenCalled();
402
962
  expect(api.sendMedia).toHaveBeenCalledTimes(1);
403
963
  });
404
964