@kafca/agentdock 0.1.22 → 0.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/README.md +4 -0
  2. package/dist/renderer/assets/{Badge-B_rgzxYO.js → Badge-CXcUMqb-.js} +1 -1
  3. package/dist/renderer/assets/{Chat-CYtp6m7T.js → Chat-BRxdPzi-.js} +1 -1
  4. package/dist/renderer/assets/{ChatMarkdown-DOBMwa9d.js → ChatMarkdown-sWGBIfIb.js} +1 -1
  5. package/dist/renderer/assets/{Config-DppMSPHF.js → Config-DqxuxIGg.js} +1 -1
  6. package/dist/renderer/assets/{CronList-BDtB1SuA.js → CronList-OgHxbti5.js} +1 -1
  7. package/dist/renderer/assets/{Dashboard-iFlRnd7Y.js → Dashboard-DmklXiH1.js} +2 -2
  8. package/dist/renderer/assets/{EmptyState-bKDt_T88.js → EmptyState-BvDmi8dq.js} +1 -1
  9. package/dist/renderer/assets/{KnowledgeDetail-CKPhKPIg.js → KnowledgeDetail-Bj59Hdpo.js} +2 -2
  10. package/dist/renderer/assets/{KnowledgeHome-CFeJre6i.js → KnowledgeHome-CigQ-nSo.js} +3 -3
  11. package/dist/renderer/assets/{Logs-CyxSVOFK.js → Logs-Bfc7gBpY.js} +1 -1
  12. package/dist/renderer/assets/{Modal-CxPnk1Ey.js → Modal-CfmiFknz.js} +1 -1
  13. package/dist/renderer/assets/{Page-kU9vubzj.js → Page-BxtLEw_b.js} +1 -1
  14. package/dist/renderer/assets/{ProjectDetail-D9FY9NWZ.js → ProjectDetail-CcUphxGm.js} +1 -1
  15. package/dist/renderer/assets/{ProjectList-DIwr2TXg.js → ProjectList-CVkvDIfy.js} +1 -1
  16. package/dist/renderer/assets/{Select-BaWoz5Dn.js → Select-DgQMZ0MD.js} +1 -1
  17. package/dist/renderer/assets/{SessionChat-Cm9CW8Zx.js → SessionChat-CRmGKfBx.js} +1 -1
  18. package/dist/renderer/assets/{SessionList-UPnWlXXO.js → SessionList-D0E8XbYw.js} +1 -1
  19. package/dist/renderer/assets/ThreadChat-DUgqZSBB.js +23 -0
  20. package/dist/renderer/assets/Workspace-Q4g4rlRT.js +14 -0
  21. package/dist/renderer/assets/{arrow-left-BmZAVGV7.js → arrow-left-BySN_zhF.js} +1 -1
  22. package/dist/renderer/assets/{book-open-DZ3GXFgI.js → book-open-BGlrnrTc.js} +1 -1
  23. package/dist/renderer/assets/{check-CeVSvDCv.js → check-BiJogEa5.js} +1 -1
  24. package/dist/renderer/assets/{chevron-down-yryuMoQ-.js → chevron-down-D2fHRJv6.js} +1 -1
  25. package/dist/renderer/assets/{circle-eASsFISH.js → circle-CIcjixmI.js} +1 -1
  26. package/dist/renderer/assets/desktop-DAXY9Bxv.js +4 -0
  27. package/dist/renderer/assets/desktop-DyA_NYew.js +1 -0
  28. package/dist/renderer/assets/{funnel-pwAe2Ixp.js → funnel-BUzwb-id.js} +1 -1
  29. package/dist/renderer/assets/{heart-B2Sp-bCm.js → heart-DZEQ1z_V.js} +1 -1
  30. package/dist/renderer/assets/{index-BC3MWbS1.js → index-CB_KyLOr.js} +39 -39
  31. package/dist/renderer/assets/{index-Cse2Br64.js → index-DgsIo4vJ.js} +3 -3
  32. package/dist/renderer/assets/{pencil-BfEunUF3.js → pencil-DGL4agcl.js} +1 -1
  33. package/dist/renderer/assets/{play-Dcs9Lj_Z.js → play-DzasCLM4.js} +1 -1
  34. package/dist/renderer/assets/{plug-B_YschQe.js → plug-dubNMe_m.js} +1 -1
  35. package/dist/renderer/assets/{plus-Pqo-cXHQ.js → plus-DN4kML4k.js} +1 -1
  36. package/dist/renderer/assets/projects-migPtfRe.js +1 -0
  37. package/dist/renderer/assets/{save-BKM5yi1A.js → save-CNIc9tjl.js} +1 -1
  38. package/dist/renderer/assets/{search-BU42h10M.js → search-C0EGbF3I.js} +1 -1
  39. package/dist/renderer/assets/{server-DA5t3Rtr.js → server-BMxQ4zmc.js} +1 -1
  40. package/dist/renderer/assets/{session-utils-Cr9VhNSq.js → session-utils-Bf5O-iMi.js} +1 -1
  41. package/dist/renderer/assets/{sessions-bvV8ZMfX.js → sessions-BMSs6rEI.js} +1 -1
  42. package/dist/renderer/assets/{shield-check-BeOJKur_.js → shield-check-i3Tf1TUz.js} +1 -1
  43. package/dist/renderer/assets/{trash-2-CQ7HKO3q.js → trash-2-SKrcp18f.js} +1 -1
  44. package/dist/renderer/assets/{user-CtFjY9SV.js → user-cNUZddjZ.js} +1 -1
  45. package/dist/renderer/assets/{wifi-off-Ci5m0ysf.js → wifi-off-Dyb84ANR.js} +1 -1
  46. package/dist/renderer/index.html +1 -1
  47. package/dist-electron/electron/channel-content.test.js +29 -0
  48. package/dist-electron/electron/lac-cli.test.js +49 -4
  49. package/dist-electron/electron/local-core-acp-content.test.js +39 -0
  50. package/dist-electron/electron/local-core-contracts.test.js +67 -0
  51. package/dist-electron/electron/local-core-refactor.test.js +628 -0
  52. package/dist-electron/electron/plugin-kernel.test.js +74 -14
  53. package/dist-electron/electron/workspace-task-store.test.js +124 -0
  54. package/dist-electron/packages/contracts/src/local-core.js +110 -0
  55. package/dist-electron/packages/core-sdk/src/index.js +40 -20
  56. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-content.js +25 -4
  57. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-response-processor.js +3 -2
  58. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-store.js +44 -28
  59. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +5 -1
  60. package/dist-electron/services/local-ai-core/src/acp/workspace-acp-permissions.js +15 -6
  61. package/dist-electron/services/local-ai-core/src/channel/lark/cards.js +210 -0
  62. package/dist-electron/services/local-ai-core/src/channel/lark/config.js +54 -0
  63. package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +280 -496
  64. package/dist-electron/services/local-ai-core/src/channel/lark/registration.js +50 -0
  65. package/dist-electron/services/local-ai-core/src/channel/lark/runtime-state.js +191 -0
  66. package/dist-electron/services/local-ai-core/src/channel/lark/types.js +2 -0
  67. package/dist-electron/services/local-ai-core/src/channel/shared/content.js +23 -0
  68. package/dist-electron/services/local-ai-core/src/channel/shared/plugin.js +31 -0
  69. package/dist-electron/services/local-ai-core/src/channel/weixin/config.js +125 -0
  70. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +164 -352
  71. package/dist-electron/services/local-ai-core/src/channel/weixin/transport.js +169 -0
  72. package/dist-electron/services/local-ai-core/src/channel/weixin/types.js +2 -0
  73. package/dist-electron/services/local-ai-core/src/cli/lac.js +19 -16
  74. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +5 -0
  75. package/dist-electron/services/local-ai-core/src/plugins/builtin/channel-lark-plugin.js +18 -39
  76. package/dist-electron/services/local-ai-core/src/plugins/builtin/channel-weixin-plugin.js +18 -39
  77. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +55 -44
  78. package/dist-electron/services/local-ai-core/src/runtime/server.js +10 -8
  79. package/dist-electron/services/local-ai-core/src/scheduler/job-id.js +15 -0
  80. package/dist-electron/services/local-ai-core/src/scheduler/scheduler-service.js +30 -5
  81. package/dist-electron/shared/desktop.js +37 -5
  82. package/dist-electron/src/pages/Threads/thread-chat-model.js +57 -0
  83. package/dist-electron/src/pages/Threads/thread-chat-permission.test.js +70 -0
  84. package/package.json +1 -1
  85. package/dist/renderer/assets/ThreadChat-YvVsACW3.js +0 -23
  86. package/dist/renderer/assets/Workspace-ST7V8u6T.js +0 -14
  87. package/dist/renderer/assets/desktop-Che1cbpE.js +0 -4
  88. package/dist/renderer/assets/desktop-DkrP_s-b.js +0 -1
  89. package/dist/renderer/assets/projects-BCj0k_Gy.js +0 -1
