@kafca/agentdock 0.1.21 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +9 -0
  2. package/dist/renderer/assets/{Badge-BRmQIqbd.js → Badge-C2K5apfV.js} +1 -1
  3. package/dist/renderer/assets/{Chat-D94ODABB.js → Chat-DI2YeVKb.js} +1 -1
  4. package/dist/renderer/assets/{ChatMarkdown-DqW7OUvV.js → ChatMarkdown-DMAr15Oi.js} +1 -1
  5. package/dist/renderer/assets/{Config-9lgzpTCp.js → Config-7rP3EuUf.js} +2 -2
  6. package/dist/renderer/assets/CronList-81QslYE8.js +1 -0
  7. package/dist/renderer/assets/{Dashboard-COISXbQy.js → Dashboard-Db2T7rSZ.js} +2 -2
  8. package/dist/renderer/assets/{EmptyState-w5-I9zg7.js → EmptyState-BBJ3cTvB.js} +1 -1
  9. package/dist/renderer/assets/{KnowledgeDetail-DVxwrTLw.js → KnowledgeDetail-Dz_6flG3.js} +2 -2
  10. package/dist/renderer/assets/{KnowledgeHome-BKkubcwi.js → KnowledgeHome-DIh046mN.js} +3 -3
  11. package/dist/renderer/assets/Logs-BXBtHJLf.js +1 -0
  12. package/dist/renderer/assets/{Modal-Ceu_MP2e.js → Modal-BB2Av_Xs.js} +1 -1
  13. package/dist/renderer/assets/{Page-CpSczAz8.js → Page-DPSdvMgU.js} +1 -1
  14. package/dist/renderer/assets/{ProjectDetail-FJ_bNXLF.js → ProjectDetail-C6EWGvGg.js} +1 -1
  15. package/dist/renderer/assets/{ProjectList-DZKL4K9C.js → ProjectList-BBo5ygnS.js} +1 -1
  16. package/dist/renderer/assets/{Select-BHQk6qCU.js → Select-CNX-Tx_O.js} +2 -7
  17. package/dist/renderer/assets/SessionChat-BIn46jqZ.js +1 -0
  18. package/dist/renderer/assets/SessionList-CZt6f1Rd.js +1 -0
  19. package/dist/renderer/assets/{ThreadChat-D3s4GXGh.js → ThreadChat-BotD0gKQ.js} +7 -7
  20. package/dist/renderer/assets/Workspace-C_4tdpjX.js +14 -0
  21. package/dist/renderer/assets/{arrow-left-Dtal-eOi.js → arrow-left-cHr5wkpq.js} +1 -1
  22. package/dist/renderer/assets/{book-open-q814R_Ov.js → book-open-4nx5C8T-.js} +1 -1
  23. package/dist/renderer/assets/{check-DjfCdLvw.js → check-exTS-R-x.js} +1 -1
  24. package/dist/renderer/assets/chevron-down-DSvxXQa0.js +6 -0
  25. package/dist/renderer/assets/{circle-vfhmyE8v.js → circle-DyvEovdw.js} +1 -1
  26. package/dist/renderer/assets/desktop-B-KVT10D.js +1 -0
  27. package/dist/renderer/assets/desktop-CVibRyAw.js +4 -0
  28. package/dist/renderer/assets/{funnel-CUQibo1I.js → funnel-DA9qVdTy.js} +1 -1
  29. package/dist/renderer/assets/{heart-DG9_b8P1.js → heart-DuEV0ZRJ.js} +1 -1
  30. package/dist/renderer/assets/{index-Ds0IN5rP.js → index-BgflqkCN.js} +3 -3
  31. package/dist/renderer/assets/index-Biz-aexG.js +177 -0
  32. package/dist/renderer/assets/{index-3CzgyQYB.css → index-DFiJR0vv.css} +1 -1
  33. package/dist/renderer/assets/{pencil-Dij0LlRY.js → pencil-DBDxeNmO.js} +1 -1
  34. package/dist/renderer/assets/{play-CPFGjIyJ.js → play-IzlP83_7.js} +1 -1
  35. package/dist/renderer/assets/{plug-DH3RKQyn.js → plug-CuT-qFnW.js} +1 -1
  36. package/dist/renderer/assets/{plus-afkK9_Y8.js → plus-DEqgkn63.js} +1 -1
  37. package/dist/renderer/assets/projects-DDtDN1rc.js +1 -0
  38. package/dist/renderer/assets/{save-B-cKgWcy.js → save-D4sRo07y.js} +1 -1
  39. package/dist/renderer/assets/{search-DR6PeYkt.js → search-DbPZLuV8.js} +1 -1
  40. package/dist/renderer/assets/{server-BM6KOB5a.js → server-D1jUQmwN.js} +1 -1
  41. package/dist/renderer/assets/{session-utils-BDuThM0a.js → session-utils-Aife8ep2.js} +1 -1
  42. package/dist/renderer/assets/{sessions-C1yKps4T.js → sessions-DaH3H7gC.js} +1 -1
  43. package/dist/renderer/assets/{shield-check-BFjMG-r2.js → shield-check-BGwUFWNr.js} +1 -1
  44. package/dist/renderer/assets/{trash-2-CW1heMCf.js → trash-2-CIFBioBa.js} +1 -1
  45. package/dist/renderer/assets/{user-r_2sHG7m.js → user-CnKcvHdo.js} +1 -1
  46. package/dist/renderer/assets/{wifi-off-VMUokMpE.js → wifi-off-ClO53WGa.js} +1 -1
  47. package/dist/renderer/index.html +2 -2
  48. package/dist-electron/electron/local-core-refactor.test.js +812 -3
  49. package/dist-electron/electron/plugin-kernel.test.js +74 -14
  50. package/dist-electron/packages/core-sdk/src/index.js +40 -20
  51. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +5 -1
  52. package/dist-electron/services/local-ai-core/src/acp/workspace-acp-permissions.js +55 -3
  53. package/dist-electron/services/local-ai-core/src/channel/lark/cards.js +210 -0
  54. package/dist-electron/services/local-ai-core/src/channel/lark/config.js +54 -0
  55. package/dist-electron/services/local-ai-core/src/{gateway → channel/lark}/local-core-lark-gateway.js +365 -383
  56. package/dist-electron/services/local-ai-core/src/channel/lark/registration.js +50 -0
  57. package/dist-electron/services/local-ai-core/src/channel/lark/runtime-state.js +191 -0
  58. package/dist-electron/services/local-ai-core/src/channel/lark/types.js +2 -0
  59. package/dist-electron/services/local-ai-core/src/channel/shared/file-utils.js +33 -0
  60. package/dist-electron/services/local-ai-core/src/channel/shared/plugin.js +31 -0
  61. package/dist-electron/services/local-ai-core/src/channel/weixin/config.js +125 -0
  62. package/dist-electron/services/local-ai-core/src/{gateway → channel/weixin}/local-core-weixin-gateway.js +262 -258
  63. package/dist-electron/services/local-ai-core/src/channel/weixin/transport.js +169 -0
  64. package/dist-electron/services/local-ai-core/src/channel/weixin/types.js +2 -0
  65. package/dist-electron/services/local-ai-core/src/cli/lac.js +41 -1
  66. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +5 -0
  67. package/dist-electron/services/local-ai-core/src/plugins/builtin/channel-lark-plugin.js +19 -40
  68. package/dist-electron/services/local-ai-core/src/plugins/builtin/channel-weixin-plugin.js +19 -40
  69. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +96 -44
  70. package/dist-electron/services/local-ai-core/src/runtime/server.js +20 -8
  71. package/dist-electron/shared/desktop.js +24 -1
  72. package/package.json +1 -1
  73. package/dist/renderer/assets/CronList-BYZ-x_bp.js +0 -1
  74. package/dist/renderer/assets/Logs-D7HxZ8Nb.js +0 -1
  75. package/dist/renderer/assets/SessionChat-DpgSXF3m.js +0 -1
  76. package/dist/renderer/assets/SessionList-CU6drQ4N.js +0 -1
  77. package/dist/renderer/assets/Workspace-B7looTVo.js +0 -14
  78. package/dist/renderer/assets/desktop-BPIxWbkM.js +0 -1
  79. package/dist/renderer/assets/desktop-DZHC4PDB.js +0 -3
  80. package/dist/renderer/assets/index-Bn8ane6_.js +0 -177
  81. package/dist/renderer/assets/projects-DTDS9SnT.js +0 -1
