@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.
- package/README.md +9 -0
- package/dist/renderer/assets/{Badge-BRmQIqbd.js → Badge-C2K5apfV.js} +1 -1
- package/dist/renderer/assets/{Chat-D94ODABB.js → Chat-DI2YeVKb.js} +1 -1
- package/dist/renderer/assets/{ChatMarkdown-DqW7OUvV.js → ChatMarkdown-DMAr15Oi.js} +1 -1
- package/dist/renderer/assets/{Config-9lgzpTCp.js → Config-7rP3EuUf.js} +2 -2
- package/dist/renderer/assets/CronList-81QslYE8.js +1 -0
- package/dist/renderer/assets/{Dashboard-COISXbQy.js → Dashboard-Db2T7rSZ.js} +2 -2
- package/dist/renderer/assets/{EmptyState-w5-I9zg7.js → EmptyState-BBJ3cTvB.js} +1 -1
- package/dist/renderer/assets/{KnowledgeDetail-DVxwrTLw.js → KnowledgeDetail-Dz_6flG3.js} +2 -2
- package/dist/renderer/assets/{KnowledgeHome-BKkubcwi.js → KnowledgeHome-DIh046mN.js} +3 -3
- package/dist/renderer/assets/Logs-BXBtHJLf.js +1 -0
- package/dist/renderer/assets/{Modal-Ceu_MP2e.js → Modal-BB2Av_Xs.js} +1 -1
- package/dist/renderer/assets/{Page-CpSczAz8.js → Page-DPSdvMgU.js} +1 -1
- package/dist/renderer/assets/{ProjectDetail-FJ_bNXLF.js → ProjectDetail-C6EWGvGg.js} +1 -1
- package/dist/renderer/assets/{ProjectList-DZKL4K9C.js → ProjectList-BBo5ygnS.js} +1 -1
- package/dist/renderer/assets/{Select-BHQk6qCU.js → Select-CNX-Tx_O.js} +2 -7
- package/dist/renderer/assets/SessionChat-BIn46jqZ.js +1 -0
- package/dist/renderer/assets/SessionList-CZt6f1Rd.js +1 -0
- package/dist/renderer/assets/{ThreadChat-D3s4GXGh.js → ThreadChat-BotD0gKQ.js} +7 -7
- package/dist/renderer/assets/Workspace-C_4tdpjX.js +14 -0
- package/dist/renderer/assets/{arrow-left-Dtal-eOi.js → arrow-left-cHr5wkpq.js} +1 -1
- package/dist/renderer/assets/{book-open-q814R_Ov.js → book-open-4nx5C8T-.js} +1 -1
- package/dist/renderer/assets/{check-DjfCdLvw.js → check-exTS-R-x.js} +1 -1
- package/dist/renderer/assets/chevron-down-DSvxXQa0.js +6 -0
- package/dist/renderer/assets/{circle-vfhmyE8v.js → circle-DyvEovdw.js} +1 -1
- package/dist/renderer/assets/desktop-B-KVT10D.js +1 -0
- package/dist/renderer/assets/desktop-CVibRyAw.js +4 -0
- package/dist/renderer/assets/{funnel-CUQibo1I.js → funnel-DA9qVdTy.js} +1 -1
- package/dist/renderer/assets/{heart-DG9_b8P1.js → heart-DuEV0ZRJ.js} +1 -1
- package/dist/renderer/assets/{index-Ds0IN5rP.js → index-BgflqkCN.js} +3 -3
- package/dist/renderer/assets/index-Biz-aexG.js +177 -0
- package/dist/renderer/assets/{index-3CzgyQYB.css → index-DFiJR0vv.css} +1 -1
- package/dist/renderer/assets/{pencil-Dij0LlRY.js → pencil-DBDxeNmO.js} +1 -1
- package/dist/renderer/assets/{play-CPFGjIyJ.js → play-IzlP83_7.js} +1 -1
- package/dist/renderer/assets/{plug-DH3RKQyn.js → plug-CuT-qFnW.js} +1 -1
- package/dist/renderer/assets/{plus-afkK9_Y8.js → plus-DEqgkn63.js} +1 -1
- package/dist/renderer/assets/projects-DDtDN1rc.js +1 -0
- package/dist/renderer/assets/{save-B-cKgWcy.js → save-D4sRo07y.js} +1 -1
- package/dist/renderer/assets/{search-DR6PeYkt.js → search-DbPZLuV8.js} +1 -1
- package/dist/renderer/assets/{server-BM6KOB5a.js → server-D1jUQmwN.js} +1 -1
- package/dist/renderer/assets/{session-utils-BDuThM0a.js → session-utils-Aife8ep2.js} +1 -1
- package/dist/renderer/assets/{sessions-C1yKps4T.js → sessions-DaH3H7gC.js} +1 -1
- package/dist/renderer/assets/{shield-check-BFjMG-r2.js → shield-check-BGwUFWNr.js} +1 -1
- package/dist/renderer/assets/{trash-2-CW1heMCf.js → trash-2-CIFBioBa.js} +1 -1
- package/dist/renderer/assets/{user-r_2sHG7m.js → user-CnKcvHdo.js} +1 -1
- package/dist/renderer/assets/{wifi-off-VMUokMpE.js → wifi-off-ClO53WGa.js} +1 -1
- package/dist/renderer/index.html +2 -2
- package/dist-electron/electron/local-core-refactor.test.js +812 -3
- package/dist-electron/electron/plugin-kernel.test.js +74 -14
- package/dist-electron/packages/core-sdk/src/index.js +40 -20
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +5 -1
- package/dist-electron/services/local-ai-core/src/acp/workspace-acp-permissions.js +55 -3
- package/dist-electron/services/local-ai-core/src/channel/lark/cards.js +210 -0
- package/dist-electron/services/local-ai-core/src/channel/lark/config.js +54 -0
- package/dist-electron/services/local-ai-core/src/{gateway → channel/lark}/local-core-lark-gateway.js +365 -383
- package/dist-electron/services/local-ai-core/src/channel/lark/registration.js +50 -0
- package/dist-electron/services/local-ai-core/src/channel/lark/runtime-state.js +191 -0
- package/dist-electron/services/local-ai-core/src/channel/lark/types.js +2 -0
- package/dist-electron/services/local-ai-core/src/channel/shared/file-utils.js +33 -0
- package/dist-electron/services/local-ai-core/src/channel/shared/plugin.js +31 -0
- package/dist-electron/services/local-ai-core/src/channel/weixin/config.js +125 -0
- package/dist-electron/services/local-ai-core/src/{gateway → channel/weixin}/local-core-weixin-gateway.js +262 -258
- package/dist-electron/services/local-ai-core/src/channel/weixin/transport.js +169 -0
- package/dist-electron/services/local-ai-core/src/channel/weixin/types.js +2 -0
- package/dist-electron/services/local-ai-core/src/cli/lac.js +41 -1
- package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +5 -0
- package/dist-electron/services/local-ai-core/src/plugins/builtin/channel-lark-plugin.js +19 -40
- package/dist-electron/services/local-ai-core/src/plugins/builtin/channel-weixin-plugin.js +19 -40
- package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +96 -44
- package/dist-electron/services/local-ai-core/src/runtime/server.js +20 -8
- package/dist-electron/shared/desktop.js +24 -1
- package/package.json +1 -1
- package/dist/renderer/assets/CronList-BYZ-x_bp.js +0 -1
- package/dist/renderer/assets/Logs-D7HxZ8Nb.js +0 -1
- package/dist/renderer/assets/SessionChat-DpgSXF3m.js +0 -1
- package/dist/renderer/assets/SessionList-CU6drQ4N.js +0 -1
- package/dist/renderer/assets/Workspace-B7looTVo.js +0 -14
- package/dist/renderer/assets/desktop-BPIxWbkM.js +0 -1
- package/dist/renderer/assets/desktop-DZHC4PDB.js +0 -3
- package/dist/renderer/assets/index-Bn8ane6_.js +0 -177
- 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/
|
|
18
|
-
const local_core_lark_gateway_js_1 = require("../services/local-ai-core/src/
|
|
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();
|