@yu_robotics/remote-cli-router 1.0.0
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/LICENSE +21 -0
- package/README.md +115 -0
- package/bin/remote-cli-router.js +2 -0
- package/dist/binding/BindingManager.d.ts +62 -0
- package/dist/binding/BindingManager.d.ts.map +1 -0
- package/dist/binding/BindingManager.js +119 -0
- package/dist/binding/BindingManager.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +32 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/config.d.ts +19 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +206 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/start.d.ts +5 -0
- package/dist/commands/start.d.ts.map +1 -0
- package/dist/commands/start.js +65 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/status.d.ts +5 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +103 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/stop.d.ts +5 -0
- package/dist/commands/stop.d.ts.map +1 -0
- package/dist/commands/stop.js +56 -0
- package/dist/commands/stop.js.map +1 -0
- package/dist/config/ConfigManager.d.ts +46 -0
- package/dist/config/ConfigManager.d.ts.map +1 -0
- package/dist/config/ConfigManager.js +101 -0
- package/dist/config/ConfigManager.js.map +1 -0
- package/dist/feishu/FeishuClient.d.ts +46 -0
- package/dist/feishu/FeishuClient.d.ts.map +1 -0
- package/dist/feishu/FeishuClient.js +130 -0
- package/dist/feishu/FeishuClient.js.map +1 -0
- package/dist/feishu/FeishuLongConnHandler.d.ts +149 -0
- package/dist/feishu/FeishuLongConnHandler.d.ts.map +1 -0
- package/dist/feishu/FeishuLongConnHandler.js +632 -0
- package/dist/feishu/FeishuLongConnHandler.js.map +1 -0
- package/dist/server.d.ts +80 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +533 -0
- package/dist/server.js.map +1 -0
- package/dist/storage/JsonStore.d.ts +90 -0
- package/dist/storage/JsonStore.d.ts.map +1 -0
- package/dist/storage/JsonStore.js +215 -0
- package/dist/storage/JsonStore.js.map +1 -0
- package/dist/storage/MemoryStore.d.ts +69 -0
- package/dist/storage/MemoryStore.d.ts.map +1 -0
- package/dist/storage/MemoryStore.js +117 -0
- package/dist/storage/MemoryStore.js.map +1 -0
- package/dist/types/config.d.ts +53 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +31 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/index.d.ts +93 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +18 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/PidManager.d.ts +28 -0
- package/dist/utils/PidManager.d.ts.map +1 -0
- package/dist/utils/PidManager.js +90 -0
- package/dist/utils/PidManager.js.map +1 -0
- package/dist/utils/ToolFormatter.d.ts +41 -0
- package/dist/utils/ToolFormatter.d.ts.map +1 -0
- package/dist/utils/ToolFormatter.js +273 -0
- package/dist/utils/ToolFormatter.js.map +1 -0
- package/dist/websocket/ConnectionHub.d.ts +73 -0
- package/dist/websocket/ConnectionHub.d.ts.map +1 -0
- package/dist/websocket/ConnectionHub.js +177 -0
- package/dist/websocket/ConnectionHub.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.FeishuLongConnHandler = void 0;
|
|
37
|
+
const lark = __importStar(require("@larksuiteoapi/node-sdk"));
|
|
38
|
+
const uuid_1 = require("uuid");
|
|
39
|
+
const BindingManager_1 = require("../binding/BindingManager");
|
|
40
|
+
const types_1 = require("../types");
|
|
41
|
+
/**
|
|
42
|
+
* Feishu Long Connection Handler
|
|
43
|
+
* Uses Feishu SDK's WSClient for WebSocket long connection
|
|
44
|
+
*/
|
|
45
|
+
class FeishuLongConnHandler {
|
|
46
|
+
client;
|
|
47
|
+
wsClient = null;
|
|
48
|
+
bindingManager;
|
|
49
|
+
connectionHub = null;
|
|
50
|
+
appId;
|
|
51
|
+
appSecret;
|
|
52
|
+
// Feishu message size limit (4000 chars per message)
|
|
53
|
+
FEISHU_MESSAGE_LIMIT = 4000;
|
|
54
|
+
// Track message chains: messageId -> [messageId1, messageId2, ...]
|
|
55
|
+
messageChains = new Map();
|
|
56
|
+
// Track the last processed text length for each message chain
|
|
57
|
+
// This helps us only send NEW content to existing messages, preventing duplication
|
|
58
|
+
lastProcessedLengths = new Map();
|
|
59
|
+
// Per-message serialization locks to prevent concurrent updates from creating duplicates
|
|
60
|
+
messageLocks = new Map();
|
|
61
|
+
// Pattern to match tool use separator lines
|
|
62
|
+
TOOL_USE_PATTERN = /─+ TOOL USE ─+/;
|
|
63
|
+
TOOL_SEPARATOR_PATTERN = /─{20,}/;
|
|
64
|
+
constructor(config) {
|
|
65
|
+
this.appId = config.appId;
|
|
66
|
+
this.appSecret = config.appSecret;
|
|
67
|
+
this.bindingManager = new BindingManager_1.BindingManager(config.store);
|
|
68
|
+
// Initialize Feishu SDK client for API calls
|
|
69
|
+
this.client = new lark.Client({
|
|
70
|
+
appId: config.appId,
|
|
71
|
+
appSecret: config.appSecret,
|
|
72
|
+
appType: lark.AppType.SelfBuild,
|
|
73
|
+
domain: lark.Domain.Feishu,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Set ConnectionHub (called from RouterServer)
|
|
78
|
+
*/
|
|
79
|
+
setConnectionHub(hub) {
|
|
80
|
+
this.connectionHub = hub;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Callback to register streaming message with RouterServer
|
|
84
|
+
*/
|
|
85
|
+
onStartStreaming;
|
|
86
|
+
/**
|
|
87
|
+
* Set streaming start callback
|
|
88
|
+
*/
|
|
89
|
+
setOnStartStreaming(callback) {
|
|
90
|
+
this.onStartStreaming = callback;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Handle message event
|
|
94
|
+
*/
|
|
95
|
+
async handleMessageEvent(data) {
|
|
96
|
+
try {
|
|
97
|
+
const message = data.message;
|
|
98
|
+
const sender = data.sender;
|
|
99
|
+
// Skip non-text messages
|
|
100
|
+
if (message.message_type !== 'text') {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const openId = sender.sender_id.open_id;
|
|
104
|
+
const messageId = message.message_id;
|
|
105
|
+
const content = this.parseMessageContent(message);
|
|
106
|
+
console.log(`[FeishuHandler] Received message from ${openId}: ${content}, msgId=${messageId}`);
|
|
107
|
+
// Check if it's a command
|
|
108
|
+
if (this.isCommand(content)) {
|
|
109
|
+
await this.handleCommand(openId, messageId, content);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
console.log(`[FeishuHandler] Handling regular command, msgId=${messageId}`);
|
|
113
|
+
await this.handleRegularCommand(openId, messageId, content);
|
|
114
|
+
console.log(`[FeishuHandler] Finished handling regular command, msgId=${messageId}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
console.error('Error in handleMessageEvent:', error);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Parse message content
|
|
123
|
+
*/
|
|
124
|
+
parseMessageContent(message) {
|
|
125
|
+
try {
|
|
126
|
+
const content = JSON.parse(message.content);
|
|
127
|
+
return (content.text || '').trim();
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
return '';
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Check if it's a command
|
|
135
|
+
*/
|
|
136
|
+
isCommand(content) {
|
|
137
|
+
return content.startsWith('/');
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Handle command
|
|
141
|
+
*/
|
|
142
|
+
async handleCommand(openId, messageId, content) {
|
|
143
|
+
const parts = content.split(/\s+/);
|
|
144
|
+
const command = parts[0].toLowerCase();
|
|
145
|
+
switch (command) {
|
|
146
|
+
case '/bind':
|
|
147
|
+
if (parts.length >= 2) {
|
|
148
|
+
await this.handleBindCommand(openId, messageId, parts[1]);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
await this.replyToMessage(messageId, 'Please provide binding code, format: /bind ABC-123-XYZ');
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
154
|
+
case '/status':
|
|
155
|
+
await this.handleStatusCommand(openId, messageId);
|
|
156
|
+
break;
|
|
157
|
+
case '/unbind':
|
|
158
|
+
await this.handleUnbindCommand(openId, messageId);
|
|
159
|
+
break;
|
|
160
|
+
case '/help':
|
|
161
|
+
await this.handleHelpCommand(openId, messageId);
|
|
162
|
+
break;
|
|
163
|
+
default:
|
|
164
|
+
// Pass through unknown slash commands to the client
|
|
165
|
+
// This allows users to use their local Claude Code custom commands
|
|
166
|
+
await this.handleSlashCommandPassthrough(openId, messageId, content, command);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Handle slash command passthrough to client
|
|
171
|
+
* Passes unknown slash commands to the local Claude Code instance
|
|
172
|
+
*/
|
|
173
|
+
async handleSlashCommandPassthrough(openId, messageId, content, command) {
|
|
174
|
+
try {
|
|
175
|
+
// Find user binding
|
|
176
|
+
const binding = await this.bindingManager.getUserBinding(openId);
|
|
177
|
+
if (!binding) {
|
|
178
|
+
await this.replyToMessage(messageId, '❌ You have not bound a device yet, please send /bind <binding-code> to bind first');
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
// Check if ConnectionHub is available
|
|
182
|
+
if (!this.connectionHub) {
|
|
183
|
+
await this.replyToMessage(messageId, '❌ Server error: ConnectionHub not initialized');
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// Check if device is online
|
|
187
|
+
if (!this.connectionHub.isDeviceOnline(binding.deviceId)) {
|
|
188
|
+
await this.replyToMessage(messageId, `❌ Device ${binding.deviceName} is currently offline, please ensure the device is started and connected to the server`);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
console.log(`[FeishuHandler] Passing through slash command: ${command}`);
|
|
192
|
+
// Generate message ID first
|
|
193
|
+
const commandMessageId = (0, uuid_1.v4)();
|
|
194
|
+
// Register streaming session BEFORE sending command
|
|
195
|
+
const feishuMessageId = await this.sendStreamingStart(openId, `🤔 Executing ${command}...`);
|
|
196
|
+
console.log(`[FeishuHandler] Created card ${feishuMessageId} for slash command ${commandMessageId}`);
|
|
197
|
+
if (this.onStartStreaming) {
|
|
198
|
+
this.onStartStreaming(commandMessageId, openId, feishuMessageId, binding.deviceId);
|
|
199
|
+
}
|
|
200
|
+
// Send slash command to device - the client will execute it locally
|
|
201
|
+
const success = await this.connectionHub.sendToDevice(binding.deviceId, {
|
|
202
|
+
type: types_1.MessageType.COMMAND,
|
|
203
|
+
messageId: commandMessageId,
|
|
204
|
+
timestamp: Date.now(),
|
|
205
|
+
content, // Send the full command including arguments
|
|
206
|
+
openId,
|
|
207
|
+
isSlashCommand: true, // Flag to indicate this is a slash command
|
|
208
|
+
});
|
|
209
|
+
if (!success) {
|
|
210
|
+
await this.replyToMessage(messageId, '❌ Command sending failed, please try again later');
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
console.error('Error handling slash command passthrough:', error);
|
|
215
|
+
await this.replyToMessage(messageId, '❌ Error processing command, please try again later');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Handle bind command
|
|
220
|
+
*/
|
|
221
|
+
async handleBindCommand(openId, messageId, code) {
|
|
222
|
+
try {
|
|
223
|
+
// Verify binding code
|
|
224
|
+
const bindingCode = await this.bindingManager.verifyBindingCode(code);
|
|
225
|
+
if (!bindingCode) {
|
|
226
|
+
await this.replyToMessage(messageId, '❌ Binding code is invalid or expired, please generate a new one');
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
// Bind user
|
|
230
|
+
const deviceName = 'Device'; // Will be updated by client later
|
|
231
|
+
await this.bindingManager.bindUser(openId, bindingCode.deviceId, deviceName);
|
|
232
|
+
await this.replyToMessage(messageId, `✅ Binding successful!\n\nDevice ID: ${bindingCode.deviceId}\n\nYou can now control your device through Feishu.`);
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
console.error('Error binding user:', error);
|
|
236
|
+
await this.replyToMessage(messageId, '❌ Binding failed, please try again later');
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Handle status command
|
|
241
|
+
*/
|
|
242
|
+
async handleStatusCommand(openId, messageId) {
|
|
243
|
+
try {
|
|
244
|
+
const binding = await this.bindingManager.getUserBinding(openId);
|
|
245
|
+
if (!binding) {
|
|
246
|
+
await this.replyToMessage(messageId, '❌ You have not bound a device yet, please send /bind <binding-code> to bind first');
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const isOnline = this.connectionHub?.isDeviceOnline(binding.deviceId) || false;
|
|
250
|
+
const status = isOnline ? '🟢 Online' : '🔴 Offline';
|
|
251
|
+
const message = `📊 Device Status\n\nDevice Name: ${binding.deviceName}\nDevice ID: ${binding.deviceId}\nStatus: ${status}\nBinding Time: ${new Date(binding.boundAt).toLocaleString('en-US')}`;
|
|
252
|
+
await this.replyToMessage(messageId, message);
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
console.error('Error handling status command:', error);
|
|
256
|
+
await this.replyToMessage(messageId, '❌ Status query failed, please try again later');
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Handle unbind command
|
|
261
|
+
*/
|
|
262
|
+
async handleUnbindCommand(openId, messageId) {
|
|
263
|
+
try {
|
|
264
|
+
const binding = await this.bindingManager.getUserBinding(openId);
|
|
265
|
+
if (!binding) {
|
|
266
|
+
await this.replyToMessage(messageId, '❌ You have not bound a device yet');
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
await this.bindingManager.unbindUser(openId);
|
|
270
|
+
await this.replyToMessage(messageId, `✅ Device ${binding.deviceName} has been unbound`);
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
console.error('Error handling unbind command:', error);
|
|
274
|
+
await this.replyToMessage(messageId, '❌ Unbinding failed, please try again later');
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Handle help command
|
|
279
|
+
*/
|
|
280
|
+
async handleHelpCommand(openId, messageId) {
|
|
281
|
+
const helpMessage = `📖 Feishu Remote Control Help
|
|
282
|
+
|
|
283
|
+
Available commands:
|
|
284
|
+
/bind <binding-code> - Bind your device
|
|
285
|
+
/status - View device status
|
|
286
|
+
/unbind - Unbind device
|
|
287
|
+
/help - Show help information
|
|
288
|
+
|
|
289
|
+
Regular messages will be sent directly to your device for execution.
|
|
290
|
+
|
|
291
|
+
Examples:
|
|
292
|
+
• "List files in current directory"
|
|
293
|
+
• "Run tests"
|
|
294
|
+
• "View recent git commits"`;
|
|
295
|
+
await this.replyToMessage(messageId, helpMessage);
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Handle regular command (non-slash commands)
|
|
299
|
+
*/
|
|
300
|
+
async handleRegularCommand(openId, messageId, content) {
|
|
301
|
+
try {
|
|
302
|
+
// Find user binding
|
|
303
|
+
const binding = await this.bindingManager.getUserBinding(openId);
|
|
304
|
+
if (!binding) {
|
|
305
|
+
await this.replyToMessage(messageId, '❌ You have not bound a device yet, please send /bind <binding-code> to bind first');
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
// Check if ConnectionHub is available
|
|
309
|
+
if (!this.connectionHub) {
|
|
310
|
+
await this.replyToMessage(messageId, '❌ Server error: ConnectionHub not initialized');
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
// Check if device is online
|
|
314
|
+
if (!this.connectionHub.isDeviceOnline(binding.deviceId)) {
|
|
315
|
+
await this.replyToMessage(messageId, `❌ Device ${binding.deviceName} is currently offline, please ensure the device is started and connected to the server`);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
// Generate message ID first
|
|
319
|
+
const commandMessageId = (0, uuid_1.v4)();
|
|
320
|
+
console.log(`[FeishuHandler] Creating streaming card for command ${commandMessageId}`);
|
|
321
|
+
// Register streaming session BEFORE sending command to avoid race condition
|
|
322
|
+
// where stream chunks arrive before registration
|
|
323
|
+
const feishuMessageId = await this.sendStreamingStart(openId, '🤔 Processing...');
|
|
324
|
+
console.log(`[FeishuHandler] Created card ${feishuMessageId} for command ${commandMessageId}`);
|
|
325
|
+
if (this.onStartStreaming) {
|
|
326
|
+
this.onStartStreaming(commandMessageId, openId, feishuMessageId, binding.deviceId);
|
|
327
|
+
}
|
|
328
|
+
// Send command to device
|
|
329
|
+
const success = await this.connectionHub.sendToDevice(binding.deviceId, {
|
|
330
|
+
type: types_1.MessageType.COMMAND,
|
|
331
|
+
messageId: commandMessageId,
|
|
332
|
+
timestamp: Date.now(),
|
|
333
|
+
content,
|
|
334
|
+
openId
|
|
335
|
+
});
|
|
336
|
+
if (!success) {
|
|
337
|
+
// Send failed - delete the streaming session and notify user
|
|
338
|
+
if (this.onStartStreaming) {
|
|
339
|
+
// We need a way to clean up, for now just send error as new message
|
|
340
|
+
await this.replyToMessage(messageId, '❌ Command sending failed, please try again later');
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
console.error('Error handling regular command:', error);
|
|
346
|
+
await this.replyToMessage(messageId, '❌ Error processing command, please try again later');
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Reply to a message
|
|
351
|
+
*/
|
|
352
|
+
async replyToMessage(messageId, text) {
|
|
353
|
+
try {
|
|
354
|
+
await this.client.im.message.reply({
|
|
355
|
+
path: { message_id: messageId },
|
|
356
|
+
data: {
|
|
357
|
+
msg_type: 'text',
|
|
358
|
+
content: JSON.stringify({ text }),
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
console.error('Failed to reply to message:', error?.message || error);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Send message to user
|
|
368
|
+
*/
|
|
369
|
+
async sendMessage(openId, text) {
|
|
370
|
+
try {
|
|
371
|
+
await this.client.im.message.create({
|
|
372
|
+
params: { receive_id_type: 'open_id' },
|
|
373
|
+
data: {
|
|
374
|
+
receive_id: openId,
|
|
375
|
+
msg_type: 'text',
|
|
376
|
+
content: JSON.stringify({ text }),
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
console.error('Failed to send message:', error?.message || error);
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Send streaming message with card update support
|
|
388
|
+
* Returns message_id for updating
|
|
389
|
+
*/
|
|
390
|
+
async sendStreamingStart(openId, initialText = '🤔 Thinking...') {
|
|
391
|
+
console.log(`[FeishuHandler] Creating interactive card v2 for ${openId}`);
|
|
392
|
+
try {
|
|
393
|
+
const result = await this.client.im.message.create({
|
|
394
|
+
params: { receive_id_type: 'open_id' },
|
|
395
|
+
data: {
|
|
396
|
+
receive_id: openId,
|
|
397
|
+
msg_type: 'interactive',
|
|
398
|
+
content: JSON.stringify({
|
|
399
|
+
schema: '2.0',
|
|
400
|
+
body: {
|
|
401
|
+
elements: [
|
|
402
|
+
{
|
|
403
|
+
tag: 'markdown',
|
|
404
|
+
content: initialText,
|
|
405
|
+
},
|
|
406
|
+
],
|
|
407
|
+
},
|
|
408
|
+
}),
|
|
409
|
+
},
|
|
410
|
+
});
|
|
411
|
+
return result?.data?.message_id || null;
|
|
412
|
+
}
|
|
413
|
+
catch (error) {
|
|
414
|
+
console.error('Failed to send streaming start:', error?.message || error);
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Serialize async operations per messageId to prevent race conditions.
|
|
420
|
+
* Concurrent calls for the same messageId will queue and execute in order.
|
|
421
|
+
*/
|
|
422
|
+
async withMessageLock(messageId, fn) {
|
|
423
|
+
const previous = this.messageLocks.get(messageId) || Promise.resolve();
|
|
424
|
+
const current = previous.then(fn, fn); // Run fn after previous completes (even if it failed)
|
|
425
|
+
this.messageLocks.set(messageId, current);
|
|
426
|
+
try {
|
|
427
|
+
return await current;
|
|
428
|
+
}
|
|
429
|
+
finally {
|
|
430
|
+
// Clean up lock if this is still the latest operation
|
|
431
|
+
if (this.messageLocks.get(messageId) === current) {
|
|
432
|
+
this.messageLocks.delete(messageId);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Split text into chunks that fit within Feishu's message size limit
|
|
438
|
+
* Tries to split at newlines to keep context intact
|
|
439
|
+
*
|
|
440
|
+
* Reserves space for continuation indicators:
|
|
441
|
+
* - "~➡️ Continued in next message...~" (40 chars) at end of non-final chunks
|
|
442
|
+
* - "~⬅️ Continued from previous message...~" (44 chars) at start of continuation chunks
|
|
443
|
+
*/
|
|
444
|
+
splitTextIntoChunks(text, limit = this.FEISHU_MESSAGE_LIMIT) {
|
|
445
|
+
// Reserve space for continuation indicator
|
|
446
|
+
const continuationOverhead = 50; // "\n\n_➡️ Continued in next message..._"
|
|
447
|
+
const effectiveLimit = limit - continuationOverhead;
|
|
448
|
+
if (text.length <= limit) {
|
|
449
|
+
return [text];
|
|
450
|
+
}
|
|
451
|
+
const chunks = [];
|
|
452
|
+
let remainingText = text;
|
|
453
|
+
while (remainingText.length > 0) {
|
|
454
|
+
if (remainingText.length <= limit) {
|
|
455
|
+
chunks.push(remainingText);
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
// Try to find a good split point (newline, space, or just at limit)
|
|
459
|
+
let splitPoint = effectiveLimit;
|
|
460
|
+
// Look for last newline before limit
|
|
461
|
+
const lastNewline = remainingText.lastIndexOf('\n', effectiveLimit);
|
|
462
|
+
if (lastNewline > effectiveLimit * 0.7) { // Only split at newline if it's not too far back
|
|
463
|
+
splitPoint = lastNewline + 1;
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
// Look for last space before limit
|
|
467
|
+
const lastSpace = remainingText.lastIndexOf(' ', effectiveLimit);
|
|
468
|
+
if (lastSpace > effectiveLimit * 0.8) { // Only split at space if it's close to limit
|
|
469
|
+
splitPoint = lastSpace + 1;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
chunks.push(remainingText.substring(0, splitPoint));
|
|
473
|
+
remainingText = remainingText.substring(splitPoint);
|
|
474
|
+
}
|
|
475
|
+
return chunks;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Update streaming message content
|
|
479
|
+
* Automatically creates new messages if content exceeds Feishu's size limit
|
|
480
|
+
*
|
|
481
|
+
* This method uses an incremental approach to prevent content duplication:
|
|
482
|
+
* - Once a message (except the last one) is created, its content is frozen
|
|
483
|
+
* - Only the last message in the chain gets updated with new content
|
|
484
|
+
* - New messages are only created when the last message exceeds the limit
|
|
485
|
+
*
|
|
486
|
+
* @param messageId The Feishu message ID
|
|
487
|
+
* @param elements Array of Feishu Card 2.0 elements
|
|
488
|
+
* @param openId User's open_id for creating continuation messages
|
|
489
|
+
*/
|
|
490
|
+
async updateStreamingMessage(messageId, elements, openId) {
|
|
491
|
+
return this.withMessageLock(messageId, () => this._updateStreamingMessage(messageId, elements, openId));
|
|
492
|
+
}
|
|
493
|
+
async _updateStreamingMessage(messageId, elements, openId) {
|
|
494
|
+
try {
|
|
495
|
+
// Get or initialize message chain
|
|
496
|
+
let chain = this.messageChains.get(messageId);
|
|
497
|
+
if (!chain) {
|
|
498
|
+
chain = [messageId]; // First message in chain
|
|
499
|
+
this.messageChains.set(messageId, chain);
|
|
500
|
+
}
|
|
501
|
+
// For now, just update the single message with all elements
|
|
502
|
+
// TODO: In the future, implement message chaining if elements become too large
|
|
503
|
+
await this.client.im.message.patch({
|
|
504
|
+
path: { message_id: messageId },
|
|
505
|
+
data: {
|
|
506
|
+
content: JSON.stringify({
|
|
507
|
+
schema: '2.0',
|
|
508
|
+
body: {
|
|
509
|
+
elements,
|
|
510
|
+
},
|
|
511
|
+
}),
|
|
512
|
+
},
|
|
513
|
+
});
|
|
514
|
+
return true;
|
|
515
|
+
}
|
|
516
|
+
catch (error) {
|
|
517
|
+
console.error('Failed to update streaming message:', error?.message || error);
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Finalize streaming message
|
|
523
|
+
* Automatically creates new messages if content exceeds Feishu's size limit
|
|
524
|
+
*
|
|
525
|
+
* @param messageId The Feishu message ID
|
|
526
|
+
* @param elements Array of Feishu Card 2.0 elements
|
|
527
|
+
* @param sessionAbbr Optional session abbreviation
|
|
528
|
+
* @param openId User's open_id for creating continuation messages
|
|
529
|
+
*/
|
|
530
|
+
async finalizeStreamingMessage(messageId, elements, sessionAbbr, openId) {
|
|
531
|
+
return this.withMessageLock(messageId, () => this._finalizeStreamingMessage(messageId, elements, sessionAbbr, openId));
|
|
532
|
+
}
|
|
533
|
+
async _finalizeStreamingMessage(messageId, elements, sessionAbbr, openId) {
|
|
534
|
+
try {
|
|
535
|
+
// Get or initialize message chain
|
|
536
|
+
let chain = this.messageChains.get(messageId);
|
|
537
|
+
if (!chain) {
|
|
538
|
+
chain = [messageId];
|
|
539
|
+
this.messageChains.set(messageId, chain);
|
|
540
|
+
}
|
|
541
|
+
// Build note content with session abbreviation if available
|
|
542
|
+
let noteContent = '✅ Completed';
|
|
543
|
+
if (sessionAbbr) {
|
|
544
|
+
noteContent += ` · Session: ${sessionAbbr}`;
|
|
545
|
+
}
|
|
546
|
+
// Add completion note element as markdown
|
|
547
|
+
const finalElements = [
|
|
548
|
+
...elements,
|
|
549
|
+
{
|
|
550
|
+
tag: 'markdown',
|
|
551
|
+
content: noteContent,
|
|
552
|
+
},
|
|
553
|
+
];
|
|
554
|
+
// Update the message with final content
|
|
555
|
+
// TODO: In the future, implement message chaining if elements become too large
|
|
556
|
+
await this.client.im.message.patch({
|
|
557
|
+
path: { message_id: messageId },
|
|
558
|
+
data: {
|
|
559
|
+
content: JSON.stringify({
|
|
560
|
+
schema: '2.0',
|
|
561
|
+
body: {
|
|
562
|
+
elements: finalElements,
|
|
563
|
+
},
|
|
564
|
+
}),
|
|
565
|
+
},
|
|
566
|
+
});
|
|
567
|
+
// Clean up message chain tracking
|
|
568
|
+
this.messageChains.delete(messageId);
|
|
569
|
+
this.lastProcessedLengths.delete(messageId);
|
|
570
|
+
return true;
|
|
571
|
+
}
|
|
572
|
+
catch (error) {
|
|
573
|
+
console.error('Failed to finalize streaming message:', error?.message || error);
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Start the Feishu WebSocket long connection
|
|
579
|
+
*/
|
|
580
|
+
async start() {
|
|
581
|
+
try {
|
|
582
|
+
console.log('Starting Feishu WebSocket long connection...');
|
|
583
|
+
// Create WSClient with event dispatcher
|
|
584
|
+
this.wsClient = new lark.WSClient({
|
|
585
|
+
appId: this.appId,
|
|
586
|
+
appSecret: this.appSecret,
|
|
587
|
+
loggerLevel: lark.LoggerLevel.info,
|
|
588
|
+
});
|
|
589
|
+
// Start WebSocket client with event handler
|
|
590
|
+
await this.wsClient.start({
|
|
591
|
+
eventDispatcher: new lark.EventDispatcher({}).register({
|
|
592
|
+
'im.message.receive_v1': async (data) => {
|
|
593
|
+
await this.handleMessageEvent(data);
|
|
594
|
+
}
|
|
595
|
+
})
|
|
596
|
+
});
|
|
597
|
+
console.log('✅ Feishu WebSocket long connection established');
|
|
598
|
+
console.log(' Listening for messages from Feishu...');
|
|
599
|
+
}
|
|
600
|
+
catch (error) {
|
|
601
|
+
console.error('Failed to start Feishu WebSocket connection:', error);
|
|
602
|
+
throw error;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Stop the Feishu connection
|
|
607
|
+
*/
|
|
608
|
+
async stop() {
|
|
609
|
+
try {
|
|
610
|
+
console.log('Stopping Feishu WebSocket connection...');
|
|
611
|
+
// Close WebSocket connection
|
|
612
|
+
if (this.wsClient) {
|
|
613
|
+
// The SDK doesn't provide a stop method, but closing the instance should work
|
|
614
|
+
this.wsClient = null;
|
|
615
|
+
}
|
|
616
|
+
await this.bindingManager.close();
|
|
617
|
+
console.log('✅ Feishu WebSocket connection stopped');
|
|
618
|
+
}
|
|
619
|
+
catch (error) {
|
|
620
|
+
console.error('Error stopping Feishu WebSocket connection:', error);
|
|
621
|
+
throw error;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Get BindingManager instance
|
|
626
|
+
*/
|
|
627
|
+
getBindingManager() {
|
|
628
|
+
return this.bindingManager;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
exports.FeishuLongConnHandler = FeishuLongConnHandler;
|
|
632
|
+
//# sourceMappingURL=FeishuLongConnHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FeishuLongConnHandler.js","sourceRoot":"","sources":["../../src/feishu/FeishuLongConnHandler.ts"],"names":[],"mappings":"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"}
|