@kafca/agentdock 0.1.22 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +3 -0
  2. package/dist/renderer/assets/{Badge-B_rgzxYO.js → Badge-C2K5apfV.js} +1 -1
  3. package/dist/renderer/assets/{Chat-CYtp6m7T.js → Chat-DI2YeVKb.js} +1 -1
  4. package/dist/renderer/assets/{ChatMarkdown-DOBMwa9d.js → ChatMarkdown-DMAr15Oi.js} +1 -1
  5. package/dist/renderer/assets/{Config-DppMSPHF.js → Config-7rP3EuUf.js} +1 -1
  6. package/dist/renderer/assets/{CronList-BDtB1SuA.js → CronList-81QslYE8.js} +1 -1
  7. package/dist/renderer/assets/{Dashboard-iFlRnd7Y.js → Dashboard-Db2T7rSZ.js} +2 -2
  8. package/dist/renderer/assets/{EmptyState-bKDt_T88.js → EmptyState-BBJ3cTvB.js} +1 -1
  9. package/dist/renderer/assets/{KnowledgeDetail-CKPhKPIg.js → KnowledgeDetail-Dz_6flG3.js} +2 -2
  10. package/dist/renderer/assets/{KnowledgeHome-CFeJre6i.js → KnowledgeHome-DIh046mN.js} +3 -3
  11. package/dist/renderer/assets/{Logs-CyxSVOFK.js → Logs-BXBtHJLf.js} +1 -1
  12. package/dist/renderer/assets/{Modal-CxPnk1Ey.js → Modal-BB2Av_Xs.js} +1 -1
  13. package/dist/renderer/assets/{Page-kU9vubzj.js → Page-DPSdvMgU.js} +1 -1
  14. package/dist/renderer/assets/{ProjectDetail-D9FY9NWZ.js → ProjectDetail-C6EWGvGg.js} +1 -1
  15. package/dist/renderer/assets/{ProjectList-DIwr2TXg.js → ProjectList-BBo5ygnS.js} +1 -1
  16. package/dist/renderer/assets/{Select-BaWoz5Dn.js → Select-CNX-Tx_O.js} +1 -1
  17. package/dist/renderer/assets/{SessionChat-Cm9CW8Zx.js → SessionChat-BIn46jqZ.js} +1 -1
  18. package/dist/renderer/assets/{SessionList-UPnWlXXO.js → SessionList-CZt6f1Rd.js} +1 -1
  19. package/dist/renderer/assets/{ThreadChat-YvVsACW3.js → ThreadChat-BotD0gKQ.js} +1 -1
  20. package/dist/renderer/assets/Workspace-C_4tdpjX.js +14 -0
  21. package/dist/renderer/assets/{arrow-left-BmZAVGV7.js → arrow-left-cHr5wkpq.js} +1 -1
  22. package/dist/renderer/assets/{book-open-DZ3GXFgI.js → book-open-4nx5C8T-.js} +1 -1
  23. package/dist/renderer/assets/{check-CeVSvDCv.js → check-exTS-R-x.js} +1 -1
  24. package/dist/renderer/assets/{chevron-down-yryuMoQ-.js → chevron-down-DSvxXQa0.js} +1 -1
  25. package/dist/renderer/assets/{circle-eASsFISH.js → circle-DyvEovdw.js} +1 -1
  26. package/dist/renderer/assets/desktop-B-KVT10D.js +1 -0
  27. package/dist/renderer/assets/{desktop-Che1cbpE.js → desktop-CVibRyAw.js} +3 -3
  28. package/dist/renderer/assets/{funnel-pwAe2Ixp.js → funnel-DA9qVdTy.js} +1 -1
  29. package/dist/renderer/assets/{heart-B2Sp-bCm.js → heart-DuEV0ZRJ.js} +1 -1
  30. package/dist/renderer/assets/{index-Cse2Br64.js → index-BgflqkCN.js} +3 -3
  31. package/dist/renderer/assets/{index-BC3MWbS1.js → index-Biz-aexG.js} +39 -39
  32. package/dist/renderer/assets/{pencil-BfEunUF3.js → pencil-DBDxeNmO.js} +1 -1
  33. package/dist/renderer/assets/{play-Dcs9Lj_Z.js → play-IzlP83_7.js} +1 -1
  34. package/dist/renderer/assets/{plug-B_YschQe.js → plug-CuT-qFnW.js} +1 -1
  35. package/dist/renderer/assets/{plus-Pqo-cXHQ.js → plus-DEqgkn63.js} +1 -1
  36. package/dist/renderer/assets/projects-DDtDN1rc.js +1 -0
  37. package/dist/renderer/assets/{save-BKM5yi1A.js → save-D4sRo07y.js} +1 -1
  38. package/dist/renderer/assets/{search-BU42h10M.js → search-DbPZLuV8.js} +1 -1
  39. package/dist/renderer/assets/{server-DA5t3Rtr.js → server-D1jUQmwN.js} +1 -1
  40. package/dist/renderer/assets/{session-utils-Cr9VhNSq.js → session-utils-Aife8ep2.js} +1 -1
  41. package/dist/renderer/assets/{sessions-bvV8ZMfX.js → sessions-DaH3H7gC.js} +1 -1
  42. package/dist/renderer/assets/{shield-check-BeOJKur_.js → shield-check-BGwUFWNr.js} +1 -1
  43. package/dist/renderer/assets/{trash-2-CQ7HKO3q.js → trash-2-CIFBioBa.js} +1 -1
  44. package/dist/renderer/assets/{user-CtFjY9SV.js → user-CnKcvHdo.js} +1 -1
  45. package/dist/renderer/assets/{wifi-off-Ci5m0ysf.js → wifi-off-ClO53WGa.js} +1 -1
  46. package/dist/renderer/index.html +1 -1
  47. package/dist-electron/electron/local-core-refactor.test.js +505 -0
  48. package/dist-electron/electron/plugin-kernel.test.js +74 -14
  49. package/dist-electron/packages/core-sdk/src/index.js +40 -20
  50. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +5 -1
  51. package/dist-electron/services/local-ai-core/src/acp/workspace-acp-permissions.js +55 -3
  52. package/dist-electron/services/local-ai-core/src/channel/lark/cards.js +210 -0
  53. package/dist-electron/services/local-ai-core/src/channel/lark/config.js +54 -0
  54. package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +212 -471
  55. package/dist-electron/services/local-ai-core/src/channel/lark/registration.js +50 -0
  56. package/dist-electron/services/local-ai-core/src/channel/lark/runtime-state.js +191 -0
  57. package/dist-electron/services/local-ai-core/src/channel/lark/types.js +2 -0
  58. package/dist-electron/services/local-ai-core/src/channel/shared/plugin.js +31 -0
  59. package/dist-electron/services/local-ai-core/src/channel/weixin/config.js +125 -0
  60. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +141 -324
  61. package/dist-electron/services/local-ai-core/src/channel/weixin/transport.js +169 -0
  62. package/dist-electron/services/local-ai-core/src/channel/weixin/types.js +2 -0
  63. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +5 -0
  64. package/dist-electron/services/local-ai-core/src/plugins/builtin/channel-lark-plugin.js +18 -39
  65. package/dist-electron/services/local-ai-core/src/plugins/builtin/channel-weixin-plugin.js +18 -39
  66. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +55 -44
  67. package/dist-electron/services/local-ai-core/src/runtime/server.js +10 -8
  68. package/dist-electron/shared/desktop.js +10 -0
  69. package/package.json +1 -1
  70. package/dist/renderer/assets/Workspace-ST7V8u6T.js +0 -14
  71. package/dist/renderer/assets/desktop-DkrP_s-b.js +0 -1
  72. package/dist/renderer/assets/projects-BCj0k_Gy.js +0 -1
