@kafca/agentdock 0.1.22 → 0.1.24
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 +4 -0
- package/dist/renderer/assets/{Badge-B_rgzxYO.js → Badge-CXcUMqb-.js} +1 -1
- package/dist/renderer/assets/{Chat-CYtp6m7T.js → Chat-BRxdPzi-.js} +1 -1
- package/dist/renderer/assets/{ChatMarkdown-DOBMwa9d.js → ChatMarkdown-sWGBIfIb.js} +1 -1
- package/dist/renderer/assets/{Config-DppMSPHF.js → Config-DqxuxIGg.js} +1 -1
- package/dist/renderer/assets/{CronList-BDtB1SuA.js → CronList-OgHxbti5.js} +1 -1
- package/dist/renderer/assets/{Dashboard-iFlRnd7Y.js → Dashboard-DmklXiH1.js} +2 -2
- package/dist/renderer/assets/{EmptyState-bKDt_T88.js → EmptyState-BvDmi8dq.js} +1 -1
- package/dist/renderer/assets/{KnowledgeDetail-CKPhKPIg.js → KnowledgeDetail-Bj59Hdpo.js} +2 -2
- package/dist/renderer/assets/{KnowledgeHome-CFeJre6i.js → KnowledgeHome-CigQ-nSo.js} +3 -3
- package/dist/renderer/assets/{Logs-CyxSVOFK.js → Logs-Bfc7gBpY.js} +1 -1
- package/dist/renderer/assets/{Modal-CxPnk1Ey.js → Modal-CfmiFknz.js} +1 -1
- package/dist/renderer/assets/{Page-kU9vubzj.js → Page-BxtLEw_b.js} +1 -1
- package/dist/renderer/assets/{ProjectDetail-D9FY9NWZ.js → ProjectDetail-CcUphxGm.js} +1 -1
- package/dist/renderer/assets/{ProjectList-DIwr2TXg.js → ProjectList-CVkvDIfy.js} +1 -1
- package/dist/renderer/assets/{Select-BaWoz5Dn.js → Select-DgQMZ0MD.js} +1 -1
- package/dist/renderer/assets/{SessionChat-Cm9CW8Zx.js → SessionChat-CRmGKfBx.js} +1 -1
- package/dist/renderer/assets/{SessionList-UPnWlXXO.js → SessionList-D0E8XbYw.js} +1 -1
- package/dist/renderer/assets/ThreadChat-DUgqZSBB.js +23 -0
- package/dist/renderer/assets/Workspace-Q4g4rlRT.js +14 -0
- package/dist/renderer/assets/{arrow-left-BmZAVGV7.js → arrow-left-BySN_zhF.js} +1 -1
- package/dist/renderer/assets/{book-open-DZ3GXFgI.js → book-open-BGlrnrTc.js} +1 -1
- package/dist/renderer/assets/{check-CeVSvDCv.js → check-BiJogEa5.js} +1 -1
- package/dist/renderer/assets/{chevron-down-yryuMoQ-.js → chevron-down-D2fHRJv6.js} +1 -1
- package/dist/renderer/assets/{circle-eASsFISH.js → circle-CIcjixmI.js} +1 -1
- package/dist/renderer/assets/desktop-DAXY9Bxv.js +4 -0
- package/dist/renderer/assets/desktop-DyA_NYew.js +1 -0
- package/dist/renderer/assets/{funnel-pwAe2Ixp.js → funnel-BUzwb-id.js} +1 -1
- package/dist/renderer/assets/{heart-B2Sp-bCm.js → heart-DZEQ1z_V.js} +1 -1
- package/dist/renderer/assets/{index-BC3MWbS1.js → index-CB_KyLOr.js} +39 -39
- package/dist/renderer/assets/{index-Cse2Br64.js → index-DgsIo4vJ.js} +3 -3
- package/dist/renderer/assets/{pencil-BfEunUF3.js → pencil-DGL4agcl.js} +1 -1
- package/dist/renderer/assets/{play-Dcs9Lj_Z.js → play-DzasCLM4.js} +1 -1
- package/dist/renderer/assets/{plug-B_YschQe.js → plug-dubNMe_m.js} +1 -1
- package/dist/renderer/assets/{plus-Pqo-cXHQ.js → plus-DN4kML4k.js} +1 -1
- package/dist/renderer/assets/projects-migPtfRe.js +1 -0
- package/dist/renderer/assets/{save-BKM5yi1A.js → save-CNIc9tjl.js} +1 -1
- package/dist/renderer/assets/{search-BU42h10M.js → search-C0EGbF3I.js} +1 -1
- package/dist/renderer/assets/{server-DA5t3Rtr.js → server-BMxQ4zmc.js} +1 -1
- package/dist/renderer/assets/{session-utils-Cr9VhNSq.js → session-utils-Bf5O-iMi.js} +1 -1
- package/dist/renderer/assets/{sessions-bvV8ZMfX.js → sessions-BMSs6rEI.js} +1 -1
- package/dist/renderer/assets/{shield-check-BeOJKur_.js → shield-check-i3Tf1TUz.js} +1 -1
- package/dist/renderer/assets/{trash-2-CQ7HKO3q.js → trash-2-SKrcp18f.js} +1 -1
- package/dist/renderer/assets/{user-CtFjY9SV.js → user-cNUZddjZ.js} +1 -1
- package/dist/renderer/assets/{wifi-off-Ci5m0ysf.js → wifi-off-Dyb84ANR.js} +1 -1
- package/dist/renderer/index.html +1 -1
- package/dist-electron/electron/channel-content.test.js +29 -0
- package/dist-electron/electron/lac-cli.test.js +49 -4
- package/dist-electron/electron/local-core-acp-content.test.js +39 -0
- package/dist-electron/electron/local-core-contracts.test.js +67 -0
- package/dist-electron/electron/local-core-refactor.test.js +628 -0
- package/dist-electron/electron/plugin-kernel.test.js +74 -14
- package/dist-electron/electron/workspace-task-store.test.js +124 -0
- package/dist-electron/packages/contracts/src/local-core.js +110 -0
- package/dist-electron/packages/core-sdk/src/index.js +40 -20
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-content.js +25 -4
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-response-processor.js +3 -2
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-store.js +44 -28
- 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 +15 -6
- 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/channel/lark/local-core-lark-gateway.js +280 -496
- 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/content.js +23 -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/channel/weixin/local-core-weixin-gateway.js +164 -352
- 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 +19 -16
- 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 +18 -39
- package/dist-electron/services/local-ai-core/src/plugins/builtin/channel-weixin-plugin.js +18 -39
- package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +55 -44
- package/dist-electron/services/local-ai-core/src/runtime/server.js +10 -8
- package/dist-electron/services/local-ai-core/src/scheduler/job-id.js +15 -0
- package/dist-electron/services/local-ai-core/src/scheduler/scheduler-service.js +30 -5
- package/dist-electron/shared/desktop.js +37 -5
- package/dist-electron/src/pages/Threads/thread-chat-model.js +57 -0
- package/dist-electron/src/pages/Threads/thread-chat-permission.test.js +70 -0
- package/package.json +1 -1
- package/dist/renderer/assets/ThreadChat-YvVsACW3.js +0 -23
- package/dist/renderer/assets/Workspace-ST7V8u6T.js +0 -14
- package/dist/renderer/assets/desktop-Che1cbpE.js +0 -4
- package/dist/renderer/assets/desktop-DkrP_s-b.js +0 -1
- package/dist/renderer/assets/projects-BCj0k_Gy.js +0 -1
|
@@ -39,16 +39,12 @@ const node_crypto_1 = require("node:crypto");
|
|
|
39
39
|
const node_fs_1 = require("node:fs");
|
|
40
40
|
const node_path_1 = require("node:path");
|
|
41
41
|
const desktop_js_1 = require("../../../../../shared/desktop.js");
|
|
42
|
+
const content_js_1 = require("../shared/content.js");
|
|
42
43
|
const file_utils_js_1 = require("../shared/file-utils.js");
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const parts = normalized.split(' - ');
|
|
48
|
-
if (parts.length <= 2)
|
|
49
|
-
return normalized;
|
|
50
|
-
return parts.slice(0, 2).join(' - ');
|
|
51
|
-
}
|
|
44
|
+
const cards_js_1 = require("./cards.js");
|
|
45
|
+
const config_js_1 = require("./config.js");
|
|
46
|
+
const registration_js_1 = require("./registration.js");
|
|
47
|
+
const runtime_state_js_1 = require("./runtime-state.js");
|
|
52
48
|
const PAIRING_EXPIRY_MS = 10 * 60 * 1000;
|
|
53
49
|
const LARK_MAX_UPLOAD_FILE_SIZE = 30 * 1024 * 1024;
|
|
54
50
|
class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
@@ -73,25 +69,30 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
73
69
|
async refreshBindings() {
|
|
74
70
|
const config = await this.options.readConfig();
|
|
75
71
|
const bindings = this.collectBindings(config);
|
|
76
|
-
const
|
|
77
|
-
for (const
|
|
78
|
-
if (!
|
|
79
|
-
await this.
|
|
72
|
+
const nextKeys = new Set(bindings.map((binding) => (0, config_js_1.runtimeKey)(binding.workspaceId, binding.instanceId)));
|
|
73
|
+
for (const key of [...this.runtime.keys()]) {
|
|
74
|
+
if (!nextKeys.has(key)) {
|
|
75
|
+
await this.stopWorkspaceKey(key);
|
|
80
76
|
}
|
|
81
77
|
}
|
|
82
78
|
for (const binding of bindings) {
|
|
83
|
-
const
|
|
79
|
+
const key = (0, config_js_1.runtimeKey)(binding.workspaceId, binding.instanceId);
|
|
80
|
+
const current = this.runtime.get(key);
|
|
84
81
|
if (!binding.enabled) {
|
|
85
82
|
if (current) {
|
|
86
|
-
await this.
|
|
83
|
+
await this.stopWorkspaceKey(key);
|
|
87
84
|
}
|
|
88
85
|
else {
|
|
89
|
-
this.runtime.set(
|
|
86
|
+
this.runtime.set(key, {
|
|
90
87
|
workspaceId: binding.workspaceId,
|
|
88
|
+
instanceId: binding.instanceId,
|
|
89
|
+
displayName: binding.displayName,
|
|
90
|
+
platformKey: binding.platformKey,
|
|
91
91
|
enabled: false,
|
|
92
92
|
status: 'disabled',
|
|
93
93
|
connected: false,
|
|
94
94
|
appId: binding.appId,
|
|
95
|
+
autoApprove: binding.autoApprove,
|
|
95
96
|
cardActionsEnabled: binding.cardActionsEnabled,
|
|
96
97
|
});
|
|
97
98
|
}
|
|
@@ -104,23 +105,70 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
104
105
|
}
|
|
105
106
|
this.notifyRuntimeStateChanged();
|
|
106
107
|
}
|
|
107
|
-
async testConnection(workspaceId) {
|
|
108
|
-
const binding = await this.getBinding(workspaceId);
|
|
108
|
+
async testConnection(workspaceId, instanceId) {
|
|
109
|
+
const binding = await this.getBinding(workspaceId, instanceId);
|
|
109
110
|
const result = await this.createSdkClientResult(binding);
|
|
110
111
|
return {
|
|
111
112
|
...result,
|
|
112
113
|
platform: 'lark',
|
|
113
114
|
workspaceId,
|
|
115
|
+
instanceId: binding.instanceId,
|
|
114
116
|
};
|
|
115
117
|
}
|
|
116
|
-
async enable(workspaceId) {
|
|
117
|
-
const binding = await this.getBinding(workspaceId);
|
|
118
|
+
async enable(workspaceId, instanceId) {
|
|
119
|
+
const binding = await this.getBinding(workspaceId, instanceId);
|
|
118
120
|
await this.startWorkspace(binding);
|
|
119
|
-
return this.getStatus(workspaceId);
|
|
121
|
+
return this.getStatus(workspaceId, binding.instanceId);
|
|
122
|
+
}
|
|
123
|
+
async disable(workspaceId, instanceId) {
|
|
124
|
+
await this.stopWorkspace(workspaceId, instanceId);
|
|
125
|
+
return this.getStatus(workspaceId, instanceId);
|
|
126
|
+
}
|
|
127
|
+
async getQrCode(workspaceId, instanceId) {
|
|
128
|
+
const binding = await this.getBinding(workspaceId, instanceId);
|
|
129
|
+
const data = await (0, registration_js_1.requestAppRegistration)(binding);
|
|
130
|
+
const ticket = String(data.device_code || '').trim();
|
|
131
|
+
const userCode = String(data.user_code || '').trim();
|
|
132
|
+
if (!ticket || !userCode) {
|
|
133
|
+
throw new Error('Lark app registration did not return a device code and user code.');
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
ticket,
|
|
137
|
+
expiresIn: Number(data.expires_in || registration_js_1.DEFAULT_LARK_QR_EXPIRES_IN) || registration_js_1.DEFAULT_LARK_QR_EXPIRES_IN,
|
|
138
|
+
interval: Number(data.interval || 5) || 5,
|
|
139
|
+
qrCodeUrl: `${(0, registration_js_1.getLarkOpenBase)(binding)}${registration_js_1.LARK_APP_REGISTRATION_SETUP_PATH}?user_code=${encodeURIComponent(userCode)}&from=openclaw`,
|
|
140
|
+
instanceId: binding.instanceId,
|
|
141
|
+
displayName: binding.displayName,
|
|
142
|
+
};
|
|
120
143
|
}
|
|
121
|
-
async
|
|
122
|
-
await this.
|
|
123
|
-
|
|
144
|
+
async checkQrCodeStatus(workspaceId, ticket, instanceId) {
|
|
145
|
+
const binding = await this.getBinding(workspaceId, instanceId);
|
|
146
|
+
const data = await (0, registration_js_1.pollAppRegistration)(binding, ticket);
|
|
147
|
+
const error = String(data.error || '').trim();
|
|
148
|
+
if (error === 'authorization_pending' || error === 'slow_down') {
|
|
149
|
+
return { status: 'wait' };
|
|
150
|
+
}
|
|
151
|
+
if (error === 'expired_token' || error === 'invalid_grant') {
|
|
152
|
+
return { status: 'expired' };
|
|
153
|
+
}
|
|
154
|
+
if (error === 'access_denied') {
|
|
155
|
+
throw new Error('Lark app registration was rejected.');
|
|
156
|
+
}
|
|
157
|
+
if (error) {
|
|
158
|
+
throw new Error(`Lark app registration failed: ${String(data.error_description || error)}`);
|
|
159
|
+
}
|
|
160
|
+
const appId = String(data.client_id || '').trim();
|
|
161
|
+
const appSecret = String(data.client_secret || '').trim();
|
|
162
|
+
if (!appId || !appSecret) {
|
|
163
|
+
return { status: 'wait' };
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
status: 'confirmed',
|
|
167
|
+
credentials: {
|
|
168
|
+
appId,
|
|
169
|
+
appSecret,
|
|
170
|
+
},
|
|
171
|
+
};
|
|
124
172
|
}
|
|
125
173
|
async sendScheduledCard(workspaceId, chatId, text) {
|
|
126
174
|
return this.sendImmediateCard(workspaceId, chatId, text);
|
|
@@ -129,7 +177,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
129
177
|
return this.sendScheduledCard(workspaceId, route.channelId, text);
|
|
130
178
|
}
|
|
131
179
|
async sendOutboundMessage(workspaceId, input) {
|
|
132
|
-
const state = this.
|
|
180
|
+
const state = this.resolveRuntimeState(workspaceId, input.route?.instanceId).state;
|
|
133
181
|
if (!state?.client || !state.connected) {
|
|
134
182
|
throw new Error(`Lark workspace is not connected: ${workspaceId}`);
|
|
135
183
|
}
|
|
@@ -245,15 +293,19 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
245
293
|
}
|
|
246
294
|
this.mutedThreadBridgeCounts.set(threadId, current - 1);
|
|
247
295
|
}
|
|
248
|
-
getStatus(workspaceId) {
|
|
296
|
+
getStatus(workspaceId, instanceId) {
|
|
249
297
|
this.options.store.expirePendingPairings();
|
|
250
|
-
const
|
|
298
|
+
const resolved = this.resolveRuntimeState(workspaceId, instanceId);
|
|
299
|
+
const binding = resolved.state;
|
|
300
|
+
const platformKey = binding?.platformKey || (0, config_js_1.channelPlatformKey)('lark', resolved.instanceId);
|
|
251
301
|
const pairings = this.options.store.listPendingPairings(workspaceId)
|
|
252
|
-
.filter((row) => row.platform ===
|
|
253
|
-
const users = this.options.store.listAuthorizedUsers(workspaceId,
|
|
302
|
+
.filter((row) => row.platform === platformKey && row.expires_at >= new Date().toISOString());
|
|
303
|
+
const users = this.options.store.listAuthorizedUsers(workspaceId, platformKey);
|
|
254
304
|
return {
|
|
255
305
|
workspaceId,
|
|
256
306
|
platform: 'lark',
|
|
307
|
+
instanceId: resolved.instanceId,
|
|
308
|
+
displayName: binding?.displayName,
|
|
257
309
|
enabled: Boolean(binding?.enabled),
|
|
258
310
|
connected: Boolean(binding?.connected),
|
|
259
311
|
status: binding?.status || 'disabled',
|
|
@@ -265,16 +317,20 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
265
317
|
};
|
|
266
318
|
}
|
|
267
319
|
listStatuses() {
|
|
268
|
-
return [...this.runtime.
|
|
320
|
+
return [...this.runtime.values()]
|
|
321
|
+
.sort((a, b) => `${a.workspaceId}:${a.instanceId}`.localeCompare(`${b.workspaceId}:${b.instanceId}`))
|
|
322
|
+
.map((state) => this.getStatus(state.workspaceId, state.instanceId));
|
|
269
323
|
}
|
|
270
324
|
listPendingPairings(workspaceId) {
|
|
271
325
|
this.options.store.expirePendingPairings();
|
|
272
326
|
return this.options.store
|
|
273
|
-
.listPairingRequests(workspaceId
|
|
327
|
+
.listPairingRequests(workspaceId)
|
|
328
|
+
.filter((item) => item.platform === 'lark' || item.platform.startsWith('lark:'))
|
|
274
329
|
.filter((item) => item.status === 'pending' && item.expiresAt >= new Date().toISOString());
|
|
275
330
|
}
|
|
276
331
|
listAuthorizedUsers(workspaceId) {
|
|
277
|
-
return this.options.store.listAuthorizedUsers(workspaceId
|
|
332
|
+
return this.options.store.listAuthorizedUsers(workspaceId)
|
|
333
|
+
.filter((item) => item.platform === 'lark' || item.platform.startsWith('lark:'));
|
|
278
334
|
}
|
|
279
335
|
async start() {
|
|
280
336
|
await this.refreshBindings();
|
|
@@ -288,7 +344,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
288
344
|
if (!pairing) {
|
|
289
345
|
throw new Error(`Pairing code not found: ${code}`);
|
|
290
346
|
}
|
|
291
|
-
if (pairing.platform !== 'lark') {
|
|
347
|
+
if (pairing.platform !== 'lark' && !pairing.platform.startsWith('lark:')) {
|
|
292
348
|
throw new Error(`Pairing code ${code} is not a Lark pairing`);
|
|
293
349
|
}
|
|
294
350
|
if (pairing.status !== 'pending') {
|
|
@@ -298,12 +354,13 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
298
354
|
this.options.store.updatePairingStatus(code, 'expired');
|
|
299
355
|
throw new Error(`Pairing code ${code} has expired`);
|
|
300
356
|
}
|
|
301
|
-
const existing = this.options.store.getAuthorizedUser(pairing.workspace_id, pairing.platform_user_id);
|
|
357
|
+
const existing = this.options.store.getAuthorizedUser(pairing.workspace_id, pairing.platform_user_id, pairing.platform);
|
|
302
358
|
const userId = existing?.id || `lark-user-${(0, node_crypto_1.randomUUID)()}`;
|
|
303
359
|
const authorizedAt = new Date().toISOString();
|
|
304
360
|
this.options.store.createAuthorizedUser({
|
|
305
361
|
id: userId,
|
|
306
362
|
workspace_id: pairing.workspace_id,
|
|
363
|
+
platform: pairing.platform,
|
|
307
364
|
platform_user_id: pairing.platform_user_id,
|
|
308
365
|
chat_id: pairing.chat_id,
|
|
309
366
|
display_name: pairing.display_name,
|
|
@@ -312,7 +369,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
312
369
|
});
|
|
313
370
|
this.options.store.updatePairingStatus(code, 'approved');
|
|
314
371
|
this.notifyRuntimeStateChanged();
|
|
315
|
-
const user = this.options.store.listAuthorizedUsers(pairing.workspace_id,
|
|
372
|
+
const user = this.options.store.listAuthorizedUsers(pairing.workspace_id, pairing.platform).find((entry) => entry.id === userId);
|
|
316
373
|
if (!user) {
|
|
317
374
|
throw new Error('Authorized user lookup failed after approval');
|
|
318
375
|
}
|
|
@@ -338,12 +395,14 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
338
395
|
this.options.log?.(`localcore-lark bridge route miss for sessionKey=${sessionKey} type=${event.type}`);
|
|
339
396
|
return;
|
|
340
397
|
}
|
|
341
|
-
const
|
|
398
|
+
const routeInstanceId = route.instanceId || 'default';
|
|
399
|
+
const routePlatformKey = route.platformKey || (0, config_js_1.channelPlatformKey)('lark', routeInstanceId);
|
|
400
|
+
const state = this.runtime.get((0, config_js_1.runtimeKey)(route.workspaceId, routeInstanceId)) || this.runtime.get(route.workspaceId);
|
|
342
401
|
if (!state?.client || !state.connected) {
|
|
343
402
|
this.options.log?.(`localcore-lark bridge event ignored because workspace is not connected: ${route.workspaceId}`);
|
|
344
403
|
return;
|
|
345
404
|
}
|
|
346
|
-
const initialBinding = this.options.store.getPlatformThreadBinding(route.workspaceId, route.chatId, route.platformUserId);
|
|
405
|
+
const initialBinding = this.options.store.getPlatformThreadBinding(route.workspaceId, route.chatId, route.platformUserId, routePlatformKey);
|
|
347
406
|
if (!initialBinding) {
|
|
348
407
|
this.options.log?.(`localcore-lark bridge binding miss for workspace=${route.workspaceId} chat=${route.chatId} user=${route.platformUserId}`);
|
|
349
408
|
return;
|
|
@@ -362,7 +421,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
362
421
|
const current = previous
|
|
363
422
|
.catch(() => undefined)
|
|
364
423
|
.then(async () => {
|
|
365
|
-
const binding = this.options.store.getPlatformThreadBinding(route.workspaceId, route.chatId, route.platformUserId);
|
|
424
|
+
const binding = this.options.store.getPlatformThreadBinding(route.workspaceId, route.chatId, route.platformUserId, routePlatformKey);
|
|
366
425
|
if (!binding) {
|
|
367
426
|
this.options.log?.(`localcore-lark bridge binding disappeared for sessionKey=${sessionKey}`);
|
|
368
427
|
return;
|
|
@@ -391,7 +450,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
391
450
|
this.options.store.clearPlatformThreadMessageId(route.workspaceId, route.chatId, route.platformUserId);
|
|
392
451
|
}
|
|
393
452
|
if (event.type === 'buttons') {
|
|
394
|
-
const permissionCard =
|
|
453
|
+
const permissionCard = (0, cards_js_1.renderPermissionCard)(turn, event, Boolean(state.cardActionsEnabled));
|
|
395
454
|
if (permissionCard.text || permissionCard.buttonRows.length > 0) {
|
|
396
455
|
if (!turn.permissionMessageId) {
|
|
397
456
|
const createdId = await this.sendTextAsCard(state, route.chatId, permissionCard.text, permissionCard.buttonRows, sessionKey, binding.thread_id);
|
|
@@ -409,12 +468,12 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
409
468
|
return;
|
|
410
469
|
}
|
|
411
470
|
this.consumeBridgeEvent(turn, event);
|
|
412
|
-
const rendered =
|
|
471
|
+
const rendered = (0, runtime_state_js_1.renderLarkBridgeEventMessage)(turn, event);
|
|
413
472
|
if (!rendered.text && rendered.buttonRows.length === 0) {
|
|
414
473
|
this.options.log?.(`localcore-lark bridge event produced empty render for sessionKey=${sessionKey} type=${event.type}`);
|
|
415
474
|
return;
|
|
416
475
|
}
|
|
417
|
-
const existingMessageId =
|
|
476
|
+
const existingMessageId = (0, runtime_state_js_1.getLarkRenderedMessageId)(turn, rendered);
|
|
418
477
|
const shouldThrottle = event.type === 'update_message' &&
|
|
419
478
|
rendered.isFinal &&
|
|
420
479
|
existingMessageId &&
|
|
@@ -426,9 +485,9 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
426
485
|
if (!existingMessageId) {
|
|
427
486
|
const createdId = await this.sendTextAsCard(state, route.chatId, rendered.text, rendered.buttonRows, sessionKey, binding.thread_id);
|
|
428
487
|
if (createdId) {
|
|
429
|
-
|
|
488
|
+
(0, runtime_state_js_1.setLarkRenderedMessageId)(turn, rendered, createdId);
|
|
430
489
|
if (rendered.isFinal) {
|
|
431
|
-
this.options.store.updatePlatformThreadMessageId(route.workspaceId, route.chatId, route.platformUserId, createdId);
|
|
490
|
+
this.options.store.updatePlatformThreadMessageId(route.workspaceId, route.chatId, route.platformUserId, createdId, routePlatformKey);
|
|
432
491
|
}
|
|
433
492
|
this.options.log?.(`localcore-lark sent new card message ${createdId} for sessionKey=${sessionKey}`);
|
|
434
493
|
}
|
|
@@ -452,6 +511,8 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
452
511
|
await current;
|
|
453
512
|
}
|
|
454
513
|
async handleInboundMessage(input) {
|
|
514
|
+
const instanceId = input.instanceId || 'default';
|
|
515
|
+
const platformKey = input.platformKey || (0, config_js_1.channelPlatformKey)('lark', instanceId);
|
|
455
516
|
this.options.eventBus.emit({
|
|
456
517
|
type: 'platform.message.received',
|
|
457
518
|
payload: {
|
|
@@ -465,27 +526,38 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
465
526
|
},
|
|
466
527
|
});
|
|
467
528
|
this.options.store.expirePendingPairings();
|
|
468
|
-
const
|
|
469
|
-
let
|
|
529
|
+
const runtimeState = this.resolveRuntimeState(input.workspaceId, instanceId).state;
|
|
530
|
+
let binding;
|
|
531
|
+
try {
|
|
532
|
+
binding = await this.getBinding(input.workspaceId, instanceId);
|
|
533
|
+
}
|
|
534
|
+
catch (error) {
|
|
535
|
+
if (!runtimeState) {
|
|
536
|
+
throw error;
|
|
537
|
+
}
|
|
538
|
+
this.options.log?.(`localcore-lark using active runtime binding snapshot for ${input.workspaceId}/${instanceId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
539
|
+
}
|
|
540
|
+
let authorized = this.options.store.getAuthorizedUser(input.workspaceId, input.platformUserId, platformKey);
|
|
470
541
|
if (!authorized) {
|
|
471
|
-
if (binding
|
|
542
|
+
if (binding?.autoApprove || runtimeState?.autoApprove) {
|
|
472
543
|
const authorizedAt = new Date().toISOString();
|
|
473
544
|
this.options.store.createAuthorizedUser({
|
|
474
545
|
id: `lark-user-${(0, node_crypto_1.randomUUID)()}`,
|
|
475
546
|
workspace_id: input.workspaceId,
|
|
547
|
+
platform: platformKey,
|
|
476
548
|
platform_user_id: input.platformUserId,
|
|
477
549
|
chat_id: input.chatId,
|
|
478
550
|
display_name: input.displayName,
|
|
479
551
|
thread_id: null,
|
|
480
552
|
authorized_at: authorizedAt,
|
|
481
553
|
});
|
|
482
|
-
authorized = this.options.store.getAuthorizedUser(input.workspaceId, input.platformUserId);
|
|
554
|
+
authorized = this.options.store.getAuthorizedUser(input.workspaceId, input.platformUserId, platformKey);
|
|
483
555
|
this.options.log?.(`localcore-lark auto-approved user for ${input.workspaceId}: ${input.platformUserId}`);
|
|
484
556
|
this.notifyRuntimeStateChanged();
|
|
485
557
|
}
|
|
486
558
|
}
|
|
487
559
|
if (!authorized) {
|
|
488
|
-
const existingPending = this.options.store.listPendingPairings(input.workspaceId).find((item) => item.platform ===
|
|
560
|
+
const existingPending = this.options.store.listPendingPairings(input.workspaceId).find((item) => item.platform === platformKey && item.platform_user_id === input.platformUserId && item.chat_id === input.chatId && item.status === 'pending');
|
|
489
561
|
let pairingCode = existingPending?.code || '';
|
|
490
562
|
if (!existingPending) {
|
|
491
563
|
const now = new Date();
|
|
@@ -493,6 +565,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
493
565
|
this.options.store.createPairingRequest({
|
|
494
566
|
code: pairingCode,
|
|
495
567
|
workspace_id: input.workspaceId,
|
|
568
|
+
platform: platformKey,
|
|
496
569
|
platform_user_id: input.platformUserId,
|
|
497
570
|
chat_id: input.chatId,
|
|
498
571
|
display_name: input.displayName,
|
|
@@ -502,19 +575,20 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
502
575
|
});
|
|
503
576
|
this.notifyRuntimeStateChanged();
|
|
504
577
|
}
|
|
505
|
-
await this.sendImmediateCard(input.workspaceId, input.chatId,
|
|
578
|
+
await this.sendImmediateCard(input.workspaceId, input.chatId, (0, cards_js_1.renderPendingPairingCard)(pairingCode), instanceId);
|
|
506
579
|
return { paired: false };
|
|
507
580
|
}
|
|
508
581
|
const router = this.options.getWorkspaceRouter();
|
|
509
|
-
const threadBinding = this.options.store.getPlatformThreadBinding(input.workspaceId, input.chatId, input.platformUserId);
|
|
582
|
+
const threadBinding = this.options.store.getPlatformThreadBinding(input.workspaceId, input.chatId, input.platformUserId, platformKey);
|
|
510
583
|
let threadId = threadBinding?.thread_id || authorized.thread_id || '';
|
|
511
584
|
if (!threadId) {
|
|
512
585
|
const thread = await router.createThread(input.workspaceId, input.displayName || `Lark ${input.chatId}`);
|
|
513
586
|
threadId = thread.id;
|
|
514
|
-
this.options.store.updateAuthorizedUserThread(input.workspaceId, input.platformUserId, threadId);
|
|
587
|
+
this.options.store.updateAuthorizedUserThread(input.workspaceId, input.platformUserId, threadId, platformKey);
|
|
515
588
|
const now = new Date().toISOString();
|
|
516
589
|
this.options.store.upsertPlatformThreadBinding({
|
|
517
590
|
workspace_id: input.workspaceId,
|
|
591
|
+
platform: platformKey,
|
|
518
592
|
chat_id: input.chatId,
|
|
519
593
|
platform_user_id: input.platformUserId,
|
|
520
594
|
thread_id: threadId,
|
|
@@ -534,20 +608,23 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
534
608
|
const effectiveSessionKey = this.options.getWorkspaceRouter().getThreadSessionKey(threadId);
|
|
535
609
|
this.threadRouting.set(effectiveSessionKey, {
|
|
536
610
|
workspaceId: input.workspaceId,
|
|
611
|
+
instanceId,
|
|
612
|
+
platformKey,
|
|
537
613
|
platformUserId: input.platformUserId,
|
|
538
614
|
chatId: input.chatId,
|
|
539
615
|
threadId,
|
|
540
616
|
});
|
|
541
617
|
const acknowledgement = this.createTurnState(effectiveSessionKey, input.messageId);
|
|
542
|
-
await this.addAcknowledgementReaction(input.workspaceId, input.messageId, acknowledgement);
|
|
618
|
+
await this.addAcknowledgementReaction(input.workspaceId, input.messageId, acknowledgement, instanceId);
|
|
543
619
|
const slashCommand = this.parseSlashCommand(input.text);
|
|
544
620
|
if (slashCommand?.name === 'new') {
|
|
545
621
|
const title = slashCommand.args.join(' ').trim() || `${input.displayName || 'Lark'} ${new Date().toLocaleTimeString()}`;
|
|
546
622
|
const nextThread = await router.createThread(input.workspaceId, title);
|
|
547
623
|
const now = new Date().toISOString();
|
|
548
|
-
this.options.store.updateAuthorizedUserThread(input.workspaceId, input.platformUserId, nextThread.id);
|
|
624
|
+
this.options.store.updateAuthorizedUserThread(input.workspaceId, input.platformUserId, nextThread.id, platformKey);
|
|
549
625
|
this.options.store.upsertPlatformThreadBinding({
|
|
550
626
|
workspace_id: input.workspaceId,
|
|
627
|
+
platform: platformKey,
|
|
551
628
|
chat_id: input.chatId,
|
|
552
629
|
platform_user_id: input.platformUserId,
|
|
553
630
|
thread_id: nextThread.id,
|
|
@@ -557,11 +634,13 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
557
634
|
});
|
|
558
635
|
this.threadRouting.set(this.options.getWorkspaceRouter().getThreadSessionKey(nextThread.id), {
|
|
559
636
|
workspaceId: input.workspaceId,
|
|
637
|
+
instanceId,
|
|
638
|
+
platformKey,
|
|
560
639
|
platformUserId: input.platformUserId,
|
|
561
640
|
chatId: input.chatId,
|
|
562
641
|
threadId: nextThread.id,
|
|
563
642
|
});
|
|
564
|
-
await this.sendImmediateCard(input.workspaceId, input.chatId, '**已开始新会话**');
|
|
643
|
+
await this.sendImmediateCard(input.workspaceId, input.chatId, '**已开始新会话**', instanceId);
|
|
565
644
|
return { paired: true, threadId: nextThread.id };
|
|
566
645
|
}
|
|
567
646
|
const latestRun = this.options.store.getLatestRunForThread(threadId);
|
|
@@ -572,17 +651,29 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
572
651
|
}
|
|
573
652
|
this.options.store.clearPlatformThreadMessageId(input.workspaceId, input.chatId, input.platformUserId);
|
|
574
653
|
const wrappedText = (0, desktop_js_1.wrapUserMessageWithSchedulerProtocol)(input.text);
|
|
575
|
-
await router.sendThreadMessage(threadId,
|
|
654
|
+
await router.sendThreadMessage(threadId, (0, content_js_1.createChannelThreadMessageInput)(wrappedText, input.contentParts));
|
|
576
655
|
return { paired: true, threadId };
|
|
577
656
|
}
|
|
578
657
|
close() {
|
|
579
|
-
return Promise.all([...this.runtime.keys()].map((
|
|
658
|
+
return Promise.all([...this.runtime.keys()].map((key) => this.stopWorkspaceKey(key))).then(() => undefined);
|
|
580
659
|
}
|
|
581
|
-
|
|
660
|
+
resolveRuntimeState(workspaceId, instanceId) {
|
|
661
|
+
if (instanceId) {
|
|
662
|
+
const state = this.runtime.get((0, config_js_1.runtimeKey)(workspaceId, instanceId)) || (instanceId === 'default' ? this.runtime.get(workspaceId) : undefined);
|
|
663
|
+
return { instanceId, state };
|
|
664
|
+
}
|
|
665
|
+
const states = [...this.runtime.values()].filter((entry) => entry.workspaceId === workspaceId);
|
|
666
|
+
const state = states.find((entry) => entry.instanceId === 'default') || states[0];
|
|
667
|
+
return { instanceId: state?.instanceId || 'default', state: state || this.runtime.get(workspaceId) };
|
|
668
|
+
}
|
|
669
|
+
async getBinding(workspaceId, instanceId) {
|
|
582
670
|
const config = await this.options.readConfig();
|
|
583
|
-
const
|
|
671
|
+
const bindings = this.collectBindings(config).filter((entry) => entry.workspaceId === workspaceId);
|
|
672
|
+
const binding = instanceId
|
|
673
|
+
? bindings.find((entry) => entry.instanceId === instanceId)
|
|
674
|
+
: bindings.find((entry) => entry.instanceId === 'default') || bindings[0];
|
|
584
675
|
if (!binding) {
|
|
585
|
-
throw new Error(`No Lark binding configured for workspace "${workspaceId}"`);
|
|
676
|
+
throw new Error(`No Lark binding configured for workspace "${workspaceId}"${instanceId ? ` instance "${instanceId}"` : ''}`);
|
|
586
677
|
}
|
|
587
678
|
return binding;
|
|
588
679
|
}
|
|
@@ -600,6 +691,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
600
691
|
success: true,
|
|
601
692
|
platform: 'lark',
|
|
602
693
|
workspaceId: binding.workspaceId,
|
|
694
|
+
instanceId: binding.instanceId,
|
|
603
695
|
appId: binding.appId,
|
|
604
696
|
};
|
|
605
697
|
}
|
|
@@ -608,22 +700,28 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
608
700
|
success: false,
|
|
609
701
|
platform: 'lark',
|
|
610
702
|
workspaceId: binding.workspaceId,
|
|
703
|
+
instanceId: binding.instanceId,
|
|
611
704
|
appId: binding.appId,
|
|
612
705
|
error: error instanceof Error ? error.message : String(error),
|
|
613
706
|
};
|
|
614
707
|
}
|
|
615
708
|
}
|
|
616
709
|
async startWorkspace(binding) {
|
|
617
|
-
|
|
710
|
+
const key = (0, config_js_1.runtimeKey)(binding.workspaceId, binding.instanceId);
|
|
711
|
+
await this.stopWorkspaceKey(key);
|
|
618
712
|
const status = {
|
|
619
713
|
workspaceId: binding.workspaceId,
|
|
714
|
+
instanceId: binding.instanceId,
|
|
715
|
+
displayName: binding.displayName,
|
|
716
|
+
platformKey: binding.platformKey,
|
|
620
717
|
enabled: true,
|
|
621
718
|
status: 'starting',
|
|
622
719
|
connected: false,
|
|
623
720
|
appId: binding.appId,
|
|
721
|
+
autoApprove: binding.autoApprove,
|
|
624
722
|
cardActionsEnabled: binding.cardActionsEnabled,
|
|
625
723
|
};
|
|
626
|
-
this.runtime.set(
|
|
724
|
+
this.runtime.set(key, status);
|
|
627
725
|
this.notifyRuntimeStateChanged();
|
|
628
726
|
try {
|
|
629
727
|
this.options.log?.(`localcore-lark starting workspace=${binding.workspaceId} app=${this.maskLarkAppId(binding.appId)} cardActions=${binding.cardActionsEnabled}`);
|
|
@@ -641,15 +739,15 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
641
739
|
});
|
|
642
740
|
status.eventDispatcher.register({
|
|
643
741
|
'im.message.receive_v1': async (data) => {
|
|
644
|
-
this.options.log?.(`localcore-lark received im.message.receive_v1 for ${binding.workspaceId}: ${this.summarizeLarkEvent(data)}`);
|
|
645
|
-
void this.handleMessageEvent(binding.workspaceId, data).catch((error) => {
|
|
742
|
+
this.options.log?.(`localcore-lark received im.message.receive_v1 for ${binding.workspaceId}/${binding.instanceId}: ${this.summarizeLarkEvent(data)}`);
|
|
743
|
+
void this.handleMessageEvent(binding.workspaceId, binding.instanceId, binding.platformKey, data).catch((error) => {
|
|
646
744
|
this.options.log?.(`localcore-lark inbound message failed for ${binding.workspaceId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
647
745
|
});
|
|
648
746
|
return {};
|
|
649
747
|
},
|
|
650
748
|
'card.action.trigger': async (data) => {
|
|
651
|
-
this.options.log?.(`localcore-lark received card.action.trigger for ${binding.workspaceId}`);
|
|
652
|
-
void this.handleCardActionEvent(binding.workspaceId, data);
|
|
749
|
+
this.options.log?.(`localcore-lark received card.action.trigger for ${binding.workspaceId}/${binding.instanceId}`);
|
|
750
|
+
void this.handleCardActionEvent(binding.workspaceId, binding.instanceId, data);
|
|
653
751
|
return {};
|
|
654
752
|
},
|
|
655
753
|
});
|
|
@@ -679,26 +777,34 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
679
777
|
}
|
|
680
778
|
this.notifyRuntimeStateChanged();
|
|
681
779
|
}
|
|
682
|
-
async stopWorkspace(workspaceId) {
|
|
683
|
-
const
|
|
780
|
+
async stopWorkspace(workspaceId, instanceId) {
|
|
781
|
+
const resolved = this.resolveRuntimeState(workspaceId, instanceId);
|
|
782
|
+
await this.stopWorkspaceKey((0, config_js_1.runtimeKey)(workspaceId, resolved.instanceId));
|
|
783
|
+
}
|
|
784
|
+
async stopWorkspaceKey(key) {
|
|
785
|
+
const state = this.runtime.get(key);
|
|
684
786
|
if (!state) {
|
|
685
787
|
return;
|
|
686
788
|
}
|
|
687
|
-
this.options.log?.(`localcore-lark stopping workspace=${workspaceId} status=${state.status}`);
|
|
789
|
+
this.options.log?.(`localcore-lark stopping workspace=${state.workspaceId}/${state.instanceId} status=${state.status}`);
|
|
688
790
|
try {
|
|
689
791
|
await state.wsClient?.stop?.();
|
|
690
|
-
this.options.log?.(`localcore-lark stopped workspace=${workspaceId}`);
|
|
792
|
+
this.options.log?.(`localcore-lark stopped workspace=${state.workspaceId}/${state.instanceId}`);
|
|
691
793
|
}
|
|
692
794
|
catch (error) {
|
|
693
|
-
this.options.log?.(`localcore-lark stop failed for ${workspaceId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
795
|
+
this.options.log?.(`localcore-lark stop failed for ${state.workspaceId}/${state.instanceId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
694
796
|
// Best effort: current SDK versions may not implement stop().
|
|
695
797
|
}
|
|
696
|
-
this.runtime.set(
|
|
697
|
-
workspaceId,
|
|
798
|
+
this.runtime.set(key, {
|
|
799
|
+
workspaceId: state.workspaceId,
|
|
800
|
+
instanceId: state.instanceId,
|
|
801
|
+
displayName: state.displayName,
|
|
802
|
+
platformKey: state.platformKey,
|
|
698
803
|
enabled: false,
|
|
699
804
|
status: 'stopped',
|
|
700
805
|
connected: false,
|
|
701
806
|
appId: state.appId,
|
|
807
|
+
autoApprove: state.autoApprove,
|
|
702
808
|
cardActionsEnabled: state.cardActionsEnabled,
|
|
703
809
|
});
|
|
704
810
|
this.notifyRuntimeStateChanged();
|
|
@@ -711,7 +817,11 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
711
817
|
},
|
|
712
818
|
});
|
|
713
819
|
}
|
|
714
|
-
async handleMessageEvent(workspaceId,
|
|
820
|
+
async handleMessageEvent(workspaceId, instanceIdOrData, platformKeyOrData, maybeData) {
|
|
821
|
+
const legacyCall = typeof instanceIdOrData === 'object';
|
|
822
|
+
const instanceId = legacyCall ? 'default' : instanceIdOrData;
|
|
823
|
+
const platformKey = legacyCall ? 'lark' : String(platformKeyOrData || (0, config_js_1.channelPlatformKey)('lark', instanceId));
|
|
824
|
+
const data = (legacyCall ? instanceIdOrData : maybeData) || {};
|
|
715
825
|
const payload = (data?.event && typeof data.event === 'object')
|
|
716
826
|
? data.event
|
|
717
827
|
: data;
|
|
@@ -739,7 +849,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
739
849
|
const imageKey = String(parsedContent.image_key || parsedContent.file_key || '').trim();
|
|
740
850
|
if (imageKey) {
|
|
741
851
|
try {
|
|
742
|
-
contentParts.push(await this.downloadMessageImage(workspaceId, String(message.message_id || ''), imageKey));
|
|
852
|
+
contentParts.push(await this.downloadMessageImage(workspaceId, String(message.message_id || ''), imageKey, instanceId));
|
|
743
853
|
}
|
|
744
854
|
catch (error) {
|
|
745
855
|
const errorText = `[Image download failed: ${error instanceof Error ? error.message : String(error)}]`;
|
|
@@ -748,15 +858,24 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
748
858
|
}
|
|
749
859
|
}
|
|
750
860
|
}
|
|
861
|
+
if (messageType === 'file') {
|
|
862
|
+
const fileKey = String(parsedContent.file_key || '').trim();
|
|
863
|
+
if (fileKey) {
|
|
864
|
+
try {
|
|
865
|
+
contentParts.push(await this.downloadMessageFile(workspaceId, String(message.message_id || ''), fileKey, String(parsedContent.file_name || parsedContent.name || '').trim(), instanceId));
|
|
866
|
+
}
|
|
867
|
+
catch (error) {
|
|
868
|
+
const errorText = `[File download failed: ${error instanceof Error ? error.message : String(error)}]`;
|
|
869
|
+
contentParts.push({ type: 'text', text: errorText });
|
|
870
|
+
this.options.log?.(`localcore-lark file download failed for ${workspaceId}: message=${String(message.message_id || '')} fileKey=${fileKey} error=${error instanceof Error ? error.message : String(error)}`);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
751
874
|
if (contentParts.length === 0) {
|
|
752
875
|
this.options.log?.(`localcore-lark ignored unsupported message for ${workspaceId}: type=${String(message.message_type || 'unknown')} contentKeys=${JSON.stringify(Object.keys(parsedContent))}`);
|
|
753
876
|
return;
|
|
754
877
|
}
|
|
755
|
-
const displayText = text || (contentParts
|
|
756
|
-
.filter((part) => part.type === 'text')
|
|
757
|
-
.map((part) => part.text)
|
|
758
|
-
.join('\n\n')
|
|
759
|
-
.trim());
|
|
878
|
+
const displayText = text || this.summarizeInboundContentParts(contentParts);
|
|
760
879
|
const platformUserId = String(sender.sender_id?.user_id || sender.sender_id?.open_id || '').trim();
|
|
761
880
|
const chatId = String(message.chat_id || platformUserId).trim();
|
|
762
881
|
if (!platformUserId || !chatId) {
|
|
@@ -766,6 +885,8 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
766
885
|
this.options.log?.(`localcore-lark inbound message for ${workspaceId}: chat=${chatId} user=${platformUserId} type=${messageType || 'unknown'} text=${JSON.stringify(displayText.slice(0, 120))}`);
|
|
767
886
|
await this.handleInboundMessage({
|
|
768
887
|
workspaceId,
|
|
888
|
+
instanceId,
|
|
889
|
+
platformKey,
|
|
769
890
|
platformUserId,
|
|
770
891
|
chatId,
|
|
771
892
|
displayName: String(payload?.sender?.sender_id?.user_id ||
|
|
@@ -776,27 +897,8 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
776
897
|
contentParts,
|
|
777
898
|
});
|
|
778
899
|
}
|
|
779
|
-
|
|
780
|
-
const
|
|
781
|
-
? parts.filter((part) => part.type !== 'text')
|
|
782
|
-
: [];
|
|
783
|
-
return [
|
|
784
|
-
{ type: 'text', text: wrappedText },
|
|
785
|
-
...nonTextParts,
|
|
786
|
-
];
|
|
787
|
-
}
|
|
788
|
-
createThreadMessageInput(wrappedText, parts) {
|
|
789
|
-
const hasNonTextPart = Array.isArray(parts) && parts.some((part) => part.type !== 'text');
|
|
790
|
-
if (!hasNonTextPart) {
|
|
791
|
-
return wrappedText;
|
|
792
|
-
}
|
|
793
|
-
return {
|
|
794
|
-
displayText: wrappedText,
|
|
795
|
-
contentParts: this.createWrappedContentParts(wrappedText, parts),
|
|
796
|
-
};
|
|
797
|
-
}
|
|
798
|
-
async downloadMessageImage(workspaceId, messageId, imageKey) {
|
|
799
|
-
const state = this.runtime.get(workspaceId);
|
|
900
|
+
async downloadMessageImage(workspaceId, messageId, imageKey, instanceId) {
|
|
901
|
+
const state = this.resolveRuntimeState(workspaceId, instanceId).state;
|
|
800
902
|
if (!state?.client) {
|
|
801
903
|
throw new Error('Lark client is not connected');
|
|
802
904
|
}
|
|
@@ -824,6 +926,58 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
824
926
|
fileName: `${imageKey}.${this.sniffImageExtension(buffer)}`,
|
|
825
927
|
};
|
|
826
928
|
}
|
|
929
|
+
async downloadMessageFile(workspaceId, messageId, fileKey, fileName, instanceId) {
|
|
930
|
+
const state = this.resolveRuntimeState(workspaceId, instanceId).state;
|
|
931
|
+
if (!state?.client) {
|
|
932
|
+
throw new Error('Lark client is not connected');
|
|
933
|
+
}
|
|
934
|
+
if (!messageId) {
|
|
935
|
+
throw new Error('Lark file message is missing message_id');
|
|
936
|
+
}
|
|
937
|
+
const resource = await state.client.im.messageResource.get({
|
|
938
|
+
path: {
|
|
939
|
+
message_id: messageId,
|
|
940
|
+
file_key: fileKey,
|
|
941
|
+
},
|
|
942
|
+
params: {
|
|
943
|
+
type: 'file',
|
|
944
|
+
},
|
|
945
|
+
});
|
|
946
|
+
const buffer = await this.readLarkResourceBuffer(resource);
|
|
947
|
+
if (buffer.length === 0) {
|
|
948
|
+
throw new Error('Lark file resource is empty');
|
|
949
|
+
}
|
|
950
|
+
return {
|
|
951
|
+
type: 'file',
|
|
952
|
+
data: buffer.toString('base64'),
|
|
953
|
+
mimeType: this.extractHeaderMimeType(resource?.headers) || 'application/octet-stream',
|
|
954
|
+
fileName: fileName || fileKey,
|
|
955
|
+
size: buffer.length,
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
summarizeInboundContentParts(parts) {
|
|
959
|
+
const text = parts
|
|
960
|
+
.filter((part) => part.type === 'text')
|
|
961
|
+
.map((part) => part.text)
|
|
962
|
+
.filter(Boolean)
|
|
963
|
+
.join('\n\n')
|
|
964
|
+
.trim();
|
|
965
|
+
if (text)
|
|
966
|
+
return text;
|
|
967
|
+
const attachmentSummary = parts
|
|
968
|
+
.map((part) => {
|
|
969
|
+
if (part.type === 'image') {
|
|
970
|
+
return '[Image]';
|
|
971
|
+
}
|
|
972
|
+
if (part.type === 'file') {
|
|
973
|
+
return part.fileName ? `[File: ${part.fileName}]` : '[File]';
|
|
974
|
+
}
|
|
975
|
+
return '';
|
|
976
|
+
})
|
|
977
|
+
.filter(Boolean)
|
|
978
|
+
.join('\n');
|
|
979
|
+
return attachmentSummary;
|
|
980
|
+
}
|
|
827
981
|
async readLarkResourceBuffer(resource) {
|
|
828
982
|
const readable = resource?.getReadableStream?.();
|
|
829
983
|
if (!readable || typeof readable.on !== 'function') {
|
|
@@ -930,32 +1084,8 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
930
1084
|
return `${value.slice(0, 6)}...${value.slice(-4)}`;
|
|
931
1085
|
}
|
|
932
1086
|
collectBindings(config) {
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
const platforms = Array.isArray(project.platforms) ? project.platforms : [];
|
|
936
|
-
return platforms
|
|
937
|
-
.map((platform) => ({
|
|
938
|
-
platformType: (0, desktop_js_1.normalizeDesktopPlatformType)(platform?.type),
|
|
939
|
-
options: platform?.options && typeof platform.options === 'object'
|
|
940
|
-
? platform.options
|
|
941
|
-
: {},
|
|
942
|
-
}))
|
|
943
|
-
.filter((platform) => platform.platformType === 'lark')
|
|
944
|
-
.map((platform) => ({
|
|
945
|
-
workspaceId: project.name,
|
|
946
|
-
appId: String(platform.options.app_id || '').trim(),
|
|
947
|
-
appSecret: String(platform.options.app_secret || '').trim(),
|
|
948
|
-
encryptKey: String(platform.options.encrypt_key || '').trim(),
|
|
949
|
-
verificationToken: String(platform.options.verification_token || '').trim(),
|
|
950
|
-
autoApprove: String(platform.options.auto_approve || '').trim().toLowerCase() === 'true'
|
|
951
|
-
|| platform.options.auto_approve === true,
|
|
952
|
-
cardActionsEnabled: String(platform.options.card_actions || platform.options.enable_card_actions || '').trim().toLowerCase() === 'true'
|
|
953
|
-
|| platform.options.card_actions === true
|
|
954
|
-
|| platform.options.enable_card_actions === true
|
|
955
|
-
|| this.defaultCardActionsEnabled,
|
|
956
|
-
enabled: Boolean(String(platform.options.app_id || '').trim() && String(platform.options.app_secret || '').trim()),
|
|
957
|
-
project,
|
|
958
|
-
}));
|
|
1087
|
+
return (0, config_js_1.collectLarkWorkspaceBindings)(config, {
|
|
1088
|
+
defaultCardActionsEnabled: this.defaultCardActionsEnabled,
|
|
959
1089
|
});
|
|
960
1090
|
}
|
|
961
1091
|
async getLarkModule() {
|
|
@@ -964,40 +1094,6 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
964
1094
|
}
|
|
965
1095
|
return this.larkModulePromise;
|
|
966
1096
|
}
|
|
967
|
-
buildInteractiveCard(text, buttonRows = [], sessionKey, threadId) {
|
|
968
|
-
const elements = [];
|
|
969
|
-
if (text) {
|
|
970
|
-
elements.push({ tag: 'markdown', content: text });
|
|
971
|
-
}
|
|
972
|
-
for (const row of buttonRows) {
|
|
973
|
-
const actions = row
|
|
974
|
-
.filter((button) => button.text && button.data)
|
|
975
|
-
.map((button, index) => ({
|
|
976
|
-
tag: 'button',
|
|
977
|
-
text: {
|
|
978
|
-
tag: 'plain_text',
|
|
979
|
-
content: this.formatPermissionButtonLabel(button),
|
|
980
|
-
},
|
|
981
|
-
type: this.resolveLarkButtonType(button, index),
|
|
982
|
-
value: {
|
|
983
|
-
action: 'permission_response',
|
|
984
|
-
response: (0, desktop_js_1.normalizePermissionResponse)(button.data) || button.data,
|
|
985
|
-
session_key: sessionKey || '',
|
|
986
|
-
thread_id: threadId || '',
|
|
987
|
-
},
|
|
988
|
-
}));
|
|
989
|
-
if (actions.length) {
|
|
990
|
-
elements.push({
|
|
991
|
-
tag: 'action',
|
|
992
|
-
actions,
|
|
993
|
-
});
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
return {
|
|
997
|
-
config: { wide_screen_mode: true },
|
|
998
|
-
elements,
|
|
999
|
-
};
|
|
1000
|
-
}
|
|
1001
1097
|
async sendTextAsCard(state, chatId, text, buttonRows = [], sessionKey, threadId) {
|
|
1002
1098
|
const response = await state.client.im.message.create({
|
|
1003
1099
|
params: {
|
|
@@ -1006,7 +1102,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
1006
1102
|
data: {
|
|
1007
1103
|
receive_id: chatId,
|
|
1008
1104
|
msg_type: 'interactive',
|
|
1009
|
-
content: JSON.stringify(
|
|
1105
|
+
content: JSON.stringify((0, cards_js_1.buildInteractiveCard)(text, buttonRows, sessionKey, threadId)),
|
|
1010
1106
|
},
|
|
1011
1107
|
});
|
|
1012
1108
|
return String(response?.data?.message_id || '').trim();
|
|
@@ -1044,7 +1140,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
1044
1140
|
message_id: messageId,
|
|
1045
1141
|
},
|
|
1046
1142
|
data: {
|
|
1047
|
-
content: JSON.stringify(
|
|
1143
|
+
content: JSON.stringify((0, cards_js_1.buildInteractiveCard)(text, buttonRows, sessionKey, threadId)),
|
|
1048
1144
|
},
|
|
1049
1145
|
});
|
|
1050
1146
|
}
|
|
@@ -1080,21 +1176,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
1080
1176
|
};
|
|
1081
1177
|
}
|
|
1082
1178
|
createTurnState(sessionKey, sourceMessageId) {
|
|
1083
|
-
const turn =
|
|
1084
|
-
sessionKey,
|
|
1085
|
-
sourceMessageId,
|
|
1086
|
-
awaitingPermission: false,
|
|
1087
|
-
processing: false,
|
|
1088
|
-
previewText: '',
|
|
1089
|
-
finalText: '',
|
|
1090
|
-
progressMessageIds: {},
|
|
1091
|
-
thinkingSteps: [],
|
|
1092
|
-
toolCalls: [],
|
|
1093
|
-
statusLines: [],
|
|
1094
|
-
buttonRows: [],
|
|
1095
|
-
lastPatchedAt: 0,
|
|
1096
|
-
lastPatchedAtByMessageId: {},
|
|
1097
|
-
};
|
|
1179
|
+
const turn = (0, runtime_state_js_1.createLarkTurnState)(sessionKey, sourceMessageId);
|
|
1098
1180
|
this.outboundTurns.set(sessionKey, turn);
|
|
1099
1181
|
return turn;
|
|
1100
1182
|
}
|
|
@@ -1115,209 +1197,36 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
1115
1197
|
return turn;
|
|
1116
1198
|
}
|
|
1117
1199
|
consumeBridgeEvent(turn, event) {
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
// This keeps Feishu message order aligned with user-visible timeline.
|
|
1122
|
-
turn.messageId = undefined;
|
|
1123
|
-
turn.finalMessageId = undefined;
|
|
1124
|
-
turn.progressMessageIds = {};
|
|
1125
|
-
turn.processing = true;
|
|
1126
|
-
turn.permissionMessageId = undefined;
|
|
1127
|
-
turn.previewText = '';
|
|
1128
|
-
turn.finalText = '';
|
|
1129
|
-
turn.thinkingSteps = [];
|
|
1130
|
-
turn.toolCalls = [];
|
|
1131
|
-
turn.buttonRows = [];
|
|
1132
|
-
turn.statusLines = [];
|
|
1133
|
-
turn.lastPatchedAtByMessageId = {};
|
|
1134
|
-
return;
|
|
1135
|
-
}
|
|
1136
|
-
if (event.type === 'typing_stop') {
|
|
1137
|
-
turn.processing = false;
|
|
1138
|
-
return;
|
|
1139
|
-
}
|
|
1140
|
-
if (event.type === 'preview_start' || event.type === 'update_message') {
|
|
1141
|
-
turn.previewText = content;
|
|
1142
|
-
return;
|
|
1143
|
-
}
|
|
1144
|
-
if (event.type === 'status') {
|
|
1145
|
-
if (content) {
|
|
1146
|
-
this.pushUnique(turn.statusLines, content);
|
|
1147
|
-
}
|
|
1148
|
-
return;
|
|
1149
|
-
}
|
|
1150
|
-
if (event.type === 'buttons') {
|
|
1151
|
-
turn.awaitingPermission = true;
|
|
1152
|
-
turn.buttonRows = [];
|
|
1153
|
-
if (this.mirrorPermissionStateInMainCard && content) {
|
|
1154
|
-
this.pushUnique(turn.statusLines, `等待确认: ${content}`);
|
|
1155
|
-
}
|
|
1156
|
-
return;
|
|
1157
|
-
}
|
|
1158
|
-
if (!content) {
|
|
1159
|
-
return;
|
|
1160
|
-
}
|
|
1161
|
-
if (content.startsWith('💭 ')) {
|
|
1162
|
-
this.pushUnique(turn.thinkingSteps, content.slice(3).trim());
|
|
1163
|
-
return;
|
|
1164
|
-
}
|
|
1165
|
-
if (content.startsWith('🔧 ')) {
|
|
1166
|
-
this.pushUnique(turn.toolCalls, content.slice(3).trim());
|
|
1167
|
-
return;
|
|
1168
|
-
}
|
|
1169
|
-
if (content.startsWith('⏳ ') || content.startsWith('📤 ')) {
|
|
1170
|
-
this.pushUnique(turn.statusLines, content.slice(3).trim());
|
|
1171
|
-
return;
|
|
1172
|
-
}
|
|
1173
|
-
turn.finalText = content;
|
|
1174
|
-
turn.previewText = content;
|
|
1175
|
-
}
|
|
1176
|
-
renderBridgeEventMessage(turn, event) {
|
|
1177
|
-
const content = foldToolResultForLark(String(event.content || '').trim());
|
|
1178
|
-
if (event.type === 'preview_start' || event.type === 'update_message') {
|
|
1179
|
-
if (content.startsWith('💭 ')) {
|
|
1180
|
-
return this.renderProgressMessage(event.previewHandle || 'thinking-preview', content);
|
|
1181
|
-
}
|
|
1182
|
-
if (content.startsWith('🔧 ')) {
|
|
1183
|
-
return this.renderProgressMessage(this.progressKey('tool', event), content);
|
|
1184
|
-
}
|
|
1185
|
-
return {
|
|
1186
|
-
key: 'final',
|
|
1187
|
-
text: content,
|
|
1188
|
-
buttonRows: turn.buttonRows,
|
|
1189
|
-
isFinal: true,
|
|
1190
|
-
};
|
|
1191
|
-
}
|
|
1192
|
-
if (event.type === 'status') {
|
|
1193
|
-
return this.renderProgressMessage(this.progressKey('status', event), content.startsWith('⏳ ') ? content : `⏳ ${content}`);
|
|
1194
|
-
}
|
|
1195
|
-
if (event.type === 'reply') {
|
|
1196
|
-
if (content.startsWith('💭 ')) {
|
|
1197
|
-
return this.renderProgressMessage(this.progressKey('thinking', event), content);
|
|
1198
|
-
}
|
|
1199
|
-
if (content.startsWith('🔧 ')) {
|
|
1200
|
-
return this.renderProgressMessage(this.progressKey('tool', event), content);
|
|
1201
|
-
}
|
|
1202
|
-
if (content.startsWith('⏳ ') || content.startsWith('📤 ')) {
|
|
1203
|
-
return this.renderProgressMessage(this.progressKey('status', event), content);
|
|
1204
|
-
}
|
|
1205
|
-
return {
|
|
1206
|
-
key: 'final',
|
|
1207
|
-
text: content,
|
|
1208
|
-
buttonRows: turn.buttonRows,
|
|
1209
|
-
isFinal: true,
|
|
1210
|
-
};
|
|
1211
|
-
}
|
|
1212
|
-
if (event.type === 'typing_start' || event.type === 'typing_stop') {
|
|
1213
|
-
return { key: 'noop', text: '', buttonRows: [], isFinal: false };
|
|
1214
|
-
}
|
|
1215
|
-
return {
|
|
1216
|
-
key: 'noop',
|
|
1217
|
-
text: '',
|
|
1218
|
-
buttonRows: [],
|
|
1219
|
-
isFinal: false,
|
|
1220
|
-
};
|
|
1221
|
-
}
|
|
1222
|
-
renderProgressMessage(key, text) {
|
|
1223
|
-
return {
|
|
1224
|
-
key,
|
|
1225
|
-
text,
|
|
1226
|
-
buttonRows: [],
|
|
1227
|
-
isFinal: false,
|
|
1228
|
-
};
|
|
1229
|
-
}
|
|
1230
|
-
progressKey(prefix, event) {
|
|
1231
|
-
const stableId = String(event.messageId || event.previewHandle || '').trim();
|
|
1232
|
-
if (stableId) {
|
|
1233
|
-
return `${prefix}:${stableId}`;
|
|
1234
|
-
}
|
|
1235
|
-
const content = String(event.content || '').trim().replace(/\s+/g, ' ');
|
|
1236
|
-
return `${prefix}:${content.slice(0, 120)}`;
|
|
1237
|
-
}
|
|
1238
|
-
getRenderedMessageId(turn, rendered) {
|
|
1239
|
-
if (rendered.isFinal) {
|
|
1240
|
-
return turn.finalMessageId || turn.messageId;
|
|
1241
|
-
}
|
|
1242
|
-
return turn.progressMessageIds[rendered.key];
|
|
1243
|
-
}
|
|
1244
|
-
setRenderedMessageId(turn, rendered, messageId) {
|
|
1245
|
-
if (rendered.isFinal) {
|
|
1246
|
-
turn.finalMessageId = messageId;
|
|
1247
|
-
turn.messageId = messageId;
|
|
1248
|
-
return;
|
|
1249
|
-
}
|
|
1250
|
-
turn.progressMessageIds[rendered.key] = messageId;
|
|
1251
|
-
}
|
|
1252
|
-
renderPermissionCard(turn, event, cardActionsEnabled) {
|
|
1253
|
-
const summary = this.buildPermissionSummary(turn, String(event.content || '').trim());
|
|
1254
|
-
const sections = [
|
|
1255
|
-
'**需要工具确认**',
|
|
1256
|
-
summary.command ? `\`${summary.command}\`` : '',
|
|
1257
|
-
summary.reason || '',
|
|
1258
|
-
cardActionsEnabled
|
|
1259
|
-
? '也可以直接回复:`allow` / `allow all` / `deny`'
|
|
1260
|
-
: '请直接回复:`allow` / `allow all` / `deny`',
|
|
1261
|
-
].filter(Boolean);
|
|
1262
|
-
const buttonRows = cardActionsEnabled && Array.isArray(event.buttonRows)
|
|
1263
|
-
? event.buttonRows
|
|
1264
|
-
.map((row) => Array.isArray(row)
|
|
1265
|
-
? row
|
|
1266
|
-
.filter((button) => Boolean(button?.text && button?.data))
|
|
1267
|
-
.map((button) => ({
|
|
1268
|
-
text: this.formatPermissionButtonLabel(button),
|
|
1269
|
-
data: (0, desktop_js_1.normalizePermissionResponse)(button.data) || button.data,
|
|
1270
|
-
}))
|
|
1271
|
-
: [])
|
|
1272
|
-
.filter((row) => row.length > 0)
|
|
1273
|
-
: [];
|
|
1274
|
-
return {
|
|
1275
|
-
text: sections.join('\n\n').trim(),
|
|
1276
|
-
buttonRows,
|
|
1277
|
-
};
|
|
1278
|
-
}
|
|
1279
|
-
renderPendingPairingCard(code) {
|
|
1280
|
-
const lines = [
|
|
1281
|
-
'**已收到消息**',
|
|
1282
|
-
'当前账号还未授权接入这个工作区。',
|
|
1283
|
-
'请在桌面端完成审批后再次发送消息。',
|
|
1284
|
-
];
|
|
1285
|
-
if (code) {
|
|
1286
|
-
lines.push(`配对码:\`${code}\``);
|
|
1287
|
-
}
|
|
1288
|
-
return lines.join('\n\n');
|
|
1200
|
+
(0, runtime_state_js_1.consumeLarkBridgeEvent)(turn, event, {
|
|
1201
|
+
mirrorPermissionStateInMainCard: this.mirrorPermissionStateInMainCard,
|
|
1202
|
+
});
|
|
1289
1203
|
}
|
|
1290
|
-
async handleCardActionEvent(workspaceId,
|
|
1204
|
+
async handleCardActionEvent(workspaceId, instanceIdOrData, maybeData) {
|
|
1205
|
+
const legacyCall = typeof instanceIdOrData === 'object';
|
|
1206
|
+
const instanceId = legacyCall ? 'default' : instanceIdOrData;
|
|
1207
|
+
const data = (legacyCall ? instanceIdOrData : maybeData) || {};
|
|
1291
1208
|
try {
|
|
1292
|
-
const
|
|
1293
|
-
|
|
1294
|
-
: data;
|
|
1295
|
-
const value = payload?.action?.value;
|
|
1296
|
-
if (!value || value.action !== 'permission_response') {
|
|
1297
|
-
return;
|
|
1298
|
-
}
|
|
1299
|
-
const response = (0, desktop_js_1.normalizePermissionResponse)(String(value.response || '').trim()) || String(value.response || '').trim();
|
|
1300
|
-
const threadId = String(value.thread_id || '').trim();
|
|
1301
|
-
if (!response || !threadId) {
|
|
1209
|
+
const action = (0, cards_js_1.extractCardActionValue)(data);
|
|
1210
|
+
if (!action) {
|
|
1302
1211
|
return;
|
|
1303
1212
|
}
|
|
1304
1213
|
const router = this.options.getWorkspaceRouter();
|
|
1305
|
-
await
|
|
1306
|
-
await
|
|
1307
|
-
this.options.log?.(`localcore-lark processed card action for ${workspaceId}: ${response}`);
|
|
1214
|
+
await this.markPermissionCardActionHandled(workspaceId, instanceId, data, action.event, action.value, action.response, action.threadId);
|
|
1215
|
+
await router.sendThreadAction(action.threadId, action.response);
|
|
1216
|
+
this.options.log?.(`localcore-lark processed card action for ${workspaceId}/${instanceId}: ${action.response}`);
|
|
1308
1217
|
}
|
|
1309
1218
|
catch (error) {
|
|
1310
|
-
this.options.log?.(`localcore-lark card action failed for ${workspaceId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1219
|
+
this.options.log?.(`localcore-lark card action failed for ${workspaceId}/${instanceId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1311
1220
|
}
|
|
1312
1221
|
}
|
|
1313
|
-
async markPermissionCardActionHandled(workspaceId, rootPayload, payload, value, response, threadId) {
|
|
1314
|
-
const state = this.
|
|
1222
|
+
async markPermissionCardActionHandled(workspaceId, instanceId, rootPayload, payload, value, response, threadId) {
|
|
1223
|
+
const state = this.resolveRuntimeState(workspaceId, instanceId).state;
|
|
1315
1224
|
if (!state?.client) {
|
|
1316
1225
|
return;
|
|
1317
1226
|
}
|
|
1318
1227
|
const sessionKey = String(value.session_key || '').trim();
|
|
1319
1228
|
const turn = sessionKey ? this.outboundTurns.get(sessionKey) : undefined;
|
|
1320
|
-
const messageId =
|
|
1229
|
+
const messageId = (0, cards_js_1.extractCardActionMessageId)(rootPayload, payload, value) || turn?.permissionMessageId;
|
|
1321
1230
|
if (!messageId) {
|
|
1322
1231
|
this.options.log?.(`localcore-lark could not find permission card message id for workspace=${workspaceId} sessionKey=${sessionKey || 'missing'}`);
|
|
1323
1232
|
return;
|
|
@@ -1325,7 +1234,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
1325
1234
|
try {
|
|
1326
1235
|
await this.patchTextCard(state, messageId, [
|
|
1327
1236
|
'**工具确认已处理**',
|
|
1328
|
-
`已选择:${
|
|
1237
|
+
`已选择:${(0, cards_js_1.formatPermissionResponseLabel)(response)}`,
|
|
1329
1238
|
'等待代理继续执行...',
|
|
1330
1239
|
].join('\n\n'), [], sessionKey || undefined, threadId);
|
|
1331
1240
|
if (turn) {
|
|
@@ -1337,133 +1246,8 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
1337
1246
|
this.options.log?.(`localcore-lark failed to patch handled permission card ${messageId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1338
1247
|
}
|
|
1339
1248
|
}
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
const messageId = this.extractKnownCardActionMessageId(payload);
|
|
1343
|
-
if (messageId) {
|
|
1344
|
-
return messageId;
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
for (const payload of payloads) {
|
|
1348
|
-
const messageId = this.findNestedCardActionMessageId(payload);
|
|
1349
|
-
if (messageId) {
|
|
1350
|
-
return messageId;
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
return '';
|
|
1354
|
-
}
|
|
1355
|
-
extractKnownCardActionMessageId(payload) {
|
|
1356
|
-
const source = payload;
|
|
1357
|
-
if (!source || typeof source !== 'object') {
|
|
1358
|
-
return '';
|
|
1359
|
-
}
|
|
1360
|
-
const candidates = [
|
|
1361
|
-
source?.permission_message_id,
|
|
1362
|
-
source?.action_message_id,
|
|
1363
|
-
source?.context?.open_message_id,
|
|
1364
|
-
source?.context?.message_id,
|
|
1365
|
-
source?.event?.context?.open_message_id,
|
|
1366
|
-
source?.event?.context?.message_id,
|
|
1367
|
-
source?.event_context?.open_message_id,
|
|
1368
|
-
source?.event_context?.message_id,
|
|
1369
|
-
source?.message?.message_id,
|
|
1370
|
-
source?.message?.open_message_id,
|
|
1371
|
-
source?.event?.message?.message_id,
|
|
1372
|
-
source?.event?.message?.open_message_id,
|
|
1373
|
-
source?.message_id,
|
|
1374
|
-
source?.open_message_id,
|
|
1375
|
-
];
|
|
1376
|
-
return candidates
|
|
1377
|
-
.map((candidate) => String(candidate || '').trim())
|
|
1378
|
-
.find(Boolean) || '';
|
|
1379
|
-
}
|
|
1380
|
-
findNestedCardActionMessageId(payload, seen = new Set()) {
|
|
1381
|
-
if (!payload || typeof payload !== 'object' || seen.has(payload)) {
|
|
1382
|
-
return '';
|
|
1383
|
-
}
|
|
1384
|
-
seen.add(payload);
|
|
1385
|
-
if (Array.isArray(payload)) {
|
|
1386
|
-
for (const item of payload) {
|
|
1387
|
-
const nested = this.findNestedCardActionMessageId(item, seen);
|
|
1388
|
-
if (nested)
|
|
1389
|
-
return nested;
|
|
1390
|
-
}
|
|
1391
|
-
return '';
|
|
1392
|
-
}
|
|
1393
|
-
const source = payload;
|
|
1394
|
-
for (const key of ['open_message_id', 'message_id', 'permission_message_id', 'action_message_id']) {
|
|
1395
|
-
const value = source[key];
|
|
1396
|
-
if (typeof value === 'string' && value.trim()) {
|
|
1397
|
-
return value.trim();
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
for (const value of Object.values(source)) {
|
|
1401
|
-
const nested = this.findNestedCardActionMessageId(value, seen);
|
|
1402
|
-
if (nested)
|
|
1403
|
-
return nested;
|
|
1404
|
-
}
|
|
1405
|
-
return '';
|
|
1406
|
-
}
|
|
1407
|
-
pushUnique(target, value) {
|
|
1408
|
-
const normalized = value.trim();
|
|
1409
|
-
if (!normalized) {
|
|
1410
|
-
return;
|
|
1411
|
-
}
|
|
1412
|
-
if (target[target.length - 1] === normalized) {
|
|
1413
|
-
return;
|
|
1414
|
-
}
|
|
1415
|
-
target.push(normalized);
|
|
1416
|
-
if (target.length > 6) {
|
|
1417
|
-
target.splice(0, target.length - 6);
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
formatPermissionButtonLabel(button) {
|
|
1421
|
-
return this.formatPermissionResponseLabel(button.data || button.text);
|
|
1422
|
-
}
|
|
1423
|
-
formatPermissionResponseLabel(response) {
|
|
1424
|
-
switch ((0, desktop_js_1.normalizePermissionResponse)(response) || response) {
|
|
1425
|
-
case 'allow':
|
|
1426
|
-
return '允许一次';
|
|
1427
|
-
case 'allow all':
|
|
1428
|
-
return '始终允许';
|
|
1429
|
-
case 'deny':
|
|
1430
|
-
return '拒绝';
|
|
1431
|
-
default:
|
|
1432
|
-
return response;
|
|
1433
|
-
}
|
|
1434
|
-
}
|
|
1435
|
-
resolveLarkButtonType(button, index) {
|
|
1436
|
-
const response = (0, desktop_js_1.normalizePermissionResponse)(button.data) || (0, desktop_js_1.normalizePermissionResponse)(button.text);
|
|
1437
|
-
if (response === 'deny') {
|
|
1438
|
-
return 'danger';
|
|
1439
|
-
}
|
|
1440
|
-
if (response === 'allow') {
|
|
1441
|
-
return 'primary';
|
|
1442
|
-
}
|
|
1443
|
-
return index === 0 ? 'primary' : 'default';
|
|
1444
|
-
}
|
|
1445
|
-
buildPermissionSummary(turn, rawContent) {
|
|
1446
|
-
const lastTool = turn.toolCalls[turn.toolCalls.length - 1] || '';
|
|
1447
|
-
const [commandPart = '', reasonPart = ''] = lastTool.split(/\s+-\s+/, 2);
|
|
1448
|
-
const compactContent = rawContent
|
|
1449
|
-
.replace(/\s+/g, ' ')
|
|
1450
|
-
.replace(/请选择一个选项继续执行。?/g, '')
|
|
1451
|
-
.replace(/若按钮没有显示,请直接回复:?\s*allow all \/ allow \/ deny/gi, '')
|
|
1452
|
-
.replace(/等待工具确认/gi, '')
|
|
1453
|
-
.trim();
|
|
1454
|
-
return {
|
|
1455
|
-
command: commandPart.trim(),
|
|
1456
|
-
reason: reasonPart.trim() || compactContent,
|
|
1457
|
-
};
|
|
1458
|
-
}
|
|
1459
|
-
formatCompactToolLines(toolCalls) {
|
|
1460
|
-
return toolCalls.slice(-2).map((line) => {
|
|
1461
|
-
const compact = line.replace(/\s+/g, ' ').trim();
|
|
1462
|
-
return compact.startsWith('Terminal') ? '• Terminal' : `• ${compact}`;
|
|
1463
|
-
});
|
|
1464
|
-
}
|
|
1465
|
-
async sendImmediateCard(workspaceId, chatId, text) {
|
|
1466
|
-
const state = this.runtime.get(workspaceId);
|
|
1249
|
+
async sendImmediateCard(workspaceId, chatId, text, instanceId) {
|
|
1250
|
+
const state = this.resolveRuntimeState(workspaceId, instanceId).state;
|
|
1467
1251
|
if (!state?.client || !state.connected) {
|
|
1468
1252
|
this.options.log?.(`localcore-lark immediate card skipped because workspace is not connected: ${workspaceId}`);
|
|
1469
1253
|
return '';
|
|
@@ -1476,8 +1260,8 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
1476
1260
|
return '';
|
|
1477
1261
|
}
|
|
1478
1262
|
}
|
|
1479
|
-
async addAcknowledgementReaction(workspaceId, messageId, turn) {
|
|
1480
|
-
const state = this.
|
|
1263
|
+
async addAcknowledgementReaction(workspaceId, messageId, turn, instanceId) {
|
|
1264
|
+
const state = this.resolveRuntimeState(workspaceId, instanceId).state;
|
|
1481
1265
|
if (!state?.client || !state.connected || !messageId) {
|
|
1482
1266
|
return;
|
|
1483
1267
|
}
|