@@ -39,16 +39,12 @@ const node_crypto_1 = require("node:crypto");
39
39
  const node_fs_1 = require("node:fs");
40
40
  const node_path_1 = require("node:path");
41
41
  const desktop_js_1 = require("../../../../../shared/desktop.js");
42
+ const content_js_1 = require("../shared/content.js");
42
43
  const file_utils_js_1 = require("../shared/file-utils.js");
43
- 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
- }
44
+ const cards_js_1 = require("./cards.js");
45
+ const config_js_1 = require("./config.js");
46
+ const registration_js_1 = require("./registration.js");
47
+ const runtime_state_js_1 = require("./runtime-state.js");
52
48
  const PAIRING_EXPIRY_MS = 10 * 60 * 1000;
53
49
  const LARK_MAX_UPLOAD_FILE_SIZE = 30 * 1024 * 1024;
54
50
  class LocalCoreLarkGateway extends node_events_1.EventEmitter {
@@ -73,25 +69,30 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
73
69
  async refreshBindings() {
74
70
  const config = await this.options.readConfig();
75
71
  const bindings = this.collectBindings(config);
76
- const 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);
72
+ const nextKeys = new Set(bindings.map((binding) => (0, config_js_1.runtimeKey)(binding.workspaceId, binding.instanceId)));
73
+ for (const key of [...this.runtime.keys()]) {
74
+ if (!nextKeys.has(key)) {
75
+ await this.stopWorkspaceKey(key);
80
76
  }
81
77
  }
82
78
  for (const binding of bindings) {
83
- const current = this.runtime.get(binding.workspaceId);
79
+ const key = (0, config_js_1.runtimeKey)(binding.workspaceId, binding.instanceId);
80
+ const current = this.runtime.get(key);
84
81
  if (!binding.enabled) {
85
82
  if (current) {
86
- await this.stopWorkspace(binding.workspaceId);
83
+ await this.stopWorkspaceKey(key);
87
84
  }
88
85
  else {
89
- this.runtime.set(binding.workspaceId, {
86
+ this.runtime.set(key, {
90
87
  workspaceId: binding.workspaceId,
88
+ instanceId: binding.instanceId,
89
+ displayName: binding.displayName,
90
+ platformKey: binding.platformKey,
91
91
  enabled: false,
92
92
  status: 'disabled',
93
93
  connected: false,
94
94
  appId: binding.appId,
95
+ autoApprove: binding.autoApprove,
95
96
  cardActionsEnabled: binding.cardActionsEnabled,
96
97
  });
97
98
  }
@@ -104,23 +105,70 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
104
105
  }
105
106
  this.notifyRuntimeStateChanged();
106
107
  }
107
- async testConnection(workspaceId) {
108
- const binding = await this.getBinding(workspaceId);
108
+ async testConnection(workspaceId, instanceId) {
109
+ const binding = await this.getBinding(workspaceId, instanceId);
109
110
  const result = await this.createSdkClientResult(binding);
110
111
  return {
111
112
  ...result,
112
113
  platform: 'lark',
113
114
  workspaceId,
115
+ instanceId: binding.instanceId,
114
116
  };
115
117
  }
116
- async enable(workspaceId) {
117
- const binding = await this.getBinding(workspaceId);
118
+ async enable(workspaceId, instanceId) {
119
+ const binding = await this.getBinding(workspaceId, instanceId);
118
120
  await this.startWorkspace(binding);
119
- return this.getStatus(workspaceId);
121
+ return this.getStatus(workspaceId, binding.instanceId);
122
+ }
123
+ async disable(workspaceId, instanceId) {
124
+ await this.stopWorkspace(workspaceId, instanceId);
125
+ return this.getStatus(workspaceId, instanceId);
126
+ }
127
+ async getQrCode(workspaceId, instanceId) {
128
+ const binding = await this.getBinding(workspaceId, instanceId);
129
+ const data = await (0, registration_js_1.requestAppRegistration)(binding);
130
+ const ticket = String(data.device_code || '').trim();
131
+ const userCode = String(data.user_code || '').trim();
132
+ if (!ticket || !userCode) {
133
+ throw new Error('Lark app registration did not return a device code and user code.');
134
+ }
135
+ return {
136
+ ticket,
137
+ expiresIn: Number(data.expires_in || registration_js_1.DEFAULT_LARK_QR_EXPIRES_IN) || registration_js_1.DEFAULT_LARK_QR_EXPIRES_IN,
138
+ interval: Number(data.interval || 5) || 5,
139
+ qrCodeUrl: `${(0, registration_js_1.getLarkOpenBase)(binding)}${registration_js_1.LARK_APP_REGISTRATION_SETUP_PATH}?user_code=${encodeURIComponent(userCode)}&from=openclaw`,
140
+ instanceId: binding.instanceId,
141
+ displayName: binding.displayName,
142
+ };
120
143
  }
121
- async disable(workspaceId) {
122
- await this.stopWorkspace(workspaceId);
123
- return this.getStatus(workspaceId);
144
+ async checkQrCodeStatus(workspaceId, ticket, instanceId) {
145
+ const binding = await this.getBinding(workspaceId, instanceId);
146
+ const data = await (0, registration_js_1.pollAppRegistration)(binding, ticket);
147
+ const error = String(data.error || '').trim();
148
+ if (error === 'authorization_pending' || error === 'slow_down') {
149
+ return { status: 'wait' };
150
+ }
151
+ if (error === 'expired_token' || error === 'invalid_grant') {
152
+ return { status: 'expired' };
153
+ }
154
+ if (error === 'access_denied') {
155
+ throw new Error('Lark app registration was rejected.');
156
+ }
157
+ if (error) {
158
+ throw new Error(`Lark app registration failed: ${String(data.error_description || error)}`);
159
+ }
160
+ const appId = String(data.client_id || '').trim();
161
+ const appSecret = String(data.client_secret || '').trim();
162
+ if (!appId || !appSecret) {
163
+ return { status: 'wait' };
164
+ }
165
+ return {
166
+ status: 'confirmed',
167
+ credentials: {
168
+ appId,
169
+ appSecret,
170
+ },
171
+ };
124
172
  }
125
173
  async sendScheduledCard(workspaceId, chatId, text) {
126
174
  return this.sendImmediateCard(workspaceId, chatId, text);
@@ -129,7 +177,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
129
177
  return this.sendScheduledCard(workspaceId, route.channelId, text);
130
178
  }
131
179
  async sendOutboundMessage(workspaceId, input) {
132
- const state = this.runtime.get(workspaceId);
180
+ const state = this.resolveRuntimeState(workspaceId, input.route?.instanceId).state;
133
181
  if (!state?.client || !state.connected) {
134
182
  throw new Error(`Lark workspace is not connected: ${workspaceId}`);
135
183
  }
@@ -245,15 +293,19 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
245
293
  }
246
294
  this.mutedThreadBridgeCounts.set(threadId, current - 1);
247
295
  }
248
- getStatus(workspaceId) {
296
+ getStatus(workspaceId, instanceId) {
249
297
  this.options.store.expirePendingPairings();
250
- const binding = this.runtime.get(workspaceId);
298
+ const resolved = this.resolveRuntimeState(workspaceId, instanceId);
299
+ const binding = resolved.state;
300
+ const platformKey = binding?.platformKey || (0, config_js_1.channelPlatformKey)('lark', resolved.instanceId);
251
301
  const pairings = this.options.store.listPendingPairings(workspaceId)
252
- .filter((row) => row.platform === 'lark' && row.expires_at >= new Date().toISOString());
253
- const users = this.options.store.listAuthorizedUsers(workspaceId, 'lark');
302
+ .filter((row) => row.platform === platformKey && row.expires_at >= new Date().toISOString());
303
+ const users = this.options.store.listAuthorizedUsers(workspaceId, platformKey);
254
304
  return {
255
305
  workspaceId,
256
306
  platform: 'lark',
307
+ instanceId: resolved.instanceId,
308
+ displayName: binding?.displayName,
257
309
  enabled: Boolean(binding?.enabled),
258
310
  connected: Boolean(binding?.connected),
259
311
  status: binding?.status || 'disabled',
@@ -265,16 +317,20 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
265
317
  };
266
318
  }
267
319
  listStatuses() {
268
- return [...this.runtime.keys()].sort().map((workspaceId) => this.getStatus(workspaceId));
320
+ return [...this.runtime.values()]
321
+ .sort((a, b) => `${a.workspaceId}:${a.instanceId}`.localeCompare(`${b.workspaceId}:${b.instanceId}`))
322
+ .map((state) => this.getStatus(state.workspaceId, state.instanceId));
269
323
  }
270
324
  listPendingPairings(workspaceId) {
271
325
  this.options.store.expirePendingPairings();
272
326
  return this.options.store
273
- .listPairingRequests(workspaceId, 'lark')
327
+ .listPairingRequests(workspaceId)
328
+ .filter((item) => item.platform === 'lark' || item.platform.startsWith('lark:'))
274
329
  .filter((item) => item.status === 'pending' && item.expiresAt >= new Date().toISOString());
275
330
  }
276
331
  listAuthorizedUsers(workspaceId) {
277
- return this.options.store.listAuthorizedUsers(workspaceId, 'lark');
332
+ return this.options.store.listAuthorizedUsers(workspaceId)
333
+ .filter((item) => item.platform === 'lark' || item.platform.startsWith('lark:'));
278
334
  }
279
335
  async start() {
280
336
  await this.refreshBindings();
@@ -288,7 +344,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
288
344
  if (!pairing) {
289
345
  throw new Error(`Pairing code not found: ${code}`);
290
346
  }
291
- if (pairing.platform !== 'lark') {
347
+ if (pairing.platform !== 'lark' && !pairing.platform.startsWith('lark:')) {
292
348
  throw new Error(`Pairing code ${code} is not a Lark pairing`);
293
349
  }
294
350
  if (pairing.status !== 'pending') {
@@ -298,12 +354,13 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
298
354
  this.options.store.updatePairingStatus(code, 'expired');
299
355
  throw new Error(`Pairing code ${code} has expired`);
300
356
  }
301
- const existing = this.options.store.getAuthorizedUser(pairing.workspace_id, pairing.platform_user_id);
357
+ const existing = this.options.store.getAuthorizedUser(pairing.workspace_id, pairing.platform_user_id, pairing.platform);
302
358
  const userId = existing?.id || `lark-user-${(0, node_crypto_1.randomUUID)()}`;
303
359
  const authorizedAt = new Date().toISOString();
304
360
  this.options.store.createAuthorizedUser({
305
361
  id: userId,
306
362
  workspace_id: pairing.workspace_id,
363
+ platform: pairing.platform,
307
364
  platform_user_id: pairing.platform_user_id,
308
365
  chat_id: pairing.chat_id,
309
366
  display_name: pairing.display_name,
@@ -312,7 +369,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
312
369
  });
313
370
  this.options.store.updatePairingStatus(code, 'approved');
314
371
  this.notifyRuntimeStateChanged();
315
- const user = this.options.store.listAuthorizedUsers(pairing.workspace_id, 'lark').find((entry) => entry.id === userId);
372
+ const user = this.options.store.listAuthorizedUsers(pairing.workspace_id, pairing.platform).find((entry) => entry.id === userId);
316
373
  if (!user) {
317
374
  throw new Error('Authorized user lookup failed after approval');
318
375
  }
@@ -338,12 +395,14 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
338
395
  this.options.log?.(`localcore-lark bridge route miss for sessionKey=${sessionKey} type=${event.type}`);
339
396
  return;
340
397
  }
341
- const state = this.runtime.get(route.workspaceId);
398
+ const routeInstanceId = route.instanceId || 'default';
399
+ const routePlatformKey = route.platformKey || (0, config_js_1.channelPlatformKey)('lark', routeInstanceId);
400
+ const state = this.runtime.get((0, config_js_1.runtimeKey)(route.workspaceId, routeInstanceId)) || this.runtime.get(route.workspaceId);
342
401
  if (!state?.client || !state.connected) {
343
402
  this.options.log?.(`localcore-lark bridge event ignored because workspace is not connected: ${route.workspaceId}`);
344
403
  return;
345
404
  }
346
- const initialBinding = this.options.store.getPlatformThreadBinding(route.workspaceId, route.chatId, route.platformUserId);
405
+ const initialBinding = this.options.store.getPlatformThreadBinding(route.workspaceId, route.chatId, route.platformUserId, routePlatformKey);
347
406
  if (!initialBinding) {
348
407
  this.options.log?.(`localcore-lark bridge binding miss for workspace=${route.workspaceId} chat=${route.chatId} user=${route.platformUserId}`);
349
408
  return;
@@ -362,7 +421,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
362
421
  const current = previous
363
422
  .catch(() => undefined)
364
423
  .then(async () => {
365
- const binding = this.options.store.getPlatformThreadBinding(route.workspaceId, route.chatId, route.platformUserId);
424
+ const binding = this.options.store.getPlatformThreadBinding(route.workspaceId, route.chatId, route.platformUserId, routePlatformKey);
366
425
  if (!binding) {
367
426
  this.options.log?.(`localcore-lark bridge binding disappeared for sessionKey=${sessionKey}`);
368
427
  return;
@@ -391,7 +450,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
391
450
  this.options.store.clearPlatformThreadMessageId(route.workspaceId, route.chatId, route.platformUserId);
392
451
  }
393
452
  if (event.type === 'buttons') {
394
- const permissionCard = this.renderPermissionCard(turn, event, Boolean(state.cardActionsEnabled));
453
+ const permissionCard = (0, cards_js_1.renderPermissionCard)(turn, event, Boolean(state.cardActionsEnabled));
395
454
  if (permissionCard.text || permissionCard.buttonRows.length > 0) {
396
455
  if (!turn.permissionMessageId) {
397
456
  const createdId = await this.sendTextAsCard(state, route.chatId, permissionCard.text, permissionCard.buttonRows, sessionKey, binding.thread_id);
@@ -409,12 +468,12 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
409
468
  return;
410
469
  }
411
470
  this.consumeBridgeEvent(turn, event);
412
- const rendered = this.renderBridgeEventMessage(turn, event);
471
+ const rendered = (0, runtime_state_js_1.renderLarkBridgeEventMessage)(turn, event);
413
472
  if (!rendered.text && rendered.buttonRows.length === 0) {
414
473
  this.options.log?.(`localcore-lark bridge event produced empty render for sessionKey=${sessionKey} type=${event.type}`);
415
474
  return;
416
475
  }
417
- const existingMessageId = this.getRenderedMessageId(turn, rendered);
476
+ const existingMessageId = (0, runtime_state_js_1.getLarkRenderedMessageId)(turn, rendered);
418
477
  const shouldThrottle = event.type === 'update_message' &&
419
478
  rendered.isFinal &&
420
479
  existingMessageId &&
@@ -426,9 +485,9 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
426
485
  if (!existingMessageId) {
427
486
  const createdId = await this.sendTextAsCard(state, route.chatId, rendered.text, rendered.buttonRows, sessionKey, binding.thread_id);
428
487
  if (createdId) {
429
- this.setRenderedMessageId(turn, rendered, createdId);
488
+ (0, runtime_state_js_1.setLarkRenderedMessageId)(turn, rendered, createdId);
430
489
  if (rendered.isFinal) {
431
- this.options.store.updatePlatformThreadMessageId(route.workspaceId, route.chatId, route.platformUserId, createdId);
490
+ this.options.store.updatePlatformThreadMessageId(route.workspaceId, route.chatId, route.platformUserId, createdId, routePlatformKey);
432
491
  }
433
492
  this.options.log?.(`localcore-lark sent new card message ${createdId} for sessionKey=${sessionKey}`);
434
493
  }
@@ -452,6 +511,8 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
452
511
  await current;
453
512
  }
454
513
  async handleInboundMessage(input) {
514
+ const instanceId = input.instanceId || 'default';
515
+ const platformKey = input.platformKey || (0, config_js_1.channelPlatformKey)('lark', instanceId);
455
516
  this.options.eventBus.emit({
456
517
  type: 'platform.message.received',
457
518
  payload: {
@@ -465,27 +526,38 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
465
526
  },
466
527
  });
467
528
  this.options.store.expirePendingPairings();
468
- const binding = await this.getBinding(input.workspaceId);
469
- let authorized = this.options.store.getAuthorizedUser(input.workspaceId, input.platformUserId);
529
+ const runtimeState = this.resolveRuntimeState(input.workspaceId, instanceId).state;
530
+ let binding;
531
+ try {
532
+ binding = await this.getBinding(input.workspaceId, instanceId);
533
+ }
534
+ catch (error) {
535
+ if (!runtimeState) {
536
+ throw error;
537
+ }
538
+ this.options.log?.(`localcore-lark using active runtime binding snapshot for ${input.workspaceId}/${instanceId}: ${error instanceof Error ? error.message : String(error)}`);
539
+ }
540
+ let authorized = this.options.store.getAuthorizedUser(input.workspaceId, input.platformUserId, platformKey);
470
541
  if (!authorized) {
471
- if (binding.autoApprove) {
542
+ if (binding?.autoApprove || runtimeState?.autoApprove) {
472
543
  const authorizedAt = new Date().toISOString();
473
544
  this.options.store.createAuthorizedUser({
474
545
  id: `lark-user-${(0, node_crypto_1.randomUUID)()}`,
475
546
  workspace_id: input.workspaceId,
547
+ platform: platformKey,
476
548
  platform_user_id: input.platformUserId,
477
549
  chat_id: input.chatId,
478
550
  display_name: input.displayName,
479
551
  thread_id: null,
480
552
  authorized_at: authorizedAt,
481
553
  });
482
- authorized = this.options.store.getAuthorizedUser(input.workspaceId, input.platformUserId);
554
+ authorized = this.options.store.getAuthorizedUser(input.workspaceId, input.platformUserId, platformKey);
483
555
  this.options.log?.(`localcore-lark auto-approved user for ${input.workspaceId}: ${input.platformUserId}`);
484
556
  this.notifyRuntimeStateChanged();
485
557
  }
486
558
  }
487
559
  if (!authorized) {
488
- const existingPending = this.options.store.listPendingPairings(input.workspaceId).find((item) => item.platform === 'lark' && item.platform_user_id === input.platformUserId && item.chat_id === input.chatId && item.status === 'pending');
560
+ const existingPending = this.options.store.listPendingPairings(input.workspaceId).find((item) => item.platform === platformKey && item.platform_user_id === input.platformUserId && item.chat_id === input.chatId && item.status === 'pending');
489
561
  let pairingCode = existingPending?.code || '';
490
562
  if (!existingPending) {
491
563
  const now = new Date();
@@ -493,6 +565,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
493
565
  this.options.store.createPairingRequest({
494
566
  code: pairingCode,
495
567
  workspace_id: input.workspaceId,
568
+ platform: platformKey,
496
569
  platform_user_id: input.platformUserId,
497
570
  chat_id: input.chatId,
498
571
  display_name: input.displayName,
@@ -502,19 +575,20 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
502
575
  });
503
576
  this.notifyRuntimeStateChanged();
504
577
  }
505
- await this.sendImmediateCard(input.workspaceId, input.chatId, this.renderPendingPairingCard(pairingCode));
578
+ await this.sendImmediateCard(input.workspaceId, input.chatId, (0, cards_js_1.renderPendingPairingCard)(pairingCode), instanceId);
506
579
  return { paired: false };
507
580
  }
508
581
  const router = this.options.getWorkspaceRouter();
509
- const threadBinding = this.options.store.getPlatformThreadBinding(input.workspaceId, input.chatId, input.platformUserId);
582
+ const threadBinding = this.options.store.getPlatformThreadBinding(input.workspaceId, input.chatId, input.platformUserId, platformKey);
510
583
  let threadId = threadBinding?.thread_id || authorized.thread_id || '';
511
584
  if (!threadId) {
512
585
  const thread = await router.createThread(input.workspaceId, input.displayName || `Lark ${input.chatId}`);
513
586
  threadId = thread.id;
514
- this.options.store.updateAuthorizedUserThread(input.workspaceId, input.platformUserId, threadId);
587
+ this.options.store.updateAuthorizedUserThread(input.workspaceId, input.platformUserId, threadId, platformKey);
515
588
  const now = new Date().toISOString();
516
589
  this.options.store.upsertPlatformThreadBinding({
517
590
  workspace_id: input.workspaceId,
591
+ platform: platformKey,
518
592
  chat_id: input.chatId,
519
593
  platform_user_id: input.platformUserId,
520
594
  thread_id: threadId,
@@ -534,20 +608,23 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
534
608
  const effectiveSessionKey = this.options.getWorkspaceRouter().getThreadSessionKey(threadId);
535
609
  this.threadRouting.set(effectiveSessionKey, {
536
610
  workspaceId: input.workspaceId,
611
+ instanceId,
612
+ platformKey,
537
613
  platformUserId: input.platformUserId,
538
614
  chatId: input.chatId,
539
615
  threadId,
540
616
  });
541
617
  const acknowledgement = this.createTurnState(effectiveSessionKey, input.messageId);
542
- await this.addAcknowledgementReaction(input.workspaceId, input.messageId, acknowledgement);
618
+ await this.addAcknowledgementReaction(input.workspaceId, input.messageId, acknowledgement, instanceId);
543
619
  const slashCommand = this.parseSlashCommand(input.text);
544
620
  if (slashCommand?.name === 'new') {
545
621
  const title = slashCommand.args.join(' ').trim() || `${input.displayName || 'Lark'} ${new Date().toLocaleTimeString()}`;
546
622
  const nextThread = await router.createThread(input.workspaceId, title);
547
623
  const now = new Date().toISOString();
548
- this.options.store.updateAuthorizedUserThread(input.workspaceId, input.platformUserId, nextThread.id);
624
+ this.options.store.updateAuthorizedUserThread(input.workspaceId, input.platformUserId, nextThread.id, platformKey);
549
625
  this.options.store.upsertPlatformThreadBinding({
550
626
  workspace_id: input.workspaceId,
627
+ platform: platformKey,
551
628
  chat_id: input.chatId,
552
629
  platform_user_id: input.platformUserId,
553
630
  thread_id: nextThread.id,
@@ -557,11 +634,13 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
557
634
  });
558
635
  this.threadRouting.set(this.options.getWorkspaceRouter().getThreadSessionKey(nextThread.id), {
559
636
  workspaceId: input.workspaceId,
637
+ instanceId,
638
+ platformKey,
560
639
  platformUserId: input.platformUserId,
561
640
  chatId: input.chatId,
562
641
  threadId: nextThread.id,
563
642
  });
564
- await this.sendImmediateCard(input.workspaceId, input.chatId, '**已开始新会话**');
643
+ await this.sendImmediateCard(input.workspaceId, input.chatId, '**已开始新会话**', instanceId);
565
644
  return { paired: true, threadId: nextThread.id };
566
645
  }
567
646
  const latestRun = this.options.store.getLatestRunForThread(threadId);
@@ -572,17 +651,29 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
572
651
  }
573
652
  this.options.store.clearPlatformThreadMessageId(input.workspaceId, input.chatId, input.platformUserId);
574
653
  const wrappedText = (0, desktop_js_1.wrapUserMessageWithSchedulerProtocol)(input.text);
575
- await router.sendThreadMessage(threadId, this.createThreadMessageInput(wrappedText, input.contentParts));
654
+ await router.sendThreadMessage(threadId, (0, content_js_1.createChannelThreadMessageInput)(wrappedText, input.contentParts));
576
655
  return { paired: true, threadId };
577
656
  }
578
657
  close() {
579
- return Promise.all([...this.runtime.keys()].map((workspaceId) => this.stopWorkspace(workspaceId))).then(() => undefined);
658
+ return Promise.all([...this.runtime.keys()].map((key) => this.stopWorkspaceKey(key))).then(() => undefined);
580
659
  }
581
- async getBinding(workspaceId) {
660
+ resolveRuntimeState(workspaceId, instanceId) {
661
+ if (instanceId) {
662
+ const state = this.runtime.get((0, config_js_1.runtimeKey)(workspaceId, instanceId)) || (instanceId === 'default' ? this.runtime.get(workspaceId) : undefined);
663
+ return { instanceId, state };
664
+ }
665
+ const states = [...this.runtime.values()].filter((entry) => entry.workspaceId === workspaceId);
666
+ const state = states.find((entry) => entry.instanceId === 'default') || states[0];
667
+ return { instanceId: state?.instanceId || 'default', state: state || this.runtime.get(workspaceId) };
668
+ }
669
+ async getBinding(workspaceId, instanceId) {
582
670
  const config = await this.options.readConfig();
583
- const binding = this.collectBindings(config).find((entry) => entry.workspaceId === workspaceId);
671
+ const bindings = this.collectBindings(config).filter((entry) => entry.workspaceId === workspaceId);
672
+ const binding = instanceId
673
+ ? bindings.find((entry) => entry.instanceId === instanceId)
674
+ : bindings.find((entry) => entry.instanceId === 'default') || bindings[0];
584
675
  if (!binding) {
585
- throw new Error(`No Lark binding configured for workspace "${workspaceId}"`);
676
+ throw new Error(`No Lark binding configured for workspace "${workspaceId}"${instanceId ? ` instance "${instanceId}"` : ''}`);
586
677
  }
587
678
  return binding;
588
679
  }
@@ -600,6 +691,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
600
691
  success: true,
601
692
  platform: 'lark',
602
693
  workspaceId: binding.workspaceId,
694
+ instanceId: binding.instanceId,
603
695
  appId: binding.appId,
604
696
  };
605
697
  }
@@ -608,22 +700,28 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
608
700
  success: false,
609
701
  platform: 'lark',
610
702
  workspaceId: binding.workspaceId,
703
+ instanceId: binding.instanceId,
611
704
  appId: binding.appId,
612
705
  error: error instanceof Error ? error.message : String(error),
613
706
  };
614
707
  }
615
708
  }
616
709
  async startWorkspace(binding) {
617
- await this.stopWorkspace(binding.workspaceId);
710
+ const key = (0, config_js_1.runtimeKey)(binding.workspaceId, binding.instanceId);
711
+ await this.stopWorkspaceKey(key);
618
712
  const status = {
619
713
  workspaceId: binding.workspaceId,
714
+ instanceId: binding.instanceId,
715
+ displayName: binding.displayName,
716
+ platformKey: binding.platformKey,
620
717
  enabled: true,
621
718
  status: 'starting',
622
719
  connected: false,
623
720
  appId: binding.appId,
721
+ autoApprove: binding.autoApprove,
624
722
  cardActionsEnabled: binding.cardActionsEnabled,
625
723
  };
626
- this.runtime.set(binding.workspaceId, status);
724
+ this.runtime.set(key, status);
627
725
  this.notifyRuntimeStateChanged();
628
726
  try {
629
727
  this.options.log?.(`localcore-lark starting workspace=${binding.workspaceId} app=${this.maskLarkAppId(binding.appId)} cardActions=${binding.cardActionsEnabled}`);
@@ -641,15 +739,15 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
641
739
  });
642
740
  status.eventDispatcher.register({
643
741
  'im.message.receive_v1': async (data) => {
644
- this.options.log?.(`localcore-lark received im.message.receive_v1 for ${binding.workspaceId}: ${this.summarizeLarkEvent(data)}`);
645
- void this.handleMessageEvent(binding.workspaceId, data).catch((error) => {
742
+ this.options.log?.(`localcore-lark received im.message.receive_v1 for ${binding.workspaceId}/${binding.instanceId}: ${this.summarizeLarkEvent(data)}`);
743
+ void this.handleMessageEvent(binding.workspaceId, binding.instanceId, binding.platformKey, data).catch((error) => {
646
744
  this.options.log?.(`localcore-lark inbound message failed for ${binding.workspaceId}: ${error instanceof Error ? error.message : String(error)}`);
647
745
  });
648
746
  return {};
649
747
  },
650
748
  'card.action.trigger': async (data) => {
651
- this.options.log?.(`localcore-lark received card.action.trigger for ${binding.workspaceId}`);
652
- void this.handleCardActionEvent(binding.workspaceId, data);
749
+ this.options.log?.(`localcore-lark received card.action.trigger for ${binding.workspaceId}/${binding.instanceId}`);
750
+ void this.handleCardActionEvent(binding.workspaceId, binding.instanceId, data);
653
751
  return {};
654
752
  },
655
753
  });
@@ -679,26 +777,34 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
679
777
  }
680
778
  this.notifyRuntimeStateChanged();
681
779
  }
682
- async stopWorkspace(workspaceId) {
683
- const state = this.runtime.get(workspaceId);
780
+ async stopWorkspace(workspaceId, instanceId) {
781
+ const resolved = this.resolveRuntimeState(workspaceId, instanceId);
782
+ await this.stopWorkspaceKey((0, config_js_1.runtimeKey)(workspaceId, resolved.instanceId));
783
+ }
784
+ async stopWorkspaceKey(key) {
785
+ const state = this.runtime.get(key);
684
786
  if (!state) {
685
787
  return;
686
788
  }
687
- this.options.log?.(`localcore-lark stopping workspace=${workspaceId} status=${state.status}`);
789
+ this.options.log?.(`localcore-lark stopping workspace=${state.workspaceId}/${state.instanceId} status=${state.status}`);
688
790
  try {
689
791
  await state.wsClient?.stop?.();
690
- this.options.log?.(`localcore-lark stopped workspace=${workspaceId}`);
792
+ this.options.log?.(`localcore-lark stopped workspace=${state.workspaceId}/${state.instanceId}`);
691
793
  }
692
794
  catch (error) {
693
- this.options.log?.(`localcore-lark stop failed for ${workspaceId}: ${error instanceof Error ? error.message : String(error)}`);
795
+ this.options.log?.(`localcore-lark stop failed for ${state.workspaceId}/${state.instanceId}: ${error instanceof Error ? error.message : String(error)}`);
694
796
  // Best effort: current SDK versions may not implement stop().
695
797
  }
696
- this.runtime.set(workspaceId, {
697
- workspaceId,
798
+ this.runtime.set(key, {
799
+ workspaceId: state.workspaceId,
800
+ instanceId: state.instanceId,
801
+ displayName: state.displayName,
802
+ platformKey: state.platformKey,
698
803
  enabled: false,
699
804
  status: 'stopped',
700
805
  connected: false,
701
806
  appId: state.appId,
807
+ autoApprove: state.autoApprove,
702
808
  cardActionsEnabled: state.cardActionsEnabled,
703
809
  });
704
810
  this.notifyRuntimeStateChanged();
@@ -711,7 +817,11 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
711
817
  },
712
818
  });
713
819
  }
714
- async handleMessageEvent(workspaceId, data) {
820
+ async handleMessageEvent(workspaceId, instanceIdOrData, platformKeyOrData, maybeData) {
821
+ const legacyCall = typeof instanceIdOrData === 'object';
822
+ const instanceId = legacyCall ? 'default' : instanceIdOrData;
823
+ const platformKey = legacyCall ? 'lark' : String(platformKeyOrData || (0, config_js_1.channelPlatformKey)('lark', instanceId));
824
+ const data = (legacyCall ? instanceIdOrData : maybeData) || {};
715
825
  const payload = (data?.event && typeof data.event === 'object')
716
826
  ? data.event
717
827
  : data;
@@ -739,7 +849,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
739
849
  const imageKey = String(parsedContent.image_key || parsedContent.file_key || '').trim();
740
850
  if (imageKey) {
741
851
  try {
742
- contentParts.push(await this.downloadMessageImage(workspaceId, String(message.message_id || ''), imageKey));
852
+ contentParts.push(await this.downloadMessageImage(workspaceId, String(message.message_id || ''), imageKey, instanceId));
743
853
  }
744
854
  catch (error) {
745
855
  const errorText = `[Image download failed: ${error instanceof Error ? error.message : String(error)}]`;
@@ -748,15 +858,24 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
748
858
  }
749
859
  }
750
860
  }
861
+ if (messageType === 'file') {
862
+ const fileKey = String(parsedContent.file_key || '').trim();
863
+ if (fileKey) {
864
+ try {
865
+ contentParts.push(await this.downloadMessageFile(workspaceId, String(message.message_id || ''), fileKey, String(parsedContent.file_name || parsedContent.name || '').trim(), instanceId));
866
+ }
867
+ catch (error) {
868
+ const errorText = `[File download failed: ${error instanceof Error ? error.message : String(error)}]`;
869
+ contentParts.push({ type: 'text', text: errorText });
870
+ this.options.log?.(`localcore-lark file download failed for ${workspaceId}: message=${String(message.message_id || '')} fileKey=${fileKey} error=${error instanceof Error ? error.message : String(error)}`);
871
+ }
872
+ }
873
+ }
751
874
  if (contentParts.length === 0) {
752
875
  this.options.log?.(`localcore-lark ignored unsupported message for ${workspaceId}: type=${String(message.message_type || 'unknown')} contentKeys=${JSON.stringify(Object.keys(parsedContent))}`);
753
876
  return;
754
877
  }
755
- const displayText = text || (contentParts.some((part) => part.type === 'image') ? '[Image]' : contentParts
756
- .filter((part) => part.type === 'text')
757
- .map((part) => part.text)
758
- .join('\n\n')
759
- .trim());
878
+ const displayText = text || this.summarizeInboundContentParts(contentParts);
760
879
  const platformUserId = String(sender.sender_id?.user_id || sender.sender_id?.open_id || '').trim();
761
880
  const chatId = String(message.chat_id || platformUserId).trim();
762
881
  if (!platformUserId || !chatId) {
@@ -766,6 +885,8 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
766
885
  this.options.log?.(`localcore-lark inbound message for ${workspaceId}: chat=${chatId} user=${platformUserId} type=${messageType || 'unknown'} text=${JSON.stringify(displayText.slice(0, 120))}`);
767
886
  await this.handleInboundMessage({
768
887
  workspaceId,
888
+ instanceId,
889
+ platformKey,
769
890
  platformUserId,
770
891
  chatId,
771
892
  displayName: String(payload?.sender?.sender_id?.user_id ||
@@ -776,27 +897,8 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
776
897
  contentParts,
777
898
  });
778
899
  }
779
- createWrappedContentParts(wrappedText, parts) {
780
- const nonTextParts = Array.isArray(parts)
781
- ? parts.filter((part) => part.type !== 'text')
782
- : [];
783
- return [
784
- { type: 'text', text: wrappedText },
785
- ...nonTextParts,
786
- ];
787
- }
788
- createThreadMessageInput(wrappedText, parts) {
789
- const hasNonTextPart = Array.isArray(parts) && parts.some((part) => part.type !== 'text');
790
- if (!hasNonTextPart) {
791
- return wrappedText;
792
- }
793
- return {
794
- displayText: wrappedText,
795
- contentParts: this.createWrappedContentParts(wrappedText, parts),
796
- };
797
- }
798
- async downloadMessageImage(workspaceId, messageId, imageKey) {
799
- const state = this.runtime.get(workspaceId);
900
+ async downloadMessageImage(workspaceId, messageId, imageKey, instanceId) {
901
+ const state = this.resolveRuntimeState(workspaceId, instanceId).state;
800
902
  if (!state?.client) {
801
903
  throw new Error('Lark client is not connected');
802
904
  }
@@ -824,6 +926,58 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
824
926
  fileName: `${imageKey}.${this.sniffImageExtension(buffer)}`,
825
927
  };
826
928
  }
929
+ async downloadMessageFile(workspaceId, messageId, fileKey, fileName, instanceId) {
930
+ const state = this.resolveRuntimeState(workspaceId, instanceId).state;
931
+ if (!state?.client) {
932
+ throw new Error('Lark client is not connected');
933
+ }
934
+ if (!messageId) {
935
+ throw new Error('Lark file message is missing message_id');
936
+ }
937
+ const resource = await state.client.im.messageResource.get({
938
+ path: {
939
+ message_id: messageId,
940
+ file_key: fileKey,
941
+ },
942
+ params: {
943
+ type: 'file',
944
+ },
945
+ });
946
+ const buffer = await this.readLarkResourceBuffer(resource);
947
+ if (buffer.length === 0) {
948
+ throw new Error('Lark file resource is empty');
949
+ }
950
+ return {
951
+ type: 'file',
952
+ data: buffer.toString('base64'),
953
+ mimeType: this.extractHeaderMimeType(resource?.headers) || 'application/octet-stream',
954
+ fileName: fileName || fileKey,
955
+ size: buffer.length,
956
+ };
957
+ }
958
+ summarizeInboundContentParts(parts) {
959
+ const text = parts
960
+ .filter((part) => part.type === 'text')
961
+ .map((part) => part.text)
962
+ .filter(Boolean)
963
+ .join('\n\n')
964
+ .trim();
965
+ if (text)
966
+ return text;
967
+ const attachmentSummary = parts
968
+ .map((part) => {
969
+ if (part.type === 'image') {
970
+ return '[Image]';
971
+ }
972
+ if (part.type === 'file') {
973
+ return part.fileName ? `[File: ${part.fileName}]` : '[File]';
974
+ }
975
+ return '';
976
+ })
977
+ .filter(Boolean)
978
+ .join('\n');
979
+ return attachmentSummary;
980
+ }
827
981
  async readLarkResourceBuffer(resource) {
828
982
  const readable = resource?.getReadableStream?.();
829
983
  if (!readable || typeof readable.on !== 'function') {
@@ -930,32 +1084,8 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
930
1084
  return `${value.slice(0, 6)}...${value.slice(-4)}`;
931
1085
  }
932
1086
  collectBindings(config) {
933
- 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
- }));
1087
+ return (0, config_js_1.collectLarkWorkspaceBindings)(config, {
1088
+ defaultCardActionsEnabled: this.defaultCardActionsEnabled,
959
1089
  });
960
1090
  }
961
1091
  async getLarkModule() {
@@ -964,40 +1094,6 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
964
1094
  }
965
1095
  return this.larkModulePromise;
966
1096
  }
967
- buildInteractiveCard(text, buttonRows = [], sessionKey, threadId) {
968
- const elements = [];
969
- if (text) {
970
- elements.push({ tag: 'markdown', content: text });
971
- }
972
- for (const row of buttonRows) {
973
- const actions = row
974
- .filter((button) => button.text && button.data)
975
- .map((button, index) => ({
976
- tag: 'button',
977
- text: {
978
- tag: 'plain_text',
979
- content: this.formatPermissionButtonLabel(button),
980
- },
981
- type: this.resolveLarkButtonType(button, index),
982
- value: {
983
- action: 'permission_response',
984
- response: (0, desktop_js_1.normalizePermissionResponse)(button.data) || button.data,
985
- session_key: sessionKey || '',
986
- thread_id: threadId || '',
987
- },
988
- }));
989
- if (actions.length) {
990
- elements.push({
991
- tag: 'action',
992
- actions,
993
- });
994
- }
995
- }
996
- return {
997
- config: { wide_screen_mode: true },
998
- elements,
999
- };
1000
- }
1001
1097
  async sendTextAsCard(state, chatId, text, buttonRows = [], sessionKey, threadId) {
1002
1098
  const response = await state.client.im.message.create({
1003
1099
  params: {
@@ -1006,7 +1102,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
1006
1102
  data: {
1007
1103
  receive_id: chatId,
1008
1104
  msg_type: 'interactive',
1009
- content: JSON.stringify(this.buildInteractiveCard(text, buttonRows, sessionKey, threadId)),
1105
+ content: JSON.stringify((0, cards_js_1.buildInteractiveCard)(text, buttonRows, sessionKey, threadId)),
1010
1106
  },
1011
1107
  });
1012
1108
  return String(response?.data?.message_id || '').trim();
@@ -1044,7 +1140,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
1044
1140
  message_id: messageId,
1045
1141
  },
1046
1142
  data: {
1047
- content: JSON.stringify(this.buildInteractiveCard(text, buttonRows, sessionKey, threadId)),
1143
+ content: JSON.stringify((0, cards_js_1.buildInteractiveCard)(text, buttonRows, sessionKey, threadId)),
1048
1144
  },
1049
1145
  });
1050
1146
  }
@@ -1080,21 +1176,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
1080
1176
  };
1081
1177
  }
1082
1178
  createTurnState(sessionKey, sourceMessageId) {
1083
- const turn = {
1084
- sessionKey,
1085
- sourceMessageId,
1086
- awaitingPermission: false,
1087
- processing: false,
1088
- previewText: '',
1089
- finalText: '',
1090
- progressMessageIds: {},
1091
- thinkingSteps: [],
1092
- toolCalls: [],
1093
- statusLines: [],
1094
- buttonRows: [],
1095
- lastPatchedAt: 0,
1096
- lastPatchedAtByMessageId: {},
1097
- };
1179
+ const turn = (0, runtime_state_js_1.createLarkTurnState)(sessionKey, sourceMessageId);
1098
1180
  this.outboundTurns.set(sessionKey, turn);
1099
1181
  return turn;
1100
1182
  }
@@ -1115,209 +1197,36 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
1115
1197
  return turn;
1116
1198
  }
1117
1199
  consumeBridgeEvent(turn, event) {
1118
- 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');
1200
+ (0, runtime_state_js_1.consumeLarkBridgeEvent)(turn, event, {
1201
+ mirrorPermissionStateInMainCard: this.mirrorPermissionStateInMainCard,
1202
+ });
1289
1203
  }
1290
- async handleCardActionEvent(workspaceId, data) {
1204
+ async handleCardActionEvent(workspaceId, instanceIdOrData, maybeData) {
1205
+ const legacyCall = typeof instanceIdOrData === 'object';
1206
+ const instanceId = legacyCall ? 'default' : instanceIdOrData;
1207
+ const data = (legacyCall ? instanceIdOrData : maybeData) || {};
1291
1208
  try {
1292
- const 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) {
1209
+ const action = (0, cards_js_1.extractCardActionValue)(data);
1210
+ if (!action) {
1302
1211
  return;
1303
1212
  }
1304
1213
  const router = this.options.getWorkspaceRouter();
1305
- await 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}`);
1214
+ await this.markPermissionCardActionHandled(workspaceId, instanceId, data, action.event, action.value, action.response, action.threadId);
1215
+ await router.sendThreadAction(action.threadId, action.response);
1216
+ this.options.log?.(`localcore-lark processed card action for ${workspaceId}/${instanceId}: ${action.response}`);
1308
1217
  }
1309
1218
  catch (error) {
1310
- this.options.log?.(`localcore-lark card action failed for ${workspaceId}: ${error instanceof Error ? error.message : String(error)}`);
1219
+ this.options.log?.(`localcore-lark card action failed for ${workspaceId}/${instanceId}: ${error instanceof Error ? error.message : String(error)}`);
1311
1220
  }
1312
1221
  }
1313
- async markPermissionCardActionHandled(workspaceId, rootPayload, payload, value, response, threadId) {
1314
- const state = this.runtime.get(workspaceId);
1222
+ async markPermissionCardActionHandled(workspaceId, instanceId, rootPayload, payload, value, response, threadId) {
1223
+ const state = this.resolveRuntimeState(workspaceId, instanceId).state;
1315
1224
  if (!state?.client) {
1316
1225
  return;
1317
1226
  }
1318
1227
  const sessionKey = String(value.session_key || '').trim();
1319
1228
  const turn = sessionKey ? this.outboundTurns.get(sessionKey) : undefined;
1320
- const messageId = this.extractCardActionMessageId(rootPayload, payload, value) || turn?.permissionMessageId;
1229
+ const messageId = (0, cards_js_1.extractCardActionMessageId)(rootPayload, payload, value) || turn?.permissionMessageId;
1321
1230
  if (!messageId) {
1322
1231
  this.options.log?.(`localcore-lark could not find permission card message id for workspace=${workspaceId} sessionKey=${sessionKey || 'missing'}`);
1323
1232
  return;
@@ -1325,7 +1234,7 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
1325
1234
  try {
1326
1235
  await this.patchTextCard(state, messageId, [
1327
1236
  '**工具确认已处理**',
1328
- `已选择:${this.formatPermissionResponseLabel(response)}`,
1237
+ `已选择:${(0, cards_js_1.formatPermissionResponseLabel)(response)}`,
1329
1238
  '等待代理继续执行...',
1330
1239
  ].join('\n\n'), [], sessionKey || undefined, threadId);
1331
1240
  if (turn) {
@@ -1337,133 +1246,8 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
1337
1246
  this.options.log?.(`localcore-lark failed to patch handled permission card ${messageId}: ${error instanceof Error ? error.message : String(error)}`);
1338
1247
  }
1339
1248
  }
1340
- 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);
1249
+ async sendImmediateCard(workspaceId, chatId, text, instanceId) {
1250
+ const state = this.resolveRuntimeState(workspaceId, instanceId).state;
1467
1251
  if (!state?.client || !state.connected) {
1468
1252
  this.options.log?.(`localcore-lark immediate card skipped because workspace is not connected: ${workspaceId}`);
1469
1253
  return '';
@@ -1476,8 +1260,8 @@ class LocalCoreLarkGateway extends node_events_1.EventEmitter {
1476
1260
  return '';
1477
1261
  }
1478
1262
  }
1479
- async addAcknowledgementReaction(workspaceId, messageId, turn) {
1480
- const state = this.runtime.get(workspaceId);
1263
+ async addAcknowledgementReaction(workspaceId, messageId, turn, instanceId) {
1264
+ const state = this.resolveRuntimeState(workspaceId, instanceId).state;
1481
1265
  if (!state?.client || !state.connected || !messageId) {
1482
1266
  return;
1483
1267
  }