freeform-modeling-mcp 1.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +61 -0
  2. package/index.cjs +21 -0
  3. package/package.json +48 -0
  4. package/src/cli.ts +47 -0
  5. package/src/commands/start.ts +88 -0
  6. package/src/commands/status.ts +81 -0
  7. package/src/dev.ts +24 -0
  8. package/src/lib/fileproxy/index.ts +505 -0
  9. package/src/lib/fileproxy/proxy-server.js +347 -0
  10. package/src/lib/gltf2obj/compress.js +33 -0
  11. package/src/lib/gltf2obj/engine/core/core.js +34 -0
  12. package/src/lib/gltf2obj/engine/core/eventnotifier.js +39 -0
  13. package/src/lib/gltf2obj/engine/core/taskrunner.js +88 -0
  14. package/src/lib/gltf2obj/engine/export/exporter.js +38 -0
  15. package/src/lib/gltf2obj/engine/export/exporterbase.js +86 -0
  16. package/src/lib/gltf2obj/engine/export/exportermodel.js +117 -0
  17. package/src/lib/gltf2obj/engine/export/exporterobj.js +147 -0
  18. package/src/lib/gltf2obj/engine/geometry/box3d.js +60 -0
  19. package/src/lib/gltf2obj/engine/geometry/coord2d.js +35 -0
  20. package/src/lib/gltf2obj/engine/geometry/coord3d.js +126 -0
  21. package/src/lib/gltf2obj/engine/geometry/coord4d.js +15 -0
  22. package/src/lib/gltf2obj/engine/geometry/geometry.js +56 -0
  23. package/src/lib/gltf2obj/engine/geometry/matrix.js +440 -0
  24. package/src/lib/gltf2obj/engine/geometry/octree.js +160 -0
  25. package/src/lib/gltf2obj/engine/geometry/quaternion.js +83 -0
  26. package/src/lib/gltf2obj/engine/geometry/transformation.js +63 -0
  27. package/src/lib/gltf2obj/engine/geometry/tween.js +31 -0
  28. package/src/lib/gltf2obj/engine/import/importer.js +270 -0
  29. package/src/lib/gltf2obj/engine/import/importerbase.js +115 -0
  30. package/src/lib/gltf2obj/engine/import/importerfiles.js +139 -0
  31. package/src/lib/gltf2obj/engine/import/importergltf.js +1045 -0
  32. package/src/lib/gltf2obj/engine/import/importerutils.js +102 -0
  33. package/src/lib/gltf2obj/engine/io/binaryreader.js +93 -0
  34. package/src/lib/gltf2obj/engine/io/binarywriter.js +92 -0
  35. package/src/lib/gltf2obj/engine/io/bufferutils.js +85 -0
  36. package/src/lib/gltf2obj/engine/io/externallibs.js +42 -0
  37. package/src/lib/gltf2obj/engine/io/fileutils.js +120 -0
  38. package/src/lib/gltf2obj/engine/io/textwriter.js +41 -0
  39. package/src/lib/gltf2obj/engine/main.js +19 -0
  40. package/src/lib/gltf2obj/engine/model/color.js +130 -0
  41. package/src/lib/gltf2obj/engine/model/generator.js +433 -0
  42. package/src/lib/gltf2obj/engine/model/material.js +243 -0
  43. package/src/lib/gltf2obj/engine/model/mesh.js +173 -0
  44. package/src/lib/gltf2obj/engine/model/meshbuffer.js +233 -0
  45. package/src/lib/gltf2obj/engine/model/meshinstance.js +128 -0
  46. package/src/lib/gltf2obj/engine/model/meshutils.js +64 -0
  47. package/src/lib/gltf2obj/engine/model/model.js +195 -0
  48. package/src/lib/gltf2obj/engine/model/modelfinalization.js +377 -0
  49. package/src/lib/gltf2obj/engine/model/modelutils.js +117 -0
  50. package/src/lib/gltf2obj/engine/model/node.js +178 -0
  51. package/src/lib/gltf2obj/engine/model/object.js +90 -0
  52. package/src/lib/gltf2obj/engine/model/property.js +86 -0
  53. package/src/lib/gltf2obj/engine/model/quantities.js +46 -0
  54. package/src/lib/gltf2obj/engine/model/topology.js +139 -0
  55. package/src/lib/gltf2obj/engine/model/triangle.js +99 -0
  56. package/src/lib/gltf2obj/engine/threejs/threemodelloader.js +85 -0
  57. package/src/lib/gltf2obj/handler.js +63 -0
  58. package/src/lib/gltf2obj/index.js +47 -0
  59. package/src/lib/logger.ts +304 -0
  60. package/src/lib/print.ts +37 -0
  61. package/src/lib/utils.ts +34 -0
  62. package/src/lib/wsbridge/bridge.ts +639 -0
  63. package/src/lib/wsbridge/lock.ts +258 -0
  64. package/src/llmClient.ts +245 -0
  65. package/src/prompt.ts +187 -0
  66. package/src/server.ts +125 -0
  67. package/src/tools/assets-tool.ts +314 -0
  68. package/src/tools/basic-tools.ts +531 -0
  69. package/src/tools/index.ts +2 -0
  70. package/src/tools/material-tools.ts +88 -0
  71. package/src/tools/modeling/auxiliary-curve-tool.ts +44 -0
  72. package/src/tools/modeling/find-face.ts +38 -0
  73. package/src/tools/modeling/sweep-tool.ts +60 -0
  74. package/src/tools/modeling/utils.ts +181 -0
  75. package/src/tools/modeling-tools.ts +135 -0
  76. package/src/tools/screenshot-tools.ts +107 -0
  77. package/src/tools/tools-info.ts +107 -0
  78. package/src/tools/tripo3d-tools.ts +496 -0
  79. package/src/types/index.ts +25 -0
  80. package/src/types/tripo3d.ts +38 -0
  81. package/tsconfig.json +22 -0
