@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
package/dist-electron/services/local-ai-core/src/{gateway → channel/lark}/local-core-lark-gateway.js
RENAMED
|
@@ -36,8 +36,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.LocalCoreLarkGateway = void 0;
|
|
37
37
|
const node_events_1 = require("node:events");
|
|
38
38
|
const node_crypto_1 = require("node:crypto");
|
|
39
|
-
const
|
|
39
|
+
const node_fs_1 = require("node:fs");
|
|
40
|
+
const node_path_1 = require("node:path");
|
|
41
|
+
const desktop_js_1 = require("../../../../../shared/desktop.js");
|
|
42
|
+
const file_utils_js_1 = require("../shared/file-utils.js");
|
|
43
|
+
const cards_js_1 = require("./cards.js");
|
|
44
|
+
const config_js_1 = require("./config.js");
|
|
45
|
+
const registration_js_1 = require("./registration.js");
|
|
46
|
+
const runtime_state_js_1 = require("./runtime-state.js");
|
|
40
47
|
const PAIRING_EXPIRY_MS = 10 * 60 * 1000;
|
|
48
|
+
const LARK_MAX_UPLOAD_FILE_SIZE = 30 * 1024 * 1024;
|
|
41
49
|
class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
42
50
|
options;
|
|
43
51
|
// Lark returns 200340 when card action events are not enabled in the app's
|
|
@@ -60,25 +68,30 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
60
68
|
async refreshBindings() {
|
|
61
69
|
const config = await this.options.readConfig();
|
|
62
70
|
const bindings = this.collectBindings(config);
|
|
63
|
-
const
|
|
64
|
-
for (const
|
|
65
|
-
if (!
|
|
66
|
-
await this.
|
|
71
|
+
const nextKeys = new Set(bindings.map((binding) => (0, config_js_1.runtimeKey)(binding.workspaceId, binding.instanceId)));
|
|
72
|
+
for (const key of [...this.runtime.keys()]) {
|
|
73
|
+
if (!nextKeys.has(key)) {
|
|
74
|
+
await this.stopWorkspaceKey(key);
|
|
67
75
|
}
|
|
68
76
|
}
|
|
69
77
|
for (const binding of bindings) {
|
|
70
|
-
const
|
|
78
|
+
const key = (0, config_js_1.runtimeKey)(binding.workspaceId, binding.instanceId);
|
|
79
|
+
const current = this.runtime.get(key);
|
|
71
80
|
if (!binding.enabled) {
|
|
72
81
|
if (current) {
|
|
73
|
-
await this.
|
|
82
|
+
await this.stopWorkspaceKey(key);
|
|
74
83
|
}
|
|
75
84
|
else {
|
|
76
|
-
this.runtime.set(
|
|
85
|
+
this.runtime.set(key, {
|
|
77
86
|
workspaceId: binding.workspaceId,
|
|
87
|
+
instanceId: binding.instanceId,
|
|
88
|
+
displayName: binding.displayName,
|
|
89
|
+
platformKey: binding.platformKey,
|
|
78
90
|
enabled: false,
|
|
79
91
|
status: 'disabled',
|
|
80
92
|
connected: false,
|
|
81
93
|
appId: binding.appId,
|
|
94
|
+
autoApprove: binding.autoApprove,
|
|
82
95
|
cardActionsEnabled: binding.cardActionsEnabled,
|
|
83
96
|
});
|
|
84
97
|
}
|
|
@@ -91,23 +104,70 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
91
104
|
}
|
|
92
105
|
this.notifyRuntimeStateChanged();
|
|
93
106
|
}
|
|
94
|
-
async testConnection(workspaceId) {
|
|
95
|
-
const binding = await this.getBinding(workspaceId);
|
|
107
|
+
async testConnection(workspaceId, instanceId) {
|
|
108
|
+
const binding = await this.getBinding(workspaceId, instanceId);
|
|
96
109
|
const result = await this.createSdkClientResult(binding);
|
|
97
110
|
return {
|
|
98
111
|
...result,
|
|
99
112
|
platform: 'lark',
|
|
100
113
|
workspaceId,
|
|
114
|
+
instanceId: binding.instanceId,
|
|
101
115
|
};
|
|
102
116
|
}
|
|
103
|
-
async enable(workspaceId) {
|
|
104
|
-
const binding = await this.getBinding(workspaceId);
|
|
117
|
+
async enable(workspaceId, instanceId) {
|
|
118
|
+
const binding = await this.getBinding(workspaceId, instanceId);
|
|
105
119
|
await this.startWorkspace(binding);
|
|
106
|
-
return this.getStatus(workspaceId);
|
|
120
|
+
return this.getStatus(workspaceId, binding.instanceId);
|
|
107
121
|
}
|
|
108
|
-
async disable(workspaceId) {
|
|
109
|
-
await this.stopWorkspace(workspaceId);
|
|
110
|
-
return this.getStatus(workspaceId);
|
|
122
|
+
async disable(workspaceId, instanceId) {
|
|
123
|
+
await this.stopWorkspace(workspaceId, instanceId);
|
|
124
|
+
return this.getStatus(workspaceId, instanceId);
|
|
125
|
+
}
|
|
126
|
+
async getQrCode(workspaceId, instanceId) {
|
|
127
|
+
const binding = await this.getBinding(workspaceId, instanceId);
|
|
128
|
+
const data = await (0, registration_js_1.requestAppRegistration)(binding);
|
|
129
|
+
const ticket = String(data.device_code || '').trim();
|
|
130
|
+
const userCode = String(data.user_code || '').trim();
|
|
131
|
+
if (!ticket || !userCode) {
|
|
132
|
+
throw new Error('Lark app registration did not return a device code and user code.');
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
ticket,
|
|
136
|
+
expiresIn: Number(data.expires_in || registration_js_1.DEFAULT_LARK_QR_EXPIRES_IN) || registration_js_1.DEFAULT_LARK_QR_EXPIRES_IN,
|
|
137
|
+
interval: Number(data.interval || 5) || 5,
|
|
138
|
+
qrCodeUrl: `${(0, registration_js_1.getLarkOpenBase)(binding)}${registration_js_1.LARK_APP_REGISTRATION_SETUP_PATH}?user_code=${encodeURIComponent(userCode)}&from=openclaw`,
|
|
139
|
+
instanceId: binding.instanceId,
|
|
140
|
+
displayName: binding.displayName,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
async checkQrCodeStatus(workspaceId, ticket, instanceId) {
|
|
144
|
+
const binding = await this.getBinding(workspaceId, instanceId);
|
|
145
|
+
const data = await (0, registration_js_1.pollAppRegistration)(binding, ticket);
|
|
146
|
+
const error = String(data.error || '').trim();
|
|
147
|
+
if (error === 'authorization_pending' || error === 'slow_down') {
|
|
148
|
+
return { status: 'wait' };
|
|
149
|
+
}
|
|
150
|
+
if (error === 'expired_token' || error === 'invalid_grant') {
|
|
151
|
+
return { status: 'expired' };
|
|
152
|
+
}
|
|
153
|
+
if (error === 'access_denied') {
|
|
154
|
+
throw new Error('Lark app registration was rejected.');
|
|
155
|
+
}
|
|
156
|
+
if (error) {
|
|
157
|
+
throw new Error(`Lark app registration failed: ${String(data.error_description || error)}`);
|
|
158
|
+
}
|
|
159
|
+
const appId = String(data.client_id || '').trim();
|
|
160
|
+
const appSecret = String(data.client_secret || '').trim();
|
|
161
|
+
if (!appId || !appSecret) {
|
|
162
|
+
return { status: 'wait' };
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
status: 'confirmed',
|
|
166
|
+
credentials: {
|
|
167
|
+
appId,
|
|
168
|
+
appSecret,
|
|
169
|
+
},
|
|
170
|
+
};
|
|
111
171
|
}
|
|
112
172
|
async sendScheduledCard(workspaceId, chatId, text) {
|
|
113
173
|
return this.sendImmediateCard(workspaceId, chatId, text);
|
|
@@ -115,6 +175,111 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
115
175
|
async sendScheduledMessage(workspaceId, route, text) {
|
|
116
176
|
return this.sendScheduledCard(workspaceId, route.channelId, text);
|
|
117
177
|
}
|
|
178
|
+
async sendOutboundMessage(workspaceId, input) {
|
|
179
|
+
const state = this.resolveRuntimeState(workspaceId, input.route?.instanceId).state;
|
|
180
|
+
if (!state?.client || !state.connected) {
|
|
181
|
+
throw new Error(`Lark workspace is not connected: ${workspaceId}`);
|
|
182
|
+
}
|
|
183
|
+
const channelId = String(input.route?.channelId || '').trim();
|
|
184
|
+
if (!channelId) {
|
|
185
|
+
throw new Error('Missing Lark target channel id');
|
|
186
|
+
}
|
|
187
|
+
const messageIds = [];
|
|
188
|
+
const attachments = [];
|
|
189
|
+
for (const part of input.parts || []) {
|
|
190
|
+
if (part.type === 'text') {
|
|
191
|
+
const text = String(part.text || '').trim();
|
|
192
|
+
if (text) {
|
|
193
|
+
messageIds.push(await this.sendTextAsCard(state, channelId, text));
|
|
194
|
+
}
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (part.type === 'file') {
|
|
198
|
+
const sent = await this.sendFilePart(state, channelId, part);
|
|
199
|
+
messageIds.push(sent.messageId);
|
|
200
|
+
attachments.push({
|
|
201
|
+
kind: 'file',
|
|
202
|
+
attachmentId: sent.fileKey,
|
|
203
|
+
fileName: sent.file.fileName,
|
|
204
|
+
fileSize: sent.file.fileSize,
|
|
205
|
+
metadata: {
|
|
206
|
+
fileKey: sent.fileKey,
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
platform: 'lark',
|
|
213
|
+
workspaceId,
|
|
214
|
+
channelId,
|
|
215
|
+
participantId: input.route.participantId,
|
|
216
|
+
messageIds: messageIds.filter(Boolean),
|
|
217
|
+
attachments,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
async sendFile(workspaceId, input) {
|
|
221
|
+
const result = await this.sendOutboundMessage(workspaceId, {
|
|
222
|
+
route: {
|
|
223
|
+
type: 'channel.chat',
|
|
224
|
+
channelId: input.channelId,
|
|
225
|
+
participantId: input.participantId,
|
|
226
|
+
},
|
|
227
|
+
parts: [{
|
|
228
|
+
type: 'file',
|
|
229
|
+
path: input.path,
|
|
230
|
+
fileName: input.fileName,
|
|
231
|
+
}],
|
|
232
|
+
});
|
|
233
|
+
const attachment = result.attachments?.[0];
|
|
234
|
+
return {
|
|
235
|
+
platform: 'lark',
|
|
236
|
+
workspaceId,
|
|
237
|
+
channelId: result.channelId,
|
|
238
|
+
messageId: result.messageIds[0] || '',
|
|
239
|
+
fileKey: String(attachment?.metadata?.fileKey || attachment?.attachmentId || ''),
|
|
240
|
+
fileName: attachment?.fileName || input.fileName || '',
|
|
241
|
+
fileSize: attachment?.fileSize || 0,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
async sendFilePart(state, channelId, part) {
|
|
245
|
+
const file = await (0, file_utils_js_1.prepareChannelFile)({
|
|
246
|
+
path: part.path,
|
|
247
|
+
fileName: part.fileName,
|
|
248
|
+
maxBytes: LARK_MAX_UPLOAD_FILE_SIZE,
|
|
249
|
+
platformLabel: 'Lark',
|
|
250
|
+
});
|
|
251
|
+
const upload = await state.client.im.file.create({
|
|
252
|
+
data: {
|
|
253
|
+
file_type: this.resolveLarkUploadFileType(file.fileName),
|
|
254
|
+
file_name: file.fileName,
|
|
255
|
+
file: (0, node_fs_1.createReadStream)(file.path),
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
const fileKey = String(upload?.file_key || upload?.data?.file_key || '').trim();
|
|
259
|
+
if (!fileKey) {
|
|
260
|
+
throw new Error('Lark file upload did not return a file key');
|
|
261
|
+
}
|
|
262
|
+
const response = await state.client.im.message.create({
|
|
263
|
+
params: {
|
|
264
|
+
receive_id_type: this.resolveReceiveIdType(channelId),
|
|
265
|
+
},
|
|
266
|
+
data: {
|
|
267
|
+
receive_id: channelId,
|
|
268
|
+
msg_type: 'file',
|
|
269
|
+
content: JSON.stringify({ file_key: fileKey }),
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
const messageId = String(response?.data?.message_id || '').trim();
|
|
273
|
+
if (!messageId) {
|
|
274
|
+
throw new Error('Lark file message did not return a message id');
|
|
275
|
+
}
|
|
276
|
+
this.options.log?.(`localcore-lark sent file ${file.fileName} (${file.fileSize} bytes) to ${channelId}`);
|
|
277
|
+
return {
|
|
278
|
+
messageId,
|
|
279
|
+
fileKey,
|
|
280
|
+
file,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
118
283
|
muteThreadBridge(threadId) {
|
|
119
284
|
const current = this.mutedThreadBridgeCounts.get(threadId) || 0;
|
|
120
285
|
this.mutedThreadBridgeCounts.set(threadId, current + 1);
|
|
@@ -127,15 +292,19 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
127
292
|
}
|
|
128
293
|
this.mutedThreadBridgeCounts.set(threadId, current - 1);
|
|
129
294
|
}
|
|
130
|
-
getStatus(workspaceId) {
|
|
295
|
+
getStatus(workspaceId, instanceId) {
|
|
131
296
|
this.options.store.expirePendingPairings();
|
|
132
|
-
const
|
|
297
|
+
const resolved = this.resolveRuntimeState(workspaceId, instanceId);
|
|
298
|
+
const binding = resolved.state;
|
|
299
|
+
const platformKey = binding?.platformKey || (0, config_js_1.channelPlatformKey)('lark', resolved.instanceId);
|
|
133
300
|
const pairings = this.options.store.listPendingPairings(workspaceId)
|
|
134
|
-
.filter((row) => row.platform ===
|
|
135
|
-
const users = this.options.store.listAuthorizedUsers(workspaceId,
|
|
301
|
+
.filter((row) => row.platform === platformKey && row.expires_at >= new Date().toISOString());
|
|
302
|
+
const users = this.options.store.listAuthorizedUsers(workspaceId, platformKey);
|
|
136
303
|
return {
|
|
137
304
|
workspaceId,
|
|
138
305
|
platform: 'lark',
|
|
306
|
+
instanceId: resolved.instanceId,
|
|
307
|
+
displayName: binding?.displayName,
|
|
139
308
|
enabled: Boolean(binding?.enabled),
|
|
140
309
|
connected: Boolean(binding?.connected),
|
|
141
310
|
status: binding?.status || 'disabled',
|
|
@@ -147,16 +316,20 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
147
316
|
};
|
|
148
317
|
}
|
|
149
318
|
listStatuses() {
|
|
150
|
-
return [...this.runtime.
|
|
319
|
+
return [...this.runtime.values()]
|
|
320
|
+
.sort((a, b) => `${a.workspaceId}:${a.instanceId}`.localeCompare(`${b.workspaceId}:${b.instanceId}`))
|
|
321
|
+
.map((state) => this.getStatus(state.workspaceId, state.instanceId));
|
|
151
322
|
}
|
|
152
323
|
listPendingPairings(workspaceId) {
|
|
153
324
|
this.options.store.expirePendingPairings();
|
|
154
325
|
return this.options.store
|
|
155
|
-
.listPairingRequests(workspaceId
|
|
326
|
+
.listPairingRequests(workspaceId)
|
|
327
|
+
.filter((item) => item.platform === 'lark' || item.platform.startsWith('lark:'))
|
|
156
328
|
.filter((item) => item.status === 'pending' && item.expiresAt >= new Date().toISOString());
|
|
157
329
|
}
|
|
158
330
|
listAuthorizedUsers(workspaceId) {
|
|
159
|
-
return this.options.store.listAuthorizedUsers(workspaceId
|
|
331
|
+
return this.options.store.listAuthorizedUsers(workspaceId)
|
|
332
|
+
.filter((item) => item.platform === 'lark' || item.platform.startsWith('lark:'));
|
|
160
333
|
}
|
|
161
334
|
async start() {
|
|
162
335
|
await this.refreshBindings();
|
|
@@ -170,7 +343,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
170
343
|
if (!pairing) {
|
|
171
344
|
throw new Error(`Pairing code not found: ${code}`);
|
|
172
345
|
}
|
|
173
|
-
if (pairing.platform !== 'lark') {
|
|
346
|
+
if (pairing.platform !== 'lark' && !pairing.platform.startsWith('lark:')) {
|
|
174
347
|
throw new Error(`Pairing code ${code} is not a Lark pairing`);
|
|
175
348
|
}
|
|
176
349
|
if (pairing.status !== 'pending') {
|
|
@@ -180,12 +353,13 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
180
353
|
this.options.store.updatePairingStatus(code, 'expired');
|
|
181
354
|
throw new Error(`Pairing code ${code} has expired`);
|
|
182
355
|
}
|
|
183
|
-
const existing = this.options.store.getAuthorizedUser(pairing.workspace_id, pairing.platform_user_id);
|
|
356
|
+
const existing = this.options.store.getAuthorizedUser(pairing.workspace_id, pairing.platform_user_id, pairing.platform);
|
|
184
357
|
const userId = existing?.id || `lark-user-${(0, node_crypto_1.randomUUID)()}`;
|
|
185
358
|
const authorizedAt = new Date().toISOString();
|
|
186
359
|
this.options.store.createAuthorizedUser({
|
|
187
360
|
id: userId,
|
|
188
361
|
workspace_id: pairing.workspace_id,
|
|
362
|
+
platform: pairing.platform,
|
|
189
363
|
platform_user_id: pairing.platform_user_id,
|
|
190
364
|
chat_id: pairing.chat_id,
|
|
191
365
|
display_name: pairing.display_name,
|
|
@@ -194,7 +368,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
194
368
|
});
|
|
195
369
|
this.options.store.updatePairingStatus(code, 'approved');
|
|
196
370
|
this.notifyRuntimeStateChanged();
|
|
197
|
-
const user = this.options.store.listAuthorizedUsers(pairing.workspace_id,
|
|
371
|
+
const user = this.options.store.listAuthorizedUsers(pairing.workspace_id, pairing.platform).find((entry) => entry.id === userId);
|
|
198
372
|
if (!user) {
|
|
199
373
|
throw new Error('Authorized user lookup failed after approval');
|
|
200
374
|
}
|
|
@@ -220,12 +394,14 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
220
394
|
this.options.log?.(`localcore-lark bridge route miss for sessionKey=${sessionKey} type=${event.type}`);
|
|
221
395
|
return;
|
|
222
396
|
}
|
|
223
|
-
const
|
|
397
|
+
const routeInstanceId = route.instanceId || 'default';
|
|
398
|
+
const routePlatformKey = route.platformKey || (0, config_js_1.channelPlatformKey)('lark', routeInstanceId);
|
|
399
|
+
const state = this.runtime.get((0, config_js_1.runtimeKey)(route.workspaceId, routeInstanceId)) || this.runtime.get(route.workspaceId);
|
|
224
400
|
if (!state?.client || !state.connected) {
|
|
225
401
|
this.options.log?.(`localcore-lark bridge event ignored because workspace is not connected: ${route.workspaceId}`);
|
|
226
402
|
return;
|
|
227
403
|
}
|
|
228
|
-
const initialBinding = this.options.store.getPlatformThreadBinding(route.workspaceId, route.chatId, route.platformUserId);
|
|
404
|
+
const initialBinding = this.options.store.getPlatformThreadBinding(route.workspaceId, route.chatId, route.platformUserId, routePlatformKey);
|
|
229
405
|
if (!initialBinding) {
|
|
230
406
|
this.options.log?.(`localcore-lark bridge binding miss for workspace=${route.workspaceId} chat=${route.chatId} user=${route.platformUserId}`);
|
|
231
407
|
return;
|
|
@@ -244,7 +420,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
244
420
|
const current = previous
|
|
245
421
|
.catch(() => undefined)
|
|
246
422
|
.then(async () => {
|
|
247
|
-
const binding = this.options.store.getPlatformThreadBinding(route.workspaceId, route.chatId, route.platformUserId);
|
|
423
|
+
const binding = this.options.store.getPlatformThreadBinding(route.workspaceId, route.chatId, route.platformUserId, routePlatformKey);
|
|
248
424
|
if (!binding) {
|
|
249
425
|
this.options.log?.(`localcore-lark bridge binding disappeared for sessionKey=${sessionKey}`);
|
|
250
426
|
return;
|
|
@@ -273,7 +449,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
273
449
|
this.options.store.clearPlatformThreadMessageId(route.workspaceId, route.chatId, route.platformUserId);
|
|
274
450
|
}
|
|
275
451
|
if (event.type === 'buttons') {
|
|
276
|
-
const permissionCard =
|
|
452
|
+
const permissionCard = (0, cards_js_1.renderPermissionCard)(turn, event, Boolean(state.cardActionsEnabled));
|
|
277
453
|
if (permissionCard.text || permissionCard.buttonRows.length > 0) {
|
|
278
454
|
if (!turn.permissionMessageId) {
|
|
279
455
|
const createdId = await this.sendTextAsCard(state, route.chatId, permissionCard.text, permissionCard.buttonRows, sessionKey, binding.thread_id);
|
|
@@ -291,12 +467,12 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
291
467
|
return;
|
|
292
468
|
}
|
|
293
469
|
this.consumeBridgeEvent(turn, event);
|
|
294
|
-
const rendered =
|
|
470
|
+
const rendered = (0, runtime_state_js_1.renderLarkBridgeEventMessage)(turn, event);
|
|
295
471
|
if (!rendered.text && rendered.buttonRows.length === 0) {
|
|
296
472
|
this.options.log?.(`localcore-lark bridge event produced empty render for sessionKey=${sessionKey} type=${event.type}`);
|
|
297
473
|
return;
|
|
298
474
|
}
|
|
299
|
-
const existingMessageId =
|
|
475
|
+
const existingMessageId = (0, runtime_state_js_1.getLarkRenderedMessageId)(turn, rendered);
|
|
300
476
|
const shouldThrottle = event.type === 'update_message' &&
|
|
301
477
|
rendered.isFinal &&
|
|
302
478
|
existingMessageId &&
|
|
@@ -308,9 +484,9 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
308
484
|
if (!existingMessageId) {
|
|
309
485
|
const createdId = await this.sendTextAsCard(state, route.chatId, rendered.text, rendered.buttonRows, sessionKey, binding.thread_id);
|
|
310
486
|
if (createdId) {
|
|
311
|
-
|
|
487
|
+
(0, runtime_state_js_1.setLarkRenderedMessageId)(turn, rendered, createdId);
|
|
312
488
|
if (rendered.isFinal) {
|
|
313
|
-
this.options.store.updatePlatformThreadMessageId(route.workspaceId, route.chatId, route.platformUserId, createdId);
|
|
489
|
+
this.options.store.updatePlatformThreadMessageId(route.workspaceId, route.chatId, route.platformUserId, createdId, routePlatformKey);
|
|
314
490
|
}
|
|
315
491
|
this.options.log?.(`localcore-lark sent new card message ${createdId} for sessionKey=${sessionKey}`);
|
|
316
492
|
}
|
|
@@ -334,6 +510,8 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
334
510
|
await current;
|
|
335
511
|
}
|
|
336
512
|
async handleInboundMessage(input) {
|
|
513
|
+
const instanceId = input.instanceId || 'default';
|
|
514
|
+
const platformKey = input.platformKey || (0, config_js_1.channelPlatformKey)('lark', instanceId);
|
|
337
515
|
this.options.eventBus.emit({
|
|
338
516
|
type: 'platform.message.received',
|
|
339
517
|
payload: {
|
|
@@ -347,27 +525,38 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
347
525
|
},
|
|
348
526
|
});
|
|
349
527
|
this.options.store.expirePendingPairings();
|
|
350
|
-
const
|
|
351
|
-
let
|
|
528
|
+
const runtimeState = this.resolveRuntimeState(input.workspaceId, instanceId).state;
|
|
529
|
+
let binding;
|
|
530
|
+
try {
|
|
531
|
+
binding = await this.getBinding(input.workspaceId, instanceId);
|
|
532
|
+
}
|
|
533
|
+
catch (error) {
|
|
534
|
+
if (!runtimeState) {
|
|
535
|
+
throw error;
|
|
536
|
+
}
|
|
537
|
+
this.options.log?.(`localcore-lark using active runtime binding snapshot for ${input.workspaceId}/${instanceId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
538
|
+
}
|
|
539
|
+
let authorized = this.options.store.getAuthorizedUser(input.workspaceId, input.platformUserId, platformKey);
|
|
352
540
|
if (!authorized) {
|
|
353
|
-
if (binding
|
|
541
|
+
if (binding?.autoApprove || runtimeState?.autoApprove) {
|
|
354
542
|
const authorizedAt = new Date().toISOString();
|
|
355
543
|
this.options.store.createAuthorizedUser({
|
|
356
544
|
id: `lark-user-${(0, node_crypto_1.randomUUID)()}`,
|
|
357
545
|
workspace_id: input.workspaceId,
|
|
546
|
+
platform: platformKey,
|
|
358
547
|
platform_user_id: input.platformUserId,
|
|
359
548
|
chat_id: input.chatId,
|
|
360
549
|
display_name: input.displayName,
|
|
361
550
|
thread_id: null,
|
|
362
551
|
authorized_at: authorizedAt,
|
|
363
552
|
});
|
|
364
|
-
authorized = this.options.store.getAuthorizedUser(input.workspaceId, input.platformUserId);
|
|
553
|
+
authorized = this.options.store.getAuthorizedUser(input.workspaceId, input.platformUserId, platformKey);
|
|
365
554
|
this.options.log?.(`localcore-lark auto-approved user for ${input.workspaceId}: ${input.platformUserId}`);
|
|
366
555
|
this.notifyRuntimeStateChanged();
|
|
367
556
|
}
|
|
368
557
|
}
|
|
369
558
|
if (!authorized) {
|
|
370
|
-
const existingPending = this.options.store.listPendingPairings(input.workspaceId).find((item) => item.platform ===
|
|
559
|
+
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');
|
|
371
560
|
let pairingCode = existingPending?.code || '';
|
|
372
561
|
if (!existingPending) {
|
|
373
562
|
const now = new Date();
|
|
@@ -375,6 +564,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
375
564
|
this.options.store.createPairingRequest({
|
|
376
565
|
code: pairingCode,
|
|
377
566
|
workspace_id: input.workspaceId,
|
|
567
|
+
platform: platformKey,
|
|
378
568
|
platform_user_id: input.platformUserId,
|
|
379
569
|
chat_id: input.chatId,
|
|
380
570
|
display_name: input.displayName,
|
|
@@ -384,19 +574,20 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
384
574
|
});
|
|
385
575
|
this.notifyRuntimeStateChanged();
|
|
386
576
|
}
|
|
387
|
-
await this.sendImmediateCard(input.workspaceId, input.chatId,
|
|
577
|
+
await this.sendImmediateCard(input.workspaceId, input.chatId, (0, cards_js_1.renderPendingPairingCard)(pairingCode), instanceId);
|
|
388
578
|
return { paired: false };
|
|
389
579
|
}
|
|
390
580
|
const router = this.options.getWorkspaceRouter();
|
|
391
|
-
const threadBinding = this.options.store.getPlatformThreadBinding(input.workspaceId, input.chatId, input.platformUserId);
|
|
581
|
+
const threadBinding = this.options.store.getPlatformThreadBinding(input.workspaceId, input.chatId, input.platformUserId, platformKey);
|
|
392
582
|
let threadId = threadBinding?.thread_id || authorized.thread_id || '';
|
|
393
583
|
if (!threadId) {
|
|
394
584
|
const thread = await router.createThread(input.workspaceId, input.displayName || `Lark ${input.chatId}`);
|
|
395
585
|
threadId = thread.id;
|
|
396
|
-
this.options.store.updateAuthorizedUserThread(input.workspaceId, input.platformUserId, threadId);
|
|
586
|
+
this.options.store.updateAuthorizedUserThread(input.workspaceId, input.platformUserId, threadId, platformKey);
|
|
397
587
|
const now = new Date().toISOString();
|
|
398
588
|
this.options.store.upsertPlatformThreadBinding({
|
|
399
589
|
workspace_id: input.workspaceId,
|
|
590
|
+
platform: platformKey,
|
|
400
591
|
chat_id: input.chatId,
|
|
401
592
|
platform_user_id: input.platformUserId,
|
|
402
593
|
thread_id: threadId,
|
|
@@ -416,20 +607,23 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
416
607
|
const effectiveSessionKey = this.options.getWorkspaceRouter().getThreadSessionKey(threadId);
|
|
417
608
|
this.threadRouting.set(effectiveSessionKey, {
|
|
418
609
|
workspaceId: input.workspaceId,
|
|
610
|
+
instanceId,
|
|
611
|
+
platformKey,
|
|
419
612
|
platformUserId: input.platformUserId,
|
|
420
613
|
chatId: input.chatId,
|
|
421
614
|
threadId,
|
|
422
615
|
});
|
|
423
616
|
const acknowledgement = this.createTurnState(effectiveSessionKey, input.messageId);
|
|
424
|
-
await this.addAcknowledgementReaction(input.workspaceId, input.messageId, acknowledgement);
|
|
617
|
+
await this.addAcknowledgementReaction(input.workspaceId, input.messageId, acknowledgement, instanceId);
|
|
425
618
|
const slashCommand = this.parseSlashCommand(input.text);
|
|
426
619
|
if (slashCommand?.name === 'new') {
|
|
427
620
|
const title = slashCommand.args.join(' ').trim() || `${input.displayName || 'Lark'} ${new Date().toLocaleTimeString()}`;
|
|
428
621
|
const nextThread = await router.createThread(input.workspaceId, title);
|
|
429
622
|
const now = new Date().toISOString();
|
|
430
|
-
this.options.store.updateAuthorizedUserThread(input.workspaceId, input.platformUserId, nextThread.id);
|
|
623
|
+
this.options.store.updateAuthorizedUserThread(input.workspaceId, input.platformUserId, nextThread.id, platformKey);
|
|
431
624
|
this.options.store.upsertPlatformThreadBinding({
|
|
432
625
|
workspace_id: input.workspaceId,
|
|
626
|
+
platform: platformKey,
|
|
433
627
|
chat_id: input.chatId,
|
|
434
628
|
platform_user_id: input.platformUserId,
|
|
435
629
|
thread_id: nextThread.id,
|
|
@@ -439,11 +633,13 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
439
633
|
});
|
|
440
634
|
this.threadRouting.set(this.options.getWorkspaceRouter().getThreadSessionKey(nextThread.id), {
|
|
441
635
|
workspaceId: input.workspaceId,
|
|
636
|
+
instanceId,
|
|
637
|
+
platformKey,
|
|
442
638
|
platformUserId: input.platformUserId,
|
|
443
639
|
chatId: input.chatId,
|
|
444
640
|
threadId: nextThread.id,
|
|
445
641
|
});
|
|
446
|
-
await this.sendImmediateCard(input.workspaceId, input.chatId, '**已开始新会话**');
|
|
642
|
+
await this.sendImmediateCard(input.workspaceId, input.chatId, '**已开始新会话**', instanceId);
|
|
447
643
|
return { paired: true, threadId: nextThread.id };
|
|
448
644
|
}
|
|
449
645
|
const latestRun = this.options.store.getLatestRunForThread(threadId);
|
|
@@ -458,13 +654,25 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
458
654
|
return { paired: true, threadId };
|
|
459
655
|
}
|
|
460
656
|
close() {
|
|
461
|
-
return Promise.all([...this.runtime.keys()].map((
|
|
657
|
+
return Promise.all([...this.runtime.keys()].map((key) => this.stopWorkspaceKey(key))).then(() => undefined);
|
|
462
658
|
}
|
|
463
|
-
|
|
659
|
+
resolveRuntimeState(workspaceId, instanceId) {
|
|
660
|
+
if (instanceId) {
|
|
661
|
+
const state = this.runtime.get((0, config_js_1.runtimeKey)(workspaceId, instanceId)) || (instanceId === 'default' ? this.runtime.get(workspaceId) : undefined);
|
|
662
|
+
return { instanceId, state };
|
|
663
|
+
}
|
|
664
|
+
const states = [...this.runtime.values()].filter((entry) => entry.workspaceId === workspaceId);
|
|
665
|
+
const state = states.find((entry) => entry.instanceId === 'default') || states[0];
|
|
666
|
+
return { instanceId: state?.instanceId || 'default', state: state || this.runtime.get(workspaceId) };
|
|
667
|
+
}
|
|
668
|
+
async getBinding(workspaceId, instanceId) {
|
|
464
669
|
const config = await this.options.readConfig();
|
|
465
|
-
const
|
|
670
|
+
const bindings = this.collectBindings(config).filter((entry) => entry.workspaceId === workspaceId);
|
|
671
|
+
const binding = instanceId
|
|
672
|
+
? bindings.find((entry) => entry.instanceId === instanceId)
|
|
673
|
+
: bindings.find((entry) => entry.instanceId === 'default') || bindings[0];
|
|
466
674
|
if (!binding) {
|
|
467
|
-
throw new Error(`No Lark binding configured for workspace "${workspaceId}"`);
|
|
675
|
+
throw new Error(`No Lark binding configured for workspace "${workspaceId}"${instanceId ? ` instance "${instanceId}"` : ''}`);
|
|
468
676
|
}
|
|
469
677
|
return binding;
|
|
470
678
|
}
|
|
@@ -482,6 +690,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
482
690
|
success: true,
|
|
483
691
|
platform: 'lark',
|
|
484
692
|
workspaceId: binding.workspaceId,
|
|
693
|
+
instanceId: binding.instanceId,
|
|
485
694
|
appId: binding.appId,
|
|
486
695
|
};
|
|
487
696
|
}
|
|
@@ -490,22 +699,28 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
490
699
|
success: false,
|
|
491
700
|
platform: 'lark',
|
|
492
701
|
workspaceId: binding.workspaceId,
|
|
702
|
+
instanceId: binding.instanceId,
|
|
493
703
|
appId: binding.appId,
|
|
494
704
|
error: error instanceof Error ? error.message : String(error),
|
|
495
705
|
};
|
|
496
706
|
}
|
|
497
707
|
}
|
|
498
708
|
async startWorkspace(binding) {
|
|
499
|
-
|
|
709
|
+
const key = (0, config_js_1.runtimeKey)(binding.workspaceId, binding.instanceId);
|
|
710
|
+
await this.stopWorkspaceKey(key);
|
|
500
711
|
const status = {
|
|
501
712
|
workspaceId: binding.workspaceId,
|
|
713
|
+
instanceId: binding.instanceId,
|
|
714
|
+
displayName: binding.displayName,
|
|
715
|
+
platformKey: binding.platformKey,
|
|
502
716
|
enabled: true,
|
|
503
717
|
status: 'starting',
|
|
504
718
|
connected: false,
|
|
505
719
|
appId: binding.appId,
|
|
720
|
+
autoApprove: binding.autoApprove,
|
|
506
721
|
cardActionsEnabled: binding.cardActionsEnabled,
|
|
507
722
|
};
|
|
508
|
-
this.runtime.set(
|
|
723
|
+
this.runtime.set(key, status);
|
|
509
724
|
this.notifyRuntimeStateChanged();
|
|
510
725
|
try {
|
|
511
726
|
this.options.log?.(`localcore-lark starting workspace=${binding.workspaceId} app=${this.maskLarkAppId(binding.appId)} cardActions=${binding.cardActionsEnabled}`);
|
|
@@ -523,15 +738,15 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
523
738
|
});
|
|
524
739
|
status.eventDispatcher.register({
|
|
525
740
|
'im.message.receive_v1': async (data) => {
|
|
526
|
-
this.options.log?.(`localcore-lark received im.message.receive_v1 for ${binding.workspaceId}: ${this.summarizeLarkEvent(data)}`);
|
|
527
|
-
void this.handleMessageEvent(binding.workspaceId, data).catch((error) => {
|
|
741
|
+
this.options.log?.(`localcore-lark received im.message.receive_v1 for ${binding.workspaceId}/${binding.instanceId}: ${this.summarizeLarkEvent(data)}`);
|
|
742
|
+
void this.handleMessageEvent(binding.workspaceId, binding.instanceId, binding.platformKey, data).catch((error) => {
|
|
528
743
|
this.options.log?.(`localcore-lark inbound message failed for ${binding.workspaceId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
529
744
|
});
|
|
530
745
|
return {};
|
|
531
746
|
},
|
|
532
747
|
'card.action.trigger': async (data) => {
|
|
533
|
-
this.options.log?.(`localcore-lark received card.action.trigger for ${binding.workspaceId}`);
|
|
534
|
-
void this.handleCardActionEvent(binding.workspaceId, data);
|
|
748
|
+
this.options.log?.(`localcore-lark received card.action.trigger for ${binding.workspaceId}/${binding.instanceId}`);
|
|
749
|
+
void this.handleCardActionEvent(binding.workspaceId, binding.instanceId, data);
|
|
535
750
|
return {};
|
|
536
751
|
},
|
|
537
752
|
});
|
|
@@ -561,26 +776,34 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
561
776
|
}
|
|
562
777
|
this.notifyRuntimeStateChanged();
|
|
563
778
|
}
|
|
564
|
-
async stopWorkspace(workspaceId) {
|
|
565
|
-
const
|
|
779
|
+
async stopWorkspace(workspaceId, instanceId) {
|
|
780
|
+
const resolved = this.resolveRuntimeState(workspaceId, instanceId);
|
|
781
|
+
await this.stopWorkspaceKey((0, config_js_1.runtimeKey)(workspaceId, resolved.instanceId));
|
|
782
|
+
}
|
|
783
|
+
async stopWorkspaceKey(key) {
|
|
784
|
+
const state = this.runtime.get(key);
|
|
566
785
|
if (!state) {
|
|
567
786
|
return;
|
|
568
787
|
}
|
|
569
|
-
this.options.log?.(`localcore-lark stopping workspace=${workspaceId} status=${state.status}`);
|
|
788
|
+
this.options.log?.(`localcore-lark stopping workspace=${state.workspaceId}/${state.instanceId} status=${state.status}`);
|
|
570
789
|
try {
|
|
571
790
|
await state.wsClient?.stop?.();
|
|
572
|
-
this.options.log?.(`localcore-lark stopped workspace=${workspaceId}`);
|
|
791
|
+
this.options.log?.(`localcore-lark stopped workspace=${state.workspaceId}/${state.instanceId}`);
|
|
573
792
|
}
|
|
574
793
|
catch (error) {
|
|
575
|
-
this.options.log?.(`localcore-lark stop failed for ${workspaceId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
794
|
+
this.options.log?.(`localcore-lark stop failed for ${state.workspaceId}/${state.instanceId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
576
795
|
// Best effort: current SDK versions may not implement stop().
|
|
577
796
|
}
|
|
578
|
-
this.runtime.set(
|
|
579
|
-
workspaceId,
|
|
797
|
+
this.runtime.set(key, {
|
|
798
|
+
workspaceId: state.workspaceId,
|
|
799
|
+
instanceId: state.instanceId,
|
|
800
|
+
displayName: state.displayName,
|
|
801
|
+
platformKey: state.platformKey,
|
|
580
802
|
enabled: false,
|
|
581
803
|
status: 'stopped',
|
|
582
804
|
connected: false,
|
|
583
805
|
appId: state.appId,
|
|
806
|
+
autoApprove: state.autoApprove,
|
|
584
807
|
cardActionsEnabled: state.cardActionsEnabled,
|
|
585
808
|
});
|
|
586
809
|
this.notifyRuntimeStateChanged();
|
|
@@ -593,7 +816,11 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
593
816
|
},
|
|
594
817
|
});
|
|
595
818
|
}
|
|
596
|
-
async handleMessageEvent(workspaceId,
|
|
819
|
+
async handleMessageEvent(workspaceId, instanceIdOrData, platformKeyOrData, maybeData) {
|
|
820
|
+
const legacyCall = typeof instanceIdOrData === 'object';
|
|
821
|
+
const instanceId = legacyCall ? 'default' : instanceIdOrData;
|
|
822
|
+
const platformKey = legacyCall ? 'lark' : String(platformKeyOrData || (0, config_js_1.channelPlatformKey)('lark', instanceId));
|
|
823
|
+
const data = (legacyCall ? instanceIdOrData : maybeData) || {};
|
|
597
824
|
const payload = (data?.event && typeof data.event === 'object')
|
|
598
825
|
? data.event
|
|
599
826
|
: data;
|
|
@@ -621,7 +848,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
621
848
|
const imageKey = String(parsedContent.image_key || parsedContent.file_key || '').trim();
|
|
622
849
|
if (imageKey) {
|
|
623
850
|
try {
|
|
624
|
-
contentParts.push(await this.downloadMessageImage(workspaceId, String(message.message_id || ''), imageKey));
|
|
851
|
+
contentParts.push(await this.downloadMessageImage(workspaceId, String(message.message_id || ''), imageKey, instanceId));
|
|
625
852
|
}
|
|
626
853
|
catch (error) {
|
|
627
854
|
const errorText = `[Image download failed: ${error instanceof Error ? error.message : String(error)}]`;
|
|
@@ -648,6 +875,8 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
648
875
|
this.options.log?.(`localcore-lark inbound message for ${workspaceId}: chat=${chatId} user=${platformUserId} type=${messageType || 'unknown'} text=${JSON.stringify(displayText.slice(0, 120))}`);
|
|
649
876
|
await this.handleInboundMessage({
|
|
650
877
|
workspaceId,
|
|
878
|
+
instanceId,
|
|
879
|
+
platformKey,
|
|
651
880
|
platformUserId,
|
|
652
881
|
chatId,
|
|
653
882
|
displayName: String(payload?.sender?.sender_id?.user_id ||
|
|
@@ -677,8 +906,8 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
677
906
|
contentParts: this.createWrappedContentParts(wrappedText, parts),
|
|
678
907
|
};
|
|
679
908
|
}
|
|
680
|
-
async downloadMessageImage(workspaceId, messageId, imageKey) {
|
|
681
|
-
const state = this.
|
|
909
|
+
async downloadMessageImage(workspaceId, messageId, imageKey, instanceId) {
|
|
910
|
+
const state = this.resolveRuntimeState(workspaceId, instanceId).state;
|
|
682
911
|
if (!state?.client) {
|
|
683
912
|
throw new Error('Lark client is not connected');
|
|
684
913
|
}
|
|
@@ -812,32 +1041,8 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
812
1041
|
return `${value.slice(0, 6)}...${value.slice(-4)}`;
|
|
813
1042
|
}
|
|
814
1043
|
collectBindings(config) {
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
const platforms = Array.isArray(project.platforms) ? project.platforms : [];
|
|
818
|
-
return platforms
|
|
819
|
-
.map((platform) => ({
|
|
820
|
-
platformType: (0, desktop_js_1.normalizeDesktopPlatformType)(platform?.type),
|
|
821
|
-
options: platform?.options && typeof platform.options === 'object'
|
|
822
|
-
? platform.options
|
|
823
|
-
: {},
|
|
824
|
-
}))
|
|
825
|
-
.filter((platform) => platform.platformType === 'lark')
|
|
826
|
-
.map((platform) => ({
|
|
827
|
-
workspaceId: project.name,
|
|
828
|
-
appId: String(platform.options.app_id || '').trim(),
|
|
829
|
-
appSecret: String(platform.options.app_secret || '').trim(),
|
|
830
|
-
encryptKey: String(platform.options.encrypt_key || '').trim(),
|
|
831
|
-
verificationToken: String(platform.options.verification_token || '').trim(),
|
|
832
|
-
autoApprove: String(platform.options.auto_approve || '').trim().toLowerCase() === 'true'
|
|
833
|
-
|| platform.options.auto_approve === true,
|
|
834
|
-
cardActionsEnabled: String(platform.options.card_actions || platform.options.enable_card_actions || '').trim().toLowerCase() === 'true'
|
|
835
|
-
|| platform.options.card_actions === true
|
|
836
|
-
|| platform.options.enable_card_actions === true
|
|
837
|
-
|| this.defaultCardActionsEnabled,
|
|
838
|
-
enabled: Boolean(String(platform.options.app_id || '').trim() && String(platform.options.app_secret || '').trim()),
|
|
839
|
-
project,
|
|
840
|
-
}));
|
|
1044
|
+
return (0, config_js_1.collectLarkWorkspaceBindings)(config, {
|
|
1045
|
+
defaultCardActionsEnabled: this.defaultCardActionsEnabled,
|
|
841
1046
|
});
|
|
842
1047
|
}
|
|
843
1048
|
async getLarkModule() {
|
|
@@ -846,61 +1051,53 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
846
1051
|
}
|
|
847
1052
|
return this.larkModulePromise;
|
|
848
1053
|
}
|
|
849
|
-
buildInteractiveCard(text, buttonRows = [], sessionKey, threadId) {
|
|
850
|
-
const elements = [];
|
|
851
|
-
if (text) {
|
|
852
|
-
elements.push({ tag: 'markdown', content: text });
|
|
853
|
-
}
|
|
854
|
-
for (const row of buttonRows) {
|
|
855
|
-
const actions = row
|
|
856
|
-
.filter((button) => button.text && button.data)
|
|
857
|
-
.map((button, index) => ({
|
|
858
|
-
tag: 'button',
|
|
859
|
-
text: {
|
|
860
|
-
tag: 'plain_text',
|
|
861
|
-
content: this.formatPermissionButtonLabel(button),
|
|
862
|
-
},
|
|
863
|
-
type: this.resolveLarkButtonType(button, index),
|
|
864
|
-
value: {
|
|
865
|
-
action: 'permission_response',
|
|
866
|
-
response: (0, desktop_js_1.normalizePermissionResponse)(button.data) || button.data,
|
|
867
|
-
session_key: sessionKey || '',
|
|
868
|
-
thread_id: threadId || '',
|
|
869
|
-
},
|
|
870
|
-
}));
|
|
871
|
-
if (actions.length) {
|
|
872
|
-
elements.push({
|
|
873
|
-
tag: 'action',
|
|
874
|
-
actions,
|
|
875
|
-
});
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
return {
|
|
879
|
-
config: { wide_screen_mode: true },
|
|
880
|
-
elements,
|
|
881
|
-
};
|
|
882
|
-
}
|
|
883
1054
|
async sendTextAsCard(state, chatId, text, buttonRows = [], sessionKey, threadId) {
|
|
884
|
-
const receiveIdType = chatId.startsWith('oc_') ? 'chat_id' : chatId.startsWith('ou_') ? 'open_id' : 'user_id';
|
|
885
1055
|
const response = await state.client.im.message.create({
|
|
886
1056
|
params: {
|
|
887
|
-
receive_id_type:
|
|
1057
|
+
receive_id_type: this.resolveReceiveIdType(chatId),
|
|
888
1058
|
},
|
|
889
1059
|
data: {
|
|
890
1060
|
receive_id: chatId,
|
|
891
1061
|
msg_type: 'interactive',
|
|
892
|
-
content: JSON.stringify(
|
|
1062
|
+
content: JSON.stringify((0, cards_js_1.buildInteractiveCard)(text, buttonRows, sessionKey, threadId)),
|
|
893
1063
|
},
|
|
894
1064
|
});
|
|
895
1065
|
return String(response?.data?.message_id || '').trim();
|
|
896
1066
|
}
|
|
1067
|
+
resolveReceiveIdType(receiveId) {
|
|
1068
|
+
return receiveId.startsWith('oc_') ? 'chat_id' : receiveId.startsWith('ou_') ? 'open_id' : 'user_id';
|
|
1069
|
+
}
|
|
1070
|
+
resolveLarkUploadFileType(fileName) {
|
|
1071
|
+
switch ((0, node_path_1.extname)(fileName).toLowerCase()) {
|
|
1072
|
+
case '.opus':
|
|
1073
|
+
return 'opus';
|
|
1074
|
+
case '.mp4':
|
|
1075
|
+
case '.mov':
|
|
1076
|
+
case '.m4v':
|
|
1077
|
+
return 'mp4';
|
|
1078
|
+
case '.pdf':
|
|
1079
|
+
return 'pdf';
|
|
1080
|
+
case '.doc':
|
|
1081
|
+
case '.docx':
|
|
1082
|
+
return 'doc';
|
|
1083
|
+
case '.xls':
|
|
1084
|
+
case '.xlsx':
|
|
1085
|
+
case '.csv':
|
|
1086
|
+
return 'xls';
|
|
1087
|
+
case '.ppt':
|
|
1088
|
+
case '.pptx':
|
|
1089
|
+
return 'ppt';
|
|
1090
|
+
default:
|
|
1091
|
+
return 'stream';
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
897
1094
|
async patchTextCard(state, messageId, text, buttonRows = [], sessionKey, threadId) {
|
|
898
1095
|
await state.client.im.message.patch({
|
|
899
1096
|
path: {
|
|
900
1097
|
message_id: messageId,
|
|
901
1098
|
},
|
|
902
1099
|
data: {
|
|
903
|
-
content: JSON.stringify(
|
|
1100
|
+
content: JSON.stringify((0, cards_js_1.buildInteractiveCard)(text, buttonRows, sessionKey, threadId)),
|
|
904
1101
|
},
|
|
905
1102
|
});
|
|
906
1103
|
}
|
|
@@ -936,21 +1133,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
936
1133
|
};
|
|
937
1134
|
}
|
|
938
1135
|
createTurnState(sessionKey, sourceMessageId) {
|
|
939
|
-
const turn =
|
|
940
|
-
sessionKey,
|
|
941
|
-
sourceMessageId,
|
|
942
|
-
awaitingPermission: false,
|
|
943
|
-
processing: false,
|
|
944
|
-
previewText: '',
|
|
945
|
-
finalText: '',
|
|
946
|
-
progressMessageIds: {},
|
|
947
|
-
thinkingSteps: [],
|
|
948
|
-
toolCalls: [],
|
|
949
|
-
statusLines: [],
|
|
950
|
-
buttonRows: [],
|
|
951
|
-
lastPatchedAt: 0,
|
|
952
|
-
lastPatchedAtByMessageId: {},
|
|
953
|
-
};
|
|
1136
|
+
const turn = (0, runtime_state_js_1.createLarkTurnState)(sessionKey, sourceMessageId);
|
|
954
1137
|
this.outboundTurns.set(sessionKey, turn);
|
|
955
1138
|
return turn;
|
|
956
1139
|
}
|
|
@@ -971,258 +1154,57 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
971
1154
|
return turn;
|
|
972
1155
|
}
|
|
973
1156
|
consumeBridgeEvent(turn, event) {
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
// This keeps Feishu message order aligned with user-visible timeline.
|
|
978
|
-
turn.messageId = undefined;
|
|
979
|
-
turn.finalMessageId = undefined;
|
|
980
|
-
turn.progressMessageIds = {};
|
|
981
|
-
turn.processing = true;
|
|
982
|
-
turn.permissionMessageId = undefined;
|
|
983
|
-
turn.previewText = '';
|
|
984
|
-
turn.finalText = '';
|
|
985
|
-
turn.thinkingSteps = [];
|
|
986
|
-
turn.toolCalls = [];
|
|
987
|
-
turn.buttonRows = [];
|
|
988
|
-
turn.statusLines = [];
|
|
989
|
-
turn.lastPatchedAtByMessageId = {};
|
|
990
|
-
return;
|
|
991
|
-
}
|
|
992
|
-
if (event.type === 'typing_stop') {
|
|
993
|
-
turn.processing = false;
|
|
994
|
-
return;
|
|
995
|
-
}
|
|
996
|
-
if (event.type === 'preview_start' || event.type === 'update_message') {
|
|
997
|
-
turn.previewText = content;
|
|
998
|
-
return;
|
|
999
|
-
}
|
|
1000
|
-
if (event.type === 'status') {
|
|
1001
|
-
if (content) {
|
|
1002
|
-
this.pushUnique(turn.statusLines, content);
|
|
1003
|
-
}
|
|
1004
|
-
return;
|
|
1005
|
-
}
|
|
1006
|
-
if (event.type === 'buttons') {
|
|
1007
|
-
turn.awaitingPermission = true;
|
|
1008
|
-
turn.buttonRows = [];
|
|
1009
|
-
if (this.mirrorPermissionStateInMainCard && content) {
|
|
1010
|
-
this.pushUnique(turn.statusLines, `等待确认: ${content}`);
|
|
1011
|
-
}
|
|
1012
|
-
return;
|
|
1013
|
-
}
|
|
1014
|
-
if (!content) {
|
|
1015
|
-
return;
|
|
1016
|
-
}
|
|
1017
|
-
if (content.startsWith('💭 ')) {
|
|
1018
|
-
this.pushUnique(turn.thinkingSteps, content.slice(3).trim());
|
|
1019
|
-
return;
|
|
1020
|
-
}
|
|
1021
|
-
if (content.startsWith('🔧 ')) {
|
|
1022
|
-
this.pushUnique(turn.toolCalls, content.slice(3).trim());
|
|
1023
|
-
return;
|
|
1024
|
-
}
|
|
1025
|
-
if (content.startsWith('⏳ ') || content.startsWith('📤 ')) {
|
|
1026
|
-
this.pushUnique(turn.statusLines, content.slice(3).trim());
|
|
1027
|
-
return;
|
|
1028
|
-
}
|
|
1029
|
-
turn.finalText = content;
|
|
1030
|
-
turn.previewText = content;
|
|
1031
|
-
}
|
|
1032
|
-
renderBridgeEventMessage(turn, event) {
|
|
1033
|
-
const content = String(event.content || '').trim();
|
|
1034
|
-
if (event.type === 'preview_start' || event.type === 'update_message') {
|
|
1035
|
-
if (content.startsWith('💭 ')) {
|
|
1036
|
-
return this.renderProgressMessage(event.previewHandle || 'thinking-preview', content);
|
|
1037
|
-
}
|
|
1038
|
-
if (content.startsWith('🔧 ')) {
|
|
1039
|
-
return this.renderProgressMessage(this.progressKey('tool', event), content);
|
|
1040
|
-
}
|
|
1041
|
-
return {
|
|
1042
|
-
key: 'final',
|
|
1043
|
-
text: content,
|
|
1044
|
-
buttonRows: turn.buttonRows,
|
|
1045
|
-
isFinal: true,
|
|
1046
|
-
};
|
|
1047
|
-
}
|
|
1048
|
-
if (event.type === 'status') {
|
|
1049
|
-
return this.renderProgressMessage(this.progressKey('status', event), content.startsWith('⏳ ') ? content : `⏳ ${content}`);
|
|
1050
|
-
}
|
|
1051
|
-
if (event.type === 'reply') {
|
|
1052
|
-
if (content.startsWith('💭 ')) {
|
|
1053
|
-
return this.renderProgressMessage(this.progressKey('thinking', event), content);
|
|
1054
|
-
}
|
|
1055
|
-
if (content.startsWith('🔧 ')) {
|
|
1056
|
-
return this.renderProgressMessage(this.progressKey('tool', event), content);
|
|
1057
|
-
}
|
|
1058
|
-
if (content.startsWith('⏳ ') || content.startsWith('📤 ')) {
|
|
1059
|
-
return this.renderProgressMessage(this.progressKey('status', event), content);
|
|
1060
|
-
}
|
|
1061
|
-
return {
|
|
1062
|
-
key: 'final',
|
|
1063
|
-
text: content,
|
|
1064
|
-
buttonRows: turn.buttonRows,
|
|
1065
|
-
isFinal: true,
|
|
1066
|
-
};
|
|
1067
|
-
}
|
|
1068
|
-
if (event.type === 'typing_start' || event.type === 'typing_stop') {
|
|
1069
|
-
return { key: 'noop', text: '', buttonRows: [], isFinal: false };
|
|
1070
|
-
}
|
|
1071
|
-
return {
|
|
1072
|
-
key: 'noop',
|
|
1073
|
-
text: '',
|
|
1074
|
-
buttonRows: [],
|
|
1075
|
-
isFinal: false,
|
|
1076
|
-
};
|
|
1077
|
-
}
|
|
1078
|
-
renderProgressMessage(key, text) {
|
|
1079
|
-
return {
|
|
1080
|
-
key,
|
|
1081
|
-
text,
|
|
1082
|
-
buttonRows: [],
|
|
1083
|
-
isFinal: false,
|
|
1084
|
-
};
|
|
1085
|
-
}
|
|
1086
|
-
progressKey(prefix, event) {
|
|
1087
|
-
const stableId = String(event.messageId || event.previewHandle || '').trim();
|
|
1088
|
-
if (stableId) {
|
|
1089
|
-
return `${prefix}:${stableId}`;
|
|
1090
|
-
}
|
|
1091
|
-
const content = String(event.content || '').trim().replace(/\s+/g, ' ');
|
|
1092
|
-
return `${prefix}:${content.slice(0, 120)}`;
|
|
1093
|
-
}
|
|
1094
|
-
getRenderedMessageId(turn, rendered) {
|
|
1095
|
-
if (rendered.isFinal) {
|
|
1096
|
-
return turn.finalMessageId || turn.messageId;
|
|
1097
|
-
}
|
|
1098
|
-
return turn.progressMessageIds[rendered.key];
|
|
1099
|
-
}
|
|
1100
|
-
setRenderedMessageId(turn, rendered, messageId) {
|
|
1101
|
-
if (rendered.isFinal) {
|
|
1102
|
-
turn.finalMessageId = messageId;
|
|
1103
|
-
turn.messageId = messageId;
|
|
1104
|
-
return;
|
|
1105
|
-
}
|
|
1106
|
-
turn.progressMessageIds[rendered.key] = messageId;
|
|
1107
|
-
}
|
|
1108
|
-
renderPermissionCard(turn, event, cardActionsEnabled) {
|
|
1109
|
-
const summary = this.buildPermissionSummary(turn, String(event.content || '').trim());
|
|
1110
|
-
const sections = [
|
|
1111
|
-
'**需要工具确认**',
|
|
1112
|
-
summary.command ? `\`${summary.command}\`` : '',
|
|
1113
|
-
summary.reason || '',
|
|
1114
|
-
cardActionsEnabled
|
|
1115
|
-
? '也可以直接回复:`allow` / `allow all` / `deny`'
|
|
1116
|
-
: '请直接回复:`allow` / `allow all` / `deny`',
|
|
1117
|
-
].filter(Boolean);
|
|
1118
|
-
const buttonRows = cardActionsEnabled && Array.isArray(event.buttonRows)
|
|
1119
|
-
? event.buttonRows
|
|
1120
|
-
.map((row) => Array.isArray(row)
|
|
1121
|
-
? row
|
|
1122
|
-
.filter((button) => Boolean(button?.text && button?.data))
|
|
1123
|
-
.map((button) => ({
|
|
1124
|
-
text: this.formatPermissionButtonLabel(button),
|
|
1125
|
-
data: (0, desktop_js_1.normalizePermissionResponse)(button.data) || button.data,
|
|
1126
|
-
}))
|
|
1127
|
-
: [])
|
|
1128
|
-
.filter((row) => row.length > 0)
|
|
1129
|
-
: [];
|
|
1130
|
-
return {
|
|
1131
|
-
text: sections.join('\n\n').trim(),
|
|
1132
|
-
buttonRows,
|
|
1133
|
-
};
|
|
1134
|
-
}
|
|
1135
|
-
renderPendingPairingCard(code) {
|
|
1136
|
-
const lines = [
|
|
1137
|
-
'**已收到消息**',
|
|
1138
|
-
'当前账号还未授权接入这个工作区。',
|
|
1139
|
-
'请在桌面端完成审批后再次发送消息。',
|
|
1140
|
-
];
|
|
1141
|
-
if (code) {
|
|
1142
|
-
lines.push(`配对码:\`${code}\``);
|
|
1143
|
-
}
|
|
1144
|
-
return lines.join('\n\n');
|
|
1157
|
+
(0, runtime_state_js_1.consumeLarkBridgeEvent)(turn, event, {
|
|
1158
|
+
mirrorPermissionStateInMainCard: this.mirrorPermissionStateInMainCard,
|
|
1159
|
+
});
|
|
1145
1160
|
}
|
|
1146
|
-
async handleCardActionEvent(workspaceId,
|
|
1161
|
+
async handleCardActionEvent(workspaceId, instanceIdOrData, maybeData) {
|
|
1162
|
+
const legacyCall = typeof instanceIdOrData === 'object';
|
|
1163
|
+
const instanceId = legacyCall ? 'default' : instanceIdOrData;
|
|
1164
|
+
const data = (legacyCall ? instanceIdOrData : maybeData) || {};
|
|
1147
1165
|
try {
|
|
1148
|
-
const
|
|
1149
|
-
|
|
1150
|
-
: data;
|
|
1151
|
-
const value = payload?.action?.value;
|
|
1152
|
-
if (!value || value.action !== 'permission_response') {
|
|
1153
|
-
return;
|
|
1154
|
-
}
|
|
1155
|
-
const response = (0, desktop_js_1.normalizePermissionResponse)(String(value.response || '').trim()) || String(value.response || '').trim();
|
|
1156
|
-
const threadId = String(value.thread_id || '').trim();
|
|
1157
|
-
if (!response || !threadId) {
|
|
1166
|
+
const action = (0, cards_js_1.extractCardActionValue)(data);
|
|
1167
|
+
if (!action) {
|
|
1158
1168
|
return;
|
|
1159
1169
|
}
|
|
1160
1170
|
const router = this.options.getWorkspaceRouter();
|
|
1161
|
-
await
|
|
1162
|
-
|
|
1171
|
+
await this.markPermissionCardActionHandled(workspaceId, instanceId, data, action.event, action.value, action.response, action.threadId);
|
|
1172
|
+
await router.sendThreadAction(action.threadId, action.response);
|
|
1173
|
+
this.options.log?.(`localcore-lark processed card action for ${workspaceId}/${instanceId}: ${action.response}`);
|
|
1163
1174
|
}
|
|
1164
1175
|
catch (error) {
|
|
1165
|
-
this.options.log?.(`localcore-lark card action failed for ${workspaceId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1176
|
+
this.options.log?.(`localcore-lark card action failed for ${workspaceId}/${instanceId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1166
1177
|
}
|
|
1167
1178
|
}
|
|
1168
|
-
|
|
1169
|
-
const
|
|
1170
|
-
if (!
|
|
1179
|
+
async markPermissionCardActionHandled(workspaceId, instanceId, rootPayload, payload, value, response, threadId) {
|
|
1180
|
+
const state = this.resolveRuntimeState(workspaceId, instanceId).state;
|
|
1181
|
+
if (!state?.client) {
|
|
1171
1182
|
return;
|
|
1172
1183
|
}
|
|
1173
|
-
|
|
1184
|
+
const sessionKey = String(value.session_key || '').trim();
|
|
1185
|
+
const turn = sessionKey ? this.outboundTurns.get(sessionKey) : undefined;
|
|
1186
|
+
const messageId = (0, cards_js_1.extractCardActionMessageId)(rootPayload, payload, value) || turn?.permissionMessageId;
|
|
1187
|
+
if (!messageId) {
|
|
1188
|
+
this.options.log?.(`localcore-lark could not find permission card message id for workspace=${workspaceId} sessionKey=${sessionKey || 'missing'}`);
|
|
1174
1189
|
return;
|
|
1175
1190
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
case 'allow all':
|
|
1187
|
-
return '始终允许';
|
|
1188
|
-
case 'deny':
|
|
1189
|
-
return '拒绝';
|
|
1190
|
-
default:
|
|
1191
|
-
return button.text;
|
|
1192
|
-
}
|
|
1193
|
-
}
|
|
1194
|
-
resolveLarkButtonType(button, index) {
|
|
1195
|
-
const response = (0, desktop_js_1.normalizePermissionResponse)(button.data) || (0, desktop_js_1.normalizePermissionResponse)(button.text);
|
|
1196
|
-
if (response === 'deny') {
|
|
1197
|
-
return 'danger';
|
|
1191
|
+
try {
|
|
1192
|
+
await this.patchTextCard(state, messageId, [
|
|
1193
|
+
'**工具确认已处理**',
|
|
1194
|
+
`已选择:${(0, cards_js_1.formatPermissionResponseLabel)(response)}`,
|
|
1195
|
+
'等待代理继续执行...',
|
|
1196
|
+
].join('\n\n'), [], sessionKey || undefined, threadId);
|
|
1197
|
+
if (turn) {
|
|
1198
|
+
turn.awaitingPermission = false;
|
|
1199
|
+
turn.buttonRows = [];
|
|
1200
|
+
}
|
|
1198
1201
|
}
|
|
1199
|
-
|
|
1200
|
-
|
|
1202
|
+
catch (error) {
|
|
1203
|
+
this.options.log?.(`localcore-lark failed to patch handled permission card ${messageId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1201
1204
|
}
|
|
1202
|
-
return index === 0 ? 'primary' : 'default';
|
|
1203
|
-
}
|
|
1204
|
-
buildPermissionSummary(turn, rawContent) {
|
|
1205
|
-
const lastTool = turn.toolCalls[turn.toolCalls.length - 1] || '';
|
|
1206
|
-
const [commandPart = '', reasonPart = ''] = lastTool.split(/\s+-\s+/, 2);
|
|
1207
|
-
const compactContent = rawContent
|
|
1208
|
-
.replace(/\s+/g, ' ')
|
|
1209
|
-
.replace(/请选择一个选项继续执行。?/g, '')
|
|
1210
|
-
.replace(/若按钮没有显示,请直接回复:?\s*allow all \/ allow \/ deny/gi, '')
|
|
1211
|
-
.replace(/等待工具确认/gi, '')
|
|
1212
|
-
.trim();
|
|
1213
|
-
return {
|
|
1214
|
-
command: commandPart.trim(),
|
|
1215
|
-
reason: reasonPart.trim() || compactContent,
|
|
1216
|
-
};
|
|
1217
|
-
}
|
|
1218
|
-
formatCompactToolLines(toolCalls) {
|
|
1219
|
-
return toolCalls.slice(-2).map((line) => {
|
|
1220
|
-
const compact = line.replace(/\s+/g, ' ').trim();
|
|
1221
|
-
return compact.startsWith('Terminal') ? '• Terminal' : `• ${compact}`;
|
|
1222
|
-
});
|
|
1223
1205
|
}
|
|
1224
|
-
async sendImmediateCard(workspaceId, chatId, text) {
|
|
1225
|
-
const state = this.
|
|
1206
|
+
async sendImmediateCard(workspaceId, chatId, text, instanceId) {
|
|
1207
|
+
const state = this.resolveRuntimeState(workspaceId, instanceId).state;
|
|
1226
1208
|
if (!state?.client || !state.connected) {
|
|
1227
1209
|
this.options.log?.(`localcore-lark immediate card skipped because workspace is not connected: ${workspaceId}`);
|
|
1228
1210
|
return '';
|
|
@@ -1235,8 +1217,8 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
|
|
|
1235
1217
|
return '';
|
|
1236
1218
|
}
|
|
1237
1219
|
}
|
|
1238
|
-
async addAcknowledgementReaction(workspaceId, messageId, turn) {
|
|
1239
|
-
const state = this.
|
|
1220
|
+
async addAcknowledgementReaction(workspaceId, messageId, turn, instanceId) {
|
|
1221
|
+
const state = this.resolveRuntimeState(workspaceId, instanceId).state;
|
|
1240
1222
|
if (!state?.client || !state.connected || !messageId) {
|
|
1241
1223
|
return;
|
|
1242
1224
|
}
|