@kafca/agentdock 0.1.21 → 0.1.23

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 (81) hide show
  1. package/README.md +9 -0
  2. package/dist/renderer/assets/{Badge-BRmQIqbd.js → Badge-C2K5apfV.js} +1 -1
  3. package/dist/renderer/assets/{Chat-D94ODABB.js → Chat-DI2YeVKb.js} +1 -1
  4. package/dist/renderer/assets/{ChatMarkdown-DqW7OUvV.js → ChatMarkdown-DMAr15Oi.js} +1 -1
  5. package/dist/renderer/assets/{Config-9lgzpTCp.js → Config-7rP3EuUf.js} +2 -2
  6. package/dist/renderer/assets/CronList-81QslYE8.js +1 -0
  7. package/dist/renderer/assets/{Dashboard-COISXbQy.js → Dashboard-Db2T7rSZ.js} +2 -2
  8. package/dist/renderer/assets/{EmptyState-w5-I9zg7.js → EmptyState-BBJ3cTvB.js} +1 -1
  9. package/dist/renderer/assets/{KnowledgeDetail-DVxwrTLw.js → KnowledgeDetail-Dz_6flG3.js} +2 -2
  10. package/dist/renderer/assets/{KnowledgeHome-BKkubcwi.js → KnowledgeHome-DIh046mN.js} +3 -3
  11. package/dist/renderer/assets/Logs-BXBtHJLf.js +1 -0
  12. package/dist/renderer/assets/{Modal-Ceu_MP2e.js → Modal-BB2Av_Xs.js} +1 -1
  13. package/dist/renderer/assets/{Page-CpSczAz8.js → Page-DPSdvMgU.js} +1 -1
  14. package/dist/renderer/assets/{ProjectDetail-FJ_bNXLF.js → ProjectDetail-C6EWGvGg.js} +1 -1
  15. package/dist/renderer/assets/{ProjectList-DZKL4K9C.js → ProjectList-BBo5ygnS.js} +1 -1
  16. package/dist/renderer/assets/{Select-BHQk6qCU.js → Select-CNX-Tx_O.js} +2 -7
  17. package/dist/renderer/assets/SessionChat-BIn46jqZ.js +1 -0
  18. package/dist/renderer/assets/SessionList-CZt6f1Rd.js +1 -0
  19. package/dist/renderer/assets/{ThreadChat-D3s4GXGh.js → ThreadChat-BotD0gKQ.js} +7 -7
  20. package/dist/renderer/assets/Workspace-C_4tdpjX.js +14 -0
  21. package/dist/renderer/assets/{arrow-left-Dtal-eOi.js → arrow-left-cHr5wkpq.js} +1 -1
  22. package/dist/renderer/assets/{book-open-q814R_Ov.js → book-open-4nx5C8T-.js} +1 -1
  23. package/dist/renderer/assets/{check-DjfCdLvw.js → check-exTS-R-x.js} +1 -1
  24. package/dist/renderer/assets/chevron-down-DSvxXQa0.js +6 -0
  25. package/dist/renderer/assets/{circle-vfhmyE8v.js → circle-DyvEovdw.js} +1 -1
  26. package/dist/renderer/assets/desktop-B-KVT10D.js +1 -0
  27. package/dist/renderer/assets/desktop-CVibRyAw.js +4 -0
  28. package/dist/renderer/assets/{funnel-CUQibo1I.js → funnel-DA9qVdTy.js} +1 -1
  29. package/dist/renderer/assets/{heart-DG9_b8P1.js → heart-DuEV0ZRJ.js} +1 -1
  30. package/dist/renderer/assets/{index-Ds0IN5rP.js → index-BgflqkCN.js} +3 -3
  31. package/dist/renderer/assets/index-Biz-aexG.js +177 -0
  32. package/dist/renderer/assets/{index-3CzgyQYB.css → index-DFiJR0vv.css} +1 -1
  33. package/dist/renderer/assets/{pencil-Dij0LlRY.js → pencil-DBDxeNmO.js} +1 -1
  34. package/dist/renderer/assets/{play-CPFGjIyJ.js → play-IzlP83_7.js} +1 -1
  35. package/dist/renderer/assets/{plug-DH3RKQyn.js → plug-CuT-qFnW.js} +1 -1
  36. package/dist/renderer/assets/{plus-afkK9_Y8.js → plus-DEqgkn63.js} +1 -1
  37. package/dist/renderer/assets/projects-DDtDN1rc.js +1 -0
  38. package/dist/renderer/assets/{save-B-cKgWcy.js → save-D4sRo07y.js} +1 -1
  39. package/dist/renderer/assets/{search-DR6PeYkt.js → search-DbPZLuV8.js} +1 -1
  40. package/dist/renderer/assets/{server-BM6KOB5a.js → server-D1jUQmwN.js} +1 -1
  41. package/dist/renderer/assets/{session-utils-BDuThM0a.js → session-utils-Aife8ep2.js} +1 -1
  42. package/dist/renderer/assets/{sessions-C1yKps4T.js → sessions-DaH3H7gC.js} +1 -1
  43. package/dist/renderer/assets/{shield-check-BFjMG-r2.js → shield-check-BGwUFWNr.js} +1 -1
  44. package/dist/renderer/assets/{trash-2-CW1heMCf.js → trash-2-CIFBioBa.js} +1 -1
  45. package/dist/renderer/assets/{user-r_2sHG7m.js → user-CnKcvHdo.js} +1 -1
  46. package/dist/renderer/assets/{wifi-off-VMUokMpE.js → wifi-off-ClO53WGa.js} +1 -1
  47. package/dist/renderer/index.html +2 -2
  48. package/dist-electron/electron/local-core-refactor.test.js +812 -3
  49. package/dist-electron/electron/plugin-kernel.test.js +74 -14
  50. package/dist-electron/packages/core-sdk/src/index.js +40 -20
  51. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +5 -1
  52. package/dist-electron/services/local-ai-core/src/acp/workspace-acp-permissions.js +55 -3
  53. package/dist-electron/services/local-ai-core/src/channel/lark/cards.js +210 -0
  54. package/dist-electron/services/local-ai-core/src/channel/lark/config.js +54 -0
  55. package/dist-electron/services/local-ai-core/src/{gateway → channel/lark}/local-core-lark-gateway.js +365 -383
  56. package/dist-electron/services/local-ai-core/src/channel/lark/registration.js +50 -0
  57. package/dist-electron/services/local-ai-core/src/channel/lark/runtime-state.js +191 -0
  58. package/dist-electron/services/local-ai-core/src/channel/lark/types.js +2 -0
  59. package/dist-electron/services/local-ai-core/src/channel/shared/file-utils.js +33 -0
  60. package/dist-electron/services/local-ai-core/src/channel/shared/plugin.js +31 -0
  61. package/dist-electron/services/local-ai-core/src/channel/weixin/config.js +125 -0
  62. package/dist-electron/services/local-ai-core/src/{gateway → channel/weixin}/local-core-weixin-gateway.js +262 -258
  63. package/dist-electron/services/local-ai-core/src/channel/weixin/transport.js +169 -0
  64. package/dist-electron/services/local-ai-core/src/channel/weixin/types.js +2 -0
  65. package/dist-electron/services/local-ai-core/src/cli/lac.js +41 -1
  66. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +5 -0
  67. package/dist-electron/services/local-ai-core/src/plugins/builtin/channel-lark-plugin.js +19 -40
  68. package/dist-electron/services/local-ai-core/src/plugins/builtin/channel-weixin-plugin.js +19 -40
  69. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +96 -44
  70. package/dist-electron/services/local-ai-core/src/runtime/server.js +20 -8
  71. package/dist-electron/shared/desktop.js +24 -1
  72. package/package.json +1 -1
  73. package/dist/renderer/assets/CronList-BYZ-x_bp.js +0 -1
  74. package/dist/renderer/assets/Logs-D7HxZ8Nb.js +0 -1
  75. package/dist/renderer/assets/SessionChat-DpgSXF3m.js +0 -1
  76. package/dist/renderer/assets/SessionList-CU6drQ4N.js +0 -1
  77. package/dist/renderer/assets/Workspace-B7looTVo.js +0 -14
  78. package/dist/renderer/assets/desktop-BPIxWbkM.js +0 -1
  79. package/dist/renderer/assets/desktop-DZHC4PDB.js +0 -3
  80. package/dist/renderer/assets/index-Bn8ane6_.js +0 -177
  81. package/dist/renderer/assets/projects-DTDS9SnT.js +0 -1