@@ -40,15 +40,10 @@ const node_fs_1 = require("node:fs");
40
40
  const node_path_1 = require("node:path");
41
41
  const desktop_js_1 = require("../../../../../shared/desktop.js");
42
42
  const file_utils_js_1 = require("../shared/file-utils.js");
43
- function foldToolResultForLark(content) {
44
- const normalized = String(content || '').trim();
45
- if (!normalized.startsWith('🔧 '))
46
- return normalized;
47
- const parts = normalized.split(' - ');
48
- if (parts.length <= 2)
49
- return normalized;
50
- return parts.slice(0, 2).join(' - ');
51
- }
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");
52
47
  const PAIRING_EXPIRY_MS = 10 * 60 * 1000;
53
48
  const LARK_MAX_UPLOAD_FILE_SIZE = 30 * 1024 * 1024;
54
49
  class LocalCoreLarkGateway extends node_events_1.EventEmitter {
@@ -73,25 +68,30 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
73
68
  async refreshBindings() {
74
69
  const config = await this.options.readConfig();
75
70
  const bindings = this.collectBindings(config);
76
- const nextWorkspaceIds = new Set(bindings.map((binding) => binding.workspaceId));
77
- for (const workspaceId of [...this.runtime.keys()]) {
78
- if (!nextWorkspaceIds.has(workspaceId)) {
79
- 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);
80
75
  }
81
76
  }
82
77
  for (const binding of bindings) {
83
- 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);
84
80
  if (!binding.enabled) {
85
81
  if (current) {
86
- await this.stopWorkspace(binding.workspaceId);
82
+ await this.stopWorkspaceKey(key);
87
83
  }
88
84
  else {
89
- this.runtime.set(binding.workspaceId, {
85
+ this.runtime.set(key, {
90
86
  workspaceId: binding.workspaceId,
87
+ instanceId: binding.instanceId,
88
+ displayName: binding.displayName,
89
+ platformKey: binding.platformKey,
91
90
  enabled: false,
92
91
  status: 'disabled',
93
92
  connected: false,
94
93
  appId: binding.appId,
94
+ autoApprove: binding.autoApprove,
95
95
  cardActionsEnabled: binding.cardActionsEnabled,
96
96
  });
97
97
  }
@@ -104,23 +104,70 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
104
104
  }
105
105
  this.notifyRuntimeStateChanged();
106
106
  }
107
- async testConnection(workspaceId) {
108
- const binding = await this.getBinding(workspaceId);
107
+ async testConnection(workspaceId, instanceId) {
108
+ const binding = await this.getBinding(workspaceId, instanceId);
109
109
  const result = await this.createSdkClientResult(binding);
110
110
  return {
111
111
  ...result,
112
112
  platform: 'lark',
113
113
  workspaceId,
114
+ instanceId: binding.instanceId,
114
115
  };
115
116
  }
116
- async enable(workspaceId) {
117
- const binding = await this.getBinding(workspaceId);
117
+ async enable(workspaceId, instanceId) {
118
+ const binding = await this.getBinding(workspaceId, instanceId);
118
119
  await this.startWorkspace(binding);
119
- return this.getStatus(workspaceId);
120
+ return this.getStatus(workspaceId, binding.instanceId);
121
+ }
122
+ async disable(workspaceId, instanceId) {
123
+ await this.stopWorkspace(workspaceId, instanceId);
124
+ return this.getStatus(workspaceId, instanceId);
120
125
  }
121
- async disable(workspaceId) {
122
- await this.stopWorkspace(workspaceId);
123
- return this.getStatus(workspaceId);
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
+ };
124
171
  }
125
172
  async sendScheduledCard(workspaceId, chatId, text) {
126
173
  return this.sendImmediateCard(workspaceId, chatId, text);
@@ -129,7 +176,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
129
176
  return this.sendScheduledCard(workspaceId, route.channelId, text);
130
177
  }
131
178
  async sendOutboundMessage(workspaceId, input) {
132
- const state = this.runtime.get(workspaceId);
179
+ const state = this.resolveRuntimeState(workspaceId, input.route?.instanceId).state;
133
180
  if (!state?.client || !state.connected) {
134
181
  throw new Error(`Lark workspace is not connected: ${workspaceId}`);
135
182
  }
@@ -245,15 +292,19 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
245
292
  }
246
293
  this.mutedThreadBridgeCounts.set(threadId, current - 1);
247
294
  }
248
- getStatus(workspaceId) {
295
+ getStatus(workspaceId, instanceId) {
249
296
  this.options.store.expirePendingPairings();
250
- 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);
251
300
  const pairings = this.options.store.listPendingPairings(workspaceId)
252
- .filter((row) => row.platform === 'lark' && row.expires_at >= new Date().toISOString());
253
- 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);
254
303
  return {
255
304
  workspaceId,
256
305
  platform: 'lark',
306
+ instanceId: resolved.instanceId,
307
+ displayName: binding?.displayName,
257
308
  enabled: Boolean(binding?.enabled),
258
309
  connected: Boolean(binding?.connected),
259
310
  status: binding?.status || 'disabled',
@@ -265,16 +316,20 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
265
316
  };
266
317
  }
267
318
  listStatuses() {
268
- 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));
269
322
  }
