@kafca/agentdock 0.1.22 → 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 (72) hide show
  1. package/README.md +3 -0
  2. package/dist/renderer/assets/{Badge-B_rgzxYO.js → Badge-C2K5apfV.js} +1 -1
  3. package/dist/renderer/assets/{Chat-CYtp6m7T.js → Chat-DI2YeVKb.js} +1 -1
  4. package/dist/renderer/assets/{ChatMarkdown-DOBMwa9d.js → ChatMarkdown-DMAr15Oi.js} +1 -1
  5. package/dist/renderer/assets/{Config-DppMSPHF.js → Config-7rP3EuUf.js} +1 -1
  6. package/dist/renderer/assets/{CronList-BDtB1SuA.js → CronList-81QslYE8.js} +1 -1
  7. package/dist/renderer/assets/{Dashboard-iFlRnd7Y.js → Dashboard-Db2T7rSZ.js} +2 -2
  8. package/dist/renderer/assets/{EmptyState-bKDt_T88.js → EmptyState-BBJ3cTvB.js} +1 -1
  9. package/dist/renderer/assets/{KnowledgeDetail-CKPhKPIg.js → KnowledgeDetail-Dz_6flG3.js} +2 -2
  10. package/dist/renderer/assets/{KnowledgeHome-CFeJre6i.js → KnowledgeHome-DIh046mN.js} +3 -3
  11. package/dist/renderer/assets/{Logs-CyxSVOFK.js → Logs-BXBtHJLf.js} +1 -1
  12. package/dist/renderer/assets/{Modal-CxPnk1Ey.js → Modal-BB2Av_Xs.js} +1 -1
  13. package/dist/renderer/assets/{Page-kU9vubzj.js → Page-DPSdvMgU.js} +1 -1
  14. package/dist/renderer/assets/{ProjectDetail-D9FY9NWZ.js → ProjectDetail-C6EWGvGg.js} +1 -1
  15. package/dist/renderer/assets/{ProjectList-DIwr2TXg.js → ProjectList-BBo5ygnS.js} +1 -1
  16. package/dist/renderer/assets/{Select-BaWoz5Dn.js → Select-CNX-Tx_O.js} +1 -1
  17. package/dist/renderer/assets/{SessionChat-Cm9CW8Zx.js → SessionChat-BIn46jqZ.js} +1 -1
  18. package/dist/renderer/assets/{SessionList-UPnWlXXO.js → SessionList-CZt6f1Rd.js} +1 -1
  19. package/dist/renderer/assets/{ThreadChat-YvVsACW3.js → ThreadChat-BotD0gKQ.js} +1 -1
  20. package/dist/renderer/assets/Workspace-C_4tdpjX.js +14 -0
  21. package/dist/renderer/assets/{arrow-left-BmZAVGV7.js → arrow-left-cHr5wkpq.js} +1 -1
  22. package/dist/renderer/assets/{book-open-DZ3GXFgI.js → book-open-4nx5C8T-.js} +1 -1
  23. package/dist/renderer/assets/{check-CeVSvDCv.js → check-exTS-R-x.js} +1 -1
  24. package/dist/renderer/assets/{chevron-down-yryuMoQ-.js → chevron-down-DSvxXQa0.js} +1 -1
  25. package/dist/renderer/assets/{circle-eASsFISH.js → circle-DyvEovdw.js} +1 -1
  26. package/dist/renderer/assets/desktop-B-KVT10D.js +1 -0
  27. package/dist/renderer/assets/{desktop-Che1cbpE.js → desktop-CVibRyAw.js} +3 -3
  28. package/dist/renderer/assets/{funnel-pwAe2Ixp.js → funnel-DA9qVdTy.js} +1 -1
  29. package/dist/renderer/assets/{heart-B2Sp-bCm.js → heart-DuEV0ZRJ.js} +1 -1
  30. package/dist/renderer/assets/{index-Cse2Br64.js → index-BgflqkCN.js} +3 -3
  31. package/dist/renderer/assets/{index-BC3MWbS1.js → index-Biz-aexG.js} +39 -39
  32. package/dist/renderer/assets/{pencil-BfEunUF3.js → pencil-DBDxeNmO.js} +1 -1
  33. package/dist/renderer/assets/{play-Dcs9Lj_Z.js → play-IzlP83_7.js} +1 -1
  34. package/dist/renderer/assets/{plug-B_YschQe.js → plug-CuT-qFnW.js} +1 -1
  35. package/dist/renderer/assets/{plus-Pqo-cXHQ.js → plus-DEqgkn63.js} +1 -1
  36. package/dist/renderer/assets/projects-DDtDN1rc.js +1 -0
  37. package/dist/renderer/assets/{save-BKM5yi1A.js → save-D4sRo07y.js} +1 -1
  38. package/dist/renderer/assets/{search-BU42h10M.js → search-DbPZLuV8.js} +1 -1
  39. package/dist/renderer/assets/{server-DA5t3Rtr.js → server-D1jUQmwN.js} +1 -1
  40. package/dist/renderer/assets/{session-utils-Cr9VhNSq.js → session-utils-Aife8ep2.js} +1 -1
  41. package/dist/renderer/assets/{sessions-bvV8ZMfX.js → sessions-DaH3H7gC.js} +1 -1
  42. package/dist/renderer/assets/{shield-check-BeOJKur_.js → shield-check-BGwUFWNr.js} +1 -1
  43. package/dist/renderer/assets/{trash-2-CQ7HKO3q.js → trash-2-CIFBioBa.js} +1 -1
  44. package/dist/renderer/assets/{user-CtFjY9SV.js → user-CnKcvHdo.js} +1 -1
  45. package/dist/renderer/assets/{wifi-off-Ci5m0ysf.js → wifi-off-ClO53WGa.js} +1 -1
  46. package/dist/renderer/index.html +1 -1
  47. package/dist-electron/electron/local-core-refactor.test.js +505 -0
  48. package/dist-electron/electron/plugin-kernel.test.js +74 -14
  49. package/dist-electron/packages/core-sdk/src/index.js +40 -20
  50. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +5 -1
  51. package/dist-electron/services/local-ai-core/src/acp/workspace-acp-permissions.js +55 -3
  52. package/dist-electron/services/local-ai-core/src/channel/lark/cards.js +210 -0
  53. package/dist-electron/services/local-ai-core/src/channel/lark/config.js +54 -0
  54. package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +212 -471
  55. package/dist-electron/services/local-ai-core/src/channel/lark/registration.js +50 -0
  56. package/dist-electron/services/local-ai-core/src/channel/lark/runtime-state.js +191 -0
  57. package/dist-electron/services/local-ai-core/src/channel/lark/types.js +2 -0
  58. package/dist-electron/services/local-ai-core/src/channel/shared/plugin.js +31 -0
  59. package/dist-electron/services/local-ai-core/src/channel/weixin/config.js +125 -0
  60. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +141 -324
  61. package/dist-electron/services/local-ai-core/src/channel/weixin/transport.js +169 -0
  62. package/dist-electron/services/local-ai-core/src/channel/weixin/types.js +2 -0
  63. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +5 -0
  64. package/dist-electron/services/local-ai-core/src/plugins/builtin/channel-lark-plugin.js +18 -39
  65. package/dist-electron/services/local-ai-core/src/plugins/builtin/channel-weixin-plugin.js +18 -39
  66. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +55 -44
  67. package/dist-electron/services/local-ai-core/src/runtime/server.js +10 -8
  68. package/dist-electron/shared/desktop.js +10 -0
  69. package/package.json +1 -1
  70. package/dist/renderer/assets/Workspace-ST7V8u6T.js +0 -14
  71. package/dist/renderer/assets/desktop-DkrP_s-b.js +0 -1
  72. package/dist/renderer/assets/projects-BCj0k_Gy.js +0 -1