@@ -14,10 +14,11 @@ const scheduled_conversation_executor_js_1 = require("../services/local-ai-core/
14
14
  const scheduler_run_lifecycle_js_1 = require("../services/local-ai-core/src/scheduler/scheduler-run-lifecycle.js");
15
15
  const lark_execution_policies_js_1 = require("../services/local-ai-core/src/scheduler/lark-execution-policies.js");
16
16
  const local_schedule_adapter_js_1 = require("../services/local-ai-core/src/scheduler/local-schedule-adapter.js");
17
- const local_core_weixin_gateway_js_1 = require("../services/local-ai-core/src/gateway/local-core-weixin-gateway.js");
18
- const local_core_lark_gateway_js_1 = require("../services/local-ai-core/src/gateway/local-core-lark-gateway.js");
17
+ const local_core_weixin_gateway_js_1 = require("../services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js");
18
+ const local_core_lark_gateway_js_1 = require("../services/local-ai-core/src/channel/lark/local-core-lark-gateway.js");
19
19
  const local_core_acp_turn_coordinator_js_1 = require("../services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js");
20
20
  const local_core_acp_store_js_1 = require("../services/local-ai-core/src/acp/local-core-acp-store.js");
21
+ const workspace_acp_permissions_js_1 = require("../services/local-ai-core/src/acp/workspace-acp-permissions.js");
21
22
  (0, node_test_1.default)('ACP tool call update is emitted with its pending tool name', () => {
22
23
  const appended = [];
23
24
  const emitted = [];
@@ -71,6 +72,29 @@ const local_core_acp_store_js_1 = require("../services/local-ai-core/src/acp/loc
71
72
  strict_1.default.equal(emitted.length, 1);
72
73
  strict_1.default.equal(emitted[0]?.content, appended[0]?.content);
73
74
  });
75
+ (0, node_test_1.default)('ACP permission normalization treats allow_all as allow all', () => {
76
+ strict_1.default.equal((0, workspace_acp_permissions_js_1.normalizePermissionAction)('allow_all'), 'allow all');
77
+ strict_1.default.equal((0, workspace_acp_permissions_js_1.normalizePermissionAction)('allow_always'), 'allow all');
78
+ strict_1.default.equal((0, workspace_acp_permissions_js_1.normalizePermissionAction)('always'), 'allow all');
79
+ strict_1.default.equal((0, workspace_acp_permissions_js_1.normalizePermissionAction)('allow_once'), 'allow');
80
+ });
81
+ (0, node_test_1.default)('ACP permission normalization preserves always-allow option semantics', () => {
82
+ strict_1.default.equal((0, workspace_acp_permissions_js_1.normalizePermissionOptionAction)({
83
+ optionId: 'approve',
84
+ name: 'Always allow',
85
+ kind: 'allow',
86
+ }), 'allow all');
87
+ strict_1.default.equal((0, workspace_acp_permissions_js_1.normalizePermissionOptionAction)({
88
+ optionId: 'allow-all-tools',
89
+ name: '始终允许',
90
+ kind: 'allow',
91
+ }), 'allow all');
92
+ strict_1.default.equal((0, workspace_acp_permissions_js_1.normalizePermissionOptionAction)({
93
+ optionId: 'allow_once',
94
+ name: 'Allow once',
95
+ kind: 'allow',
96
+ }), 'allow');
97
+ });
74
98
  (0, node_test_1.default)('ACP tool call running and completed updates share one message id', () => {
75
99
  const upserted = [];
76
100
  const emitted = [];
@@ -305,6 +329,55 @@ const local_core_acp_store_js_1 = require("../services/local-ai-core/src/acp/loc
305
329
  strict_1.default.match(upserted[0]?.content || '', /ls -la ~\/Desktop/);
306
330
  strict_1.default.match(upserted[0]?.content || '', /completed - Desktop file list/);
307
331
  });
332
+ (0, node_test_1.default)('ACP permission button rows preserve always allow actions', () => {
333
+ const emitted = [];
334
+ const coordinator = new local_core_acp_turn_coordinator_js_1.LocalCoreAcpTurnCoordinator({
335
+ appendMessage: () => { },
336
+ emitBridge: (event) => emitted.push(event),
337
+ updateRunStatus: () => { },
338
+ sendRaw: () => true,
339
+ });
340
+ const session = {
341
+ threadId: 'thread-1',
342
+ bridgeSessionKey: 'session:thread-1',
343
+ currentRunId: 'run-1',
344
+ currentTurn: {
345
+ runId: 'run-1',
346
+ replyCtx: 'run-1',
347
+ previewHandle: 'preview-1',
348
+ assistantText: '',
349
+ typingStarted: true,
350
+ previewStarted: false,
351
+ permission: null,
352
+ },
353
+ pendingPermissionByRun: new Map(),
354
+ loadReplayMode: false,
355
+ schedulerJobCreatedByRun: new Map(),
356
+ };
357
+ coordinator.handleAgentRequest(session, {
358
+ method: 'session/request_permission',
359
+ id: 42,
360
+ params: {
361
+ toolCall: {
362
+ title: 'Terminal',
363
+ parameters: {
364
+ command: 'system_profiler SPHardwareDataType',
365
+ },
366
+ },
367
+ options: [
368
+ { optionId: 'approve-once', name: 'Allow once', kind: 'allow' },
369
+ { optionId: 'approve-always', name: 'Always allow', kind: 'allow' },
370
+ { optionId: 'reject', name: 'Reject', kind: 'reject' },
371
+ ],
372
+ },
373
+ });
374
+ strict_1.default.deepEqual(emitted[0]?.buttonRows, [[
375
+ { text: 'allow', data: 'allow' },
376
+ { text: 'allow all', data: 'allow all' },
377
+ { text: 'deny', data: 'deny' },
378
+ ]]);
379
+ strict_1.default.equal(session.pendingPermissionByRun.get('run-1')?.options[1]?.optionId, 'approve-always');
380
+ });
308
381
  (0, node_test_1.default)('ACP bare tool call is flushed before assistant text', () => {
309
382
  const appended = [];
310
383
  const emitted = [];
@@ -573,6 +646,81 @@ const local_core_acp_store_js_1 = require("../services/local-ai-core/src/acp/loc
573
646
  strict_1.default.equal(patchedCards[0]?.text, '💭 先理解问题,再检查代码');
574
647
  strict_1.default.deepEqual(storedMessageIds, ['lark-msg-2']);
575
648
  });
649
+ (0, node_test_1.default)('lark bridge sends final reply as a separate card after streamed final updates', async () => {
650
+ const createdCards = [];
651
+ const patchedCards = [];
652
+ const storedMessageIds = [];
653
+ const client = {
654
+ im: {
655
+ message: {
656
+ create: async (request) => {
657
+ const messageId = `lark-msg-${createdCards.length + 1}`;
658
+ const card = JSON.parse(String(request.data.content || '{}'));
659
+ createdCards.push({
660
+ messageId,
661
+ text: String(card.elements?.[0]?.content || ''),
662
+ });
663
+ return { data: { message_id: messageId } };
664
+ },
665
+ patch: async (request) => {
666
+ const card = JSON.parse(String(request.data.content || '{}'));
667
+ patchedCards.push({
668
+ messageId: String(request.path.message_id || ''),
669
+ text: String(card.elements?.[0]?.content || ''),
670
+ });
671
+ },
672
+ },
673
+ },
674
+ };
675
+ const gateway = new local_core_lark_gateway_js_1.LocalCoreLarkGateway({
676
+ store: {
677
+ getPlatformThreadBinding: () => ({
678
+ workspace_id: 'default',
679
+ platform: 'lark',
680
+ chat_id: 'chat-1',
681
+ platform_user_id: 'user-1',
682
+ thread_id: 'thread-1',
683
+ last_platform_message_id: null,
684
+ }),
685
+ updatePlatformThreadMessageId: (_workspaceId, _chatId, _platformUserId, messageId) => {
686
+ storedMessageIds.push(messageId);
687
+ },
688
+ },
689
+ readConfig: async () => null,
690
+ getWorkspaceRouter: () => ({}),
691
+ eventBus: { emit: () => { }, on: () => () => { } },
692
+ });
693
+ const internals = gateway;
694
+ internals.runtime.set('default', {
695
+ workspaceId: 'default',
696
+ enabled: true,
697
+ status: 'running',
698
+ connected: true,
699
+ appId: 'app-1',
700
+ client,
701
+ });
702
+ internals.threadRouting.set('session:thread-1', {
703
+ workspaceId: 'default',
704
+ platformUserId: 'user-1',
705
+ chatId: 'chat-1',
706
+ threadId: 'thread-1',
707
+ });
708
+ await gateway.onBridgeEvent({
709
+ type: 'update_message',
710
+ sessionKey: 'session:thread-1',
711
+ replyCtx: 'run-1',
712
+ content: '流式中的最终回答草稿',
713
+ });
714
+ await gateway.onBridgeEvent({
715
+ type: 'reply',
716
+ sessionKey: 'session:thread-1',
717
+ replyCtx: 'run-1',
718
+ content: '真正最终回答',
719
+ });
720
+ strict_1.default.deepEqual(createdCards.map((card) => card.text), ['流式中的最终回答草稿', '真正最终回答']);
721
+ strict_1.default.equal(patchedCards.length, 0);
722
+ strict_1.default.deepEqual(storedMessageIds, ['lark-msg-1', 'lark-msg-2']);
723
+ });
576
724
  (0, node_test_1.default)('lark bridge does not leave typing placeholders or throttle thought updates', async () => {
577
725
  const createdCards = [];
578
726
  const patchedCards = [];
@@ -669,6 +817,7 @@ const local_core_acp_store_js_1 = require("../services/local-ai-core/src/acp/loc
669
817
  });
670
818
  (0, node_test_1.default)('lark permission requests render as clickable card buttons', async () => {
671
819
  const createdCards = [];
820
+ const patchedCards = [];
672
821
  const threadActions = [];
673
822
  const client = {
674
823
  im: {
@@ -677,7 +826,12 @@ const local_core_acp_store_js_1 = require("../services/local-ai-core/src/acp/loc
677
826
  createdCards.push(JSON.parse(String(request.data.content || '{}')));
678
827
  return { data: { message_id: 'permission-msg-1' } };
679
828
  },
680
- patch: async () => { },
829
+ patch: async (request) => {
830
+ patchedCards.push({
831
+ messageId: request.path?.message_id,
832
+ card: JSON.parse(String(request.data.content || '{}')),
833
+ });
834
+ },
681
835
  },
682
836
  },
683
837
  };
@@ -757,13 +911,244 @@ const local_core_acp_store_js_1 = require("../services/local-ai-core/src/acp/loc
757
911
  action: 'permission_response',
758
912
  response: 'allow all',
759
913
  thread_id: 'thread-1',
914
+ session_key: 'session:thread-1',
760
915
  },
761
916
  },
917
+ context: {
918
+ open_message_id: 'permission-msg-1',
919
+ },
762
920
  },
763
921
  });
764
922
  strict_1.default.deepEqual(threadActions, [
765
923
  { threadId: 'thread-1', action: 'allow all' },
766
924
  ]);
925
+ strict_1.default.equal(patchedCards.length, 1);
926
+ strict_1.default.equal(patchedCards[0]?.messageId, 'permission-msg-1');
927
+ strict_1.default.match(patchedCards[0]?.card?.elements?.[0]?.content || '', /工具确认已处理/);
928
+ strict_1.default.equal(patchedCards[0]?.card?.elements?.some((element) => element.tag === 'action'), false);
929
+ });
930
+ (0, node_test_1.default)('lark allow all card action preserves the final reply after tool execution', async () => {
931
+ const createdCards = [];
932
+ const patchedCards = [];
933
+ let nextMessageId = 1;
934
+ let gateway;
935
+ const client = {
936
+ im: {
937
+ message: {
938
+ create: async (request) => {
939
+ const messageId = `card-${nextMessageId++}`;
940
+ createdCards.push({
941
+ messageId,
942
+ card: JSON.parse(String(request.data.content || '{}')),
943
+ });
944
+ return { data: { message_id: messageId } };
945
+ },
946
+ patch: async (request) => {
947
+ patchedCards.push({
948
+ messageId: request.path?.message_id,
949
+ card: JSON.parse(String(request.data.content || '{}')),
950
+ });
951
+ },
952
+ },
953
+ },
954
+ };
955
+ gateway = new local_core_lark_gateway_js_1.LocalCoreLarkGateway({
956
+ store: {
957
+ getPlatformThreadBinding: () => ({
958
+ workspace_id: 'default',
959
+ platform: 'lark',
960
+ chat_id: 'chat-1',
961
+ platform_user_id: 'user-1',
962
+ thread_id: 'thread-1',
963
+ last_platform_message_id: null,
964
+ }),
965
+ clearPlatformThreadMessageId: () => { },
966
+ updatePlatformThreadMessageId: () => { },
967
+ },
968
+ readConfig: async () => null,
969
+ getWorkspaceRouter: () => ({
970
+ sendThreadAction: async (threadId, action) => {
971
+ strict_1.default.equal(threadId, 'thread-1');
972
+ strict_1.default.equal(action, 'allow all');
973
+ await gateway.onBridgeEvent({
974
+ type: 'typing_start',
975
+ sessionKey: 'session:thread-1',
976
+ replyCtx: 'run-1',
977
+ });
978
+ await gateway.onBridgeEvent({
979
+ type: 'reply',
980
+ sessionKey: 'session:thread-1',
981
+ replyCtx: 'run-1',
982
+ content: '桌面文件列表:AI进展报告_2026年4月.md',
983
+ });
984
+ return { runId: 'run-1' };
985
+ },
986
+ }),
987
+ eventBus: { emit: () => { }, on: () => () => { } },
988
+ });
989
+ const internals = gateway;
990
+ internals.runtime.set('default', {
991
+ workspaceId: 'default',
992
+ enabled: true,
993
+ status: 'running',
994
+ connected: true,
995
+ appId: 'app-1',
996
+ cardActionsEnabled: true,
997
+ client,
998
+ });
999
+ internals.threadRouting.set('session:thread-1', {
1000
+ workspaceId: 'default',
1001
+ platformUserId: 'user-1',
1002
+ chatId: 'chat-1',
1003
+ threadId: 'thread-1',
1004
+ });
1005
+ internals.outboundTurns.set('session:thread-1', {
1006
+ sessionKey: 'session:thread-1',
1007
+ progressMessageIds: {},
1008
+ permissionMessageId: 'permission-msg-1',
1009
+ awaitingPermission: true,
1010
+ processing: true,
1011
+ previewText: '',
1012
+ finalText: '',
1013
+ thinkingSteps: [],
1014
+ toolCalls: [],
1015
+ statusLines: [],
1016
+ buttonRows: [[{ text: '始终允许', data: 'allow all' }]],
1017
+ lastPatchedAt: 0,
1018
+ lastPatchedAtByMessageId: {},
1019
+ });
1020
+ await internals.handleCardActionEvent('default', {
1021
+ event: {
1022
+ action: {
1023
+ value: {
1024
+ action: 'permission_response',
1025
+ response: 'allow all',
1026
+ thread_id: 'thread-1',
1027
+ session_key: 'session:thread-1',
1028
+ },
1029
+ },
1030
+ context: {
1031
+ open_message_id: 'permission-msg-1',
1032
+ },
1033
+ },
1034
+ });
1035
+ strict_1.default.equal(patchedCards[0]?.messageId, 'permission-msg-1');
1036
+ strict_1.default.match(patchedCards[0]?.card?.elements?.[0]?.content || '', /工具确认已处理/);
1037
+ strict_1.default.equal(createdCards.length, 1);
1038
+ strict_1.default.match(createdCards[0]?.card?.elements?.[0]?.content || '', /桌面文件列表/);
1039
+ });
1040
+ (0, node_test_1.default)('lark channel folds tool result output in progress cards', async () => {
1041
+ const createdCards = [];
1042
+ const patchedCards = [];
1043
+ const client = {
1044
+ im: {
1045
+ message: {
1046
+ create: async (request) => {
1047
+ createdCards.push(JSON.parse(String(request.data.content || '{}')));
1048
+ return { data: { message_id: 'tool-msg-1' } };
1049
+ },
1050
+ patch: async (request) => {
1051
+ patchedCards.push(JSON.parse(String(request.data.content || '{}')));
1052
+ },
1053
+ },
1054
+ },
1055
+ };
1056
+ const gateway = new local_core_lark_gateway_js_1.LocalCoreLarkGateway({
1057
+ store: {
1058
+ getPlatformThreadBinding: () => ({
1059
+ workspace_id: 'default',
1060
+ platform: 'lark',
1061
+ chat_id: 'chat-1',
1062
+ platform_user_id: 'user-1',
1063
+ thread_id: 'thread-1',
1064
+ last_platform_message_id: null,
1065
+ }),
1066
+ },
1067
+ readConfig: async () => null,
1068
+ getWorkspaceRouter: () => ({}),
1069
+ eventBus: { emit: () => { }, on: () => () => { } },
1070
+ });
1071
+ const internals = gateway;
1072
+ internals.runtime.set('default', {
1073
+ workspaceId: 'default',
1074
+ enabled: true,
1075
+ status: 'running',
1076
+ connected: true,
1077
+ appId: 'app-1',
1078
+ client,
1079
+ });
1080
+ internals.threadRouting.set('session:thread-1', {
1081
+ workspaceId: 'default',
1082
+ platformUserId: 'user-1',
1083
+ chatId: 'chat-1',
1084
+ threadId: 'thread-1',
1085
+ });
1086
+ await gateway.onBridgeEvent({
1087
+ type: 'reply',
1088
+ sessionKey: 'session:thread-1',
1089
+ replyCtx: 'run-1',
1090
+ messageId: 'tool-1',
1091
+ content: '🔧 Terminal: ls - completed - secret terminal output',
1092
+ });
1093
+ const text = createdCards[0]?.elements?.[0]?.content || patchedCards[0]?.elements?.[0]?.content || '';
1094
+ strict_1.default.match(text, /Terminal: ls - completed/);
1095
+ strict_1.default.doesNotMatch(text, /secret terminal output/);
1096
+ });
1097
+ (0, node_test_1.default)('lark card action message id can be extracted from full callback payload', async () => {
1098
+ const patchedCards = [];
1099
+ const threadActions = [];
1100
+ const client = {
1101
+ im: {
1102
+ message: {
1103
+ patch: async (request) => {
1104
+ patchedCards.push({
1105
+ messageId: request.path?.message_id,
1106
+ card: JSON.parse(String(request.data.content || '{}')),
1107
+ });
1108
+ },
1109
+ },
1110
+ },
1111
+ };
1112
+ const gateway = new local_core_lark_gateway_js_1.LocalCoreLarkGateway({
1113
+ store: {},
1114
+ readConfig: async () => null,
1115
+ getWorkspaceRouter: () => ({
1116
+ sendThreadAction: async (threadId, action) => {
1117
+ threadActions.push({ threadId, action });
1118
+ return { runId: 'run-1' };
1119
+ },
1120
+ }),
1121
+ eventBus: { emit: () => { }, on: () => () => { } },
1122
+ });
1123
+ const internals = gateway;
1124
+ internals.runtime.set('default', {
1125
+ workspaceId: 'default',
1126
+ enabled: true,
1127
+ status: 'running',
1128
+ connected: true,
1129
+ appId: 'app-1',
1130
+ client,
1131
+ });
1132
+ await internals.handleCardActionEvent('default', {
1133
+ schema: '2.0',
1134
+ event: {
1135
+ action: {
1136
+ value: {
1137
+ action: 'permission_response',
1138
+ response: 'allow',
1139
+ thread_id: 'thread-1',
1140
+ },
1141
+ },
1142
+ },
1143
+ event_context: {
1144
+ open_message_id: 'permission-msg-nested',
1145
+ },
1146
+ });
1147
+ strict_1.default.deepEqual(threadActions, [
1148
+ { threadId: 'thread-1', action: 'allow' },
1149
+ ]);
1150
+ strict_1.default.equal(patchedCards[0]?.messageId, 'permission-msg-nested');
1151
+ strict_1.default.equal(patchedCards[0]?.card?.elements?.some((element) => element.tag === 'action'), false);
767
1152
  });
