@yu_robotics/remote-cli-router 1.0.1 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/binding/BindingManager.d.ts +28 -16
- package/dist/binding/BindingManager.d.ts.map +1 -1
- package/dist/binding/BindingManager.js +117 -26
- package/dist/binding/BindingManager.js.map +1 -1
- package/dist/feishu/FeishuLongConnHandler.d.ts +74 -1
- package/dist/feishu/FeishuLongConnHandler.d.ts.map +1 -1
- package/dist/feishu/FeishuLongConnHandler.js +600 -30
- package/dist/feishu/FeishuLongConnHandler.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +4 -3
- package/dist/server.js.map +1 -1
- package/dist/storage/JsonStore.d.ts +18 -18
- package/dist/storage/JsonStore.d.ts.map +1 -1
- package/dist/storage/JsonStore.js +82 -29
- package/dist/storage/JsonStore.js.map +1 -1
- package/dist/storage/MemoryStore.d.ts +6 -6
- package/dist/storage/MemoryStore.d.ts.map +1 -1
- package/dist/storage/MemoryStore.js +24 -14
- package/dist/storage/MemoryStore.js.map +1 -1
- package/dist/types/index.d.ts +17 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/ToolFormatter.d.ts +2 -2
- package/dist/utils/ToolFormatter.d.ts.map +1 -1
- package/dist/utils/ToolFormatter.js +67 -17
- package/dist/utils/ToolFormatter.js.map +1 -1
- package/package.json +1 -1
|
@@ -49,8 +49,14 @@ class FeishuLongConnHandler {
|
|
|
49
49
|
connectionHub = null;
|
|
50
50
|
appId;
|
|
51
51
|
appSecret;
|
|
52
|
-
// Feishu message size limit (4000 chars per message)
|
|
52
|
+
// Feishu message size limit (4000 chars per message) - DEPRECATED for Card 2.0
|
|
53
53
|
FEISHU_MESSAGE_LIMIT = 4000;
|
|
54
|
+
// Feishu Card 2.0 limits (from official docs)
|
|
55
|
+
// Note: Using conservative limits - official says 200, we use 150 with proper recursive counting
|
|
56
|
+
// The 150 limit leaves a 50-node safety buffer while still being practical
|
|
57
|
+
CARD_ELEMENT_LIMIT = 150; // Conservative: 150 tagged nodes per card (official: 200)
|
|
58
|
+
CARD_DATA_SIZE_LIMIT = 3000000; // Max 3MB (3,000,000 chars) for data field
|
|
59
|
+
CARD_SIZE_BUFFER = 100000; // Safety buffer: use 2.9MB instead of 3MB
|
|
54
60
|
// Track message chains: messageId -> [messageId1, messageId2, ...]
|
|
55
61
|
messageChains = new Map();
|
|
56
62
|
// Track the last processed text length for each message chain
|
|
@@ -157,6 +163,9 @@ class FeishuLongConnHandler {
|
|
|
157
163
|
case '/unbind':
|
|
158
164
|
await this.handleUnbindCommand(openId, messageId);
|
|
159
165
|
break;
|
|
166
|
+
case '/device':
|
|
167
|
+
await this.handleDeviceCommand(openId, messageId, parts.slice(1));
|
|
168
|
+
break;
|
|
160
169
|
case '/help':
|
|
161
170
|
await this.handleHelpCommand(openId, messageId);
|
|
162
171
|
break;
|
|
@@ -178,14 +187,20 @@ class FeishuLongConnHandler {
|
|
|
178
187
|
await this.replyToMessage(messageId, '❌ You have not bound a device yet, please send /bind <binding-code> to bind first');
|
|
179
188
|
return;
|
|
180
189
|
}
|
|
190
|
+
// Get active device
|
|
191
|
+
const activeDevice = await this.bindingManager.getActiveDevice(openId);
|
|
192
|
+
if (!activeDevice) {
|
|
193
|
+
await this.replyToMessage(messageId, '❌ No active device found. Please use /device list to view your devices');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
181
196
|
// Check if ConnectionHub is available
|
|
182
197
|
if (!this.connectionHub) {
|
|
183
198
|
await this.replyToMessage(messageId, '❌ Server error: ConnectionHub not initialized');
|
|
184
199
|
return;
|
|
185
200
|
}
|
|
186
201
|
// Check if device is online
|
|
187
|
-
if (!this.connectionHub.isDeviceOnline(
|
|
188
|
-
await this.replyToMessage(messageId, `❌ Device ${
|
|
202
|
+
if (!this.connectionHub.isDeviceOnline(activeDevice.deviceId)) {
|
|
203
|
+
await this.replyToMessage(messageId, `❌ Device ${activeDevice.deviceName} is currently offline, please ensure the device is started and connected to the server`);
|
|
189
204
|
return;
|
|
190
205
|
}
|
|
191
206
|
console.log(`[FeishuHandler] Passing through slash command: ${command}`);
|
|
@@ -195,10 +210,10 @@ class FeishuLongConnHandler {
|
|
|
195
210
|
const feishuMessageId = await this.sendStreamingStart(openId, `🤔 Executing ${command}...`);
|
|
196
211
|
console.log(`[FeishuHandler] Created card ${feishuMessageId} for slash command ${commandMessageId}`);
|
|
197
212
|
if (this.onStartStreaming) {
|
|
198
|
-
this.onStartStreaming(commandMessageId, openId, feishuMessageId,
|
|
213
|
+
this.onStartStreaming(commandMessageId, openId, feishuMessageId, activeDevice.deviceId);
|
|
199
214
|
}
|
|
200
215
|
// Send slash command to device - the client will execute it locally
|
|
201
|
-
const success = await this.connectionHub.sendToDevice(
|
|
216
|
+
const success = await this.connectionHub.sendToDevice(activeDevice.deviceId, {
|
|
202
217
|
type: types_1.MessageType.COMMAND,
|
|
203
218
|
messageId: commandMessageId,
|
|
204
219
|
timestamp: Date.now(),
|
|
@@ -226,10 +241,21 @@ class FeishuLongConnHandler {
|
|
|
226
241
|
await this.replyToMessage(messageId, '❌ Binding code is invalid or expired, please generate a new one');
|
|
227
242
|
return;
|
|
228
243
|
}
|
|
244
|
+
// Check if device is already bound
|
|
245
|
+
const existingDevices = await this.bindingManager.getUserDevices(openId);
|
|
246
|
+
const alreadyBound = existingDevices.some(d => d.deviceId === bindingCode.deviceId);
|
|
247
|
+
if (alreadyBound) {
|
|
248
|
+
await this.replyToMessage(messageId, '❌ This device is already bound to your account');
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
229
251
|
// Bind user
|
|
230
252
|
const deviceName = 'Device'; // Will be updated by client later
|
|
231
253
|
await this.bindingManager.bindUser(openId, bindingCode.deviceId, deviceName);
|
|
232
|
-
|
|
254
|
+
const isFirstDevice = existingDevices.length === 0;
|
|
255
|
+
const statusNote = isFirstDevice
|
|
256
|
+
? '\n\n📱 This is your first device and will be set as active.'
|
|
257
|
+
: '\n\n📱 Use /device switch to activate this device.';
|
|
258
|
+
await this.replyToMessage(messageId, `✅ Binding successful!\n\nDevice ID: ${bindingCode.deviceId}\n\nYou can now control your device through Feishu.${statusNote}\n\nUse /device list to view all your devices.`);
|
|
233
259
|
}
|
|
234
260
|
catch (error) {
|
|
235
261
|
console.error('Error binding user:', error);
|
|
@@ -246,9 +272,24 @@ class FeishuLongConnHandler {
|
|
|
246
272
|
await this.replyToMessage(messageId, '❌ You have not bound a device yet, please send /bind <binding-code> to bind first');
|
|
247
273
|
return;
|
|
248
274
|
}
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
275
|
+
const devices = binding.devices;
|
|
276
|
+
if (devices.length === 0) {
|
|
277
|
+
await this.replyToMessage(messageId, '❌ No devices found');
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
// Build status message
|
|
281
|
+
let message = `📊 Device Status\n\n`;
|
|
282
|
+
for (const device of devices) {
|
|
283
|
+
const isOnline = this.connectionHub?.isDeviceOnline(device.deviceId) || false;
|
|
284
|
+
const status = isOnline ? '🟢 Online' : '🔴 Offline';
|
|
285
|
+
const activeIndicator = device.isActive ? ' ⭐ ACTIVE' : '';
|
|
286
|
+
message += `\n**${device.deviceName}**${activeIndicator}\n`;
|
|
287
|
+
message += `Device ID: ${device.deviceId}\n`;
|
|
288
|
+
message += `Status: ${status}\n`;
|
|
289
|
+
message += `Bound: ${new Date(device.boundAt).toLocaleString('en-US')}\n`;
|
|
290
|
+
message += `Last Active: ${new Date(device.lastActiveAt).toLocaleString('en-US')}\n`;
|
|
291
|
+
}
|
|
292
|
+
message += `\n\nTotal Devices: ${devices.length}`;
|
|
252
293
|
await this.replyToMessage(messageId, message);
|
|
253
294
|
}
|
|
254
295
|
catch (error) {
|
|
@@ -258,6 +299,8 @@ class FeishuLongConnHandler {
|
|
|
258
299
|
}
|
|
259
300
|
/**
|
|
260
301
|
* Handle unbind command
|
|
302
|
+
* Usage: /unbind or /unbind all (unbind all devices)
|
|
303
|
+
* For unbinding specific device, use /device unbind <device-id>
|
|
261
304
|
*/
|
|
262
305
|
async handleUnbindCommand(openId, messageId) {
|
|
263
306
|
try {
|
|
@@ -266,14 +309,188 @@ class FeishuLongConnHandler {
|
|
|
266
309
|
await this.replyToMessage(messageId, '❌ You have not bound a device yet');
|
|
267
310
|
return;
|
|
268
311
|
}
|
|
312
|
+
const deviceCount = binding.devices.length;
|
|
313
|
+
// Unbind all devices
|
|
269
314
|
await this.bindingManager.unbindUser(openId);
|
|
270
|
-
await this.replyToMessage(messageId, `✅
|
|
315
|
+
await this.replyToMessage(messageId, `✅ Successfully unbound ${deviceCount} device(s)`);
|
|
271
316
|
}
|
|
272
317
|
catch (error) {
|
|
273
318
|
console.error('Error handling unbind command:', error);
|
|
274
319
|
await this.replyToMessage(messageId, '❌ Unbinding failed, please try again later');
|
|
275
320
|
}
|
|
276
321
|
}
|
|
322
|
+
/**
|
|
323
|
+
* Handle device command
|
|
324
|
+
* Usage:
|
|
325
|
+
* /device - List all bound devices (same as /device list)
|
|
326
|
+
* /device list - List all bound devices
|
|
327
|
+
* /device switch <device-id|index> - Switch active device (by ID or index number)
|
|
328
|
+
* /device <device-id|index> - Quick switch to device (by ID or index number)
|
|
329
|
+
* /device unbind <device-id|index> - Unbind a specific device (by ID or index number)
|
|
330
|
+
*/
|
|
331
|
+
async handleDeviceCommand(openId, messageId, args) {
|
|
332
|
+
try {
|
|
333
|
+
const binding = await this.bindingManager.getUserBinding(openId);
|
|
334
|
+
if (!binding) {
|
|
335
|
+
await this.replyToMessage(messageId, '❌ You have not bound a device yet, please send /bind <binding-code> to bind first');
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
// No args - show device list
|
|
339
|
+
if (args.length === 0) {
|
|
340
|
+
await this.handleDeviceList(openId, messageId, binding);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const subcommand = args[0]?.toLowerCase();
|
|
344
|
+
switch (subcommand) {
|
|
345
|
+
case 'list':
|
|
346
|
+
await this.handleDeviceList(openId, messageId, binding);
|
|
347
|
+
break;
|
|
348
|
+
case 'switch':
|
|
349
|
+
if (args.length < 2) {
|
|
350
|
+
await this.replyToMessage(messageId, '❌ Please provide device ID or index, format: /device switch <device-id-or-index>');
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
await this.handleDeviceSwitch(openId, messageId, args[1], binding);
|
|
354
|
+
break;
|
|
355
|
+
case 'unbind':
|
|
356
|
+
if (args.length < 2) {
|
|
357
|
+
await this.replyToMessage(messageId, '❌ Please provide device ID or index, format: /device unbind <device-id-or-index>');
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
await this.handleDeviceUnbind(openId, messageId, args[1], binding);
|
|
361
|
+
break;
|
|
362
|
+
default:
|
|
363
|
+
// If the argument looks like a number (index) or device ID, treat it as a quick switch
|
|
364
|
+
await this.handleDeviceSwitch(openId, messageId, args[0], binding);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
console.error('Error handling device command:', error);
|
|
369
|
+
await this.replyToMessage(messageId, '❌ Error processing device command, please try again later');
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Handle /device list
|
|
374
|
+
*/
|
|
375
|
+
async handleDeviceList(openId, messageId, binding) {
|
|
376
|
+
const devices = binding.devices;
|
|
377
|
+
if (devices.length === 0) {
|
|
378
|
+
await this.replyToMessage(messageId, '❌ No devices found');
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
let message = `📱 Your Devices (${devices.length})\n\n`;
|
|
382
|
+
for (let i = 0; i < devices.length; i++) {
|
|
383
|
+
const device = devices[i];
|
|
384
|
+
const isOnline = this.connectionHub?.isDeviceOnline(device.deviceId) || false;
|
|
385
|
+
const status = isOnline ? '🟢 Online' : '🔴 Offline';
|
|
386
|
+
const activeIndicator = device.isActive ? ' ⭐ ACTIVE' : '';
|
|
387
|
+
message += `${i + 1}. **${device.deviceName}**${activeIndicator}\n`;
|
|
388
|
+
message += ` ID: \`${device.deviceId}\`\n`;
|
|
389
|
+
message += ` Status: ${status}\n`;
|
|
390
|
+
message += ` Bound: ${new Date(device.boundAt).toLocaleString('en-US')}\n\n`;
|
|
391
|
+
}
|
|
392
|
+
message += `\n💡 Quick switch: /device <index> or /device <device-id>`;
|
|
393
|
+
message += `\n Example: /device 1 or /device switch 1`;
|
|
394
|
+
await this.replyToMessage(messageId, message);
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Resolve device identifier (ID or index) to device ID
|
|
398
|
+
* @returns Resolved device ID or null if not found
|
|
399
|
+
*/
|
|
400
|
+
resolveDeviceIdentifier(identifier, binding) {
|
|
401
|
+
// Try to parse as index (1-based)
|
|
402
|
+
const index = parseInt(identifier, 10);
|
|
403
|
+
if (!isNaN(index) && index > 0 && index <= binding.devices.length) {
|
|
404
|
+
return binding.devices[index - 1].deviceId;
|
|
405
|
+
}
|
|
406
|
+
// Treat as device ID - check if it exists
|
|
407
|
+
const device = binding.devices.find((d) => d.deviceId === identifier);
|
|
408
|
+
if (device) {
|
|
409
|
+
return device.deviceId;
|
|
410
|
+
}
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Handle /device switch <device-id-or-index>
|
|
415
|
+
* Also handles quick switch: /device <device-id-or-index>
|
|
416
|
+
*/
|
|
417
|
+
async handleDeviceSwitch(openId, messageId, identifier, binding) {
|
|
418
|
+
try {
|
|
419
|
+
// Get binding if not provided
|
|
420
|
+
const userBinding = binding || await this.bindingManager.getUserBinding(openId);
|
|
421
|
+
if (!userBinding) {
|
|
422
|
+
await this.replyToMessage(messageId, '❌ You have not bound a device yet');
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
// Resolve identifier to device ID
|
|
426
|
+
const deviceId = this.resolveDeviceIdentifier(identifier, userBinding);
|
|
427
|
+
if (!deviceId) {
|
|
428
|
+
await this.replyToMessage(messageId, `❌ Device "${identifier}" not found. Use /device to see available devices and their indices.`);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const result = await this.bindingManager.switchActiveDevice(openId, deviceId);
|
|
432
|
+
if (!result) {
|
|
433
|
+
await this.replyToMessage(messageId, '❌ Device switch failed');
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
const device = await this.bindingManager.getActiveDevice(openId);
|
|
437
|
+
if (!device) {
|
|
438
|
+
await this.replyToMessage(messageId, '❌ Failed to get active device after switch');
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
await this.replyToMessage(messageId, `✅ Switched to device: **${device.deviceName}**\n\nDevice ID: \`${device.deviceId}\``);
|
|
442
|
+
}
|
|
443
|
+
catch (error) {
|
|
444
|
+
console.error('Error switching device:', error);
|
|
445
|
+
await this.replyToMessage(messageId, '❌ Failed to switch device, please try again later');
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Handle /device unbind <device-id-or-index>
|
|
450
|
+
*/
|
|
451
|
+
async handleDeviceUnbind(openId, messageId, identifier, binding) {
|
|
452
|
+
try {
|
|
453
|
+
// Get binding if not provided
|
|
454
|
+
const userBinding = binding || await this.bindingManager.getUserBinding(openId);
|
|
455
|
+
if (!userBinding) {
|
|
456
|
+
await this.replyToMessage(messageId, '❌ You have not bound a device yet');
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
// Resolve identifier to device ID
|
|
460
|
+
const deviceId = this.resolveDeviceIdentifier(identifier, userBinding);
|
|
461
|
+
if (!deviceId) {
|
|
462
|
+
await this.replyToMessage(messageId, `❌ Device "${identifier}" not found. Use /device to see available devices and their indices.`);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
const device = userBinding.devices.find((d) => d.deviceId === deviceId);
|
|
466
|
+
if (!device) {
|
|
467
|
+
await this.replyToMessage(messageId, '❌ Device not found');
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
const wasActive = device.isActive;
|
|
471
|
+
const result = await this.bindingManager.unbindDevice(openId, deviceId);
|
|
472
|
+
if (!result) {
|
|
473
|
+
await this.replyToMessage(messageId, '❌ Failed to unbind device');
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
let responseMessage = `✅ Device **${device.deviceName}** has been unbound`;
|
|
477
|
+
// If we unbound the active device, inform about the new active device
|
|
478
|
+
if (wasActive) {
|
|
479
|
+
const newActiveDevice = await this.bindingManager.getActiveDevice(openId);
|
|
480
|
+
if (newActiveDevice) {
|
|
481
|
+
responseMessage += `\n\n📱 New active device: **${newActiveDevice.deviceName}**`;
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
responseMessage += `\n\n⚠️ No devices remaining. Use /bind to add a new device.`;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
await this.replyToMessage(messageId, responseMessage);
|
|
488
|
+
}
|
|
489
|
+
catch (error) {
|
|
490
|
+
console.error('Error unbinding device:', error);
|
|
491
|
+
await this.replyToMessage(messageId, '❌ Failed to unbind device, please try again later');
|
|
492
|
+
}
|
|
493
|
+
}
|
|
277
494
|
/**
|
|
278
495
|
* Handle help command
|
|
279
496
|
*/
|
|
@@ -281,14 +498,30 @@ class FeishuLongConnHandler {
|
|
|
281
498
|
const helpMessage = `📖 Feishu Remote Control Help
|
|
282
499
|
|
|
283
500
|
Available commands:
|
|
284
|
-
/bind <binding-code> - Bind
|
|
285
|
-
/status - View device
|
|
286
|
-
/unbind - Unbind
|
|
501
|
+
/bind <binding-code> - Bind a new device
|
|
502
|
+
/status - View all device statuses
|
|
503
|
+
/unbind - Unbind all devices
|
|
504
|
+
/device - List all your devices
|
|
505
|
+
/device list - List all your devices
|
|
506
|
+
/device switch <device-id-or-index> - Switch active device
|
|
507
|
+
/device <device-id-or-index> - Quick switch to device
|
|
508
|
+
/device unbind <device-id-or-index> - Unbind a specific device
|
|
287
509
|
/help - Show help information
|
|
288
510
|
|
|
289
|
-
Regular messages will be sent
|
|
511
|
+
Regular messages will be sent to your active device for execution.
|
|
512
|
+
|
|
513
|
+
Multi-device support:
|
|
514
|
+
• You can bind multiple devices to your account
|
|
515
|
+
• Only one device is active at a time
|
|
516
|
+
• Commands are sent to the active device
|
|
517
|
+
• Use /device or /device list to see your devices
|
|
518
|
+
• Switch by index: /device 1 or /device switch 2
|
|
519
|
+
• Switch by ID: /device <device-id>
|
|
290
520
|
|
|
291
521
|
Examples:
|
|
522
|
+
• "/device" - List all devices
|
|
523
|
+
• "/device 1" - Switch to device #1
|
|
524
|
+
• "/device switch 2" - Switch to device #2
|
|
292
525
|
• "List files in current directory"
|
|
293
526
|
• "Run tests"
|
|
294
527
|
• "View recent git commits"`;
|
|
@@ -305,14 +538,20 @@ Examples:
|
|
|
305
538
|
await this.replyToMessage(messageId, '❌ You have not bound a device yet, please send /bind <binding-code> to bind first');
|
|
306
539
|
return;
|
|
307
540
|
}
|
|
541
|
+
// Get active device
|
|
542
|
+
const activeDevice = await this.bindingManager.getActiveDevice(openId);
|
|
543
|
+
if (!activeDevice) {
|
|
544
|
+
await this.replyToMessage(messageId, '❌ No active device found. Please use /device list to view your devices');
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
308
547
|
// Check if ConnectionHub is available
|
|
309
548
|
if (!this.connectionHub) {
|
|
310
549
|
await this.replyToMessage(messageId, '❌ Server error: ConnectionHub not initialized');
|
|
311
550
|
return;
|
|
312
551
|
}
|
|
313
552
|
// Check if device is online
|
|
314
|
-
if (!this.connectionHub.isDeviceOnline(
|
|
315
|
-
await this.replyToMessage(messageId, `❌ Device ${
|
|
553
|
+
if (!this.connectionHub.isDeviceOnline(activeDevice.deviceId)) {
|
|
554
|
+
await this.replyToMessage(messageId, `❌ Device ${activeDevice.deviceName} is currently offline, please ensure the device is started and connected to the server`);
|
|
316
555
|
return;
|
|
317
556
|
}
|
|
318
557
|
// Generate message ID first
|
|
@@ -323,10 +562,10 @@ Examples:
|
|
|
323
562
|
const feishuMessageId = await this.sendStreamingStart(openId, '🤔 Processing...');
|
|
324
563
|
console.log(`[FeishuHandler] Created card ${feishuMessageId} for command ${commandMessageId}`);
|
|
325
564
|
if (this.onStartStreaming) {
|
|
326
|
-
this.onStartStreaming(commandMessageId, openId, feishuMessageId,
|
|
565
|
+
this.onStartStreaming(commandMessageId, openId, feishuMessageId, activeDevice.deviceId);
|
|
327
566
|
}
|
|
328
567
|
// Send command to device
|
|
329
|
-
const success = await this.connectionHub.sendToDevice(
|
|
568
|
+
const success = await this.connectionHub.sendToDevice(activeDevice.deviceId, {
|
|
330
569
|
type: types_1.MessageType.COMMAND,
|
|
331
570
|
messageId: commandMessageId,
|
|
332
571
|
timestamp: Date.now(),
|
|
@@ -474,6 +713,192 @@ Examples:
|
|
|
474
713
|
}
|
|
475
714
|
return chunks;
|
|
476
715
|
}
|
|
716
|
+
/**
|
|
717
|
+
* Split Card 2.0 elements into chunks based on Feishu limits
|
|
718
|
+
*
|
|
719
|
+
* Feishu Card 2.0 has two main limits:
|
|
720
|
+
* 1. Element count: Max 200 tagged nodes per card (we use 150 for safety)
|
|
721
|
+
* 2. Data size: Max 3,000,000 characters in the data field (JSON.stringify result)
|
|
722
|
+
*
|
|
723
|
+
* This function splits elements array into chunks that satisfy both limits,
|
|
724
|
+
* and adds continuation indicators between chunks for better UX.
|
|
725
|
+
*
|
|
726
|
+
* Note: Element counting uses recursive tag counting - all nodes with 'tag' property
|
|
727
|
+
* are counted, including nested components, text elements, and container children.
|
|
728
|
+
*
|
|
729
|
+
* @param elements Array of Feishu Card 2.0 elements
|
|
730
|
+
* @returns Array of element chunks, each satisfying Feishu's limits
|
|
731
|
+
*/
|
|
732
|
+
splitElementsIntoChunks(elements) {
|
|
733
|
+
// If empty or very small, return as-is
|
|
734
|
+
if (elements.length === 0) {
|
|
735
|
+
return [elements];
|
|
736
|
+
}
|
|
737
|
+
// Check if we need to split at all
|
|
738
|
+
const needsSplitting = this.checkIfElementsNeedSplitting(elements);
|
|
739
|
+
console.log(`[FeishuHandler] Elements check: count=${elements.length}, needsSplitting=${needsSplitting}`);
|
|
740
|
+
if (!needsSplitting) {
|
|
741
|
+
return [elements];
|
|
742
|
+
}
|
|
743
|
+
const chunks = [];
|
|
744
|
+
let currentChunk = [];
|
|
745
|
+
let currentChunkSize = 0;
|
|
746
|
+
let currentChunkTaggedNodes = 0;
|
|
747
|
+
// Reserve elements for continuation indicators (they add ~2 elements and ~200 bytes)
|
|
748
|
+
const continuationIndicatorSize = 200; // Approximate size of indicator element
|
|
749
|
+
const continuationIndicatorCount = 2; // Maximum 2 indicators per chunk (start + end)
|
|
750
|
+
for (let i = 0; i < elements.length; i++) {
|
|
751
|
+
const element = elements[i];
|
|
752
|
+
const elementSize = JSON.stringify(element).length;
|
|
753
|
+
const elementTaggedNodes = this.countTaggedNodes(element);
|
|
754
|
+
// Check if adding this element would exceed limits
|
|
755
|
+
// Reserve space for continuation indicators
|
|
756
|
+
const wouldExceedElementLimit = currentChunkTaggedNodes + elementTaggedNodes > (this.CARD_ELEMENT_LIMIT - continuationIndicatorCount);
|
|
757
|
+
const wouldExceedSizeLimit = currentChunkSize + elementSize + continuationIndicatorSize >
|
|
758
|
+
(this.CARD_DATA_SIZE_LIMIT - this.CARD_SIZE_BUFFER);
|
|
759
|
+
if (currentChunk.length > 0 && (wouldExceedElementLimit || wouldExceedSizeLimit)) {
|
|
760
|
+
// Start a new chunk
|
|
761
|
+
chunks.push(currentChunk);
|
|
762
|
+
console.log(`[FeishuHandler] Chunk finished: ${currentChunk.length} top-level elements, ${currentChunkTaggedNodes} tagged nodes, ${currentChunkSize} bytes`);
|
|
763
|
+
currentChunk = [];
|
|
764
|
+
currentChunkSize = 0;
|
|
765
|
+
currentChunkTaggedNodes = 0;
|
|
766
|
+
}
|
|
767
|
+
currentChunk.push(element);
|
|
768
|
+
currentChunkSize += elementSize;
|
|
769
|
+
currentChunkTaggedNodes += elementTaggedNodes;
|
|
770
|
+
}
|
|
771
|
+
// Add the last chunk if not empty
|
|
772
|
+
if (currentChunk.length > 0) {
|
|
773
|
+
chunks.push(currentChunk);
|
|
774
|
+
console.log(`[FeishuHandler] Chunk finished: ${currentChunk.length} top-level elements, ${currentChunkTaggedNodes} tagged nodes, ${currentChunkSize} bytes`);
|
|
775
|
+
}
|
|
776
|
+
console.log(`[FeishuHandler] Split ${elements.length} top-level elements into ${chunks.length} chunk(s)`);
|
|
777
|
+
chunks.forEach((chunk, i) => {
|
|
778
|
+
const chunkSize = JSON.stringify({ schema: '2.0', body: { elements: chunk } }).length;
|
|
779
|
+
const chunkTaggedNodes = chunk.reduce((sum, el) => sum + this.countTaggedNodes(el), 0);
|
|
780
|
+
console.log(`[FeishuHandler] Chunk ${i + 1}: ${chunk.length} top-level, ${chunkTaggedNodes} tagged nodes, ${chunkSize} bytes`);
|
|
781
|
+
});
|
|
782
|
+
// Add continuation indicators between chunks
|
|
783
|
+
if (chunks.length > 1) {
|
|
784
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
785
|
+
const isFirst = i === 0;
|
|
786
|
+
const isLast = i === chunks.length - 1;
|
|
787
|
+
if (!isLast) {
|
|
788
|
+
// Add "continued in next message" indicator at the end
|
|
789
|
+
chunks[i].push({
|
|
790
|
+
tag: 'markdown',
|
|
791
|
+
content: '\n\n_➡️ Continued in next message..._',
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
if (!isFirst) {
|
|
795
|
+
// Add "continued from previous message" indicator at the start
|
|
796
|
+
chunks[i].unshift({
|
|
797
|
+
tag: 'markdown',
|
|
798
|
+
content: '_⬅️ Continued from previous message..._\n\n',
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
return chunks;
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Recursively count all nodes with 'tag' property in the element tree
|
|
807
|
+
* According to Feishu docs, all nodes with 'tag' property count towards the 200 limit
|
|
808
|
+
*
|
|
809
|
+
* @param obj The object or array to count tags in
|
|
810
|
+
* @returns Total count of nodes with 'tag' property
|
|
811
|
+
*/
|
|
812
|
+
countTaggedNodes(obj) {
|
|
813
|
+
if (!obj || typeof obj !== 'object') {
|
|
814
|
+
return 0;
|
|
815
|
+
}
|
|
816
|
+
let count = 0;
|
|
817
|
+
// If this object has a 'tag' property, count it
|
|
818
|
+
if (obj.tag) {
|
|
819
|
+
count = 1;
|
|
820
|
+
}
|
|
821
|
+
// Recursively count in all properties
|
|
822
|
+
for (const key in obj) {
|
|
823
|
+
if (obj.hasOwnProperty(key)) {
|
|
824
|
+
const value = obj[key];
|
|
825
|
+
if (Array.isArray(value)) {
|
|
826
|
+
// Recursively count in array elements
|
|
827
|
+
for (const item of value) {
|
|
828
|
+
count += this.countTaggedNodes(item);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
else if (typeof value === 'object' && value !== null) {
|
|
832
|
+
// Recursively count in nested objects
|
|
833
|
+
count += this.countTaggedNodes(value);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
return count;
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Check if elements array needs to be split based on Feishu limits
|
|
841
|
+
* @param elements Array of elements to check
|
|
842
|
+
* @returns true if splitting is needed
|
|
843
|
+
*/
|
|
844
|
+
checkIfElementsNeedSplitting(elements) {
|
|
845
|
+
// Safety check: ensure elements is an array
|
|
846
|
+
if (!Array.isArray(elements)) {
|
|
847
|
+
console.error('[FeishuHandler] checkIfElementsNeedSplitting received non-array:', typeof elements);
|
|
848
|
+
return false;
|
|
849
|
+
}
|
|
850
|
+
// Check element count limit - MUST count ALL nodes with 'tag' property recursively
|
|
851
|
+
const totalTaggedNodes = elements.reduce((sum, element) => sum + this.countTaggedNodes(element), 0);
|
|
852
|
+
console.log(`[FeishuHandler] Element count check: top-level=${elements.length}, total tagged nodes=${totalTaggedNodes}, limit=${this.CARD_ELEMENT_LIMIT}`);
|
|
853
|
+
if (totalTaggedNodes > this.CARD_ELEMENT_LIMIT) {
|
|
854
|
+
console.log(`[FeishuHandler] Total tagged nodes (${totalTaggedNodes}) exceeds limit (${this.CARD_ELEMENT_LIMIT})`);
|
|
855
|
+
return true;
|
|
856
|
+
}
|
|
857
|
+
// Check data size limit
|
|
858
|
+
const cardData = {
|
|
859
|
+
schema: '2.0',
|
|
860
|
+
body: { elements },
|
|
861
|
+
};
|
|
862
|
+
const jsonSize = JSON.stringify(cardData).length;
|
|
863
|
+
const sizeLimit = this.CARD_DATA_SIZE_LIMIT - this.CARD_SIZE_BUFFER;
|
|
864
|
+
console.log(`[FeishuHandler] Card data size: ${jsonSize} bytes, limit: ${sizeLimit} bytes`);
|
|
865
|
+
// Use buffer for safety (2.9MB instead of 3MB)
|
|
866
|
+
if (jsonSize > sizeLimit) {
|
|
867
|
+
console.log(`[FeishuHandler] Data size (${jsonSize}) exceeds limit (${sizeLimit})`);
|
|
868
|
+
return true;
|
|
869
|
+
}
|
|
870
|
+
return false;
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Create a continuation card message
|
|
874
|
+
* Used when elements need to be split across multiple cards
|
|
875
|
+
*
|
|
876
|
+
* @param openId User's open_id to send the message to
|
|
877
|
+
* @param elements Array of Feishu Card 2.0 elements
|
|
878
|
+
* @returns The new message ID, or null on error
|
|
879
|
+
*/
|
|
880
|
+
async createContinuationCard(openId, elements) {
|
|
881
|
+
try {
|
|
882
|
+
const result = await this.client.im.message.create({
|
|
883
|
+
params: { receive_id_type: 'open_id' },
|
|
884
|
+
data: {
|
|
885
|
+
receive_id: openId,
|
|
886
|
+
msg_type: 'interactive',
|
|
887
|
+
content: JSON.stringify({
|
|
888
|
+
schema: '2.0',
|
|
889
|
+
body: {
|
|
890
|
+
elements,
|
|
891
|
+
},
|
|
892
|
+
}),
|
|
893
|
+
},
|
|
894
|
+
});
|
|
895
|
+
return result.data?.message_id || null;
|
|
896
|
+
}
|
|
897
|
+
catch (error) {
|
|
898
|
+
console.error('Failed to create continuation card:', error?.message || error);
|
|
899
|
+
return null;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
477
902
|
/**
|
|
478
903
|
* Update streaming message content
|
|
479
904
|
* Automatically creates new messages if content exceeds Feishu's size limit
|
|
@@ -498,19 +923,90 @@ Examples:
|
|
|
498
923
|
chain = [messageId]; // First message in chain
|
|
499
924
|
this.messageChains.set(messageId, chain);
|
|
500
925
|
}
|
|
501
|
-
//
|
|
502
|
-
|
|
926
|
+
// Split elements into chunks based on Feishu Card 2.0 limits
|
|
927
|
+
const chunks = this.splitElementsIntoChunks(elements);
|
|
928
|
+
console.log(`[FeishuHandler] Need ${chunks.length} card(s), currently have ${chain.length} card(s)`);
|
|
929
|
+
// Update the first message with the first chunk
|
|
503
930
|
await this.client.im.message.patch({
|
|
504
|
-
path: { message_id:
|
|
931
|
+
path: { message_id: chain[0] },
|
|
505
932
|
data: {
|
|
506
933
|
content: JSON.stringify({
|
|
507
934
|
schema: '2.0',
|
|
508
935
|
body: {
|
|
509
|
-
elements,
|
|
936
|
+
elements: chunks[0],
|
|
510
937
|
},
|
|
511
938
|
}),
|
|
512
939
|
},
|
|
513
940
|
});
|
|
941
|
+
// Handle continuation chunks if needed
|
|
942
|
+
if (chunks.length > 1 && openId) {
|
|
943
|
+
const existingContinuationCards = chain.slice(1);
|
|
944
|
+
const neededContinuationCards = chunks.length - 1;
|
|
945
|
+
// Update existing continuation cards
|
|
946
|
+
for (let i = 0; i < Math.min(existingContinuationCards.length, neededContinuationCards); i++) {
|
|
947
|
+
const cardMessageId = existingContinuationCards[i];
|
|
948
|
+
const chunkIndex = i + 1;
|
|
949
|
+
console.log(`[FeishuHandler] Updating existing continuation card ${i + 1}/${neededContinuationCards}`);
|
|
950
|
+
await this.client.im.message.patch({
|
|
951
|
+
path: { message_id: cardMessageId },
|
|
952
|
+
data: {
|
|
953
|
+
content: JSON.stringify({
|
|
954
|
+
schema: '2.0',
|
|
955
|
+
body: {
|
|
956
|
+
elements: chunks[chunkIndex],
|
|
957
|
+
},
|
|
958
|
+
}),
|
|
959
|
+
},
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
// Create new continuation cards if needed
|
|
963
|
+
if (neededContinuationCards > existingContinuationCards.length) {
|
|
964
|
+
console.log(`[FeishuHandler] Creating ${neededContinuationCards - existingContinuationCards.length} new continuation card(s)`);
|
|
965
|
+
for (let i = existingContinuationCards.length; i < neededContinuationCards; i++) {
|
|
966
|
+
const chunkIndex = i + 1;
|
|
967
|
+
const newMessageId = await this.createContinuationCard(openId, chunks[chunkIndex]);
|
|
968
|
+
if (newMessageId) {
|
|
969
|
+
chain.push(newMessageId);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
// Delete excess continuation cards if we need fewer cards now
|
|
974
|
+
if (existingContinuationCards.length > neededContinuationCards) {
|
|
975
|
+
const cardsToDelete = existingContinuationCards.slice(neededContinuationCards);
|
|
976
|
+
console.log(`[FeishuHandler] Deleting ${cardsToDelete.length} excess continuation card(s)`);
|
|
977
|
+
for (const cardMessageId of cardsToDelete) {
|
|
978
|
+
try {
|
|
979
|
+
await this.client.im.message.delete({
|
|
980
|
+
path: { message_id: cardMessageId },
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
catch (error) {
|
|
984
|
+
console.error(`Failed to delete continuation card ${cardMessageId}:`, error?.message || error);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
// Update chain to remove deleted cards
|
|
988
|
+
chain.length = chunks.length;
|
|
989
|
+
}
|
|
990
|
+
this.messageChains.set(messageId, chain);
|
|
991
|
+
}
|
|
992
|
+
else if (chunks.length === 1 && chain.length > 1) {
|
|
993
|
+
// No longer need continuation cards, delete all of them
|
|
994
|
+
const cardsToDelete = chain.slice(1);
|
|
995
|
+
console.log(`[FeishuHandler] No longer need continuation cards, deleting ${cardsToDelete.length} card(s)`);
|
|
996
|
+
for (const cardMessageId of cardsToDelete) {
|
|
997
|
+
try {
|
|
998
|
+
await this.client.im.message.delete({
|
|
999
|
+
path: { message_id: cardMessageId },
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
catch (error) {
|
|
1003
|
+
console.error(`Failed to delete continuation card ${cardMessageId}:`, error?.message || error);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
// Update chain to only keep the first message
|
|
1007
|
+
chain.length = 1;
|
|
1008
|
+
this.messageChains.set(messageId, chain);
|
|
1009
|
+
}
|
|
514
1010
|
return true;
|
|
515
1011
|
}
|
|
516
1012
|
catch (error) {
|
|
@@ -527,10 +1023,10 @@ Examples:
|
|
|
527
1023
|
* @param sessionAbbr Optional session abbreviation
|
|
528
1024
|
* @param openId User's open_id for creating continuation messages
|
|
529
1025
|
*/
|
|
530
|
-
async finalizeStreamingMessage(messageId, elements, sessionAbbr, openId) {
|
|
531
|
-
return this.withMessageLock(messageId, () => this._finalizeStreamingMessage(messageId, elements, sessionAbbr, openId));
|
|
1026
|
+
async finalizeStreamingMessage(messageId, elements, sessionAbbr, openId, cwd) {
|
|
1027
|
+
return this.withMessageLock(messageId, () => this._finalizeStreamingMessage(messageId, elements, sessionAbbr, openId, cwd));
|
|
532
1028
|
}
|
|
533
|
-
async _finalizeStreamingMessage(messageId, elements, sessionAbbr, openId) {
|
|
1029
|
+
async _finalizeStreamingMessage(messageId, elements, sessionAbbr, openId, cwd) {
|
|
534
1030
|
try {
|
|
535
1031
|
// Get or initialize message chain
|
|
536
1032
|
let chain = this.messageChains.get(messageId);
|
|
@@ -538,11 +1034,16 @@ Examples:
|
|
|
538
1034
|
chain = [messageId];
|
|
539
1035
|
this.messageChains.set(messageId, chain);
|
|
540
1036
|
}
|
|
541
|
-
// Build note content with session abbreviation if available
|
|
1037
|
+
// Build note content with session abbreviation and working directory if available
|
|
542
1038
|
let noteContent = '✅ Completed';
|
|
543
1039
|
if (sessionAbbr) {
|
|
544
1040
|
noteContent += ` · Session: ${sessionAbbr}`;
|
|
545
1041
|
}
|
|
1042
|
+
if (cwd) {
|
|
1043
|
+
// Format cwd to show ~ for home directory
|
|
1044
|
+
const formattedCwd = cwd.replace(process.env.HOME || '/Users', '~');
|
|
1045
|
+
noteContent += `\n📂 **Working Directory:** \`${formattedCwd}\``;
|
|
1046
|
+
}
|
|
546
1047
|
// Add completion note element as markdown
|
|
547
1048
|
const finalElements = [
|
|
548
1049
|
...elements,
|
|
@@ -551,19 +1052,88 @@ Examples:
|
|
|
551
1052
|
content: noteContent,
|
|
552
1053
|
},
|
|
553
1054
|
];
|
|
554
|
-
//
|
|
555
|
-
|
|
1055
|
+
// Split elements into chunks based on Feishu Card 2.0 limits
|
|
1056
|
+
const chunks = this.splitElementsIntoChunks(finalElements);
|
|
1057
|
+
console.log(`[FeishuHandler] Finalizing: need ${chunks.length} card(s), currently have ${chain.length} card(s)`);
|
|
1058
|
+
// Update the first message with the first chunk
|
|
556
1059
|
await this.client.im.message.patch({
|
|
557
|
-
path: { message_id:
|
|
1060
|
+
path: { message_id: chain[0] },
|
|
558
1061
|
data: {
|
|
559
1062
|
content: JSON.stringify({
|
|
560
1063
|
schema: '2.0',
|
|
561
1064
|
body: {
|
|
562
|
-
elements:
|
|
1065
|
+
elements: chunks[0],
|
|
563
1066
|
},
|
|
564
1067
|
}),
|
|
565
1068
|
},
|
|
566
1069
|
});
|
|
1070
|
+
// Handle continuation chunks if needed
|
|
1071
|
+
if (chunks.length > 1 && openId) {
|
|
1072
|
+
const existingContinuationCards = chain.slice(1);
|
|
1073
|
+
const neededContinuationCards = chunks.length - 1;
|
|
1074
|
+
// Update existing continuation cards
|
|
1075
|
+
for (let i = 0; i < Math.min(existingContinuationCards.length, neededContinuationCards); i++) {
|
|
1076
|
+
const cardMessageId = existingContinuationCards[i];
|
|
1077
|
+
const chunkIndex = i + 1;
|
|
1078
|
+
console.log(`[FeishuHandler] Finalize: Updating existing continuation card ${i + 1}/${neededContinuationCards}`);
|
|
1079
|
+
await this.client.im.message.patch({
|
|
1080
|
+
path: { message_id: cardMessageId },
|
|
1081
|
+
data: {
|
|
1082
|
+
content: JSON.stringify({
|
|
1083
|
+
schema: '2.0',
|
|
1084
|
+
body: {
|
|
1085
|
+
elements: chunks[chunkIndex],
|
|
1086
|
+
},
|
|
1087
|
+
}),
|
|
1088
|
+
},
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
// Create new continuation cards if needed
|
|
1092
|
+
if (neededContinuationCards > existingContinuationCards.length) {
|
|
1093
|
+
console.log(`[FeishuHandler] Finalize: Creating ${neededContinuationCards - existingContinuationCards.length} new continuation card(s)`);
|
|
1094
|
+
for (let i = existingContinuationCards.length; i < neededContinuationCards; i++) {
|
|
1095
|
+
const chunkIndex = i + 1;
|
|
1096
|
+
const newMessageId = await this.createContinuationCard(openId, chunks[chunkIndex]);
|
|
1097
|
+
if (newMessageId) {
|
|
1098
|
+
chain.push(newMessageId);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
// Delete excess continuation cards if we need fewer cards now
|
|
1103
|
+
if (existingContinuationCards.length > neededContinuationCards) {
|
|
1104
|
+
const cardsToDelete = existingContinuationCards.slice(neededContinuationCards);
|
|
1105
|
+
console.log(`[FeishuHandler] Finalize: Deleting ${cardsToDelete.length} excess continuation card(s)`);
|
|
1106
|
+
for (const cardMessageId of cardsToDelete) {
|
|
1107
|
+
try {
|
|
1108
|
+
await this.client.im.message.delete({
|
|
1109
|
+
path: { message_id: cardMessageId },
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
catch (error) {
|
|
1113
|
+
console.error(`Failed to delete continuation card ${cardMessageId}:`, error?.message || error);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
// Update chain to remove deleted cards
|
|
1117
|
+
chain.length = chunks.length;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
else if (chunks.length === 1 && chain.length > 1) {
|
|
1121
|
+
// No longer need continuation cards, delete all of them
|
|
1122
|
+
const cardsToDelete = chain.slice(1);
|
|
1123
|
+
console.log(`[FeishuHandler] Finalize: No longer need continuation cards, deleting ${cardsToDelete.length} card(s)`);
|
|
1124
|
+
for (const cardMessageId of cardsToDelete) {
|
|
1125
|
+
try {
|
|
1126
|
+
await this.client.im.message.delete({
|
|
1127
|
+
path: { message_id: cardMessageId },
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
catch (error) {
|
|
1131
|
+
console.error(`Failed to delete continuation card ${cardMessageId}:`, error?.message || error);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
// Update chain to only keep the first message
|
|
1135
|
+
chain.length = 1;
|
|
1136
|
+
}
|
|
567
1137
|
// Clean up message chain tracking
|
|
568
1138
|
this.messageChains.delete(messageId);
|
|
569
1139
|
this.lastProcessedLengths.delete(messageId);
|