@@ -0,0 +1,639 @@
1
+ // src/websocket-bridge.ts
2
+ import WebSocket, { WebSocketServer } from 'ws';
3
+ import { EventEmitter } from 'events';
4
+ import { v4 as uuidv4 } from 'uuid';
5
+ import * as http from 'http';
6
+ import { BridgeLockManager, getActiveBridges } from './lock';
7
+ import logger from '../logger';
8
+
9
+ // 客户端类型
10
+ export enum ClientType {
11
+ MCP = 'mcp',
12
+ KOOMASTER = 'koomaster'
13
+ }
14
+
15
+ // 定义命令接口
16
+ interface Command {
17
+ id: string;
18
+ type: string;
19
+ params: Record<string, any>;
20
+ mcpClientId?: string; // 记录发送命令的MCP客户端ID
21
+ }
22
+
23
+ // 定义响应接口
24
+ interface CommandResponse {
25
+ id: string;
26
+ status: 'success' | 'error';
27
+ result?: any;
28
+ message?: string;
29
+ }
30
+
31
+ // 客户端信息接口
32
+ interface ClientInfo {
33
+ id: string;
34
+ type: ClientType;
35
+ lastActivity: number;
36
+ }
37
+
38
+ // Bridge状态
39
+ export enum BridgeStatus {
40
+ STOPPED = 'stopped',
41
+ RUNNING = 'running',
42
+ USING_EXISTING = 'using_existing'
43
+ }
44
+
45
+ // WebSocketBridge类 - 充当MCP和Koomaster之间的通信桥梁
46
+ export class WebSocketBridge extends EventEmitter {
47
+ private server: WebSocketServer | null = null;
48
+ private httpServer: http.Server | null = null;
49
+ private clients: Map<WebSocket, ClientInfo> = new Map();
50
+ private pendingCommands: Map<string, {
51
+ mcpClient: WebSocket,
52
+ resolve: (value: any) => void,
53
+ reject: (reason: any) => void,
54
+ timeout: NodeJS.Timeout
55
+ }> = new Map();
56
+ private commandTimeout: number = 120000; // 120秒超时
57
+ private pingInterval: number = 30000; // 30秒心跳间隔
58
+ private pingTimer: NodeJS.Timeout | null = null;
59
+ private lockManager: BridgeLockManager | null = null;
60
+ private static instance: WebSocketBridge | null = null;
61
+ private _status: BridgeStatus = BridgeStatus.STOPPED;
62
+
63
+ // // 单例方法
64
+ // public static async getInstance(port: number = 8765): Promise<WebSocketBridge> {
65
+ // if (!WebSocketBridge.instance) {
66
+ // WebSocketBridge.instance = new WebSocketBridge(port);
67
+ // await WebSocketBridge.instance.initialize();
68
+ // }
69
+ // return WebSocketBridge.instance;
70
+ // }
71
+
72
+ constructor(private port: number = 8765) {
73
+ super();
74
+ }
75
+
76
+ // 初始化 - 检查是否有现有桥接实例存在
77
+ async initialize(): Promise<number> {
78
+ try {
79
+ // 检查是否有活跃的桥接实例
80
+ const activeBridges = getActiveBridges();
81
+
82
+ if (activeBridges.length > 0) {
83
+ // 找到一个活跃的桥接实例,使用它的端口
84
+ const existingBridge = activeBridges[0];
85
+ this.port = existingBridge.port;
86
+
87
+ logger.info(`Found existing WebSocketBridge at port ${this.port} (PID: ${existingBridge.pid})`);
88
+ logger.info(`Will use existing bridge, no need to start a new server`);
89
+
90
+ this._status = BridgeStatus.USING_EXISTING;
91
+ // 使用现有的桥接,不需要启动服务器
92
+ return;
93
+ }
94
+
95
+ // 没有找到现有实例,需要启动服务器
96
+ logger.info(`No existing WebSocketBridge found, will start a new server`);
97
+
98
+ // 创建锁管理器
99
+ this.lockManager = new BridgeLockManager(this.port);
100
+
101
+ // 使用锁管理器获取可用端口
102
+ this.port = await this.lockManager.initialize();
103
+
104
+ // 创建 HTTP 服务器
105
+ this.httpServer = http.createServer((req, res) => {
106
+ // 处理HTTP请求
107
+ if (req.url === '/bridge-status') {
108
+ res.writeHead(200, { 'Content-Type': 'application/json' });
109
+ res.end(JSON.stringify({
110
+ service: 'WebSocketBridge',
111
+ version: '1.0.0',
112
+ status: this._status,
113
+ clients: {
114
+ total: this.clients.size,
115
+ mcp: this.getClientCount(ClientType.MCP),
116
+ koomaster: this.getClientCount(ClientType.KOOMASTER)
117
+ }
118
+ }));
119
+ } else {
120
+ res.writeHead(426, { 'Content-Type': 'text/plain' });
121
+ res.end('Upgrade to WebSocket protocol required');
122
+ }
123
+ });
124
+
125
+ // 创建 WebSocket 服务器
126
+ this.server = new WebSocketServer({ server: this.httpServer });
127
+
128
+ // 设置事件处理程序
129
+ this.setupEventHandlers();
130
+
131
+ // 设置锁管理器的关闭事件处理
132
+ this.lockManager.on('shutdown', () => {
133
+ this.stop().catch(err => {
134
+ logger.error('Error shutting down WebSocketBridge:', err);
135
+ });
136
+ });
137
+
138
+ logger.info(`WebSocketBridge initialized with port ${this.port}`);
139
+ this._status = BridgeStatus.STOPPED;
140
+
141
+ await this.start();
142
+ } catch (error) {
143
+ logger.error('Failed to initialize WebSocketBridge:', error);
144
+ throw error;
145
+ }
146
+ }
147
+
148
+ // 启动桥接服务器
149
+ protected async start(): Promise<void> {
150
+ // 如果使用现有桥接,则不需要启动
151
+ if (this._status === BridgeStatus.USING_EXISTING) {
152
+ logger.info(`Using existing WebSocketBridge at port ${this.port}, no need to start`);
153
+ return;
154
+ }
155
+
156
+ // 如果已经运行,也不需要启动
157
+ if (this._status === BridgeStatus.RUNNING) {
158
+ logger.info('WebSocketBridge is already running');
159
+ return;
160
+ }
161
+
162
+ // 确保服务器已初始化
163
+ if (!this.httpServer || !this.server) {
164
+ throw new Error('Cannot start: HTTP server not initialized');
165
+ }
166
+
167
+ // 启动服务器
168
+ return new Promise<void>((resolve, reject) => {
169
+ try {
170
+ this.httpServer!.on('listening', () => {
171
+ logger.info(`WebSocketBridge server listening on port ${this.port}`);
172
+ this.startPingTimer();
173
+ this._status = BridgeStatus.RUNNING;
174
+ resolve();
175
+ });
176
+
177
+ this.httpServer!.on('error', (err: any) => {
178
+ if (err.code === 'EADDRINUSE') {
179
+ logger.error(`Port ${this.port} is already in use, please restart the application`);
180
+ reject(new Error(`Port ${this.port} is already in use`));
181
+ } else {
182
+ logger.error(`HTTP server error: ${err.message}`);
183
+ reject(err);
184
+ }
185
+ });
186
+
187
+ // 启动 HTTP 服务器
188
+ this.httpServer!.listen(this.port);
189
+ } catch (error) {
190
+ logger.error('Failed to start WebSocketBridge:', error);
191
+ reject(error);
192
+ }
193
+ });
194
+ }
195
+
196
+ // 停止桥接服务器
197
+ public async stop(): Promise<void> {
198
+ // 如果使用现有桥接,则不需要停止
199
+ if (this._status === BridgeStatus.USING_EXISTING) {
200
+ logger.info('Using existing WebSocketBridge, nothing to stop');
201
+ return;
202
+ }
203
+
204
+ // 如果已经停止,也不需要停止
205
+ if (this._status === BridgeStatus.STOPPED) {
206
+ logger.info('WebSocketBridge is already stopped');
207
+ return;
208
+ }
209
+
210
+ return new Promise<void>((resolve, reject) => {
211
+ // 清理心跳计时器
212
+ if (this.pingTimer) {
213
+ clearInterval(this.pingTimer);
214
+ this.pingTimer = null;
215
+ }
216
+
217
+ // 清理所有挂起的命令
218
+ for (const { resolve, reject, timeout } of this.pendingCommands.values()) {
219
+ clearTimeout(timeout);
220
+ reject(new Error('Bridge shutting down'));
221
+ }
222
+ this.pendingCommands.clear();
223
+
224
+ // 关闭所有客户端连接
225
+ for (const client of this.clients.keys()) {
226
+ client.close(1000, 'Bridge shutting down');
227
+ }
228
+
229
+ // 释放锁
230
+ if (this.lockManager) {
231
+ this.lockManager.release();
232
+ }
233
+
234
+ // 关闭WebSocket服务器和HTTP服务器
235
+ const closeServer = () => {
236
+ if (this.httpServer) {
237
+ this.httpServer.close((err) => {
238
+ if (err) {
239
+ logger.error('Error closing HTTP server:', err);
240
+ reject(err);
241
+ } else {
242
+ logger.info('WebSocketBridge server closed');
243
+ WebSocketBridge.instance = null; // 重置单例
244
+ this._status = BridgeStatus.STOPPED;
245
+ resolve();
246
+ }
247
+ });
248
+ } else {
249
+ this._status = BridgeStatus.STOPPED;
250
+ resolve();
251
+ }
252
+ };
253
+
254
+ // 如果WebSocket服务器存在,先关闭它
255
+ if (this.server) {
256
+ this.server.close((err) => {
257
+ if (err) {
258
+ logger.error('Error closing WebSocket server:', err);
259
+ }
260
+ closeServer();
261
+ });
262
+ } else {
263
+ closeServer();
264
+ }
265
+ });
266
+ }
267
+
268
+ // 获取当前连接的客户端数量
269
+ public getClientCount(type?: ClientType): number {
270
+ if (this._status !== BridgeStatus.RUNNING) {
271
+ return 0;
272
+ }
273
+
274
+ if (!type) {
275
+ return this.clients.size;
276
+ }
277
+
278
+ let count = 0;
279
+ for (const clientInfo of this.clients.values()) {
280
+ if (clientInfo.type === type) {
281
+ count++;
282
+ }
283
+ }
284
+ return count;
285
+ }
286
+
287
+ // 检查是否有特定类型的客户端连接
288
+ public hasConnectedClients(type?: ClientType): boolean {
289
+ if (this._status !== BridgeStatus.RUNNING) {
290
+ return false;
291
+ }
292
+
293
+ if (!type) {
294
+ return this.clients.size > 0;
295
+ }
296
+
297
+ for (const clientInfo of this.clients.values()) {
298
+ if (clientInfo.type === type) {
299
+ return true;
300
+ }
301
+ }
302
+ return false;
303
+ }
304
+
305
+ // 获取桥接状态
306
+ public getStatus(): BridgeStatus {
307
+ return this._status;
308
+ }
309
+
310
+ // 获取桥接端口
311
+ public getPort(): number {
312
+ return this.port;
313
+ }
314
+
315
+ // 私有方法:设置事件处理程序
316
+ private setupEventHandlers(): void {
317
+ if (!this.server) return;
318
+
319
+ // 处理新连接
320
+ this.server.on('connection', (socket, request) => {
321
+ const clientId = uuidv4();
322
+ logger.info(`New client connected (ID: ${clientId}), IP: ${request.socket.remoteAddress}`);
323
+
324
+ // 等待客户端标识自己的类型
325
+ socket.once('message', (data) => {
326
+ try {
327
+ const message = JSON.parse(data.toString());
328
+
329
+ // 识别客户端类型
330
+ if (message.type === 'register') {
331
+ const clientType = message.clientType === ClientType.MCP
332
+ ? ClientType.MCP
333
+ : ClientType.KOOMASTER;
334
+
335
+ // 存储客户端信息
336
+ this.clients.set(socket, {
337
+ id: clientId,
338
+ type: clientType,
339
+ lastActivity: Date.now()
340
+ });
341
+
342
+ // 发送欢迎消息
343
+ socket.send(JSON.stringify({
344
+ type: 'welcome',
345
+ message: `Connected to WebSocketBridge as ${clientType}`,
346
+ clientId
347
+ }));
348
+
349
+ // 通知客户端连接事件
350
+ this.emit('clientConnected', { id: clientId, type: clientType });
351
+
352
+ logger.info(`Client ${clientId} registered as ${clientType}`);
353
+
354
+ // 设置消息处理
355
+ this.setupMessageHandler(socket, clientId, clientType);
356
+ } else {
357
+ // 未注册的客户端
358
+ socket.send(JSON.stringify({
359
+ type: 'error',
360
+ message: 'Please register with clientType first'
361
+ }));
362
+ socket.close(4000, 'Registration required');
363
+ }
364
+ } catch (error) {
365
+ logger.error('Error processing registration:', error);
366
+ socket.close(4000, 'Invalid registration');
367
+ }
368
+ });
369
+
370
+ // 处理关闭
371
+ socket.on('close', (code, reason) => {
372
+ const clientInfo = this.clients.get(socket);
373
+ if (clientInfo) {
374
+ logger.info(`Client ${clientInfo.id} (${clientInfo.type}) disconnected (Code: ${code}, Reason: ${reason})`);
375
+ this.clients.delete(socket);
376
+ this.emit('clientDisconnected', { id: clientInfo.id, type: clientInfo.type });
377
+ }
378
+ });
379
+
380
+ // 处理错误
381
+ socket.on('error', (error) => {
382
+ const clientInfo = this.clients.get(socket);
383
+ logger.error(`WebSocket error for client ${clientInfo?.id || 'unknown'}:`, error);
384
+ });
385
+ });
386
+
387
+ // 处理服务器错误
388
+ this.server.on('error', (error) => {
389
+ logger.error('WebSocketBridge server error:', error);
390
+ this.emit('error', error);
391
+ });
392
+ }
393
+
394
+ // 为已注册的客户端设置消息处理
395
+ private setupMessageHandler(socket: WebSocket, clientId: string, clientType: ClientType): void {
396
+ socket.on('message', (data) => {
397
+ try {
398
+ // 更新最后活动时间
399
+ const clientInfo = this.clients.get(socket);
400
+ if (clientInfo) {
401
+ clientInfo.lastActivity = Date.now();
402
+ }
403
+
404
+ // 解析消息
405
+ const message = JSON.parse(data.toString());
406
+
407
+ // 根据客户端类型处理不同的消息
408
+ if (clientType === ClientType.KOOMASTER) {
409
+ this.handleKoomasterMessage(socket, message);
410
+ } else if (clientType === ClientType.MCP) {
411
+ this.handleMcpMessage(socket, message);
412
+ }
413
+ } catch (error) {
414
+ logger.error(`Error processing message from ${clientType} ${clientId}:`, error);
415
+ }
416
+ });
417
+ }
418
+
419
+ // 处理来自Koomaster客户端的消息
420
+ private handleKoomasterMessage(socket: WebSocket, message: any): void {
421
+ const clientInfo = this.clients.get(socket);
422
+
423
+ // 处理响应
424
+ if (message.id && (message.status === 'success' || message.status === 'error')) {
425
+ this.handleResponse(message);
426
+ }
427
+ // 处理心跳
428
+ else if (message.type === 'pong') {
429
+ logger.info(`Received pong from Koomaster client ${clientInfo?.id}`);
430
+ }
431
+ // 处理其他消息类型
432
+ else {
433
+ logger.info(`Received message from Koomaster client ${clientInfo?.id}:`, message);
434
+ }
435
+ }
436
+
437
+ // 处理来自MCP客户端的消息
438
+ private handleMcpMessage(socket: WebSocket, message: any): void {
439
+ const clientInfo = this.clients.get(socket);
440
+
441
+ // 处理心跳
442
+ if (message.type === 'pong') {
443
+ logger.info(`Received pong from MCP client ${clientInfo?.id}`);
444
+ return;
445
+ }
446
+
447
+ // 处理Koomaster状态查询
448
+ if (message.type === 'query_koomaster_status') {
449
+ logger.debug(`Received Koomaster status query from MCP client ${clientInfo?.id} with queryId: ${message.queryId}`);
450
+
451
+ const hasKoomasterClients = this.hasConnectedClients(ClientType.KOOMASTER);
452
+
453
+ // 构建响应
454
+ const response = {
455
+ type: 'koomaster_status',
456
+ queryId: message.queryId,
457
+ connected: hasKoomasterClients
458
+ };
459
+
460
+ logger.debug(`Sending Koomaster status response: ${JSON.stringify(response)}`);
461
+
462
+ // 发送响应,包含查询ID以便客户端关联请求和响应
463
+ socket.send(JSON.stringify(response), (err) => {
464
+ if (err) {
465
+ logger.error(`Failed to send Koomaster status response: ${err.message}`);
466
+ }
467
+ });
468
+
469
+ logger.debug(`Responded to Koomaster status query from MCP client ${clientInfo?.id}: ${hasKoomasterClients}`);
470
+ return;
471
+ }
472
+ // 处理命令请求
473
+ if (message.type && message.params) {
474
+ logger.info(`Received command from MCP client ${clientInfo?.id}: ${message.type}`);
475
+
476
+ // 转发命令到Koomaster
477
+ this.forwardCommand(socket, message.type, message.params, message.id);
478
+ } else {
479
+ logger.warn(`Received invalid message from MCP client ${clientInfo?.id}:`, message);
480
+ }
481
+ }
482
+
483
+ // 转发命令从MCP到Koomaster
484
+ private forwardCommand(
485
+ mcpClient: WebSocket,
486
+ commandType: string,
487
+ params: Record<string, any> = {},
488
+ clientCommandId?: string
489
+ ): void {
490
+ // 检查MCPClient是否有效
491
+ const mcpClientInfo = this.clients.get(mcpClient);
492
+ if (!mcpClientInfo || mcpClientInfo.type !== ClientType.MCP) {
493
+ mcpClient.send(JSON.stringify({
494
+ id: clientCommandId || uuidv4(),
495
+ status: 'error',
496
+ message: 'Invalid MCP client'
497
+ }));
498
+ return;
499
+ }
500
+
501
+ // 检查是否有可用的Koomaster客户端
502
+ const koomasterClient = this.getMostRecentClientByType(ClientType.KOOMASTER);
503
+ if (!koomasterClient) {
504
+ mcpClient.send(JSON.stringify({
505
+ id: clientCommandId || uuidv4(),
506
+ status: 'error',
507
+ message: 'No Koomaster clients connected'
508
+ }));
509
+ return;
510
+ }
511
+
512
+ // 创建命令ID
513
+ const commandId = clientCommandId || uuidv4();
514
+
515
+ // 创建命令对象
516
+ const command: Command = {
517
+ id: commandId,
518
+ type: commandType,
519
+ params,
520
+ mcpClientId: mcpClientInfo.id
521
+ };
522
+
523
+ // 设置超时
524
+ const timeout = setTimeout(() => {
525
+ // 如果命令超时,从挂起命令中移除
526
+ if (this.pendingCommands.has(commandId)) {
527
+ this.pendingCommands.delete(commandId);
528
+
529
+ // 通知MCP客户端超时
530
+ mcpClient.send(JSON.stringify({
531
+ id: commandId,
532
+ status: 'error',
533
+ message: `Command ${commandType} timed out after ${this.commandTimeout}ms`
534
+ }));
535
+ }
536
+ }, this.commandTimeout);
537
+
538
+ // 存储挂起的命令
539
+ this.pendingCommands.set(commandId, {
540
+ mcpClient,
541
+ resolve: () => { }, // 这些函数在这个简化版本中不再使用
542
+ reject: () => { },
543
+ timeout
544
+ });
545
+
546
+ // 发送命令到Koomaster客户端
547
+ koomasterClient.send(JSON.stringify(command), (err) => {
548
+ if (err) {
549
+ clearTimeout(timeout);
550
+ this.pendingCommands.delete(commandId);
551
+
552
+ // 通知MCP客户端发送失败
553
+ mcpClient.send(JSON.stringify({
554
+ id: commandId,
555
+ status: 'error',
556
+ message: `Failed to forward command: ${err.message}`
557
+ }));
558
+ } else {
559
+ logger.info(`Forwarded command ${commandType} (ID: ${commandId}) from MCP client ${mcpClientInfo.id} to Koomaster client`);
560
+ }
561
+ });
562
+ }
563
+
564
+ // 私有方法:处理来自 Koomaster 的响应
565
+ private handleResponse(response: CommandResponse): void {
566
+ // 查找挂起的命令
567
+ const pendingCommand = this.pendingCommands.get(response.id);
568
+ if (pendingCommand) {
569
+ // 清除超时
570
+ clearTimeout(pendingCommand.timeout);
571
+ // 从挂起命令中移除
572
+ this.pendingCommands.delete(response.id);
573
+
574
+ // 获取MCP客户端
575
+ const mcpClient = pendingCommand.mcpClient;
576
+ const mcpClientInfo = this.clients.get(mcpClient);
577
+
578
+ if (mcpClientInfo && mcpClientInfo.type === ClientType.MCP) {
579
+ // 将响应发送回MCP客户端
580
+ mcpClient.send(JSON.stringify(response), (err) => {
581
+ if (err) {
582
+ logger.error(`Failed to send response to MCP client ${mcpClientInfo.id}:`, err);
583
+ }
584
+ });
585
+
586
+ logger.info(`Forwarded response for command ${response.id} to MCP client ${mcpClientInfo.id}`);
587
+ }
588
+ } else {
589
+ logger.warn(`Received response for unknown command ID: ${response.id}`);
590
+ }
591
+ }
592
+
593
+ // 私有方法:获取特定类型的最近活动客户端
594
+ private getMostRecentClientByType(type: ClientType): WebSocket | null {
595
+ let mostRecent: WebSocket | null = null;
596
+ let lastActivity = 0;
597
+
598
+ for (const [client, info] of this.clients.entries()) {
599
+ if (info.type === type && info.lastActivity > lastActivity) {
600
+ mostRecent = client;
601
+ lastActivity = info.lastActivity;
602
+ }
603
+ }
604
+
605
+ return mostRecent;
606
+ }
607
+
608
+ // 私有方法:启动心跳计时器
609
+ private startPingTimer(): void {
610
+ if (this.pingTimer) {
611
+ clearInterval(this.pingTimer);
612
+ }
613
+ this.pingTimer = setInterval(() => {
614
+ this.pingClients();
615
+ }, this.pingInterval);
616
+ }
617
+
618
+ // 私有方法:发送心跳到所有客户端
619
+ private pingClients(): void {
620
+ const now = Date.now();
621
+ const timeoutThreshold = now - (this.pingInterval * 2);
622
+ // 检查和移除超时的客户端
623
+ for (const [client, info] of this.clients.entries()) {
624
+ if (info.lastActivity < timeoutThreshold) {
625
+ logger.warn(`Client ${info.id} (${info.type}) timed out, closing connection`);
626
+ client.close(1000, 'Connection timed out');
627
+ this.clients.delete(client);
628
+ this.emit('clientDisconnected', { id: info.id, type: info.type });
629
+ continue;
630
+ }
631
+ // 发送 ping
632
+ try {
633
+ client.send(JSON.stringify({ type: 'ping', timestamp: now }));
634
+ } catch (error) {
635
+ logger.error(`Error sending ping to client ${info.id}:`, error);
636
+ }
637
+ }
638
+ }
639
+ }