768
1153
  (0, node_test_1.default)('lark permission requests fall back to text commands when card actions are disabled', async () => {
769
1154
  const createdCards = [];
@@ -917,6 +1302,183 @@ const local_core_acp_store_js_1 = require("../services/local-ai-core/src/acp/loc
917
1302
  strict_1.default.equal(sentMessages[0]?.content?.contentParts?.[1]?.mimeType, 'image/png');
918
1303
  strict_1.default.equal(sentMessages[0]?.content?.contentParts?.[1]?.data, pngBytes.toString('base64'));
919
1304
  });
1305
+ (0, node_test_1.default)('lark channel can upload and send a local file', async () => {
1306
+ const tempDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'lark-file-send-'));
1307
+ try {
1308
+ const filePath = (0, node_path_1.join)(tempDir, 'report.pdf');
1309
+ (0, node_fs_1.writeFileSync)(filePath, 'pdf content');
1310
+ const uploads = [];
1311
+ const messages = [];
1312
+ const client = {
1313
+ im: {
1314
+ file: {
1315
+ create: async (request) => {
1316
+ uploads.push(request);
1317
+ await new Promise((resolve, reject) => {
1318
+ request.data.file.on('data', () => { });
1319
+ request.data.file.on('error', reject);
1320
+ request.data.file.on('end', resolve);
1321
+ });
1322
+ return { file_key: 'file-key-1' };
1323
+ },
1324
+ },
1325
+ message: {
1326
+ create: async (request) => {
1327
+ messages.push(request);
1328
+ return { data: { message_id: 'msg-file-1' } };
1329
+ },
1330
+ },
1331
+ },
1332
+ };
1333
+ const gateway = new local_core_lark_gateway_js_1.LocalCoreLarkGateway({
1334
+ store: {},
1335
+ readConfig: async () => null,
1336
+ getWorkspaceRouter: () => ({}),
1337
+ eventBus: { emit: () => { }, on: () => () => { } },
1338
+ });
1339
+ const internals = gateway;
1340
+ internals.runtime.set('default', {
1341
+ workspaceId: 'default',
1342
+ enabled: true,
1343
+ status: 'running',
1344
+ connected: true,
1345
+ appId: 'app-1',
1346
+ client,
1347
+ });
1348
+ const result = await gateway.sendFile('default', {
1349
+ path: filePath,
1350
+ channelId: 'oc_chat_1',
1351
+ });
1352
+ strict_1.default.equal(uploads.length, 1);
1353
+ strict_1.default.equal(uploads[0]?.data?.file_type, 'pdf');
1354
+ strict_1.default.equal(uploads[0]?.data?.file_name, 'report.pdf');
1355
+ strict_1.default.equal(messages.length, 1);
1356
+ strict_1.default.equal(messages[0]?.params?.receive_id_type, 'chat_id');
1357
+ strict_1.default.equal(messages[0]?.data?.receive_id, 'oc_chat_1');
1358
+ strict_1.default.equal(messages[0]?.data?.msg_type, 'file');
1359
+ strict_1.default.deepEqual(JSON.parse(messages[0]?.data?.content), { file_key: 'file-key-1' });
1360
+ strict_1.default.deepEqual(result, {
1361
+ platform: 'lark',
1362
+ workspaceId: 'default',
1363
+ channelId: 'oc_chat_1',
1364
+ messageId: 'msg-file-1',
1365
+ fileKey: 'file-key-1',
1366
+ fileName: 'report.pdf',
1367
+ fileSize: Buffer.byteLength('pdf content'),
1368
+ });
1369
+ }
1370
+ finally {
1371
+ (0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
1372
+ }
1373
+ });
1374
+ (0, node_test_1.default)('weixin channel can encrypt, upload, and send a local file', async () => {
1375
+ const tempDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'weixin-file-send-'));
1376
+ const originalFetch = globalThis.fetch;
1377
+ try {
1378
+ const filePath = (0, node_path_1.join)(tempDir, 'report.txt');
1379
+ (0, node_fs_1.writeFileSync)(filePath, 'hello weixin');
1380
+ const uploadUrlRequests = [];
1381
+ const cdnUploads = [];
1382
+ const sentMessages = [];
1383
+ globalThis.fetch = (async (url, init) => {
1384
+ const target = String(url);
1385
+ if (target.endsWith('/ilink/bot/getuploadurl')) {
1386
+ uploadUrlRequests.push(JSON.parse(String(init?.body || '{}')));
1387
+ return {
1388
+ ok: true,
1389
+ json: async () => ({ ret: 0, upload_param: 'upload-param-1' }),
1390
+ };
1391
+ }
1392
+ if (target.includes('/upload?')) {
1393
+ const body = init?.body instanceof Uint8Array ? init.body : new Uint8Array();
1394
+ cdnUploads.push({ url: target, size: body.byteLength });
1395
+ return {
1396
+ ok: true,
1397
+ headers: {
1398
+ get: (name) => name.toLowerCase() === 'x-encrypted-param' ? 'download-param-1' : null,
1399
+ },
1400
+ };
1401
+ }
1402
+ if (target.endsWith('/ilink/bot/sendmessage')) {
1403
+ sentMessages.push(JSON.parse(String(init?.body || '{}')));
1404
+ return {
1405
+ ok: true,
1406
+ json: async () => ({ ret: 0 }),
1407
+ };
1408
+ }
1409
+ throw new Error(`Unexpected fetch ${target}`);
1410
+ });
1411
+ const gateway = new local_core_weixin_gateway_js_1.LocalCoreWeixinGateway({
1412
+ store: {
1413
+ getPlatformThreadBinding: () => ({
1414
+ workspace_id: 'default',
1415
+ platform: 'weixin',
1416
+ chat_id: 'user-1',
1417
+ platform_user_id: 'user-1',
1418
+ thread_id: 'thread-1',
1419
+ last_platform_message_id: 'ctx-1',
1420
+ }),
1421
+ listAuthorizedUsers: () => [],
1422
+ },
1423
+ readConfig: async () => ({
1424
+ projects: [{
1425
+ name: 'default',
1426
+ root: '/tmp/project',
1427
+ platforms: [{
1428
+ type: 'weixin',
1429
+ options: {
1430
+ token: 'token-1',
1431
+ account_id: 'account-1',
1432
+ base_url: 'https://weixin.example',
1433
+ cdn_base_url: 'https://cdn.example/c2c',
1434
+ },
1435
+ }],
1436
+ }],
1437
+ }),
1438
+ getWorkspaceRouter: () => ({}),
1439
+ eventBus: { emit: () => { }, on: () => () => { } },
1440
+ });
1441
+ const internals = gateway;
1442
+ internals.runtime.set('default', {
1443
+ workspaceId: 'default',
1444
+ enabled: true,
1445
+ status: 'running',
1446
+ connected: true,
1447
+ accountId: 'account-1',
1448
+ });
1449
+ const result = await gateway.sendFile('default', {
1450
+ path: filePath,
1451
+ channelId: 'user-1',
1452
+ participantId: 'user-1',
1453
+ });
1454
+ strict_1.default.equal(uploadUrlRequests.length, 1);
1455
+ strict_1.default.equal(uploadUrlRequests[0]?.media_type, 3);
1456
+ strict_1.default.equal(uploadUrlRequests[0]?.to_user_id, 'user-1');
1457
+ strict_1.default.equal(uploadUrlRequests[0]?.rawsize, Buffer.byteLength('hello weixin'));
1458
+ strict_1.default.equal(uploadUrlRequests[0]?.filesize, 16);
1459
+ strict_1.default.equal(cdnUploads.length, 1);
1460
+ strict_1.default.match(cdnUploads[0]?.url || '', /encrypted_query_param=upload-param-1/);
1461
+ strict_1.default.equal(cdnUploads[0]?.size, 16);
1462
+ strict_1.default.equal(sentMessages.length, 1);
1463
+ const message = sentMessages[0]?.msg;
1464
+ strict_1.default.equal(message?.to_user_id, 'user-1');
1465
+ strict_1.default.equal(message?.context_token, 'ctx-1');
1466
+ strict_1.default.equal(message?.item_list?.[0]?.type, 4);
1467
+ strict_1.default.equal(message?.item_list?.[0]?.file_item?.file_name, 'report.txt');
1468
+ strict_1.default.equal(message?.item_list?.[0]?.file_item?.len, String(Buffer.byteLength('hello weixin')));
1469
+ strict_1.default.equal(message?.item_list?.[0]?.file_item?.media?.encrypt_query_param, 'download-param-1');
1470
+ strict_1.default.equal(result.platform, 'weixin');
1471
+ strict_1.default.equal(result.channelId, 'user-1');
1472
+ strict_1.default.equal(result.fileName, 'report.txt');
1473
+ strict_1.default.equal(result.fileSize, Buffer.byteLength('hello weixin'));
1474
+ strict_1.default.match(result.messageId, /^openclaw-weixin-/);
1475
+ strict_1.default.ok(result.fileKey);
1476
+ }
1477
+ finally {
1478
+ globalThis.fetch = originalFetch;
1479
+ (0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
1480
+ }
1481
+ });
920
1482
  (0, node_test_1.default)('lark message callbacks acknowledge before long thread runs finish', async () => {
921
1483
  let registeredHandlers = {};
922
1484
  let sentMessages = 0;
@@ -1006,6 +1568,80 @@ const local_core_acp_store_js_1 = require("../services/local-ai-core/src/acp/loc
1006
1568
  line.includes('sender=user-1')));
1007
1569
  strict_1.default.ok(logs.some((line) => line.includes('handling message event') && line.includes('contentBytes=')));
1008
1570
  });