270
323
  listPendingPairings(workspaceId) {
271
324
  this.options.store.expirePendingPairings();
272
325
  return this.options.store
273
- .listPairingRequests(workspaceId, 'lark')
326
+ .listPairingRequests(workspaceId)
327
+ .filter((item) => item.platform === 'lark' || item.platform.startsWith('lark:'))
274
328
  .filter((item) => item.status === 'pending' && item.expiresAt >= new Date().toISOString());
275
329
  }
276
330
  listAuthorizedUsers(workspaceId) {
277
- return this.options.store.listAuthorizedUsers(workspaceId, 'lark');
331
+ return this.options.store.listAuthorizedUsers(workspaceId)
332
+ .filter((item) => item.platform === 'lark' || item.platform.startsWith('lark:'));
278
333
  }
279
334
  async start() {
280
335
  await this.refreshBindings();
@@ -288,7 +343,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
288
343
  if (!pairing) {
289
344
  throw new Error(`Pairing code not found: ${code}`);
290
345
  }
291
- if (pairing.platform !== 'lark') {
346
+ if (pairing.platform !== 'lark' && !pairing.platform.startsWith('lark:')) {
292
347
  throw new Error(`Pairing code ${code} is not a Lark pairing`);
293
348
  }
294
349
  if (pairing.status !== 'pending') {
@@ -298,12 +353,13 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
298
353
  this.options.store.updatePairingStatus(code, 'expired');
299
354
  throw new Error(`Pairing code ${code} has expired`);
300
355
  }
301
- 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);
302
357
  const userId = existing?.id || `lark-user-${(0, node_crypto_1.randomUUID)()}`;
303
358
  const authorizedAt = new Date().toISOString();
304
359
  this.options.store.createAuthorizedUser({
305
360
  id: userId,
306
361
  workspace_id: pairing.workspace_id,
362
+ platform: pairing.platform,
307
363
  platform_user_id: pairing.platform_user_id,
308
364
  chat_id: pairing.chat_id,
309
365
  display_name: pairing.display_name,
@@ -312,7 +368,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
312
368
  });
313
369
  this.options.store.updatePairingStatus(code, 'approved');
314
370
  this.notifyRuntimeStateChanged();
315
- 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);
316
372
  if (!user) {
317
373
  throw new Error('Authorized user lookup failed after approval');
318
374
  }
@@ -338,12 +394,14 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
338
394
  this.options.log?.(`localcore-lark bridge route miss for sessionKey=${sessionKey} type=${event.type}`);
339
395
  return;
340
396
  }
341
- 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);
342
400
  if (!state?.client || !state.connected) {
343
401
  this.options.log?.(`localcore-lark bridge event ignored because workspace is not connected: ${route.workspaceId}`);
344
402
  return;
345
403
  }