@@ -1,4 +1,4 @@
1
- import{c as a}from"./index-BC3MWbS1.js";/**
1
+ import{c as a}from"./index-Biz-aexG.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as o}from"./index-BC3MWbS1.js";/**
1
+ import{c as o}from"./index-Biz-aexG.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as o}from"./index-BC3MWbS1.js";/**
1
+ import{c as o}from"./index-Biz-aexG.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as e}from"./index-BC3MWbS1.js";/**
1
+ import{c as e}from"./index-Biz-aexG.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -0,0 +1 @@
1
+ import{z as t}from"./index-Biz-aexG.js";const e=()=>t.get("/projects"),r=o=>t.get(`/projects/${o}`);export{r as g,e as l};
@@ -1,4 +1,4 @@
1
- import{c as a}from"./index-BC3MWbS1.js";/**
1
+ import{c as a}from"./index-Biz-aexG.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c}from"./index-BC3MWbS1.js";/**
1
+ import{c}from"./index-Biz-aexG.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as e}from"./index-BC3MWbS1.js";/**
1
+ import{c as e}from"./index-Biz-aexG.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as o}from"./index-BC3MWbS1.js";/**
1
+ import{c as o}from"./index-Biz-aexG.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1 +1 @@
1
- import{z as o}from"./index-BC3MWbS1.js";const i=s=>o.get(`/projects/${s}/sessions`),c=(s,e,t)=>o.get(`/projects/${s}/sessions/${e}`,{history_limit:String(t)}),r=(s,e,t)=>o.patch(`/projects/${s}/sessions/${e}`,t),a=(s,e)=>o.delete(`/projects/${s}/sessions/${e}`),p=(s,e)=>o.post(`/projects/${s}/sessions/switch`,e),$=(s,e)=>o.post(`/projects/${s}/send`,e);export{$ as a,a as d,c as g,i as l,r,p as s};
1
+ import{z as o}from"./index-Biz-aexG.js";const i=s=>o.get(`/projects/${s}/sessions`),c=(s,e,t)=>o.get(`/projects/${s}/sessions/${e}`,{history_limit:String(t)}),r=(s,e,t)=>o.patch(`/projects/${s}/sessions/${e}`,t),a=(s,e)=>o.delete(`/projects/${s}/sessions/${e}`),p=(s,e)=>o.post(`/projects/${s}/sessions/switch`,e),$=(s,e)=>o.post(`/projects/${s}/send`,e);export{$ as a,a as d,c as g,i as l,r,p as s};
@@ -1,4 +1,4 @@
1
- import{c}from"./index-BC3MWbS1.js";/**
1
+ import{c}from"./index-Biz-aexG.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as e}from"./index-BC3MWbS1.js";/**
1
+ import{c as e}from"./index-Biz-aexG.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as e}from"./index-BC3MWbS1.js";/**
1
+ import{c as e}from"./index-Biz-aexG.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as a}from"./index-BC3MWbS1.js";/**
1
+ import{c as a}from"./index-Biz-aexG.js";/**
2
2
  * @license lucide-react v0.487.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -5,7 +5,7 @@
5
5
  <link rel="icon" type="image/svg+xml" href="./favicon.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>AgentDock</title>
8
- <script type="module" crossorigin src="./assets/index-BC3MWbS1.js"></script>
8
+ <script type="module" crossorigin src="./assets/index-Biz-aexG.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="./assets/index-DFiJR0vv.css">
10
10
  </head>
11
11
  <body class="bg-[#f5f5f7] dark:bg-[#151516]">
@@ -18,6 +18,7 @@ const local_core_weixin_gateway_js_1 = require("../services/local-ai-core/src/ch
18
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 = [];
@@ -779,6 +927,116 @@ const local_core_acp_store_js_1 = require("../services/local-ai-core/src/acp/loc
779
927
  strict_1.default.match(patchedCards[0]?.card?.elements?.[0]?.content || '', /工具确认已处理/);
780
928
  strict_1.default.equal(patchedCards[0]?.card?.elements?.some((element) => element.tag === 'action'), false);
781
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
+ });
782
1040
  (0, node_test_1.default)('lark channel folds tool result output in progress cards', async () => {
783
1041
  const createdCards = [];
784
1042
  const patchedCards = [];
@@ -1310,6 +1568,80 @@ const local_core_acp_store_js_1 = require("../services/local-ai-core/src/acp/loc
1310
1568
  line.includes('sender=user-1')));
1311
1569
  strict_1.default.ok(logs.some((line) => line.includes('handling message event') && line.includes('contentBytes=')));
1312
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
+ });
1313
1645
  (0, node_test_1.default)('ACP skips empty generic running tool updates', () => {
1314
1646
  const appended = [];
1315
1647
  const emitted = [];
@@ -1696,6 +2028,8 @@ const local_core_acp_store_js_1 = require("../services/local-ai-core/src/acp/loc
1696
2028
  ticket: 'ticket-1',
1697
2029
  expiresIn: 180,
1698
2030
  qrCodeUrl: 'https://liteapp.weixin.qq.com/q/test?qrcode=ticket-1&bot_type=3',
2031
+ instanceId: 'default',
2032
+ displayName: 'WeChat 1',
1699
2033
  });
1700
2034
  strict_1.default.equal(requests[0]?.url, 'https://ilinkai.weixin.qq.com/ilink/bot/get_bot_qrcode?bot_type=3');
1701
2035
  strict_1.default.equal(requests[0]?.headers.has('Authorization'), false);
@@ -1764,6 +2098,177 @@ const local_core_acp_store_js_1 = require("../services/local-ai-core/src/acp/loc
1764
2098
  (0, node_fs_1.rmSync)(stateDir, { recursive: true, force: true });
1765
2099
  }
1766
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
+ });
1767
2272
  (0, node_test_1.default)('weixin inbound message handling is idempotent by message identity', async () => {
1768
2273
  const sentThreadMessages = [];
1769
2274
  const users = new Map();