1571
+ (0, node_test_1.default)('lark inbound messages use active runtime binding before config refresh catches up', async () => {
1572
+ const users = new Map();
1573
+ const threadBindings = new Map();
1574
+ const createdThreads = [];
1575
+ const sentCards = [];
1576
+ const platformKey = 'lark:lark-hot';
1577
+ const bindingKey = `${platformKey}:chat-1:user-1`;
1578
+ const gateway = new local_core_lark_gateway_js_1.LocalCoreLarkGateway({
1579
+ store: {
1580
+ expirePendingPairings: () => { },
1581
+ listPendingPairings: () => [],
1582
+ listAuthorizedUsers: () => [...users.values()],
1583
+ getAuthorizedUser: (_workspaceId, platformUserId, requestedPlatform) => users.get(`${requestedPlatform}:${platformUserId}`),
1584
+ createAuthorizedUser: (user) => users.set(`${user.platform}:${user.platform_user_id}`, user),
1585
+ updateAuthorizedUserThread: (_workspaceId, platformUserId, threadId, requestedPlatform) => {
1586
+ users.set(`${requestedPlatform}:${platformUserId}`, { ...users.get(`${requestedPlatform}:${platformUserId}`), thread_id: threadId });
1587
+ },
1588
+ getPlatformThreadBinding: () => threadBindings.get(bindingKey),
1589
+ upsertPlatformThreadBinding: (binding) => threadBindings.set(bindingKey, binding),
1590
+ getLatestRunForThread: () => null,
1591
+ clearPlatformThreadMessageId: () => { },
1592
+ },
1593
+ readConfig: async () => ({ projects: [] }),
1594
+ getWorkspaceRouter: () => ({
1595
+ createThread: async (_workspaceId, title) => {
1596
+ const id = `thread-${createdThreads.length + 1}`;
1597
+ createdThreads.push(`${id}:${title}`);
1598
+ return { id };
1599
+ },
1600
+ getThreadSessionKey: (threadId) => `session:${threadId}`,
1601
+ sendThreadMessage: async () => ({ runId: 'run-1' }),
1602
+ }),
1603
+ eventBus: { emit: () => { }, on: () => () => { } },
1604
+ });
1605
+ const internals = gateway;
1606
+ internals.runtime.set('project-1::lark-hot', {
1607
+ workspaceId: 'project-1',
1608
+ instanceId: 'lark-hot',
1609
+ displayName: 'Lark Hot',
1610
+ platformKey,
1611
+ enabled: true,
1612
+ status: 'running',
1613
+ connected: true,
1614
+ appId: 'app-1',
1615
+ autoApprove: true,
1616
+ cardActionsEnabled: true,
1617
+ client: {
1618
+ im: {
1619
+ message: {
1620
+ create: async (request) => {
1621
+ sentCards.push(JSON.parse(String(request.data.content || '{}')));
1622
+ return { data: { message_id: 'card-1' } };
1623
+ },
1624
+ },
1625
+ messageReaction: {
1626
+ create: async () => ({ data: { reaction_id: 'reaction-1' } }),
1627
+ },
1628
+ },
1629
+ },
1630
+ });
1631
+ await gateway.handleInboundMessage({
1632
+ workspaceId: 'project-1',
1633
+ instanceId: 'lark-hot',
1634
+ platformKey,
1635
+ platformUserId: 'user-1',
1636
+ chatId: 'chat-1',
1637
+ displayName: 'User',
1638
+ text: '/new',
1639
+ messageId: 'msg-1',
1640
+ });
1641
+ strict_1.default.equal(users.get(`${platformKey}:user-1`)?.thread_id, 'thread-2');
1642
+ strict_1.default.deepEqual(createdThreads.map((item) => item.split(':')[0]), ['thread-1', 'thread-2']);
1643
+ strict_1.default.match(sentCards[0]?.elements?.[0]?.content || '', /已开始新会话/);
1644
+ });
1009
1645
  (0, node_test_1.default)('ACP skips empty generic running tool updates', () => {
1010
1646
  const appended = [];
1011
1647
  const emitted = [];
@@ -1392,6 +2028,8 @@ const local_core_acp_store_js_1 = require("../services/local-ai-core/src/acp/loc
1392
2028
  ticket: 'ticket-1',
1393
2029
  expiresIn: 180,
1394
2030
  qrCodeUrl: 'https://liteapp.weixin.qq.com/q/test?qrcode=ticket-1&bot_type=3',
2031
+ instanceId: 'default',
2032
+ displayName: 'WeChat 1',
1395
2033
  });
1396
2034
  strict_1.default.equal(requests[0]?.url, 'https://ilinkai.weixin.qq.com/ilink/bot/get_bot_qrcode?bot_type=3');
1397
2035
  strict_1.default.equal(requests[0]?.headers.has('Authorization'), false);
@@ -1460,6 +2098,177 @@ const local_core_acp_store_js_1 = require("../services/local-ai-core/src/acp/loc
1460
2098
  (0, node_fs_1.rmSync)(stateDir, { recursive: true, force: true });
1461
2099
  }
1462
2100
  });