346
- 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);
347
405
  if (!initialBinding) {
348
406
  this.options.log?.(`localcore-lark bridge binding miss for workspace=${route.workspaceId} chat=${route.chatId} user=${route.platformUserId}`);
349
407
  return;
@@ -362,7 +420,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
362
420
  const current = previous
363
421
  .catch(() => undefined)
364
422
  .then(async () => {
365
- 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);
366
424
  if (!binding) {
367
425
  this.options.log?.(`localcore-lark bridge binding disappeared for sessionKey=${sessionKey}`);
368
426
  return;
@@ -391,7 +449,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
391
449
  this.options.store.clearPlatformThreadMessageId(route.workspaceId, route.chatId, route.platformUserId);
392
450
  }
393
451
  if (event.type === 'buttons') {
394
- const permissionCard = this.renderPermissionCard(turn, event, Boolean(state.cardActionsEnabled));
452
+ const permissionCard = (0, cards_js_1.renderPermissionCard)(turn, event, Boolean(state.cardActionsEnabled));
395
453
  if (permissionCard.text || permissionCard.buttonRows.length > 0) {
396
454
  if (!turn.permissionMessageId) {
397
455
  const createdId = await this.sendTextAsCard(state, route.chatId, permissionCard.text, permissionCard.buttonRows, sessionKey, binding.thread_id);
@@ -409,12 +467,12 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
409
467
  return;
410
468
  }
411
469
  this.consumeBridgeEvent(turn, event);
412
- const rendered = this.renderBridgeEventMessage(turn, event);
470
+ const rendered = (0, runtime_state_js_1.renderLarkBridgeEventMessage)(turn, event);
413
471
  if (!rendered.text && rendered.buttonRows.length === 0) {
414
472
  this.options.log?.(`localcore-lark bridge event produced empty render for sessionKey=${sessionKey} type=${event.type}`);
415
473
  return;
416
474
  }
417
- const existingMessageId = this.getRenderedMessageId(turn, rendered);
475
+ const existingMessageId = (0, runtime_state_js_1.getLarkRenderedMessageId)(turn, rendered);
418
476
  const shouldThrottle = event.type === 'update_message' &&
419
477
  rendered.isFinal &&
420
478
  existingMessageId &&
@@ -426,9 +484,9 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
426
484
  if (!existingMessageId) {
427
485
  const createdId = await this.sendTextAsCard(state, route.chatId, rendered.text, rendered.buttonRows, sessionKey, binding.thread_id);
428
486
  if (createdId) {
429
- this.setRenderedMessageId(turn, rendered, createdId);
487
+ (0, runtime_state_js_1.setLarkRenderedMessageId)(turn, rendered, createdId);
430
488
  if (rendered.isFinal) {
431
- this.options.store.updatePlatformThreadMessageId(route.workspaceId, route.chatId, route.platformUserId, createdId);
489
+ this.options.store.updatePlatformThreadMessageId(route.workspaceId, route.chatId, route.platformUserId, createdId, routePlatformKey);
432
490
  }
433
491
  this.options.log?.(`localcore-lark sent new card message ${createdId} for sessionKey=${sessionKey}`);
434
492
  }
@@ -452,6 +510,8 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
452
510
  await current;
453
511
  }
454
512
  async handleInboundMessage(input) {
513
+ const instanceId = input.instanceId || 'default';
514
+ const platformKey = input.platformKey || (0, config_js_1.channelPlatformKey)('lark', instanceId);
455
515
  this.options.eventBus.emit({
456
516
  type: 'platform.message.received',
457
517
  payload: {
@@ -465,27 +525,38 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
465
525
  },
466
526
  });
467
527
  this.options.store.expirePendingPairings();
468
- const binding = await this.getBinding(input.workspaceId);
469
- 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);
470
540
  if (!authorized) {
471
- if (binding.autoApprove) {
541
+ if (binding?.autoApprove || runtimeState?.autoApprove) {
472
542
  const authorizedAt = new Date().toISOString();
473
543
  this.options.store.createAuthorizedUser({
474
544
  id: `lark-user-${(0, node_crypto_1.randomUUID)()}`,
475
545
  workspace_id: input.workspaceId,
546
+ platform: platformKey,
476
547
  platform_user_id: input.platformUserId,
477
548
  chat_id: input.chatId,
478
549
  display_name: input.displayName,
479
550
  thread_id: null,
480
551
  authorized_at: authorizedAt,
481
552
  });
482
- authorized = this.options.store.getAuthorizedUser(input.workspaceId, input.platformUserId);
553
+ authorized = this.options.store.getAuthorizedUser(input.workspaceId, input.platformUserId, platformKey);
483
554
  this.options.log?.(`localcore-lark auto-approved user for ${input.workspaceId}: ${input.platformUserId}`);
484
555
  this.notifyRuntimeStateChanged();
485
556
  }
486
557
  }
487
558
  if (!authorized) {
488
- 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');
489
560
  let pairingCode = existingPending?.code || '';
490
561
  if (!existingPending) {
491
562
  const now = new Date();
@@ -493,6 +564,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
493
564
  this.options.store.createPairingRequest({
494
565
  code: pairingCode,
495
566
  workspace_id: input.workspaceId,
567
+ platform: platformKey,
496
568
  platform_user_id: input.platformUserId,
497
569
  chat_id: input.chatId,
498
570
  display_name: input.displayName,
@@ -502,19 +574,20 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
502
574
  });
503
575
  this.notifyRuntimeStateChanged();
504
576
  }
505
- 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);
506
578
  return { paired: false };
507
579
  }
508
580
  const router = this.options.getWorkspaceRouter();
509
- 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);
510
582
  let threadId = threadBinding?.thread_id || authorized.thread_id || '';
511
583
  if (!threadId) {
512
584
  const thread = await router.createThread(input.workspaceId, input.displayName || `Lark ${input.chatId}`);
513
585
  threadId = thread.id;
514
- this.options.store.updateAuthorizedUserThread(input.workspaceId, input.platformUserId, threadId);
586
+ this.options.store.updateAuthorizedUserThread(input.workspaceId, input.platformUserId, threadId, platformKey);
515
587
  const now = new Date().toISOString();
516
588
  this.options.store.upsertPlatformThreadBinding({
517
589
  workspace_id: input.workspaceId,
590
+ platform: platformKey,
518
591
  chat_id: input.chatId,
519
592
  platform_user_id: input.platformUserId,
520
593
  thread_id: threadId,
@@ -534,20 +607,23 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
534
607
  const effectiveSessionKey = this.options.getWorkspaceRouter().getThreadSessionKey(threadId);
535
608
  this.threadRouting.set(effectiveSessionKey, {
536
609
  workspaceId: input.workspaceId,
610
+ instanceId,
611
+ platformKey,
537
612
  platformUserId: input.platformUserId,
538
613
  chatId: input.chatId,
539
614
  threadId,
540
615
  });
541
616
  const acknowledgement = this.createTurnState(effectiveSessionKey, input.messageId);
542
- await this.addAcknowledgementReaction(input.workspaceId, input.messageId, acknowledgement);
617
+ await this.addAcknowledgementReaction(input.workspaceId, input.messageId, acknowledgement, instanceId);
543
618
  const slashCommand = this.parseSlashCommand(input.text);
544
619
  if (slashCommand?.name === 'new') {
545
620
  const title = slashCommand.args.join(' ').trim() || `${input.displayName || 'Lark'} ${new Date().toLocaleTimeString()}`;
546
621
  const nextThread = await router.createThread(input.workspaceId, title);
547
622
  const now = new Date().toISOString();
548
- this.options.store.updateAuthorizedUserThread(input.workspaceId, input.platformUserId, nextThread.id);
623
+ this.options.store.updateAuthorizedUserThread(input.workspaceId, input.platformUserId, nextThread.id, platformKey);
549
624
  this.options.store.upsertPlatformThreadBinding({
550
625
  workspace_id: input.workspaceId,
626
+ platform: platformKey,
551
627
  chat_id: input.chatId,
552
628
  platform_user_id: input.platformUserId,
553
629
  thread_id: nextThread.id,
@@ -557,11 +633,13 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
557
633
  });
558
634
  this.threadRouting.set(this.options.getWorkspaceRouter().getThreadSessionKey(nextThread.id), {
559
635
  workspaceId: input.workspaceId,
636
+ instanceId,
637
+ platformKey,
560
638
  platformUserId: input.platformUserId,
561
639
  chatId: input.chatId,
562
640
  threadId: nextThread.id,
563
641
  });
564
- await this.sendImmediateCard(input.workspaceId, input.chatId, '**已开始新会话**');
642
+ await this.sendImmediateCard(input.workspaceId, input.chatId, '**已开始新会话**', instanceId);
565
643
  return { paired: true, threadId: nextThread.id };
566
644
  }
567
645
  const latestRun = this.options.store.getLatestRunForThread(threadId);
@@ -576,13 +654,25 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
576
654
  return { paired: true, threadId };
577
655
  }
578
656
  close() {
579
- 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);
658
+ }
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) };
580
667
  }
581
- async getBinding(workspaceId) {
668
+ async getBinding(workspaceId, instanceId) {
582
669
  const config = await this.options.readConfig();
583
- 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];
584
674
  if (!binding) {
585
- throw new Error(`No Lark binding configured for workspace "${workspaceId}"`);
675
+ throw new Error(`No Lark binding configured for workspace "${workspaceId}"${instanceId ? ` instance "${instanceId}"` : ''}`);
586
676
  }
587
677
  return binding;
588
678
  }
@@ -600,6 +690,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
600
690
  success: true,
601
691
  platform: 'lark',
602
692
  workspaceId: binding.workspaceId,
693
+ instanceId: binding.instanceId,
603
694
  appId: binding.appId,
604
695
  };
605
696
  }
@@ -608,22 +699,28 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
608
699
  success: false,
609
700
  platform: 'lark',
610
701
  workspaceId: binding.workspaceId,
702
+ instanceId: binding.instanceId,
611
703
  appId: binding.appId,
612
704
  error: error instanceof Error ? error.message : String(error),
613
705
  };
614
706
  }
615
707
  }