@@ -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 desktop_js_1 = require("../../../../shared/desktop.js");
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 nextWorkspaceIds = new Set(bindings.map((binding) => binding.workspaceId));
64
- for (const workspaceId of [...this.runtime.keys()]) {
65
- if (!nextWorkspaceIds.has(workspaceId)) {
66
- await this.stopWorkspace(workspaceId);
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 current = this.runtime.get(binding.workspaceId);
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.stopWorkspace(binding.workspaceId);
82
+ await this.stopWorkspaceKey(key);
74
83
  }
75
84
  else {
76
- this.runtime.set(binding.workspaceId, {
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 binding = this.runtime.get(workspaceId);
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 === 'lark' && row.expires_at >= new Date().toISOString());
135
- const users = this.options.store.listAuthorizedUsers(workspaceId, 'lark');
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.keys()].sort().map((workspaceId) => this.getStatus(workspaceId));
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, 'lark')
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, 'lark');
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, 'lark').find((entry) => entry.id === userId);
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 state = this.runtime.get(route.workspaceId);
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 = this.renderPermissionCard(turn, event, Boolean(state.cardActionsEnabled));
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 = this.renderBridgeEventMessage(turn, event);
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 = this.getRenderedMessageId(turn, rendered);
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
- this.setRenderedMessageId(turn, rendered, createdId);
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 binding = await this.getBinding(input.workspaceId);
351
- let authorized = this.options.store.getAuthorizedUser(input.workspaceId, input.platformUserId);
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.autoApprove) {
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 === 'lark' && item.platform_user_id === input.platformUserId && item.chat_id === input.chatId && item.status === 'pending');
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, this.renderPendingPairingCard(pairingCode));
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((workspaceId) => this.stopWorkspace(workspaceId))).then(() => undefined);
657
+ return Promise.all([...this.runtime.keys()].map((key) => this.stopWorkspaceKey(key))).then(() => undefined);
462
658
  }