2101
+ (0, node_test_1.default)('lark channel can request an official app registration QR code without extra setup', async () => {
2102
+ const originalFetch = globalThis.fetch;
2103
+ const requests = [];
2104
+ globalThis.fetch = (async (input, init) => {
2105
+ requests.push({
2106
+ url: String(input),
2107
+ method: init?.method,
2108
+ body: String(init?.body || ''),
2109
+ headers: new Headers(init?.headers),
2110
+ });
2111
+ return new Response(JSON.stringify({
2112
+ device_code: 'device-code-1',
2113
+ user_code: 'ABCD-EFGH',
2114
+ expires_in: 300,
2115
+ interval: 5,
2116
+ }), {
2117
+ status: 200,
2118
+ headers: { 'Content-Type': 'application/json' },
2119
+ });
2120
+ });
2121
+ try {
2122
+ const gateway = new local_core_lark_gateway_js_1.LocalCoreLarkGateway({
2123
+ store: {},
2124
+ readConfig: async () => ({
2125
+ projects: [
2126
+ {
2127
+ name: 'default',
2128
+ agent: { type: 'localcore-acp', providers: [] },
2129
+ platforms: [{ type: 'lark', options: {} }],
2130
+ },
2131
+ ],
2132
+ }),
2133
+ getWorkspaceRouter: () => ({}),
2134
+ eventBus: { emit: () => { }, on: () => () => { } },
2135
+ });
2136
+ const result = await gateway.getQrCode('default');
2137
+ strict_1.default.deepEqual(result, {
2138
+ ticket: 'device-code-1',
2139
+ expiresIn: 300,
2140
+ interval: 5,
2141
+ qrCodeUrl: 'https://open.feishu.cn/page/openclaw?user_code=ABCD-EFGH&from=openclaw',
2142
+ instanceId: 'default',
2143
+ displayName: 'Lark 1',
2144
+ });
2145
+ strict_1.default.equal(requests[0]?.url, 'https://accounts.feishu.cn/oauth/v1/app/registration');
2146
+ strict_1.default.equal(requests[0]?.method, 'POST');
2147
+ strict_1.default.equal(requests[0]?.headers.get('Content-Type'), 'application/x-www-form-urlencoded');
2148
+ strict_1.default.equal(new URLSearchParams(requests[0]?.body).get('action'), 'begin');
2149
+ strict_1.default.equal(new URLSearchParams(requests[0]?.body).get('archetype'), 'PersonalAgent');
2150
+ strict_1.default.equal(new URLSearchParams(requests[0]?.body).get('request_callbacks'), null);
2151
+ }
2152
+ finally {
2153
+ globalThis.fetch = originalFetch;
2154
+ }
2155
+ });
2156
+ (0, node_test_1.default)('lark app registration QR confirmation returns app credentials', async () => {
2157
+ const originalFetch = globalThis.fetch;
2158
+ const requests = [];
2159
+ globalThis.fetch = (async (input, init) => {
2160
+ const url = String(input);
2161
+ requests.push({ url, body: String(init?.body || '') });
2162
+ return new Response(JSON.stringify({
2163
+ client_id: 'cli_lark_1',
2164
+ client_secret: 'secret-1',
2165
+ }), {
2166
+ status: 200,
2167
+ headers: { 'Content-Type': 'application/json' },
2168
+ });
2169
+ });
2170
+ try {
2171
+ const gateway = new local_core_lark_gateway_js_1.LocalCoreLarkGateway({
2172
+ store: {},
2173
+ readConfig: async () => ({
2174
+ projects: [
2175
+ {
2176
+ name: 'default',
2177
+ agent: { type: 'localcore-acp', providers: [] },
2178
+ platforms: [{ type: 'lark', options: {} }],
2179
+ },
2180
+ ],
2181
+ }),
2182
+ getWorkspaceRouter: () => ({}),
2183
+ eventBus: { emit: () => { }, on: () => () => { } },
2184
+ });
2185
+ const result = await gateway.checkQrCodeStatus('default', 'lark-ticket-1');
2186
+ strict_1.default.equal(requests[0]?.url, 'https://accounts.feishu.cn/oauth/v1/app/registration');
2187
+ strict_1.default.equal(new URLSearchParams(requests[0]?.body).get('action'), 'poll');
2188
+ strict_1.default.equal(new URLSearchParams(requests[0]?.body).get('device_code'), 'lark-ticket-1');
2189
+ strict_1.default.deepEqual(result, {
2190
+ status: 'confirmed',
2191
+ credentials: {
2192
+ appId: 'cli_lark_1',
2193
+ appSecret: 'secret-1',
2194
+ },
2195
+ });
2196
+ }
2197
+ finally {
2198
+ globalThis.fetch = originalFetch;
2199
+ }
2200
+ });
2201
+ (0, node_test_1.default)('lark channel keeps multiple bot instances in one workspace isolated', async () => {
2202
+ const gateway = new local_core_lark_gateway_js_1.LocalCoreLarkGateway({
2203
+ store: {
2204
+ expirePendingPairings: () => { },
2205
+ listPendingPairings: () => [],
2206
+ listAuthorizedUsers: () => [],
2207
+ },
2208
+ readConfig: async () => ({
2209
+ projects: [
2210
+ {
2211
+ name: 'default',
2212
+ agent: { type: 'localcore-acp', providers: [] },
2213
+ platforms: [
2214
+ { type: 'lark', options: { instance_id: 'bot-a', app_id: 'cli_a', app_secret: 'secret-a' } },
2215
+ { type: 'lark', options: { instance_id: 'bot-b', app_id: 'cli_b', app_secret: 'secret-b' } },
2216
+ ],
2217
+ },
2218
+ ],
2219
+ }),
2220
+ getWorkspaceRouter: () => ({}),
2221
+ eventBus: { emit: () => { }, on: () => () => { } },
2222
+ });
2223
+ gateway.larkModulePromise = Promise.resolve({
2224
+ AppType: { SelfBuild: 'self-build' },
2225
+ Domain: { Feishu: 'feishu' },
2226
+ LoggerLevel: { info: 'info' },
2227
+ Client: class {
2228
+ },
2229
+ EventDispatcher: class {
2230
+ register() { }
2231
+ },
2232
+ WSClient: class {
2233
+ async start() { }
2234
+ },
2235
+ });
2236
+ await gateway.refreshBindings();
2237
+ const statuses = gateway.listStatuses();
2238
+ strict_1.default.deepEqual(statuses.map((status) => [status.workspaceId, status.instanceId, status.appId, status.status]), [
2239
+ ['default', 'bot-a', 'cli_a', 'running'],
2240
+ ['default', 'bot-b', 'cli_b', 'running'],
2241
+ ]);
2242
+ });
2243
+ (0, node_test_1.default)('weixin channel keeps multiple bot instances in one workspace isolated', async () => {
2244
+ const gateway = new local_core_weixin_gateway_js_1.LocalCoreWeixinGateway({
2245
+ store: {
2246
+ expirePendingPairings: () => { },
2247
+ listPendingPairings: () => [],
2248
+ listAuthorizedUsers: () => [],
2249
+ },
2250
+ readConfig: async () => ({
2251
+ projects: [
2252
+ {
2253
+ name: 'default',
2254
+ agent: { type: 'localcore-acp', providers: [] },
2255
+ platforms: [
2256
+ { type: 'weixin', options: { instance_id: 'wx-a', account_id: 'account-a' } },
2257
+ { type: 'weixin', options: { instance_id: 'wx-b', account_id: 'account-b' } },
2258
+ ],
2259
+ },
2260
+ ],
2261
+ }),
2262
+ getWorkspaceRouter: () => ({}),
2263
+ eventBus: { emit: () => { }, on: () => () => { } },
2264
+ });
2265
+ await gateway.refreshBindings();
2266
+ const statuses = gateway.listStatuses();
2267
+ strict_1.default.deepEqual(statuses.map((status) => [status.workspaceId, status.instanceId, status.appId, status.status]), [
2268
+ ['default', 'wx-a', 'account-a', 'stopped'],
2269
+ ['default', 'wx-b', 'account-b', 'stopped'],
2270
+ ]);
2271
+ });
1463
2272
  (0, node_test_1.default)('weixin inbound message handling is idempotent by message identity', async () => {
1464
2273
  const sentThreadMessages = [];
1465
2274
  const users = new Map();