616
708
  async startWorkspace(binding) {
617
- await this.stopWorkspace(binding.workspaceId);
709
+ const key = (0, config_js_1.runtimeKey)(binding.workspaceId, binding.instanceId);
710
+ await this.stopWorkspaceKey(key);
618
711
  const status = {
619
712
  workspaceId: binding.workspaceId,
713
+ instanceId: binding.instanceId,
714
+ displayName: binding.displayName,
715
+ platformKey: binding.platformKey,
620
716
  enabled: true,
621
717
  status: 'starting',
622
718
  connected: false,
623
719
  appId: binding.appId,
720
+ autoApprove: binding.autoApprove,
624
721
  cardActionsEnabled: binding.cardActionsEnabled,
625
722
  };
626
- this.runtime.set(binding.workspaceId, status);
723
+ this.runtime.set(key, status);
627
724
  this.notifyRuntimeStateChanged();
628
725
  try {
629
726
  this.options.log?.(`localcore-lark starting workspace=${binding.workspaceId} app=${this.maskLarkAppId(binding.appId)} cardActions=${binding.cardActionsEnabled}`);
@@ -641,15 +738,15 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
641
738
  });
642
739
  status.eventDispatcher.register({
643
740
  'im.message.receive_v1': async (data) => {
644
- this.options.log?.(`localcore-lark received im.message.receive_v1 for ${binding.workspaceId}: ${this.summarizeLarkEvent(data)}`);
645
- void this.handleMessageEvent(binding.workspaceId, data).catch((error) => {
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) => {
646
743
  this.options.log?.(`localcore-lark inbound message failed for ${binding.workspaceId}: ${error instanceof Error ? error.message : String(error)}`);
647
744
  });
648
745
  return {};
649
746
  },
650
747
  'card.action.trigger': async (data) => {
651
- this.options.log?.(`localcore-lark received card.action.trigger for ${binding.workspaceId}`);
652
- void this.handleCardActionEvent(binding.workspaceId, data);
748
+ this.options.log?.(`localcore-lark received card.action.trigger for ${binding.workspaceId}/${binding.instanceId}`);
749
+ void this.handleCardActionEvent(binding.workspaceId, binding.instanceId, data);
653
750
  return {};
654
751
  },
655
752
  });
@@ -679,26 +776,34 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
679
776
  }
680
777
  this.notifyRuntimeStateChanged();
681
778
  }
682
- async stopWorkspace(workspaceId) {
683
- 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);
684
785
  if (!state) {
685
786
  return;
686
787
  }
687
- 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}`);
688
789
  try {
689
790
  await state.wsClient?.stop?.();
690
- this.options.log?.(`localcore-lark stopped workspace=${workspaceId}`);
791
+ this.options.log?.(`localcore-lark stopped workspace=${state.workspaceId}/${state.instanceId}`);
691
792
  }
692
793
  catch (error) {
693
- 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)}`);
694
795
  // Best effort: current SDK versions may not implement stop().
695
796
  }
696
- this.runtime.set(workspaceId, {
697
- workspaceId,
797
+ this.runtime.set(key, {
798
+ workspaceId: state.workspaceId,
799
+ instanceId: state.instanceId,
800
+ displayName: state.displayName,
801
+ platformKey: state.platformKey,
698
802
  enabled: false,
699
803
  status: 'stopped',
700
804
  connected: false,
701
805
  appId: state.appId,
806
+ autoApprove: state.autoApprove,
702
807
  cardActionsEnabled: state.cardActionsEnabled,
703
808
  });
704
809
  this.notifyRuntimeStateChanged();
@@ -711,7 +816,11 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
711
816
  },
712
817
  });
713
818
  }
714
- 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) || {};
715
824
  const payload = (data?.event && typeof data.event === 'object')
716
825
  ? data.event
717
826
  : data;
@@ -739,7 +848,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
739
848
  const imageKey = String(parsedContent.image_key || parsedContent.file_key || '').trim();