463
- async getBinding(workspaceId) {
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 binding = this.collectBindings(config).find((entry) => entry.workspaceId === workspaceId);
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
- await this.stopWorkspace(binding.workspaceId);
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(binding.workspaceId, status);
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 state = this.runtime.get(workspaceId);
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(workspaceId, {
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, data) {
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.runtime.get(workspaceId);
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
- const projects = Array.isArray(config?.projects) ? config.projects : [];
816
- return projects.flatMap((project) => {
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: receiveIdType,
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(this.buildInteractiveCard(text, buttonRows, sessionKey, threadId)),
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(this.buildInteractiveCard(text, buttonRows, sessionKey, threadId)),
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
- const content = String(event.content || '').trim();
975
- if (event.type === 'typing_start') {
976
- // Always start a fresh card for each generation phase.
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, data) {
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 payload = (data?.event && typeof data.event === 'object')
1149
- ? data.event
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 router.sendThreadAction(threadId, response);
1162
- this.options.log?.(`localcore-lark processed card action for ${workspaceId}: ${response}`);
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
- pushUnique(target, value) {
1169
- const normalized = value.trim();
1170
- if (!normalized) {
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
- if (target[target.length - 1] === normalized) {
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
- target.push(normalized);
1177
- if (target.length > 6) {
1178
- target.splice(0, target.length - 6);
1179
- }
1180
- }
1181
- formatPermissionButtonLabel(button) {
1182
- const response = (0, desktop_js_1.normalizePermissionResponse)(button.data) || (0, desktop_js_1.normalizePermissionResponse)(button.text);
1183
- switch (response) {
1184
- case 'allow':
1185
- return '允许一次';
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
- if (response === 'allow') {
1200
- return 'primary';
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.runtime.get(workspaceId);
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.runtime.get(workspaceId);
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
  }