740
849
  if (imageKey) {
741
850
  try {
742
- contentParts.push(await this.downloadMessageImage(workspaceId, String(message.message_id || ''), imageKey));
851
+ contentParts.push(await this.downloadMessageImage(workspaceId, String(message.message_id || ''), imageKey, instanceId));
743
852
  }
744
853
  catch (error) {
745
854
  const errorText = `[Image download failed: ${error instanceof Error ? error.message : String(error)}]`;
@@ -766,6 +875,8 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
766
875
  this.options.log?.(`localcore-lark inbound message for ${workspaceId}: chat=${chatId} user=${platformUserId} type=${messageType || 'unknown'} text=${JSON.stringify(displayText.slice(0, 120))}`);
767
876
  await this.handleInboundMessage({
768
877
  workspaceId,
878
+ instanceId,
879
+ platformKey,
769
880
  platformUserId,
770
881
  chatId,
771
882
  displayName: String(payload?.sender?.sender_id?.user_id ||
@@ -795,8 +906,8 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
795
906
  contentParts: this.createWrappedContentParts(wrappedText, parts),
796
907
  };
797
908
  }
798
- async downloadMessageImage(workspaceId, messageId, imageKey) {
799
- const state = this.runtime.get(workspaceId);
909
+ async downloadMessageImage(workspaceId, messageId, imageKey, instanceId) {
910
+ const state = this.resolveRuntimeState(workspaceId, instanceId).state;
800
911
  if (!state?.client) {
801
912
  throw new Error('Lark client is not connected');
802
913
  }
@@ -930,32 +1041,8 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
930
1041
  return `${value.slice(0, 6)}...${value.slice(-4)}`;
931
1042
  }
932
1043
  collectBindings(config) {
933
- const projects = Array.isArray(config?.projects) ? config.projects : [];
934
- return projects.flatMap((project) => {
935
- const platforms = Array.isArray(project.platforms) ? project.platforms : [];
936
- return platforms
937
- .map((platform) => ({
938
- platformType: (0, desktop_js_1.normalizeDesktopPlatformType)(platform?.type),
939
- options: platform?.options && typeof platform.options === 'object'
940
- ? platform.options
941
- : {},
942
- }))
943
- .filter((platform) => platform.platformType === 'lark')
944
- .map((platform) => ({
945
- workspaceId: project.name,
946
- appId: String(platform.options.app_id || '').trim(),
947
- appSecret: String(platform.options.app_secret || '').trim(),
948
- encryptKey: String(platform.options.encrypt_key || '').trim(),
949
- verificationToken: String(platform.options.verification_token || '').trim(),
950
- autoApprove: String(platform.options.auto_approve || '').trim().toLowerCase() === 'true'
951
- || platform.options.auto_approve === true,
952
- cardActionsEnabled: String(platform.options.card_actions || platform.options.enable_card_actions || '').trim().toLowerCase() === 'true'
953
- || platform.options.card_actions === true
954
- || platform.options.enable_card_actions === true
955
- || this.defaultCardActionsEnabled,
956
- enabled: Boolean(String(platform.options.app_id || '').trim() && String(platform.options.app_secret || '').trim()),
957
- project,
958
- }));
1044
+ return (0, config_js_1.collectLarkWorkspaceBindings)(config, {
1045
+ defaultCardActionsEnabled: this.defaultCardActionsEnabled,
959
1046
  });
960
1047
  }
961
1048
  async getLarkModule() {
@@ -964,40 +1051,6 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
964
1051
  }
965
1052
  return this.larkModulePromise;
966
1053
  }
967
- buildInteractiveCard(text, buttonRows = [], sessionKey, threadId) {
968
- const elements = [];
969
- if (text) {
970
- elements.push({ tag: 'markdown', content: text });
971
- }
972
- for (const row of buttonRows) {
973
- const actions = row
974
- .filter((button) => button.text && button.data)
975
- .map((button, index) => ({
976
- tag: 'button',
977
- text: {
978
- tag: 'plain_text',
979
- content: this.formatPermissionButtonLabel(button),
980
- },
981
- type: this.resolveLarkButtonType(button, index),
982
- value: {
983
- action: 'permission_response',
984
- response: (0, desktop_js_1.normalizePermissionResponse)(button.data) || button.data,
985
- session_key: sessionKey || '',
986
- thread_id: threadId || '',
987
- },
988
- }));
989
- if (actions.length) {
990
- elements.push({
991
- tag: 'action',
992
- actions,
993
- });
994
- }
995
- }
996
- return {
997
- config: { wide_screen_mode: true },
998
- elements,
999
- };
1000
- }
1001
1054
  async sendTextAsCard(state, chatId, text, buttonRows = [], sessionKey, threadId) {
1002
1055
  const response = await state.client.im.message.create({
1003
1056
  params: {
@@ -1006,7 +1059,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
1006
1059
  data: {
1007
1060
  receive_id: chatId,
1008
1061
  msg_type: 'interactive',
1009
- content: JSON.stringify(this.buildInteractiveCard(text, buttonRows, sessionKey, threadId)),
1062
+ content: JSON.stringify((0, cards_js_1.buildInteractiveCard)(text, buttonRows, sessionKey, threadId)),
1010
1063
  },
1011
1064
  });
1012
1065
  return String(response?.data?.message_id || '').trim();
@@ -1044,7 +1097,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
1044
1097
  message_id: messageId,
1045
1098
  },
1046
1099
  data: {
1047
- content: JSON.stringify(this.buildInteractiveCard(text, buttonRows, sessionKey, threadId)),
1100
+ content: JSON.stringify((0, cards_js_1.buildInteractiveCard)(text, buttonRows, sessionKey, threadId)),
1048
1101
  },
1049
1102
  });
1050
1103
  }
@@ -1080,21 +1133,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
1080
1133
  };
1081
1134
  }
1082
1135
  createTurnState(sessionKey, sourceMessageId) {
1083
- const turn = {
1084
- sessionKey,
1085
- sourceMessageId,
1086
- awaitingPermission: false,
1087
- processing: false,
1088
- previewText: '',
1089
- finalText: '',
1090
- progressMessageIds: {},
1091
- thinkingSteps: [],
1092
- toolCalls: [],
1093
- statusLines: [],
1094
- buttonRows: [],
1095
- lastPatchedAt: 0,
1096
- lastPatchedAtByMessageId: {},
1097
- };
1136
+ const turn = (0, runtime_state_js_1.createLarkTurnState)(sessionKey, sourceMessageId);
1098
1137
  this.outboundTurns.set(sessionKey, turn);
1099
1138
  return turn;
1100
1139
  }
@@ -1115,209 +1154,36 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
1115
1154
  return turn;
1116
1155
  }
1117
1156
  consumeBridgeEvent(turn, event) {
1118
- const content = foldToolResultForLark(String(event.content || '').trim());
1119
- if (event.type === 'typing_start') {
1120
- // Always start a fresh card for each generation phase.
1121
- // This keeps Feishu message order aligned with user-visible timeline.
1122
- turn.messageId = undefined;
1123
- turn.finalMessageId = undefined;
1124
- turn.progressMessageIds = {};
1125
- turn.processing = true;
1126
- turn.permissionMessageId = undefined;
1127
- turn.previewText = '';
1128
- turn.finalText = '';
1129
- turn.thinkingSteps = [];
1130
- turn.toolCalls = [];
1131
- turn.buttonRows = [];
1132
- turn.statusLines = [];
1133
- turn.lastPatchedAtByMessageId = {};
1134
- return;
1135
- }
1136
- if (event.type === 'typing_stop') {
1137
- turn.processing = false;
1138
- return;
1139
- }
1140
- if (event.type === 'preview_start' || event.type === 'update_message') {
1141
- turn.previewText = content;
1142
- return;
1143
- }
1144
- if (event.type === 'status') {
1145
- if (content) {
1146
- this.pushUnique(turn.statusLines, content);
1147
- }
1148
- return;
1149
- }
1150
- if (event.type === 'buttons') {
1151
- turn.awaitingPermission = true;
1152
- turn.buttonRows = [];
1153
- if (this.mirrorPermissionStateInMainCard && content) {
1154
- this.pushUnique(turn.statusLines, `等待确认: ${content}`);
1155
- }
1156
- return;
1157
- }
1158
- if (!content) {
1159
- return;
1160
- }
1161
- if (content.startsWith('💭 ')) {
1162
- this.pushUnique(turn.thinkingSteps, content.slice(3).trim());
1163
- return;
1164
- }
1165
- if (content.startsWith('🔧 ')) {
1166
- this.pushUnique(turn.toolCalls, content.slice(3).trim());
1167
- return;
1168
- }
1169
- if (content.startsWith('⏳ ') || content.startsWith('📤 ')) {
1170
- this.pushUnique(turn.statusLines, content.slice(3).trim());
1171
- return;
1172
- }
1173
- turn.finalText = content;
1174
- turn.previewText = content;
1175
- }
1176
- renderBridgeEventMessage(turn, event) {
1177
- const content = foldToolResultForLark(String(event.content || '').trim());
1178
- if (event.type === 'preview_start' || event.type === 'update_message') {
1179
- if (content.startsWith('💭 ')) {
1180
- return this.renderProgressMessage(event.previewHandle || 'thinking-preview', content);
1181
- }
1182
- if (content.startsWith('🔧 ')) {
1183
- return this.renderProgressMessage(this.progressKey('tool', event), content);
1184
- }
1185
- return {
1186
- key: 'final',
1187
- text: content,
1188
- buttonRows: turn.buttonRows,
1189
- isFinal: true,
1190
- };
1191
- }
1192
- if (event.type === 'status') {
1193
- return this.renderProgressMessage(this.progressKey('status', event), content.startsWith('⏳ ') ? content : `⏳ ${content}`);
1194
- }
1195
- if (event.type === 'reply') {
1196
- if (content.startsWith('💭 ')) {
1197
- return this.renderProgressMessage(this.progressKey('thinking', event), content);
1198
- }
1199
- if (content.startsWith('🔧 ')) {
1200
- return this.renderProgressMessage(this.progressKey('tool', event), content);
1201
- }
1202
- if (content.startsWith('⏳ ') || content.startsWith('📤 ')) {
1203
- return this.renderProgressMessage(this.progressKey('status', event), content);
1204
- }
1205
- return {
1206
- key: 'final',
1207
- text: content,
1208
- buttonRows: turn.buttonRows,
1209
- isFinal: true,
1210
- };
1211
- }
1212
- if (event.type === 'typing_start' || event.type === 'typing_stop') {
1213
- return { key: 'noop', text: '', buttonRows: [], isFinal: false };
1214
- }
1215
- return {
1216
- key: 'noop',
1217
- text: '',
1218
- buttonRows: [],
1219
- isFinal: false,
1220
- };
1221
- }
1222
- renderProgressMessage(key, text) {
1223
- return {
1224
- key,
1225
- text,
1226
- buttonRows: [],
1227
- isFinal: false,
1228
- };
1229
- }
1230
- progressKey(prefix, event) {
1231
- const stableId = String(event.messageId || event.previewHandle || '').trim();
1232
- if (stableId) {
1233
- return `${prefix}:${stableId}`;
1234
- }
1235
- const content = String(event.content || '').trim().replace(/\s+/g, ' ');
1236
- return `${prefix}:${content.slice(0, 120)}`;
1237
- }
1238
- getRenderedMessageId(turn, rendered) {
1239
- if (rendered.isFinal) {
1240
- return turn.finalMessageId || turn.messageId;
1241
- }
1242
- return turn.progressMessageIds[rendered.key];
1243
- }
1244
- setRenderedMessageId(turn, rendered, messageId) {
1245
- if (rendered.isFinal) {
1246
- turn.finalMessageId = messageId;
1247
- turn.messageId = messageId;
1248
- return;
1249
- }
1250
- turn.progressMessageIds[rendered.key] = messageId;
1251
- }
1252
- renderPermissionCard(turn, event, cardActionsEnabled) {
1253
- const summary = this.buildPermissionSummary(turn, String(event.content || '').trim());
1254
- const sections = [
1255
- '**需要工具确认**',
1256
- summary.command ? `\`${summary.command}\`` : '',
1257
- summary.reason || '',
1258
- cardActionsEnabled
1259
- ? '也可以直接回复:`allow` / `allow all` / `deny`'
1260
- : '请直接回复:`allow` / `allow all` / `deny`',
1261
- ].filter(Boolean);
1262
- const buttonRows = cardActionsEnabled && Array.isArray(event.buttonRows)
1263
- ? event.buttonRows
1264
- .map((row) => Array.isArray(row)
1265
- ? row
1266
- .filter((button) => Boolean(button?.text && button?.data))
1267
- .map((button) => ({
1268
- text: this.formatPermissionButtonLabel(button),
1269
- data: (0, desktop_js_1.normalizePermissionResponse)(button.data) || button.data,
1270
- }))
1271
- : [])
1272
- .filter((row) => row.length > 0)
1273
- : [];
1274
- return {
1275
- text: sections.join('\n\n').trim(),
1276
- buttonRows,
1277
- };
1278
- }
1279
- renderPendingPairingCard(code) {
1280
- const lines = [
1281
- '**已收到消息**',
1282
- '当前账号还未授权接入这个工作区。',
1283
- '请在桌面端完成审批后再次发送消息。',
1284
- ];
1285
- if (code) {
1286
- lines.push(`配对码:\`${code}\``);
1287
- }
1288
- return lines.join('\n\n');
1157
+ (0, runtime_state_js_1.consumeLarkBridgeEvent)(turn, event, {
1158
+ mirrorPermissionStateInMainCard: this.mirrorPermissionStateInMainCard,
1159
+ });
1289
1160
  }
1290
- 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) || {};
1291
1165
  try {
1292
- const payload = (data?.event && typeof data.event === 'object')
1293
- ? data.event
1294
- : data;
1295
- const value = payload?.action?.value;
1296
- if (!value || value.action !== 'permission_response') {
1297
- return;
1298
- }
1299
- const response = (0, desktop_js_1.normalizePermissionResponse)(String(value.response || '').trim()) || String(value.response || '').trim();
1300
- const threadId = String(value.thread_id || '').trim();
1301
- if (!response || !threadId) {
1166
+ const action = (0, cards_js_1.extractCardActionValue)(data);
1167
+ if (!action) {
1302
1168
  return;
1303
1169
  }
1304
1170
  const router = this.options.getWorkspaceRouter();
1305
- await router.sendThreadAction(threadId, response);
1306
- await this.markPermissionCardActionHandled(workspaceId, data, payload, value, response, threadId);
1307
- 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}`);
1308
1174
  }
1309
1175
  catch (error) {
1310
- 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)}`);
1311
1177
  }
1312
1178
  }
1313
- async markPermissionCardActionHandled(workspaceId, rootPayload, payload, value, response, threadId) {
1314
- const state = this.runtime.get(workspaceId);
1179
+ async markPermissionCardActionHandled(workspaceId, instanceId, rootPayload, payload, value, response, threadId) {
1180
+ const state = this.resolveRuntimeState(workspaceId, instanceId).state;
1315
1181
  if (!state?.client) {
1316
1182
  return;
1317
1183
  }
1318
1184
  const sessionKey = String(value.session_key || '').trim();
1319
1185
  const turn = sessionKey ? this.outboundTurns.get(sessionKey) : undefined;
1320
- const messageId = this.extractCardActionMessageId(rootPayload, payload, value) || turn?.permissionMessageId;
1186
+ const messageId = (0, cards_js_1.extractCardActionMessageId)(rootPayload, payload, value) || turn?.permissionMessageId;
1321
1187
  if (!messageId) {
1322
1188
  this.options.log?.(`localcore-lark could not find permission card message id for workspace=${workspaceId} sessionKey=${sessionKey || 'missing'}`);
1323
1189
  return;
@@ -1325,7 +1191,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
1325
1191
  try {
1326
1192
  await this.patchTextCard(state, messageId, [
1327
1193
  '**工具确认已处理**',
1328
- `已选择:${this.formatPermissionResponseLabel(response)}`,
1194
+ `已选择:${(0, cards_js_1.formatPermissionResponseLabel)(response)}`,
1329
1195
  '等待代理继续执行...',
1330
1196
  ].join('\n\n'), [], sessionKey || undefined, threadId);
1331
1197
  if (turn) {
@@ -1337,133 +1203,8 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
1337
1203
  this.options.log?.(`localcore-lark failed to patch handled permission card ${messageId}: ${error instanceof Error ? error.message : String(error)}`);
1338
1204
  }
1339
1205
  }
1340
- extractCardActionMessageId(...payloads) {
1341
- for (const payload of payloads) {
1342
- const messageId = this.extractKnownCardActionMessageId(payload);
1343
- if (messageId) {
1344
- return messageId;
1345
- }
1346
- }
1347
- for (const payload of payloads) {
1348
- const messageId = this.findNestedCardActionMessageId(payload);
1349
- if (messageId) {
1350
- return messageId;
1351
- }
1352
- }
1353
- return '';
1354
- }
1355
- extractKnownCardActionMessageId(payload) {
1356
- const source = payload;
1357
- if (!source || typeof source !== 'object') {
1358
- return '';
1359
- }
1360
- const candidates = [
1361
- source?.permission_message_id,
1362
- source?.action_message_id,
1363
- source?.context?.open_message_id,
1364
- source?.context?.message_id,
1365
- source?.event?.context?.open_message_id,
1366
- source?.event?.context?.message_id,
1367
- source?.event_context?.open_message_id,
1368
- source?.event_context?.message_id,
1369
- source?.message?.message_id,
1370
- source?.message?.open_message_id,
1371
- source?.event?.message?.message_id,
1372
- source?.event?.message?.open_message_id,
1373
- source?.message_id,
1374
- source?.open_message_id,
1375
- ];
1376
- return candidates
1377
- .map((candidate) => String(candidate || '').trim())
1378
- .find(Boolean) || '';
1379
- }
1380
- findNestedCardActionMessageId(payload, seen = new Set()) {
1381
- if (!payload || typeof payload !== 'object' || seen.has(payload)) {
1382
- return '';
1383
- }
1384
- seen.add(payload);
1385
- if (Array.isArray(payload)) {
1386
- for (const item of payload) {
1387
- const nested = this.findNestedCardActionMessageId(item, seen);
1388
- if (nested)
1389
- return nested;
1390
- }
1391
- return '';
1392
- }
1393
- const source = payload;
1394
- for (const key of ['open_message_id', 'message_id', 'permission_message_id', 'action_message_id']) {
1395
- const value = source[key];
1396
- if (typeof value === 'string' && value.trim()) {
1397
- return value.trim();
1398
- }
1399
- }
1400
- for (const value of Object.values(source)) {
1401
- const nested = this.findNestedCardActionMessageId(value, seen);
1402
- if (nested)
1403
- return nested;
1404
- }
1405
- return '';
1406
- }
1407
- pushUnique(target, value) {
1408
- const normalized = value.trim();
1409
- if (!normalized) {
1410
- return;
1411
- }
1412
- if (target[target.length - 1] === normalized) {
1413
- return;
1414
- }
1415
- target.push(normalized);
1416
- if (target.length > 6) {
1417
- target.splice(0, target.length - 6);
1418
- }
1419
- }
1420
- formatPermissionButtonLabel(button) {
1421
- return this.formatPermissionResponseLabel(button.data || button.text);
1422
- }
1423
- formatPermissionResponseLabel(response) {
1424
- switch ((0, desktop_js_1.normalizePermissionResponse)(response) || response) {
1425
- case 'allow':
1426
- return '允许一次';
1427
- case 'allow all':
1428
- return '始终允许';
1429
- case 'deny':
1430
- return '拒绝';
1431
- default:
1432
- return response;
1433
- }
1434
- }
1435
- resolveLarkButtonType(button, index) {
1436
- const response = (0, desktop_js_1.normalizePermissionResponse)(button.data) || (0, desktop_js_1.normalizePermissionResponse)(button.text);
1437
- if (response === 'deny') {
1438
- return 'danger';
1439
- }
1440
- if (response === 'allow') {
1441
- return 'primary';
1442
- }
1443
- return index === 0 ? 'primary' : 'default';
1444
- }
1445
- buildPermissionSummary(turn, rawContent) {
1446
- const lastTool = turn.toolCalls[turn.toolCalls.length - 1] || '';
1447
- const [commandPart = '', reasonPart = ''] = lastTool.split(/\s+-\s+/, 2);
1448
- const compactContent = rawContent
1449
- .replace(/\s+/g, ' ')
1450
- .replace(/请选择一个选项继续执行。?/g, '')
1451
- .replace(/若按钮没有显示,请直接回复:?\s*allow all \/ allow \/ deny/gi, '')
1452
- .replace(/等待工具确认/gi, '')
1453
- .trim();
1454
- return {
1455
- command: commandPart.trim(),
1456
- reason: reasonPart.trim() || compactContent,
1457
- };
1458
- }
1459
- formatCompactToolLines(toolCalls) {
1460
- return toolCalls.slice(-2).map((line) => {
1461
- const compact = line.replace(/\s+/g, ' ').trim();
1462
- return compact.startsWith('Terminal') ? '• Terminal' : `• ${compact}`;
1463
- });
1464
- }
1465
- async sendImmediateCard(workspaceId, chatId, text) {
1466
- const state = this.runtime.get(workspaceId);
1206
+ async sendImmediateCard(workspaceId, chatId, text, instanceId) {
1207
+ const state = this.resolveRuntimeState(workspaceId, instanceId).state;
1467
1208
  if (!state?.client || !state.connected) {
1468
1209
  this.options.log?.(`localcore-lark immediate card skipped because workspace is not connected: ${workspaceId}`);
1469
1210
  return '';
@@ -1476,8 +1217,8 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
1476
1217
  return '';
1477
1218
  }
1478
1219
  }
1479
- async addAcknowledgementReaction(workspaceId, messageId, turn) {
1480
- const state = this.runtime.get(workspaceId);
1220
+ async addAcknowledgementReaction(workspaceId, messageId, turn, instanceId) {
1221
+ const state = this.resolveRuntimeState(workspaceId, instanceId).state;
1481
1222
  if (!state?.client || !state.connected || !messageId) {
1482
1223
  return;
1483
1224
  }